Have you ever wondered "what if a website worked like a Unix terminal"? With ClIte, now you know.
Find a file
2023-11-26 19:54:37 +10:00
clite shell history and tabfill 2023-11-26 19:54:37 +10:00
data add a readme file, mostly for git 2023-11-21 12:03:20 +10:00
index.html terminal scrolling headaches 2023-11-07 21:11:28 +10:00
readme.txt version bump, documentation 2023-11-26 15:13:04 +10:00

Welcome to CLIte (pronounced like 'site' with an added L, or like 'slight').

CLIte is an experimental javascript project based on answering the
 question no one asked: "What if a website worked like a Unix Terminal?"

CLIte is licensed under the GNU Affero General Public License, V3.
 Though this may change.

A functioning test instance of CLIte is available at
	http://www.ltmnet.com/clite/index.html
CLite has also been designed, with some work-arounds and bugs, to work
 locally without a web server. Just download or clone the git repository
 locally and open index.html in your browser.

The following commands are currently available in the CLIte shell:

help	- a simple help command for new users, gives a basic introduction
	to "What do I do now?"

ls	- list directory contents, accepts the -l argument for showing
	output in long form, with more detail.

cat	- prints files to either the terminal, or with the -d argument
	to the local device (downloads the file)

touch	- create a new file, or load a file's remote data

less	- simple text file viewer, up and down arrows will scroll the
	text, q or escape to exit

file	- determines the file type

reboot	- reboots the system, this will force-reload all javascript, so
	helps get around issues with browser cache

uname	- prints system information


Under Development:
view	- file viewer, should display files something like the way a
	text mode browser would, so is for actually viewing the website
	content
edit	- text file editor, will probably work similar to nano, unless
	it ends up being a vi clone

Running any command with the argument -? will give you help for that
 program.

The shell supports argument in "quotes" and 'single quotes', as well as
  $VARIABLES (environment variables include PATH, USER, PWD, and HOME).
 Support for `command substitution` is in progress.
 Some tabfill support is functional, but more work is needed to tabfill
  paths.
 Shell paths support tilde (~) for the user's home directory, as well as
  dot (.) and dot dot (..) for current and parent directories. The
  asterisk (*) wildcard is not currently supported
 The shell maintains a command history, use up and down arrows to access.
 Piping and io redirects are not currently supported, yet.
 The following shell builtin commands or macros are available:

