main.ts

Your config. It runs at module scope on every launch, and on every reload.

commands.register("deploy", {
    async run() {
        await commands.run("agent restart")
    },
    description: "Restart and redeploy",
})

keys.register("ctrl+o", () => commands.run("session open"))

palette.create("branches", {
    key: "&",
    async list() {
        return (await gitBranches()).map(name => ({
            id: name,
            label: name,
            action: () => checkout(name),
        }))
    },
})

There is no setup() and no default export. Importing the file is loading itcommands.register(...) runs as the module body evaluates.

Loads first, so it wins

main.ts  →  plugins/*.ts  →  each extension, in profile.config.ts order

Your own config beats every extension. A collision throws, naming both sides — nothing is ever silently shadowed.

Split it up

// main.ts
import "./keybindings"
import "./commands/git"

An ordinary import. Anything reachable from here runs, which is how a large config stays navigable. (Do not import from plugins/ — those already auto-load.)

A throw does not cost you the terminal

commands.register("a", run)
throw new Error("boom")      // "a" stays registered
commands.register("b", run)  // never runs

Registrations made before the throw are kept, and the failure lands in the config error list on the chat page. You still get a working terminal — the one you need in order to fix it.

Reloading

:reload re-runs everything, tearing down the previous generation first. Saving the file does the same. Nothing accumulates across reloads.

See API for the eight globals.