Why this matters
local-jsx is the command kind for opening a screen or performing a UI-aware
state handoff. It is the branch you use when plain text is not enough, and the
command needs to show React UI before control returns to the session.
Unlike a text-only result, a local-jsx command needs onDone because the UI
has to tell the runtime when it is finished, what should happen next, and
whether any follow-up input should be sent. That is why these commands get
richer context instead of returning a plain text result.
Three representative cases
The three representative cases are /help, /context, and /plan.
/help is the smallest lazy UI command. It loads its implementation only when
the command is invoked, then renders a help screen and hands control back
through onDone.
import type { Command } from '../../commands.js'
const help = {
type: 'local-jsx',
name: 'help',
description: 'Show help and available commands',
load: () => import('./help.js'),
} satisfies Command
export default help
import * as React from 'react';
import { HelpV2 } from '../../components/HelpV2/HelpV2.js';
import type { LocalJSXCommandCall } from '../../types/command.js';
export const call: LocalJSXCommandCall = async (onDone, {
options: {
commands
}
}) => {
return <HelpV2 commands={commands} onClose={onDone} />;
};
/context shows the interactive and non-interactive split. The interactive
command is a local-jsx view, while the non-interactive version is a separate
local command that stays available when there is no live UI.
import { getIsNonInteractiveSession } from '../../bootstrap/state.js'
import type { Command } from '../../commands.js'
export const context: Command = {
name: 'context',
description: 'Visualize current context usage as a colored grid',
isEnabled: () => !getIsNonInteractiveSession(),
type: 'local-jsx',
load: () => import('./context.js'),
}
export const contextNonInteractive: Command = {
type: 'local',
name: 'context',
supportsNonInteractive: true,
description: 'Show current context usage',
get isHidden() {
return !getIsNonInteractiveSession()
},
isEnabled() {
return getIsNonInteractiveSession()
},
load: () => import('./context-noninteractive.js'),
}
/plan is the command that changes session mode and can also render the
existing plan content once the mode switch is done. Its loader advertises the
argument hint up front, and its implementation shows the mode transition path
before it falls through to rendering the saved plan.
import type { Command } from '../../commands.js'
const plan = {
type: 'local-jsx',
name: 'plan',
description: 'Enable plan mode or view the current session plan',
argumentHint: '[open|<description>]',
load: () => import('./plan.js'),
} satisfies Command
export default plan
if (currentMode !== 'plan') {
handlePlanModeTransition(currentMode, 'plan');
setAppState(prev => ({
...prev,
toolPermissionContext: applyPermissionUpdate(prepareContextForPlanMode(prev.toolPermissionContext), {
type: 'setMode',
mode: 'plan',
destination: 'session'
})
}));
const description = args.trim();
if (description && description !== 'open') {
onDone('Enabled plan mode', {
shouldQuery: true
});
} else {
onDone('Enabled plan mode');
}
return null;
}
const editor = getExternalEditor();
const editorName = editor ? toIDEDisplayName(editor) : undefined;
const display = <PlanDisplay planContent={planContent} planPath={planPath} editorName={editorName} />;
// Render to string and pass to onDone like local commands do
const output = await renderToString(display);
onDone(output);
return null;
Why immediate shows up here
Some UI-facing commands need to run right away instead of waiting in the queue.
The mcp command is a concrete example of that rule.
import type { Command } from '../../commands.js'
const mcp = {
type: 'local-jsx',
name: 'mcp',
description: 'Manage MCP servers',
immediate: true,
argumentHint: '[enable|disable [server-name]]',
load: () => import('./mcp.js'),
} satisfies Command
export default mcp
The main pattern is simple: local-jsx commands are for screens and handoffs,
not plain text. They start with a lazy loader, render a UI, and use onDone to
return control to the runtime when the UI work is finished.