Extensions
An extension is your config, packaged so someone else can install it.
That is the whole idea, and it is meant literally: same files, same globals, same way of
writing them. If you have written a main.ts you have already written an extension —
publishing it is giving it a name.
my-extension/
├── main.ts # commands, keys, palettes — exactly as in your profile
├── plugins/ # lifecycle hooks — exactly as in your profile
├── extension.config.ts # marks the directory as an extension
└── package.json # name, version, description
Creating one
axon ext init @you/my-extension
Scaffolds the layout above, ready to run. main.ts registers at module scope, the same
as a profile's:
commands.register("mine", async () => {
tui.info("hello from my extension")
}, { description: "An example command" })
There is no setup() function and no default export to call. Importing the file is
loading it.
extension.config.ts is empty on purpose. It marks the directory as an extension —
main.ts cannot do that job, since a profile has one too — and it is where declarative
per-extension options will land if they are ever needed.
Installing
axon ext install @axon/vim # from the registry
axon ext install ./extensions/mine # a local directory
axon ext list
axon ext uninstall @axon/vim
Installing does two things: fetches the extension, then records it in
profile.config.ts. In that order — a config naming something that is not on disk would
report an error on every launch.
export default defineProfile({
extensions: [
"@axon/vim",
"./extensions/mine",
],
})
You can edit this file by hand. The array is load order, and load order decides which of two extensions wins a name collision — so moving an entry up gives it precedence.
Disable one without losing the entry:
extensions: [
{ source: "@axon/vim", enabled: false },
]
Uninstalling removes the config entry and leaves the files. The directory may be one you are writing, and deleting source is not recoverable.
Boot installs what is missing
Axon fetches anything your config declares but your machine does not have. Clone your dotfiles onto a new machine, launch Axon, and your extensions arrive on their own.
Publishing
axon ext publish
Extensions publish to the same registry as agents, modules, cognets and prompts, into the
same namespace. Names are scoped — @you/name — and versions are immutable.
axon ext init @you/git-tools
cd git-tools
# ... write main.ts
axon ext publish
Anyone can then install it:
axon ext install @you/git-tools
What an extension may do
Everything your own config can, and nothing more — the same seven globals, the same boundaries.
Worth stating plainly, because an extension runs on someone else's machine: it can drive
conversations, register commands, bind keys and read your agent list. It cannot read what
a model produced, and it cannot take a key the terminal needs. Built-ins always win a
collision, and so does the installing user's own main.ts, which loads first.
Reference
| Command | What it does |
|---|---|
axon ext init <name> | Scaffold a new extension |
axon ext prepare | Install dependencies and regenerate types |
axon ext publish | Publish to the registry |
axon ext install <source> | Install and record in profile.config.ts |
axon ext uninstall <source> | Stop loading it; files stay |
axon ext list | What your profile declares, in load order |
axon extension is an alias for axon ext throughout.
What's next
Your Config — the profile directory and the seven globals.
Publishing — how the registry, namespaces and versioning work.