Extending it
Two ways to add a command without forking. Both are gated on the same terms as a
built-in — platforms, needsDisplay, blocking — and both automatically
become MCP tools.
Adapters — when it is a command line you already run
Section titled “Adapters — when it is a command line you already run”Drop a JSON file in ~/.config/aos/adapters/ and it becomes a group:
{ "group": "notes", "description": "Personal notes", "commands": [ { "name": "today", "summary": "Open today's note", "run": "$EDITOR ~/notes/$(date +%F).md", "platforms": ["darwin", "linux"] } ]}$ aos adapters example --write # a working starter file$ aos adapters path/Users/you/.config/aos/adapters$ aos adapters listno adapters in /Users/you/.config/aos/adaptersrun `aos adapters example --write` to start onerun goes through the platform shell with the user’s arguments appended and
quoted.
This is where anything site-specific belongs
Section titled “This is where anything site-specific belongs”aos used to ship a msg group that talked to a local messenger hub. It
was removed, because a built-in command that only works if you happen to run one
particular service is a command that fails for everyone else — and the command
list is a promise about what this machine can do.
Notifications are personal: Slack, Telegram, ntfy, a webhook, a hub of your own.
So they are an adapter, and yours works exactly like a built-in — it shows up in
commands, in --help, and as an MCP tool:
{ "group": "notify", "description": "Tell me things", "commands": [ { "name": "send", "summary": "Post a message to my Slack webhook", "args": "<text...>", "run": "curl -sS -X POST -H 'Content-Type: application/json' -d \"{\\\"text\\\":\\\"$*\\\"}\" \"$SLACK_WEBHOOK_URL\"" } ]}aos notify send "build passed"Keep the secret in the environment, not in the adapter file — the run line is
a shell command, so $SLACK_WEBHOOK_URL expands at call time and never has to
be written down here.
Plugins — when you need a program
Section titled “Plugins — when you need a program”Any executable named aos-<group>-<name> in $AOS_BIN_DIR,
~/.config/aos/bin, or on PATH joins the CLI, and therefore the MCP
tool list. It describes itself with comment headers in its first 80 lines:
#!/bin/sh# aos:summary=Do the thing# aos:args=<target># aos:examples=aos demo do thing a | aos demo do thing b# aos:platforms=darwin | linux# aos:route=theme bg-switcher # when a word contains a hyphenNeither can shadow a built-in
Section titled “Neither can shadow a built-in”A file in the config directory must not be able to change what a shipped command does, so a built-in always wins a route collision. Shipping a Go implementation of a route transparently replaces the script that used to serve it.
A malformed adapter is reported, not ignored:
aos commands --check # registry lint; non-zero when something is offSeeing what a machine picked up
Section titled “Seeing what a machine picked up”$ aos debugaos devgo go1.26.3platform darwin/arm64commands 96 available of 98 registered, in 34 groups
plugin dirs: /Users/you/.config/aos/bin …doctor reports the count too: ok plugins 0 external commands across 38 directories.