| askForDecision | Asks the human for a decision through two channels in parallel, whichever answers first wins: (1) the installed InteractionPort (the keyboard, for a person running the process in their own terminal, see tui/terminalInteraction.ts), and (2) a response file, for when something else (e.g. Claude Code, or a non-terminal host driving its own UI — an Electron main process, a Tauri sidecar) is piloting the run and has no interactive stdin to write to. |
| askForManualIntervention | Like askForDecision(), for a manual-intervention checkpoint (see tools/manualLogin.ts). |
| buildSessionOptions | Builds the options object passed to the Agent SDK's query() — everything about how the agent runs a session (system prompt, tools, MCP servers, hooks), independent of what is said to start it off (that's the caller's prompt, a plain string for a one-shot run or an AsyncIterable for a multi-turn chat). |
| checkFileScope | The reason to deny toolName with input under scope, or undefined to let it through. Tools other than Read/Write/Edit/Grep are never judged here, and a call without a path is left for the tool itself to reject. |
| checkPlanScope | The reason to deny toolName with input in plan mode, or undefined to let it through. Anything not known to only read is denied, so a tool nobody thought of can't change anything while the human reviews the plan. |
| createConsoleRenderer | The console rendering of runQuery()'s event stream, shared by runChatTui() and by any one-shot run that prints the same events. Streamed text rarely ends in a newline, so every other line (actions, notices, errors) starts by ending the current line only if it isn't already ended — never with a fixed "\n", which leaves a blank line between consecutive actions. |
| createDeferred | Externally-controllable promise — used to know when a chat turn has finished. |
| createFileScopeGate | PreToolUse hook enforcing scope on Read/Write/Edit/Grep, for the main agent and its subagents alike. Bash, when a subagent has it, is not covered: it can reach any path, which is why Bash-granting features stay opt-in. |
| createFriendlyToolLabel | Builds a friendlyToolLabel(toolName, toolInput) — describe lets the host agent layer its own domain-specific cases (e.g. Playwright's browser_* tools) on top of this kit's generic ones; extraLocalServers names any additional MCP server (beyond this kit's own "approvals"/"manualLogin"/"sourceFiles") whose tools should be unwrapped without a "[server] " prefix, e.g. "playwright". |
| createHumanApprovalServer | Checkpoint tool for "guided" mode: the agent explores freely, but must call this tool and wait for human approval right before any action that publishes something visible to others and that's hard to naturally undo. Everything else — browsing and reading — doesn't go through this. |
| createInputQueue | User message queue backed by a single long-lived generator, for any multi-turn (chat-shaped) caller of query(). |
| createManualLoginServer | Checkpoint tool for when the agent hits something it can't do itself (typically a login it has no credentials for, or one that fails unexpectedly). Pauses execution until a human confirms they've intervened by hand in whatever live interface the agent is driving (a browser window, say), and then lets the agent continue. There is deliberately no default wording: what counts as a manual intervention is entirely the domain's, so the concrete agent always supplies texts (see AgentSpec.manualInterventionTexts). |
| createModeControl | A ModeControl starting at initial (buildSessionOptions() returns one for its session). |
| createPlanGate | PreToolUse hook for "plan" mode: the agent only reads and plans (see checkPlanScope()). Registered for every session that can be in plan mode and deciding only while isActive() says it is; otherwise it gives no decision, so the call goes on as if it weren't there. It's the kit's own gate, not the SDK's permissionMode: "plan" (ADR-023). |
| createProgressView | The Ink counterpart of createConsoleRenderer() for one-shot ("run"-style) sessions: the same methods and the same text (so onWrite logs exactly what the console version would), plus a spinner with the current action, approval panels and a status bar. Checkpoints go through an Ink InteractionPort until close(). |
| createPromptLoader | Builds a loadPrompt(relativePath, vars) bound to rootDir — so each agent built on this kit supplies its own prompts directory (its own domain content) and gets the same {{key}} substitution/fail-loudly behavior for free. |
| createRunFolder | Creates a new, empty run folder under runsDir. |
| createRunStore | A SessionStore (the SDK's adapter for keeping transcripts elsewhere) that keeps one session in one run folder: conversation.jsonl for the main transcript, subagents/*.jsonl for each subagent's, and session.json with the session's id and which file is which subagent's. The SDK appends to it as the transcript grows and loads from it before resuming (confirmed empirically: a resumed session remembers the conversation, its subagents' transcripts included, even with the CLI's own copy deleted). The CLI still writes that copy under ~/.claude/projects/: it can't be turned off while a store is in use. |
| createSaveToSourcesServer | save_to_sources: the only way the agent adds to sourcesDir (Write/Edit are scoped to the notes folder, see hooks/fileScopeGate.ts), so the originals stay as obtained. Deliberately just an fs.copyFile with COPYFILE_EXCL — no parsing, no format conversion, no overwriting. A document downloaded via a browser-automation tool typically lands in this run's own folder, which the file tools can't reach; this tool's only job is getting it into sourcesDir, where the SDK's own Read tool already knows how to interpret it (PDF/DOCX text extraction, multimodal images). |
| createStepGate | PreToolUse hook for "interactive" mode: pauses before every action and asks for confirmation (by keyboard or by file, see humanInput.ts), like reviewing a plan step by step. |
| createSubagentBashGate | PreToolUse hook restricting the Bash tool to subagents only, never the main agent directly. See session.ts's buildSessionOptions(), which attaches this hook whenever AgentSpec.buildSubagents() returned something for this config. |
| createSubagentForegroundGate | PreToolUse hook forcing run_in_background: false on every Agent-tool call that spawns one of this session's own subagents. The Agent tool's own input schema defaults to background execution ("Agents run in the background by default", its own description says) unless the model explicitly opts out per call — confirmed empirically (in a real consuming agent) that with a less directive prompt, the model left it on that default, the main turn's "result" fired before the subagent had done anything at all, and the whole invocation was silently lost: no file written, no error, nothing — because the caller's message loop had no code watching for background-task completion (SDKTaskStartedMessage/SDKTaskUpdatedMessage/etc. are never handled). Setting background: false on the AgentDefinition itself (session.ts's agents: {...}) does NOT fix this — confirmed empirically it has no effect on which way the model's own per-call run_in_background input resolves. Rewriting the tool input here, before it ever executes, is the only lever that actually forces synchronous execution regardless of what the model asked for. |
| createSubagentTypeGate | PreToolUse hook restricting the Agent tool to the subagent types this session actually registered (session.ts's agents: {...}, from AgentSpec.buildSubagents()) — confirmed empirically that the Agent SDK also exposes a built-in "general-purpose" subagent_type regardless of what agents lists, and that spawning one that way inherits the whole session's own tools (including Bash, whenever any opt-in subagent turned it on) rather than the narrow AgentDefinition.tools each declared subagent actually declares. Without this hook, that built-in type is a way to route around subagentBashGate.ts entirely: once inside it, agent_id is set exactly like an intended subagent, so the Bash-from-main-thread check alone can't tell them apart — this is exactly what happened in practice in a real consuming agent (the model delegated a plain file deletion to a spontaneous "general-purpose" agent to get at Bash, instead of using its own tools and leaving the stray file alone). |
| createTranscriptLogger | Logs every tool call (request + result) as one JSON line, redacting secrets (e.g. a domain-specific password — see BaseSessionConfig.secrets) plus CLAUDE_CODE_OAUTH_TOKEN, which this kit always scrubs regardless of domain since it owns the auth-token concept (see claudeAuth.ts). |
| detectLanguage | resolveLanguage() with the running process's arguments, locale and environment. |
| ensureClaudeAuth | The terminal-facing counterpart to resolveClaudeAuth() (core/claudeAuth.ts), which only looks up a token. This one also gets you one: if resolveClaudeAuth(config) comes back empty, it prints why, offers to run "claude setup-token" interactively, and sets the result on process.env.CLAUDE_CODE_OAUTH_TOKEN for this run. Exits the process if the user declines or cancels — there's no agent to run without a token. |
| getInteractionPort | The port checkpoints ask through right now, or null. |
| getLanguage | The language of the kit's texts right now. |
| getSharedReadline | The registered readline interface, or null. |
| getTheme | The theme in use right now. |
| isExitPromptError | True for the error @inquirer/prompts throws when the user cancels (Ctrl+C) a prompt. |
| knowledgePluginRoot | Absolute path of the plugin shipped with the kit — assets/knowledge-plugin next to dist/ (or src/ under tsx). |
| knowledgePromptSection | The "Knowledge base" section appended to the system prompt: the layout and the working rules. The exact page templates live in the knowledge-pages skill, to keep this short. |
| listRuns | The runs under runsDir that kept a conversation, newest first; runs without session.json are left out. |
| messagesFor | A language's texts; any it lacks come from English (nested groups key by key). |
| readConversation | The human's messages and the agent's replies in a run's conversation, in order. |
| resolveClaudeAuth | Pure lookup, no I/O and no console output of any kind — safe to call from a headless/non-interactive consumer (a server, an Electron main process) as much as from a terminal one. |
| resolveLanguage | The language for the kit's texts and the agent's replies: --language=<code>, then the agent's option, then the system's language, then English. A code that is asked for but isn't supported falls through to the next source, with a warning. Pure: detectLanguage() fills the sources from the running process. |
| runChatInk | The Ink counterpart of runChatTui(), with the same options, session log and history file: history in the scrollback, the reply streaming in place, a spinner with the current action (and a subagent's), approval panels, "/command" completion and a status bar. Checkpoints go through an Ink InteractionPort while the chat runs, so no second stdin reader ever competes with Ink's; the response file keeps working as always. |
| runChatTui | A ready-to-run interactive terminal chat loop, assembled from this kit's own chat primitives: createInputQueue() feeds typed lines into a multi-turn query() session, runQuery()'s normalized event stream is rendered to the console (streamed text, friendly action labels via createFriendlyToolLabel(), turn failures), and this loop's own readline.Interface is registered via setSharedReadline() so any human-in-the-loop checkpoint the session hits (the interactive-mode step gate, or the guided-mode approval/ manual-login tools — see terminalInteraction.ts) prompts on the same interface instead of a second one fighting it for stdin's raw mode. |
| runQuery | Thin wrapper around the SDK's own query(): same two-argument shape (a plain string for a one-shot run, or an AsyncIterable<SDKUserMessage> — see createInputQueue() — for a multi-turn chat), translating its raw message stream into AgentEvents as described above. Every other control surface of the underlying Query (interrupt, close, supportedCommands) is passed through unchanged. |
| runWizard | Asks steps in order and resolves with every answer by step name. A step's message, choices or default can depend on earlier answers, and when skips it, so a consumer's whole interactive menu becomes a list of step definitions. |
| setInteractionPort | Installs the port every checkpoint asks through (null: answer through the response file only). One per process. |
| setLanguage | Sets the language of the kit's texts directly (a host that resolved it itself, tests). |
| setSharedReadline | Registers the chat's readline interface, so checkpoints ask on it instead of opening a second one (null to unregister). |
| setTheme | The kit's default theme with overrides on top: only the roles given change. |
| summarizeToolResponse | Avoids bloating the transcript with large payloads (browser-tool screenshots/base64). |
| togglePlanMode | What the chats' /plan does: switches control into plan mode, or, when it's already there, back to the mode it was entered from ("guided" if it started in plan mode). Returns the new mode, or null when this session can't be in plan mode (an autonomous one). |
| truncate | Cuts text to max characters, with "…" at the end. |
| truncatePath | Cuts a path to max characters from the front, with "…": unlike truncate() (which cuts the tail off long freeform text), a path's most useful part, the file name, is at the end. |