Skip to content

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"]
}
]
}
Terminal window
$ aos adapters example --write # a working starter file
$ aos adapters path
/Users/you/.config/aos/adapters
$ aos adapters list
no adapters in /Users/you/.config/aos/adapters
run `aos adapters example --write` to start one

run 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\""
}
]
}
Terminal window
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.

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 hyphen

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:

Terminal window
aos commands --check # registry lint; non-zero when something is off
Terminal window
$ aos debug
aos dev
go go1.26.3
platform darwin/arm64
commands 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.