;
/**
* One key the person pressed while a `Client` had the focus, as
* `surface.onKey` hands it. Escape never arrives: it returns the focus.
*/
export type ClientKeyEvent = {
/**
* A special key's name (`up`, `down`, `left`, `right`, `return`, `tab`,
* `backspace`, `delete`, `pageup`, `home`, ...) or the character typed.
*/
key: string;
/**
* Modifier keys held with it, each present only when true.
*/
ctrl?: true;
shift?: true;
meta?: true;
};
/**
* The component a surface module exports (default, or its one PascalCase
* export): from props and surface to the tree drawn (no nested `Client`).
*
* Runs in the plugin's surface environment on the drawing thread, under a
* time budget per call; a throw or an overrun unmounts the instance and
* draws one line naming the plugin and the module in its place.
*/
export type ClientModule = (props: P, surface: ClientSurface) => RenderElement;
/**
* One pointer event over a `Client`'s region, as `surface.onPointer` hands
* it: cell coordinates relative to the region's top-left corner.
*
* After a `down` in the region the instance holds the pointer until the
* `up`: every `move` reaches it, past the edges too (negative, or beyond
* `columns`/`rows`), and the transcript neither selects nor scrolls.
*/
export type ClientPointerEvent = {
type: ClientPointerType;
/**
* The column under the pointer, 0 at the region's left edge; on `enter`
* and `leave`, the last column a move reported.
*/
x: number;
/**
* The row under the pointer, 0 at the region's top edge.
*/
y: number;
/**
* Which button is down (`down`, `move` while held) or came up (`up`);
* absent on a hover `move`, `enter` and `leave`.
*/
button?: 'left' | 'middle' | 'right';
/**
* Modifier keys the terminal reported held, each present only when true.
*/
shift?: true;
alt?: true;
ctrl?: true;
};
/**
* What the pointer did over a `Client`'s region: a button went down, the
* pointer moved, the button came up, or the pointer crossed the region's edge.
*/
export type ClientPointerType = 'down' | 'move' | 'up' | 'enter' | 'leave';
/**
* The props of `Client`: which of the plugin's surface modules draws here,
* under what key, with what data, in how much room.
*/
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: "./.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;
};
/**
* 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.
*/
export type ClientSurface = {
/**
* 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.
*/
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.
*/
post: (data: JsonValue) => void;
};
/**
* 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;
};
/**
* 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.
*/
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';
};
/**
* The input of `command.describe`: how one slash command presents in the
* typeahead and `/help`, at the moment the engine lists it.
*/
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;
};
/**
* 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;
/**
* One slash command as `$.command.list()` returns it.
*/
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;
};
/**
* 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.
*/
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.
*/
isFullscreen: boolean;
/**
* The terminal's width in cells as the command runs; 80 where no terminal
* has measured (headless with no tty).
*/
columns: number;
};
/**
* `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 & {
/**
* Everything after the name, as the person would type it; left out, `""`.
*/
args?: string;
};
/**
* 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.
*/
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;
};
/**
* What a `command.run` hook returns and what `next(e)` and `$.command.run`
* resolve to: the command's output text, when it has one.
*
* From core, `text` is what the command printed (a `local` command's
* returned text; a panel command may print nothing) 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.
*/
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;
/**
* 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;
};
/**
* 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';
/**
* What `$.command.register` takes: the slash command this plugin serves.
*/
export type CommandSpec = {
/**
* The command's name without the slash (letters, digits, `_`, `-`; up to
* 64); the person runs it as `/`.
*/
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 `/` 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;
};
type ConfigChangeHookInput = BaseHookInput & {
hook_event_name: 'ConfigChange';
source: 'user_settings' | 'project_settings' | 'local_settings' | 'policy_settings' | 'skills';
file_path?: string;
};
/**
* The input of `config.describe`: how one `/config` row presents, at the
* moment the menu lists it (and for `$.config.list`).
*/
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;
};
/**
* 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;
/**
* 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';
/**
* 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.
*/
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;
};
/**
* One `/config` row as `$.config.list()` returns it: what the menu would
* draw now, after every `config.describe` hook, a hidden row left out.
*/
export type ConfigRow = {
/**
* Names the row: the panel's id for a built-in, `.` 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;
};
/**
* `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;
/**
* 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.
*/
export type ConfigSetInput = {
/**
* Names the row: the panel's own id for a built-in (`theme`, `verbose`,
* `autoCompact`), `.` 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;
};
/**
* 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;
};
/**
* 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[];
/**
* One custom agent whose description the Agent tool's prompt carries;
* built-in agents are left out.
*/
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;
};
/**
* The token counts the last API response of the live window reported, as
* the API spells them; the breakdown's `Messages` row is reconciled to it.
*/
export type ContextApiUsage = {
/**
* Uncached input tokens the response was answered over.
*/
input_tokens: number;
/**
* Tokens the response generated.
*/
output_tokens: number;
/**
* Input tokens written to the prompt cache by the request.
*/
cache_creation_input_tokens: number;
/**
* Input tokens read from the prompt cache by the request.
*/
cache_read_input_tokens: number;
};
/**
* 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';
/**
* One row of the breakdown, as /context lists it beside the grid (`System
* prompt`, `Messages`, `Free space`, `Autocompact buffer`).
*/
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;
};
/**
* 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';
/**
* One square of the grid /context draws: which row it belongs to and how
* full it is.
*/
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;
};
/**
* One MCP tool's schema as the context carries it.
*/
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;
};
/**
* One memory file the context carries (a CLAUDE.md, a rules file, an
* auto-memory entry).
*/
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;
};
/**
* One skill whose listing the context carries.
*/
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;
};
/**
* The skills the context lists for the model: how many there are, how many
* fit the listing's budget, and each one's share.
*/
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[];
};
/**
* The slash commands the Skill tool's prompt lists, counted.
*/
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;
};
/**
* 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';
/**
* 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.
*/
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
* transcript line, a pane the surface places, a window or a ring moved.
*/
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` or `prompt.context` hook: dropped next turn.
*
* @param event `ui.render`, `prompt.section`, `prompt.context`,
* `tool.describe`, `command.describe` or `config.describe`
*/
invalidate: (event: InvalidatableEventName) => void;
/**
* Repaints a `Raster` this plugin's own render hook drew, still mounted,
* with new cells, without the redraw `invalidate` asks for.
*
* The surface keeps the cells for that Raster (by site and `key`) and
* paints them at its next frame, so blits between frames fold into one:
* an animation runs at the frame rate. A resize is a redraw instead.
*
* @param args `requestId` (the site), `key` (the Raster's), `cells`
* (RasterProps), `columns` and `rows` (the mounted size)
* @returns `{}` once the cells are its next frame, or `{ deny }` (not
* mounted, not this plugin's, another size, cells that do not
* decode)
* @example
* $.clock.every(33, () => $.ui.blit({ requestId, key, cells: frame() }))
*/
blit: (args: UiBlitArgs) => Promise;
/**
* 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 {await next(e)}done
*/
resolve: (e: E) => Elements[E['surface']];
/**
* Appends one line to the transcript, drawn like a system notice (dim;
* not sent to the model), and records it in the debug log.
*
* The line is a row of its own at the surface's next frame, wherever the
* transcript is then; lines keep the order they were logged in. A `-p` or
* SDK run has no transcript: its host receives the line as `ui_log`.
*
* @param text the line's text
* @example
* $.ui.log(`prompt from ${e.origin.kind}: ${e.text.length} chars`)
*/
log: (text: string) => 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;
/**
* Shows `text` on the notification bar under the prompt for a few
* seconds, the way the engine's own "context left" notice appears.
*
* It leaves the transcript and the model untouched.
*
* @param text the line to show
* @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; undefined clears it
* @example
* $.ui.status("thinking..."); return next(e)
*/
status: (text: string | undefined) => void;
/**
* Opens a pane: a framed region the surface places, whose body this
* plugin draws by hooking `ui.render` for `{ component: "Pane" }`.
*
* One instance per id (`requestId`): opening an open id retitles it; a hook
* on `ui.open` may refuse it. The keyboard is the person's; unasked, it
* waits undrawn below 144 columns (110 once asked), judged at each open.
*
* @param pane `id` (1-64 of letters, digits, `_`, `-`), `title`, `focus`,
* `closeOnEscape` and `holdToasts` (a dialog), `rows` it wants inline
* @returns settles once the pane is open (or retitled)
* @example
* await $.ui.open({ id: "clock", title: "Clock" })
* @example
* await $.ui.open({ id: "ask", focus: true, closeOnEscape: true, rows: 9 })
*/
open: (pane: PaneOpenArgs) => Promise;
/**
* 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;
/**
* 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;
/**
* 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 the event `ui.focus`, origin `plugin`; the engine's inverse
* marks the element. The keyboard is the person's to give: a site not
* holding it, or holding it on another plugin's element, is `{ deny }`.
*
* @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
* onPress: () => $.ui.focus({ requestId: "files", key: "row:0" })
*/
focus: (args: UiFocusArgs) => Promise;
};
/**
* Completions through the session's own client and credentials.
*/
model: {
/**
* Runs one text completion through the session's own API client and
* resolves to the reply's text.
*
* No tools, no history, no system prompt beyond the CLI's identity block
* and `request.system`.
*
* @param request the model (an alias such as `haiku`, or a full id,
* resolved like a `--model` value), the prompt, and a token cap
* @returns the reply's text
* @example
* const reply = await $.model.complete({ model: "haiku", prompt: "Hi." })
*/
complete: (request: ModelCompleteRequest) => Promise;
/**
* Runs one tool-less completion over the session's OWN transcript, sharing
* the main thread's prompt cache; the reply's text and the fork's usage.
*
* Not `complete`: sharing the cache prefix means the model and system
* prompt are the session's, read from its last turn's cache-safe snapshot,
* and tools are denied. Null on a cold snapshot or an API error.
*
* @param request the one user message the fork answers
* @returns the reply's text with the fork's usage, or null on a cold
* snapshot or an API error
* @example
* const reply = await $.model.fork({ prompt: "One line to learn?" })
* if (reply !== null) $.ui.log(`${reply.usage.output_tokens} tokens`)
*/
fork: (request: ModelForkRequest) => Promise;
/**
* 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;
};
/**
* 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;
/**
* 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;
};
/**
* 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. Resolves to the tool's result
* as MCP returns it: `content` blocks and `isError`. No permission prompt:
* the plugin's call, seen by the hooks above it, is the grant.
*
* @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")
*/
call: (server: string, tool: string, args?: Record) => Promise;
};
/**
* The running session, read as plain data, and compacting it.
*/
session: {
/**
* Returns the transcript so far, one entry per user or assistant
* message; progress rows, `$.ui.log` lines and notices are not messages.
*
* Each entry is a SessionMessage, `{ role, text, toolUses }` (a user
* message may add `toolResults`; a `toolUses` entry adds its `result` and
* `text` once answered). A long transcript answers its newest 4096.
*
* @example
* const last = (await $.session.messages()).at(-1)
*/
messages: () => Promise;
/**
* Returns the directory the session runs in, absolute.
*/
cwd: () => Promise;
/**
* Returns the main loop's model, as `/model` shows it.
*/
model: () => Promise;
/**
* Returns how many prompts the user has sent this session (user turns in
* the transcript).
*/
turns: () => Promise;
/**
* Returns the session's id (the transcript file's name).
*/
id: () => Promise;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* Returns the context window's fill, the rate-limit windows and the
* cost, as the status line has them; with `breakdown`, by category too.
*
* 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 `{ 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 usage = await $.session.usage({ breakdown: "full", columns })
* for (const row of usage.context.breakdown?.gridRows ?? []) draw(row)
*/
usage: (args?: SessionUsageArgs) => Promise;
/**
* 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'];
/**
* 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
* where the build or the provider has no first-party credential to hold.
*
* @example
* await $.http.fetch(url, { auth: (await $.session.authorize())?.handle })
*/
authorize: () => Promise;
};
/**
* 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;
};
/**
* Submitting a prompt the model reads as a user turn, and putting a text
* in the person's prompt box, written or proposed.
*/
prompt: {
/**
* Submits a prompt: the event `prompt.submit`, the same call the engine
* makes for a typed prompt; `input.text` runs when 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'];
/**
* Writes `input.text` into the prompt box as the person's draft, cursor
* at its end, replacing what it held: the event `prompt.fill`.
*
* It goes through every other plugin's hook with `e.origin` `{ kind:
* 'plugin', name }`. Resolves `{ isFilled: false }` when a dialog holds
* the keys or the session has no box (headless); nothing is submitted.
*
* @example
* const { isFilled } = await $.prompt.fill({ text: "/review latest" })
*/
fill: EventCalls['prompt']['fill'];
/**
* 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;
/**
* 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____`.
*
* Serve it with a `tool.call` hook on `{ tool: "mcp____" }`
* 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____`
* @example
* await $.tool.register({ name: "weather", description: "Weather." })
*/
register: (tool: ToolSpec) => Promise;
};
/**
* 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;
/**
* 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 `/` for this session, listed in the
* typeahead from the next keystroke on.
*
* Serve it with a `command.run` hook on `{ command: "" }` 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;
};
/**
* 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;
/**
* 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;
};
/**
* The file system as the engine's own process reaches it, text only
* (UTF-8); a relative path is under the session's working directory.
*
* An absolute path is used as given. A read or a write over 4 MiB
* rejects; what the operating system refuses rejects with its errno
* (`ENOENT`, `EACCES`). Where a path may go is an `fs.*` hook's to say.
*/
fs: {
/**
* Reads a file and returns its text. Rejects when missing.
*
* @param path relative to the working directory, or absolute
* @returns the file's text
* @example
* const readme = await $.fs.read("README.md")
*/
read: (path: string) => Promise;
/**
* 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;
/**
* Lists a directory: `{ name, kind, size }` per entry, by name.
*
* @param path the directory's path; absent, the working directory
* @returns the entries, `{ name, kind, size }` each
*/
list: (path?: string) => Promise;
/**
* Returns whether the path exists; never rejects.
*/
exists: (path: string) => Promise;
/**
* Returns `{ kind, size, mtimeMs }` of the path. Rejects when missing.
*/
stat: (path: string) => Promise;
/**
* 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; with `of`, on down to that file's
* directory, as the engine reads a nested CLAUDE.md when a file is read.
*
* @param request `names`, relative `.md` file names (no `..`) looked for
* in each directory; `of`, the file whose directory the walk goes down to
* @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 })
*/
ancestors: (request: FsAncestorsRequest) => Promise;
};
/**
* 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;
/**
* 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;
/**
* Removes `key` from the store.
*/
delete: (key: string) => Promise;
/**
* Returns every key set, in insertion order.
*/
keys: () => Promise;
};
/**
* 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;
/**
* Resolves after `ms` milliseconds; rejects at once when `signal` aborts.
*
* @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;
/**
* 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 }` (body a string)
* @returns `{ status, ok, headers, text }` once the body is read
* @example
* const { ok, text } = await $.http.fetch("https://example.com/status")
*/
fetch: (url: string, init?: HttpInit) => Promise;
};
/**
* 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;
};
/**
* 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;
};
/**
* 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;
/**
* 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;
};
}
/**
* The name of an event the engine defines itself (a key of CoreEventOf);
* EventName adds the declared plugin nouns' events.
*/
type CoreEventName = keyof CoreEventOf;
/**
* 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;
type CwdChangedHookInput = BaseHookInput & {
hook_event_name: 'CwdChanged';
old_cwd: string;
new_cwd: string;
};
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';
};
/**
* 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 (`done`
* passes the string, `{count}` the number), and a mapped list
* beside a sibling as a nested list (`{rows.map(row)}ok`).
*/
export type ElementChildren = {
children?: RenderChildren;
};
/**
* 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 `...` 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 = (props: P & ElementChildren) => RenderElement;
/**
* 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];
/**
* 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`; every remote surface
* `Svg`; all but mobile `Input` and `Select`; terminal and desktop `Client`;
* terminal `Raster`. Narrowed on `e.surface`, that table; else the union.
*/
export type Elements = {
terminal: {
Box: ElementConstructor;
Text: ElementConstructor;
Button: ElementConstructor;
Input: ElementConstructor;
Select: ElementConstructor;
Link: ElementConstructor;
Code: ElementConstructor;
Client: ElementConstructor;
Raster: ElementConstructor;
};
desktop: {
Box: ElementConstructor;
Text: ElementConstructor;
Button: ElementConstructor;
Input: ElementConstructor;
Select: ElementConstructor;
Svg: ElementConstructor;
Link: ElementConstructor;
Code: ElementConstructor;
Client: ElementConstructor;
};
/**
* No `Input` or `Select`: the control protocol carries presses (ui_press)
* but no ui_input or ui_select yet; not a limit of the device.
*
* The table grows when those messages exist.
*/
mobile: {
Box: ElementConstructor;
Text: ElementConstructor;
Button: ElementConstructor;
Svg: ElementConstructor;
Link: ElementConstructor;
Code: ElementConstructor;
};
/**
* 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;
Text: ElementConstructor;
Button: ElementConstructor;
Input: ElementConstructor;
Select: ElementConstructor;
Svg: ElementConstructor;
Link: ElementConstructor;
Code: ElementConstructor;
};
};
/**
* The table `ui.resolve` answers for an argument of surface `P`.
*/
export type ElementTable = Elements[P];
/**
* Hook input for the Elicitation event. Fired when an MCP server requests user input. Hooks can auto-respond (accept/decline) instead of showing the dialog.
*/
type ElicitationHookInput = BaseHookInput & {
hook_event_name: 'Elicitation';
mcp_server_name: string;
message: string;
mode?: 'form' | 'url';
url?: string;
elicitation_id?: string;
requested_schema?: Record;
};
/**
* Hook input for the ElicitationResult event. Fired after the user responds to an MCP elicitation. Hooks can observe or override the response before it is sent to the server.
*/
type ElicitationResultHookInput = BaseHookInput & {
hook_event_name: 'ElicitationResult';
mcp_server_name: string;
elicitation_id?: string;
mode?: 'form' | 'url';
action: 'accept' | 'decline' | 'cancel';
content?: Record;
};
/**
* The input of `engine.create`: the fold that builds `$`, once per load,
* core innermost.
*
* A hook is written in post-order: `const built = await next(e)` is `$` as
* built so far; `return { ...built, voice: { say } }` adds this plugin's noun.
* See EngineEventOf's `engine.create` for what a step may and may not do.
*/
export type EngineCreateInput = {
/**
* In list order, first is outermost (managed plugins first, so an org
* plugin's withholding wins).
*/
plugins: readonly string[];
};
/**
* What an `engine.create` hook returns: `$` as built so far with this
* plugin's nouns added, less any it withheld.
*
* Every declared noun is optional here. Between hooks it crosses the chain
* as interface descriptors; a hook sees objects (EngineInterfaceBuilt).
*/
export type EngineCreateResult = Partial & {
readonly [noun: string]: unknown;
};
/**
* The events the engine raises at its call sites, and `engine.create`; the
* classic settings hooks' events are ClassicEventOf.
*
* At every one, a hook that fails (throws, overruns its budget, answers a
* wrong shape) is skipped: the hooks beneath and core run in its place, or
* its last `next` result stands; the failure is reported, naming it.
*/
export type EngineEventOf = {
/**
* Fires when the engine is about to run a tool. `next(e)` runs the hooks
* beneath, then core (the permission prompt, the tool itself).
*
* Return `{ deny: reason }` to refuse or `{ result }` to answer yourself; a
* hook that returns while its `next` is pending aborts what runs beneath.
* The managed-settings hooks run first: their deny is the call's result.
*/
'tool.call': ToolCallInput;
/**
* Fires when the engine decides whether a tool call may run, after the
* `tool.call` and PreToolUse hooks and before the mode settles an ask.
*
* `next(e)` resolves to the engine's verdict (rules, mode, the tool's own
* check, PreToolUse's decision); return any `{ decision }`. `$.tool.check`
* runs the same chain and executes nothing.
*
* @example
* on("tool.check", { tool: "Read" }, () => ({ decision: "allow" }))
*/
'tool.check': ToolCheckInput;
/**
* Fires when the engine is about to draw a component: once per input value
* (props, viewport width), plugin load or `$.ui.invalidate("ui.render")`.
*
* A repaint reuses the answer; a clock invalidates. `next(e)` resolves to the
* drawing: return it, wrap it, draw your own, or rewrite `props`. A tree that
* does not validate draws the engine's own; `--plugin-dir` is told why.
*/
'ui.render': RenderInput;
/**
* Fires when the plugins load (not per draw), once per surface, component
* and plugin: `e` names the surface and component, never the props.
*
* `next(e)` resolves to the surface's table, which `$.ui.resolve(e)` then
* reads. Return it, one with an element restyled for every other plugin,
* or one with a key left out (a fragment there); own table: hook skipped.
*/
'ui.resolve': ResolveInput;
/**
* Fires when a `Button` a render hook drew is pressed on a surface; `e` is
* `{ plugin, element, component, surface }`, `element` the button's `key`.
*
* `next(e)` runs the hooks beneath, then core: the element's own `onPress`
* closure, in its plugin's environment, resolving to `{ element }`. Return
* `next(e)` to let the press through, or `{ element }` to take it.
*/
'ui.press': UiPressArgument;
/**
* Fires when an `Input` a render hook drew changes or is submitted; `e` is
* `{ plugin, element, component, surface, kind, value }`.
*
* `next(e)` runs the hooks beneath, then the element's own `onInput` or
* `onSubmit` with `e.value` as the chain left it, resolving to `{ element,
* value }`; `next({ ...e, value })` rewrites the typing, an answer takes it.
*/
'ui.input': UiInputArgument;
/**
* Fires when a `Select` a render hook drew is picked from; `e` is
* `{ plugin, element, component, surface, value }`.
*
* `next(e)` runs the hooks beneath, then the element's own `onSelect` with
* `e.value` as the chain left it, resolving to `{ element, value }`;
* `next({ ...e, value })` rewrites the pick, an answer takes it.
*/
'ui.select': UiSelectArgument;
/**
* Fires when a `Client` THIS plugin drew posts from its surface module
* (`surface.post(data)`); only this plugin's hooks see it.
*
* `next.origin` names `client`: `data` came from code. Core answers `{}`;
* `next({ ...e, data })` rewrites the data; `{ props }` hands the posting
* instance its next props without a redraw. One per instance per frame.
*/
'ui.message': UiMessageArgument;
/**
* Fires before a site's window moves: the person's wheel or scroll keys on
* a `Pane` body or the `AbovePrompt` band, at its edges too; `$.ui.scroll`.
*
* `next(e)` moves it to `e.offset` and draws: `{}`; `next({ ...e, offset
* })` elsewhere; no `next` (`{}` or `{ deny }`) leaves it undrawn, so a hook
* drawing its own rows under a header moves them by `e.by` and invalidates.
*
* @example
* on("ui.scroll", { requestId: "log" }, ($, e) => (scrollOwnRows(e.by), {}))
*/
'ui.scroll': UiScrollInput;
/**
* Fires before a site's focus ring moves: the person's Tab, arrows or click
* in a `Pane` or the band; an `autoFocus` element taking it; `$.ui.focus`.
*
* `next(e)` lands it on `e.element` (absent: one of the engine's stops) and
* draws: `{}`; `next({ ...e, element })` on another of `e.plugin`'s; no
* `next` (`{}` or `{ deny }`) keeps it where it was, drawn as it was.
*
* @example
* on("ui.focus", { requestId: "list" }, ($, e, next) => (mark(e), next(e)))
*/
'ui.focus': UiFocusInput;
/**
* Fires when the engine offers an agent type to the model, in the agent
* listing and again at dispatch; `next(e)` resolves to `{ isOffered: true }`.
*
* Return `{ isOffered: false }` to keep the type out of the listing and
* refuse its dispatch. A hook that fails passes it through.
*
* @example
* on("agent.offer", { agent: "Plan" }, () => ({ isOffered: false }))
*/
'agent.offer': AgentOfferInput;
/**
* Fires when the Agent tool is about to start a subagent, everything
* decided and its model not yet resolved.
*
* `next(e)` resolves to `{ model }`. Return it, `next({ ...e, model })`,
* `{ model }` of your own (an alias resolves like the tool's parameter), or
* `{ deny: reason }`.
*/
'agent.spawn': AgentSpawnInput;
/**
* Fires when a prompt is submitted, before the turn starts. `next(e)` runs
* the hooks beneath and the UserPromptSubmit settings hooks.
*
* Rewrite with `next({ ...e, text })` (the user message on screen follows)
* or stop it with `{ drop: reason }`; a broken plugin never blocks a prompt.
* A prompt typed while a turn ran fires at Enter, with that turn's id.
*/
'prompt.submit': PromptSubmitInput;
/**
* Fires when a text is about to be written into the prompt box as the
* person's draft (a plugin's `$.prompt.fill`); `next(e)` writes it.
*
* Rewrite with `next({ ...e, text })`, or answer `{ isFilled: false }`
* without `next` to keep it out; `origin` passes on as received. Core
* answers `{ isFilled: false }` where no box can take it (a dialog is up).
*
* @example
* on("prompt.fill", ($, e, next) => next({ ...e, text: e.text.trim() }))
*/
'prompt.fill': PromptFillInput;
/**
* Fires when a text is proposed as the prompt box's dim suggestion, Tab to
* take: the engine's guess after a turn, or a plugin's `$.prompt.suggest`.
*
* `next(e)` shows it: `{ isShown }`. Rewrite with `next({ ...e, text })`,
* or answer `{ isShown: false }` without `next` to drop it; core answers
* that too while the box holds text or a turn runs.
*
* @example
* on("prompt.suggest", { origin: { kind: "suggestion" } }, hide)
*/
'prompt.suggest': PromptSuggestInput;
/**
* Fires once per named section of the system prompt, when the engine
* assembles it; `next(e)` resolves to `{ text }` as core computed it.
*
* Sections are cached by name for the session until
* `$.ui.invalidate("prompt.section")`: an unstable answer spends the
* model's prompt cache on every call. A hook that fails passes it through.
*
* @example
* on("prompt.section", { name: "memory" }, () => ({ text: null }))
*/
'prompt.section': PromptSectionInput;
/**
* Fires once per conversation, when the engine computes the context blocks
* its first user message carries; `next(e)` resolves to `{ blocks }`.
*
* Append, drop, reorder or rewrite with `next({ ...e, blocks })`; the
* engine renders what comes back, in order, until
* `$.ui.invalidate("prompt.context")` or a re-read (compaction, `/clear`).
*
* @example
* on("prompt.context", () => ({ blocks: [] }))
*/
'prompt.context': PromptContextInput;
/**
* Fires once per tool, when the engine first renders the tool's schema for
* the model in a session; `next(e)` resolves to `{ description }`.
*
* Rendered schemas are cached for the session until
* `$.ui.invalidate("tool.describe")`: an unstable answer spends the model's
* prompt cache on every call. A hook that fails passes it through.
*
* @example
* on("tool.describe", { tool: "Bash" }, () => ({ description: "Shell." }))
*/
'tool.describe': ToolDescribeInput;
/**
* Fires when a slash command is about to run (`/name args` typed, or a
* plugin's `$.command.run`); `next(e)` resolves to `{ text }`, its output.
*
* Core is the engine's command (a registered one has none). Rewrite `args`
* with `next`, or return `{ text }` without it to answer in its place; one
* after `next` replaces a printed output, not a panel or prompt it opened.
*
* @example
* on("command.run", { command: "hello" }, () => ({ text: "hello" }))
*/
'command.run': CommandRunInput;
/**
* Fires once per command, when the engine lists it for the typeahead and
* `/help`; `next(e)` resolves to `{ description, argumentHint, isHidden }`.
*
* Listed answers are cached for the session until
* `$.ui.invalidate("command.describe")`. A hook that fails passes it
* through.
*
* @example
* on("command.describe", ($, e, next) => next({ ...e, isHidden: true }))
*/
'command.describe': CommandDescribeInput;
/**
* Fires when a `/config` row is about to change, from the menu or a
* plugin's `$.config.set`; `next(e)` resolves to `{ value }` once written.
*
* Return `{ deny: reason }` to leave the row as it is (the menu says why),
* or `next({ ...e, value })` to clamp it; a value of the wrong kind for
* the row is refused. A row a trusted source owns is core's to refuse.
*
* @example
* on("config.set", { key: "theme" }, () => ({ deny: "the theme stays" }))
*/
'config.set': ConfigSetInput;
/**
* Fires once per `/config` row, when the menu lists it and for
* `$.config.list`; `next(e)` resolves to `{ label, description, isHidden }`.
*
* Relabel, re-describe or hide with `next({ ...e, isHidden: true })`; the
* answers are cached until `$.ui.invalidate("config.describe")` or the
* loaded plugins change. A hook that fails passes the row through.
*
* @example
* on("config.describe", { key: "tips" }, hide) // answers isHidden: true
*/
'config.describe': ConfigDescribeInput;
/**
* Fires when the engine expands a skill's prompt for the model (`/name`,
* the Skill tool, a preload); `next(e)` resolves to `{ text }` as computed.
*
* Return `{ text }` with the text the model reads instead. A hook that
* fails passes it through.
*
* @example
* on("skill.prompt", { skill: "commit" }, () => ({ text: "A haiku." }))
*/
'skill.prompt': SkillPromptInput;
/**
* Fires when the engine composes a git text the model is to write (`kind`:
* `commit`, `pr`, `exemption`, `remedy`); `next(e)` resolves to `{ text }`.
*
* Return `{ text }` with the text the model reads instead. A hook that
* fails passes it through.
*
* @example
* on("attribution.text", { kind: "commit" }, () => ({ text: "" }))
*/
'attribution.text': AttributionTextInput;
/**
* Fires once per process for each loaded plugin, before the first prompt,
* and again for one that loads or reloads later; `next(e)` is `{ cwd }`.
*
* Observe. The first is awaited, so a `$.tool.register` here is listed by
* turn one. A later one (an edit, new options, `/reload-plugins`, an enable)
* runs that plugin's hooks alone, so its timers start again. Not `/clear`.
*
* @example
* on("session.start", ($, e, next) => $.tool.register(t).then(() => next(e)))
*/
'session.start': SessionStartInput;
/**
* Fires when a delivery reaches the session (a relay's event, a peer's
* message, a Remote Control prompt), before it is queued; `{ text }`.
*
* Rewrite with `next({ ...e, text })`, or return `{ consumed: reason }` to
* take it: nothing is queued, shown or read by the model. `origin` and
* `event` pass on as received; `session.send`, its dual, is reserved.
*
* @example
* on("session.receive", { origin: "peer" }, () => ({ consumed: "muted" }))
*/
'session.receive': SessionReceiveInput;
/**
* Fires when the conversation is about to be compacted (`/compact`, the
* threshold, a plugin, or ahead of time); `next(e)` resolves `{ messages }`.
*
* Rewrite `instructions` or `messages` on the way down, the messages on
* the way up, or answer `{ messages }` of your own; `{ skip: reason }`
* leaves the conversation as it is. `trigger` passes on as received.
*
* @example
* on("session.compact", { trigger: "precompute" }, () => ({ skip: "off" }))
*/
'session.compact': SessionCompactInput;
/**
* Fires when a remote client joins the session's roster of attached
* surfaces: it said so (ui_attach), or it first asked to draw.
*
* Observe (a phone joined: draw the lobby); `next(e)` resolves to
* `{ clientId }`, a different return changes nothing. `$.session.surfaces()`
* reads the roster; a render hook still reads `e.surface` per ask.
*
* @example
* on("session.attach", { surface: "mobile" }, ($, e, next) => next(e))
*/
'session.attach': SessionAttachInput;
/**
* Fires when a client leaves the roster: it detached, or the session ended
* with it attached (`e.reason`). Observe; `next(e)` echoes `{ clientId }`.
*/
'session.detach': SessionDetachInput;
/**
* Fires once per hooks module about to join the chain, at load (the set
* folded and built, nothing swapped in) and at reload; core allows.
*
* Return `{ refuse: reason }` and it never joins: no hook, no noun, no tool
* of it; the transcript names who refused. Its judges, `$` whole, are the
* plugins admitted before it and the binary's; judge by `tier` and `uses`.
*
* @example
* on("plugin.register", { tier: "user" }, () => ({ refuse: "managed only" }))
*/
'plugin.register': PluginRegisterInput;
/**
* Fires when a model turn begins, before its first model call; `next(e)`
* resolves to `{ turnId }`. Observe: a different return changes nothing.
*/
'turn.start': TurnStartInput;
/**
* Fires when the engine is about to send a model request of a turn, main's
* or a subagent's (`e.agentId`); `next(e)` resolves to the whole response.
*
* `next({ ...e, model })` or `effort` sends another; the turn, the index and
* the message count are pinned. An answer without `next` sends no request.
* Every request of the turn passes here; `turn.complete` follows the last.
*/
'turn.step': TurnStepInput;
/**
* Fires when a model turn has ended, at the point its duration is reported;
* `next(e)` resolves to `{ text }`, the answer. `e.reason` says why.
*
* Return `{ text }` with a different text to show it beneath the answer (a
* synopsis, a TL;DR line); the transcript's record is never rewritten. A
* hook that fails leaves the answer as it was.
*/
'turn.complete': TurnCompleteInput;
/**
* Runs while `$` is being built, once per load or reload of this plugin
* and before any other hook of it; `next(e)` resolves to `$` built so far.
*
* A step may ADD nouns and WITHHOLD nouns (leave one out, or return without
* `next`); it may NOT REPLACE one another step added: the step fails, named
* with both plugins. A step that fails unloads its plugin; `$` is rebuilt.
*/
'engine.create': EngineCreateInput;
};
/**
* `$`, the first parameter of every hook. Frozen; core's interface plus
* every noun the plugins' `engine.create` steps added.
*
* Flat, `.`; it does not carry `on`, since registration happens
* before `$` exists. An interface so a plugin types the noun it provides by
* declaration merging, the way a jQuery plugin types `$.fn`.
*
* @example
* declare module "claude-code" { interface EngineInterface { voice: Voice } }
*/
export interface EngineInterface extends CoreEngineInterface {
}
/**
* What `next(e)` resolves to at `engine.create`: `$` as the steps beneath
* built it, typed as `$` is, open to nouns no declaration names yet.
*
* A withheld noun is on it as a stub (a step inside withheld it, or the
* last fold did and this is a reload), and the host refuses an op on one a
* step outside withholds later; a typed module bootstraps at load on it.
*/
export type EngineInterfaceBuilt = EngineInterface & {
readonly [noun: string]: unknown;
};
/**
* The engine's events' results.
*/
export type EngineResultOf = {
/**
* `{ result, context? }`, `{ deny }`, or core's `{ ref, result }`.
*/
'tool.call': ToolCallResult;
/**
* `{ decision, reason?, rule? }`.
*/
'tool.check': ToolCheckResult;
/**
* The tree to draw; `{ type: "engine", ref }` is core's own drawing.
*/
'ui.render': RenderElement;
/**
* The surface's element table (Elements[e.surface]): constructors from props
* to a RenderElement.
*/
'ui.resolve': ElementTable;
/**
* `{ element }`: the element whose handler the press reached.
*/
'ui.press': UiPressResult;
/**
* `{ element, value }`: the field whose handler the input reached, and
* the text it received.
*/
'ui.input': UiInputResult;
/**
* `{ element, value }`: the picker whose handler the pick reached, and
* the value it received.
*/
'ui.select': UiSelectResult;
/**
* `{ props? }`: the posting instance's next props, when a hook hands some.
*/
'ui.message': UiMessageResult;
/**
* `{}` once the window moved, or `{ deny }`.
*/
'ui.scroll': UiScrollResult;
/**
* `{}` once the ring moved, or `{ deny }`.
*/
'ui.focus': UiFocusResult;
/**
* `{ isOffered }`.
*/
'agent.offer': AgentOfferResult;
/**
* `{ model }` or `{ deny }`.
*/
'agent.spawn': AgentSpawnResult;
/**
* `{ text, context? }` or `{ drop }`.
*/
'prompt.submit': PromptSubmitResult;
/**
* `{ isFilled }`.
*/
'prompt.fill': PromptFillResult;
/**
* `{ isShown }`.
*/
'prompt.suggest': PromptSuggestResult;
/**
* `{ text }` (null leaves the section out).
*/
'prompt.section': PromptSectionResult;
/**
* `{ blocks }` (a block left out is not sent).
*/
'prompt.context': PromptContextResult;
/**
* `{ description }`.
*/
'tool.describe': ToolDescribeResult;
/**
* `{ text }` (the command's output, when it printed one).
*/
'command.run': CommandRunResult;
/**
* `{ description, argumentHint, isHidden }`.
*/
'command.describe': CommandDescribeResult;
/**
* `{ value }` once written, or `{ deny }`.
*/
'config.set': ConfigSetResult;
/**
* `{ label, description, isHidden }`.
*/
'config.describe': ConfigDescribeResult;
/**
* `{ text }`.
*/
'skill.prompt': SkillPromptResult;
/**
* `{ text }`.
*/
'attribution.text': AttributionTextResult;
/**
* `{ cwd }`.
*/
'session.start': SessionStartResult;
/**
* `{ text }`, or `{ consumed }`.
*/
'session.receive': SessionReceiveResult;
/**
* `{ messages, tokensBefore?, tokensAfter? }`, or `{ skip }`.
*/
'session.compact': SessionCompactResult;
/**
* `{ clientId }`.
*/
'session.attach': SessionAttachResult;
/**
* `{ clientId }`.
*/
'session.detach': SessionDetachResult;
/**
* `{ allow: true }`, or `{ refuse }`.
*/
'plugin.register': PluginRegisterResult;
/**
* `{ turnId }`.
*/
'turn.start': TurnStartResult;
/**
* The response: `{ turnId, index, answer, toolUses, stopReason, usage }`.
*/
'turn.step': TurnStepResult;
/**
* `{ text }`.
*/
'turn.complete': TurnCompleteResult;
/**
* `$` as built so far, with this plugin's interface added and any it
* withheld left out; `next(e)` resolves to EngineInterfaceBuilt.
*/
'engine.create': EngineCreateResult;
};
/**
* The engine's own events as calls on `$`, one signature each:
* `$..(input)` resolves to its result, or to its stream.
*
* The engine raises its events through these same calls; a plugin's call
* runs the same chain with the calling hook alone skipped. `input` may leave
* out what the engine fills (`tool_use_id`, the parent agent).
*/
export type EventCalls = {
tool: {
call: ToolCallOverloads;
check: (input: ToolCheckArgs) => Promise;
describe: (input: ToolDescribeInput) => Promise;
};
command: {
run: (input: CommandRunArgs) => Promise;
describe: (input: CommandDescribeInput) => Promise;
};
config: {
set: (input: ConfigSetArgs) => Promise;
describe: (input: ConfigDescribeInput) => Promise;
};
prompt: {
submit: (input: PromptSubmitArgs) => Promise;
fill: (input: PromptFillArgs) => Promise;
suggest: (input: PromptSuggestArgs) => Promise;
section: (input: PromptSectionInput) => Promise;
context: (input: PromptContextInput) => Promise;
};
skill: {
prompt: (input: SkillPromptInput) => Promise;
};
attribution: {
text: (input: AttributionTextInput) => Promise;
};
agent: {
offer: (input: AgentOfferInput) => Promise;
spawn: (input: AgentSpawnArgs) => Promise;
};
session: {
start: (input: SessionStartInput) => Promise;
receive: (input: SessionReceiveInput) => Promise;
compact: (input?: SessionCompactArgs) => Promise;
attach: (input: SessionAttachInput) => Promise;
detach: (input: SessionDetachInput) => Promise;
};
turn: {
start: (input: TurnStartInput) => Promise;
step: (input: TurnStepInput) => HookStream;
complete: (input: TurnCompleteInput) => Promise;
};
ui: {
render: (input: RenderInput) => Promise;
resolve: (e: E) => Elements[E['surface']];
scroll: (input: UiScrollArgs) => Promise;
focus: (input: UiFocusArgs) => Promise;
};
};
/**
* The name of an event: a key of EventOf, the engine's own (CoreEventName)
* and the declared plugin nouns' (NounEventName).
*/
export type EventName = keyof EventOf;
/**
* The argument of each event, by event name: what a hook receives as `e`
* and what the call on `$` takes. Plain data, frozen to every depth.
*
* The engine's own events (CoreEventOf: a plugin's `$.fs.write(...)` is
* a dispatch the hooks above it see) and the methods of the plugin nouns
* declared on EngineInterface (NounEventOf).
*/
export type EventOf = CoreEventOf & NounEventOf;
/**
* The result of event `N`: what its hooks return and what their `next(e)`
* resolves to.
*/
export type EventResult = ResultOf[N];
/**
* The hook signature of each event, `($, e, next)`, as one mapped type over
* EventOf; a streaming event's is the generator form (StreamHook).
*
* With one handler type per event, `Events[E]` for a generic E would be a
* union; as one mapped type it stays a single function type the engine
* calls without a cast (TS 4.6 correlated unions).
*
* @param $ the engine interface, frozen, the same object at every invocation;
* at `engine.create` the empty table, since `$` exists after the fold
* @param e the event's argument, frozen to every depth (`e.command = "ls"`
* is a type error and throws); a rewrite is a copy passed to `next`
* @param next the rest of the chain; a chain a hook raises through `$` skips
* this hook and runs every other, its plugin's others too
*/
export type Events = {
[E in keyof EventOf]: E extends StreamingEventName ? StreamHook : ($: E extends 'engine.create' ? NoEngineInterface : EngineInterface, e: Frozen>, next: Next) => EventResult | Promise>;
};
type ExitReason = 'clear' | 'resume' | 'logout' | 'prompt_input_exit' | 'other';
type FileChangedHookInput = BaseHookInput & {
hook_event_name: 'FileChanged';
file_path: string;
event: 'change' | 'add' | 'unlink';
};
/**
* `T` with every property read-only to every depth, arrays and tuples kept
* as declared: how a hook's `e` is typed.
*
* `e.command = 'x'` is a type error; `next({ ...e, command: 'x' })` compiles.
*/
export type Frozen = T extends (...args: never[]) => unknown ? T : T extends readonly unknown[] ? {
[K in keyof T]: Frozen;
} : T extends object ? {
readonly [K in keyof T]: Frozen;
} : T;
/**
* One file `$.fs.ancestors` found: the directory it stands in, the name it
* was asked for by, and its text as the engine's memory loader reads it.
*/
export type FsAncestor = {
/**
* The directory the file stands in, absolute.
*/
dir: string;
/**
* The spelling the caller asked for it by.
*/
name: string;
/**
* The file's text, with what its `@include`s bring after it.
*/
content: string;
};
/**
* The argument of `$.fs.ancestors`: the file names to look for in each
* directory, and the file to walk down to.
*/
export type FsAncestorsRequest = {
/**
* Relative `.md` file names, each looked for in every directory.
*/
names: readonly string[];
/**
* The file the walk goes on down to the directory of, relative to the
* working directory or absolute; absent, it ends at the working directory.
*/
of?: string;
};
/**
* One entry of `$.fs.list`.
*/
export type FsEntry = {
/**
* The entry's name (no directory part).
*/
name: string;
/**
* `file`, `dir`, or `other`.
*/
kind: 'file' | 'dir' | 'other';
/**
* Bytes, for a file.
*/
size: number;
};
/**
* What `$.fs.stat` resolves with.
*/
export type FsStat = {
/**
* `file`, `dir`, or `other`.
*/
kind: 'file' | 'dir' | 'other';
/**
* Bytes, for a file.
*/
size: number;
/**
* Last modification, milliseconds since the epoch.
*/
mtimeMs: number;
};
/**
* Every event (`*`), or every event under a namespace (`classic.*`: each
* one whose name starts with `classic.`).
*/
export type Glob = '*' | `${Namespace}.*`;
/**
* The hook `on(pattern, hook)` takes for a glob or a negation: one function
* placed on every selected event, `e` and the result typed as their union.
*
* `next.event` says which event a run is; `next.is(pattern, e)` narrows `e`
* to one of them. At `engine.create` (a negation may select it) `$` is the
* empty table and the hook observes the fold, as a `*` hook does.
*/
export type GlobHook> = ($: EngineInterface, e: Frozen>, next: GlobNext) => EventResult | Promise>;
/**
* `next` in a hook on a glob or a negation: an overload per selected event,
* then one over their union for an `e` not yet narrowed.
*
* `is` narrows `e` to the selected events its pattern names; `event` is one
* of the selected names.
*/
export type GlobNext> = OrderedOverloads & {
(e: Args): Promise>;
/**
* Continues this dispatch at a tier, as Next's `to` (a managed hook's),
* over the selected events' union for an `e` not yet narrowed.
*/
readonly to: (e: Args, tier: TargetTier) => Promise>;
readonly signal: AbortSignal;
readonly is: >(pattern: M, e: unknown) => e is Frozen>>>;
readonly event: N;
readonly origin: Origin;
readonly trace: readonly TraceEntry, GlobNextResult>[];
};
/**
* What `next(e)` resolves to in a glob hook before `e` is narrowed: the
* NextResult of each selected event, as a union.
*/
type GlobNextResult = {
[K in N]: NextResult;
}[N];
/**
* One hook, `($, e, next)`, on event `E`.
*/
export type Hook = Events[E];
/**
* Why a hook failed, as its `.catch` handler reads it on `next.error`: plain
* frozen data.
*
* `throw`: the hook threw, or returned what the site refuses, `message`
* saying what; `timeout`: it outran its budget, `message` then what its last
* `next()` rejected with, if it did. `budget` is the handler's own grace.
*/
export type HookFailure = {
readonly kind: 'throw' | 'timeout';
/**
* The thrown error's message, or for a timeout what the hook's last
* `next()` rejected with; absent for a timeout with nothing rejected.
*/
readonly message?: string;
/**
* The grace the handler runs under, in milliseconds; past it, the hook is
* absent as if it had no handler.
*/
readonly budget: number;
};
/**
* The hook type per pattern: an event's own (Events), `*`'s (AnyEventHook),
* or a glob's over the events it selects (GlobHook), as one conditional type.
*
* One type, so the two-argument `on` stays ONE generic signature: as an
* overload set the language service offers no tool-name completions inside
* `e.tool === "`; as an index into a table TS intersects every argument.
*/
export type HookFor = P extends '*' ? AnyEventHook : P extends EventName ? Events[P] : GlobHook
;
type HookInput = PreToolUseHookInput | PostToolUseHookInput | PostToolUseFailureHookInput | PostToolBatchHookInput | PermissionDeniedHookInput | NotificationHookInput | UserPromptSubmitHookInput | UserPromptExpansionHookInput | SessionStartHookInput | SessionEndHookInput | StopHookInput | StopFailureHookInput | SubagentStartHookInput | SubagentStopHookInput | PreCompactHookInput | PostCompactHookInput | PreModelSwitchHookInput | PostModelSwitchHookInput | PermissionRequestHookInput | SetupHookInput | TeammateIdleHookInput | TaskCreatedHookInput | TaskCompletedHookInput | ElicitationHookInput | ElicitationResultHookInput | ConfigChangeHookInput | InstructionsLoadedHookInput | WorktreeCreateHookInput | WorktreeRemoveHookInput | CwdChangedHookInput | FileChangedHookInput | DirectoryAddedHookInput | MessageDisplayHookInput;
/**
* The hook event `E` takes: an async generator over its chunks for a
* streaming event (`turn.step`), `($, e, next) => result` for every other.
*/
export type HookOf = Events[E];
/**
* What a hooks module exports: `register`, and nothing the loader reads
* besides.
*/
export type HooksModule = {
register: Register;
};
/**
* What `next(e)` returns on a streaming event: the stream of everything
* beneath, chunk by chunk, whose return value is the result from beneath.
*
* `yield* next(e)` forwards the chunks and evaluates to that result; a
* transforming hook reads `for await (const chunk of stream)` and then
* `await stream.result`. Each call runs what is beneath afresh.
*/
export type HookStream = AsyncGenerator & {
/**
* Settles with what beneath returned once the stream has been read to
* its end; rejects if the stream is closed before that.
*/
readonly result: Promise;
};
/**
* Options of `$.http.fetch`.
*/
export type HttpInit = {
/**
* `GET` (default), `POST`, ...
*/
method?: string;
/**
* Request headers.
*/
headers?: Record;
/**
* The request body, as text.
*/
body?: string;
/**
* The handle `$.session.authorize()` answered: the engine sets the
* session's credential header itself, only for a first-party host.
*/
auth?: string;
};
/**
* What `$.http.fetch` resolves with.
*/
export type HttpResponse = {
/**
* The HTTP status code.
*/
status: number;
/**
* True for a 2xx status.
*/
ok: boolean;
/**
* Response headers, lower-cased names.
*/
headers: Record;
/**
* The body, as text.
*/
text: string;
};
/**
* The keys of object pattern `P` that object member `E` cannot satisfy, `D`
* levels down; `never` when there is none, which is what keeps the member.
*
* A key `E` does not have (an open record has every string key), or one
* whose value `P` narrows to nothing.
*/
type ImpossibleKeys = {
[K in keyof P]-?: K extends keyof E ? [NarrowedValue, P[K], D>] extends [never] ? K : never : K;
}[keyof P];
/**
* The props of `Input`, every surface's one-line text field: an address,
* optional texts, and the closures a change and a submit run. A leaf.
*
* Focused through the same ring as `Button` (`abovePrompt:focus`); while it
* has focus every printable key reaches it alone and Esc returns them; a
* change and Enter raise `ui.input`, whose bottom is `onInput` / `onSubmit`.
*/
export type InputProps = {
/**
* The element's address: `e.element` at `ui.input`, what a matcher names.
*/
key: string;
/**
* Text drawn before the field.
*/
label?: string;
/**
* Text drawn dim in an empty field.
*/
placeholder?: string;
/**
* The text the field holds when drawn; the person's typing replaces it
* until the hook draws another.
*/
value?: string;
/**
* What Enter does, in a word or two, drawn beside the field while it has
* focus (`send`). Defaults to `submit`.
*/
submitLabel?: string;
/**
* The site's focus ring starts here when the site takes the keyboard,
* instead of on nothing, as the DOM's `autofocus`: Enter acts on it at once.
*
* A pane opened with `focus`, or the person's focus chord or click, is the
* take. Of several in one site the first drawn wins; it raises `ui.focus`,
* origin this plugin. A ring the person has moved stays where it was put.
*/
autoFocus?: true;
/**
* Runs on every change of the text, in the plugin's own environment: the
* bottom of a `ui.input` chain of kind `change`.
*/
onInput?: (value: string, e: UiInputArgument) => void;
/**
* Runs on Enter with the text, in the plugin's own environment: the bottom
* of a `ui.input` chain of kind `submit`. No model turn unless it asks one.
*/
onSubmit: (value: string, e: UiInputArgument) => void;
};
type InstructionsLoadedHookInput = BaseHookInput & {
hook_event_name: 'InstructionsLoaded';
file_path: string;
memory_type: 'User' | 'Project' | 'Local' | 'Managed';
load_reason: 'session_start' | 'nested_traversal' | 'path_glob_match' | 'include' | 'compact';
globs?: string[];
trigger_file_path?: string;
parent_file_path?: string;
};
/**
* What `$.ui.invalidate` takes: a render event, or one of the five events
* whose answers the engine caches for the session.
*/
export type InvalidatableEventName = RenderEventName | 'prompt.section' | 'prompt.context' | 'tool.describe' | 'command.describe' | 'config.describe';
/**
* Whether tag key `K` selects members of `I`: it does when each member gives
* it ONE literal (`component: "ToolUse"` on the ToolUse variant).
*
* A key that is the same union on every member is a filter at runtime; it
* is left out of the selection so that it cannot defeat the narrowing the
* other keys give.
*/
type IsDiscriminant = I extends unknown ? K extends KnownKeys ? IsSingleLiteral : true : never;
/**
* Whether `V` is made of literals only: `"a" | "b"` is, `string` is not.
*/
type IsLiteralValued = string extends V ? false : number extends V ? false : boolean extends V ? false : [V] extends [string | number | boolean] ? true : false;
/**
* Whether `V` is exactly one string, number or boolean literal.
*/
type IsSingleLiteral = [V] extends [string | number | boolean] ? IsUnion extends true ? false : true : false;
/**
* Whether `T` is a union of two or more members.
*/
type IsUnion = T extends unknown ? [U] extends [T] ? false : true : never;
/**
* Plain data: what JSON holds, and what crosses between a plugin's hooks
* module and its surface module whole (a `Client`'s props, a post's data).
*
* A function, a class instance, `undefined` or a cycle is not plain data;
* the engine refuses one where it checks, and drops it where it clones.
*/
export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
readonly [key: string]: JsonValue;
};
/**
* What `next` takes in a matched hook: the variants of `e` the matcher can
* match (KeptMembers), as declared, so a rewrite of a pinned field passes.
*/
type KeptEvent = MatchedNames
extends infer N extends EventName ? N extends unknown ? KeptMembers, M> : never : never;
/**
* The members of the argument union `E` matcher `P` can match, as declared;
* what `next` takes, so a rewrite may change a field the matcher pinned.
*
* A member is dropped when `P` names a key it lacks, or gives a key, at any
* depth, a value none of that key's values can equal (ImpossibleKeys).
*/
type KeptMembers = E extends unknown ? [ImpossibleKeys] extends [never] ? E : never : never;
/**
* The declared keys of `T`, the string and number index signatures left out.
*/
type KnownKeys = keyof {
[K in keyof T as string extends K ? never : number extends K ? never : K]: 0;
};
/**
* The events whose overload must come after the rest, lest it shadow them.
*
* `classic.PreToolUse` shares `tool.call`'s envelope, `turn.abort` every
* turn event's `turnId`, and an object of any shape is assignable to NoArgs.
*/
type LateOverload = 'classic.PreToolUse' | 'turn.abort' | NoArgsEvent;
/**
* The props of `Link`, a hyperlink every surface draws: an OSC 8 span on the
* terminal (else its text then the URL in dim), an anchor on desktop.
*
* An inline element: its children are the text, strings and inline
* elements; absent children the `label`, absent both the URL. The engine
* bounds `href` before the tree crosses.
*/
export type LinkProps = {
/**
* Where the link goes: an `https:` URL (or `http://localhost`), at most
* 2048 characters of printable ASCII, spelled as `new URL(href).href`.
*
* No `user@host` part, no raw `@`, space or non-ASCII letter (encode them);
* anything else refuses the tree the Link is in.
*/
href: string;
/**
* The text drawn when the element has no children; absent both, the URL
* itself is the text.
*/
label?: string;
};
/**
* What a matcher value selects by: itself, or `unknown` for a RegExp, which
* selects nothing.
*/
type Literal = X extends RegExp ? unknown : X;
/**
* The argument a matched hook receives: `e` narrowed by `M` (Narrowed), per
* event the registration covers.
*/
export type MatchedEvent = MatchedNames
extends infer N extends EventName ? N extends unknown ? Narrowed, M> : never : never;
/**
* The hook `on(pattern, matcher, hook)` takes: `($, e, next)` with `e`
* narrowed by the matcher (MatchedEvent), and a tagged result the same way.
*
* On a streaming event it is the generator form (MatchedStreamHook). `next`
* takes the variants the matcher keeps, as declared (KeptEvent); `next.is`
* names the events the registration covers and narrows as the matcher does.
*/
export type MatchedHook = P extends StreamingEventName ? MatchedStreamHook
: ($: EngineInterface, e: Frozen>, next: Next, KeptEvent, MatchedResult
, {
[K in MatchedNames
]: Narrowed, M>;
}>) => MatchedResult | Promise>;
/**
* The events a matched registration on `P` covers: the event named, or for a
* glob every selected event whose input has each key the matcher names.
*/
type MatchedNames = P extends EventName ? P : {
[N in Selected
]: [M] extends [never] ? N : keyof M extends AnyKeyOf> ? N : never;
}[Selected];
/**
* What a matched hook returns: the event's result, narrowed by `M` where the
* result is a union tagged by the matcher's tag keys.
*/
export type MatchedResult
= MatchedNames
extends infer N extends EventName ? N extends unknown ? Select, Selection, M>> : never : never;
/**
* The hook `on(event, matcher, hook)` takes on a streaming event: the
* generator form, `e` narrowed by the matcher, `next(e)` the stream beneath.
*/
export type MatchedStreamHook = ($: EngineInterface, e: Frozen>, next: MatchedStreamNext) => StreamHookBody, MatchedResult>;
/**
* What a matched streaming hook's `next.is(pattern, e)` narrows `e` to:
* the streaming event's argument narrowed by the matcher.
*/
type MatchedStreamNarrowings
= {
[K in P]: Narrowed, M>;
};
/**
* A matched streaming hook's `next`: the variants the matcher keeps, the
* result tagged the same way, `next.is` narrowing as the matcher does.
*/
type MatchedStreamNext = StreamNext
, MatchedResult
, MatchedStreamNarrowings
>;
/**
* What `on(event, matcher, hook)` takes for an argument of type `I`: the
* shape of the `e` the hook wants, a partial of it at any depth.
*
* A leaf is `===` or a RegExp (`{ command: /^p4 / }`); an array is any-of;
* an object is a partial of an OBJECT, so `{ command: { startsWith } }` is
* a type error where `e` is typed and free where it is `unknown`.
*/
export type Matcher = I extends unknown ? {
readonly [K in KnownKeys]?: MatcherValue>;
} & (string extends keyof I ? OpenMatcher : unknown) : never;
/**
* Any matcher at all, for a field typed `unknown` (a tool's input, a
* result's output): the kinds the engine accepts, unchecked there.
*
* The four kinds: a scalar is `===`; a RegExp tests the value as a string
* (`{ command: /^p4 / }`); an array matches if any element does; an object
* is a partial of an object. `{ startsWith: 'p4' }` never matches a string.
*/
type MatcherData = string | number | boolean | null | RegExp | readonly MatcherData[] | {
readonly [key: string]: MatcherData;
};
/**
* The declared keys of every variant of `I` (index signatures aside).
*/
type MatcherKeys = I extends unknown ? KnownKeys : never;
/**
* What matches one value of type `V`, by the runtime's kinds:
* a scalar leaf takes the value or a RegExp; an object, a partial of it.
*
* For an array, what matches one ELEMENT of it, since a pattern against an
* array value holds when some element matches; for `unknown`, any matcher.
* A scalar matches by `===`, a RegExp tests the value as a string.
*/
type MatcherOne = unknown extends V ? MatcherData : V extends readonly (infer Item)[] ? MatcherOne- : V extends string | number | boolean | null ? V | RegExp : V extends object ? Matcher : V extends undefined ? never : unknown;
/**
* What a matcher gives a key whose value is `V` on this variant and `Across`
* over every variant: one MatcherOne, or an array of them matched as one-of.
*
* The one-of is typed over every variant, so `{ tool: ['Bash', 'Read'] }`
* types on the Bash variant; with a nested pattern beside it, the pattern is
* checked against a variant the one-of names, not against each of them.
*/
type MatcherValue = MatcherOne | readonly MatcherOne[];
/**
* The type of key `K` across the variants of `I` that declare it.
*/
type MatcherValueOf = I extends unknown ? K extends KnownKeys ? I[K] : never : never;
/**
* One block of an MCP result: `type` and the fields that kind of block carries.
*/
export type McpContentBlock = {
/**
* The block's kind: `text`, `image`, `audio`, `resource`, `resource_link`.
*/
type: string;
/**
* Set on a `text` block.
*/
text?: string;
/**
* Set on a `resource_link` (or embedded `resource`) block.
*/
uri?: string;
/**
* Declared by an image, audio or resource block.
*/
mimeType?: string;
[field: string]: unknown;
};
/**
* The MCP branch: one variant per declared tool when McpToolInputs has
* entries, else one loose variant over every `mcp__*` name.
*/
export type McpToolCallInput = [keyof McpToolInputs] extends [never] ? McpToolCallInputFallback : {
[N in keyof McpToolInputs & string]: ToolInputOf>;
}[keyof McpToolInputs & string];
/**
* The MCP branch's answer when no MCP tool is declared: every `mcp__*`
* name, its args unconstrained.
*/
type McpToolCallInputFallback = {
/**
* The name of the tool being called (`mcp____`); comparing
* it narrows `e`. Reserved: a rewrite of it is ignored by core.
*/
tool: McpToolName;
/**
* The tool_use block's id: the same at every event of the call and in
* `$.ui.notice`. Reserved: a rewrite of it is ignored by core.
*/
tool_use_id: string;
[argument: string]: unknown;
};
/**
* The inputs of the MCP tools this project knows, keyed by full tool name,
* for declaration merging; empty by default, then every MCP tool is loose.
*
* A `.d.ts` in the plugin author's project (written by `/plugin-types `
* from the connected servers' JSON Schemas, or by hand) adds entries under
* `declare module "claude-code"`; `e.tool === ` then narrows to them.
*
* @example
* interface McpToolInputs { "mcp__my_server__send": { to: string } }
*/
export interface McpToolInputs {
}
/**
* The name of an MCP tool as the engine spells it: `mcp____`.
*/
export type McpToolName = `mcp__${string}__${string}`;
/**
* An MCP tools/call result as the SDK returns it, plain data.
*/
export type McpToolResult = {
/**
* The result's content blocks, in order (text, image, resource,
* resource_link, ...).
*/
content: McpContentBlock[];
/**
* True when the server reported the call as failed; the blocks then describe
* the error.
*/
isError: boolean;
/**
* The server's structured result, when its tool declares an output schema.
*/
structuredContent?: unknown;
};
/**
* Hook input for the MessageDisplay event. Fired with each batch of newly completed lines while an assistant message streams. Display-only: the stored message and what the model sees are untouched.
*/
type MessageDisplayHookInput = BaseHookInput & {
hook_event_name: 'MessageDisplay';
/**
* UUID of the current turn.
*/
turn_id: string;
/**
* UUID of the assistant message being displayed. Stable across every flush of the same message. Not the API msg_... id.
*/
message_id: string;
/**
* Zero-based index of this delta within the message. Increments by one per flush.
*/
index: number;
/**
* True on the message's last flush. Exactly one flush per message has it.
*/
final: boolean;
/**
* The newly completed lines since the prior flush. Always whole lines, except on the final flush which may end mid-line. The delta of the final flush is empty when the message ends on a newline; treat final as the end-of-message signal regardless.
*/
delta: string;
};
/**
* What `$.model.complete` takes.
*/
export type ModelCompleteRequest = {
/**
* An alias (`haiku`) or a full model id; resolved and allowlist-checked like
* a `--model` value.
*/
model: string;
/**
* The one user message; the reply's text comes back.
*/
prompt: string;
/**
* Precedes the completion as its system prompt, after the CLI's identity
* block. Default none.
*/
system?: string;
/**
* The reply's token cap. Default 256.
*/
maxTokens?: number;
};
/**
* What `$.model.fork` takes.
*/
export type ModelForkRequest = {
/**
* The one user message, appended to the session's own transcript.
*
* The reply's text and usage come back, or null on an API error or a cold
* transcript.
*/
prompt: string;
};
/**
* What `$.model.fork` resolves to when the fork answered: the reply's text
* and what the fork cost.
*/
export type ModelForkResult = {
/**
* The non-error replies' text, joined by newlines.
*/
text: string;
/**
* The fork's token counts, so a plugin can account for what it spent.
*/
usage: ModelForkUsage;
};
/**
* What one fork cost, as the API counted it: the four token counts of the
* fork's completions summed.
*/
export type ModelForkUsage = {
input_tokens: number;
output_tokens: number;
cache_read_input_tokens: number;
cache_creation_input_tokens: number;
};
/**
* The prefixes a glob may name: one or more whole leading segments of an
* event name (`tool` of `tool.call`), derived, plugin nouns included.
*/
export type Namespace = N extends `${infer Head}.${infer Rest}` ? Head | `${Head}.${Namespace}` : never;
/**
* How many object or array levels a matcher narrows `e` through, counted as
* a tuple's length: the runtime's own limit, which refuses a deeper matcher.
*
* Past it a field keeps its declared type; nothing becomes `any`.
*/
type NarrowDepth = 8;
/**
* `e` in a matched hook: the members of the argument union `E` matcher `P`
* can match, each with the keys `P` names narrowed to what a match implies.
*
* A scalar narrows to the literal, a one-of to what its alternatives give,
* an object key recursively, an array some element of which must match to
* a non-empty tuple; other keys, `unknown` and RegExp-matched fields keep.
*/
export type Narrowed = E extends unknown ? NarrowedMember : never;
/**
* A value of declared type `V` under a one-of, folded over the tuple into
* `Found`: the union of what each alternative narrows `V` to (NarrowedByOne).
*
* An alternative that cannot match adds nothing, so a one-of none of whose
* alternatives can is `never`; a one-of typed as a plain array rather than
* a tuple narrows by the union of its elements at once.
*/
type NarrowedByAny = Alternatives extends readonly [infer First, ...infer Rest] ? NarrowedByAny> : Alternatives extends readonly [] ? Found : Found | NarrowedByOne;
/**
* A value of declared type `V` under one matcher node `Q` that is not a
* one-of, member of `V` by member; a member that cannot match is `never`.
*
* An array some element of which must match becomes NonEmpty; a RegExp
* keeps the member; an object pattern recurses into an object member, one
* level down; a scalar keeps a member as narrow, and replaces a wider one.
*/
type NarrowedByOne = V extends readonly (infer Item)[] ? [NarrowedValue