Command line interface¶
Purpose¶
airfs is a Go SDK first: its capabilities are consumed as a library. The command
line exists so that a person, a shell profile, or a service manager can drive the
same capabilities without writing Go, and so that the SDK's behaviour is
observable without a test harness.
Scope¶
The command surface, what each command reports, and exit codes. The behaviour each command drives is specified elsewhere and is not restated here.
Principle¶
The command line is a thin frontend over the library. It adds argument parsing, human-readable reporting, and exit codes โ nothing else. Any behaviour it appears to add, another spec is missing.
Every command resolves the configuration of source-config.md, and every command takes the same two overrides, so that an experiment against an alternative source list is one flag rather than an edited file:
--target <dir>โ the directory the view is exposed under. Defaults to$HOME/.ai-resources: the directory holding the configuration file and one mounted subdirectory per kind. The target is a property of the frontend rather than of the source list, so it is a default and an override, never a line in the configuration file โ the same sources may be exposed at more than one target.--config <file>โ the configuration file to read. Defaults tosources.txtinside the target. Overriding it does not move the target: the two are independent, which is what allows one source list to be tried against a scratch target, and one target to be rebuilt from an alternative list.
Both accept a ~-prefixed path, expanded as in
source-config.md. A relative --target or --config
resolves against the current working directory, because it was written on the
command line, where the working directory is the obvious frame of reference.
Paths inside the configuration file are unaffected: they resolve against the
directory containing that file, per
source-config.md, so a source list means the same thing
wherever it is read from.
Commands¶
sources โ resolve the configuration and report it: the ordered sources, the
entry count each contributes per kind, and every shadowed entry with its winner and
losers. Touches nothing. This is the command that answers "is my repository being
layered, and where in the order?", and the shadowing report is what makes
precedence debuggable rather than mysterious.
mount โ serve the merged view under the target per
fuse-mount.md, reporting the layers behind it. Blocks while
serving, since the mount lives as long as the process; --detach returns once
the view is ready and reports how to stop it, which is what a service manager or
a shell profile needs.
--source <path>, short -s, declares one source and is accepted more than
once; the order the flags are given in is the precedence order, most general
first. Giving any of them replaces the configuration file with exactly that
list before the view is served, and creates the target directory if it is not
there yet โ so that a workspace can be brought into being by one command instead
of by a file that has to be authored first.
It is a replacement, never a merge: whatever the file said, comments included, is gone, and nothing is kept alongside it. That is the point of it being one flag on one command rather than a general editing facility โ someone who typed the list they want is stating the whole list, and a flag that quietly appended to what was already there would produce an order nobody wrote. The new list is resolved before the old file is replaced, though, so a mistyped path leaves the existing configuration standing rather than replacing it with something that does not resolve.
A path given this way is written down as it was typed, so that ~ and $VAR
survive into the file and it keeps the form its author would recognise. The one
exception is a path still relative after expansion: on the command line it means
"from the working directory", inside the file it would mean "from the file's
directory", so it is made absolute before being written. Writing it verbatim
would silently change which directory it names.
The flag is on mount alone. sources and status report and touch nothing; a
read-only command that rewrote the configuration as a side effect would be a
trap.
umount โ release the target's mounts, including recovering a stale mountpoint
left by a serving process that died. Reports that nothing was mounted rather than
failing.
status โ report whether the target is being served, and what is visible
through it. Distinguishing a live mount from a stale one matters here: a stale
mountpoint looks mounted and serves nothing, and a contributor who cannot tell them
apart cannot act.
doctor โ check the host's mount prerequisites: /dev/fuse, and a setuid
fusermount3. For each, report whether it is satisfied and, when it is not, which
system package provides it. This exists as its own command because the
requirements are the project's most likely first failure, and a mount error alone
does not explain which requirement is missing or what to install.
Reporting and exit codes¶
Output is for a person reading a terminal: the resolved state, then what changed. Reports name sources by the path the configuration declared, not by a resolved symlink target.
There is no machine-readable output mode. A program that needs the merged view consumes the SDK, in-process and without parsing anything; adding a second, stringly-typed interface for the same data would be a contract to keep stable for no caller that could not do better.
Exit codes are 0 for success, 2 for an unsatisfied precondition โ a missing
configuration, an unresolvable source, a non-empty mountpoint, an absent mount
prerequisite โ and 1 for everything else that failed. The precondition code is
separate because it is the outcome a caller acts on differently: it means the host
or the configuration needs attention, not that airfs malfunctioned.
Two commands report a state rather than an outcome, and their codes say which state, so that a shell profile can branch without parsing prose:
statusexits0when the target is fully served, and2when it is not โ nothing mounted, only some kinds mounted, or a mountpoint gone stale. Each of those is a condition to act on, and each is named in the output.doctorexits0when every prerequisite is satisfied and2when any is missing. It reports every prerequisite either way, since the second missing one is worth knowing before installing the first.
Shadowing is not a failure. sources exits 0 with shadowed entries reported,
because shadowing is the mechanism working, and an exit code that punished it
would make the normal case indistinguishable from a broken configuration.
Non-goals¶
- Editing the configuration file in place.
mount --sourcereplaces it whole; nothing appends a line to it, rewrites one line of it, or reorders it. A command that edited it piecewise would produce diffs nobody wrote, and the file is otherwise authored by hand and reviewed in git. - Installing mount prerequisites.
doctorreports and explains; installing needs root, and a tool that asks for root to install a system package is a tool that should have printed the command instead. - Managing source repositories: cloning, pulling, or reporting their git state.
- Supervision. Keeping a mount alive across reboots belongs to the host's service manager; the SDK provides no daemon of its own beyond detaching one.
- Authoring or validating resources.
airfslayers directories; it does not know what a skill is.