;
+
+ /**
+ * 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