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

CommandWhat it does
axon ext init <name>Scaffold a new extension
axon ext prepareInstall dependencies and regenerate types
axon ext publishPublish 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 listWhat 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.