aldos operator's manual
ortfero
the operator
aldos is the operating system of the aldan machine. it serves one
installation, one operator and one console.
- there is one console. there is no second screen and no log file.
- there is no facility for remote administration. no other party can be called on to maintain, repair or audit the installation.
- every byte on the disk is the consequence of an action taken by the operator, or of a program the operator ran.
- the operator determines which programs are present on the disk, which modules are loaded at boot, and the state the system is left in between sessions.
the operator is accountable for every byte on the disk.
the system
layers
the machine is built in layers, lowest first.
- the firmware, in the boot rom. it brings the machine up, loads the resident binary, and owns every hardware driver: the screen and its cursor, the keyboard, the disk, the host filesystem, the sound device, the network and the clock. it publishes one call table in memory at boot, and every layer above reaches hardware only through that table.
- almac, the resident binary, and the only binary there is: the language, its compiler, and the emergency prompt.
- the aldos modules: the task manager, the memory manager, the volume manager, the filesystem and the service manager. they are written in almac and loaded from the disk.
- the core library.
- the applications, and the environment.
there is no separate kernel image and no system-call interface. a
program reaches an aldos module by importing it, exactly as it reaches
core/tty or a module of the operator's own.
modules
- a module is a unit of almac code kept on the disk as a text file with the extension .alm.
- nothing is linked in advance. almac compiles a module the first time it is named in an import, and keeps it in the working image until the session ends or reset is called.
- the core and aldos modules are named the same way as any other.
- a module may define setup and teardown. setup runs as the module is loaded, after everything it imports is up. teardown runs when the image gives the module up, and modules are torn down in the reverse of the order they were loaded.
fibers
- a fiber is a single thread of execution.
- the scheduler is cooperative. a fiber runs until it yields, waits on a device, or returns. nothing takes the processor away from it.
- the environment is a fiber. a program written to run forever holds the console for as long as it runs, unless it is started as a service.
services
- a service is named background work: a fiber the system keeps running after the call that started it has returned.
- a name identifies a service, and only one service may hold a name.
- services lists them, stop ends one, restart replaces one, journal reads what they have printed.
- a service has no screen. its output goes to the journal under its own name.
- a service is started by a program through core/service. nothing on the shipped disk starts one.
the tick
- the machine takes a timer interrupt one hundred times a second.
- the tick switches no fibers.
- the tick drives the sound mixdown, the device probes that wake a fiber whose disk transfer or datagram has arrived, and the deadlines that rouse a sleeping fiber, a service running on a period among them.
faults and traps
everything runs in one address space: the aldos modules, the core
library, the operator's definitions and every program are modules of the
one almac image, and nothing isolates one from another. a program that
writes where it should not can damage anything.
- a fault raised by a line typed at the prompt unwinds through the defers, the display is given back, and the fault is reported. the operator keeps the prompt.
- a hardware trap raised by a line typed at the prompt unwinds to the emergency prompt, past the environment's defers. the environment's display and output state are left standing. reset puts them right.
- a fault in a service, or in any other fiber, is not recovered and stops the machine.
booting
booting is the sequence from power-on to the first pane. the firmware
needs no input and the operator cannot interrupt it.
- the firmware brings the machine to a usable state: it publishes its call table into memory, initialises the display processor and the shared cursor, marks the whole operating pool free in the page allocator, clears the screen, and installs the trap table.
- the firmware reads the resident binary /a/almac.bin out of the boot volume's root directory, copies it into memory, and transfers control to it. on failure it writes a diagnostic to the screen and halts.
- almac registers its primitive types and built-in operations.
- almac reads /a/rc.alm. this one read goes straight through the firmware, because the volume manager that would ordinarily perform it is among the modules the script is about to bring up. every later file operation passes through aldos.
- the script's imports pull in the core library and, beneath it, the aldos modules. each module's setup runs as the module is loaded: this is where the task manager writes its scheduler into the firmware's yield and tick slots.
- the aldos modules bring themselves up as they are first used, not in one pass. the task manager seeds the boot fiber on the first yield; the memory manager binds itself to the page allocator on the first claim; the volume manager mounts the boot volume at drive a, from the raw merefs volume at the start of the boot disk, and registers the host filesystem at drive b on the first file operation.
- the rest of rc.alm defines the shell commands and binds the entry points of the standing applications.
- the last line of rc.alm calls loom. the environment takes the display, prints its greeting into the prompt's pane, and comes up on the listing. this call does not return while the machine is working.
a successful boot prints what rc.alm chooses to say — in the shipped
script, two lines of heap figures — and the environment then covers it.
anything else printed before the environment comes up is a diagnostic
naming the step that failed.
if the boot does not reach the environment:
- a failure to load the resident binary is reported by the firmware.
- a failure inside the start-up script is reported by almac as a module name, a line number and a reason. the emergency prompt is then offered, with whatever the script defined before the bad line.
- the causes to suspect, in order: a corrupt or missing file at the named step, a damaged merefs volume, a hardware fault.
the environment
the environment is loom. it is the shell, and the only prompt a working
machine shows.
- rc.alm starts it as the last thing it does, and it does not return.
- no key leaves it.
- it draws a canvas over the console grid and holds the display until it leaves.
- a full-screen program run from it — plot, snake, golife, galaxy — takes the whole display for the length of its run and hands the environment's back when it leaves. text, pictures and music are not among them: each has a pane, and music goes on playing while another pane is up.
the emergency prompt is the almac prompt inside the resident binary. it
is reached when the environment cannot be compiled, when a fault reaches
out of it, and when reset is typed. it offers the language and nothing
else. the word loom returns to the environment.
panes
there are eight panes, one up at a time.
| key | pane | what it holds |
| ctrl+1 | the listing | the working directory |
| ctrl+2 | the editor | the documents that are open |
| ctrl+3 | the reader | one document, read and unchanged |
| ctrl+4 | the picture pane | one picture |
| ctrl+5 | the music pane | a tune or a recording, and the files beside it |
| ctrl+6 | the journal pane | what the services have written, as they write it |
| ctrl+7 | the prompt's pane | everything the prompt has said |
| ctrl+8 | the debugger pane | a session of the debugger |
- ctrl+1 to ctrl+8 bring up the pane with that number.
- ctrl+tab moves to the next pane in that order, and ctrl+shift+tab to the previous one, both round.
- a switch that jumps — a ctrl+1 to ctrl+8, a file opened from the listing with enter or shift+enter, ctrl+b, ctrl+q — is remembered, and ctrl+shift+tab takes it back, as a browser's back button does: it goes back over the switches in the order they were made, and ctrl+tab goes forward over them again. only with nothing left to take back or do again do the two keys step along the order.
- the two keys always undo each other, and the last eight switches can be taken back.
- the reader is a stop only while it holds a document, the picture pane only while it holds a picture, the music pane only while it holds a file, and the debugger pane only while there is a session. an empty one is stepped over by ctrl+tab and ctrl+shift+tab, and its number does nothing, so a machine on which nothing has been opened has four panes: the listing, the editor, the journal pane and the prompt's pane.
- the journal pane is always a stop, since there is always a journal. it is opened when the environment starts and is never put down.
- the machine comes up on the listing.
- the listing and the prompt's pane share one input line, on the last row. neither document pane has one: the editor states the document on its last row, and the reader gives every row but the first to the text. the picture pane is the whole display, and the music pane has nothing to type into.
- arriving at the listing reads the working directory again.
- running a typed line brings the prompt's pane up, whichever pane was up when the line was submitted.
- enter over a name in the listing is the exception: a viewer that ran and produced no answer gives the listing back. an answer — a value, a diagnostic, a fault — keeps the prompt's pane.
the input line
- the line holds at most 256 bytes.
- left, right, home and end move the cursor; backspace and del remove; tab inserts two spaces; a printable key inserts.
- a key with ctrl held is not the line's, and types nothing. the environment answers three of them over it: ctrl+c copies the whole line to the clipboard, ctrl+x copies it and empties the line, and ctrl+v puts what is held in at the cursor.
- only the first line of what is held is pasted here, since the line is one line. a run copied out of a document carries the newline that closes it; what arrives on the line is its text.
- with the listing up and nothing typed, the three belong to the file under the cursor instead of to the line: ctrl+c and ctrl+x take it, to be copied and to be moved, and ctrl+v puts a held file down. see the listing. with nothing held, ctrl+v is the line's as usual — and what a taken file leaves on the clipboard is its path, so that is what arrives.
- while the listing is up, all four arrows belong to the listing, and the line has no cursor movement.
- enter submits the line. with the listing up and nothing typed, enter opens the name under the cursor instead.
the listing
- the top row names the directory the listing is of. each entry follows on its own row.
- the first entry is always the parent, written as two dots. it is put there by the listing, and the entries a volume reports for the current and parent directory are dropped.
- directories come first, then files. within each kind the volume's order stands; the listing does not sort.
- a directory is written with a trailing slash.
- a file's size is written right-aligned in whole kilobytes, rounded up, in a column of the width fm/config names. the column is left off when it is set to 0, and in a rectangle too narrow to carry both a name and a number.
- the first column holds a cursor mark: a greater-than sign on the row the cursor is on.
- the second column holds the caller's mark: a star for a file open in the editor, an exclamation mark for one open with unsaved changes, and a minus for the file ctrl+x has taken.
- a listing shows at most 128 entries and at most 64 bytes of a name. a longer directory is cut; a longer name is shortened.
- a directory that cannot be read is reported under the listing.
| key | what it does |
| up, down | walk the cursor |
| page up, page down | walk the cursor by twenty |
| right | open what the cursor is on |
| left | leave for the parent directory |
| enter | with nothing typed, open what the cursor is on |
| shift+enter | open the name under the cursor in the editor |
| ctrl+c | with nothing typed, take the file under the cursor, to be copied |
| ctrl+x | with nothing typed, take it to be moved |
| ctrl+v | with nothing typed and a file held, put it down in the directory being listed |
- enter and the right arrow descend into a directory. over a file, they run the command fm/config pairs with the name's ending; a name that ends in none of them does not open.
- shift+enter opens the name in the editor whatever its ending, and whether or not the table names anything for it. a directory is descended into either way.
- the command is run exactly as if it had been typed, with the name as its argument.
- the listing follows the editor: switching to a document that lives in another directory and then turning to the listing moves the listing there. a cd typed at the prompt is not overruled by this.
ctrl+c, ctrl+x and ctrl+v carry a file from one directory to another.
- ctrl+c takes the file under the cursor to be copied; ctrl+x takes it to be moved. what is remembered is its path, so the file stays where it is until it is put down, and walking away from the directory does not lose it.
- a held file is written in the mark column: a plus for one to be copied, a minus for one to be moved.
- taking a second file forgets the first. there is one held file.
- a directory is not held, nor is the parent row, nor a drive.
- ctrl+v puts the held file into the directory being listed, under the name it already had, and puts the cursor on it.
- the run is an mv or cp line, echoed into the prompt's pane and remembered in the history like a typed one. one that does not go through — a name already there, a volume that refuses — says so in the prompt's pane and keeps the file held, so the key can be pressed again somewhere else.
- ctrl+v where the file was taken from lets it go and moves nothing.
- taking a file also puts its path on the text clipboard, so ctrl+v in the editor writes the path into the document and ctrl+v on the prompt writes it into the line. the path of a directory goes there too, and a directory is not held for anything else.
the table fm/config carries:
| ending | opened with |
| .moff | read |
| .alm | read |
| .pixl | look |
| .foto | look |
| .plot | plot |
| .echo | play |
| .song | play |
the editor
- the editor holds a ring of documents: the scratch, and as many files as edit/config names.
- slot zero is the scratch. it has no file, it is always present, and ctrl+q empties it rather than closing it.
- a document is named by the path it was opened with, made absolute against the working directory at the time it was opened.
- a file that does not exist is opened empty and written on the first ctrl+s.
- a file larger than edit/config's document_bytes does not open, and the editor says so.
- opening one more file than the ring holds takes the slot of the least recently used document that has no unsaved changes. a document with unsaved changes is never taken. when every slot has unsaved changes, nothing is opened and the editor says so.
- there is no save-as. a document that is to become a file is opened as that file.
- nothing has to be saved to leave the editor. a document stays open, with its changes in it, until it is closed.
- the top row of the pane names the documents in the ring. the bottom row states the document that is up, a star if it has unsaved changes, the cursor's line and column, and either a message or the chords.
| key | what it does |
| ctrl+s | write the document back to its file |
| ctrl+c, ctrl+x | copy or cut the selection, or the whole line the cursor is on when nothing is selected |
| ctrl+v | paste, over the selection if one stands |
| ctrl+q | close the document |
| ctrl+comma, ctrl+left | the document before this one in the ring |
| ctrl+period, ctrl+right | the document after this one in the ring |
| ctrl+f | find, from the cursor |
| ctrl+r | replace, one match at a time |
| ctrl+b | write the document and compile it |
| ctrl+tab | next pane; with shift, back |
- ctrl+q over a document with unsaved changes takes two presses. the first says so; the second drops the changes.
- a shifted motion selects, and every other key collapses the selection. the line ctrl+c and ctrl+x take when nothing is selected comes with the newline that closes it, so it pastes as a whole line. the clipboard is the machine's: what is copied out of one document pastes into another.
- ctrl+s over the scratch does nothing and says so: the scratch has no file.
- a host window manager commonly takes ctrl+left and ctrl+right for itself, in which case the guest never sees them and the two punctuation chords are the ones that work.
- ctrl+f turns the bottom row into a line holding the last search. enter selects the next match from the cursor, wrapping to the top, so ctrl+f and enter again finds the one after it. esc puts the line away.
- ctrl+r asks for what to find and then what to put in its place, each line holding its last text. each match is then selected in turn: enter replaces it, tab skips it, esc stops. the pass ends by itself when it comes round to where it began, and says how many it replaced.
everything else is the document's:
| key | what it does |
| left, right | move by one byte |
| up, down | move by one row |
| page up, page down | move by one window |
| home | to the first thing on the line, then to its left edge |
| end | to the end of the line |
| backspace, del | remove |
| tab | insert two spaces |
| enter | open a line |
a long line wraps rather than scrolling sideways, and the window follows
the cursor.
the left of the document is a gutter of line numbers, always shown. they
are source lines, so the wrapped continuation of a long line carries no
number of its own, and neither does a row below the end of the text. a
pane too narrow to carry both the numbers and the text is all text.
the reader
- the reader holds one document, read and not changed. there is no cursor in it and no key that writes a byte.
- it is brought up by enter over a name the table pairs with read, and by a read line typed at the prompt.
- the storage for the document is claimed when it is opened, sized to the file, and given back when it is closed, so an empty reader costs the machine nothing. a file larger than read/config's max_bytes is refused, and so is one the heap cannot hold; the refusal is said in the prompt's pane, which is where the line was typed.
- opening the file that is already open does not read it again: the window stays where the operator left it.
| key | what it does |
| up, down | one row |
| page up, page down | a windowful, less two rows |
| home, end | the top of the document, and its last page |
| ctrl+w | the other reading width |
| ctrl+q | put the document down |
| ctrl+tab | next pane; with shift, back |
- the reading column is narrower than the pane and centred in it. ctrl+w goes between read/config's two widths — narrow_columns, 72, for prose and wide_columns, the whole pane, for source — and keeps the place across the change. the width belongs to the operator and not to the file: it is kept across opening another document.
- the first row names the document, the source line the window begins at, and how far into the text the bottom of the window is.
- ctrl+q leaves an empty pane, which is no longer a stop, so the environment goes to the editor — what was being written beside the document.
- the reader is /a/modules/loom/read/read.
the picture pane
- the picture pane holds one picture, in either of the machine's raster formats: pixl, the lossless one, and foto, the lossy one. the first four bytes of the file say which it is.
- it is brought up by enter over a name the table pairs with look, and by a look line typed at the prompt.
- the picture is the whole display: the pane draws no text. a picture smaller than the 720x480 screen is centred, and one larger than it is refused, since the display processor neither scales nor clips a blit.
- a picture carrying its own palette is shown in it. the next pane to come up is in the console's colours again.
- the storage is claimed when a picture is opened and given back when it is closed. the file is never held whole: the front of it is kept, for the palette, and the body is read into the plane. a file that is not a picture, a picture larger than the screen, and one the heap cannot hold are refused, and the refusal is said in the prompt's pane.
| key | what it does |
| right, left | the next, and the previous, picture in the directory |
| ctrl+q | put the picture down |
| ctrl+tab | next pane; with shift, back |
- the arrows walk the files in the picture's directory that end the way it does, in the order the listing shows them. a step with nowhere to go does nothing; a step onto a file that is refused says so and keeps the picture that was up.
- ctrl+q leaves an empty pane, which is no longer a stop, so the environment goes to the listing the picture was picked from.
- the picture pane is /a/modules/loom/look/look.
the music pane
- the music pane holds one file and plays it: a recording in the echo format, or a tune in the song format over the synth. the first four bytes of the file say which it is.
- it is brought up by enter over a name the table pairs with play, and by a play line typed at the prompt.
- the music goes on playing when another pane is brought up. that is what the pane is for: listening while writing.
- the environment plays it from its own loop, looking at the music every play/config beat_ms (10 ms) while something is sounding. a line that keeps the environment busy — a compile, a full-screen program — keeps the music waiting too: a recording falls silent once its queue has run dry, a tune holds its notes, and both go on from where they were.
- the first row names the file and says how far into it the music is, against its length. the three under it are what the file says about itself: a recording's description, a tune's title and source and what it is made of. under them are the other files in the same directory that end the same way, the one playing marked with a star and the one the cursor is over with an arrow. the listing is read when a file is opened, and is the directory as it was then.
- the storage is claimed when a file is opened and given back when the pane is closed. a tune larger than 64kb, a tune with a bad line, a damaged recording, and a file the heap cannot hold are refused, and the refusal is said in the prompt's pane.
| key | what it does |
| up, down | walk the listing |
| page up, page down, home, end | walk it further |
| enter | play what the cursor is over |
| right, left | the next, and the previous, file in the listing |
| ctrl+q | stop the music and put it down |
| ctrl+tab | next pane, the music playing on |
- a file that has played out stays in the pane, stopped. enter over it plays it again; enter over the file that is still playing does nothing.
- a step with nowhere to go does nothing. a step onto a file that is refused before the music was stopped says so and keeps the music playing; one refused after it leaves an empty pane.
- there is no seeking.
- ctrl+q leaves an empty pane, which is no longer a stop, so the environment goes to the listing the file was picked from.
- the music pane is /a/modules/loom/play/play, over /a/modules/sound/song and /a/modules/sound/recording.
the journal pane
- the journal pane shows the system journal: the ring of text the services write to, each line headed by the service's name. it is always a stop: ctrl+6 reaches it, and a journal line typed at the prompt brings it up.
- it holds a copy of the ring, and takes the copy again whenever the journal has grown: every log/config refresh_ms (250 ms) while the pane is up, and on coming back to it. nothing is looked at while another pane is up.
- the window follows the tail, so new lines come in at the bottom. moving up lets go of the tail, and what is being read stays where it is while more arrives; end takes the tail up again.
- the first row says how many lines the ring has had to drop, and whether the window has let go of the tail.
| key | what it does |
| up, down | one row |
| page up, page down | a windowful, less two rows |
| home, end | the oldest line, and the tail, which it then follows |
| ctrl+tab | next pane; with shift, back |
- the journal pane is /a/modules/loom/log/log.
the prompt's pane
- the pane holds what the prompt has said, up to repl/config's screen_history_bytes. whole lines are forgotten off the front to make room.
- output from code run at the prompt lands here.
- page up and page down move the window by twenty rows. reaching the bottom pins it to follow again, and running a line pins it.
- up and down walk back through the lines already entered.
- ctrl+l empties the pane. ctrl+l belongs to the listing and to the prompt's pane, and not to the editor.
- there is one clipboard, and every pane works it: a line cut at the prompt pastes into a document, and a line copied out of a document pastes onto the prompt.
the debugger pane
- the debugger pane shows a session of the debugger (core/debug): one compiled line, run an instruction at a time. a session is started at the prompt — launch "sieve.print 100" compiles the line and stops before its first instruction — and launching one brings this pane up.
- the prompt drives the same session: step 3, over, out, go, until 'fn, stop_at 'fn, clear_at 'fn, regs, fregs, dis 'fn, code, where and kill are commands (see the debugger, under resident commands), and the pane's keys are those commands, called rather than typed, so what they say lands in the prompt's pane.
- the first row says what stopped the session and where. the listing is on the left, around a selected instruction: > marks the current one and * a breakpoint. the registers and the call stack are on the right; a register the last command changed is marked *.
- the selection follows the current instruction after every command, and the arrows move it in between. b and u act on it.
- the debugger never stops inside the kernel (/a/aldos) or the firmware: a step into a call that lands there runs the call to its return, and a breakpoint there is refused. there are six breakpoints.
- go runs until a breakpoint, the end, or a key. while it runs the environment does not draw.
| key | what it does |
| s, f11 | step one instruction |
| n, f10 | over: step, running a call to its return |
| o, shift+f11 | out: run to the return of this function |
| g, c, f5 | go |
| b | set or clear a breakpoint at the selection |
| u | run until the selection |
| up, down | move the selection a row; page up, page down a windowful |
| home | the selection back to the current instruction |
| ctrl+q | end the session, and go to the prompt's pane |
- the debugger pane is /a/modules/loom/debugger/debugger, over /a/core/debug.
lines the environment reads
eight lines are read by the environment before the compiler sees them:
edit, read, look, play, save, compile, journal and reset. the first
seven name the pane rather than a symbol, and cannot be commands.
- edit 'path, and edit "path", open the path in the editor and bring that pane up. only that shape is the environment's: a line beginning with edit that is anything else goes to the compiler.
- read 'path, and read "path", open the path in the reader and bring that pane up. the same rule about the shape holds. a file that cannot be read is refused in the prompt's pane and the reader keeps what it had.
- look 'path, and look "path", open the picture in the picture pane and bring that pane up, under the same rules.
- play 'path, and play "path", play the file in the music pane and bring that pane up, under the same rules.
- journal, on its own, opens the journal pane and brings it up. outside the environment journal is the command that prints the ring once.
- save writes the document that is up. the scratch is not written.
- compile compiles the document that is up.
- reset is run, and the environment then leaves, because the rebuild wipes the arena the environment's own code is in. the rebuild reloads rc.alm, which starts the environment again.
- ctrl+b over the editor runs save and then compile, each submitted as if it had been typed, and stops at the first that does not go through. a document whose save failed is not compiled. the scratch has no save and is compiled directly.
compiling has two shapes, because a document has two.
- a document with a file is compiled as an import of its own path. the whole file is read again, everything it defines is defined again, and the sibling _spec is brought with it.
- only a module on the boot volume can be compiled: an .alm file under /a, with no space in its name.
- the scratch has no file. its text is compiled as typed input: the statements in it run, and its definitions are made where a typed line's would be, callable by their bare names at once.
- the scratch must therefore have the shape the prompt reads and not the shape a module file has. a module pasted into the scratch is refused at its first bare prototype.
- a compile does not replace code that is already compiled. a call was bound to its target when its caller was compiled, so the environment itself, and everything else standing on the old module, goes on calling what it called before. reset is what puts a rebuilt environment on the screen.
- every compile costs arena that is not given back. reset reclaims it.
limits
| limit | value |
| the input line | 256 bytes |
| one entry, continuation lines included | 4096 bytes |
| entries a listing shows | 128 |
| a name in a listing | 64 bytes |
| documents besides the scratch | edit/config documents, 3 |
| one document | edit/config document_bytes, 65536 |
| a document read | read/config max_bytes, 262144, held between 4096 and 262144 |
| the reading widths | read/config narrow_columns, 72, and wide_columns, 0 — the whole pane |
| the prompt's pane | repl/config screen_history_bytes, 9600, held between 4800 and 240000 |
| lines the arrows walk back | repl/config command_history_lines, 16, held at 16 |
| names one compiled unit may replace | 16 |
| the page up and page down step | 20 rows |
when either input limit is reached the environment refuses further input
on that entry; the operator submits or abandons what is already typed. a
scratch of seventeen definitions compiles once and is refused the second
time, reported as incomplete.
the prompt
whatever is typed at the prompt is almac source. there are no shell
commands in the unix sense.
- the prompt is a greater-than sign followed by a space.
- an entry the compiler cannot finish — an unclosed compound, an unfinished expression — is continued with a period and a space, and is held until it compiles or is abandoned.
- a value the line produces is printed.
- a line the compiler refuses is reported and dropped. the report names the module and the line within it, then the reason.
> fn greet () {
. tty.say "hello";
. }
> cd 'nowhere
[repl:1] unknown symbol
what a name means:
- the resident commands are ordinary functions, defined by the standing start-up script. they need no import.
- most of the applications on the disk are imported by that same script and bound to a bare name. they too are called without an import.
- what the script does not import, the operator imports by hand and calls through the module's own name.
> plot '/a/samples/plots/rect.plot
> import '/a/modules/math/sieve
> sieve.bench
how a call is written:
- a zero-argument call needs no parentheses.
- a string argument is written with a leading quote for a name without spaces, and in double quotes for a name with spaces.
- arguments are separated by commas.
- an argument that is an operator expression or a composite is wrapped in parentheses.
- no command takes an option.
files, volumes and devices
paths
- a path takes the form of a slash, a drive letter, a slash, and the path within the drive.
- a forward slash separates directory components.
- a single dot is the current directory; two dots are the parent.
- a path that does not begin with a slash is relative to the working directory.
- there is no shared root above the drives.
- there are two drives, and no provision for a third.
drive a
drive a is the boot volume, and is read-write. it holds:
| path | what it holds |
| /a/almac.bin | the resident binary |
| /a/rc.alm | the start-up script |
| /a/rc | the module the script draws its commands from |
| /a/core | the core library |
| /a/aldos | the operating-system modules |
| /a/modules | the applications |
| /a/codex | the documents |
| /a/samples | the sample pictures, tunes and recordings |
the applications are one directory to a sort: text, graphics, sound,
games, demos, math, net, compression, system, and loom. the samples are
in pixels, fotos, plots, songs and music. whatever else is on the disk
the operator put there.
drive b
- drive b is a passthrough to a single directory on the machine that hosts aldan. it is read-write.
- it is mounted at every boot. there is no configuration that removes it, and no installation on which it is absent.
- the directory it passes through is the one named aldan in the home directory of the account that started the machine: /home/ortfero/aldan on a unix host, c:\users\ortfero\aldan on a windows host.
- nothing above that directory is reachable. a path that climbs out of it is refused.
- a windows host creates the directory at start if it is absent. on every other host the operator creates it.
- when the directory does not exist, drive b still mounts, and every listing and every open on it fails as not found.
- a file written to drive b lands in the host's own filesystem, outside the boot volume, and is not undone by powering the machine down.
where the drives differ
- on drive a, creating a file that already exists truncates it and reuses the name.
- on drive b, creating a file that already exists is refused.
- so a command that writes its result to a fixed name — cp onto an existing file, compress over a .lz it made before — succeeds on drive a and fails on drive b until the old file is removed.
devices
- devices are referred to by role, not by port: the keyboard, the display, the disk, the sound channel, the network. the firmware's call table resolves the name.
- there are no port numbers, no interrupt vectors and no device addresses.
- there is no disk task. a read or a write is submitted to the device and the calling fiber is put to sleep on it; the tick probes the device and wakes the fiber when the transfer has completed.
- transfers are serialised in the order they were submitted. two writes issued in quick succession are never interleaved.
- the network is a datagram device: a program opens a socket, sends and receives, and closes it.
- a receive with nothing waiting parks the calling fiber, and the tick wakes it when a datagram arrives. the rest of the system runs on.
- sending promises nothing — this is udp — and a failed send is reported as a fault rather than retried.
configuration
there are two places to configure the machine, and reset is what makes a
change to either take effect.
the start-up script
- when almac begins, after the boot sequence and before the environment comes up, it reads /a/rc.alm and evaluates it as almac source in the global namespace.
- if the file is absent, almac proceeds without it and does not complain.
- anything defined in rc.alm is visible at the prompt thereafter, exactly as if the operator had typed it.
- the operator places in it the imports that are to be in effect at the prompt, and the functions that are to be callable without loading a module.
- the last line of the shipped script calls loom. a script that does not call it leaves the machine at the emergency prompt.
- rc.alm is a text file and is edited in the editor pane.
import 'core/file, 'core/bytes, 'core/boolean, 'core/heap,
'/a/modules/graphics/plot
fn art () raises file.fault | bytes.fault | boolean.fault | heap.fault {
plot.show '/a/samples/plots/vicsek.plot;
}
the environment's configuration
an app in the environment is a directory, and every one of them has a
config module of the same name.
- a config is an almac module. its settings are constants, and the app reads them by importing the file.
- there is no configuration format and no parser. a mistake is a compile diagnostic naming the file and the line, at boot.
- a change takes effect at the next reset.
- a value out of range is held to the bounds the app can honour; the bounds are in the app's own source.
| module | what it holds |
| /a/modules/loom/repl/config | screen_history_bytes, command_history_lines |
| /a/modules/loom/fm/config | size_column, extensions, commands |
| /a/modules/loom/edit/config | document_bytes, documents |
- extensions and commands in fm/config are one table read down: the nth ending is opened by the nth command. they must be kept the same length.
programs
- a program is a module: a text file with the extension .alm. there is no executable format and no loader.
- importing a module compiles it into the running image and binds its public names. the compiled code stays there for the rest of the session.
- a module has two sections. the first declares what it exports — types, constants, and function signatures. the second defines them.
- a program that is to be called from the prompt exports one entry function. the operator calls it as module.entry, or by the bare name the start-up script binds it to.
- setup and teardown are not declared. setup runs once the module has been loaded, after everything it imports is up. teardown runs when the image gives the module up: at reset, and when a compile that imported the module fails and is rolled back.
- modules are torn down in the reverse of the order they were loaded.
- a module that takes something over from the machine does it in setup and hands it back in teardown.
- nothing reclaims a module while the session lasts. reset gives the boot image back.
resident commands
each entry gives a synopsis, the rules, and an example. no command takes
an option. the lines the environment reads for itself — edit, save and
compile — are described under the environment.
import
synopsis: import 'name, 'name, ...
- imports a module from the disk. the path is given without the .alm extension.
- the module is compiled on demand and kept for the session.
- several modules may be imported with one import.
- a module that is already imported is not imported again.
- an import that names a module that cannot be found raises a fault, and the line is rejected.
> import '/a/modules/math/sieve
> import '/a/modules/graphics/plot, '/a/modules/games/snake
reset
synopsis: reset
- returns the image to the state it had at boot.
- every loaded module is given up, in the reverse of the order it was loaded, each one first running its teardown: the services stop and the kernel hands the firmware call table back.
- the shell then discards everything it holds — modules, definitions, imports — rebuilds itself, and evaluates /a/rc.alm again. the standing applications and the shell commands return as they were at boot, and the environment comes up again.
- the command is deferred: it takes effect once the line that called it has finished, never in the middle of it.
- anything the operator defined or imported at the prompt is gone afterwards. anything named in /a/rc.alm comes back.
- work held on disk is untouched.
- a running service is stopped, not asked.
- reset is what reclaims the arena a compile at the prompt has spent, and what puts the environment right after a hardware trap.
> reset
tty.say
synopsis: tty.say expression, expression, ...
- writes each expression to the prompt's pane, and appends a newline after the last.
- several values may be passed in one call.
- say belongs to the tty module, which the default rc.alm imports.
> tty.say "hello"
hello
> tty.say (1 + 2)
3
> tty.say "x = ", 42
x = 42
tty.sayin
synopsis: tty.sayin expression, expression, ...
- writes each expression with no trailing newline.
> tty.sayin "x = "
> tty.say 42
x = 42
clear
synopsis: clear
- takes the display back from whatever a program left presented, blanks the console grid, and homes the cursor.
- the console grid is what the emergency prompt prints into. the environment covers it with its own canvas, so clear does not empty a pane: ctrl+l empties the prompt's pane.
- clear is defined in the default rc.alm.
> clear
date
synopsis: date
- writes the current date and time, in the form yyyy-mm-dd hh:mm:ss.
- date is defined in the default rc.alm.
> date
2026-06-16 14:30:00
calendar
synopsis: calendar
- writes the previous month, the current month and the next one side by side, three columns across.
- the current day is marked. weeks run from sunday.
- the clock read is the machine's, so the date shown is whatever the host is keeping.
- calendar is defined in the default rc.alm.
> calendar
july 2026 august 2026 september 2026
su mo tu we th fr sa su mo tu we th fr sa su mo tu we th fr sa
...
ls
synopsis: ls
- lists the entries of the current working directory, one to a line.
- a directory is written with a trailing slash, a file without one.
- ls takes no argument and lists the working directory only. to list another, use lsd.
> ls
almac.bin
rc.alm
rc/
core/
aldos/
modules/
codex/
lsd
synopsis: lsd 'path
- lists the entries of the directory at the named path, one to a line, without changing the working directory.
- the path may be absolute or relative to the working directory.
- a directory is written with a trailing slash.
> lsd '/a/modules/math
sieve.alm
cd
synopsis: cd 'path
- changes the working directory to the named path, and prints the new working directory.
- there is no pwd command. cd with a single dot prints the working directory without changing it.
> cd '/a/notes
/a/notes
> cd '..
/a
mkdir
synopsis: mkdir 'path
- creates a directory at the named path.
- the parent of the new directory must exist.
- one directory is created. a chain of directories is created by calling mkdir on each in turn.
> mkdir '/a/notes
rmdir
synopsis: rmdir 'path
- removes the named directory.
- the directory must be empty. a directory holding files or sub-directories is not removed.
> rmdir '/a/notes
touch
synopsis: touch 'path
- creates the named file as an empty file, if it does not exist.
- if the file exists, touch does nothing: the file is left as it stands, contents and all.
- touch does not create the parent directory.
- the machine keeps no modification time, so touch is only ever a way of making an empty file.
> touch '/a/notes/today
rm
synopsis: rm 'path
- removes the named file.
- one file is removed. a directory is not removed.
- there is no confirmation prompt and there is no recovery.
> rm '/a/notes/old
cat
synopsis: cat 'path
- writes the contents of the named file to the prompt's pane.
- cat is for short text files. a longer file is read in the reader pane, with read.
> cat '/a/notes/today
buy bread.
fix the disk light.
cp
synopsis: cp 'source, 'destination
- copies the source file to the destination.
- when the destination names an existing directory, the source's final component is joined onto it and the copy lands inside. otherwise the destination is the literal path of the new file.
- one file is copied.
- the destination's parent directory must exist.
- an existing destination file is overwritten on drive a and refused on drive b.
> cp '/a/notes/today, '/a/notes/yesterday
> cp '/a/notes/today, '/b
mv
synopsis: mv 'source, 'destination
- moves or renames the source file to the destination.
- one file is moved.
- the destination's parent directory must exist.
> mv '/a/notes/old, '/a/archive/old
fsck
synopsis: fsck
- checks the boot volume and reclaims what nothing refers to.
- the filesystem's write path is ordered so that the only damage a crash can leave is space marked used and referred to by no directory entry. fsck rebuilds both allocation maps from the directory tree and gives that space back.
- no file is lost or moved by it.
- fsck is a program, not part of the filesystem, and works on drive a only.
- it is worth running after an abrupt power-off. there is no need to run it otherwise.
> fsck
services
synopsis: services
- lists the background services now running, one name to a line. when none is running, it says so.
- a name identifies a service and only one service may hold it, so this listing also answers whether a given name is free.
> services
no services
stop
synopsis: stop 'name
- stops the named service.
- the service is asked first: it sees the request at the next point it looks, and is given a moment to leave on its own, which lets its cleanup run and its files close.
- a service that does not take the moment is struck off.
- a service that returns from its own work function unwinds properly. one struck off mid-wait does not.
> services
watcher
> stop 'watcher
restart
synopsis: restart 'name
- stops the named service and starts it again on a fresh fiber, with the work, the cleanup and the period it was launched with.
- this is the only way to get a new instance of a name.
- the service must be running. a stopped service must be started again by whatever started it first.
> restart 'watcher
journal
synopsis: journal
- prints the ring of text the services have written, each line headed by the service's own name, together with the started and stopped lines the system writes itself.
- the ring holds a fixed number of bytes. when it is full the oldest whole line goes, never a fragment.
- lines lost that way are counted, and the count is printed before the text.
- in the environment the word is read before the compiler, and opens the journal pane instead, which follows the ring as it grows. see the journal pane under the environment.
> journal
watcher: started
watcher: 3 files changed
memory
synopsis: memory
- reports what the operating pool is doing: its total and free size, the number of live blocks, what has been asked for against what has been reserved, the difference between them, and what the byte allocator is holding against what is live in it.
- the live figures are this instant. the boot figures cover every block claimed since the machine came up.
- a claim of sixteen kilobytes or less is carved out of a shared page region and costs close to what it asks for. a larger one is rounded up to whole pages, and it is that rounding the waste figures measure.
> memory
pool total : 3712kb
pool free : 2860kb
live blocks : 34
...
halt
synopsis: halt
- powers the machine off.
- nothing is asked and nothing is saved. work not already written to the disk is lost.
- services are not stopped first and no teardown runs.
> halt
reboot
synopsis: reboot
- restarts the machine from the reset vector, exactly as a power cycle would. the firmware runs again, the resident binary is loaded again, and rc.alm is evaluated again.
- reset rebuilds the shell without going back through the firmware, and is the cheaper instrument.
> reboot
help
synopsis: help
- prints the list of shell commands and applications, each with its arguments and a line of description, followed by a note on how string arguments are written.
- the listing is written out by hand in the start-up script's command module rather than derived from what is bound. a command added by the operator is added there too if it is to appear.
> help
commands:
clear clear the screen
date print the current date and time
...
the debugger
synopsis: launch "line". step n. over. out. go. until 'fn. stop_at 'fn.
clear_at 'fn. regs. fregs. dis 'fn. code. where. kill
a session of the debugger is one compiled line, run an instruction at a
time. the commands are core/debug's, bound to bare names by the start-up
script. break, continue, halt and stop were already taken, hence the
names stop_at, go and kill.
- launch compiles the line and stops before its first instruction. a line with arguments has spaces in it, so it is written in double quotes. a session already standing is ended first, and the debugger pane comes up.
- the line runs on a stack of its own, but on the fiber that launched it. code is never patched: a breakpoint is one of the processor's debug triggers, costs nothing to any other fiber, and survives anything the line does.
- step n runs n instructions. over steps, running a call to its return. out runs to the return of the current function. until runs to a function's entry. go runs until a breakpoint, the end, or a key.
- every one of them reports what stopped the session and where, and the instruction it stopped at.
- stop_at sets a breakpoint, and clear_at removes one. there are six. clear_at with an empty name, written "", removes them all.
- a function is named as the prompt names it, fn or module.fn. an address is written 0x followed by hex digits, in double quotes: stop_at "0x1f2c".
- regs prints the integer registers and the program counter; fregs the floating registers. dis lists a whole function. code lists the instructions around the current one, which is marked with a greater-than sign; a breakpoint is marked with a star. where prints the call stack, innermost first, at most eight frames deep.
- the debugger never stops inside the kernel (/a/aldos) or the firmware. a step into a call that lands there runs the call to its return, a breakpoint or an until there is refused, and a key that interrupts go there lets it run on until it leaves.
- when the line returns, the session has finished, and its value or fault is printed. step, over, out, go and until then refuse until the next launch; the registers, the listings and the call stack can still be read.
- kill ends the session and gives its storage back. the breakpoints stay set for the next one.
- the debugger cannot be driven from inside the line it is debugging.
> import '/a/modules/math/sieve
> stop_at 'sieve.next
breakpoint 1 at sieve.next+0x0
> launch "sieve.print 30"
launched at launch+0x0
...
> go
breakpoint at sieve.next+0x0
...
> where
#0 sieve.next+0x0
#1 sieve.print+0x...
#2 launch+0x...
> clear_at ""
cleared 1 breakpoint(s)
> go
2 3 5 7 11 13 17 19 23 29
finished
> kill
applications
these are modules on the disk. the start-up script imports most of them
and binds each to a bare name; the function a name stands for is given
in each entry. an application the script does not import is marked as
such, and is imported by hand before it is called.
edit
synopsis: edit 'path
- opens the named file in the editor pane and brings that pane up.
- a file that does not exist is opened empty and written on the first ctrl+s.
- the path is made absolute against the working directory at the time it is opened.
- edit is not a command. it is a line the environment reads before the compiler, because there is nothing in a command for a pane to be named by. the pane, its keys and its limits are described under the environment.
- the editor is /a/modules/loom/edit/edit, over /a/modules/loom/editor.
> edit '/a/notes/today
read
synopsis: read 'path
- opens the named file in the reader pane and brings that pane up: a file too long to cat, read beside whatever is being written.
- the path is made absolute against the working directory at the time it is opened.
- read is not a command. it is a line the environment reads before the compiler, for the same reason edit is: there is nothing in a command for a pane to be named by. the pane, its keys and its limits are described under the environment.
- the reader is read-only. to change a file, open it in the editor pane.
- the reader is /a/modules/loom/read/read. it replaces leaf, the full-screen pager, which is deleted.
> read '/a/codex/aldos_operators_manual.moff
look
synopsis: look 'path
- opens the named pixl or foto picture in the picture pane and brings that pane up.
- the path is made absolute against the working directory at the time it is opened.
- look is not a command. it is a line the environment reads before the compiler, for the same reason edit and read are. the pane, its keys and its limits are described under the environment.
- the picture pane is /a/modules/loom/look/look. it replaces gaze, the full-screen viewer, which is deleted.
> look '/a/samples/pixels/kandinsky/several_circles.pixl
> look '/a/samples/fotos/kandinsky/several_circles.foto
play
synopsis: play 'path
- plays the named recording (.echo) or tune (.song) in the music pane and brings that pane up. the music goes on playing while other panes are up.
- the path is made absolute against the working directory at the time it is opened.
- play is not a command. it is a line the environment reads before the compiler, for the same reason edit, read and look are. the pane, its keys and its limits are described under the environment.
- a song is line-oriented text: patch declarations, phrases of note rows, and a chain naming the order they play in. it may be edited in the editor pane. an unknown directive is ignored rather than refused, which is what lets a file carry the title and source lines the pane shows.
- two tunes ship on the disk, in /a/samples/songs, and one recording, in /a/samples/music/bach.
- the music pane is /a/modules/loom/play/play. it replaces phono and the full-screen play, and deck, the screen they shared, which are deleted.
> play '/a/samples/songs/ode_of_joy.song
ode_of_joy.song playing 0:04 / 0:32
ode to joy
beethoven, ninth symphony, 1824
tempo 250 ms/row, 3 phrases, 96 rows, 4 in the chain
*> ode_of_joy.song
flat_wave_society.song
snake
synopsis: snake
- a game. a snake moves across the screen, lengthening when it eats and ending when it strikes a wall or itself.
- the arrow keys steer. ctrl-q exits.
- the score is shown at the top of the screen and is not preserved between runs.
- snake is snake.play of /a/modules/games/snake, bound by the start-up script.
> snake
sieve
synopsis: sieve.bench. sieve.print n
- a processor benchmark and a prime number stream.
- sieve.bench drains the sieve of eratosthenes over and over for a fixed window, counts the instructions the processor retired, and returns the best rate reached in whole millions of instructions a second.
- the count is read from the processor's own retired-instruction counter, so the figure is a property of the machine rather than of the program.
- sieve.print n prints the primes not exceeding n.
- a program may drain the same stream: sieve.create sets a sieve.stream record up, sieve.next hands out one prime at a time until it returns false, and sieve.dispose disposes the stream.
- the stream sieves into a claimed flag store covering numbers up to 100000, which is the greatest n it accepts. asking past that raises a contract failure.
- the start-up script does not import sieve.
> import '/a/modules/math/sieve
> sieve.bench
10
> sieve.print 30
2 3 5 7 11 13 17 19 23 29
golife
synopsis: golife.play
- conway's game of life. golife takes the whole display and evolves a grid of cells one generation at a time.
- space pauses and resumes, r reseeds the grid at random, ctrl-q exits.
- the generation and population counts are shown above the grid.
- the start-up script does not import golife.
> import '/a/modules/demos/golife
> golife.play
plot
synopsis: plot 'path
- a viewer for the plot format: line art written as text, a pen that moves without drawing and a pen that draws as it moves, one command to a line, in pixels of the 720x480 plane.
- plot reads the file, turns each line into a display-list command, and holds the display until q or escape.
- a picture longer than the display list can hold is drawn as far as it fits and reported as truncated.
- a file too large to read is refused outright.
- plot is plot.show of /a/modules/graphics/plot, bound by the start-up script.
> plot '/a/samples/plots/vicsek.plot
galaxy
synopsis: galaxy
- a percolation model of a spiral galaxy. cells on a set of rotating rings ignite star formation in their neighbours a step later, and the rings turn at different speeds.
- the picture is a display list of short strokes, one to a lit cell, brightest at the leading edge of a front and fading along its trail.
- space pauses and resumes, r reseeds, ctrl-q exits.
- galaxy is galaxy.play of /a/modules/demos/galaxy, bound by the start-up script.
> galaxy
jingle
synopsis: jingle
- plays a short phrase written to show what the synth can do: a filtered pulse lead, a saw bass under it, noise percussion, and a pitch slide on the closing note.
- it returns when the phrase is over.
- rendering hangs off the timer tick rather than off this program, so the phrase keeps time while other work is running.
- jingle is jingle.play of /a/modules/sound/jingle, bound by the start-up script.
> jingle
compress
synopsis: compress 'path
- compresses the named file with the lz codec, writing the result beside it under the same name with .lz appended.
- the original is left alone.
- a fault part way through removes the partial output.
- on drive b an existing .lz of the same name must be removed first.
- compress is lz.compress of /a/modules/compression/lz, bound by the start-up script.
> compress '/a/notes/log.moff
> lsd '/a/notes
log.moff
log.moff.lz
decompress
synopsis: decompress 'path
- decompresses a .lz file, writing the original back beside it under the name with .lz removed.
- the .lz file is left alone.
- a path that does not end in .lz is refused, as is a file whose contents are not an lz stream this version can read.
- decompress is lz.decompress of /a/modules/compression/lz, bound by the start-up script.
> decompress '/a/notes/log.moff.lz
pack
synopsis: pack 'dir
- archives a whole directory tree into one file, named after the tree with .pack appended.
- the archive stores bytes and does not compress them. the same tree always gives the same archive.
- an archive is built from a directory in one act and is never edited afterwards. to compress it as well, run compress over the .pack.
> pack '/a/notes
> lsd '/a
notes/
notes.pack
unpack
synopsis: unpack 'path
- restores the tree an archive carries, at the directory the archive is named after: dir.pack becomes dir again.
- unpacking destroys. what stands at the target directory is taken apart first, so what remains afterwards is the archive and nothing else: anything under that directory the archive does not name is gone.
- the archive is opened before the target is touched, so a file that is not an archive takes nothing with it.
- a target that comes out naming a whole drive is refused rather than emptied.
- a path that does not end in .pack is refused.
> unpack '/a/notes.pack
catalog
synopsis: catalog 'path
- lists the tree inside an archive without unpacking it, one entry to a line, indented by depth, each file followed by its size.
- nothing is written and nothing is extracted.
> catalog '/a/notes.pack
today.moff 1204
old/
log.moff 8817
udp
synopsis: udp.echo port. udp.say "host", port, "message"
- the two smallest network programs there are, for finding out whether a machine's network works at all.
- udp.echo binds the given port and answers every datagram back to whoever sent it, printing each as it arrives. a datagram beginning with "bye" ends it.
- udp.say opens a socket, ships one datagram to the named address and port, and closes.
- the address is written as a dotted quad in quotes, and is refused if it is not one.
- a receive holds only the fiber that called it. the rest of the system runs on.
- udp is imported by the start-up script but bound to no bare name, so it is called through the module.
> udp.echo 7777
> udp.say "127.0.0.1", 9999, "hello"
fex
synopsis: fex.generate 'dir, 'name, 'intro. fex.generate_relay 'dir,
'name, 'addr, 'intro. fex.identity 'root. fex.publish 'root.
fex.inventory 'root. fex.roster 'root. fex.fetch 'root, 'path
- the client for fex, by which a member publishes one directory — a capsule — to a relay and reads it back from there a file at a time.
- a member owns exactly one capsule and can read no one else's.
- every datagram is sealed under a key each side works out on its own from the two cards, so the relay holds bytes and nothing more.
- fex is imported by the start-up script but bound to no bare name, so it is called through the module.
every command but generate takes the member's root, a directory laid out
so:
| path | what it holds |
| keys/node.dano | the member's identity, secret and all |
| keys/relay.relay.danl | the relay's card, which carries its address |
| self/name@relay/files/ | the capsule: the content, and nothing else |
| self/name@relay/files/ inventory.danl | the relay's inventory as last known |
| self/name@relay/state/staging/ | downloads in progress |
- generate makes a key pair and writes it as two files in dir: name.dano, the identity, and name.card.danl, the card. neither is ever written over. generate_relay does the same for a relay, with the address it answers on, in the form "10.0.2.2:4444", as a further argument.
- the identity is the member's secret. it goes to keys/node.dano under the root, and nowhere else.
- the card is public. the relay registers a member by appending its card to the relay's roster as it stands. the relay's own card goes to keys/ under the root, renamed relay.relay.danl.
- the operator makes the capsule's directory, self/name@relay/files/, and puts the content in it. the client finds the capsule there and does not make one.
- a root holds one relay's card and one capsule. the client finds both by their names, and refuses a root with none or several of either.
- identity prints the fingerprint of the root's identity. a member and a relay check each other's fingerprints over some independent channel before either trusts a card the other sent.
- publish sends the capsule up, and prints the sequence number it was published as, or that it was already published. a file changed while it was being sent starts the publication over.
- inventory brings files/inventory.danl level with the relay and prints it, a path and a size to a line. the hashes stay in the file, for cat.
- roster prints who is registered on the relay: a name, a fingerprint and the card's introduction to a line.
- fetch brings one named file back down into files/, and says when the file standing there is already the one the relay holds. a download is written to a staging file as it arrives and checked against the hash that named it before it is put in place, so a file of any size costs the same memory.
- every command opens a socket of its own, does its work and closes it, on the fiber that called it. there is no service.
- nothing in this client ever deletes anything.
- the machine has no source of randomness, so a key made here is only as unguessable as the moment it was made.
> fex.generate '/b/fex/keys, 'alice, "family photos"
> mv '/b/fex/keys/alice.dano, '/b/fex/keys/node.dano
> fex.identity '/b/fex
> fex.publish '/b/fex
> fex.inventory '/b/fex
> fex.roster '/b/fex
> fex.fetch '/b/fex, 'docs/readme.txt