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). │ ╰──────────────────────────────────────────────────────────────────────────────╯