Skip to content

Commands

Commands are the core unit of the extension platform. Every action in OpenConduit — new conversation, open settings, toggle the bottom panel — is a CommandContribution registered in the commandRegistry. Extensions add their own commands the same way built-in commands are registered.

Quick Start

ts
import { commandRegistry } from '@openconduit/core';

commandRegistry.register({
  id: 'my-extension.helloWorld',
  label: 'Hello World',
  shortcut: '⌘⇧H',
  keybinding: { key: 'h', mod: true, shift: true },
  action: () => {
    console.log('Hello from my extension!');
  },
});

The command immediately appears in the command palette (> mode) and its keyboard shortcut is active globally.

To remove the command (e.g. on extension deactivation):

ts
commandRegistry.unregister('my-extension.helloWorld');

CommandContribution Interface

ts
interface CommandContribution {
  /** Unique namespaced id, e.g. 'my-ext.doThing' */
  id: string;

  /** Label shown in the command palette */
  label: string;

  /** Short human-readable shortcut hint, e.g. '⌘⇧H' */
  shortcut?: string;

  /** Machine-readable keybinding for the global shortcut handler */
  keybinding?: {
    key: string;     // KeyboardEvent.key value, case-insensitive
    mod?: boolean;   // metaKey || ctrlKey
    shift?: boolean;
    alt?: boolean;
  };

  /** Icon rendered in the command palette result row */
  icon?: React.ReactNode;

  /**
   * The handler. Use store.getState() for runtime context —
   * actions run outside React component lifecycle.
   */
  action: () => void;

  /**
   * Optional visibility guard. When present, the command is hidden from
   * the palette and its keybinding is ignored when this returns false.
   */
  when?: () => boolean;
}

commandRegistry API

MethodDescription
register(cmd)Register a command. Re-registering the same id replaces the previous entry.
unregister(id)Remove a command by id.
get(id)Get a single command by id. Returns undefined if not found.
getAll()All registered commands in registration order.
execute(id)Execute a command by id. Respects the when guard — silently no-ops if it returns false.

Built-in Commands

All built-in commands are registered in src/commands/coreCommandContributions.ts at startup.

IDLabelShortcut
core.newConversationNew conversation⌘T
core.newConversationAlt(alias, hidden from palette)⌘N
core.toggleSidebarToggle sidebar
core.openSettingsOpen settings⌘,
core.openSettingsFileOpen settings.json(Electron only)
core.closeTabClose tab⌘W
core.compareModelsCompare models
core.toggleBottomPanelToggle bottom panel⌘J
core.openCommandPaletteOpen command palette⌘K (hidden from palette)

Context-sensitive Commands

Use the when guard to show a command only in certain states:

ts
import { commandRegistry } from '@openconduit/core';
import { useConversationStore } from '@openconduit/core';

commandRegistry.register({
  id: 'my-ext.exportActive',
  label: 'Export active conversation',
  when: () => !!useConversationStore.getState().activeConversationId,
  action: () => {
    const { activeConversationId, conversations } = useConversationStore.getState();
    // ...
  },
});

When when() returns false the command is hidden from the palette and its keybinding does nothing.

Writing Actions

Actions run outside React so you cannot use hooks. Use Zustand's getState() instead:

ts
// ✅ Correct — getState() works anywhere
import { useUiStore } from '@openconduit/core';

action: () => {
  useUiStore.getState().setShowSettings(true);
},

// ❌ Wrong — hooks only work inside React components
action: () => {
  const { setShowSettings } = useUiStore(); // runtime error
  setShowSettings(true);
},

Released under the AGPLv3 License.