mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
444 lines
147 KiB
JSON
444 lines
147 KiB
JSON
{
|
||
"name": "Kit",
|
||
"version": "1.0.0",
|
||
"pages": [
|
||
{
|
||
"url": "/advanced/json-output",
|
||
"title": "JSON Output",
|
||
"description": "Machine-readable JSON output for scripting and automation.",
|
||
"headings": [
|
||
"Response format",
|
||
"Fields",
|
||
"Top-level",
|
||
"Usage",
|
||
"Message parts",
|
||
"Parsing in scripts",
|
||
"bash + jq",
|
||
"Go SDK"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# JSON Output\n\nUse the `--json` flag to get structured output for scripting and automation:\n\n```bash\nkit \"Explain main.go\" --json --quiet --no-session\n```\n\n## Response format\n\n```json\n{\n \"response\": \"Final assistant response text\",\n \"model\": \"anthropic/claude-haiku-latest\",\n \"stop_reason\": \"end_turn\",\n \"session_id\": \"a1b2c3d4e5f6\",\n \"usage\": {\n \"input_tokens\": 1024,\n \"output_tokens\": 512,\n \"total_tokens\": 1536,\n \"cache_read_tokens\": 0,\n \"cache_creation_tokens\": 0\n },\n \"messages\": [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"data\": \"...\"},\n {\"type\": \"tool_call\", \"data\": {\"name\": \"...\", \"args\": \"...\"}},\n {\"type\": \"tool_result\", \"data\": {\"name\": \"...\", \"result\": \"...\"}}\n ]\n }\n ]\n}\n```\n\n## Fields\n\n### Top-level\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `response` | string | The final assistant response text |\n| `model` | string | The model that was used |\n| `stop_reason` | string | Why the model stopped (e.g., `end_turn`) |\n| `session_id` | string | Session identifier (omitted in `--no-session` mode) |\n| `usage` | object | Token usage statistics |\n| `messages` | array | Full conversation history |\n\n### Usage\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `input_tokens` | int | Tokens sent to the model |\n| `output_tokens` | int | Tokens generated by the model |\n| `total_tokens` | int | Sum of input and output tokens |\n| `cache_read_tokens` | int | Tokens read from prompt cache |\n| `cache_creation_tokens` | int | Tokens written to prompt cache |\n\n### Message parts\n\nEach message contains a `parts` array with typed entries:\n\n| Type | Description |\n|------|-------------|\n| `text` | Assistant text content |\n| `tool_call` | Tool invocation with name and args |\n| `tool_result` | Tool execution result |\n| `reasoning` | Extended thinking content |\n| `finish` | End-of-turn marker |\n\n## Parsing in scripts\n\n### bash + jq\n\n```bash\nresult=$(kit \"Count files\" --json --quiet --no-session)\nresponse=$(echo \"$result\" | jq -r '.response')\ntokens=$(echo \"$result\" | jq '.usage.total_tokens')\n```\n\n### Go SDK\n\nFor Go programs, use the SDK's `PromptResult` method instead of parsing JSON:\n\n```go\nresult, err := host.PromptResult(ctx, \"Count files\")\nfmt.Println(result.Response)\nfmt.Println(result.Usage.TotalTokens)\n```\n"
|
||
},
|
||
{
|
||
"url": "/advanced/subagents",
|
||
"title": "Subagents",
|
||
"description": "Multi-agent orchestration with Kit subagents.",
|
||
"headings": [
|
||
"Subprocess pattern",
|
||
"Built-in subagent tool",
|
||
"Extension subagents",
|
||
"Monitoring subagents from extensions",
|
||
"Go SDK subagents",
|
||
"Real-time subagent events"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Subagents\n\nKit supports multi-agent orchestration through both subprocess spawning and in-process subagents.\n\n## Subprocess pattern\n\nSpawn Kit as a subprocess for isolated agent execution:\n\n```bash\nkit \"Analyze codebase\" \\\n --json \\\n --no-session \\\n --no-extensions \\\n --quiet \\\n --model anthropic/claude-haiku-latest\n```\n\nKey flags for subprocess usage:\n\n| Flag | Purpose |\n|------|---------|\n| `--quiet` | Stdout only, no TUI |\n| `--no-session` | Ephemeral, no persistence |\n| `--no-extensions` | Prevent recursive extension loading |\n| `--json` | Machine-readable output |\n| `--system-prompt` | Custom system prompt (string or file path) |\n\nPositional arguments are the prompt. `@file` arguments attach file content as context.\n\n## Built-in subagent tool\n\nKit includes a built-in `subagent` tool that the LLM can use to delegate tasks to independent child agents:\n\n```\nsubagent(\n task: \"Analyze the test files and summarize coverage\",\n model: \"anthropic/claude-haiku-latest\", // optional\n system_prompt: \"You are a test analysis expert.\", // optional\n timeout_seconds: 300 // optional, max 1800\n)\n```\n\nSubagents run as separate in-process Kit instances with full tool access (except spawning further subagents, to prevent infinite recursion). They can run in parallel.\n\n## Extension subagents\n\nExtensions can spawn subagents programmatically:\n\n```go\nresult := ctx.SpawnSubagent(ext.SubagentConfig{\n Task: \"Review this code for security issues\",\n Model: \"anthropic/claude-sonnet-latest\",\n SystemPrompt: \"You are a security auditor.\",\n})\n```\n\n### Monitoring subagents from extensions\n\nWhen the LLM (not the extension itself) spawns a subagent using the `subagent` tool, extensions can monitor its activity in real-time using three lifecycle event handlers:\n\n```go\n// Track active subagents and display their output\nvar subagentWidgets map[string]*SubagentWidget\n\nfunc Init(api ext.API) {\n // Subagent started by the main agent\n api.OnSubagentStart(func(e ext.SubagentStartEvent, ctx ext.Context) {\n // e.ToolCallID — unique ID for this subagent invocation\n // e.Task — the task/prompt sent to the subagent\n widget := NewWidget(e.ToolCallID, e.Task)\n subagentWidgets[e.ToolCallID] = widget\n ctx.SetWidget(widget.Config())\n })\n\n // Real-time streaming from subagent\n api.OnSubagentChunk(func(e ext.SubagentChunkEvent, ctx ext.Context) {\n // e.ToolCallID — matches the start event\n // e.ChunkType — \"text\", \"tool_call\", \"tool_execution_start\", \"tool_result\"\n // e.Content — text content\n // e.ToolName — tool name (for tool chunks)\n // e.IsError — true if tool result failed\n widget := subagentWidgets[e.ToolCallID]\n if widget != nil {\n widget.AddOutput(e)\n ctx.SetWidget(widget.Config())\n }\n })\n\n // Subagent completed\n api.OnSubagentEnd(func(e ext.SubagentEndEvent, ctx ext.Context) {\n // e.Response — final response from subagent\n // e.ErrorMsg — error message if subagent failed\n widget := subagentWidgets[e.ToolCallID]\n if widget != nil {\n widget.MarkComplete(e.Response, e.ErrorMsg)\n ctx.SetWidget(widget.Config())\n delete(subagentWidgets, e.ToolCallID)\n }\n })\n}\n```\n\n**Event structs:**\n\n```go\ntype SubagentStartEvent struct {\n ToolCallID string // Unique ID for this subagent invocation\n Task string // The task/prompt sent to subagent\n}\n\ntype SubagentChunkEvent struct {\n ToolCallID string // Matches SubagentStartEvent.ToolCallID\n Task string // Task description\n ChunkType string // \"text\", \"tool_call\", \"tool_execution_start\", \"tool_result\"\n Content string // For text chunks\n ToolName string // For tool-related chunks\n IsError bool // For tool_result chunks\n}\n\ntype SubagentEndEvent struct {\n ToolCallID string // Matches start event\n Task string // Task description\n Response string // Final response from subagent\n ErrorMsg string // Error message if failed\n}\n```\n\nThis enables building monitoring widgets that display real-time activity from all subagents spawned by the main agent.\n\n## Go SDK subagents\n\nThe SDK provides in-process subagent spawning:\n\n```go\nresult, err := host.Subagent(ctx, kit.SubagentConfig{\n Task: \"Summarize the changes in this PR\",\n Model: \"anthropic/claude-haiku-latest\",\n SystemPrompt: \"You are a code reviewer.\",\n Timeout: 5 * time.Minute,\n})\n```\n\n### Real-time subagent events\n\nUse `SubscribeSubagent` to receive real-time events from LLM-initiated subagents (i.e., when the model uses the `subagent` tool). Register inside an `OnToolCall` handler using the tool call ID:\n\n```go\nhost.OnToolCall(func(e kit.ToolCallEvent) {\n if e.ToolName == \"subagent\" {\n host.SubscribeSubagent(e.ToolCallID, func(event kit.Event) {\n switch ev := event.(type) {\n case kit.MessageUpdateEvent:\n fmt.Print(ev.Chunk) // streaming text from child\n case kit.ToolCallEvent:\n fmt.Printf(\"Child calling: %s\\n\", ev.ToolName)\n case kit.ToolResultEvent:\n fmt.Printf(\"Child result: %s\\n\", ev.ToolName)\n }\n })\n }\n})\n```\n\nThe listener receives the same event types as `Subscribe()` (`ToolCallEvent`, `MessageUpdateEvent`, `ReasoningDeltaEvent`, etc.) but scoped to the child agent's activity. Listeners are cleaned up automatically when the subagent completes.\n\nIf no listeners are registered for a tool call, no event dispatching overhead is incurred.\n"
|
||
},
|
||
{
|
||
"url": "/advanced/testing",
|
||
"title": "Testing with tmux",
|
||
"description": "Test Kit's TUI non-interactively using tmux.",
|
||
"headings": [
|
||
"Basic pattern",
|
||
"Testing extensions",
|
||
"Tips"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Testing with tmux\n\nKit's interactive TUI can be tested non-interactively using tmux. This is useful for automated testing, CI pipelines, and extension development.\n\n## Basic pattern\n\n```bash\n# Start Kit in a detached tmux session\ntmux new-session -d -s kittest -x 120 -y 40 \\\n \"output/kit -e ext.go --no-session 2>kit_stderr.log\"\n\n# Wait for startup\nsleep 3\n\n# Capture the current screen\ntmux capture-pane -t kittest -p\n\n# Send input\ntmux send-keys -t kittest '/command' Enter\n\n# Wait for response\nsleep 2\n\n# Capture updated screen\ntmux capture-pane -t kittest -p\n\n# Cleanup\ntmux kill-session -t kittest\n```\n\n## Testing extensions\n\nWhen testing extensions, the pattern is:\n\n1. Build Kit with your changes\n2. Start Kit in tmux with the extension loaded\n3. Send slash commands or prompts\n4. Capture and verify the screen output\n5. Check stderr logs for errors\n\n```bash\n# Build first\ngo build -o output/kit ./cmd/kit\n\n# Start with extension\ntmux new-session -d -s kittest -x 120 -y 40 \\\n \"output/kit -e examples/extensions/widget-status.go --no-session 2>kit_stderr.log\"\n\nsleep 3\n\n# Verify widget appears in screen\ntmux capture-pane -t kittest -p | grep \"Status\"\n\n# Send a slash command\ntmux send-keys -t kittest '/stats' Enter\nsleep 1\ntmux capture-pane -t kittest -p\n\n# Cleanup\ntmux kill-session -t kittest\n```\n\n## Tips\n\n- Use `-x` and `-y` to set consistent terminal dimensions\n- Redirect stderr to a log file (`2>kit.log`) for debugging\n- Use `--no-session` to avoid creating session files during tests\n- Add sufficient `sleep` between commands for the TUI to render\n- Use `grep` on captured pane output to verify specific content\n"
|
||
},
|
||
{
|
||
"url": "/cli/commands",
|
||
"title": "Commands",
|
||
"description": "Complete reference for all Kit CLI subcommands.",
|
||
"headings": [
|
||
"Authentication",
|
||
"Model database",
|
||
"Extension management",
|
||
"Installing extensions from git",
|
||
"Skills",
|
||
"Interactive slash commands",
|
||
"Prompt history",
|
||
"Cancelling operations",
|
||
"External editor",
|
||
"Mid-turn steering",
|
||
"Image attachments",
|
||
"Prompt templates",
|
||
"Creating templates",
|
||
"Using templates",
|
||
"Argument placeholders",
|
||
"CLI flags",
|
||
"ACP server"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Commands\n\n## Authentication\n\nFor OAuth-enabled providers like Anthropic.\n\n```bash\nkit auth login [provider] # Start OAuth flow (e.g., anthropic)\nkit auth login [provider] --set-default # Set provider's default model as system default\nkit auth logout [provider] # Remove credentials for provider\nkit auth status # Check authentication status\n```\n\n## Model database\n\nManage the local model database that maps provider names to API configurations.\n\n```bash\nkit models [provider] # List available models (optionally filter by provider)\nkit models --all # Show all providers (not just LLM-compatible)\nkit update-models [source] # Update model database\n```\n\nThe `update-models` command accepts an optional source argument:\n- *(none)* — update from [models.dev](https://models.dev)\n- A URL — fetch from a custom endpoint\n- A file path — load from a local file\n- `embedded` — reset to the bundled database\n\n## Extension management\n\n```bash\nkit extensions list # List discovered extensions\nkit extensions validate # Validate extension files\nkit extensions init # Generate example extension template\n```\n\n### Installing extensions from git\n\n```bash\nkit install <git-url> # Install extensions from git repositories\nkit install -l <git-url> # Install to project-local .kit/git/ directory\nkit install -u <git-url> # Update an already-installed package\nkit install --uninstall <pkg> # Remove an installed package\nkit install --all # Install all extensions without prompting\n```\n\n## Skills\n\n```bash\nkit skill # Install the Kit extensions skill via skills.sh\n```\n\n## Interactive slash commands\n\nThese commands are available inside the Kit TUI during an interactive session:\n\n| Command | Description |\n|---------|-------------|\n| `/help` | Show available commands |\n| `/tools` | List available MCP tools |\n| `/servers` | Show connected MCP servers |\n| `/model [name]` | Switch model or open model selector |\n| `/theme [name]` | Switch color theme or list available themes |\n| `/thinking [level]` | Set thinking level (off, none, minimal, low, medium, high) |\n| `/compact [focus]` | Summarize older messages to free context |\n| `/clear` | Clear conversation |\n| `/clear-queue` | Clear queued messages |\n| `/usage` | Show token usage |\n| `/reset-usage` | Reset usage statistics |\n| `/tree` | Navigate session tree |\n| `/fork` | Fork to new session from an earlier message |\n| `/new` | Start a new session (creates new session file) |\n| `/name [name]` | Set or show session display name |\n| `/resume` | Open session picker to switch sessions (alias: `/r`) |\n| `/session` | Show session info |\n| `/export [path]` | Export session as JSONL (default: auto-generated path) |\n| `/import <path>` | Import a session from a JSONL file |\n| `/share` | Upload session to GitHub Gist and get a shareable viewer URL |\n| `/quit` | Exit Kit |\n\n### Prompt history\n\nUse **↑** and **↓** arrow keys to navigate through previously submitted prompts. Kit keeps the last 100 entries. Consecutive duplicates are skipped.\n\n### Cancelling operations\n\nPress **ESC twice** to cancel the current operation:\n- During a tool call: rolls back the entire turn to maintain API message pairing\n- During streaming: stops the response generation\n\nThis ensures that `tool_use` and `tool_result` messages are always sent to the API as matched pairs, avoiding errors from orphaned tool calls.\n\n### External editor\n\nPress **Ctrl+X e** to open your `$VISUAL` or `$EDITOR` in a temporary file pre-populated with the current input text. On save and quit, the edited content replaces the input textarea. On error exit (e.g., `:cq` in Vim), the original input is preserved.\n\n### Mid-turn steering\n\nPress **Ctrl+X s** during streaming to inject a system-level instruction mid-turn. This allows you to steer the conversation direction without waiting for the model to finish:\n\n- Works during streaming output\n- Sends a steering instruction as a system message\n- Model continues from the interruption point with the new guidance\n\nExample: While the model is writing code, press Ctrl+X s and type \"Use async/await instead\" to change the implementation approach.\n\n### Image attachments\n\nAttach images to your next prompt straight from the clipboard:\n\n- Copy an image (e.g. a screenshot) to the system clipboard, then press **Ctrl+V** in the input to attach it.\n- Press **Ctrl+U** to clear all pending image attachments.\n- Attachments are sent alongside your text when you submit, and cleared afterward.\n\nWhen a terminal supports color, Kit renders a small low-resolution **thumbnail preview** of each pending image directly in the input, below the `[N image(s) attached]` indicator, so you can confirm the right image was attached before sending.\n\nThe preview is drawn with Unicode half-block characters and ordinary terminal colors — not a graphics protocol — so it renders correctly inside terminal multiplexers like **tmux** and **zellij**. Thumbnails are capped to a small cell box for a glanceable, low-res look.\n\n- Best fidelity needs a **truecolor** terminal (`COLORTERM=truecolor`); Kit degrades to 256-color where truecolor is unavailable.\n- On terminals with neither, the preview is skipped and the `[N image(s) attached]` text indicator is shown alone.\n\nYou can also attach image files by referencing them with `@path/to/image.png` — binary files are auto-detected by MIME type. See [Quick Start](/quick-start) for the `@` attachment syntax.\n\n## Prompt templates\n\n### Creating templates\n\nTemplates use YAML frontmatter for metadata and support argument placeholders:\n\n```markdown\n---\ndescription: Review code for issues\n---\nReview the following code for bugs and security issues.\nFocus on $1 specifically.\n```\n\nSave to `~/.kit/prompts/review.md` or `.kit/prompts/review.md`.\n\n### Using templates\n\nTemplates appear as slash commands:\n\n```\n/review error handling\n```\n\n### Argument placeholders\n\n| Placeholder | Description |\n|-------------|-------------|\n| `$1`, `$2`, etc. | Individual arguments by position |\n| `$@`, `$ARGUMENTS` | All arguments joined with spaces (zero or more) |\n| `$+` | All arguments joined with spaces (one or more required) |\n| `${@:N}` | Arguments from position N onwards |\n| `${@:N:L}` | L arguments starting at position N |\n\nPlaceholders inside fenced code blocks (`` ``` ``) and inline code spans are ignored, so documentation examples won't be substituted.\n\n### CLI flags\n\n```bash\n# Load a specific template by name\nkit --prompt-template review\n\n# Disable template loading\nkit --no-prompt-templates\n```\n\n## ACP server\n\nRun Kit as an [ACP (Agent Client Protocol)](https://agentclientprotocol.com) agent server. ACP-compatible clients communicate with Kit over JSON-RPC 2.0 on stdin/stdout.\n\n```bash\nkit acp # Start as ACP agent\nkit acp --debug # With debug logging to stderr\n```\n"
|
||
},
|
||
{
|
||
"url": "/cli/flags",
|
||
"title": "Global Flags",
|
||
"description": "Complete reference for all Kit CLI flags.",
|
||
"headings": [
|
||
"Model and provider",
|
||
"Session management",
|
||
"Behavior",
|
||
"Extensions",
|
||
"Generation parameters",
|
||
"System"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Global Flags\n\nAll flags can be passed to the root `kit` command.\n\n## Model and provider\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--model` | `-m` | `anthropic/claude-sonnet-latest` | Model to use (provider/model format) |\n| `--provider-api-key` | — | — | API key for the provider |\n| `--provider-url` | — | — | Base URL for provider API |\n| `--tls-skip-verify` | — | `false` | Skip TLS certificate verification |\n\n## Session management\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--session` | `-s` | — | Open specific JSONL session file |\n| `--continue` | `-c` | `false` | Resume most recent session for current directory |\n| `--resume` | `-r` | `false` | Interactive session picker |\n| `--no-session` | — | `false` | Ephemeral mode, no persistence |\n\n## Behavior\n\nThese flags control Kit's behavior. When a prompt is passed as a positional argument, Kit runs in non-interactive mode.\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--quiet` | — | `false` | Suppress all output (non-interactive only) |\n| `--json` | — | `false` | Output response as JSON (non-interactive only) |\n| `--no-exit` | — | `false` | Enter interactive mode after prompt completes |\n| `--max-steps` | — | `0` | Maximum agent steps (0 for unlimited) |\n| `--stream` | — | `true` | Enable streaming output |\n| `--compact` | — | `false` | Enable compact output mode |\n| `--auto-compact` | — | `false` | Auto-compact conversation near context limit |\n\n## Extensions\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--extension` | `-e` | — | Load additional extension file(s) (repeatable) |\n| `--no-extensions` | — | `false` | Disable all extensions |\n| `--prompt-template` | — | — | Load a specific prompt template by name |\n| `--no-prompt-templates` | — | `false` | Disable prompt template loading |\n\n## Generation parameters\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--max-tokens` | — | `8192` | Base cap for output tokens. Auto-raised per-model up to 32768 when the model's catalog ceiling is higher and no explicit value is set. |\n| `--temperature` | — | `0.7` | Randomness 0.0–1.0 |\n| `--top-p` | — | `0.95` | Nucleus sampling 0.0–1.0 |\n| `--top-k` | — | `40` | Limit top K tokens |\n| `--stop-sequences` | — | — | Custom stop sequences (comma-separated) |\n| `--frequency-penalty` | — | `0.0` | Penalize frequent tokens (0.0–2.0) |\n| `--presence-penalty` | — | `0.0` | Penalize present tokens (0.0–2.0) |\n| `--thinking-level` | — | `off` | Extended thinking level: off, none, minimal, low, medium, high |\n\n## System\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--config` | — | `~/.kit.yml` | Config file path |\n| `--system-prompt` | — | — | System prompt text or file path |\n| `--debug` | — | `false` | Enable debug logging |\n"
|
||
},
|
||
{
|
||
"url": "/configuration",
|
||
"title": "Configuration",
|
||
"description": "Configure Kit using config files, environment variables, and CLI flags.",
|
||
"headings": [
|
||
"Basic configuration",
|
||
"All configuration keys",
|
||
"Environment variables",
|
||
"MCP server configuration",
|
||
"MCP server fields",
|
||
"MCP tasks (long-running tools)",
|
||
"Custom models",
|
||
"Custom model fields",
|
||
"Per-model settings",
|
||
"Per-model fields",
|
||
"Precedence summary",
|
||
"Theme configuration",
|
||
"Preferences persistence"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Configuration\n\nKit looks for configuration in the following locations, in order of priority:\n\n1. CLI flags\n2. Environment variables (with `KIT_` prefix)\n3. `./.kit.yml` / `./.kit.yaml` / `./.kit.json` (project-local)\n4. `~/.kit.yml` / `~/.kit.yaml` / `~/.kit.json` (global)\n\n## Basic configuration\n\nCreate `~/.kit.yml`:\n\n```yaml\nmodel: anthropic/claude-sonnet-latest\nmax-tokens: 8192\ntemperature: 0.7\nstream: true\n```\n\n## All configuration keys\n\n| Key | Type | Default | Description |\n|-----|------|---------|-------------|\n| `model` | string | `anthropic/claude-sonnet-latest` | Model to use (provider/model format) |\n| `max-tokens` | int | `8192` | Base cap for output tokens. Auto-raised per-model up to 32768 when the model's catalog ceiling is higher and no explicit value is set. Use [`modelSettings[provider/model].maxTokens`](#per-model-settings) to override per-model. |\n| `temperature` | float | `0.7` | Randomness 0.0–1.0 |\n| `top-p` | float | `0.95` | Nucleus sampling 0.0–1.0 |\n| `top-k` | int | `40` | Limit top K tokens |\n| `stream` | bool | `true` | Enable streaming output |\n| `debug` | bool | `false` | Enable debug logging |\n| `compact` | bool | `false` | Enable compact output mode |\n| `system-prompt` | string | — | System prompt text or file path |\n| `max-steps` | int | `0` | Maximum agent steps (0 = unlimited) |\n| `thinking-level` | string | `off` | Extended thinking: off, none, minimal, low, medium, high |\n| `provider-api-key` | string | — | API key for the provider |\n| `provider-url` | string | — | Base URL for provider API |\n| `tls-skip-verify` | bool | `false` | Skip TLS certificate verification |\n| `frequency-penalty` | float | `0.0` | Penalize frequent tokens (0.0–2.0) |\n| `presence-penalty` | float | `0.0` | Penalize present tokens (0.0–2.0) |\n| `stop-sequences` | list | — | Custom stop sequences |\n| `theme` | object or string | — | UI theme ([inline overrides or file path](/themes)) |\n| `prompt-templates` | bool | `true` | Enable prompt template loading |\n| `prompt-template` | string | — | Specific template to load by name |\n\n## Environment variables\n\nAny configuration key can be set via environment variable with the `KIT_` prefix. Hyphens become underscores:\n\n```bash\nexport KIT_MODEL=\"openai/gpt-4o\"\nexport KIT_MAX_TOKENS=\"8192\"\nexport KIT_TEMPERATURE=\"0.5\"\n```\n\nProvider API keys use their own environment variables:\n\n```bash\nexport ANTHROPIC_API_KEY=\"sk-...\"\nexport OPENAI_API_KEY=\"sk-...\"\nexport GOOGLE_API_KEY=\"...\"\n```\n\n## MCP server configuration\n\nAdd external MCP servers to your `.kit.yml`:\n\n```yaml\nmcpServers:\n filesystem:\n type: local\n command: [\"npx\", \"-y\", \"@modelcontextprotocol/server-filesystem\", \"/path/to/allowed\"]\n environment:\n LOG_LEVEL: \"info\"\n allowedTools: [\"read_file\", \"write_file\"]\n excludedTools: [\"delete_file\"]\n\n search:\n type: remote\n url: \"https://mcp.example.com/search\"\n\n pubmed:\n type: remote\n url: \"https://pubmed.mcp.example.com\"\n noOAuth: true # skip OAuth for public servers\n headers:\n - \"ApiKey: ${env://API_KEY}\" # required env var\n - \"X-Tenant: ${env://TENANT_ID:-default}\" # with fallback default\n\n builds:\n type: remote\n url: \"https://builds.mcp.example.com\"\n tasksMode: always # always run tools/call as async tasks (Phase 1 MVP)\n```\n\n### MCP server fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `type` | string | `local` (stdio) or `remote` (streamable HTTP) |\n| `command` | list | Command and args for local servers |\n| `environment` | map | Environment variables for the server process |\n| `url` | string | URL for remote servers |\n| `allowedTools` | list | Whitelist of tool names to expose |\n| `excludedTools` | list | Blacklist of tool names to hide |\n| `noOAuth` | bool | Skip OAuth for this server (for public servers that don't require auth) |\n| `headers` | list of strings | HTTP headers to attach to every request, each as a `\"Key: Value\"` string. Values support env-substitution: `${env://VAR}` or `${env://VAR:-default}`. |\n| `tasksMode` | string | When to augment `tools/call` with MCP task metadata: `auto` (default — only when the server advertises task support), `never`, or `always`. See [MCP tasks](#mcp-tasks-long-running-tools). |\n\nA legacy format with `transport`, `args`, and `env` fields is also supported; `headers` works in both the current and legacy formats.\n\n### MCP tasks (long-running tools)\n\nKit advertises [MCP task support](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)\nduring `initialize` so servers can respond to `tools/call` with a\n`CreateTaskResult` (a task ID + `working` status) instead of blocking until\nthe operation finishes. Kit then polls `tasks/get` / `tasks/result` until the\ntask reaches a terminal state, and best-effort `tasks/cancel`s on context\ncancellation.\n\nThis avoids HTTP/SSE proxy timeouts on long builds, deploys, and batch jobs,\nand lets the user/agent abort cleanly with Ctrl-C.\n\n**Per-server `tasksMode`:**\n\n| Value | Behaviour |\n|-------|-----------|\n| `auto` (default) | Augment `tools/call` with task metadata only when the server advertised `tasks/toolCalls` capability. Servers that don't advertise it run synchronously, exactly as before. |\n| `never` | Always issue `tools/call` synchronously, regardless of server capability. |\n| `always` | Always opt into task augmentation, even when the server didn't advertise the capability. The server may still respond synchronously — this just expresses client intent unconditionally. |\n\nDefaults are safe: any existing MCP server keeps its previous behaviour\nbit-for-bit. SDK consumers can also override the mode programmatically and\nplug in a progress callback — see [SDK options](/sdk/options#mcp-tasks).\n\n## Custom models\n\nDefine custom models in your `.kit.yml` for use with the `custom` provider. This is useful for self-hosted models or API endpoints not in the built-in database:\n\n```yaml\ncustomModels:\n my-model:\n name: \"My Custom Model\"\n baseUrl: \"http://localhost:8080/v1\"\n apiKey: \"my-secret-key\"\n reasoning: true\n temperature: true\n cost:\n input: 0.002\n output: 0.004\n limit:\n context: 128000\n output: 32000\n```\n\n### Custom model fields\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `name` | string | Yes | Display name for the model |\n| `baseUrl` | string | No | Per-model base URL override; when set, `--provider-url` is not required |\n| `apiKey` | string | No | Per-model API key override |\n| `reasoning` | bool | No | Whether the model supports reasoning/thinking |\n| `temperature` | bool | No | Whether the model supports temperature adjustment |\n| `cost.input` | float | No | Cost per 1K input tokens |\n| `cost.output` | float | No | Cost per 1K output tokens |\n| `limit.context` | int | Yes | Maximum context window in tokens |\n| `limit.output` | int | No | Maximum output tokens |\n\nUse with a per-model `baseUrl` (no `--provider-url` needed):\n\n```bash\nkit --model custom/my-model \"Hello\"\n```\n\nOr override the base URL at runtime:\n\n```bash\nkit --provider-url \"http://localhost:8080/v1\" --model custom/my-model \"Hello\"\n```\n\nWhen `--provider-url` is specified without `--model`, Kit defaults to `custom/custom` which has zero cost tracking and a 262K context window.\n\n## Per-model settings\n\nOverride generation parameters and system prompt on a per-model basis using `modelSettings`:\n\n```yaml\nmodelSettings:\n anthropic/claude-sonnet-4-5-20250929:\n temperature: 0.3\n maxTokens: 8192\n systemPrompt: \"You are a concise coding assistant.\"\n openai/gpt-4o:\n temperature: 0.7\n frequencyPenalty: 0.5\n```\n\n### Per-model fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `temperature` | float | Temperature override for this model |\n| `maxTokens` | int | Max output tokens override |\n| `topP` | float | Top-p override |\n| `topK` | int | Top-k override |\n| `frequencyPenalty` | float | Frequency penalty override |\n| `presencePenalty` | float | Presence penalty override |\n| `stopSequences` | list | Stop sequences override |\n| `thinkingLevel` | string | Thinking level override |\n| `systemPrompt` | string | Per-model system prompt (used when no explicit prompt is set) |\n\nSettings from `modelSettings` and `customModels.params` act as model-level defaults — explicit CLI flags, `KIT_*` environment variables, global config values, and SDK `Options.*` fields all take precedence over them.\n\nWhen switching models via `/model` or `SetModel()`, if the new model has a per-model system prompt and no custom global prompt was set, the per-model prompt automatically replaces the previous one.\n\n### Precedence summary\n\nFor the generation and provider parameters documented above, the resolved value at runtime comes from the first source that sets it:\n\n1. CLI flag (e.g. `--max-tokens`, `--temperature`, `--provider-api-key`)\n2. SDK `Options.X` when embedding Kit as a library (`kit.Options.MaxTokens`, `Temperature`, `ProviderAPIKey`, etc.)\n3. `KIT_*` environment variable (`KIT_MAX_TOKENS`, `KIT_TEMPERATURE`, ...)\n4. `.kit.yml` / `.kit.yaml` / `.kit.json` (project-local, then global)\n5. Per-model defaults (`modelSettings[provider/model]` / `customModels[...].params`)\n6. Provider-level defaults (e.g. Anthropic's own temperature default)\n7. SDK last-resort floor — currently an 8192 output-token ceiling matching the CLI `--max-tokens` default, auto-raised per-model up to 32768 when the model's catalog ceiling is higher\n\nSee the [SDK options reference](/sdk/options) for the full list of `kit.Options` fields that map to these keys.\n\n## Theme configuration\n\n```yaml\n# Inline partial overrides (unspecified fields inherit from default)\ntheme:\n primary:\n light: \"#8839ef\"\n dark: \"#cba6f7\"\n error:\n dark: \"#FF0000\"\n```\n\n```yaml\n# Reference external theme file\ntheme: \"./themes/my-custom-theme.yml\"\n```\n\nSee [Themes](/themes) for the full theme file format, built-in themes, and the extension theme API.\n\n## Preferences persistence\n\nKit automatically saves your UI preferences across sessions to `~/.config/kit/preferences.yml`:\n\n- **Theme** — Set via `/theme <name>` or `ctx.SetTheme()`\n- **Model** — Set via `/model <name>` or the model selector\n- **Thinking level** — Set via `/thinking <level>` or Shift+Tab cycling\n\nThese preferences are restored on next launch. Precedence (highest to lowest):\n1. CLI flags (`--model`, `--thinking-level`)\n2. Config file (`model:`, `thinking-level:`)\n3. Saved preferences (`~/.config/kit/preferences.yml`)\n4. Default values\n"
|
||
},
|
||
{
|
||
"url": "/development",
|
||
"title": "Development",
|
||
"description": "Build, test, and contribute to Kit.",
|
||
"headings": [
|
||
"Build and test",
|
||
"Project structure",
|
||
"Architecture overview",
|
||
"Multi-provider LLM support",
|
||
"MCP client-server model",
|
||
"Extension system",
|
||
"TUI architecture",
|
||
"Decoupling pattern",
|
||
"Contributing",
|
||
"Community"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Development\n\n## Build and test\n\n```bash\n# Build\ngo build -o output/kit ./cmd/kit\n\n# Run all tests\ngo test -race ./...\n\n# Run a specific test\ngo test -race ./cmd -run TestScriptExecution\n\n# Lint\ngo vet ./...\n\n# Format\ngo fmt ./...\n```\n\n## Project structure\n\n```\ncmd/kit/ - CLI entry point (main.go)\ncmd/ - CLI command implementations (root, auth, models, etc.)\npkg/kit/ - Go SDK for embedding Kit\ninternal/app/ - Application orchestrator (agent loop, message store, queue)\ninternal/agent/ - Agent execution and tool dispatch\ninternal/auth/ - OAuth authentication and credential storage\ninternal/acpserver/ - ACP (Agent Client Protocol) server\ninternal/clipboard/ - Cross-platform clipboard operations\ninternal/compaction/ - Conversation compaction and summarization\ninternal/config/ - Configuration management\ninternal/core/ - Built-in tools (bash with sudo password prompt, read, write, edit, grep, find, ls)\ninternal/extensions/ - Yaegi extension system\ninternal/kitsetup/ - Initial setup wizard\ninternal/message/ - Message content types and structured content blocks\ninternal/models/ - Provider and model management\ninternal/session/ - Session persistence (tree-based JSONL)\ninternal/skills/ - Skill loading and system prompt composition\ninternal/tools/ - MCP tool integration\ninternal/ui/ - Bubble Tea TUI components\nexamples/extensions/ - Example extension files\nnpm/ - NPM package wrapper for distribution\n```\n\n## Architecture overview\n\nKit is built around a few key architectural patterns:\n\n### Multi-provider LLM support\n\nThe `llm.Provider` interface abstracts different LLM providers. Each provider implements message formatting, tool calling, and streaming for its specific API.\n\n### MCP client-server model\n\nExternal tools are integrated via the Model Context Protocol (MCP). Kit acts as an MCP client, connecting to MCP servers configured in `.kit.yml`.\n\n### Extension system\n\nExtensions are Go source files interpreted at runtime by Yaegi. The `internal/extensions/` package manages loading, symbol export, and lifecycle dispatch. See the [Extension System](/extensions/overview) docs for details.\n\n### TUI architecture\n\nThe interactive terminal UI is built with [Bubble Tea v2](https://github.com/charmbracelet/bubbletea), using a parent-child model where `AppModel` manages child components (`InputComponent`, `StreamComponent`, etc.).\n\n### Decoupling pattern\n\n`cmd/root.go` contains converter functions (e.g., `widgetProviderForUI()`) that bridge `internal/extensions/` types to `internal/ui/` types. The UI never imports the extensions package directly.\n\n## Contributing\n\nContributions are welcome! Please see the [contribution guide](https://github.com/mark3labs/kit/blob/master/contribute/contribute.md) for guidelines.\n\n## Community\n\n- [Discord](https://discord.gg/RqSS2NQVsY)\n- [GitHub Issues](https://github.com/mark3labs/kit/issues)\n"
|
||
},
|
||
{
|
||
"url": "/extensions/capabilities",
|
||
"title": "Capabilities",
|
||
"description": "All extension capabilities — lifecycle events, tools, commands, widgets, and more.",
|
||
"headings": [
|
||
"Lifecycle events",
|
||
"Example",
|
||
"Tools",
|
||
"Commands",
|
||
"Widgets",
|
||
"Headers and footers",
|
||
"Status bar",
|
||
"Shortcuts",
|
||
"Overlays",
|
||
"Tool renderers",
|
||
"Message renderers",
|
||
"Editor interceptors",
|
||
"Interactive prompts",
|
||
"Options",
|
||
"Subagents",
|
||
"Monitoring subagents spawned by the main agent",
|
||
"LLM completion",
|
||
"Themes",
|
||
"Custom events",
|
||
"Bridged SDK APIs",
|
||
"Tree Navigation",
|
||
"Skill Loading",
|
||
"Template Parsing",
|
||
"Model Resolution"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Extension Capabilities\n\n## Lifecycle events\n\nExtensions can hook into 26 lifecycle events:\n\n| Event | Description |\n|-------|-------------|\n| `OnSessionStart` | Session initialized |\n| `OnSessionShutdown` | Session ending |\n| `OnBeforeAgentStart` | Before the agent loop begins |\n| `OnAgentStart` | Agent loop started |\n| `OnAgentEnd` | Agent loop completed |\n| `OnToolCall` | Tool call requested by the model |\n| `OnToolCallInputStart` | LLM began generating tool call arguments (tool name known, args streaming) |\n| `OnToolCallInputDelta` | Streamed JSON fragment of tool call arguments |\n| `OnToolCallInputEnd` | Tool argument streaming complete, before execution begins |\n| `OnToolExecutionStart` | Tool execution beginning |\n| `OnToolOutput` | Streaming tool output chunk (for long-running tools) |\n| `OnToolExecutionEnd` | Tool execution completed |\n| `OnToolResult` | Tool result returned |\n| `OnInput` | User input received |\n| `OnMessageStart` | Assistant message started |\n| `OnMessageUpdate` | Streaming text chunk received |\n| `OnMessageEnd` | Assistant message completed |\n| `OnModelChange` | Model switched |\n| `OnContextPrepare` | Context being assembled for the model |\n| `OnBeforeFork` | Before forking a conversation branch |\n| `OnBeforeSessionSwitch` | Before switching sessions |\n| `OnBeforeCompact` | Before conversation compaction |\n| `OnCustomEvent` | Custom inter-extension event received |\n| `OnSubagentStart` | Subagent spawned by the main agent |\n| `OnSubagentChunk` | Real-time output from subagent (text, tool calls, results) |\n| `OnSubagentEnd` | Subagent completed with final response/error |\n\n### Example\n\n```go\napi.OnToolCall(func(event ext.ToolCallEvent, ctx ext.Context) {\n ctx.PrintInfo(\"Calling tool: \" + event.Name)\n})\n\napi.OnAgentEnd(func(_ ext.AgentEndEvent, ctx ext.Context) {\n ctx.PrintInfo(\"Agent finished\")\n})\n```\n\n## Tools\n\nRegister custom tools that the LLM can invoke:\n\n```go\napi.RegisterTool(ext.ToolDef{\n Name: \"weather\",\n Description: \"Get current weather for a location\",\n Parameters: map[string]ext.ParameterDef{\n \"city\": {Type: \"string\", Description: \"City name\", Required: true},\n },\n Handler: func(ctx ext.Context, params map[string]any) (string, error) {\n city := params[\"city\"].(string)\n return \"Sunny, 72°F in \" + city, nil\n },\n})\n```\n\n## Commands\n\nRegister slash commands that users can invoke directly:\n\n```go\napi.RegisterCommand(ext.CommandDef{\n Name: \"stats\",\n Description: \"Show context statistics\",\n Handler: func(ctx ext.Context, args string) {\n stats := ctx.GetContextStats()\n ctx.PrintInfo(fmt.Sprintf(\"Tokens: %d\", stats.TotalTokens))\n },\n})\n```\n\n## Widgets\n\nAdd persistent status displays above or below the input area:\n\n```go\nctx.SetWidget(ext.WidgetConfig{\n ID: \"token-count\",\n Position: \"bottom\",\n Content: ext.WidgetContent{Text: \"Tokens: 1,234\"},\n})\n\n// Update later\nctx.SetWidget(ext.WidgetConfig{\n ID: \"token-count\",\n Position: \"bottom\",\n Content: ext.WidgetContent{Text: \"Tokens: 2,456\"},\n})\n\n// Remove\nctx.RemoveWidget(\"token-count\")\n```\n\n## Headers and footers\n\nPersistent content above and below the conversation:\n\n```go\nctx.SetHeader(ext.HeaderFooterConfig{\n Content: ext.WidgetContent{Text: \"Project: my-app | Branch: main\"},\n})\n\nctx.SetFooter(ext.HeaderFooterConfig{\n Content: ext.WidgetContent{Text: \"Plan Mode (read-only)\"},\n})\n```\n\n## Status bar\n\nCustom status bar entries:\n\n```go\nctx.SetStatus(\"mode\", \"Planning\")\nctx.RemoveStatus(\"mode\")\n```\n\n## Shortcuts\n\nGlobal keyboard shortcuts:\n\n```go\napi.RegisterShortcut(ext.ShortcutDef{\n Key: \"ctrl+t\",\n Description: \"Toggle plan mode\",\n}, func(ctx ext.Context) {\n // handle shortcut\n})\n```\n\n## Overlays\n\nModal dialogs with markdown content:\n\n```go\nctx.ShowOverlay(ext.OverlayConfig{\n Title: \"Help\",\n Content: \"# Keyboard Shortcuts\\n\\n- **ctrl+t** — Toggle plan mode\\n- **ctrl+s** — Save session\",\n})\n```\n\n## Tool renderers\n\nCustomize how specific tool calls are displayed in the TUI:\n\n```go\napi.RegisterToolRenderer(ext.ToolRenderConfig{\n ToolName: \"bash\",\n Render: func(name, args, result string, isError bool) string {\n return \"$ \" + args + \"\\n\" + result\n },\n})\n```\n\n## Message renderers\n\nCustom rendering for assistant messages:\n\n```go\napi.RegisterMessageRenderer(ext.MessageRendererConfig{\n Name: \"custom\",\n Render: func(content string) string {\n return \">> \" + content\n },\n})\n```\n\n## Editor interceptors\n\nHandle key events and wrap the editor's rendering:\n\n```go\nctx.SetEditor(ext.EditorConfig{\n HandleKey: func(key, text string) ext.EditorKeyAction {\n if key == \"escape\" {\n return ext.EditorKeyAction{Handled: true}\n }\n return ext.EditorKeyAction{Handled: false}\n },\n})\n```\n\n## Interactive prompts\n\nSelect, confirm, input, and multi-select dialogs:\n\n```go\n// Single select\nresponse := ctx.PromptSelect(ext.PromptSelectConfig{\n Title: \"Choose a model\",\n Options: []string{\"claude-sonnet\", \"gpt-4o\", \"llama3\"},\n})\n\n// Confirm\nconfirmed := ctx.PromptConfirm(ext.PromptConfirmConfig{\n Title: \"Delete this file?\",\n})\n\n// Text input\nname := ctx.PromptInput(ext.PromptInputConfig{\n Title: \"Enter project name\",\n Placeholder: \"my-project\",\n})\n```\n\n## Options\n\nRegister configurable extension options:\n\n```go\napi.RegisterOption(ext.OptionDef{\n Name: \"auto-commit\",\n Description: \"Automatically commit on shutdown\",\n DefaultValue: \"false\",\n})\n```\n\n## Subagents\n\nSpawn in-process child Kit instances:\n\n```go\nresult := ctx.SpawnSubagent(ext.SubagentConfig{\n Task: \"Analyze the test files and summarize coverage\",\n Model: \"anthropic/claude-haiku-latest\",\n SystemPrompt: \"You are a test analysis expert.\",\n})\n```\n\n### Monitoring subagents spawned by the main agent\n\nWhen the LLM uses the built-in `subagent` tool, extensions can monitor the subagent's activity in real-time using three lifecycle events:\n\n```go\n// Subagent started\napi.OnSubagentStart(func(e ext.SubagentStartEvent, ctx ext.Context) {\n // e.ToolCallID — unique ID for this subagent invocation\n // e.Task — the task/prompt sent to the subagent\n ctx.PrintInfo(fmt.Sprintf(\"Subagent started: %s\", e.Task))\n})\n\n// Real-time streaming output from subagent\napi.OnSubagentChunk(func(e ext.SubagentChunkEvent, ctx ext.Context) {\n // e.ToolCallID — matches the start event\n // e.Task — task description\n // e.ChunkType — \"text\", \"tool_call\", \"tool_execution_start\", \"tool_result\"\n // e.Content — text content (for text chunks)\n // e.ToolName — tool name (for tool-related chunks)\n // e.IsError — true if tool result is an error\n switch e.ChunkType {\n case \"text\":\n // Streaming text output\n case \"tool_call\":\n // Subagent is calling a tool\n case \"tool_execution_start\":\n // Tool execution started\n case \"tool_result\":\n // Tool execution completed (check e.IsError)\n }\n})\n\n// Subagent completed\napi.OnSubagentEnd(func(e ext.SubagentEndEvent, ctx ext.Context) {\n // e.ToolCallID — matches start event\n // e.Task — task description\n // e.Response — final response from subagent\n // e.ErrorMsg — error message if subagent failed\n if e.ErrorMsg != \"\" {\n ctx.PrintError(fmt.Sprintf(\"Subagent failed: %s\", e.ErrorMsg))\n } else {\n ctx.PrintInfo(fmt.Sprintf(\"Subagent completed: %s\", e.Response))\n }\n})\n```\n\nThis enables building widgets that display real-time subagent activity.\n\n## LLM completion\n\nMake direct model calls without going through the agent loop:\n\n```go\nresponse := ctx.Complete(ext.CompleteRequest{\n Prompt: \"Summarize this in one sentence: \" + content,\n})\n```\n\n## Themes\n\nRegister and switch color themes at runtime:\n\n```go\n// Register a custom theme\nctx.RegisterTheme(\"neon\", ext.ThemeColorConfig{\n Primary: ext.ThemeColor{Light: \"#CC00FF\", Dark: \"#FF00FF\"},\n Secondary: ext.ThemeColor{Light: \"#0088CC\", Dark: \"#00FFFF\"},\n Success: ext.ThemeColor{Light: \"#00CC44\", Dark: \"#00FF66\"},\n Warning: ext.ThemeColor{Light: \"#CCAA00\", Dark: \"#FFFF00\"},\n Error: ext.ThemeColor{Light: \"#CC0033\", Dark: \"#FF0055\"},\n Info: ext.ThemeColor{Light: \"#0088CC\", Dark: \"#00CCFF\"},\n Text: ext.ThemeColor{Light: \"#111111\", Dark: \"#F0F0F0\"},\n Background: ext.ThemeColor{Light: \"#F0F0F0\", Dark: \"#0A0A14\"},\n})\n\n// Switch to it\nctx.SetTheme(\"neon\")\n\n// List all available themes\nnames := ctx.ListThemes()\n```\n\nSee [Themes](/themes) for the full theme file format, built-in themes, and color reference.\n\n## Custom events\n\nInter-extension communication:\n\n```go\n// Emit\nctx.EmitCustomEvent(\"my-extension:data-ready\", payload)\n\n// Listen\napi.OnCustomEvent(\"my-extension:data-ready\", func(data any, ctx ext.Context) {\n // handle event\n})\n```\n\n## Bridged SDK APIs\n\nExtensions can access powerful internal SDK capabilities that enable advanced features like conversation tree navigation, dynamic skill loading, template parsing, and model resolution.\n\n### Tree Navigation\n\nNavigate the conversation tree, summarize branches, and implement \"fresh context\" loops:\n\n```go\n// Get a specific node by ID with full metadata and children\nnode := ctx.GetTreeNode(\"entry-id\")\n// node.ID, node.ParentID, node.Type (\"message\"/\"branch_summary\"/etc)\n// node.Role, node.Content, node.Model, node.Children ([]string)\n\n// Get the current branch from root to leaf\nbranch := ctx.GetCurrentBranch() // []ext.TreeNode\n\n// Get child entry IDs of a node\nchildren := ctx.GetChildren(\"entry-id\") // []string\n\n// Navigate/fork to a different entry in the tree\nresult := ctx.NavigateTo(\"entry-id\") // ext.TreeNavigationResult{Success, Error}\n\n// Summarize a range of the branch using LLM\nsummary := ctx.SummarizeBranch(\"from-id\", \"to-id\") // string\n\n// Collapse a branch range into a summary entry (fresh context primitive)\nresult := ctx.CollapseBranch(\"from-id\", \"to-id\", \"summary text\")\n```\n\n### Skill Loading\n\nLoad and inject skills dynamically at runtime:\n\n```go\n// Discover skills from standard locations\nresult := ctx.DiscoverSkills() // ext.SkillLoadResult{Skills, Error}\n// Standard locations: ~/.config/kit/skills/, .kit/skills/, .agents/skills/\n\n// Load a specific skill file\nskill, err := ctx.LoadSkill(\"/path/to/skill.md\") // (*ext.Skill, error string)\n// skill.Name, skill.Description, skill.Content, skill.Tags, skill.When\n\n// Load all skills from a directory\nresult := ctx.LoadSkillsFromDir(\"/path/to/skills\") // ext.SkillLoadResult\n\n// Inject a skill as context (pre-loads for next turn)\nerr := ctx.InjectSkillAsContext(\"skill-name\") // error string\n\n// Inject a skill file directly\nerr := ctx.InjectRawSkillAsContext(\"/path/to/skill.md\") // error string\n\n// Get all discovered skills\nskills := ctx.GetAvailableSkills() // []ext.Skill\n```\n\n### Template Parsing\n\nParse and render templates with variable substitution:\n\n```go\n// Parse a template to extract {{variables}}\ntpl := ctx.ParseTemplate(\"name\", \"Hello {{name}}, welcome to {{place}}!\")\n// tpl.Name, tpl.Content, tpl.Variables ([]string)\n\n// Render a template with variable values\nvars := map[string]string{\"name\": \"Alice\", \"place\": \"Kit\"}\nrendered := ctx.RenderTemplate(tpl, vars) // \"Hello Alice, welcome to Kit!\"\n\n// Parse command-line style arguments\npattern := ext.ArgumentPattern{\n Positional: []string{\"command\", \"target\"}, // $1, $2\n Rest: \"args\", // $@\n Flags: map[string]string{\"--loop\": \"loop\", \"-f\": \"force\"},\n}\nresult := ctx.ParseArguments(\"deploy staging --loop 5\", pattern)\n// result.Vars[\"command\"] = \"deploy\"\n// result.Vars[\"target\"] = \"staging\"\n// result.Flags[\"--loop\"] = \"5\"\n\n// Simple positional argument parsing ($1, $2, $@)\nargs := ctx.SimpleParseArguments(\"deploy staging --force\", 2)\n// args[0] = \"deploy staging --force\" (full input)\n// args[1] = \"deploy\" ($1)\n// args[2] = \"staging\" ($2)\n// args[3] = \"--force\" ($@)\n\n// Evaluate model conditionals with wildcards\nmatches := ctx.EvaluateModelConditional(\"claude-*\") // bool\n// Patterns: * matches any, ? matches single char, comma = OR\n\n// Render content with <if-model> conditionals\ncontent := `<if-model is=\"claude-*\">Hi Claude<else>Hi there</if-model>`\nrendered := ctx.RenderWithModelConditionals(content) // based on current model\n```\n\n### Model Resolution\n\nResolve model fallback chains and query capabilities:\n\n```go\n// Resolve a chain of model preferences (tries each until available)\nresult := ctx.ResolveModelChain([]string{\n \"anthropic/claude-opus-4\",\n \"anthropic/claude-sonnet-4\",\n \"openai/gpt-4o\",\n})\n// result.Model (selected), result.Capabilities, result.Attempted, result.Error\n\n// Get capabilities for a specific model\ncaps, err := ctx.GetModelCapabilities(\"anthropic/claude-sonnet-4\")\n// caps.Provider, caps.ModelID, caps.ContextLimit, caps.Reasoning, caps.Streaming\n\n// Check if a model is available (provider exists)\navailable := ctx.CheckModelAvailable(\"anthropic/claude-sonnet-4\") // bool\n\n// Get current provider/model ID\nprovider := ctx.GetCurrentProvider() // \"anthropic\"\nmodelID := ctx.GetCurrentModelID() // \"claude-sonnet-4\"\n```\n"
|
||
},
|
||
{
|
||
"url": "/extensions/examples",
|
||
"title": "Examples",
|
||
"description": "Catalog of example extensions included with Kit.",
|
||
"headings": [
|
||
"UI and display",
|
||
"Workflow and automation",
|
||
"Interactive features",
|
||
"Agent and context",
|
||
"Bridged SDK APIs",
|
||
"Themes",
|
||
"Multi-agent",
|
||
"Development",
|
||
"Subdirectory extensions",
|
||
"Project-local example"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Extension Examples\n\nKit ships with a rich set of example extensions in the `examples/extensions/` directory. These serve as both documentation and starting points for your own extensions.\n\n## UI and display\n\n| Extension | Description |\n|-----------|-------------|\n| [`minimal.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/minimal.go) | Clean UI with custom footer |\n| [`branded-output.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/branded-output.go) | Branded output rendering |\n| [`header-footer-demo.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/header-footer-demo.go) | Custom headers and footers |\n| [`widget-status.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/widget-status.go) | Persistent status widgets |\n| [`overlay-demo.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/overlay-demo.go) | Modal dialogs |\n| [`tool-renderer-demo.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/tool-renderer-demo.go) | Custom tool call rendering |\n| [`custom-editor-demo.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/custom-editor-demo.go) | Vim-like modal editor |\n| [`pirate.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/pirate.go) | Pirate-themed personality |\n\n## Workflow and automation\n\n| Extension | Description |\n|-----------|-------------|\n| [`auto-commit.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/auto-commit.go) | Auto-commit changes on shutdown |\n| [`plan-mode.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/plan-mode.go) | Read-only planning mode |\n| [`permission-gate.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/permission-gate.go) | Permission gating for destructive tools |\n| [`confirm-destructive.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/confirm-destructive.go) | Confirm destructive operations |\n| [`protected-paths.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/protected-paths.go) | Path protection for sensitive files |\n| [`project-rules.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/project-rules.go) | Project-specific rules injection |\n| [`compact-notify.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/compact-notify.go) | Notification on conversation compaction |\n\n## Interactive features\n\n| Extension | Description |\n|-----------|-------------|\n| [`prompt-demo.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/prompt-demo.go) | Interactive prompts (select/confirm/input) |\n| [`bookmark.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/bookmark.go) | Bookmark conversations |\n| [`inline-bash.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/inline-bash.go) | Inline bash execution |\n| [`interactive-shell.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/interactive-shell.go) | Interactive shell integration |\n| [`notify.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/notify.go) | Desktop notifications |\n\n## Agent and context\n\n| Extension | Description |\n|-----------|-------------|\n| [`tool-logger.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/tool-logger.go) | Log all tool calls |\n| [`context-inject.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/context-inject.go) | Inject context into conversations |\n| [`summarize.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/summarize.go) | Conversation summarization |\n| [`lsp-diagnostics.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/lsp-diagnostics.go) | LSP diagnostic integration |\n\n## Bridged SDK APIs\n\nThese examples demonstrate the new bridged SDK APIs that give extensions access to internal Kit capabilities:\n\n| Extension | Description |\n|-----------|-------------|\n| [`bridge-demo.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/bridge_demo.go) | Comprehensive demo of all bridged APIs — tree navigation, skill loading, template parsing, and model resolution |\n| [`conversation-manager.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/conversation-manager.go) | Tree navigation (`GetTreeNode`, `GetCurrentBranch`, `NavigateTo`), branch summarization (`SummarizeBranch`), and fresh context loops (`CollapseBranch`) |\n| [`prompt-templates.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/prompt-templates.go) | Frontmatter-driven templates with model fallback chains (`ResolveModelChain`), skill injection (`InjectSkillAsContext`), and template parsing (`ParseTemplate`, `RenderTemplate`) |\n\n## Themes\n\n| Extension | Description |\n|-----------|-------------|\n| [`neon-theme.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/neon-theme.go) | Custom theme registration and switching |\n\n## Multi-agent\n\n| Extension | Description |\n|-----------|-------------|\n| [`kit-kit.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/kit-kit.go) | Kit-in-Kit sub-agent spawning |\n| [`subagent-widget.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/subagent-widget.go) | Multi-agent orchestration with status widget |\n| [`subagent-test.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/subagent-test.go) | Subagent testing utilities |\n\n## Development\n\n| Extension | Description |\n|-----------|-------------|\n| [`dev-reload.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/dev-reload.go) | Development live-reload |\n| [`tool-logger_test.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/tool-logger_test.go) | Example extension tests (see [Testing](/extensions/testing)) |\n| [`extension_test_template.go`](https://github.com/mark3labs/kit/blob/master/examples/extensions/extension_test_template.go) | Copy-and-paste test template for your extensions |\n\n## Subdirectory extensions\n\n| Directory | Description |\n|-----------|-------------|\n| [`kit-kit-agents/`](https://github.com/mark3labs/kit/tree/master/examples/extensions/kit-kit-agents) | Multi-agent orchestration example |\n| [`kit-telegram/`](https://github.com/mark3labs/kit/tree/master/examples/extensions/kit-telegram) | Telegram bot integration |\n| [`status-tools/`](https://github.com/mark3labs/kit/tree/master/examples/extensions/status-tools) | Status bar tool examples |\n\n## Project-local example\n\nThe Kit repository also includes a project-local extension at `.kit/extensions/go-edit-lint.go` that demonstrates running `gopls` and `golangci-lint` on Go file edits. This serves as an example of how to create extensions specific to a project by placing them in the `.kit/extensions/` directory.\n"
|
||
},
|
||
{
|
||
"url": "/extensions/loading",
|
||
"title": "Loading Extensions",
|
||
"description": "How Kit discovers and loads extensions.",
|
||
"headings": [
|
||
"Auto-discovery",
|
||
"Explicit loading",
|
||
"Disabling extensions",
|
||
"Installing from git",
|
||
"Extension structure",
|
||
"Single-file extensions",
|
||
"Subdirectory extensions",
|
||
"Package-level state"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Loading Extensions\n\n## Auto-discovery\n\nKit automatically discovers and loads extensions from these paths, in order:\n\n| Path | Scope |\n|------|-------|\n| `~/.config/kit/extensions/*.go` | Global single files |\n| `~/.config/kit/extensions/*/main.go` | Global subdirectory extensions |\n| `.kit/extensions/*.go` | Project-local single files |\n| `.kit/extensions/*/main.go` | Project-local subdirectory extensions |\n| `~/.local/share/kit/git/` | Global git-installed packages |\n| `.kit/git/` | Project-local git-installed packages |\n\n## Explicit loading\n\nLoad extensions by path using the `-e` flag:\n\n```bash\nkit -e path/to/extension.go\n```\n\nLoad multiple extensions:\n\n```bash\nkit -e ext1.go -e ext2.go\n```\n\n## Disabling extensions\n\nDisable all auto-discovered extensions:\n\n```bash\nkit --no-extensions\n```\n\nYou can combine `--no-extensions` with `-e` to load only specific extensions:\n\n```bash\nkit --no-extensions -e my-extension.go\n```\n\n## Installing from git\n\nInstall extensions from git repositories using `kit install`:\n\n```bash\n# Install globally (to ~/.local/share/kit/git/)\nkit install https://github.com/user/my-kit-extension.git\n\n# Install project-locally (to .kit/git/)\nkit install -l https://github.com/user/my-kit-extension.git\n\n# Update an installed package\nkit install -u https://github.com/user/my-kit-extension.git\n\n# Remove\nkit install --uninstall my-kit-extension\n```\n\n## Extension structure\n\n### Single-file extensions\n\nA single `.go` file with an `Init` function:\n\n```go\n//go:build ignore\n\npackage main\n\nimport \"kit/ext\"\n\nfunc Init(api ext.API) {\n // register handlers, tools, commands, etc.\n}\n```\n\nThe `//go:build ignore` directive prevents the Go toolchain from trying to compile the file as part of a normal build.\n\n### Subdirectory extensions\n\nFor more complex extensions, create a directory with a `main.go` entry point:\n\n```\n.kit/extensions/my-extension/\n├── main.go # Must contain Init(api ext.API)\n├── helpers.go # Additional source files\n└── config.go\n```\n\n### Package-level state\n\nYaegi supports package-level variables captured in closures. This is the standard way to maintain state across event callbacks:\n\n```go\npackage main\n\nimport \"kit/ext\"\n\nvar callCount int\n\nfunc Init(api ext.API) {\n api.OnToolCall(func(_ ext.ToolCallEvent, ctx ext.Context) {\n callCount++\n ctx.SetFooter(ext.HeaderFooterConfig{\n Content: ext.WidgetContent{\n Text: fmt.Sprintf(\"Tools called: %d\", callCount),\n },\n })\n })\n}\n```\n"
|
||
},
|
||
{
|
||
"url": "/extensions/overview",
|
||
"title": "Extension System",
|
||
"description": "Overview of Kit's Go-based extension system.",
|
||
"headings": [
|
||
"Minimal extension",
|
||
"How extensions work",
|
||
"Key concepts",
|
||
"The API object",
|
||
"The Context object"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Extension System\n\nExtensions are Go source files interpreted at runtime via [Yaegi](https://github.com/traefik/yaegi). They can add custom tools, slash commands, widgets, keyboard shortcuts, and intercept lifecycle events — all without recompiling Kit.\n\n## Minimal extension\n\n```go\n//go:build ignore\n\npackage main\n\nimport \"kit/ext\"\n\nfunc Init(api ext.API) {\n api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {\n ctx.SetFooter(ext.HeaderFooterConfig{\n Content: ext.WidgetContent{Text: \"Custom Footer\"},\n })\n })\n}\n```\n\nRun it with:\n\n```bash\nkit -e examples/extensions/minimal.go\n```\n\n## How extensions work\n\n1. Kit discovers extension files from [auto-discovery paths](/extensions/loading) or explicit `-e` flags\n2. Each `.go` file is loaded into a Yaegi interpreter with access to the `kit/ext` package\n3. Kit calls the `Init(api ext.API)` function in each extension\n4. The extension registers callbacks, tools, commands, and UI components via the `api` and `ctx` objects\n\n## Key concepts\n\n### The `API` object\n\nPassed to `Init()`, the `API` object is used to register lifecycle event handlers and static components:\n\n- **Lifecycle handlers** — `api.OnSessionStart(...)`, `api.OnToolCall(...)`, etc.\n- **Tools** — `api.RegisterTool(ext.ToolDef{...})`\n- **Commands** — `api.RegisterCommand(ext.CommandDef{...})`\n- **Shortcuts** — `api.RegisterShortcut(ext.ShortcutDef{...}, handler)`\n- **Tool renderers** — `api.RegisterToolRenderer(ext.ToolRenderConfig{...})`\n- **Message renderers** — `api.RegisterMessageRenderer(ext.MessageRendererConfig{...})`\n- **Options** — `api.RegisterOption(ext.OptionDef{...})`\n\n### The `Context` object\n\nPassed to event handlers, the `Context` object provides runtime access to Kit's state and UI:\n\n- **Output** — `ctx.Print(...)`, `ctx.PrintInfo(...)`, `ctx.PrintError(...)`\n- **UI components** — `ctx.SetWidget(...)`, `ctx.SetHeader(...)`, `ctx.SetFooter(...)`, `ctx.SetStatus(...)`\n- **Editor** — `ctx.SetEditor(...)`, `ctx.ResetEditor()`\n- **Prompts** — `ctx.PromptSelect(...)`, `ctx.PromptConfirm(...)`, `ctx.PromptInput(...)`\n- **Overlays** — `ctx.ShowOverlay(...)`\n- **Messages** — `ctx.SendMessage(...)`, `ctx.GetMessages()`\n- **Model** — `ctx.SetModel(...)`, `ctx.GetAvailableModels()`\n- **Tools** — `ctx.GetAllTools()`, `ctx.SetActiveTools(...)`\n- **Context stats** — `ctx.GetContextStats()`\n- **Session data** — `ctx.AppendEntry(...)`, `ctx.GetEntries(...)`\n- **Subagents** — `ctx.SpawnSubagent(...)`\n- **LLM completion** — `ctx.Complete(...)`\n- **Custom events** — `ctx.EmitCustomEvent(...)`\n\nSee [Capabilities](/extensions/capabilities) for full details on each component type, and [Testing](/extensions/testing) for writing tests for your extensions.\n"
|
||
},
|
||
{
|
||
"url": "/extensions/testing",
|
||
"title": "Testing Extensions",
|
||
"description": "Write unit tests for your Kit extensions using the test package.",
|
||
"headings": [
|
||
"Overview",
|
||
"Installation",
|
||
"Basic Usage",
|
||
"Testing an Extension File",
|
||
"Testing Inline Extension Code",
|
||
"Common Testing Patterns",
|
||
"Testing Handler Registration",
|
||
"Testing Tool Registration",
|
||
"Testing Commands",
|
||
"Testing Widgets",
|
||
"Testing Input Handling",
|
||
"Testing Headers and Footers",
|
||
"Testing Status Bar",
|
||
"Testing Print Output",
|
||
"Testing with Prompts",
|
||
"Testing Complete Session Flow",
|
||
"Available Assertions",
|
||
"Event Results",
|
||
"Context Interactions",
|
||
"Registration",
|
||
"Messaging",
|
||
"Helper Functions",
|
||
"Advanced Usage",
|
||
"Accessing the Mock Context",
|
||
"Testing Multiple Extensions",
|
||
"Running Tests",
|
||
"Best Practices",
|
||
"Limitations",
|
||
"Complete Example"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Testing Extensions\n\nKit provides a testing package (`github.com/mark3labs/kit/pkg/extensions/test`) that enables you to write unit tests for your extensions. Tests run outside the Yaegi interpreter but load your extension code into an isolated interpreter instance, allowing you to verify behavior without running the full Kit TUI.\n\n## Overview\n\nExtension tests allow you to:\n\n- Test event handlers without running the interactive TUI\n- Verify tool/command registration\n- Assert that context methods (Print, SetWidget, etc.) are called correctly\n- Test blocking and non-blocking event handling\n- Simulate user input and tool calls\n- Verify widget, header, footer, and status bar updates\n\n## Installation\n\nThe test package is part of the Kit codebase. Import it in your extension tests:\n\n```go\nimport (\n \"testing\"\n \"github.com/mark3labs/kit/pkg/extensions/test\"\n \"github.com/mark3labs/kit/internal/extensions\"\n)\n```\n\n## Basic Usage\n\n### Testing an Extension File\n\nCreate a test file alongside your extension (e.g., `my-ext_test.go`):\n\n```go\npackage main\n\nimport (\n \"testing\"\n \"github.com/mark3labs/kit/pkg/extensions/test\"\n \"github.com/mark3labs/kit/internal/extensions\"\n)\n\nfunc TestMyExtension(t *testing.T) {\n // Create a test harness\n harness := test.New(t)\n \n // Load your extension\n harness.LoadFile(\"my-ext.go\")\n \n // Emit events and check results\n result, err := harness.Emit(extensions.ToolCallEvent{\n ToolName: \"my_tool\",\n Input: `{\"key\": \"value\"}`,\n })\n if err != nil {\n t.Fatalf(\"unexpected error: %v\", err)\n }\n \n // Use assertion helpers\n test.AssertNotBlocked(t, result)\n test.AssertPrinted(t, harness, \"expected output\")\n}\n```\n\n### Testing Inline Extension Code\n\nFor quick tests or edge cases, you can load extension source directly:\n\n```go\nfunc TestToolBlocking(t *testing.T) {\n src := `package main\n\nimport \"kit/ext\"\n\nfunc Init(api ext.API) {\n api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {\n if tc.ToolName == \"dangerous\" {\n return &ext.ToolCallResult{Block: true, Reason: \"not allowed\"}\n }\n return nil\n })\n}\n`\n harness := test.New(t)\n harness.LoadString(src, \"test-ext.go\")\n \n // Test the tool is blocked\n result, _ := harness.Emit(extensions.ToolCallEvent{\n ToolName: \"dangerous\",\n Input: \"{}\",\n })\n \n test.AssertBlocked(t, result, \"not allowed\")\n}\n```\n\n## Common Testing Patterns\n\n### Testing Handler Registration\n\nVerify your extension registers the expected handlers:\n\n```go\nfunc TestHandlers(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n test.AssertHasHandlers(t, harness, extensions.ToolCall)\n test.AssertHasHandlers(t, harness, extensions.SessionStart)\n test.AssertNoHandlers(t, harness, extensions.AgentEnd) // Verify no unexpected handlers\n}\n```\n\n### Testing Tool Registration\n\n```go\nfunc TestTools(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n // Verify a specific tool is registered\n test.AssertToolRegistered(t, harness, \"my_tool\")\n \n // Or inspect all tools\n tools := harness.RegisteredTools()\n for _, tool := range tools {\n t.Logf(\"Tool: %s - %s\", tool.Name, tool.Description)\n }\n}\n```\n\n### Testing Commands\n\n```go\nfunc TestCommands(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n test.AssertCommandRegistered(t, harness, \"mycommand\")\n}\n```\n\n### Testing Widgets\n\n```go\nfunc TestWidgets(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n // Trigger event that creates the widget\n _, _ = harness.Emit(extensions.SessionStartEvent{SessionID: \"test\"})\n \n // Verify widget was set\n test.AssertWidgetSet(t, harness, \"my-widget\")\n test.AssertWidgetText(t, harness, \"my-widget\", \"Expected Text\")\n test.AssertWidgetTextContains(t, harness, \"my-widget\", \"partial\")\n \n // Check widget properties directly\n widget, ok := harness.Context().GetWidget(\"my-widget\")\n if ok {\n t.Logf(\"Border color: %s\", widget.Style.BorderColor)\n }\n}\n```\n\n### Testing Input Handling\n\n```go\nfunc TestInput(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n result, _ := harness.Emit(extensions.InputEvent{\n Text: \"!mycommand\",\n Source: \"cli\",\n })\n \n test.AssertInputHandled(t, result, \"handled\")\n}\n```\n\n### Testing Headers and Footers\n\n```go\nfunc TestHeaderFooter(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n _, _ = harness.Emit(extensions.SessionStartEvent{SessionID: \"test\"})\n \n test.AssertHeaderSet(t, harness)\n test.AssertFooterSet(t, harness)\n \n // Inspect content\n header := harness.Context().GetHeader()\n if header != nil {\n t.Logf(\"Header text: %s\", header.Content.Text)\n }\n}\n```\n\n### Testing Status Bar\n\n```go\nfunc TestStatus(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n _, _ = harness.Emit(extensions.AgentEndEvent{})\n \n test.AssertStatusSet(t, harness, \"myext:status\")\n test.AssertStatusText(t, harness, \"myext:status\", \"Ready\")\n}\n```\n\n### Testing Print Output\n\n```go\nfunc TestOutput(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n _, _ = harness.Emit(extensions.ToolCallEvent{ToolName: \"test\"})\n \n // Exact match\n test.AssertPrinted(t, harness, \"exact output\")\n \n // Partial match\n test.AssertPrintedContains(t, harness, \"partial\")\n \n // Styled output\n test.AssertPrintInfo(t, harness, \"info message\")\n test.AssertPrintError(t, harness, \"error message\")\n}\n```\n\n### Testing with Prompts\n\nConfigure mock prompt results for testing interactive behavior:\n\n```go\nfunc TestWithPrompts(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n // Configure what prompts should return\n harness.Context().SetPromptSelectResult(extensions.PromptSelectResult{\n Value: \"option1\",\n Index: 0,\n Cancelled: false,\n })\n \n harness.Context().SetPromptConfirmResult(extensions.PromptConfirmResult{\n Value: true,\n Cancelled: false,\n })\n \n // Now when your extension calls ctx.PromptSelect(), it gets this result\n _, _ = harness.Emit(extensions.SessionStartEvent{SessionID: \"test\"})\n}\n```\n\n### Testing Complete Session Flow\n\n```go\nfunc TestFullSession(t *testing.T) {\n harness := test.New(t)\n harness.LoadFile(\"my-ext.go\")\n \n // Simulate a complete session\n _, _ = harness.Emit(extensions.SessionStartEvent{SessionID: \"test\"})\n _, _ = harness.Emit(extensions.BeforeAgentStartEvent{})\n _, _ = harness.Emit(extensions.AgentStartEvent{})\n \n // Multiple tool calls\n tools := []string{\"Read\", \"Grep\", \"Bash\"}\n for _, tool := range tools {\n _, _ = harness.Emit(extensions.ToolCallEvent{ToolName: tool})\n _, _ = harness.Emit(extensions.ToolResultEvent{ToolName: tool})\n }\n \n _, _ = harness.Emit(extensions.AgentEndEvent{})\n _, _ = harness.Emit(extensions.SessionShutdownEvent{})\n \n // Verify final state\n test.AssertWidgetTextContains(t, harness, \"status\", \"Complete\")\n}\n```\n\n## Available Assertions\n\nThe test package provides these assertion helpers:\n\n### Event Results\n\n| Function | Description |\n|----------|-------------|\n| `AssertNotBlocked(t, result)` | Verify tool was not blocked |\n| `AssertBlocked(t, result, reason)` | Verify tool was blocked with reason |\n| `AssertInputHandled(t, result, action)` | Verify input was handled |\n| `AssertInputTransformed(t, result, text)` | Verify input was transformed |\n\n### Context Interactions\n\n| Function | Description |\n|----------|-------------|\n| `AssertPrinted(t, harness, text)` | Verify exact print output |\n| `AssertPrintedContains(t, harness, substring)` | Verify partial print output |\n| `AssertPrintInfo(t, harness, text)` | Verify PrintInfo was called |\n| `AssertPrintError(t, harness, text)` | Verify PrintError was called |\n| `AssertWidgetSet(t, harness, id)` | Verify widget was set |\n| `AssertWidgetNotSet(t, harness, id)` | Verify widget was not set |\n| `AssertWidgetText(t, harness, id, text)` | Verify widget content |\n| `AssertWidgetTextContains(t, harness, id, substring)` | Verify widget contains text |\n| `AssertHeaderSet(t, harness)` | Verify header was set |\n| `AssertFooterSet(t, harness)` | Verify footer was set |\n| `AssertStatusSet(t, harness, key)` | Verify status was set |\n| `AssertStatusText(t, harness, key, text)` | Verify status text |\n\n### Registration\n\n| Function | Description |\n|----------|-------------|\n| `AssertToolRegistered(t, harness, name)` | Verify tool registration |\n| `AssertCommandRegistered(t, harness, name)` | Verify command registration |\n| `AssertHasHandlers(t, harness, eventType)` | Verify handlers exist |\n| `AssertNoHandlers(t, harness, eventType)` | Verify no handlers |\n\n### Messaging\n\n| Function | Description |\n|----------|-------------|\n| `AssertMessageSent(t, harness, text)` | Verify SendMessage was called |\n| `AssertCancelAndSend(t, harness, text)` | Verify CancelAndSend was called |\n\n## Helper Functions\n\nFor custom assertions, extract result details:\n\n```go\nresult, _ := harness.Emit(extensions.ToolCallEvent{...})\ntcr := test.GetToolCallResult(result)\nif tcr != nil {\n t.Logf(\"Block: %v, Reason: %s\", tcr.Block, tcr.Reason)\n}\n\nir := test.GetInputResult(result)\ntrr := test.GetToolResultResult(result)\n```\n\n## Advanced Usage\n\n### Accessing the Mock Context\n\nFor custom verification:\n\n```go\nctx := harness.Context()\n\n// Get all recorded prints\nprints := ctx.GetPrints()\n\n// Check options\nvalue := ctx.GetOption(\"my-option\")\n\n// Verify widget properties\nwidget, ok := ctx.GetWidget(\"my-widget\")\nif ok && widget.Style.BorderColor == \"#ff0000\" {\n t.Log(\"Widget has red border\")\n}\n\n// Check status entries\nstatus, ok := ctx.GetStatus(\"myext:status\")\n```\n\n### Testing Multiple Extensions\n\nEach harness is isolated:\n\n```go\nharness1 := test.New(t)\nharness1.LoadFile(\"ext1.go\")\n\nharness2 := test.New(t)\nharness2.LoadFile(\"ext2.go\")\n\n// Events to one don't affect the other\n```\n\n### Running Tests\n\nRun all tests in your extension directory:\n\n```bash\ncd examples/extensions\ngo test -v\n```\n\nRun with race detector:\n\n```bash\ngo test -race -v\n```\n\nRun a specific test:\n\n```bash\ngo test -v -run TestMyExtension\n```\n\n## Best Practices\n\n1. **Test one behavior per test** — Keep tests focused and readable\n2. **Use inline source for edge cases** — `LoadString()` is great for testing specific scenarios\n3. **Use `LoadFile()` for integration tests** — Tests the actual extension file\n4. **Assert on context calls** — Verify your extension interacts with the context correctly\n5. **Test both positive and negative cases** — Verify tools are blocked AND allowed appropriately\n6. **Test all event handlers** — Make sure all registered handlers work correctly\n7. **Use descriptive test names** — `TestExtension_BlocksDangerousTools` is clearer than `Test1`\n\n## Limitations\n\nThe test harness has these intentional limitations:\n\n- **No TUI rendering** — Widgets are recorded but not rendered visually\n- **Prompts return configured values** — Pre-configure prompt results in tests\n- **Subagents don't spawn real processes** — `SpawnSubagent()` returns nil/empty results\n- **LLM completions are mocked** — `Complete()` returns empty responses\n- **Some context methods are no-ops** — `Exit()`, `SetActiveTools()`, etc. don't have side effects\n\nThese limitations focus testing on extension logic rather than the full Kit runtime.\n\n## Complete Example\n\nSee `examples/extensions/tool-logger_test.go` for a complete example with 14 tests covering:\n\n- Handler registration\n- Tool call and result handling\n- Session lifecycle events\n- Input commands (`!time`, `!status`)\n- Unknown command handling\n- Concurrent operations (race condition check)\n- Real file logging verification\n"
|
||
},
|
||
{
|
||
"url": "/",
|
||
"title": "Kit",
|
||
"description": "Kit is a powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.",
|
||
"headings": [
|
||
"Features",
|
||
"Quick links"
|
||
],
|
||
"tags": [],
|
||
"content": "\n<div style=\"text-align: center; margin: 2rem 0;\">\n <img src=\"/logo.jpg\" alt=\"KIT\" style=\"max-width: 400px; width: 100%; margin: 0 auto; display: block;\" />\n</div>\n\nA powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.\n\n## Features\n\n- **Multi-Provider LLM Support** — Anthropic, OpenAI, Google Gemini, Ollama, Azure OpenAI, AWS Bedrock, OpenRouter, and more\n- **Built-in Core Tools** — bash (with interactive sudo password prompt), read, write, edit, grep, find, ls, subagent with no MCP overhead\n- **Smart @ Attachments** — Binary files auto-detected via MIME type, MCP resources via `@mcp:server:uri`\n- **MCP Integration** — Connect external MCP servers for expanded capabilities (tools, prompts, and resources)\n- **Extension System** — Write custom tools, commands, widgets, and UI modifications in Go\n- **Interactive TUI** — Rich terminal interface powered by Bubble Tea with streaming, syntax highlighting, and custom rendering\n- **Session Management** — Tree-based conversation history with branching support\n- **Non-Interactive Mode** — Script-friendly positional args with JSON output\n- **ACP Server** — Run Kit as an [Agent Client Protocol](https://agentclientprotocol.com) agent over stdio\n- **Go SDK** — Embed Kit in your own applications\n\n## Quick links\n\n| Resource | Description |\n|----------|-------------|\n| [Installation](/installation) | Get Kit up and running |\n| [Quick Start](/quick-start) | Your first Kit session |\n| [Configuration](/configuration) | Customize Kit for your workflow |\n| [Extensions](/extensions/overview) | Build custom tools and UI components |\n| [Go SDK](/sdk/overview) | Embed Kit in your applications |\n"
|
||
},
|
||
{
|
||
"url": "/installation",
|
||
"title": "Installation",
|
||
"description": "Install Kit using npm, bun, pnpm, Go, or build from source.",
|
||
"headings": [
|
||
"Using npm / bun / pnpm",
|
||
"Using Go",
|
||
"Building from source",
|
||
"Verifying the installation",
|
||
"Setting up a provider"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Installation\n\n## Using npm / bun / pnpm\n\n```bash\nnpm install -g @mark3labs/kit\n```\n\n```bash\nbun install -g @mark3labs/kit\n```\n\n```bash\npnpm install -g @mark3labs/kit\n```\n\n## Using Go\n\n```bash\ngo install github.com/mark3labs/kit/cmd/kit@latest\n```\n\n## Building from source\n\n```bash\ngit clone https://github.com/mark3labs/kit.git\ncd kit\ngo build -o kit ./cmd/kit\n```\n\n## Verifying the installation\n\nAfter installing, verify Kit is available:\n\n```bash\nkit --help\n```\n\n## Setting up a provider\n\nKit needs at least one LLM provider configured. Set an API key for your preferred provider:\n\n```bash\n# Anthropic (default provider)\nexport ANTHROPIC_API_KEY=\"sk-...\"\n\n# OpenAI\nexport OPENAI_API_KEY=\"sk-...\"\n\n# Google Gemini\nexport GOOGLE_API_KEY=\"...\"\n```\n\nFor OAuth-enabled providers like Anthropic, you can also authenticate interactively:\n\n```bash\nkit auth login anthropic\n```\n\nSee [Providers](/providers) for the full list of supported providers and their configuration.\n"
|
||
},
|
||
{
|
||
"url": "/providers",
|
||
"title": "Providers",
|
||
"description": "Supported LLM providers and model configuration.",
|
||
"headings": [
|
||
"Supported providers",
|
||
"Model string format",
|
||
"Model aliases",
|
||
"Anthropic Claude",
|
||
"OpenAI GPT",
|
||
"Google Gemini",
|
||
"Specifying a model",
|
||
"Authentication",
|
||
"API keys",
|
||
"OAuth",
|
||
"Custom provider URL",
|
||
"Auto-routed providers",
|
||
"Model database"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Providers\n\nKit supports a wide range of LLM providers through a unified `provider/model` string format.\n\n## Supported providers\n\n| Provider | Prefix | Description |\n|----------|--------|-------------|\n| **Anthropic** | `anthropic/` | Claude models (native, prompt caching, OAuth) |\n| **OpenAI** | `openai/` | GPT models |\n| **Google** | `google/` or `gemini/` | Gemini models |\n| **Ollama** | `ollama/` | Local models |\n| **Azure OpenAI** | `azure/` | Azure-hosted OpenAI |\n| **AWS Bedrock** | `bedrock/` | Bedrock models |\n| **Google Vertex** | `google-vertex-anthropic/` | Claude on Vertex AI |\n| **OpenRouter** | `openrouter/` | Multi-provider router |\n| **Vercel AI** | `vercel/` | Vercel AI SDK models |\n| **Custom** | `custom/` | Any OpenAI-compatible endpoint |\n| **Auto-routed** | any | Any provider from the models.dev database |\n\n## Model string format\n\n```bash\nprovider/model # Standard format\nanthropic/claude-sonnet-latest\nopenai/gpt-4o\nollama/llama3\ngoogle/gemini-2.5-flash\n```\n\n## Model aliases\n\nKit provides aliases for commonly used models:\n\n### Anthropic Claude\n\n```bash\nclaude-opus-latest → claude-opus-4-6\nclaude-sonnet-latest → claude-sonnet-4-6\nclaude-haiku-latest → claude-haiku-4-5\nclaude-4-opus-latest → claude-opus-4-6\nclaude-4-sonnet-latest → claude-sonnet-4-6\nclaude-4-haiku-latest → claude-haiku-4-5\nclaude-3-7-sonnet-latest → claude-3-7-sonnet-20250219\nclaude-3-5-sonnet-latest → claude-3-5-sonnet-20241022\nclaude-3-5-haiku-latest → claude-3-5-haiku-20241022\nclaude-3-opus-latest → claude-3-opus-20240229\n```\n\n### OpenAI GPT\n\n```bash\no1-latest → o1\no3-latest → o3\no4-latest → o4-mini\ngpt-5-latest → gpt-5.4\ngpt-5-chat-latest → gpt-5.4\ngpt-4-latest → gpt-4o\ngpt-4 → gpt-4o\ngpt-3.5-latest → gpt-3.5-turbo\ngpt-3.5 → gpt-3.5-turbo\ncodex-latest → codex-mini-latest\n```\n\n### Google Gemini\n\n```bash\ngemini-pro-latest → gemini-2.5-pro\ngemini-flash-latest → gemini-2.5-flash\ngemini-flash → gemini-2.5-flash\ngemini-pro → gemini-2.5-pro\n```\n\n## Specifying a model\n\nVia CLI flag:\n\n```bash\nkit --model openai/gpt-4o\nkit -m ollama/llama3\n```\n\nVia config file:\n\n```yaml\nmodel: anthropic/claude-sonnet-latest\n```\n\nVia environment variable:\n\n```bash\nexport KIT_MODEL=\"google/gemini-2.0-flash-exp\"\n```\n\n## Authentication\n\n### API keys\n\nSet the appropriate environment variable for your provider:\n\n```bash\nexport ANTHROPIC_API_KEY=\"sk-...\"\nexport OPENAI_API_KEY=\"sk-...\"\nexport GOOGLE_API_KEY=\"...\"\n```\n\nOr pass it directly:\n\n```bash\nkit --provider-api-key \"sk-...\" --model openai/gpt-4o\n```\n\n### OAuth\n\nFor providers that support OAuth (e.g., Anthropic):\n\n```bash\nkit auth login anthropic # Start OAuth flow\nkit auth status # Check authentication status\nkit auth logout anthropic # Remove credentials\n```\n\n### Custom provider URL\n\nFor self-hosted or proxy endpoints:\n\n```bash\nkit --provider-url \"https://my-proxy.example.com/v1\" --model openai/gpt-4o\n```\n\nWhen `--provider-url` is set with an explicit `--model`, Kit routes through the\n`custom` (OpenAI-compatible) wire and strips any provider prefix from the model\nname. So `openai/gpt-4o`, `google/gemma-4-12b`, and bare `gpt-4o` all resolve\nto the same endpoint — Kit treats `--provider-url` as authoritative about *where*\nto send the request, and the model string as just the upstream model id.\n\nThis avoids name collisions when a local server (LM Studio, Ollama, vLLM, ...)\nhappens to expose a model whose name matches a known cloud provider.\n\nWhen `--provider-url` is provided without `--model`, Kit automatically defaults to `custom/custom`:\n\n```bash\nkit --provider-url \"http://localhost:8080/v1\" \"Hello\"\n```\n\nThe `custom/custom` model has zero cost, 262K context window, and supports reasoning. It routes through the `openaicompat` provider and accepts any OpenAI-compatible API endpoint.\n\nOptionally set `CUSTOM_API_KEY` environment variable or use `--provider-api-key` for endpoints requiring authentication.\n\n## Auto-routed providers\n\nAny provider in the [models.dev](https://models.dev) database can be used with the\nstandard `provider/model` format, even without a dedicated native integration. Kit\nauto-routes the request through the matching **wire protocol** — the actual API\nshape the provider speaks — rather than requiring a per-provider code path:\n\n| Wire protocol | npm package (models.dev) | Transport used |\n|---------------|--------------------------|----------------|\n| OpenAI (Responses API) | `@ai-sdk/openai` | OpenAI |\n| OpenAI (chat completions) | `@ai-sdk/openai-compatible` | OpenAI-compatible |\n| Anthropic | `@ai-sdk/anthropic` | Anthropic |\n| Google Gemini | `@ai-sdk/google` | Google |\n\nThe provider's `api` URL from the database is used as the base URL. A provider\nwhose npm package isn't recognized but that has an `api` URL falls back to the\nOpenAI-compatible wire.\n\nBecause routing follows the wire protocol, aggregator/proxy providers work across\n**all** of their models — including ones they re-flavor onto a different protocol\nvia a per-model override. For example, an aggregator that proxies Claude, GPT,\n*and* Gemini routes them to the Anthropic, OpenAI, and Google transports\nrespectively:\n\n```bash\nkit --model opencode/claude-haiku-4-5 \"Hello\" # → Anthropic wire\nkit --model opencode/gpt-5 \"Hello\" # → OpenAI wire\nkit --model opencode/gemini-3.5-flash \"Hello\" # → Google wire\n```\n\nProvide the provider's API key the same way as any other — via its environment\nvariable (e.g. `OPENCODE_API_KEY`) or `--provider-api-key`.\n\n## Model database\n\nKit ships with a local model database that maps provider names to API configurations. You can manage it with:\n\n```bash\nkit models # List available models\nkit models openai # Filter by provider\nkit models --all # Show all providers\nkit update-models # Update from models.dev\nkit update-models embedded # Reset to bundled database\n```\n"
|
||
},
|
||
{
|
||
"url": "/quick-start",
|
||
"title": "Quick Start",
|
||
"description": "Get up and running with Kit in minutes.",
|
||
"headings": [
|
||
"Basic usage",
|
||
"Non-interactive mode",
|
||
"Resuming sessions",
|
||
"ACP server mode"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Quick Start\n\n## Basic usage\n\nStart an interactive session:\n\n```bash\nkit\n```\n\nRun a one-off prompt:\n\n```bash\nkit \"List files in src/\"\n```\n\nAttach files as context using the `@` prefix:\n\n```bash\nkit @main.go @test.go \"Review these files\"\n```\n\nBinary files (images, audio, PDFs) are automatically detected via MIME type and sent as multimodal attachments. You can also reference MCP resources:\n\n```bash\nkit @mcp:myserver:file:///data/report.csv \"Summarize this data\"\n```\n\nUse a specific model:\n\n```bash\nkit --model anthropic/claude-sonnet-latest\n```\n\n## Non-interactive mode\n\nKit can run as a non-interactive tool for scripting and automation.\n\nGet JSON output:\n\n```bash\nkit \"Explain main.go\" --json\n```\n\nQuiet mode (final response only, no TUI):\n\n```bash\nkit \"Run tests\" --quiet\n```\n\nEphemeral mode (no session file created):\n\n```bash\nkit \"Quick question\" --no-session\n```\n\n## Resuming sessions\n\nContinue the most recent session for the current directory:\n\n```bash\nkit --continue\n# or\nkit -c\n```\n\nPick from previous sessions interactively:\n\n```bash\nkit --resume\n# or\nkit -r\n```\n\n## ACP server mode\n\nKit can run as an [ACP (Agent Client Protocol)](https://agentclientprotocol.com) agent server, enabling ACP-compatible clients (such as [OpenCode](https://github.com/sst/opencode)) to drive Kit as a remote coding agent over stdio:\n\n```bash\n# Start Kit as an ACP server (JSON-RPC 2.0 on stdin/stdout)\nkit acp\n\n# With debug logging to stderr\nkit acp --debug\n```\n\nThe ACP server exposes Kit's full capabilities — LLM execution, tool calls (bash, read, write, edit, grep, etc.), and session persistence — over the standard ACP protocol.\n"
|
||
},
|
||
{
|
||
"url": "/sdk/callbacks",
|
||
"title": "Callbacks",
|
||
"description": "Monitor tool calls and streaming output with the Kit Go SDK.",
|
||
"headings": [
|
||
"Event-based monitoring",
|
||
"Tool call argument streaming",
|
||
"Hook system",
|
||
"BeforeToolCall — block tool execution",
|
||
"AfterToolResult — modify tool output",
|
||
"BeforeTurn — modify prompt, inject messages",
|
||
"AfterTurn — observation only",
|
||
"PrepareStep — intercept messages between steps",
|
||
"Hook priorities",
|
||
"All event types",
|
||
"Subagent event monitoring"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Callbacks\n\n## Event-based monitoring\n\nSubscribe to events for real-time monitoring. Each method returns an unsubscribe function:\n\n```go\nunsub := host.OnToolCall(func(event kit.ToolCallEvent) {\n fmt.Printf(\"Tool: %s, Args: %s\\n\", event.ToolName, event.ToolArgs)\n})\ndefer unsub()\n\nunsub2 := host.OnToolResult(func(event kit.ToolResultEvent) {\n fmt.Printf(\"Result: %s (error: %v)\\n\", event.ToolName, event.IsError)\n})\ndefer unsub2()\n\nunsub3 := host.OnMessageUpdate(func(event kit.MessageUpdateEvent) {\n fmt.Print(event.Chunk)\n})\ndefer unsub3()\n\nunsub4 := host.OnResponse(func(event kit.ResponseEvent) {\n fmt.Println(\"Final response received\")\n})\ndefer unsub4()\n\nunsub5 := host.OnTurnStart(func(event kit.TurnStartEvent) {\n fmt.Println(\"Turn started\")\n})\ndefer unsub5()\n\nunsub6 := host.OnTurnEnd(func(event kit.TurnEndEvent) {\n fmt.Println(\"Turn ended\")\n})\ndefer unsub6()\n```\n\n## Tool call argument streaming\n\nFor tools with large arguments (e.g., `write` with a full file body), the `ToolCallEvent` only fires after the full argument JSON finishes streaming — which can take 5-10+ seconds of \"dead air.\" These three events fire during argument generation so UIs can show activity immediately:\n\n```go\nhost.OnToolCallStart(func(event kit.ToolCallStartEvent) {\n // Fires as soon as the LLM begins generating tool arguments.\n // event.ToolCallID, event.ToolName, event.ToolKind\n fmt.Printf(\"⏳ %s generating arguments...\\n\", event.ToolName)\n})\n\nhost.OnToolCallDelta(func(event kit.ToolCallDeltaEvent) {\n // Each streamed JSON fragment of the tool arguments.\n // event.ToolCallID, event.Delta\n // Useful for live-previewing content or showing byte progress.\n})\n\nhost.OnToolCallEnd(func(event kit.ToolCallEndEvent) {\n // Tool argument streaming complete — execution about to begin.\n // event.ToolCallID\n fmt.Printf(\"✓ Arguments ready, executing...\\n\")\n})\n```\n\n**Full tool lifecycle**: `ToolCallStartEvent` → `ToolCallDeltaEvent` (repeated) → `ToolCallEndEvent` → `ToolCallEvent` → `ToolExecutionStartEvent` → `ToolOutputEvent` (optional) → `ToolExecutionEndEvent` → `ToolResultEvent`\n\n## Hook system\n\nHooks can **modify or cancel** operations. Unlike events (read-only), hooks are read-write interceptors.\n\n### BeforeToolCall — block tool execution\n\n```go\nhost.OnBeforeToolCall(kit.HookPriorityNormal, func(h kit.BeforeToolCallHook) *kit.BeforeToolCallResult {\n // h.ToolCallID, h.ToolName, h.ToolArgs\n if h.ToolName == \"bash\" && strings.Contains(h.ToolArgs, \"rm -rf\") {\n return &kit.BeforeToolCallResult{Block: true, Reason: \"dangerous command\"}\n }\n return nil // allow\n})\n```\n\n### AfterToolResult — modify tool output\n\n```go\nhost.OnAfterToolResult(kit.HookPriorityNormal, func(h kit.AfterToolResultHook) *kit.AfterToolResultResult {\n // h.ToolCallID, h.ToolName, h.ToolArgs, h.Result, h.IsError\n if h.ToolName == \"read\" {\n filtered := redactSecrets(h.Result)\n return &kit.AfterToolResultResult{Result: &filtered}\n }\n return nil\n})\n```\n\n### BeforeTurn — modify prompt, inject messages\n\n```go\nhost.OnBeforeTurn(kit.HookPriorityNormal, func(h kit.BeforeTurnHook) *kit.BeforeTurnResult {\n // h.Prompt\n newPrompt := h.Prompt + \"\\nAlways respond in JSON.\"\n return &kit.BeforeTurnResult{Prompt: &newPrompt}\n // Also available: SystemPrompt *string, InjectText *string\n})\n```\n\n### AfterTurn — observation only\n\n```go\nhost.OnAfterTurn(kit.HookPriorityNormal, func(h kit.AfterTurnHook) {\n // h.Response, h.Error\n log.Printf(\"Turn completed: %d chars\", len(h.Response))\n})\n```\n\n### PrepareStep — intercept messages between steps\n\nThe most powerful hook — fires between steps within a multi-step agent turn, after any steering messages are injected and before messages are sent to the LLM. Can replace the entire context window.\n\n```go\nhost.OnPrepareStep(kit.HookPriorityNormal, func(h kit.PrepareStepHook) *kit.PrepareStepResult {\n // h.StepNumber — zero-based step index within the turn\n // h.Messages — current context window (includes any steering)\n \n // Example: transform tool results with images into user messages\n modified := transformImageToolResults(h.Messages)\n return &kit.PrepareStepResult{Messages: modified}\n // Return nil to pass through unchanged\n})\n```\n\nUse cases: transforming tool results (e.g., image data for vision models), dynamic tool filtering per step, mid-turn context injection, custom stop conditions.\n\n### Hook priorities\n\n```go\nkit.HookPriorityHigh = 0 // runs first\nkit.HookPriorityNormal = 50 // default\nkit.HookPriorityLow = 100 // runs last\n```\n\nLower values run first. First non-nil result wins.\n\n## All event types\n\n| Event | Typed Subscriber | Description |\n|-------|-----------------|-------------|\n| `TurnStartEvent` | `OnTurnStart` | Agent turn started |\n| `TurnEndEvent` | `OnTurnEnd` | Agent turn completed |\n| `MessageStartEvent` | `OnMessageStart` | New assistant message begins |\n| `MessageUpdateEvent` | `OnMessageUpdate` | Streaming text chunk from LLM |\n| `MessageEndEvent` | `OnMessageEnd` | Assistant message complete |\n| `ToolCallStartEvent` | `OnToolCallStart` | LLM began generating tool call arguments |\n| `ToolCallDeltaEvent` | `OnToolCallDelta` | Streamed JSON fragment of tool call arguments |\n| `ToolCallEndEvent` | `OnToolCallEnd` | Tool argument streaming complete |\n| `ToolCallEvent` | `OnToolCall` | Tool call fully parsed, about to execute |\n| `ToolExecutionStartEvent` | `OnToolExecutionStart` | Tool begins executing |\n| `ToolExecutionEndEvent` | `OnToolExecutionEnd` | Tool finishes executing |\n| `ToolResultEvent` | `OnToolResult` | Tool execution completed with result |\n| `ToolCallContentEvent` | `OnToolCallContent` | Text content alongside tool calls |\n| `ToolOutputEvent` | `OnToolOutput` | Streaming output chunk from tool (e.g., bash) |\n| `ResponseEvent` | `OnResponse` | Final response received |\n| `ReasoningStartEvent` | `OnReasoningStart` | LLM begins reasoning/thinking |\n| `ReasoningDeltaEvent` | `OnReasoningDelta` | Streaming reasoning/thinking chunk |\n| `ReasoningCompleteEvent` | `OnReasoningComplete` | Reasoning/thinking finished |\n| `StepStartEvent` | `OnStepStart` | New LLM call begins within a turn |\n| `StepFinishEvent` | `OnStepFinish` | Step completes (with usage, finish reason, tool call info) |\n| `StepUsageEvent` | `OnStepUsage` | Per-step token usage |\n| `StreamFinishEvent` | `OnStreamFinish` | Per-step stream completes (with usage + finish reason) |\n| `TextStartEvent` | `OnTextStart` | LLM begins text content generation |\n| `TextEndEvent` | `OnTextEnd` | LLM finishes text content generation |\n| `WarningsEvent` | `OnWarnings` | LLM provider returned warnings |\n| `SourceEvent` | `OnSource` | LLM referenced a source (e.g., web search) |\n| `ErrorEvent` | `OnError` | Agent-level error during streaming |\n| `RetryEvent` | `OnRetry` | LLM request retried after transient error |\n| `CompactionEvent` | `OnCompaction` | Conversation compacted |\n| `SteerConsumedEvent` | `OnSteerConsumed` | Steering messages injected into turn |\n| `PasswordPromptEvent` | — | Sudo command needs password (respond via `ResponseCh`) |\n\n> **Note:** `OnStreaming` is a deprecated alias for `OnMessageUpdate` and will be removed in a future release.\n\n## Subagent event monitoring\n\nMonitor real-time events from LLM-initiated subagents (when the model uses the `subagent` tool):\n\n```go\nhost.OnToolCall(func(e kit.ToolCallEvent) {\n if e.ToolName == \"subagent\" {\n host.SubscribeSubagent(e.ToolCallID, func(event kit.Event) {\n // Receives the same event types as Subscribe(), scoped to the child agent\n switch ev := event.(type) {\n case kit.MessageUpdateEvent:\n fmt.Print(ev.Chunk)\n case kit.ToolCallEvent:\n fmt.Printf(\"Subagent calling: %s\\n\", ev.ToolName)\n }\n })\n }\n})\n```\n\n`SubscribeSubagent` returns an unsubscribe function. Listeners are also cleaned up automatically when the subagent completes. See [Subagents](/advanced/subagents) for more details.\n"
|
||
},
|
||
{
|
||
"url": "/sdk/options",
|
||
"title": "SDK Options",
|
||
"description": "Configuration options for the Kit Go SDK.",
|
||
"headings": [
|
||
"Full options reference",
|
||
"Options fields",
|
||
"Core",
|
||
"Generation parameters",
|
||
"Provider configuration",
|
||
"Session",
|
||
"Tools & extensions",
|
||
"Skills & configuration",
|
||
"Compaction & MCP",
|
||
"MCP OAuth Authorization",
|
||
"MCP Tasks",
|
||
"Per-server mode",
|
||
"Progress callbacks",
|
||
"Inspecting and cancelling tasks",
|
||
"Precedence",
|
||
"Tool configuration"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# SDK Options\n\nPass an `Options` struct to `kit.New()` to configure the Kit instance.\n\n::: tip\nFor simple setups, `kit.NewAgent(ctx, ...Option)` provides functional-options\nhelpers (`WithModel`, `WithStreaming`, `Ephemeral`, ...) over the same `Options`\nstruct. See [Functional options](/sdk/overview#functional-options-newagent).\n:::\n\nEach `kit.New` / `kit.NewAgent` call owns an isolated configuration store, so\nthese options never leak between Kit instances in the same process. See\n[Per-instance config isolation](/sdk/overview#per-instance-config-isolation).\n\n## Full options reference\n\n```go\nhost, err := kit.New(ctx, &kit.Options{\n // Model\n Model: \"ollama/llama3\",\n SystemPrompt: \"You are a helpful bot\",\n ConfigFile: \"/path/to/config.yml\",\n\n // Behavior\n MaxSteps: 10,\n Streaming: ptrBool(true), // *bool: nil = unset (default true), &false = off\n Quiet: true,\n Debug: true,\n\n // Generation parameters (override env/config/per-model defaults)\n MaxTokens: 16384, // 0 = auto-resolve; non-zero suppresses right-sizing\n ThinkingLevel: \"medium\", // \"off\", \"none\", \"minimal\", \"low\", \"medium\", \"high\"\n Temperature: ptrFloat32(0.2), // pointer so explicit 0.0 != unset\n TopP: nil, // nil = provider/per-model default\n TopK: nil,\n FrequencyPenalty: nil,\n PresencePenalty: nil,\n\n // Provider configuration\n ProviderAPIKey: \"sk-...\", // \"\" = use config / provider env var\n ProviderURL: \"https://proxy.internal/v1\", // \"\" = provider default endpoint\n TLSSkipVerify: false, // only effective when true\n\n // Session\n SessionPath: \"./session.jsonl\",\n SessionDir: \"/custom/sessions/\",\n Continue: true,\n NoSession: true,\n\n // Tools\n Tools: []kit.Tool{...}, // Replace default tool set entirely\n ExtraTools: []kit.Tool{...}, // Add tools alongside defaults\n DisableCoreTools: true, // Use no core tools (0 tools, for chat-only)\n\n // Configuration\n SkipConfig: true, // Skip .kit.yml files (viper defaults + env vars still apply)\n\n // Compaction\n AutoCompact: true,\n\n // Skills\n Skills: []string{\"/path/to/skill.md\"},\n SkillsDir: \"/path/to/skills/\",\n NoSkills: true,\n\n // Feature toggles\n NoExtensions: true, // disable Yaegi extension loading\n NoContextFiles: true, // disable automatic AGENTS.md loading\n\n // Session (advanced)\n SessionManager: myCustomSession, // custom SessionManager implementation\n\n // MCP OAuth — both opt-in. Leave MCPAuthHandler nil to disable\n // OAuth entirely (remote MCP 401s bubble up as errors). CLI apps\n // pass kit.NewCLIMCPAuthHandler(); custom UX embedders implement\n // MCPAuthHandler or configure DefaultMCPAuthHandler + OnAuthURL.\n MCPAuthHandler: authHandler, // nil = OAuth disabled\n MCPTokenStoreFactory: func(serverURL string) (kit.MCPTokenStore, error) {\n return myStore(serverURL), nil\n },\n\n // In-Process MCP Servers\n InProcessMCPServers: map[string]*kit.MCPServer{\n \"docs\": mcpSrv, // *server.MCPServer from mcp-go\n },\n})\n```\n\n## Options fields\n\n### Core\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `Model` | `string` | config default | Model string (provider/model format) |\n| `SystemPrompt` | `string` | — | System prompt text or file path |\n| `ConfigFile` | `string` | `~/.kit.yml` | Path to config file |\n| `MaxSteps` | `int` | `0` | Max agent steps (0 = unlimited) |\n| `Streaming` | `*bool` | `nil` | Enable streaming output. `nil` leaves it to the precedence chain (env → config → default `true`); `&true`/`&false` forces it. Pointer so unset is distinct from explicit `false`. |\n| `Quiet` | `bool` | `false` | Suppress output |\n| `Debug` | `bool` | `false` | Enable debug logging |\n\n### Generation parameters\n\nThese fields override the corresponding values from `.kit.yml` / `KIT_*`\nenvironment variables. Leaving a field at its zero/nil value lets the\nprecedence chain resolve a value (`KIT_*` env → config file → per-model\ndefaults from `modelSettings`/`customModels` → an 8192 SDK floor for\n`MaxTokens` (matching the CLI `--max-tokens` default) and provider-level\ndefaults for samplers).\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `MaxTokens` | `int` | auto-resolved | Max output tokens per response. `0` = auto-resolve; non-zero suppresses automatic right-sizing (same semantics as `--max-tokens`). |\n| `ThinkingLevel` | `string` | auto-resolved | Reasoning effort: `\"off\"`, `\"none\"`, `\"minimal\"`, `\"low\"`, `\"medium\"`, `\"high\"`. `\"\"` falls through to config/env/per-model/`\"off\"`. |\n| `Temperature` | `*float32` | — | Sampling randomness. Pointer type so explicit `0.0` is distinguishable from \"unset\". |\n| `TopP` | `*float32` | — | Nucleus sampling cutoff. `nil` leaves provider/per-model default. |\n| `TopK` | `*int32` | — | Top-K sampling limit. `nil` leaves provider/per-model default. |\n| `FrequencyPenalty` | `*float32` | — | OpenAI-family frequency penalty. `nil` leaves provider default. |\n| `PresencePenalty` | `*float32` | — | OpenAI-family presence penalty. `nil` leaves provider default. |\n\nPointer-typed fields (`Streaming` and the samplers) are populated via tiny helpers:\n\n```go\nfunc ptrBool(v bool) *bool { return &v }\nfunc ptrFloat32(v float32) *float32 { return &v }\n```\n\nThese fields eliminate the need for `viper.Set()` calls before `kit.New()`\nwhen embedding Kit as a library.\n\n### Provider configuration\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `ProviderAPIKey` | `string` | — | API key used to authenticate with the provider. `\"\"` falls back to config / provider-specific env var (e.g. `ANTHROPIC_API_KEY`). When set, it takes precedence over config and env values on this instance's store. |\n| `ProviderURL` | `string` | — | Override the provider endpoint (e.g. LiteLLM, vLLM, Azure OpenAI, internal proxy). `\"\"` = provider default. |\n| `TLSSkipVerify` | `bool` | `false` | Disable TLS certificate verification on the provider HTTP client. Only effective when `true`; to force-disable, use config file or env var instead. For self-signed dev certs only. |\n\n### Session\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `SessionPath` | `string` | — | Open a specific session file |\n| `SessionDir` | `string` | — | Base directory for session discovery |\n| `Continue` | `bool` | `false` | Resume most recent session |\n| `NoSession` | `bool` | `false` | Ephemeral mode (no persistence) |\n| `SessionManager` | `SessionManager` | — | Custom session backend (advanced) |\n\n### Tools & extensions\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `Tools` | `[]Tool` | — | Replace the entire default tool set |\n| `ExtraTools` | `[]Tool` | — | Additional tools alongside core/MCP/extension tools |\n| `DisableCoreTools` | `bool` | `false` | Use no core tools (0 tools, for chat-only) |\n| `NoExtensions` | `bool` | `false` | Disable Yaegi extension loading |\n| `NoContextFiles` | `bool` | `false` | Disable automatic AGENTS.md loading |\n\n### Skills & configuration\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `SkipConfig` | `bool` | `false` | Skip `.kit.yml` file loading (viper defaults + env vars still apply) |\n| `Skills` | `[]string` | — | Explicit skill files/dirs to load |\n| `SkillsDir` | `string` | — | Override default skills directory |\n| `NoSkills` | `bool` | `false` | Disable skill loading entirely |\n\nThese fields only control the **initial** skill and context-file set picked\nup by `New()`. To add, remove, or replace skills and `AGENTS.md`-style\ncontext files at runtime (e.g. per user or per session), use the\n`AddSkill` / `LoadAndAddSkill` / `RemoveSkill` / `SetSkills` and\n`AddContextFile` / `AddContextFileContent` / `RemoveContextFile` /\n`SetContextFiles` methods on `*kit.Kit`. See\n[Runtime skills and context files](/sdk/overview#runtime-skills-and-context-files).\n\n### Compaction & MCP\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `AutoCompact` | `bool` | `false` | Auto-compact when near context limit |\n| `CompactionOptions` | `*CompactionOptions` | — | Configuration for auto-compaction |\n| `MCPAuthHandler` | `MCPAuthHandler` | — | OAuth handler for remote MCP servers. `nil` disables OAuth (servers returning 401 fail with the authorization-required error). See [MCP OAuth](#mcp-oauth-authorization) below. |\n| `MCPTokenStoreFactory` | `func` | — | Custom OAuth token storage for MCP servers (default: JSON file in `$XDG_CONFIG_HOME/.kit/mcp_tokens.json`). |\n| `InProcessMCPServers` | `map[string]*MCPServer` | — | In-process mcp-go servers (no subprocess) |\n| `MCPTaskMode` | `map[string]MCPTaskMode` | — | Per-server override for task-augmented `tools/call`. Keys are server names; missing entries fall back to the `tasksMode` field of the matching `MCPServerConfig`. See [MCP Tasks](#mcp-tasks). |\n| `MCPTaskTimeout` | `time.Duration` | `15m` | Maximum wall-clock to wait for a task to reach a terminal state. Independent of any per-call context deadline. |\n| `MCPTaskTTL` | `time.Duration` | — | TTL hint sent in `TaskParams` for every task-augmented call. Zero omits the field and lets the server pick. |\n| `MCPTaskPollInterval` | `time.Duration` | `1s` | Fallback interval between `tasks/get` requests when the server does not suggest one. |\n| `MCPTaskMaxPollInterval` | `time.Duration` | `5s` | Cap on the polling interval (a server-supplied `pollInterval` can otherwise grow without bound). |\n| `MCPTaskProgress` | `MCPTaskProgressHandler` | — | Optional callback invoked once when a task is accepted and on every observed status transition. The final invocation always carries a terminal status. |\n\n## MCP OAuth Authorization\n\nWhen a remote MCP server (SSE or Streamable HTTP) requires OAuth, Kit runs\nthe full authorization flow (dynamic client registration → PKCE → user\nconsent → token exchange → token persistence) but delegates the **user-facing\nstep** — displaying the authorization URL and receiving the callback — to\nan `MCPAuthHandler`.\n\nThe SDK is deliberately inert when `MCPAuthHandler` is `nil`: it does **not**\nauto-construct a default handler, bind a local TCP port, or open a browser.\nThis keeps library, daemon, and web-app embedders free of surprise I/O.\nConsumers opt in by passing a handler explicitly.\n\n| Building block | When to use |\n|---|---|\n| `MCPAuthHandler = nil` (default) | OAuth disabled. Remote MCP servers requiring auth fail with a clear error. Correct for libraries, daemons, and web apps. |\n| `kit.NewCLIMCPAuthHandler()` | CLI/TUI apps. Opens the system browser, prints status to stderr (or via `NotifyFunc`), runs a localhost callback server. Used by the `kit` binary. |\n| `kit.NewDefaultMCPAuthHandler()` + `OnAuthURL` | Custom UX. Use the SDK's port reservation and callback server; plug in your own presentation via the `OnAuthURL(serverName, authURL)` closure. |\n| Implement `kit.MCPAuthHandler` directly | Full control. No localhost binding — e.g. return the URL from an HTTP endpoint and have the consumer POST the callback URL back. |\n\n**CLI-style embedder:**\n\n```go\nauthHandler, err := kit.NewCLIMCPAuthHandler()\nif err != nil {\n log.Fatal(err)\n}\ndefer authHandler.Close() // release the reserved port\n\nhost, _ := kit.New(ctx, &kit.Options{\n MCPAuthHandler: authHandler,\n})\n```\n\n**Custom UX embedder (TUI modal, QR code, web redirect, etc.):**\n\n```go\nauthHandler, _ := kit.NewDefaultMCPAuthHandler()\nauthHandler.OnAuthURL = func(serverName, authURL string) {\n // No browser or terminal assumptions — render however you like.\n myUI.ShowAuthPrompt(serverName, authURL)\n}\ndefer authHandler.Close()\n\nhost, _ := kit.New(ctx, &kit.Options{\n MCPAuthHandler: authHandler,\n})\n```\n\n**Fully custom handler (no local port binding at all):**\n\n```go\ntype WebAuthHandler struct {\n redirectURI string\n callbacks chan string\n}\n\nfunc (h *WebAuthHandler) RedirectURI() string { return h.redirectURI }\n\nfunc (h *WebAuthHandler) HandleAuth(ctx context.Context, serverName, authURL string) (string, error) {\n // Push the URL to the user's existing browser session via your web app,\n // then block on the callback that your HTTP handler pushes onto the channel.\n h.pushToUserSession(serverName, authURL)\n select {\n case callbackURL := <-h.callbacks:\n return callbackURL, nil\n case <-ctx.Done():\n return \"\", ctx.Err()\n }\n}\n```\n\n::: warning\n`DefaultMCPAuthHandler` with no `OnAuthURL` set will silently drop the\nauthorization URL and hang until the 2-minute callback timeout fires. Always\nset `OnAuthURL`, or use a higher-level wrapper like `CLIMCPAuthHandler`.\n:::\n\n## MCP Tasks\n\nThe [MCP Tasks utility](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)\nturns a synchronous `tools/call` into a pollable async job: the server\nreturns a `taskId` with status `working` immediately, and the client polls\n`tasks/get` / `tasks/result` until the task reaches a terminal state.\n\nKit advertises task support during `initialize` and, by default, augments\n`tools/call` with task metadata only when the server advertises\n`tasks/toolCalls` capability — so any existing MCP server keeps its previous\nsynchronous behaviour bit-for-bit. Long-running tools (builds, deployments,\nbatch jobs, sub-agent runs) get HTTP/SSE timeout-resistance and clean\ncancellation \"for free\" once both sides opt in.\n\n### Per-server mode\n\n```go\nimport \"time\"\n\nhost, _ := kit.New(ctx, &kit.Options{\n MCPTaskMode: map[string]kit.MCPTaskMode{\n \"build-server\": kit.MCPTaskModeAlways, // force task-augmented calls\n \"chat-server\": kit.MCPTaskModeNever, // force synchronous calls\n // any server not in the map honours its `tasksMode` config field\n // (default \"auto\")\n },\n})\n```\n\n| Mode | Behaviour |\n|---|---|\n| `MCPTaskModeAuto` (default) | Augment `tools/call` with `TaskParams` only when the server advertised `tasks/toolCalls`. |\n| `MCPTaskModeNever` | Always issue `tools/call` synchronously, ignoring server capability. |\n| `MCPTaskModeAlways` | Always opt in, even when the server didn't advertise the capability. The server may still respond synchronously. |\n\n### Progress callbacks\n\n```go\nhost, _ := kit.New(ctx, &kit.Options{\n MCPTaskTimeout: 15 * time.Minute, // total wall-clock cap\n MCPTaskTTL: 30 * time.Minute, // server retention hint\n MCPTaskProgress: func(p kit.MCPTaskProgress) {\n log.Printf(\"%s/%s: %s %s\", p.Server, p.TaskID, p.Status, p.Message)\n },\n})\n```\n\nThe handler fires once when a task is accepted and again on every observed\nstatus transition. The final call always carries a terminal status\n(`MCPTaskStatusCompleted`, `MCPTaskStatusFailed`, or `MCPTaskStatusCancelled`).\nDo not block in the handler — dispatch long work on a goroutine.\n\n### Inspecting and cancelling tasks\n\n```go\ntasks, _ := host.ListMCPTasks(ctx, \"build-server\")\nfor _, t := range tasks {\n fmt.Printf(\"%s: %s (%s)\\n\", t.TaskID, t.Status, t.StatusMessage)\n}\n\nt, _ := host.GetMCPTask(ctx, \"build-server\", taskID)\nif !t.Status.IsTerminal() {\n _, _ = host.CancelMCPTask(ctx, \"build-server\", taskID)\n}\n```\n\n`Kit.ListMCPTasks`, `Kit.GetMCPTask`, and `Kit.CancelMCPTask` work against any\nloaded MCP server that advertises the corresponding capability.\n`MCPTaskStatus.IsTerminal()` is the canonical check for completion.\n\nContext cancellation also works end-to-end: cancelling the `ctx` passed to a\ntool execution triggers a best-effort `tasks/cancel` before the call returns.\n\n## Precedence\n\nFor any given generation or provider field, the effective value is resolved\nin this order (highest priority first):\n\n1. `Options.X` (SDK caller)\n2. `KIT_X` environment variable\n3. `.kit.yml` (project-local then `~/.kit.yml`)\n4. Per-model defaults (`modelSettings[provider/model]` or `customModels[...].params`)\n5. Provider-level defaults (e.g. Anthropic's own temperature default)\n6. SDK last-resort floor (currently: `MaxTokens = 8192`, matching the CLI `--max-tokens` default)\n\nSampling params that remain `nil` after the SDK resolution step are left out\nof the provider call entirely, so the LLM library applies its own default.\n\n## Tool configuration\n\n**`Tools`** replaces ALL default tools (core + MCP + extension). **`ExtraTools`** adds tools alongside the defaults. Use `Tools` to restrict capabilities; use `ExtraTools` to extend them.\n\nCreate custom tools with `kit.NewTool` — no external dependencies needed:\n\n```go\ntype LookupInput struct {\n ID string `json:\"id\" description:\"Record ID to look up\"`\n}\n\nlookupTool := kit.NewTool(\"lookup\", \"Look up a record by ID\",\n func(ctx context.Context, input LookupInput) (kit.ToolOutput, error) {\n record := db.Find(input.ID)\n return kit.TextResult(record.String()), nil\n },\n)\n\nhost, _ := kit.New(ctx, &kit.Options{\n ExtraTools: []kit.Tool{lookupTool},\n})\n```\n\nSee [Overview](/sdk/overview#custom-tools) for full custom tool documentation.\n"
|
||
},
|
||
{
|
||
"url": "/sdk/overview",
|
||
"title": "Go SDK",
|
||
"description": "Embed Kit in your Go applications.",
|
||
"headings": [
|
||
"Installation",
|
||
"Basic usage",
|
||
"Functional options (NewAgent)",
|
||
"When to use which",
|
||
"Per-instance config isolation",
|
||
"Multi-turn conversations",
|
||
"Additional prompt methods",
|
||
"Custom tools",
|
||
"Generation & provider overrides",
|
||
"Event system",
|
||
"Model management",
|
||
"Dynamic MCP servers",
|
||
"In-process MCP servers",
|
||
"Runtime skills and context files",
|
||
"MCP prompts and resources",
|
||
"MCP tasks (long-running tools)",
|
||
"Context and compaction",
|
||
"In-process subagents"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Go SDK\n\nThe `pkg/kit` package lets you embed Kit as a library in your Go applications.\n\n## Installation\n\n```bash\ngo get github.com/mark3labs/kit/pkg/kit\n```\n\n## Basic usage\n\n```go\npackage main\n\nimport (\n \"context\"\n \"log\"\n\n kit \"github.com/mark3labs/kit/pkg/kit\"\n)\n\nfunc main() {\n ctx := context.Background()\n\n // Create Kit instance with default configuration\n host, err := kit.New(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n defer host.Close()\n\n // Send a prompt\n response, err := host.Prompt(ctx, \"What is 2+2?\")\n if err != nil {\n log.Fatal(err)\n }\n\n println(response)\n}\n```\n\n## Functional options (`NewAgent`)\n\nFor simple programmatic setups, `kit.NewAgent` offers an ergonomic\nfunctional-options front door over `kit.New`. Streaming is **enabled by\ndefault**; pass `kit.WithStreaming(false)` to opt out.\n\n```go\nhost, err := kit.NewAgent(ctx,\n kit.WithModel(\"anthropic/claude-sonnet-4-5-20250929\"),\n kit.WithSystemPrompt(\"You are a helpful assistant.\"),\n kit.WithMaxTokens(8192),\n kit.WithThinkingLevel(\"medium\"),\n kit.Ephemeral(), // in-memory session, no persistence\n)\nif err != nil {\n log.Fatal(err)\n}\ndefer host.Close()\n```\n\nAvailable options:\n\n| Option | Sets |\n|--------|------|\n| `WithModel(string)` | `Options.Model` (provider/model format) |\n| `WithSystemPrompt(string)` | `Options.SystemPrompt` (inline text or file path) |\n| `WithStreaming(bool)` | `Options.Streaming` (default `true` under `NewAgent`) |\n| `WithMaxTokens(int)` | `Options.MaxTokens` |\n| `WithThinkingLevel(string)` | `Options.ThinkingLevel` |\n| `WithTools(...Tool)` | `Options.Tools` (replaces the default set) |\n| `WithExtraTools(...Tool)` | `Options.ExtraTools` (adds alongside defaults) |\n| `WithProviderAPIKey(string)` | `Options.ProviderAPIKey` |\n| `WithProviderURL(string)` | `Options.ProviderURL` |\n| `WithConfigFile(string)` | `Options.ConfigFile` |\n| `WithDebug()` | `Options.Debug = true` |\n| `Ephemeral()` | `Options.NoSession = true` |\n\nOptions are applied in order, so later options override earlier ones. `Option`\nis a plain `func(*Options)`, so you can define your own. For advanced\nconfiguration not covered by the helpers (custom MCP config, in-process MCP\nservers, session backends, MCP task tuning) construct an `Options` value\nexplicitly and call `kit.New`.\n\n### When to use which\n\n| Constructor | Use when |\n|-------------|----------|\n| `kit.NewAgent(ctx, ...Option)` | Quick programmatic setups; you only need the common fields. Streaming defaults on. |\n| `kit.New(ctx, *Options)` | You need fields without a `With*` helper (`MCPConfig`, `InProcessMCPServers`, `SessionManager`, MCP task tuning, etc.), or you already hold an `Options` value. |\n\n## Per-instance config isolation\n\nEach `kit.New` / `kit.NewAgent` call owns an **isolated configuration store**,\nso constructing multiple Kit instances in the same process is safe: setting the\nmodel, thinking level, or generation parameters on one never affects another,\nand runtime mutators (`SetModel`, `SetThinkingLevel`) only touch the owning\ninstance. This makes subagent spawning and multi-Kit embedding race-free with\nno external synchronization required.\n\n```go\na, _ := kit.NewAgent(ctx, kit.WithThinkingLevel(\"low\"))\nb, _ := kit.NewAgent(ctx, kit.WithThinkingLevel(\"high\"))\n\na.SetThinkingLevel(ctx, \"medium\")\n// a.GetThinkingLevel() == \"medium\"; b.GetThinkingLevel() is still \"high\"\n```\n\n## Multi-turn conversations\n\nConversations retain context automatically across calls:\n\n```go\nhost.Prompt(ctx, \"My name is Alice\")\nresponse, _ := host.Prompt(ctx, \"What's my name?\")\n// response: \"Your name is Alice\"\n```\n\n## Additional prompt methods\n\nThe SDK provides several prompt variants:\n\n| Method | Description |\n|--------|-------------|\n| `Prompt(ctx, message)` | Simple prompt, returns response string |\n| `PromptWithOptions(ctx, message, opts)` | With per-call options |\n| `PromptResult(ctx, message)` | Returns full `TurnResult` with usage stats |\n| `PromptResultWithFiles(ctx, message, files)` | Multimodal with file attachments |\n| `Steer(ctx, instruction)` | System-level steering without user message |\n| `FollowUp(ctx, text)` | Continue without new user input |\n\n## Custom tools\n\nCreate custom tools with `kit.NewTool`. The JSON schema is auto-generated from the input struct — no external dependencies required:\n\n```go\ntype WeatherInput struct {\n City string `json:\"city\" description:\"City name\"`\n}\n\nweatherTool := kit.NewTool(\"get_weather\", \"Get current weather for a city\",\n func(ctx context.Context, input WeatherInput) (kit.ToolOutput, error) {\n return kit.TextResult(\"72°F, sunny in \" + input.City), nil\n },\n)\n\nhost, _ := kit.New(ctx, &kit.Options{\n ExtraTools: []kit.Tool{weatherTool},\n})\n```\n\nStruct tags control the schema:\n\n- `json:\"name\"` — parameter name\n- `description:\"...\"` — description shown to the LLM\n- `enum:\"a,b,c\"` — restrict valid values\n- `omitempty` — marks the parameter as optional\n\nReturn values:\n\n| Helper | Description |\n|--------|-------------|\n| `kit.TextResult(s)` | Successful text result |\n| `kit.ErrorResult(s)` | Error result (LLM sees it as a tool error) |\n| `kit.ImageResult(s, data, mediaType)` | Image result with binary data (e.g. `\"image/png\"`) |\n| `kit.MediaResult(s, data, mediaType)` | Non-image media result (e.g. `\"audio/mpeg\"`) |\n\nBinary data (images, audio, etc.) in `ToolOutput.Data` is automatically forwarded to the LLM when `MediaType` is set. For advanced use, return a `kit.ToolOutput` struct directly with `Data`, `MediaType`, and `Metadata` fields.\n\nUse `kit.NewParallelTool` for tools that are safe to run concurrently. Use `kit.ToolCallIDFromContext(ctx)` to retrieve the LLM-assigned call ID for logging or tracing.\n\n## Generation & provider overrides\n\nSDK consumers can configure generation parameters and provider endpoints\nentirely in-code via `Options`, without touching `.kit.yml` or `viper.Set()`:\n\n```go\nhost, _ := kit.New(ctx, &kit.Options{\n Model: \"anthropic/claude-sonnet-4-5-20250929\",\n MaxTokens: 16384, // 0 = auto-resolve (env → config → per-model → floor)\n ThinkingLevel: \"high\", // \"off\" | \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\"\n Temperature: ptrFloat32(0.2), // nil = provider/per-model default\n ProviderAPIKey: os.Getenv(\"MY_SECRET\"), // overrides pre-existing viper state\n ProviderURL: \"https://proxy.internal/v1\",\n})\n\nfunc ptrFloat32(v float32) *float32 { return &v }\n```\n\nSee [Options](/sdk/options#generation-parameters) for the full field reference,\nincluding `TopP`, `TopK`, `FrequencyPenalty`, `PresencePenalty`, and `TLSSkipVerify`.\n\n## Event system\n\nSubscribe to events for monitoring:\n\n```go\nunsubscribe := host.OnToolCall(func(event kit.ToolCallEvent) {\n fmt.Println(\"Tool called:\", event.Name)\n})\ndefer unsubscribe()\n\nhost.OnToolResult(func(event kit.ToolResultEvent) {\n fmt.Println(\"Tool result:\", event.Name)\n})\n\nhost.OnMessageUpdate(func(event kit.MessageUpdateEvent) {\n fmt.Print(event.Chunk)\n})\n```\n\n## Model management\n\nSwitch models at runtime:\n\n```go\nhost.SetModel(ctx, \"openai/gpt-4o\")\ninfo := host.GetModelInfo()\nmodels := host.GetAvailableModels()\n```\n\n## Dynamic MCP servers\n\nAdd and remove MCP servers at runtime:\n\n```go\nn, err := host.AddMCPServer(ctx, \"github\", kit.MCPServerConfig{\n Command: []string{\"npx\", \"-y\", \"@modelcontextprotocol/server-github\"},\n})\nfmt.Printf(\"Loaded %d tools\\n\", n)\n\nerr = host.RemoveMCPServer(\"github\")\nservers := host.ListMCPServers() // []kit.MCPServerStatus\n```\n\n### In-process MCP servers\n\nRegister mcp-go servers running in the same process — zero subprocess overhead:\n\n```go\nimport (\n \"github.com/mark3labs/mcp-go/mcp\"\n \"github.com/mark3labs/mcp-go/server\"\n)\n\nmcpSrv := server.NewMCPServer(\"my-tools\", \"1.0.0\",\n server.WithToolCapabilities(true),\n)\nmcpSrv.AddTool(mcp.NewTool(\"search_docs\",\n mcp.WithDescription(\"Search documentation\"),\n mcp.WithString(\"query\", mcp.Required()),\n), searchHandler)\n\n// At init time\nhost, _ := kit.New(ctx, &kit.Options{\n InProcessMCPServers: map[string]*kit.MCPServer{\n \"docs\": mcpSrv,\n },\n})\n\n// Or at runtime\nn, _ := host.AddInProcessMCPServer(ctx, \"docs\", mcpSrv)\n```\n\n## Runtime skills and context files\n\nKit auto-discovers skills and `AGENTS.md`-style context files during `New()`,\nbut multi-tenant hosts (chatbots, web services, per-user agents) often need\nto swap these **after** construction. The runtime mutators below recompose\nthe system prompt and apply it to the agent so the next turn picks up the\nupdated instructions — no restart, no file shuffling.\n\n```go\n// Add a programmatic skill — no file on disk required.\nhost.AddSkill(&kit.Skill{\n Name: \"polite-french\",\n Description: \"Respond in French and always greet the user.\",\n Content: \"Always reply in French. Open every response with 'Bonjour'.\",\n})\n\n// Or load one from disk.\nhost.LoadAndAddSkill(\"/var/skills/refund-policy.md\")\n\n// Project context (AGENTS.md equivalents): inline content from a DB...\nhost.AddContextFileContent(\n fmt.Sprintf(\"session://%s/AGENTS.md\", userID),\n rulesFromDB,\n)\n// ...or load from disk.\nhost.LoadAndAddContextFile(\"/etc/agents/tenant-acme.md\")\n\n// Remove individually when a session ends.\nhost.RemoveSkill(\"polite-french\")\nhost.RemoveContextFile(fmt.Sprintf(\"session://%s/AGENTS.md\", userID))\n\n// Or replace the whole set in one call.\nhost.SetSkills(activeSkillsForUser)\nhost.SetContextFiles(activeContextForUser)\n\n// Inspect current state (snapshot copies — safe to mutate).\nskills := host.GetSkills()\nctxFiles := host.GetContextFiles()\n```\n\nKey points:\n\n- **Auto-refresh.** Every `Add*` / `Remove*` / `Set*` call recomposes the system\n prompt against the captured base prompt (preserving per-model overrides and\n `--system-prompt` resolution) and pushes the result onto the agent. Call\n `host.RefreshSystemPrompt()` only if you mutate state through a different\n path and need to force a re-render.\n- **Dedup keys.** Skills dedupe by `Name`; context files dedupe by `Path`.\n Re-adding the same key replaces the entry instead of appending a duplicate.\n- **Path is opaque.** `ContextFile.Path` does not have to point at a real file\n — it's only used for dedup and for the `Instructions from: <Path>` header\n injected into the prompt. URIs like `session://user-123/AGENTS.md` work fine.\n- **Thread safety.** All readers and mutators are safe to call concurrently\n from multiple goroutines; the underlying state is guarded by an internal\n `RWMutex`.\n- **Init-time options still apply.** `Options.Skills`, `Options.SkillsDir`,\n `Options.NoSkills`, and `Options.NoContextFiles` continue to control the\n startup set; the runtime API mutates from whatever state `New()` produced.\n See [SDK options](/sdk/options#skills--configuration).\n\n## MCP prompts and resources\n\nQuery prompts and resources exposed by connected MCP servers:\n\n```go\n// List and expand prompts\nprompts := host.ListMCPPrompts()\nresult, _ := host.GetMCPPrompt(ctx, \"server\", \"prompt-name\", map[string]string{\"key\": \"value\"})\n\n// List and read resources\nresources := host.ListMCPResources()\ncontent, _ := host.ReadMCPResource(ctx, \"server\", \"file:///path\")\n```\n\n## MCP tasks (long-running tools)\n\nKit advertises [MCP task support](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)\nduring `initialize`, so cooperating servers can return a `taskId` immediately\nand let Kit poll `tasks/get` / `tasks/result` until the operation completes.\nThis avoids HTTP/SSE proxy timeouts on long tools and gives you clean\ncancellation via context.\n\n```go\nhost, _ := kit.New(ctx, &kit.Options{\n MCPTaskMode: map[string]kit.MCPTaskMode{\n \"build-server\": kit.MCPTaskModeAlways,\n },\n MCPTaskProgress: func(p kit.MCPTaskProgress) {\n log.Printf(\"%s: %s\", p.TaskID, p.Status)\n },\n})\n\n// Inspect / cancel in-flight tasks\ntasks, _ := host.ListMCPTasks(ctx, \"build-server\")\n_, _ = host.CancelMCPTask(ctx, \"build-server\", tasks[0].TaskID)\n```\n\nDefaults to `MCPTaskModeAuto` per server, so any existing MCP server keeps\nits previous synchronous behaviour. See [SDK options → MCP Tasks](/sdk/options#mcp-tasks)\nfor the full surface.\n\n## Context and compaction\n\nMonitor and manage context usage:\n\n```go\ntokens := host.EstimateContextTokens()\nstats := host.GetContextStats()\n\nif host.ShouldCompact() {\n result, err := host.Compact(ctx, nil, \"\")\n}\n```\n\n## In-process subagents\n\nSpawn child Kit instances without subprocess overhead:\n\n```go\nresult, err := host.Subagent(ctx, kit.SubagentConfig{\n Prompt: \"Analyze the test files\",\n Model: \"anthropic/claude-haiku-3-5-20241022\",\n NoSession: true,\n Timeout: 2 * time.Minute,\n})\n```\n\nSee [Options](/sdk/options), [Callbacks](/sdk/callbacks), and [Sessions](/sdk/sessions) for more details.\n"
|
||
},
|
||
{
|
||
"url": "/sdk/sessions",
|
||
"title": "SDK Sessions",
|
||
"description": "Session management in the Kit Go SDK.",
|
||
"headings": [
|
||
"Automatic persistence",
|
||
"Accessing session info",
|
||
"Configuring sessions via Options",
|
||
"Clearing history",
|
||
"Tree-based sessions",
|
||
"Listing and managing sessions",
|
||
"Custom session manager"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# SDK Sessions\n\n## Automatic persistence\n\nBy default, Kit automatically persists sessions to JSONL files. Multi-turn conversations retain context across calls:\n\n```go\nhost.Prompt(ctx, \"My name is Alice\")\nresponse, _ := host.Prompt(ctx, \"What's my name?\")\n// response: \"Your name is Alice\"\n```\n\n## Accessing session info\n\n```go\n// Get the current session file path\npath := host.GetSessionPath()\n\n// Get the session ID\nid := host.GetSessionID()\n\n// Get the current model string\nmodel := host.GetModelString()\n```\n\n## Configuring sessions via Options\n\nSession behavior is configured at initialization:\n\n```go\n// Open a specific session file\nhost, _ := kit.New(ctx, &kit.Options{\n SessionPath: \"./my-session.jsonl\",\n})\n\n// Resume the most recent session for the current directory\nhost, _ := kit.New(ctx, &kit.Options{\n Continue: true,\n})\n\n// Ephemeral mode (no file persistence)\nhost, _ := kit.New(ctx, &kit.Options{\n NoSession: true,\n})\n\n// Custom session directory\nhost, _ := kit.New(ctx, &kit.Options{\n SessionDir: \"/custom/sessions/\",\n})\n```\n\n## Clearing history\n\nClear the in-memory conversation history (does not delete the session file):\n\n```go\nhost.ClearSession()\n```\n\n## Tree-based sessions\n\nKit's session model is tree-based, supporting branching. You can branch from any entry to explore alternate conversation paths:\n\n```go\n// Access the tree session manager\nts := host.GetTreeSession()\n\n// Branch from a specific entry\nerr := host.Branch(\"entry-id-123\")\n```\n\n## Listing and managing sessions\n\nPackage-level functions for session discovery:\n\n```go\n// List sessions for a specific directory\nsessions := kit.ListSessions(\"/home/user/project\")\n\n// List all sessions across all directories\nall := kit.ListAllSessions()\n\n// Delete a session file\nkit.DeleteSession(\"/path/to/session.jsonl\")\n```\n\n## Custom session manager\n\nFor advanced use cases (databases, cloud storage, multi-user apps), implement the `SessionManager` interface to replace the default JSONL file backend:\n\n```go\nhost, _ := kit.New(ctx, &kit.Options{\n SessionManager: myCustomSession,\n})\n```\n\nThe interface requires methods for message storage, branching, compaction, extension data, and lifecycle management. See the [SDK skill reference](https://github.com/mark3labs/kit) for the complete interface definition.\n\nWhen using a custom `SessionManager`, the `SessionPath`, `Continue`, and `NoSession` options are ignored — your manager handles its own storage and session selection.\n"
|
||
},
|
||
{
|
||
"url": "/sessions",
|
||
"title": "Session Management",
|
||
"description": "How Kit persists and manages conversation sessions.",
|
||
"headings": [
|
||
"Session storage",
|
||
"Compaction",
|
||
"Auto-cleanup",
|
||
"Resuming sessions",
|
||
"Continue most recent",
|
||
"Interactive picker",
|
||
"Open a specific session",
|
||
"Session commands",
|
||
"Ephemeral mode",
|
||
"Sharing sessions",
|
||
"Preferences persistence"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Session Management\n\nKit uses a tree-based session model that supports branching and forking conversations.\n\n## Session storage\n\nSessions are stored as JSONL (JSON Lines) files:\n\n```\n~/.kit/sessions/<cwd-path>/<timestamp>_<id>.jsonl\n```\n\nPath separators in the working directory are replaced with `--`. For example, `/home/user/project` becomes `home--user--project`.\n\nEach line in the session file is a JSON entry representing a message, tool call, model change, or extension data. The tree structure allows branching from any message to explore alternate paths.\n\n## Compaction\n\nWhen conversations grow long, Kit can compact them to free up context window space. The compaction system:\n\n- **Non-destructive**: Old messages remain on disk for history; only the LLM context is summarized\n- **File tracking**: Tracks which files were read and modified across compactions\n- **Split-turn handling**: Can summarize large single turns by splitting them\n- **Tool result truncation**: Caps tool output during serialization to stay within token budgets\n\nUse `/compact [focus]` to manually compact, or enable `--auto-compact` to compact automatically near the context limit.\n\n## Auto-cleanup\n\nKit automatically cleans up empty sessions on shutdown and when using `/resume`. A session is considered empty if it has no messages beyond the initial system prompt. This prevents cluttering your sessions directory with unused files.\n\nTo start fresh without creating a session file at all, use ephemeral mode:\n\n```bash\nkit --no-session\n```\n\n## Resuming sessions\n\n### Continue most recent\n\n```bash\nkit --continue\nkit -c\n```\n\n### Interactive picker\n\nChoose from previous sessions interactively:\n\n```bash\nkit --resume\nkit -r\n```\n\nThe session picker supports search, scope/filter toggles (all sessions vs. current directory), and session deletion. You can also open it during a session with the `/resume` slash command.\n\n### Open a specific session\n\n```bash\nkit --session path/to/session.jsonl\nkit -s path/to/session.jsonl\n```\n\n## Session commands\n\nThese slash commands are available during an interactive session:\n\n| Command | Description |\n|---------|-------------|\n| `/name [name]` | Set or display the session's display name |\n| `/session` | Show session info (path, ID, message count) |\n| `/resume` | Open the session picker to switch sessions |\n| `/export [path]` | Export session as JSONL (auto-generates path if omitted) |\n| `/import <path>` | Import and switch to a session from a JSONL file |\n| `/share` | Upload session to GitHub Gist and get a shareable viewer URL |\n| `/tree` | Navigate the session tree |\n| `/fork` | Fork to new session from an earlier message (creates new session file) |\n| `/new` | Start a new session (creates new session file) |\n\n## Ephemeral mode\n\nRun without creating a session file:\n\n```bash\nkit --no-session\n```\n\nThis is useful for one-off prompts, scripting, and subagent patterns where persistence isn't needed.\n\n## Sharing sessions\n\nThe `/share` command uploads your session JSONL to GitHub Gist (via the `gh` CLI) and prints a shareable viewer URL:\n\n```\n/share\n```\n\nThe shared session includes:\n- The **system prompt** that was active during the conversation\n- The **model** used (e.g., `anthropic/claude-sonnet-4-5`)\n\nThe viewer displays this information in a collapsible \"System Prompt\" section at the top of the session, with the model shown as a badge in the header.\n\nThe viewer is available at `https://go-kit.dev/session/#GIST_ID` and supports all message types including text, reasoning blocks, tool calls, images, and model changes.\n\nYou can also load any JSONL session via URL parameter: `https://go-kit.dev/session/?url=https://example.com/session.jsonl`\n\n## Preferences persistence\n\nKit automatically saves your preferences across sessions to `~/.config/kit/preferences.yml`:\n\n- **Theme** — Set via `/theme <name>`\n- **Model** — Set via `/model <name>` or the model selector\n- **Thinking level** — Set via `/thinking <level>` or Shift+Tab cycling\n\nThese preferences are restored on next launch. Precedence: CLI flag > config file > saved preference > default.\n"
|
||
},
|
||
{
|
||
"url": "/themes",
|
||
"title": "Themes",
|
||
"description": "Customize Kit's appearance with built-in themes, custom theme files, and the extension theme API.",
|
||
"headings": [
|
||
"Quick start",
|
||
"Built-in themes",
|
||
"Custom theme files",
|
||
"Theme file format",
|
||
"Partial themes",
|
||
"Distributing themes",
|
||
"Config file theme",
|
||
"Extension theme API",
|
||
"Registering a theme",
|
||
"Switching themes",
|
||
"Listing available themes",
|
||
"ThemeColorConfig fields",
|
||
"Precedence order",
|
||
"Startup theme resolution"
|
||
],
|
||
"tags": [],
|
||
"content": "\n# Themes\n\nKit ships with 22 built-in color themes and supports custom themes via YAML/JSON files or the extension API. Themes control all UI colors: input borders, popups, system messages, markdown rendering, syntax highlighting, and diff displays.\n\n## Quick start\n\nSwitch themes at runtime with the `/theme` command:\n\n```\n/theme dracula\n/theme catppuccin\n/theme kitt\n```\n\nRun `/theme` with no arguments to list all available themes.\n\n**Theme selections are automatically saved** to `~/.config/kit/preferences.yml` and restored on next launch. You don't need to add anything to your config file — just `/theme <name>` and it sticks. This persistence also applies to **model** and **thinking level** selections.\n\n## Built-in themes\n\n| Theme | Style |\n|-------|-------|\n| `kitt` | KITT-inspired reds and ambers (default) |\n| `catppuccin` | Soothing pastels (Mocha/Latte) |\n| `dracula` | Purple and cyan dark theme |\n| `tokyonight` | Cool blues with warm accents |\n| `nord` | Arctic, north-bluish palette |\n| `gruvbox` | Retro groove colors |\n| `monokai` | Classic syntax theme |\n| `solarized` | Precision colors for machines and people |\n| `github` | GitHub's light and dark palettes |\n| `one-dark` | Atom One Dark |\n| `rose-pine` | Soho vibes with muted tones |\n| `ayu` | Simple with bright colors |\n| `material` | Material Design palette |\n| `everforest` | Green-focused comfortable theme |\n| `kanagawa` | Dark theme inspired by Katsushika Hokusai |\n| `amoled` | Pure black background, vivid accents |\n| `synthwave` | Retro neon glows |\n| `vesper` | Warm minimalist dark theme |\n| `flexoki` | Inky reading palette |\n| `matrix` | Green-on-black terminal aesthetic |\n| `vercel` | Clean monochrome with blue accents |\n| `zenburn` | Low-contrast, warm dark theme |\n\nAll themes support both light and dark terminal modes via adaptive colors.\n\n## Custom theme files\n\nCreate a `.yml`, `.yaml`, or `.json` file with color definitions. Kit discovers themes from two directories:\n\n| Location | Scope | Precedence |\n|----------|-------|------------|\n| `~/.config/kit/themes/` | User (global) | Overrides built-ins |\n| `.kit/themes/` | Project-local | Overrides user and built-ins |\n\n### Theme file format\n\nA theme file defines adaptive color pairs with `light` and `dark` hex values. Any field left empty inherits from the default KITT theme.\n\n```yaml\n# ~/.config/kit/themes/my-theme.yml\n\n# Core semantic colors\nprimary:\n light: \"#8839ef\"\n dark: \"#cba6f7\"\nsecondary:\n light: \"#04a5e5\"\n dark: \"#89dceb\"\nsuccess:\n light: \"#40a02b\"\n dark: \"#a6e3a1\"\nwarning:\n light: \"#df8e1d\"\n dark: \"#f9e2af\"\nerror:\n light: \"#d20f39\"\n dark: \"#f38ba8\"\ninfo:\n light: \"#1e66f5\"\n dark: \"#89b4fa\"\n\n# Text and chrome\ntext:\n light: \"#4c4f69\"\n dark: \"#cdd6f4\"\nmuted:\n light: \"#6c6f85\"\n dark: \"#a6adc8\"\nvery-muted:\n light: \"#9ca0b0\"\n dark: \"#6c7086\"\nbackground:\n light: \"#eff1f5\"\n dark: \"#1e1e2e\"\nborder:\n light: \"#acb0be\"\n dark: \"#585b70\"\nmuted-border:\n light: \"#ccd0da\"\n dark: \"#313244\"\n\n# Semantic roles\nsystem:\n light: \"#179299\"\n dark: \"#94e2d5\"\ntool:\n light: \"#fe640b\"\n dark: \"#fab387\"\naccent:\n light: \"#ea76cb\"\n dark: \"#f5c2e7\"\nhighlight:\n light: \"#e6e9ef\"\n dark: \"#181825\"\n\n# Diff backgrounds\ndiff-insert-bg:\n light: \"#d5f0d5\"\n dark: \"#1a3a2a\"\ndiff-delete-bg:\n light: \"#f5d5d5\"\n dark: \"#3a1a2a\"\ndiff-equal-bg:\n light: \"#eceef3\"\n dark: \"#232336\"\ndiff-missing-bg:\n light: \"#e4e6eb\"\n dark: \"#1a1a2e\"\n\n# Code block backgrounds\ncode-bg:\n light: \"#eceef3\"\n dark: \"#232336\"\ngutter-bg:\n light: \"#e4e6eb\"\n dark: \"#1a1a2e\"\nwrite-bg:\n light: \"#d5f0d5\"\n dark: \"#1a3a2a\"\n\n# Markdown and syntax highlighting\nmarkdown:\n heading:\n light: \"#1e66f5\"\n dark: \"#89b4fa\"\n link:\n light: \"#1e66f5\"\n dark: \"#89b4fa\"\n keyword:\n light: \"#8839ef\"\n dark: \"#cba6f7\"\n string:\n light: \"#40a02b\"\n dark: \"#a6e3a1\"\n number:\n light: \"#fe640b\"\n dark: \"#fab387\"\n comment:\n light: \"#9ca0b0\"\n dark: \"#6c7086\"\n```\n\n### Partial themes\n\nYou only need to define the colors you want to change. Unspecified fields fall back to the default theme:\n\n```yaml\n# Just override the primary and accent colors\nprimary:\n dark: \"#FF00FF\"\naccent:\n dark: \"#00FFFF\"\n```\n\n### Distributing themes\n\n- **Personal**: Drop a file in `~/.config/kit/themes/`\n- **Team/project**: Drop a file in `.kit/themes/` and commit it to version control\n- **Override built-in**: Name your file the same as a built-in (e.g., `dracula.yml`) and it takes precedence\n\n## Config file theme\n\nYou can also set theme colors directly in `.kit.yml`:\n\n```yaml\ntheme:\n primary:\n light: \"#8839ef\"\n dark: \"#cba6f7\"\n error:\n dark: \"#FF0000\"\n```\n\nOr reference an external theme file:\n\n```yaml\ntheme: \"./themes/my-custom-theme.yml\"\n```\n\n## Extension theme API\n\nExtensions can register and switch themes programmatically at runtime.\n\n### Registering a theme\n\n```go\napi.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {\n ctx.RegisterTheme(\"neon\", ext.ThemeColorConfig{\n Primary: ext.ThemeColor{Light: \"#CC00FF\", Dark: \"#FF00FF\"},\n Secondary: ext.ThemeColor{Light: \"#0088CC\", Dark: \"#00FFFF\"},\n Success: ext.ThemeColor{Light: \"#00CC44\", Dark: \"#00FF66\"},\n Warning: ext.ThemeColor{Light: \"#CCAA00\", Dark: \"#FFFF00\"},\n Error: ext.ThemeColor{Light: \"#CC0033\", Dark: \"#FF0055\"},\n Info: ext.ThemeColor{Light: \"#0088CC\", Dark: \"#00CCFF\"},\n Text: ext.ThemeColor{Light: \"#111111\", Dark: \"#F0F0F0\"},\n Background: ext.ThemeColor{Light: \"#F0F0F0\", Dark: \"#0A0A14\"},\n MdKeyword: ext.ThemeColor{Light: \"#CC00FF\", Dark: \"#FF00FF\"},\n MdString: ext.ThemeColor{Light: \"#00CC44\", Dark: \"#00FF66\"},\n MdComment: ext.ThemeColor{Light: \"#888888\", Dark: \"#555555\"},\n })\n})\n```\n\n### Switching themes\n\n```go\nerr := ctx.SetTheme(\"dracula\")\n```\n\n### Listing available themes\n\n```go\nnames := ctx.ListThemes()\n```\n\n### ThemeColorConfig fields\n\n| Field | Description |\n|-------|-------------|\n| `Primary` | Main brand/accent color |\n| `Secondary` | Secondary accent |\n| `Success` | Success states |\n| `Warning` | Warning states |\n| `Error` | Error/critical states |\n| `Info` | Informational states |\n| `Text` | Primary text |\n| `Muted` | Dimmed text |\n| `VeryMuted` | Very dimmed text |\n| `Background` | Base background |\n| `Border` | Panel borders |\n| `MutedBorder` | Subtle dividers |\n| `System` | System messages |\n| `Tool` | Tool-related elements |\n| `Accent` | Secondary highlight |\n| `Highlight` | Highlighted regions |\n| `MdHeading` | Markdown headings |\n| `MdLink` | Markdown links |\n| `MdKeyword` | Syntax: keywords |\n| `MdString` | Syntax: strings |\n| `MdNumber` | Syntax: numbers |\n| `MdComment` | Syntax: comments |\n\nEach field is an `ext.ThemeColor` with `Light` and `Dark` hex strings. Empty fields inherit from the default theme.\n\n## Precedence order\n\nWhen multiple sources define the same theme name, later sources win:\n\n1. Built-in presets (lowest)\n2. User themes (`~/.config/kit/themes/`)\n3. Project-local themes (`.kit/themes/`)\n4. Extension-registered themes (highest)\n\n### Startup theme resolution\n\nAt startup, Kit determines which theme to apply:\n\n1. **`.kit.yml` `theme:` key** — explicit config always wins (highest priority)\n2. **`~/.config/kit/preferences.yml`** — persisted `/theme` selection\n3. **Default `kitt` theme** — fallback\n\nThe preferences file is updated automatically whenever you use `/theme` or `ctx.SetTheme()`. It is separate from `.kit.yml` so it never clobbers your config comments or formatting.\n\nTheme changes via `/theme` or `ctx.SetTheme()` take effect immediately on all UI elements, including previously rendered messages.\n"
|
||
}
|
||
]
|
||
} |