otto host — verbs from methods
otto host <id> <verb> is not a hand-written command set: every verb is
synthesized from a @cli_exposed coroutine method on the resolved host’s
class. The Python API is the source of truth and the CLI is a projection of
it — add a method, get a subcommand.
Class-scoped synthesis
The group behind otto host (HostGroup in otto/cli/expose.py) collects
@cli_exposed methods across every registered host class — built-in and
project-registered alike — and then filters the visible, dispatchable set to
the verbs defined on the class of the host actually named on the command
line. Scoping falls out of method definedness: UnixHost defines lsmod,
so otto host router1 lsmod exists; an EmbeddedHost doesn’t, so the verb
isn’t offered there. A project that registers MyHost with a @cli_exposed
method gets its verb with no extra wiring — the same first/third-party
symmetry as everywhere else (Registries and the pluggable CLI).
Each verb’s flags come from the method’s own signature, via the same
options-to-parameters machinery instructions and suites use; Arg/Opt/
Exclude annotations fine-tune what the CLI projection looks like without
touching the Python call shape.
Rendering and exit codes
A verb’s return value is rendered by one shared path: members of the
Result family drive both output and the exit code
(result.exit_code, ssh-like semantics — Results and exit codes);
None means side-effect-only success; any other value is the documented
third-party fallback — printed as-is, exit 0. Command output itself
streams live during execution, so a successful run verb prints nothing
extra at the end.
Completion, scoped like the verbs
Tab completion mirrors the synthesis model at every position. Host ids come
from the completion cache’s snapshot (falling back to a live lab scan on a
cold cache — completion never runs user code,
Registries and the pluggable CLI); note the built-in local host in the
candidates:
otto host <TAB><TAB>
example-device local
$ otto host
Once a host id is typed, the verb candidates are that host’s class menu —
the same definedness scoping that decides what is dispatchable:
otto host example-device <TAB><TAB>
cp exists get install is-installed is-uninstalled load login ls lsmod
mkdir mv power put read-file reboot rm run shutdown stage uninstall
unload write-file
$ otto host example-device
And option values backed by registries complete from the registry — here the
term backends, so a project-registered backend completes exactly like a
built-in:
otto host example-device --term <TAB><TAB>
ssh telnet
$ otto host example-device --term
What is unique about host
The full preamble applies, but read-only verbs (ls, exists,
read-file, …) are registered with output_dir=False — inspecting a host
should not litter --xdir with empty run directories.
Per-invocation --term / --transfer / --hop overrides apply option
overlays to the one resolved host before the verb runs
(Connection control).
otto host --help
otto host --help
Usage: otto host [OPTIONS] [HOST_ID] COMMAND [ARGS]...
Run commands and transfer files on lab hosts.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ [host_id] TEXT Host ID to operate on. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --hop TEXT Host ID to use as an SSH hop to reach the │
│ target. │
│ --term TEXT Override the terminal protocol for this session. │
│ --transfer TEXT Override the file transfer protocol for this │
│ session. │
│ --list-hosts Show all valid host IDs. │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ cp Copy *src* to *dst* on the host (``cp``; *recursive* → │
│ ``-r``). │
│ exists Return True when *path* exists on the host (``test -e``). │
│ get Transfer files from remote host to the local machine. │
│ install Stage, then install every product. │
│ login Open an interactive shell bridged to the local terminal. │
│ is-installed Return True iff there is at least one product and all are │
│ installed. │
│ is-uninstalled Inverse of :meth:`is_installed`. │
│ load Insert a kernel module: stage the .ko to the host, then │
│ ``insmod`` it. │
│ ls List entry names in *path* (``ls -1``; *all* adds ``-A`` for │
│ dotfiles). │
│ lsmod List the kernel modules currently loaded on the host. │
│ mkdir Create directory *path* (``mkdir``; *parents* adds ``-p``). │
│ mv Move/rename *src* to *dst* on the host (``mv``). │
│ power Power this host ``'on'``/``'off'``, or toggle when *state* │
│ is None. │
│ put Transfer files from local machine to remote host. │
│ read-file Return the text contents of *path*. │
│ reboot Reboot this host. │
│ rm Remove *path* (``rm``; *recursive* → ``-r``, *force* → │
│ ``-f``). │
│ run Execute one or more commands on the host via the persistent │
│ shell session. │
│ shutdown │
│ stage Stage every product onto this host (transfer/place, no │
│ install). │
│ uninstall Uninstall every product (best-effort). │
│ unload Remove a kernel module (``rmmod``). │
│ write-file Write *data* to *path* (overwrite, or append). │
╰──────────────────────────────────────────────────────────────────────────────╯