Skip to content

Commands

A command adds an entry to Aero's command palette. Commands are the only contribution type that may carry code: declare the command in aero.json, and optionally register a handler in sandboxed JS via the aero.commands API.

1. Declare the command

json
"contributes": {
  "commands": [
    { "command": "dracula-warm.apply", "title": "Dracula Warm: Apply Theme" }
  ]
}
FieldRequiredConstraint
commandyes^[A-Za-z0-9_.-]+$. Unique within the IDE.
titleyes≥ 1 char. Shown in the palette.
  • Id convention: "<name>.<verb>", e.g. dracula-warm.apply. It must be unique across all installed extensions and built-ins.
  • The title is what users see and search for in the palette (Cmd/Ctrl+Shift+P).

2. Three ways a command behaves

SetupResult
Command + registered JS handlerRunning it activates the extension (if needed) and calls your handler.
Command + no handler + no mainNo-op — the palette still lists it.
Command bound to a built-in via keybindingThe keybinding triggers built-in behaviour.

So a command always appears in the palette; whether it does something depends on whether a handler is registered.

3. Add a handler (optional JS)

To make a command do something, set main in the manifest and register a handler in your entry file. Also declare the matching activation event so the host loads your JS lazily.

json
{
  "main": "extension.js",
  "activationEvents": ["onCommand:dracula-warm.apply"],
  "contributes": {
    "commands": [
      { "command": "dracula-warm.apply", "title": "Dracula Warm: Apply Theme" }
    ]
  }
}
js
// extension.js — runs sandboxed; only `aero.*` is available.
'use strict';

function activate(context) {
  const sub = aero.commands.registerCommand('dracula-warm.apply', async () => {
    await aero.theme.apply('Dracula Warm');
    aero.window.setStatusBarMessage('Dracula Warm applied', 2000);
  });
  context.subscriptions.push(sub);
}

function deactivate() {}

When the user runs dracula-warm.apply, the host:

  1. fires the onCommand:dracula-warm.apply activation event;
  2. loads extension.js in the sandbox and calls activate(context);
  3. invokes the handler you registered.

Sandbox boundary

extension.js runs in a sandboxed <iframe sandbox="allow-scripts"> — no DOM, no Node, no window.api, no arbitrary network. The only channel is the postMessage bridge exposing aero.*. The host enforces a method allow-list, a 256 KB arg cap, one in-flight activation per extension, and 5 s timeouts.

4. Calling other commands

Run any command — your own, another extension's, or a built-in — with aero.commands.executeCommand:

js
const result = await aero.commands.executeCommand('some-other.command', arg1);

5. The command lifecycle on the bus

For host/integrator reference, command activity surfaces on AeroBus:

  • command:run { command, args } — a command was invoked.
  • ext:activated { id } — an extension's main was loaded and activate ran.

Extension authors don't touch the bus directly (it's renderer-side, outside the sandbox), but these events are how the IDE wires the palette to your handler.

Lean, AI-ready, under your control.