Every named declaration in the file, 570 of them, verbatim and in the order Claude Code wrote them, 50 to a page. This is the page to land on from a signature: a verb that returns ToolCallResult links here, at whichever page it is on, and what you get is the declaration itself rather than a description of it.
The source is 13,820 lines of TypeScript mined from build 2.1.283. Anything longer than 12 lines is folded; click the line count to open it. To find a name on another page, search every symbol.
Names on this page, 51 to 100 of 570
ClientProps type
#
line 1313
Added in 2.1.267
The props of Client: which of the plugin's surface modules draws here, under what key, with what data, in how much room.
42 lines
export type ClientProps = {
/**
* The instance's address within the drawing: two `Client`s of one plugin in
* one tree take two keys. What `e.element` carries at `ui.message`.
*
* The engine keeps the instance (its local state, its timers) across the
* plugin's redraws while a `Client` under this key stays in the tree.
*/
key: string;
/**
* The surface module's path, a string literal relative to this file:
* `module: "./<name>.tsx"` (or `.jsx`, `.ts`, `.js`, `.mjs`).
*
* Read off the source: a variable there is refused at load, as is a path
* outside the plugin or naming no file. Its default export draws, else
* its one PascalCase export; loaded, the tree carries the plugin's path.
*/
module: string;
/**
* Plain data (JsonValue) handed to the module function; a new value on a
* redraw reaches the running instance, its state kept.
*
* Bounded as a tree's text is; not a channel for closures.
* Typed `unknown` so a matcher over a tree stays shallow.
*/
props?: unknown;
/**
* Columns the instance's region takes: a count, or a percentage of the
* parent. Absent, the region is as wide as what the module draws.
*/
width?: number | string;
/**
* Rows the instance's region takes: a count, or a percentage of the parent.
* Absent, the region is as tall as what the module draws.
*/
height?: number | string;
/**
* How the region grows into free room along the parent's direction, as a
* Box's `flexGrow`.
*/
flexGrow?: number;
};
ClientSurface type
#
line 1364
Added in 2.1.267
What a surface module's function receives as its second argument: its elements, the instance's local state, its region, input, clock and port.
Called again (same surface, same state) on new props, after setState, and on a resize; what it returns is drawn in the region. No $ here: the hooks module has it, and post is the way to reach it.
56 lines
export type ClientSurface<S = unknown> = {
/**
* The surface's element table (ClientElements), the tags the module draws
* with: `const { Box, Text } = surface.elements`. No `Client` in it.
*/
readonly elements: ClientElements;
/**
* The instance's local state: `undefined` until the first `setState`.
* Kept across the plugin's redraws; dropped with the instance.
*/
readonly state: S | undefined;
/**
* Replaces the local state and schedules one more call of the function on
* the next frame; several calls before it coalesce into one redraw.
*
* A `setState` on each of three renders in a row with no key, pointer,
* tick, press or props between is a render loop: the instance unmounts.
*/
setState: (next: S) => void;
/**
* The region's width in cells, as last laid out (0 before the first
* layout).
*/
readonly columns: number;
/**
* The region's height in cells, as last laid out (0 before the first
* layout).
*/
readonly rows: number;
/**
* Calls `fn` every `ms` milliseconds on the surface's frame clock until
* the returned function is called or the instance unmounts.
*
* Start it once (while `state` is still undefined), not on every call.
*/
every: (ms: number, fn: () => void) => () => void;
/**
* Sets the instance's pointer listener (one; a later call replaces it)
* and returns what clears it. See ClientPointerEvent for capture.
*/
onPointer: (fn: (event: ClientPointerEvent) => void) => () => void;
/**
* Sets the instance's key listener (one; a later call replaces it),
* reached while a click has given it the focus; Escape returns that.
*/
onKey: (fn: (event: ClientKeyEvent) => void) => () => void;
/**
* Sends plain data to the plugin's hooks module: `e.data` of a
* `ui.message` only that plugin's hooks see, one per frame at most.
*
* A later post in the same frame replaces an undelivered one; a hook
* answering `{ props }` hands this instance its next props. At most
* 20,000 values, 32 deep, 100,000 characters, or the post is not sent.
*/
post: (data: JsonValue) => void;
};
ClockWait type
#
line 1425
Added in 2.1.271
The argument of the $.clock waits (sleep, after, every): how long, in milliseconds, before the dispatch resolves.
type ClockWait = {
/**
* The wait, a non-negative number of milliseconds.
*/
ms: number;
};
CodeProps type
#
line 1440
Added in 2.1.265
The props of Code, source text every surface draws with the engine's own highlighter: coloured tokens, a line gutter on request, or a unified diff.
A leaf: no children. source is the element's data as a string is a Text's, bounded and free of control characters the same way; the colour on screen is the engine's, never the plugin's.
49 lines
export type CodeProps = {
/**
* The text drawn: source code, or under `format: 'diff'` one or more
* unified-diff hunks.
*
* At most 10000 characters; tab and newline are the only control
* characters it may hold.
*/
source: string;
/**
* A highlighter language id or alias (`typescript`, `ts`, `py`), a
* plugin-contributed grammar's included.
*
* Absent, the language is inferred from `path`; when neither resolves,
* the text is drawn plain.
*/
language?: string;
/**
* A file path the language is inferred from when `language` is absent:
* its extension or name, else a shebang on the first line.
*
* Drawn nowhere and never read: nothing touches the disk.
*/
path?: string;
/**
* The 1-based number of the first line of `source`: present, a dim
* right-aligned gutter numbers the lines from it; absent, no gutter.
*
* Ignored under `format: 'diff'`, whose hunks carry their own numbers.
*/
startLine?: number;
/**
* `'source'` (the default) draws `source` as code; `'diff'` reads it as
* unified-diff hunks and draws gutters, markers, add and remove colouring.
*
* A hunk is `@@ -a,b +c,d @@` then lines starting ` `, `+` or `-`; a
* leading `---`/`+++` pair and `\ No newline at end of file` are read
* past. A source that does not parse as hunks is refused.
*/
format?: 'source' | 'diff';
/**
* What a line wider than the room does, in Text's `wrap` vocabulary:
* `'wrap'` (the default) continues it on rows under the gutter.
*
* `'truncate-end'` cuts it at the edge with an ellipsis; Text's other
* spellings are refused, since a gutter leaves them no sense here.
*/
wrap?: 'wrap' | 'truncate-end';
};
CommandDescribeInput type
#
line 1494
Added in 2.1.265 · changed in 2.1.267, 2.1.268
The input of command.describe: how one slash command presents in the typeahead and /help, at the moment the engine lists it.
33 lines
export type CommandDescribeInput = {
/**
* Names the command without its slash; the key a matcher narrows on. A
* rewrite is refused.
*/
command: string;
/**
* The one-line description the menu shows, as the command declares it.
*/
description: string;
/**
* The hint drawn dim after the name (`[name]`), when the command has one.
*/
argumentHint?: string;
/**
* Whether the typeahead and `/help` leave the command out; a hidden
* command still runs when typed in full.
*/
isHidden: boolean;
/**
* Whether the command declares it runs at once when typed mid-turn,
* instead of waiting for the turn to end.
*
* One that decides per invocation reads false. Read only: not part of
* what a hook answers, and a rewrite is refused.
*/
immediate: boolean;
/**
* Who provides this command: the plugin and its tier; `{ plugin: "engine",
* tier: "core" }` for a built-in. Pinned: a rewrite is refused.
*/
provider: Origin;
};
CommandDescribeResult type
#
line 1532
Added in 2.1.265 · changed in 2.1.267, 2.1.268
What a command.describe hook returns: the description, hint and hidden flag the menu uses; the name, immediate and provider stay as they were.
export type CommandDescribeResult = Omit<CommandDescribeInput, 'command' | 'immediate' | 'provider'>;
CommandInfo type
#
line 1537
Added in 2.1.265 · changed in 2.1.268
One slash command as $.command.list() returns it.
19 lines
export type CommandInfo = {
/**
* What the person runs it by, without the slash.
*/
name: string;
/**
* The one line the typeahead shows for it.
*/
description: string;
/**
* Where it comes from (CommandSource).
*/
source: CommandSource;
/**
* Which plugin added it, when `source` is `plugin` and the engine knows:
* the one that registered it, or whose manifest carries it.
*/
plugin?: string;
};
CommandPresentation type
#
line 1566
Added in 2.1.271
Where a command's answer will show: which of the terminal's two layouts the surface renders, and how wide it is when the command runs.
A fact the engine stamps on command.run, so a command that draws (opens a pane, prints a wide table) can suit the room it has: the fullscreen layout docks a pane beside the transcript from 110 columns, the main screen places it inline above the prompt at any width.
15 lines
export type CommandPresentation = {
/**
* True under the fullscreen (alternate-screen) layout; false on the main
* screen (`CLAUDE_CODE_NO_FLICKER=0`, tmux by default) and headless.
*
* The fact `RenderViewport`'s `isFullscreen` carries on every terminal
* drawing, from the same source: the two agree.
*/
isFullscreen: boolean;
/**
* The terminal's width in cells as the command runs; 80 where no terminal
* has measured (headless with no tty).
*/
columns: number;
};
CommandRunArgs type
#
line 1586
Added in 2.1.265 · changed in 2.1.268, 2.1.269, 2.1.271
command.run's input as a plugin's $.command.run takes it: args may be left out (/command, bare); origin and presentation the engine sets.
export type CommandRunArgs = Omit<CommandRunInput, 'origin' | 'args' | 'presentation'> & {
/**
* Everything after the name, as the person would type it; left out, `""`.
*/
args?: string;
};
CommandRunInput type
#
line 1597
Added in 2.1.265 · changed in 2.1.268, 2.1.271
The input of command.run: one slash command about to run, the way the person typed it (/name args), and where the run came from.
28 lines
export type CommandRunInput = {
/**
* Names the command without its slash (`compact`, `hello`), aliases and
* folds resolved; the key a matcher narrows on. A rewrite is refused.
*/
command: string;
/**
* Everything after the name, as typed (`""` when nothing was); a hook
* rewrites it with `next({ ...e, args })`.
*/
args: string;
/**
* Where the run came from, in `prompt.submit`'s words (PromptOrigin):
* the person's Enter (`composer`), the bridge, the SDK, or a plugin.
*
* A plugin's `$.command.run` reads `{ kind: 'plugin', name }`. `next(e)`
* passes it on as received.
*/
origin: PromptOrigin;
/**
* Where the command's answer will show (CommandPresentation): the
* fullscreen layout or the main screen, and the terminal's width.
*
* Pinned: the engine stamps it, `next(e)` passes it on, a rewrite that
* leaves it out keeps it and one that changes it is refused.
*/
presentation: CommandPresentation;
};
CommandRunResult type
#
line 1642
Added in 2.1.265 · changed in 2.1.268, 2.1.274
What a command.run hook returns and what next(e) and $.command.run resolve to: the command's output text and the notes it leaves the model.
From core, text is what the command printed (a local command's returned text; a panel command may print nothing), context what it recorded for the model beside that, and ref names the run.
A hook's own answer without next runs no command: its text is shown as the command's output, under the names of the plugins hooking the command unless each is bundled with Claude Code, whose answer reads as the built-in command's own, and its context is recorded after it.
Both are the plugin's to size, as a core command's output is; what bounds them is what bounds any transcript row where a surface draws it.
24 lines
export type CommandRunResult = {
/**
* The command's output as a transcript line, or undefined when the
* command showed nothing as text (a panel, a prompt for the model).
*/
text?: string;
/**
* What the model reads after the command's output and the person never
* sees, each entry one hidden user message recorded after the output row.
*
* From core, the notes the command left the model, absent when none. Kept
* whole from `next`: left out after `next`, the last answer's notes ride
* along; written, it keeps every entry that answer had (none empty).
*/
context?: readonly string[];
/**
* Set by core on what `next(e)` resolves to: names the engine's run of
* the command (its result stays on the host side).
*
* A hook that returns the object it got makes the engine use that run
* verbatim. Absent on a hook's own `{ text }` and on `$.command.run`'s.
*/
ref?: number;
};
CommandSource type
#
line 1674
Added in 2.1.265 · changed in 2.1.268
Where a slash command comes from, as $.command.list() tells them apart.
builtin ships with Claude Code; plugin is a plugin's markdown command, skill or $.command.register; user is the user's or project's own file; mcp an MCP server's prompt.
export type CommandSource = 'builtin' | 'plugin' | 'user' | 'mcp';
CommandSpec type
#
line 1679
Added in 2.1.265 · changed in 2.1.268
What $.command.register takes: the slash command this plugin serves.
24 lines
export type CommandSpec = {
/**
* The command's name without the slash (letters, digits, `_`, `-`; up to
* 64); the person runs it as `/<name>`.
*/
name: string;
/**
* The one line the typeahead and `/help` show for it.
*/
description: string;
/**
* The hint drawn dim after the name (`[name]`), when it takes arguments.
*/
argumentHint?: string;
/**
* Set so that `/<name>` typed while a turn is in flight runs at once
* instead of waiting for the turn to end, as it does when left out.
*
* Its `command.run` hook then runs while a turn may still be streaming and
* must not assume the turn's state (what the transcript holds, whether a
* tool is mid-call); its `{ text }` prints as an idle run's does.
*/
immediate?: true;
};
ConfigChangeHookInput type
#
line 1704
Added in 2.1.265
type ConfigChangeHookInput = BaseHookInput & {
hook_event_name: 'ConfigChange';
source: 'user_settings' | 'project_settings' | 'local_settings' | 'policy_settings' | 'skills';
file_path?: string;
};
ConfigDescribeInput type
#
line 1714
Added in 2.1.269
The input of config.describe: how one /config row presents, at the moment the menu lists it (and for $.config.list).
25 lines
export type ConfigDescribeInput = {
/**
* Names the row, as `config.set`'s `key` does; the key a matcher narrows
* on. Pinned: a rewrite is refused.
*/
key: string;
/**
* What the menu draws for the row, before its value.
*/
label: string;
/**
* The help text beneath the label, when the row has one (a plugin
* field's `description`); absent for the panel's own rows.
*/
description?: string;
/**
* Whether the menu leaves the row out; a hidden row still answers
* `$.config.set` and `/config key=value`.
*/
isHidden: boolean;
/**
* Who owns the row, as `config.set`'s `provider`. Pinned.
*/
provider: Origin;
};
ConfigDescribeResult type
#
line 1744
Added in 2.1.269
What a config.describe hook returns: the label, help text and hidden flag the menu uses; the key and provider stay as they were.
export type ConfigDescribeResult = Omit<ConfigDescribeInput, 'key' | 'provider'>;
ConfigKind type
#
line 1750
Added in 2.1.269
How a /config row takes its value: boolean toggles, choice picks one of its options, text takes a string, number a number.
export type ConfigKind = 'boolean' | 'choice' | 'text' | 'number';
ConfigOrigin type
#
line 1758
Added in 2.1.269
Where a config.set came from, in prompt.submit's words: the person in the /config menu (composer), the bridge, or a plugin, named.
Set by the engine; next(e) passes it on as received and none sets it.
22 lines
export type ConfigOrigin = {
/**
* The person's own change in the `/config` menu (a toggle, a pick, a
* typed value), or their `/config key=value`.
*/
kind: 'composer';
} | {
/**
* A `/config key=value` that arrived over the Remote Control bridge (a
* phone or web client, or a relay): not attestably the owner's hand.
*/
kind: 'bridge';
} | {
/**
* A plugin's `$.config.set`; that plugin's own hooks do not see it.
*/
kind: 'plugin';
/**
* The calling plugin's name.
*/
name: string;
};
ConfigRow type
#
line 1785
Added in 2.1.269
One /config row as $.config.list() returns it: what the menu would draw now, after every config.describe hook, a hidden row left out.
37 lines
export type ConfigRow = {
/**
* Names the row: the panel's id for a built-in, `<plugin>.<field>` for a
* plugin's `userConfig` field; what `$.config.set` takes.
*/
key: string;
/**
* What the menu draws for the row, before its value.
*/
label: string;
/**
* The help text under the label, when the row has one.
*/
description?: string;
/**
* How the row takes its value (ConfigKind).
*/
kind: ConfigKind;
/**
* What the row holds now.
*/
value: ConfigValue;
/**
* The values a `choice` row takes, in order (a plugin string field's
* declared `options` for its row); absent otherwise.
*/
options?: readonly string[];
/**
* Who owns the row: the engine for the panel's own, else the plugin.
*/
provider: Origin;
/**
* Whether a trusted source (managed settings, the organization's policy)
* owns the value, so the menu shows it and refuses a change.
*/
isLocked: boolean;
};
ConfigSetArgs type
#
line 1827
Added in 2.1.269
config.set's input as a plugin's $.config.set(args) takes it: the row's key and the value; the engine fills the rest.
export type ConfigSetArgs = Pick<ConfigSetInput, 'key' | 'value'>;
ConfigSetInput type
#
line 1833
Added in 2.1.269
The input of config.set: one /config row about to change, from the menu or a plugin's $.config.set, with what it holds now and who owns it.
28 lines
export type ConfigSetInput = {
/**
* Names the row: the panel's own id for a built-in (`theme`, `verbose`,
* `autoCompact`), `<plugin>.<field>` for a plugin's `userConfig` field.
*
* The key a matcher narrows on; pinned: a rewrite is refused.
*/
key: string;
/**
* What the row is being set to; `next({ ...e, value })` clamps or
* replaces it, held to the row's kind (a toggle takes a boolean).
*/
value: ConfigValue;
/**
* What the row holds before the change, as the menu shows it. Pinned.
*/
previous: ConfigValue;
/**
* Who owns the row: `{ plugin: "engine", tier: "core" }` for the panel's
* own, the plugin and its tier for a `userConfig` field. Pinned.
*/
provider: Origin;
/**
* Where the change came from (ConfigOrigin): the person in the menu
* (`composer`) or a plugin's `$.config.set`. Pinned.
*/
origin: ConfigOrigin;
};
ConfigSetResult type
#
line 1869
Added in 2.1.269
What a config.set hook returns and what next(e) resolves to: { value } once written, or { deny: reason }, the row left as it was.
The menu shows a deny's reason beside the row; a plugin's $.config.set resolves with it.
export type ConfigSetResult = {
value: ConfigValue;
deny?: undefined;
} | {
deny: string;
value?: undefined;
};
ConfigValue type
#
line 1881
Added in 2.1.269
A /config row's value as a hook and $.config see it: a toggle's boolean, a choice's or a text's string, a number, or a list of strings.
export type ConfigValue = boolean | string | number | readonly string[];
ContextAgent type
#
line 1887
Added in 2.1.271
One custom agent whose description the Agent tool's prompt carries; built-in agents are left out.
15 lines
export type ContextAgent = {
/**
* The agent's type, as the Agent tool names it.
*/
agentType: string;
/**
* Where it was defined, by the engine's word (`projectSettings`,
* `userSettings`, `plugin`); the display label is the renderer's.
*/
source: string;
/**
* The description's estimated tokens.
*/
tokens: number;
};
ContextBreakdownDetail type
#
line 1909
Added in 2.1.271
How a context breakdown is counted: full with the token-count API per category, summary from the last response's usage and local estimates.
The SDK's get_context_usage takes the same two words as its detail.
export type ContextBreakdownDetail = 'summary' | 'full';
ContextCategory type
#
line 1915
Added in 2.1.271
One row of the breakdown, as /context lists it beside the grid (System prompt, Messages, Free space, Autocompact buffer).
25 lines
export type ContextCategory = {
/**
* The row's label as /context prints it.
*/
name: string;
/**
* The row's estimated tokens; a `deferred` row's do not count toward the
* total.
*/
tokens: number;
/**
* The theme colour /context draws the row and its squares in, by its key
* in the theme (`promptBorder`, `inactive`, `permission`).
*/
color: string;
/**
* Whether the row is tool schemas loaded on demand, which the grid leaves
* out; the same fact as `kind` `deferred`.
*/
isDeferred: boolean;
/**
* What the row is (ContextCategoryKind), stamped by the engine.
*/
kind: ContextCategoryKind;
};
ContextCategoryKind type
#
line 1948
Added in 2.1.271
What a breakdown row is; branch on this, never on the row's name.
used content occupies the window, free is the window left, buffer the compaction reserve, deferred tool schemas loaded on demand and outside the window.
export type ContextCategoryKind = 'used' | 'free' | 'buffer' | 'deferred';
ContextGridSquare type
#
line 1954
Added in 2.1.271
One square of the grid /context draws: which row it belongs to and how full it is.
28 lines
export type ContextGridSquare = {
/**
* The theme colour of the square's row, by its key in the theme.
*/
color: string;
/**
* Whether the square holds any of its row's tokens.
*/
isFilled: boolean;
/**
* The `name` of the row the square belongs to (`Free space` for the
* window left).
*/
categoryName: string;
/**
* The row's tokens, repeated on each of its squares.
*/
tokens: number;
/**
* The row's share of the window as a whole percentage, repeated likewise.
*/
percentage: number;
/**
* How full this one square is, 0 to 1: a row's last square is the partial
* one (/context draws it hollow under 0.7).
*/
squareFullness: number;
};
ContextMcpTool type
#
line 1986
Added in 2.1.271
One MCP tool's schema as the context carries it.
19 lines
export type ContextMcpTool = {
/**
* The tool's wire name (`mcp__linear__create_issue`).
*/
name: string;
/**
* The server it belongs to, as /mcp lists it.
*/
serverName: string;
/**
* The schema's estimated tokens.
*/
tokens: number;
/**
* Whether the schema is inside the window now: always, unless tool
* schemas load on demand and this one has not been searched for yet.
*/
isLoaded: boolean;
};
ContextMemoryFile type
#
line 2010
Added in 2.1.271
One memory file the context carries (a CLAUDE.md, a rules file, an auto-memory entry).
15 lines
export type ContextMemoryFile = {
/**
* The file's path, absolute.
*/
path: string;
/**
* The display label of where it was loaded from (`Project`, `User`,
* `Local`, `Managed`, `AutoMem`).
*/
type: string;
/**
* The file's estimated tokens.
*/
tokens: number;
};
ContextSkill type
#
line 2029
Added in 2.1.271
One skill whose listing the context carries.
19 lines
export type ContextSkill = {
/**
* The skill's name as `/skills` lists it.
*/
name: string;
/**
* Where it came from, by the engine's word (`userSettings`, `plugin`,
* `built-in`, `mcp`, `syncedSkills`); the display label is the renderer's.
*/
source: string;
/**
* The providing plugin's name, when the skill comes from one.
*/
pluginName?: string;
/**
* The listing's estimated tokens.
*/
tokens: number;
};
ContextSkills type
#
line 2053
Added in 2.1.271
The skills the context lists for the model: how many there are, how many fit the listing's budget, and each one's share.
18 lines
export type ContextSkills = {
/**
* How many skills the session has.
*/
totalSkills: number;
/**
* How many the listing included within its token budget.
*/
includedSkills: number;
/**
* The listing's tokens in all.
*/
tokens: number;
/**
* One entry per listed skill.
*/
skillFrontmatter: ContextSkill[];
};
ContextSlashCommands type
#
line 2075
Added in 2.1.271
The slash commands the Skill tool's prompt lists, counted.
14 lines
export type ContextSlashCommands = {
/**
* How many commands the session has.
*/
totalCommands: number;
/**
* How many the listing included.
*/
includedCommands: number;
/**
* The listing's tokens in all.
*/
tokens: number;
};
ContextWindowSource type
#
line 2098
Added in 2.1.271
How the window the breakdown measures against was settled; /context prints its Auto-compact window line off this.
auto is the model's own limit; the rest name a compaction window by who set it: the CLAUDE_CODE_AUTO_COMPACT_WINDOW variable, the settings, the account, an experiment, the model's default, or an unrecognised model's.
export type ContextWindowSource = 'env' | 'settings' | 'clientdata' | 'experiment' | 'model-default' | 'unknown-model' | 'auto';
CoreEngineInterface interface
#
line 2107
In the first published surface (2.1.259)
The plugin's identity (plugin) and the nouns core contributes to $ as the innermost step of the engine.create fold.
A plugin's finished $ (EngineInterface) has every core noun an outer step did not withhold, and every noun the plugins' steps added.
1158 lines
export interface CoreEngineInterface {
/**
* This plugin, as loaded: its manifest name and its directory.
*/
plugin: {
/**
* From plugin.json; debug-log and `$.ui.log` lines carry it.
*/
name: string;
/**
* The plugin's directory (the one holding plugin.json), absolute.
*/
root: string;
};
/**
* Display: a line under an open dialog, a redraw or a repaint, a log line,
* panes the surface places, a window or ring, a surface's clipboard.
*/
ui: {
/**
* Shows `text` as one line under the dialog open for `tool_use_id`, or
* removes the line when `text` is undefined.
*
* Core removes the line when the call resolves. A call that is not open
* is refused, as is another plugin's `$.tool.call` run.
*
* @param tool_use_id the call whose open dialog gets the line
* @param text the line to show; undefined removes it
* @example
* $.ui.notice(e.tool_use_id, "checked by my-plugin"); return next(e)
*/
notice: (tool_use_id: string, text: string | undefined) => void;
/**
* Re-runs an event whose results the engine caches: `ui.render` draws the
* instances this plugin may draw again; the others drop the cached answers.
*
* A render hook whose state changed (a countdown) calls it for a redraw, at
* most ten a second, thirty for the shown pane and the band (calls sooner
* fold); a prompt section, context or attachment hook: dropped next turn.
*
* @param event `ui.render`, or a cached-answer event: `prompt.section`,
* `prompt.context`, `prompt.attachment`, `tool/command/config.describe`
*/
invalidate: (event: InvalidatableEventName) => void;
/**
* Repaints a mounted `Raster` this plugin's own render hook drew with new
* cells, or swaps a keyed `Image` it drew to a new source; no redraw.
*
* The surface paints the cells or source at its next frame, so blits
* between frames fold into one: up to 120 a second taken, some sixty
* shown. An Image swap sends one small command; a resize is a redraw.
*
* @param args `requestId` (the site), `key`, and `cells` (RasterProps) or
* `source` (ImageSource); `columns`, `rows` (the mounted size)
* @returns `{}` once the cells or source are its next frame, or `{ deny }`
* (not mounted, not this plugin's, another size, cells that do
* not decode, a bad source, or an Image drawing its alt there:
* no placeholder images, or a file this terminal cannot read)
* @example
* $.clock.every(33, () => $.ui.blit({ requestId, key, cells: frame() }))
* @example
* await $.ui.blit({ requestId: 'browser', key: 'view',
* source: { shm: name, format: 'rgb', width, height } })
*/
blit: (args: UiBlitArgs) => Promise<UiBlitResult>;
/**
* The elements of the surface `e` is drawn on (Elements[e.surface]): a
* frozen table of constructors, the JSX tags a render hook draws with.
*
* A read, not a dispatch: the engine ran `ui.resolve` (the other hooks,
* then core) at load, per surface and component. Narrowed `e.surface`:
* that table exactly; unnarrowed: the union, so shared names type-check.
*
* @param e this hook's own `ui.render` argument (its surface and
* component pick the table)
* @returns the surface's frozen element table (`Elements[e.surface]`)
* @example
* const { Box, Text } = $.ui.resolve(e)
* return <Box>{await next(e)}<Text dimColor>done</Text></Box>
*/
resolve: <E extends ResolveInput>(e: E) => Elements[E['surface']];
/**
* Appends one line to the transcript, drawn like a system notice (dim;
* not sent to the model), or with `{ to: "debug" }` to the debug log alone.
*
* A row of its own at the next frame, in logging order; a `-p` or SDK
* host receives it as `ui_log`; the debug log has every line under this
* plugin's name. Raised as `ui.log`: a hook above may rewrite `e.to`.
*
* @param text the line's text
* @param options `to`: `transcript` (the default) or `debug`
* @example
* $.ui.log(`prompt from ${e.origin.kind}: ${e.text.length} chars`)
* @example
* $.ui.log(`cache miss for ${e.tool_use_id}`, { to: "debug" })
*/
log: (text: string, options?: UiLogOptions) => void;
/**
* Asks the user `question` in the engine's own AskUserQuestion dialog and
* resolves to the label they chose, or the text typed under "Other".
*
* A `tool.call` of `AskUserQuestion` through every hook but the calling
* one, drawn by `ui.render` on `AskUserQuestion`; a multi-select answer is
* comma-joined. Rejects when dismissed, and in a `-p` run (no one to ask).
*
* @param question the question, ending in a question mark
* @param options 2-4 option labels, or `{ options, header, multiSelect }`
* @returns the label chosen, the chosen labels comma-joined, or free text
* typed under "Other" (so compare it with the labels exactly)
* @example
* const mood = await $.ui.ask("How careful?", ["Bold", "Careful"])
*/
ask: (question: string, options?: readonly string[] | AskOptions) => Promise<string>;
/**
* Shows `text` for a few seconds under the plugin's name: a small box on
* the stack of plugin toasts over the transcript's top right corner.
*
* A click takes it off, the pointer over it holds it. Where the transcript
* is printed into scrollback (nothing to float over) it is one line on the
* notification bar. It leaves the transcript and the model untouched.
*
* @param text the line to show; an unpaired surrogate half in it is drawn
* as U+FFFD
* @param options `timeoutMs`: how long it stays (default 4000)
* @example
* $.ui.toast(`turn took ${Math.round(e.durationMs / 1000)} s`)
*/
toast: (text: string, options?: ToastOptions) => void;
/**
* Pins `text` as this plugin's status line under the prompt, beside the
* engine's own pinned notices, until the next call replaces it.
*
* One per plugin; `undefined` removes it.
*
* @param text the line to keep on screen (an unpaired surrogate half is
* drawn as U+FFFD); undefined clears it
* @example
* $.ui.status("thinking..."); return next(e)
*/
status: (text: string | undefined) => void;
/**
* Opens a pane, a framed region the surface places and this plugin draws
* by hooking `ui.render` for `{ component: "Pane" }`; says if it is drawn.
*
* One per id (an open id retitles; a `ui.open` hook may refuse). Asked,
* the person's command, prompt or press behind it (not `focus`), it is
* placed at any width; unasked, from 144 columns, 110 for one once asked.
*
* @remarks Below that it waits undrawn (`$.ui.panes()`: `isPlaced` false)
* until they open it or the terminal widens; a `-p` run places all.
* @param pane `id` (1-64 of letters, digits, `_`, `-`), `title`, `focus`,
* `closeOnEscape` and `holdToasts` (a dialog), `rows` / `columns` wanted
* @returns `{ isPlaced: true }` once drawn (or retitled), else `{ isPlaced:
* false, reason }` (UiOpenResult); `dock` or `inline` is on `Pane` props
* @example
* await $.ui.open({ id: "clock", title: "Clock" })
* @example
* await $.ui.open({ id: "ask", focus: true, closeOnEscape: true, rows: 9 })
* @example
* const opened = await $.ui.open({ id: "clock" })
* if (!opened.isPlaced) $.ui.toast("clock: widen the terminal to see it")
*/
open: (pane: PaneOpenArgs) => Promise<UiOpenResult>;
/**
* Closes one of the open panes; an id that is not open is left alone.
*
* Every close raises `ui.close`, `e.origin` naming whose it is: this call
* (`plugin`), the person's mark or key (`person`), an unload (`unload`).
* A hook answering without `next` keeps the pane open, save on an unload.
*
* @param pane `id`: the id the pane was opened under
* @returns settles once the pane is gone or a hook answered for it;
* rejects on a hook's `{ deny }`
* @example
* onPress: () => $.ui.close({ id: "clock" })
*/
close: (pane: PaneCloseArgs) => Promise<void>;
/**
* Lists this plugin's own open panes (UiPane): each one's id and title,
* and whether it is shown, holds the keyboard, and is placed.
*
* The engine's record, not the module's: a module reloaded while its
* pane stayed up finds it here. Another plugin's panes are not listed.
*
* @returns the panes in open order, placed ones first; empty with none
* @example
* const isUp = (await $.ui.panes()).some(pane => pane.id === "clock")
* if (!isUp) await $.ui.open({ id: "clock", title: "Clock" })
*/
panes: () => Promise<readonly UiPane[]>;
/**
* Scrolls something into view as the DOM's `scrollIntoView` would: a
* render instance by `requestId`, an element by `key`, a site's edge.
*
* A site of this plugin's (its pane, the band it draws into) moves under
* the event `ui.scroll`, origin `plugin`. A transcript row moves only
* while this call answers the person's own input, where one scrolls.
*
* @param args `to` (what), `in` (which site, required for `start` and
* `end`), `block` (where it lands; `nearest` by default)
* @returns `{}` once it moved, or `{ deny }` saying why not
* @example
* onPress: () => $.ui.scroll({ in: "log", to: "end" })
*/
scroll: (args: UiScrollArgs) => Promise<UiScrollResult>;
/**
* Moves the focus ring of one of this plugin's sites onto an element it
* drew there, as the DOM's `element.focus()`, while it holds the keys.
*
* Raised as `ui.focus`, origin `plugin`; the engine's inverse marks it.
* A site not holding the keys, or holding them on another's element, is
* `{ deny }`; an element not yet drawn is awaited, bounded: no retrying.
*
* @param args `requestId` (which site: a pane's id, the band's) and `key`
* (the element's, as drawn)
* @returns `{}` once it moved, or `{ deny }` saying why not
* @example
* await $.ui.open({ id: "files", focus: true })
* await $.ui.focus({ requestId: "files", key: "row:0" })
*/
focus: (args: UiFocusArgs) => Promise<UiFocusResult>;
/**
* Puts `text` on the clipboard of a surface the session draws on, as the
* DOM's `navigator.clipboard.writeText`, and says whether it took.
*
* `surface` names which (a press hook passes `e.surface`); left out, the
* session's first. Raised as `ui.copy` with that target on `e`. The
* terminal writes as `/copy` does; a remote surface has no path yet.
*
* @param args `text`, what the person pastes; `surface`, where
* @returns `{ isCopied: true }`, or `{ isCopied: false, reason }`
* @example
* onPress: press => $.ui.copy({ text: url, surface: press.surface })
*/
copy: (args: UiCopyArgs) => Promise<UiCopyResult>;
};
/**
* Completions through the session's own client and credentials.
*/
model: {
/**
* Runs one text completion through the session's own API client and
* resolves a result: the reply's text and cost, or why there is no text.
*
* No tools, no history, no system prompt beyond the CLI's identity block
* and `request.system`; ModelCompleteRequest says the rest. Only a request
* the engine refuses to send (a blocked model, a bad cap) rejects.
*
* @param request the model (an alias such as `haiku`, or a full id
* resolved like `--model`), prompt, cap, effort, time limit
* @returns the reply (`isAnswered`, `text`, `usage`), else the `reason`:
* `api-error` with `status` and `error`, `empty-reply`, `aborted`
* @example
* const r = await $.model.complete({ model: "haiku", prompt, effort })
*/
complete: (request: ModelCompleteRequest) => Promise<ModelCompleteResult>;
/**
* Runs one tool-less completion over the session's OWN transcript as the
* main thread last sent it, so the API serves that prefix from its cache.
*
* The main thread's last request again (its model, system prompt, tools,
* messages) with `prompt` after it: every tool denied, its own tail never
* cached, the prefix billed afresh once the entry lapsed or after `/model`.
*
* @param request the one user message the fork answers
* @returns always a result, never null: the reply (`isAnswered`, `text`,
* `usage`), or why there is no text (`reason`: `nothing-to-fork`
* before the first response and after `/clear`, `api-error` with
* its HTTP `status` and `error` kind, `empty-reply`, or `aborted`
* when the turn whose hook forked was interrupted)
* @example
* const reply = await $.model.fork({ prompt: "One line to learn?" })
* $.ui.log(reply.isAnswered ? reply.text : `no reply: ${reply.reason}`)
*/
fork: (request: ModelForkRequest) => Promise<ModelForkResult>;
/**
* Picks one of `labels` for `text` with one completion over
* `$.model.complete` and a fixed classifier prompt.
*
* `text` is data; the model answers with a label alone. It resolves
* `undefined` when the answer named none of `labels`. A failed request,
* an abort or a reply with no text rejects, naming the cause.
*
* @param text what to classify
* @param labels the labels to choose from (2 or more)
* @param options `model`: an alias or id; default the engine's small
* fast model
* @returns the label the model named, or undefined when it named none;
* rejects when the request fails or the reply has no text
* @example
* const kind = await $.model.classify(e.text, ["bug", "feature"])
* if (kind === undefined) return next(e)
*/
classify: (text: string, labels: readonly string[], options?: ClassifyOptions) => Promise<string | undefined>;
};
/**
* Sound: clip playback and platform speech.
*/
audio: {
/**
* Plays one audio clip, starting now; clips are not queued, so two calls
* play together (a bed under speech).
*
* `{ asset }` is the plugin's own file, loaded by the engine and played
* through the platform's player (`afplay` on macOS). Resolves when
* playback ends; rejects, naming the cause, when the clip cannot play.
*
* @param clip the plugin's own file (`{ asset }`), a URL the engine
* fetches, or the bytes as base64 with their MIME type
* @param options `shouldLoop`, `gain`, and an AbortSignal that stops the
* clip
*/
play: (clip: AudioClip, options?: PlayOptions) => Promise<void>;
/**
* Speaks `text` with the platform's own synthesizer (`say` on macOS).
* Plain text.
*
* Utterances are queued among themselves; clips are not. Resolves when
* the utterance has ended; rejects, naming the cause, when there is no
* synthesizer, the voice is not installed, or the utterance failed.
*
* @param text what to say, as plain text
* @param options `voice`: the system voice's exact name (`Samantha`);
* absent, the synthesizer's default
* @returns which synthesizer spoke, once the utterance has ended
*/
speak: (text: string, options?: SpeakOptions) => Promise<SpeakResult>;
};
/**
* The engine's connected MCP servers.
*/
mcp: {
/**
* Calls `tool` on one of the engine's connected MCP servers with the
* engine's own connection and credentials.
*
* A `cached` server is dialed on first use. No permission prompt: the
* plugin's call, seen by the hooks above it, is the grant. Positional, not
* the `{ tool: "mcp__server__tool", ... }` shape a `tool.call` hook sees.
*
* @param server the server's name as /mcp lists it (`claude.ai Gmail`;
* the tool-name spelling `claude_ai_Gmail` is accepted too)
* @param tool the tool's name on that server (`create_draft`)
* @param args the tool's arguments; none when absent
* @returns the tool's result as MCP returns it: `content` blocks and
* `isError`
* @example
* const { content } = await $.mcp.call("claude.ai Gmail", "create_draft", {
* to: "[email protected]",
* subject: "Release notes",
* })
*/
call: (server: string, tool: string, args?: Record<string, unknown>) => Promise<McpToolResult>;
};
/**
* The running session, read as plain data; compacting it; and sending a
* message from it to another agent or session.
*/
session: {
/**
* Returns the transcript so far, one SessionMessage per user or assistant
* message; progress rows, `$.ui.log` lines and notices are not messages.
*
* With `{ agentId }`, one of this session's agents' instead (a subagent, a
* fork, a teammate in this process): what the session saved for it joined
* with what the engine holds. With `{ as: "api" }`, either as ApiMessage.
*
* @param args `agentId`, the id `tool.call`, `turn.complete` and `$.agent
* .list()` carry; `as: "api"` for ApiMessage; hooks see both
* @returns the newest 4096 entries, `{ role, text, toolUses }` (a user
* message may add `toolResults`; a `toolUses` entry adds `result`
* and `text` once answered, an Agent tool use its `agentId`), or
* with `as` `{ role, content }`, blocks intact, at most 4096
* opening on a user message; for an `agentId` the session
* cannot read, `{ deny }` (SessionMessagesDeny), never main
* @example
* const last = (await $.session.messages()).at(-1)
* @example
* const request = await $.session.messages({ as: "api" })
* await $.http.fetch(AUDIT_URL, {
* method: "POST",
* body: JSON.stringify({ session: await $.session.id(), request }),
* })
* @example
* on("turn.complete", async ($, e, next) => {
* if (e.agentId === undefined) return next(e)
* const found = await $.session.messages({ agentId: e.agentId })
* if (!("deny" in found)) $.ui.log(`${found.length} messages`)
* return next(e)
* })
*/
messages: SessionMessagesCall;
/**
* Returns the directory the session runs in, absolute.
*/
cwd: () => Promise<string>;
/**
* Returns the session's project root, absolute: where it started, or
* where `/cd`, a host's directory change or a worktree move took it.
*
* A shell `cd` during the session does not move it; nested instruction
* files are read only beneath it.
*/
root: () => Promise<string>;
/**
* Returns the main loop's model, as `/model` shows it.
*/
model: () => Promise<string>;
/**
* Returns how many prompts the user has sent this session (user turns in
* the transcript).
*/
turns: () => Promise<number>;
/**
* Returns the session's id (the transcript file's name).
*/
id: () => Promise<string>;
/**
* Returns the git repository the session runs in, read from the working
* copy on each call; null when the directory is not inside one.
*
* @example
* const repo = await $.session.repo(); const publicRepo = !repo?.internal
*/
repo: () => Promise<SessionRepo | null>;
/**
* Returns every surface the session draws on, each once: `terminal` under
* the REPL first, then the remote ones in the order they attached.
*
* A session may draw on several at once (a terminal and two phones):
* clients attach (`session.attach`) and detach, and a render hook still
* reads `e.surface` per ask. Empty in a plain -p run; never rejects.
*
* @example
* const inApp = (await $.session.surfaces()).some(s => s !== "terminal")
*/
surfaces: () => Promise<readonly RenderSurface[]>;
/**
* Returns the first of `$.session.surfaces()`, or null where nothing
* draws.
*
* @deprecated use `surfaces()`; a session may draw on several surfaces at
* once
*/
surface: () => Promise<RenderSurface | null>;
/**
* Returns when the session began, and the context window's fill, the
* rate-limit windows and the cost as the status line has them, itemized.
*
* The plain call costs nothing; `"full"` counts each category with the
* token-count API as /context does, `"summary"` estimates locally, and
* `context.breakdown` comes back in the SDK's `get_context_usage` shape.
*
* @param args `{ breakdown, columns }`: how the breakdown is counted and
* the width its grid is drawn in; nothing for the status line's figures
* @returns `{ startedAt, context, rateLimits, cost }` as the status line
* has them
* @example
* const { context } = await $.session.usage()
* if ((context.percent ?? 0) >= 85) await $.session.compact()
* @example
* const isOlder = stat.mtimeMs < (await $.session.usage()).startedAt
* @example
* const usage = await $.session.usage({ breakdown: "full", columns })
* for (const row of usage.context.breakdown?.gridRows ?? []) draw(row)
*/
usage: (args?: SessionUsageArgs) => Promise<SessionUsage>;
/**
* Returns the version of the engine the session runs on, the release it
* is built from, and when it was built.
*
* The same three values the engine's own analytics rows carry, answered
* in every mode and build; `base` is absent when the version is not
* spelled as a release, `builtAt` in a run from source that stamps none.
*
* @returns the full `version`, its release `base` (`2.1.280`, or
* `2.1.280-dev` for a development build) and an ISO `builtAt`
* @example
* const { version, base, builtAt } = await $.session.version()
* row.env = { version, version_base: base, build_time: builtAt }
* @example
* const isRelease = !(await $.session.version()).base?.endsWith("-dev")
*/
version: () => Promise<SessionVersion>;
/**
* Compacts the conversation: the event `session.compact` with `trigger`
* `plugin`, the same call `/compact` makes, between turns.
*
* It runs through every hook but the calling one, then core: a summary
* and the kept messages in the transcript's place. Resolves `{ skip }`
* when a hook vetoed it; rejects while a turn runs.
*
* @example
* const { skip } = await $.session.compact({ instructions: "the plan" })
*/
compact: EventCalls['session']['compact'];
/**
* Sends a plain-text message to another agent or session: the event
* `session.send`, the model's SendMessage tool's own call and delivery.
*
* `to` is the tool's spelling (a name, an agent id, a received `from`
* address) or `{ sessionId }` / `{ agentId }`; framed at the receiver as a
* peer's, `origin.plugin` naming this plugin there. Resolves once queued.
*
* @example
* const sent = await $.session.send({ to: { sessionId }, text: "ok" })
* @example
* await $.session.send({ to: { agentId }, text: "stop after this file" })
*/
send: EventCalls['session']['send'];
/**
* Holds the session's Anthropic credential on the host and answers an
* opaque handle and its kind; the secret never reaches the plugin.
*
* The handle is spent through `$.http.fetch(url, { auth: handle })`,
* which sets the credential header, only for a first-party host. Null
* with no first-party credential (a 3P provider, a gateway, no login).
*
* @example
* await $.http.fetch(url, { auth: (await $.session.authorize())?.handle })
*/
authorize: () => Promise<SessionAuthorization>;
};
/**
* The running model turn: ending it.
*/
turn: {
/**
* Cancels the running model turn: the one whose id `turn.start` handed
* this plugin, its running tools stopped, no interruption marker.
*
* The event `turn.abort`, seen by the hooks above; the prompt this
* plugin submits next is the context. Rejects, naming both ids, when
* `turnId` is not the running turn's; a hook may end its own turn.
*
* @param input `turnId`: the id `turn.start` carried
* @example
* on("turn.start", ($, e, next) => { held = e.turnId; return next(e) })
*/
abort: (input: OpEventOf['turn.abort']) => Promise<void>;
};
/**
* Submitting a prompt the model reads as a user turn, and the person's
* prompt box: read as it stands, written, or proposed into.
*/
prompt: {
/**
* Submits a prompt: the event `prompt.submit`, the same call the engine
* makes for a typed prompt; a turn of its own, once the session is idle.
*
* It goes through every hook but the calling one (the plugin's others
* see it) with `e.origin` `{ kind: 'plugin', name }`, the name the
* model reads it under unless a hook leaves it out of its answer.
*
* @example
* void $.prompt.submit({ text: "List the TODOs you just mentioned." })
*/
submit: EventCalls['prompt']['submit'];
/**
* Returns the prompt box as it stands, the draft typed so far and the
* cursor's offset into it, so a `fill` can keep what the person typed.
*
* Never rejects: `{ text: '', cursor: 0 }` where the session draws no box
* (a -p run, an SDK host) or none is mounted yet.
*
* @example
* const { text, cursor } = await $.prompt.read()
*/
read: () => Promise<PromptBox>;
/**
* Puts `input.text` in the prompt box as the draft, by `mode`: `replace`
* (the default) over it, `append` after it, `insert` at the cursor.
*
* The event `prompt.fill` through the other plugins' hooks; `isFilled:
* false` under a dialog or headless. To hand the model text WITH the next
* prompt instead, a `prompt.submit` hook adds `context` (second example).
*
* @example
* await $.prompt.fill({ text: `> ${quote}\n`, mode: "insert" })
* @example
* ($, e, next) => next({ ...e, context: [...(e.context ?? []), hunk] })
*/
fill: (input: PromptFillArgs) => Promise<PromptFilled>;
/**
* Proposes `input.text` as the prompt box's dim suggestion, Tab to take:
* the event `prompt.suggest`, as the engine's own guess after a turn.
*
* It goes through every other plugin's hook with `e.origin` `{ kind:
* 'plugin', name }`, the engine's own suggestions on or off; `{ isShown:
* false }` while the box holds text, a turn runs, or headless (no box).
*
* @example
* void $.prompt.suggest({ text: "run the tests you just wrote" })
*/
suggest: EventCalls['prompt']['suggest'];
};
/**
* The tools the model has in this session, and running one.
*/
tool: {
/**
* Returns the tools the model can call now, built-in and MCP alike, in
* the order the model sees them.
*
* @example
* const names = (await $.tool.list()).map(t => t.name)
*/
list: () => Promise<ToolInfo[]>;
/**
* Calls a tool: the event `tool.call`, the same call the engine makes for
* the model's tool calls, under a `tool_use_id` of its own.
*
* It runs through every hook but the calling one (the plugin's others
* see it), the permission check and its dialog, then the tool. Rejects
* when no tool has that name or the call is aborted.
*
* @example
* const { text } = await $.tool.call({ tool: "Read", file_path: "a.md" })
*/
call: EventCalls['tool']['call'];
/**
* Asks the engine's permission decision for a tool call now: the event
* `tool.check`, resolved to `{ decision, reason?, rule? }`.
*
* The hooks run (the calling hook's own frame skipped, `next.origin` this
* plugin, no `tool_use_id`); nothing runs, no dialog opens, no PreToolUse
* hook or classifier is asked.
*
* @example
* const { decision } = await $.tool.check({ tool: "Read", input })
*/
check: EventCalls['tool']['check'];
/**
* Declares a tool the model can call from the next prompt on: the name,
* description and input schema of `mcp__<plugin>__<name>`.
*
* Serve it with a `tool.call` hook on `{ tool: "mcp__<plugin>__<name>" }`
* that returns the result (a call no hook answers fails); a name registered
* again is replaced. Rejects until the session binds, at `session.start`.
*
* @param tool `name`, `description` (what the model reads), `inputSchema`
* (a JSON schema object; default `{ type: "object" }`)
* @returns `{ tool }`, the registered tool's full name
* `mcp__<plugin>__<name>`
* @example
* await $.tool.register({ name: "weather", description: "Weather." })
*/
register: (tool: ToolSpec) => Promise<OpValueOf['tool.register']>;
};
/**
* The slash commands the person can run in this session, and running one.
*/
command: {
/**
* Returns the slash commands the person can run now, built-in, plugin
* and MCP alike, in the order the typeahead lists them.
*
* @example
* const names = (await $.command.list()).map(c => c.name)
*/
list: () => Promise<CommandInfo[]>;
/**
* Runs a slash command as if the person typed `/command args`: the
* event `command.run`, queued and run once the session is idle.
*
* It runs through every hook but the calling one with `e.origin`
* `{ kind: 'plugin', name }`, its lines in the transcript. Rejects an
* unknown name, and inside a hook the turn is waiting on.
*
* @example
* const { text } = await $.command.run({ command: "status" })
*/
run: EventCalls['command']['run'];
/**
* Declares the slash command `/<name>` for this session, listed in the
* typeahead from the next keystroke on.
*
* Serve it with a `command.run` hook on `{ command: "<name>" }` that
* returns `{ text }`; a run no hook answers says so as its output.
* Registering a name again replaces it; a built-in's name is refused.
*
* @param command `name`, `description` (what the menu shows),
* `argumentHint` (dim after the name), `immediate` (runs mid-turn)
* @returns `{ command }`, the registered name
* @example
* await $.command.register({ name: "hello", description: "Says hi." })
*/
register: (command: CommandSpec) => Promise<OpValueOf['command.register']>;
};
/**
* Every row of the settings menu (`/config`), the panel's own and each
* enabled plugin's `userConfig` fields alike: listing and changing them.
*/
config: {
/**
* Returns the rows the `/config` menu would draw now, in its order,
* each with its current value, its kind, its owner and its lock.
*
* After every `config.describe` hook: a hidden row is left out, a
* relabelled one carries the new label.
*
* @example
* const theme = (await $.config.list()).find(row => row.key === "theme")
*/
list: () => Promise<ConfigRow[]>;
/**
* Changes one row as if the person did in the menu: the event
* `config.set` with `origin` `{ kind: 'plugin', name }`, then the writer.
*
* Through the other plugins' hooks, this plugin's own skipped; `{ deny }`
* when a hook refused, the value does not fit, a trusted source owns the
* row or only its dialog changes it. Rejects a key no row has.
*
* @param args `key` (as `list` names it) and `value` (the row's kind)
* @returns `{ value }` once written, or `{ deny }`
* @example
* const { deny } = await $.config.set({ key: "verbose", value: true })
*/
set: EventCalls['config']['set'];
};
/**
* Subagents.
*/
agent: {
/**
* Spawns a subagent: the event `agent.spawn`, the same call the engine
* makes when the Agent tool starts one; the engine fills the rest.
*
* It runs every hook but the calling one, then the Agent tool in the
* background under this call's origin: `{ model, agentId }` once the
* subagent started (its answer is its `turn.complete`), or `{ deny }`.
*
* @example
* const { agentId } = await $.agent.spawn({ prompt: "Read README.md." })
*/
spawn: EventCalls['agent']['spawn'];
/**
* Returns the session's subagents so far, the ones the model spawned and
* the ones plugins did alike.
*/
list: () => Promise<AgentInfo[]>;
/**
* Defines an agent type the Agent tool dispatches from the next turn on,
* named `<plugin>:<name>`: the event `agent.register`.
*
* Every field takes effect as in an agent file; re-registered, a name is
* replaced; unloaded, a plugin's types go. `agent.offer` hides it from
* the model alone; any plugin's `$.agent.spawn` answers to `agent.spawn`.
*
* @param spec `name`, `description` (when to delegate), `prompt` (its
* system prompt), and any other field of an agent definition
* @returns `{ agent }`, the full name; rejects until the session binds,
* on a spec the schema refuses (its reason), on a hook's deny
* @example
* await $.agent.register({ name: "runner", description: "Runs a spec",
* prompt: RUNNER_PROMPT, tools: ["Read", "Bash"], omitClaudeMd: true })
* @example
* // runner-only: hidden from the model, spawned by this plugin's tool,
* // answered by the subagent's turn.complete (matched by agentId)
* on("agent.offer", { agent: "lab:runner" }, () => ({ isOffered: false }))
* on("tool.call", { tool: "mcp__lab__run" }, async ($, e) => {
* const { agentId, deny } = await $.agent.spawn({
* subagentType: "lab:runner", prompt: e.spec, description: "run" })
* return { result: deny ?? (await answerOf(agentId)) }
* })
*/
register: (spec: AgentSpec) => Promise<OpValueOf['agent.register']>;
};
/**
* The file system as the engine's own process reaches it; a relative path
* is under the session's working directory, and text is UTF-8.
*
* An absolute path is used as given; where one may go is an `fs.*` hook's
* to say. A read or write over 4 MiB rejects, a foreign network location
* as spelled rejects untouched, and an OS refusal rejects with its errno.
*/
fs: {
/**
* Reads a file and returns its text, or with `{ as: "bytes" }` its bytes
* as `{ base64 }`.
*
* Rejects when missing, or over 4 MiB, which bounds what one read copies
* into the plugin's environment. A file the plugin ships is under
* `$.plugin.root`.
*
* @param path relative to the working directory, or absolute
* @param options `as`: `"text"` (the default) or `"bytes"`
* @returns the file's text, or `{ base64 }`
* @example
* const readme = await $.fs.read("README.md")
* @example
* const { base64 } = await $.fs.read(
* `${$.plugin.root}/hooks/weights.bin`, { as: "bytes" })
* const weights = Uint8Array.fromBase64(base64)
*/
read: FsReadCall;
/**
* Writes `text` to a file, creating it and its directories as needed.
*
* @param path relative to the working directory, or absolute
* @param text the whole new content
*/
write: (path: string, text: string) => Promise<void>;
/**
* Lists a directory: `{ name, kind, size, isLink }` per entry, by name,
* each entry as it stands (a symbolic link is `other` with `isLink`).
*
* @param path the directory's path; absent, the working directory
* @returns the entries, `{ name, kind, size, isLink }` each
*/
list: (path?: string) => Promise<FsEntry[]>;
/**
* Returns whether the path exists; rejects only a network location as
* spelled, which no `fs` call reaches.
*/
exists: (path: string) => Promise<boolean>;
/**
* Returns `{ kind, size, mtimeMs, isLink }` of the path: what it leads to,
* and whether it is itself a symbolic link. Rejects when missing.
*
* With `{ resolve: true }` also `realPath`, where the path lands, absent
* when it leads nowhere; a guard matches it and denies without it, since a
* spelling it cannot resolve (`~`, a file not there yet) the tool may open.
*
* @param path relative to the working directory, or absolute
* @param options `resolve`: also answer `realPath` (one more file system
* call)
* @returns the stat, `realPath` with it when asked and resolvable; rejects
* `ENOENT` for a missing path
* @example
* // ROOT was resolved the same way and SEP is its separator; an
* // allow-list under it is the robust guard, a deny-list on spellings
* // only best effort (a hard link or a case alias keeps its own)
* on("tool.call", { tool: "Read" }, async ($, e, next) => {
* const stat = await $.fs.stat(e.file_path, { resolve: true })
* .catch(() => undefined)
* const real = stat?.realPath
* const isInside = real !== undefined && real.startsWith(ROOT + SEP)
* return isInside ? next(e) : { deny: "outside the project" }
* })
* @example
* // a Write may name a file not there yet: `placed` answers where the
* // path lands or undefined, and the guard denies on undefined or
* // outside ROOT. Unplaceable by spelling first, with no file system
* // call (drive-relative `D:x`, a `\\` or `//` network or device path,
* // a name that is empty, ".", ".." or itself `C:...`); then the file
* // if it stats; else its folder, cut after the last separator and
* // keeping it so a drive or share root stays that root, plus the name
* const placed = async (path) => {
* const cut = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
* const name = path.slice(cut + 1)
* const isPlaceable = !/^[A-Za-z]:(?![\\/])/.test(path) &&
* !/^[\\/][\\/]/.test(path) && !/^[A-Za-z]:/.test(name) &&
* name !== "" && name !== "." && name !== ".."
* if (!isPlaceable) return undefined
* const own = await $.fs.stat(path, { resolve: true })
* .catch(() => undefined)
* if (own) return own.realPath
* const folder = cut < 0 ? "." : path.slice(0, cut + 1)
* const dir = await $.fs.stat(folder, { resolve: true })
* .catch(() => undefined)
* return dir?.realPath === undefined ? undefined
* : `${dir.realPath.replace(/[\\/]$/, "")}${SEP}${name}`
* }
* const real = await placed(e.file_path)
* const isInside = real !== undefined && real.startsWith(ROOT + SEP)
* return isInside ? next(e) : { deny: "cannot place it, or outside" }
*/
stat: (path: string, options?: FsStatOptions) => Promise<FsStat>;
/**
* Reads the named instruction files in every directory above the
* session's original working directory, the way the engine reads CLAUDE.md.
*
* Root first, each `{ dir, name, content }` that exists, the content
* with its `@include`s after it; `of` and `below` together are the
* engine's own walk for a nested CLAUDE.md between root and read file.
*
* @param request `names`, relative `.md` file names (no `..`); `of`, the
* file walked down to; `below`, the directory the walk stays inside
* @returns the files found, root first
* @example
* const found = await $.fs.ancestors({ names: ["AGENTS.md"] })
* const stack = await $.fs.ancestors({ names: ["AGENTS.md"], of: path })
* const nested = await $.fs.ancestors({
* names: ["AGENTS.md"],
* of: e.file_path,
* below: await $.session.root(),
* })
*/
ancestors: (request: FsAncestorsRequest) => Promise<readonly FsAncestor[]>;
};
/**
* This plugin's own key-value store, kept between sessions and hot
* reloads; values are JSON data.
*
* A JSON file of the plugin's own under the user's Claude Code
* configuration directory.
*/
store: {
/**
* Returns the value under `key`, or `undefined` when unset.
*
* @example
* const count = Number((await $.store.get("count")) ?? 0) + 1
*/
get: (key: string) => Promise<unknown>;
/**
* Sets `key` to `value`, which must be JSON data.
*
* `get` reads back `JSON.parse(JSON.stringify(value))`: a Date is its ISO
* string, an `undefined` field is dropped, a Map or Set is `{}`. Rejects
* a function, a cycle, or a store over 4 MiB of JSON text in all.
*/
set: (key: string, value: unknown) => Promise<void>;
/**
* Removes `key` from the store.
*/
delete: (key: string) => Promise<void>;
/**
* Returns every key set, in insertion order.
*/
keys: () => Promise<string[]>;
};
/**
* Named values held by the host for the session, each with a version: plain
* data that survives a hot reload of the plugin's code.
*
* A `get` made while a `ui.render` hook draws subscribes that instance: a
* later `set` draws it again, nobody calling `$.ui.invalidate`. Any plugin
* reads any value; its owner alone writes it. Persist through `$.store`.
*/
state: {
/**
* Resolves the value under `ref` and the version it stands at; a value
* never written is `undefined` at version 0.
*
* Every `get` of one dispatch reads one moment, whatever is written
* meanwhile. `plugin` and `key` must be literals in source (`claude plugin
* validate` lists them); only a family member's `id` may be computed.
*
* @param ref `{ plugin, key }` as the owner's contract declares it in
* PluginState, with `id` for a StateFamily key
* @returns `{ value, version }`
* @example
* const workers = { plugin: "swarm", key: "workers" } as const
* const { value = [] } = await $.state.get(workers)
*/
get: <P extends keyof PluginState & string, K extends keyof PluginState[P] & string>(ref: StateRef<P, K>) => Promise<StateRead<StateValue<P, K>>>;
/**
* Writes `value` under `ref`, which must be this plugin's own; the sites
* that read it while drawing are drawn again, at the redraw rate.
*
* Refused while a `ui.render` hook draws (write from `onPress` or another
* event) and for another plugin's value (hook its `state.set` and rewrite
* `e.value`). JSON data, as `$.store.set` takes; never `undefined`.
*
* @param ref `{ plugin, key }`, with `id` for a StateFamily key
* @param value the value, of the type the contract declares
* @param options `ifVersion`: write only while it stands at that version
* @returns `{ isSet, version }`; `isSet` false when `ifVersion` missed
* @example
* await $.state.set(count, held.value + 1, { ifVersion: held.version })
*/
set: <P extends keyof PluginState & string, K extends keyof PluginState[P] & string>(ref: StateRef<P, K>, value: StateValue<P, K>, options?: StateSetOptions) => Promise<StateSetResult>;
};
/**
* The time and timers, each an event through the host: `clock.now` reads
* the time; `clock.sleep`, `after` and `every` wait until it has passed.
*
* A timer's callback is the plugin's own function, kept in its environment
* and run there when the wait resolves; a hot reload of the plugin cancels
* its pending waits with the old environment.
*/
clock: {
/**
* Resolves milliseconds since the epoch, now.
*
* @example
* const startedAt = await $.clock.now()
*/
now: () => Promise<number>;
/**
* Resolves after `ms` milliseconds; rejects at once when `signal` aborts.
*
* The wait is the hook's own time and its budget runs on through it, as
* through no other `$` call: a `turn.step` generator that polls with it
* pays every sleep out of its one budget (`next.budget.remainingMs`).
*
* @param ms how long, in milliseconds
* @param options `signal`: ends the wait early with a rejection (pass
* `next.signal` so a hook's wait ends with its dispatch)
* @example
* await $.clock.sleep(500, { signal: next.signal })
*/
sleep: (ms: number, options?: SleepOptions) => Promise<void>;
/**
* Calls `fn` once after `ms` milliseconds; `cancel()` before then stops it.
*
* One `clock.after` dispatch: `fn` runs when it resolves, and never when
* a hook refuses it.
*/
after: TimerCall;
/**
* Calls `fn` every `ms` milliseconds (at least 1) until `cancel()`.
*
* One `clock.every` dispatch per period: `fn` runs when it resolves and
* the next period is asked; a refused period ends the interval.
*
* @example
* const tick = $.clock.every(1000, () => $.ui.status("polling"))
*/
every: TimerCall;
};
/**
* The network, through the host.
*/
http: {
/**
* Fetches `url` through the host (never the plugin's own network) and
* resolves `{ status, ok, headers, text }` once the body is read.
*
* http or https, to whatever the host process can reach, unless the
* administrator's policy switches refuse it; an `auth` handle from
* `$.session.authorize()` rides https only, to a first-party host.
*
* @param url the URL (http or https)
* @param init `{ method, headers, body, auth, socketPath }` (body a
* string; socketPath a Unix socket to go over instead of TCP)
* @returns `{ status, ok, headers, text }` once the body is read
* @example
* const { ok, text } = await $.http.fetch("https://example.com/status")
* @example
* await $.http.fetch("http://bridge/reload", {
* method: "POST",
* socketPath: `${runDirectory}/bridge.sock`,
* })
*/
fetch: (url: string, init?: HttpInit) => Promise<HttpResponse>;
};
/**
* Commands on the host, run as the user the session runs as. CLI only.
*
* Local execution, not a network path: what a command of its own reaches
* is its own, as for the Bash tool and a settings `command` hook.
*/
process: {
/**
* Runs a command on the host by its argument vector (no shell) and
* resolves `{ exitCode, stdout, stderr }` once it exits, any exit code.
*
* One shot: the whole output is read, so a background process left
* writing holds the call until the timeout. Rejects when the command
* cannot start or is still running then. Git runs with repo hooks off.
*
* @param argv the command and its arguments, `argv[0]` the executable
* @param init `{ cwd, env, stdin, timeoutMs }` (cwd the session's by
* default; timeout 30 s by default, ten minutes at most)
* @returns `{ exitCode, stdout, stderr }` once the child exits
* @example
* const { exitCode, stdout } = await $.process.run(["git", "status"])
*/
run: (argv: readonly string[], init?: ProcessRunInit) => Promise<ProcessRunResult>;
/**
* Starts a command on the host by its argument vector (no shell) and
* streams what it writes, piece by piece, then how it ended.
*
* The loop is the child's life: leaving it, `return()` on the stream,
* `next.signal` aborting or the module unloading kills the child, and
* nothing else does, a hook's return included: end the loop to end it.
*
* @param request `{ argv, cwd, env, input }`, `$.process.run`'s rules;
* `e` for a hook on `process.spawn` (a generator, its budget per piece)
* @returns the pieces `{ stream, text }` (text as it came, not lines),
* then `{ code, signal }`; rejects its first pull if it cannot start
* @example
* // inline: the hook's dispatch waits on the loop; leaving it kills make
* const make = $.process.spawn({ argv: ["make"], cwd: "build" })
* for await (const { stream, text } of make) {
* if (stream === "stderr" && text.includes("error")) break
* }
* @example
* // a child for the session's life: the loop runs on after the hook
* // returns, and ends with the child or with the module
* on("session.start", async ($, e, next) => {
* const started = await next(e)
* void (async () => {
* const bridge = $.process.spawn({ argv: ["bridge", "watch", e.cwd] })
* for await (const { text } of bridge) $.ui.log(text, { to: "debug" })
* })()
* return started
* })
* @example
* // above another plugin's spawn: redact each piece before it reads it
* on("process.spawn", async function* ($, e, next) {
* for await (const chunk of next(e)) {
* yield { ...chunk, text: chunk.text.replaceAll(token, "***") }
* }
* })
*/
spawn: (request: ProcessSpawnRequest) => HookStream<ProcessSpawnChunk, ProcessSpawnResult>;
};
/**
* What the settings files, `--settings` and managed policy hold, as the
* engine runs under it; read only.
*
* Every key crosses as the source holds it, `env` and the helper commands
* included: nothing is filtered. The OAuth session and the global config
* (~/.claude.json) are not settings and are never read.
*/
settings: {
/**
* Resolves with the settings merged over every source, as the engine
* reads them, or with one source's settings as loaded (`{ source }`).
*
* A snapshot in plain data each call; a source with no file answers
* `{}`. The sources (SettingsSource) rise in precedence from `user` to
* `policy`: the merge takes a key from the last source that has it.
*
* @param args `{ source }` to read one source; nothing for the merge
* @returns the settings object, keyed as a settings.json is
* @example
* const { permissions } = await $.settings.read()
* const policy = await $.settings.read({ source: "policy" })
*/
read: (args?: SettingsReadArgs) => Promise<Settings>;
};
/**
* The environment of this process, the one every Bash child, MCP server
* and `$.process.run` command started after inherits.
*
* `get` and `set` take the variable's name as a string literal, so what a
* module reads and writes is read off its source: `claude plugin validate`
* lists the names, and a name the module does not spell is refused.
*/
env: {
/**
* Resolves with the variable's value, or `undefined` when it is unset.
*
* `name` must be a string literal; `claude plugin validate` lists the
* names your module reads and writes.
*
* @example
* const home = await $.env.get("HOME")
*/
get: (name: string) => Promise<string | undefined>;
/**
* Sets the variable for this process and everything it starts after, or
* unsets it when `value` is `undefined`.
*
* `name` must be a string literal; `claude plugin validate` lists the
* names your module reads and writes.
*
* @example
* await $.env.set("GIT_PAGER", "cat")
*/
set: (name: string, value: string | undefined) => Promise<void>;
};
}
CoreEventName type
#
line 3270
Added in 2.1.265
The name of an event the engine defines itself (a key of CoreEventOf); EventName adds the declared plugin nouns' events.
type CoreEventName = keyof CoreEventOf;
CoreEventOf type
#
line 3276
Added in 2.1.265
The argument of each event the engine defines itself: its call sites' (EngineEventOf), the classic hooks' (ClassicEventOf), the calls on $.
type CoreEventOf = EngineEventOf & ClassicEventOf & OpEventOf;
CwdChangedHookInput type
#
line 3278
Added in 2.1.265
type CwdChangedHookInput = BaseHookInput & {
hook_event_name: 'CwdChanged';
old_cwd: string;
new_cwd: string;
};
DeclaredEvents type
#
line 3288
Added in 2.1.281
The state events of each declared value in a union of [plugin, key] pairs, one variant per pair (it distributes), so a matcher narrows e.
type DeclaredEvents<Pair> = Pair extends readonly [
infer P extends keyof PluginState & string,
DeclaredEventsOf type
#
line 3297
Added in 2.1.281
e of state.get and of state.set for one declared value: its typed reference, and for a write the change, of the declared type.
type DeclaredEventsOf<P extends keyof PluginState & string, K extends keyof PluginState[P] & string> = {
get: StateRef<P, K>;
set: StateRef<P, K> & DeclaredStateChange<P, K>;
};
DeclaredPair type
#
line 3306
Added in 2.1.281
Every named value the enabled contracts declare, as one union of [plugin, key] pairs; never while PluginState is empty.
type DeclaredPair = {
[P in keyof PluginState & string]: {
[K in keyof PluginState[P] & string]: readonly [plugin: P, key: K];
}[keyof PluginState[P] & string];
}[keyof PluginState & string];
DeclaredStateChange type
#
line 3316
Added in 2.1.281
What a state.set on one declared value carries beside its reference: the value being written and the one that stood there, of the declared type.
type DeclaredStateChange<P extends keyof PluginState & string, K extends keyof PluginState[P] & string> = {
value: StateValue<P, K>;
previous: StateValue<P, K> | undefined;
/**
* The version the caller's write is conditional on, when it gave one.
*/
ifVersion?: number;
};
Derived type
#
line 3332
Added in 2.1.281
A value computed from other values, as derive(sources, fn) makes it: read runs fn again only when a source's version moved.
It holds nothing on the host: the cache is the plugin's own, lost with a reload of its code, and rebuilt by the next read.
export type Derived<T> = {
/**
* The atoms and references it is computed from, read in this order.
*/
readonly sources: readonly (Atom<unknown> | StateAddress)[];
/**
* The function over the sources' values, in the order they were given.
*/
readonly compute: (...values: never[]) => T;
};
DeriveFunction type
#
line 3353
Added in 2.1.281
derive(sources, fn): a value computed from atoms and references, cached by their versions: read runs fn again only when one moved.
Pure: it builds a description and calls nothing; reading it while drawing subscribes the drawing to every source.
const busy = derive([workers], list => list.filter(w => w.isBusy).length)
export type DeriveFunction = <const S extends readonly unknown[], T>(sources: S, compute: (...values: SourceValues<S>) => T) => Derived<T>;
DirectoryAddedHookInput type
#
line 3355
Added in 2.1.265
type DirectoryAddedHookInput = BaseHookInput & {
hook_event_name: 'DirectoryAdded';
/**
* Absolute path of the directory that was added.
*/
directory: string;
/**
* How the directory was added: "slash_command" for /add-dir, "register_repo_root" for the SDK control_request.
*/
source: 'slash_command' | 'register_repo_root';
};
ElementChildren type
#
line 3375
In the first published surface (2.1.259) · changed in 2.1.267
The children field every element constructor's props carry, appended beside its own props type: one child, or a list that may nest.
JSX types a lone child as the child itself (<Text dimColor>done</Text> passes the string, <Text>{count}</Text> the number), and a mapped list beside a sibling as a nested list (<Box>{rows.map(row)}<Text>ok</Text>).
export type ElementChildren = {
children?: RenderChildren;
};
ElementConstructor type
#
line 3387
In the first published surface (2.1.259)
An element as $.ui.resolve(e) hands it out: a constructor from props to the frozen plain-data element, children among the props as JSX passes.
const { Box } = $.ui.resolve(e) then <Box gap={1}>...</Box> compiles to h(Box, { gap: 1 }, ...children), and h calls a function tag with its props, so the table's constructors are the JSX tags.
export type ElementConstructor<P> = (props: P & ElementChildren) => RenderElement;
ElementName type
#
line 3393
In the first published surface (2.1.259)
Every element name of every surface: what a table handed out is completed to (an omitted one draws a fragment; see ui.resolve).
export type ElementName = {
[P in RenderSurface]: keyof Elements[P];
}[RenderSurface];
Elements type
#
line 3405
In the first published surface (2.1.259) · changed in 2.1.260, 2.1.265, 2.1.267, 2.1.268, 2.1.271, 2.1.273, 2.1.274, 2.1.275
The element constructors each surface draws, by e.surface: what $.ui.resolve(e) returns and a ui.resolve hook passes on; no globals.
All carry Box, Text, Button, Link, Code, Markdown; every remote surface Svg; all but mobile Input and Select; terminal and desktop Client; terminal Raster and Image. Narrowed on e.surface, that table.
68 lines
export type Elements = {
terminal: {
Box: ElementConstructor<BoxProps>;
Text: ElementConstructor<TextProps>;
Button: ElementConstructor<ButtonProps>;
Input: ElementConstructor<InputProps>;
Select: ElementConstructor<SelectProps>;
Link: ElementConstructor<LinkProps>;
Code: ElementConstructor<CodeProps>;
Markdown: ElementConstructor<MarkdownProps>;
/**
* A region one of the plugin's surface modules draws (ClientModule), named
* by `module`: a string literal, the module's path relative to this file.
*
* The engine reads the surface module off the hooks module's source
* before anything runs, so a variable, a template with a substitution or
* a computed path as `module` is refused at load, naming the line.
*/
Client: ElementConstructor<ClientProps>;
Raster: ElementConstructor<RasterProps>;
Image: ElementConstructor<ImageProps>;
};
desktop: {
Box: ElementConstructor<BoxProps>;
Text: ElementConstructor<TextProps>;
Button: ElementConstructor<ButtonProps>;
Input: ElementConstructor<InputProps>;
Select: ElementConstructor<SelectProps>;
Svg: ElementConstructor<SvgProps>;
Link: ElementConstructor<LinkProps>;
Code: ElementConstructor<CodeProps>;
Markdown: ElementConstructor<MarkdownProps>;
Client: ElementConstructor<ClientProps>;
};
/**
* No `Input` or `Select`: the mobile app draws no field yet; not a limit
* of the device, nor of the control protocol (ui_input, ui_select).
*
* The table grows when the app draws them.
*/
mobile: {
Box: ElementConstructor<BoxProps>;
Text: ElementConstructor<TextProps>;
Button: ElementConstructor<ButtonProps>;
Svg: ElementConstructor<SvgProps>;
Link: ElementConstructor<LinkProps>;
Code: ElementConstructor<CodeProps>;
Markdown: ElementConstructor<MarkdownProps>;
};
/**
* The desktop's table without `Client`: a remote `Client`'s module, presses
* and posts (ui_client_module, ui_client_press, ui_message) name no surface.
*
* They are the desktop's alone today, not a limit of the editor's webview:
* the table gains `Client` when those asks name a surface.
*/
vscode: {
Box: ElementConstructor<BoxProps>;
Text: ElementConstructor<TextProps>;
Button: ElementConstructor<ButtonProps>;
Input: ElementConstructor<InputProps>;
Select: ElementConstructor<SelectProps>;
Svg: ElementConstructor<SvgProps>;
Link: ElementConstructor<LinkProps>;
Code: ElementConstructor<CodeProps>;
Markdown: ElementConstructor<MarkdownProps>;
};
};
ElementTable type
#
line 3477
In the first published surface (2.1.259)
The table ui.resolve answers for an argument of surface P.
export type ElementTable<P extends RenderSurface = RenderSurface> = Elements[P];