clear	- clears the terminal window
cd	- changes the present working directory
pwd	- prints the present working directory (equivalent to `echo $PWD')
echo	- prints text or variables
which	- prints the full path of a command: `which ls' - > "ls is /bin/ls"
type	- similar to which, but also tells if a command is a shell builtin
whoami	- prints the current username (equivalent to `echo $USER')
export	- without arguments: will print all environment variables and their
		values.
	- with argument: allows an environment variable to be changed or
		created. `export FOO=bar'


Command details and API:

Commands are currently all contained in clite/commands.js and are loaded in
 at runtime. Each command looks something like:


clite.commands.load('name',function(args,env,io) {
	io.write("Hello World!");
	return 0;
}

The load function is only available during boot time, and will:
 1. load the program into the vfs at /bin/name
 2. load the source of the program into the vfs at /usr/src/name.js

The function in the second argument is roughly equivalent to main() in C.
 This function takes 3 arguments:
 'args' is equivalent to argv in C, being an array of strings containing
  the command line arguments, args[0] is the command itself, args.length
  is equivalent to argc in C.
 'env' contains the current environment variables: env.PWD contains the
  present working directory, and so on.
 'io' contains file descriptors for accessing standard input, output,
  and error, as well as a method for loading in libraries:

 io.stdout
	file descriptor for standard output

 io.stderr
	file descriptor for standard error

 io.stdin
	file descriptor for standard input

	Note that writting to stdout or stderr, when it is a tty, will
	 currently always print that output as a line, with a newline
	 appended. This may change in the future.

 io.exit(value) exits the program, equivalent to the C exit() function.
  A program can also be exited by returning a non-null value from the 'main'
  function.

 io.include('name') loads a library into the current scope for use. See
  below for more details.


Libraries:

System libraries may be loaded into a program for use of their API using the
 io.include() function:

var stdio = io.include('stdio');

 This returns a reference to the library which can be stored in a variable as
 seen above. Calls to library functions can then be made using that reference:

stdio.open('/path/to/file');

 There is no need to name the variable the same as the library name, however
 this is considered good practice.

The libraries, and their API functions are listed below:

stdlib: io.include('stdlib')
 Provides a growing standard unix-like library.

 basename('path')
	 returns the base name of a file path:
	 '/usr/home/guest/file.txt' -> 'file.txt'

 dirname('path')
	 returns the directory name of a file path:
	 '/usr/home/guest/file.txt' -> '/usr/home/guest'

 resolvePath('path','base')
	 special function that resolves a relative path to a full path,
	 using the present working directory or 'base':
	 resolvePath('file.txt') -> '/usr/home/guest/file.txt'
	 resolvePath('file.txt','/etc') -> '/etc/file.txt'
	 resolvePath('~/../file.txt') -> '/usr/home/file.txt'

 strToArgs('string')
	 special function that splits a string into an array of arguments
	 for passing to exec(), supports quotes and so on:
	 'ls -l /var' -> ['ls','-l','var']

 uname()
	 returns an object containing system information:
	 {
		sysname:'CLIte',	// system name, always 'CLIte'
		nodename:'localhost',	// network hostname
		release:'0.1...',	// contains the current CLIte version as stored in clite.state.version
		version:'0.1...',	// same as release
		machine:navigator.userAgent // contains the browser user agent string
	}

	nodename is either the website domain, or 'localhost' if loaded
	 without a webserver. This allows programs to test if the system
	 is running locally or not (example is `cat -l' which does not
	 print unloaded files if running without a webserver).

 fork(env,io,call)
	 Creates a new process, with environment and io data passed to it.
	 Returns the pid of the new process, or 0 on failure.
	 call should be a function that accepts the env and io as
	  arguments, this is where the new process begins.

	 function newProc(env,io) {
		io.write('this is a new process!');
		io.exit(0);
	 }
	 var pid = fork(env,io,newProc);

 exec(path,args,env,io)
	 Executes a new program, replacing the current one.
	 Returns 0 on success, non-zero on failure.
	 Unlike typical unix exec(), this always returns. On success the
	  original program should do nothing more, including not exiting.

	 path is the fully resolved file path of the program to be
	  executed, such as '/bin/ls'.
	 args is the argument array created by passing a command line to
	  strToArgs, see above.
	 env and io, are the current environment and io data.

	 var command = "ls -l";
	 var args = stdlib.strToArgs(command);
	 var path = stdlib.resolvePath(args[0],'/bin');
	 var r = stdlib.exec(path,args,env,io);
	 if (r == 0)
		return;

 wait(cb)
	Calls cb(pid) once any child of the current process has exited.
	 Calls immediately if the are no child processes.
	Returns false on error.

 waitpid(pid,cb)
	Calls cb(pid) when the process with id pid has exited. Calls
	 immediately if the process does not exist.
	If pid is less than 0, functions like wait(cb)
	Returns false on error.

 waitall(cb)
	Calls cb(pid) once all child processes of the current process
	 group have exited. Calls immediately if the are no child processes.
	Returns false on error.

 getuid()
	Returns the numeric user id of the current user.

 getgid()
	Returns teh numeric group id of the current user.


stdio: io.include('stdio')
 Provides access to io functions and types for file access

 stdio.types:
  object for mapping values of stat.type:
	FT_UNKOWN:	0 Unknown file type
	FT_TEXT:	1 Plain text file
	FT_BINARY:	2 Binary file, likely a javascript function
	FT_DIR:		3 Directory
	FT_LINK:	4 Symbolic link
	FT_DEV:		5 Device
	FT_REMOTE:	6 Unloaded remote data (will change after loading)
	FT_SCRIPT:	7 Plain text file beginning with #!
	FT_IMAGE:	8 Image file, specifically a javascript Image object

 creat('path','-')
	creates a new file at path
	returns true on success

 open('path',callback,open_link)
	Open the file at path, returns a file descriptor.
	 On error returns null, and calls callback(null) if set.
	If callback is set, will call the function at callback with the
	 file descriptor, this allows remote data to be loaded for the
	 file before beginning read or write operations.
	If callback is false, open will return directly, without loading
	 remote data.
	If open_link is set and true, and 'path' is a symbolic link, the
	  returned file descriptor is for the link, not the file pointed to.

	 var fd = stdio.open('path',false); // opens the file without loading data
	 var fd = stdio.open('path',callback); // calls callback(fd) when data is loaded
	 var fd = stdio.open('path',callback,true); // as above, but will not follow a link
	 var fd = stdio.open('path'); // as a general rule, don't do this

 close(fd)
	Closes a file opened with open()

		stdio.close(fd);

 read(fd,callback)
	Reads a single character or keystroke from a file.
	callback is an optional callback function, used soley for
	 asynchronously reading from a tty.
	Returns null if there is no data to read.

	When reading from a regular file, will return a single character.
	When reading from a tty:
		Returns true on success or false on failure.
		Calls callback() and passes as an argument either:
			A full line of text as entered by the user.
			\1 (start of header) followed by a special key
			 name (such as ArrowUp).
			null if the tty cannot be read from.

	var c = stdio.read(fd);

 readLine(fd,callback)
	Reads a line from a file, up to the next newline, or end of file.
	Returns null if there is no data to read.

	When reading from a tty, functions the same as read().

	var line = stdio.readLine(fd);

 readAll(fd)
	Returns the entire content of a file.
	Returns null if there is no data to read.
	Works only on regular files (and some non-tty devices).

	var data = stdio.readAll(fd);

 write(fd,data)
	Write data to a file.
	Returns true on success.

	if (stdio.write(fd,'string')) {
		// it worked
	}else{
		// it failed
	}

 ftruncate(fd,length)
 truncate('path',length)
	Truncates a file's size to no more than length.
	Does not increase a file's size to length.
	Returns true on success.

	var result = stdio.ftruncate(fd,10);
	var result = stdio.truncate('/usr/home/guest/file.txt',10);

 seek(fd,pos)
	Moves the read/write position of an open file to pos.
	If pos is less than 0, then returns the current position without
	 changing, otherwise returns the new position.
	pos is always relative to the start of the file. When a file is
	 first opened, pos will be set to the start of the file.
	To set the position to the end of a file, first get the file size
	using stat() or fstat().

	var p = stdio.seek(fd,10);

 remove('path')
	Deletes a file or directory.
	Returns true on success.
	To delete a directory, the directory must be empty.

	if (stdio.remove('/usr/home/guest/file.txt'))
		// success!

 link('path','target')
	Creates a new symbolic link at 'path' which points to 'target'.
	'target' must exist.
	If 'path' exists, and is already a link, will update the link.
	Returns true on success.

	if (stdio.link('/usr/home/guest/logfile','/vr/logs'))
		// success!

 stat('path')
 fstat(fd)
	Returns a stat object with infomation about a file.
	Returns null on error.
	Editing the returned object does not change anything for the
	 actual file, it just means your stat object is now wrong.

	var st = stdio.stat('/usr/home/guest/file.txt');
	var st = stdio.fstat(fd);

	Stat object contents:
	st.name:  string containing the file name
	st.type:  file type identifier, see stdio.types above for more info
	st.uid:   numeric id of the file owner
	st.gid:   numeric id of the file group
	st.size:  file size, or 0 for non text files
	st.perms: the permissions string for the file, see chmod below.

 chmod('path','mode')
 fchmod(fd,'mode')
	Change a file's mode (permissions).
	Returns true on success.

	The mode string is a 9 or 10 character string describing the
	 file permissions. The optional first character describes the
	 file type, and cannot be changed. Attempting to change the first
	 character will not cause the function to fail, but only the
	 permissions will be changed.

	 10 character string: -rwxrwxrwx
	 9 character string: rwxrwxrwx

	After the option first character, the mode string is comprised of
	 3 sets of permissions for Read, Write, and eXecute, one each for
	 the user, group, and others.

	rwx		permissions for the file's owner
	   rwx		permissions for users in the file's group
	      rwx	permissions for other users

	Replacing any of the permissions with a dash '-' will remove that
	 permission from that set:

	rwxr-xr--
	The user has all permissions, the group has read and execute
	 permissions, others have only read permissions.

 isattty(fd)
	returns true if fd refers to a tty.

 fprintf(fd,format,args)
	Print formatted text to the file at fd.
	Actually formatting is a work in progress, ignore the args.
	Returns true on success.

 printf(fmt,args)
	Equivalent to fprintf(io.stdout,fmt,args);

term: io.include('term')
 Provides access to raw terminal and tty functions.

 clear()
	Clears the current terminal, equivalent to running `clear' from
	 the shell.

 opentty()
	Creates a new blank terminal (tty) with raw key events, for
	 custom displays and interactions. (The less command uses this).
	Returns a reference object for interacting with the new tty.
	Returns null on error.

	var tty = term.opentty();

 closetty(tty)
	Closes a tty created with opentty().

 ttyctrl(func,v)
	Special function for setting and accessing CLIte-specific tty
	 data. Specifically for interacting directly with the form used
	 for user data input.
	func is a string containing the intended function.
	v is the value to set.
	Returns either the requested data, or false on error.

	term.ttyctrl('iset',string);	// sets the current input value
	var txt = term.ttyctrl('iget');	// gets the current input value
	term.ttyctrl('prompt',string);	// sets the current prompt text


Example Programs:
 Due to the nature of javascript, it is not possible to simply stop half
  way through a function to wait for user input or for some remote data
  to load. Instead we have to use a callback function, which complicates
  things a little, and means there are two kinds of programs: Syncronous,
  and Asyncronous.

Syncronous Program:
 A Syncronous program runs and then exits, with no waiting for callbacks.
 As such, it looks much like a regular unix program might, and simply
  returns with an exit code.
 Here's a "Hello World" as an example:

clite.commands.load('hello',function(args,env,io) {
	io.write("Hello World!");
	return 0;
}

Asyncronous Program:
 An Asycronous program typically uses io calls to interact with data
  that may not be immediately available; such as remotely loading file
  data or reading input from a user. As such callbacks are needed to
  handle that data once it is available.
 Therefore a return null is used, which lets the system know it is an
  asyncronous program that will exit later using the io.exit() function.
 Here's a simple program that reads in a file, and prints it to stdout:

clite.commands.load('show',function(args,env,io) {
	var stdlib = io.include('stdlib');
	var stdio = io.include('stdio');

	// check there's a file to read from
	if (args.length != 2) {
		io.error('Specify a file to read');
		return 1; // not asyncronous yet, so just return
	}

	// take the argument, and get it's full path
	var file = stdlib.resolvePath(args[1]);

	// file callback function that will receive the file descriptor
	// once the file has data in it
	function fcb(fd) {
		if (!fd) {
			// print an error, exit the program, then end
			io.error('could not open file');
			io.exit(1);
			return;
		}

		// read in the whole file in one go
		var data = stdio.readAll(fd);
		// close the file
		stdio.close(fd);

		// check there's something there
		if (!data) {
			// print an error, exit the program, then end
			io.error('could not read file');
			io.exit(1);
			return;
		}

		// write to stdout
		io.write(data);

		// and exit successfully
		io.exit(0);
	}

	// open the file, and set the callback
	var fd = stdio.open(file,fcb);

	// we don't want to exit the program yet,
	// so return null to let the system know that the
	// program is asyncronous (reads user data, or loads remote data)
	return null;
}