Compare commits

..

754 Commits

Author SHA1 Message Date
Sai Karthik 7f366eab84 cmd: add --no-skills, --skill, and --skills-dir CLI flags & config (#55)
* cmd: add --no-skills, --skill, and --skills-dir CLI flags

The pkg/kit Options struct already had full backend support for skills
control (NoSkills, Skills []string, SkillsDir) wired into loadSkills()
in pkg/kit/kit.go, but there were no corresponding CLI flags to drive
them. This commit closes that gap.

Changes in cmd/root.go:

- Add three package-level flag variables alongside the existing
  noExtensionsFlag/extensionPaths group:
    noSkillsFlag bool
    skillsPaths  []string
    skillsDir    string

- Register three persistent cobra flags in init():
    --no-skills        disable skill loading (auto-discovery and explicit)
    --skill <path>     load a skill file or directory (repeatable)
    --skills-dir <dir> override the project-local skills directory
                       used for auto-discovery

- Wire all three into the kitOpts struct literal in runNormalMode()
  so they flow directly into kit.New() -> loadSkills().

No changes to pkg/kit or internal/skills -- the backend was already
complete. No viper binding is needed because kit.go reads these fields
directly from opts rather than from viper (unlike NoExtensions which
uses the viper fallback path).

Example usage:
  kit --no-skills "prompt"
  kit --skill ./my-skill.md --skill ./other-skill.md "prompt"
  kit --skills-dir /path/to/skills "prompt"

Co-authored-by: Claude <claude@anthropic.com>

* docs: document --no-skills, --skill, and --skills-dir CLI flags

Add the three new skills CLI flags to all relevant documentation:

- README.md: add Skills section under Global Flags CLI reference
- www/pages/cli/flags.md: add Skills table (mirrors Extensions section pattern)
- www/pages/cli/commands.md: expand the Skills section with usage examples
  and a description of auto-discovery vs explicit loading vs --no-skills

Co-authored-by: Claude <claude@anthropic.com>

* feat: add config file support for skills options

Skills could previously only be controlled via CLI flags or SDK Options
fields. This commit wires all three skills settings into viper so they
can also be set in .kit.yml / .kit.yaml / .kit.json and via KIT_*
environment variables — matching the pattern used by no-extensions,
no-core-tools, and prompt-template.

cmd/root.go:
- Bind --no-skills, --skill, and --skills-dir flags to viper keys
  (no-skills, skill, skills-dir) so config file values flow through.

pkg/kit/kit.go:
- At skill-load time, merge opts fields with viper values:
  - noSkills = opts.NoSkills || v.GetBool("no-skills")
  - skillPaths: opts.Skills if non-empty, else v.GetStringSlice("skill")
  - skillsDir: opts.SkillsDir if non-empty, else v.GetString("skills-dir")
- Build a shallow-copied mergedOpts so loadSkills() picks up the
  resolved values without mutating the original Options struct.

docs:
- README.md: add skills keys to the Basic Configuration YAML example
- www/pages/configuration.md: add no-skills, skill, skills-dir rows to
  the All configuration keys table

Config file example (.kit.yml):
  no-skills: false
  skill:
    - /path/to/skill.md
  skills-dir: /path/to/skills/

Co-authored-by: Claude <claude@anthropic.com>

* config: add skills keys to default .kit.yml template

Add no-skills, skill, and skills-dir as commented-out examples in the
default config file generated by EnsureConfigExists(), alongside the
existing application settings block.

Co-authored-by: Claude <claude@anthropic.com>

* test: add test coverage for skills CLI flags and config keys

Four test locations updated:

pkg/kit/export_test.go:
- Add ConfigStringSliceForTest() helper to expose v.GetStringSlice()
  from the Kit's isolated viper store, needed to assert skill list values.

pkg/kit/kit_test.go (TestNewWithSkillsOptions):
- NoSkills=true: GetSkills() returns empty slice
- SkillsDir=<empty dir>: kit.New() succeeds with zero skills
- Skills=[file]: single explicit skill file is loaded and name parsed correctly

pkg/kit/viper_isolation_test.go:
- TestSkillsViperKeys: no-API-key struct-level checks for NoSkills, Skills,
  and SkillsDir fields on Options
- TestSkillsConfigFileKeys: full kit.New() round-trips via a written .kit.yml
  for each of the three config keys:
    no-skills: true  → GetSkills() returns empty
    skill: [path]    → named skill loaded from config file path
    skills-dir: dir  → custom discovery root accepted without error

internal/config/config_test.go (TestEnsureConfigExists):
- Assert generated ~/.kit.yml template contains '# Skills configuration',
  'no-skills:', and 'skills-dir:' comment blocks.

Co-authored-by: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-06-12 16:23:17 +03:00
Ed Zynda e8e99b19a8 refactor: dedupe cross-package logic and remove dead code from audit (#58)
* Remove dead code: 5 unused symbols across internal packages

- internal/models: LoadModelSettingsFromConfig (zero refs)
- internal/prompts: PromptTemplate.ExpandWithArgs (zero refs)
- internal/app: NewMessageStore (tests migrated to NewMessageStoreWithMessages)
- internal/config: HasEnvVars (+ its test)
- internal/core: ContextWithSudoPassword (test migrated to context.WithValue)

* pkg/kit: use TreeManager alias in exported signatures

NewTreeManagerAdapter and InitTreeSession now spell their signatures with
the public kit.TreeManager alias instead of internal/session.TreeManager,
so go doc renders domain types rather than internal paths.

* Consolidate tool-kind classification into internal/extensions

coreToolKinds + toolKindFor were duplicated verbatim in
internal/extensions/wrapper.go and pkg/kit/events.go, risking silent
divergence between extension events and SDK events. Single source of
truth now lives in internal/extensions/toolkinds.go; pkg/kit re-exports
the constants.

* Consolidate Anthropic OAuth detection and usage-tracker refresh

The 'is the active Anthropic credential a stored OAuth token' check was
copy-pasted at 5 sites, all prefix-matching the magic string
'stored OAuth' produced in internal/auth. Now:

- internal/auth: new CredentialSourceOAuth constant + IsAnthropicOAuth()
- internal/ui: new UpdateUsageTrackerForModel(); CreateUsageTracker and
  SetupCLI share lookupTrackableModel (SetupCLI no longer re-inlines the
  tracker construction)
- cmd/root.go + cmd/extension_context.go: verbatim-duplicated tracker
  refresh blocks replaced with ui.UpdateUsageTrackerForModel
- pkg/kit isAnthropicOAuth delegates to auth.IsAnthropicOAuth
- internal/models compares source against the constant

* pkg/kit: consolidate model-path helpers and argument tokenizer

- ExtractModelFromPath mis-parsed model IDs containing '/' (e.g.
  'openrouter/meta/llama' -> 'meta'); it now delegates to
  RemoveProviderFromModel and is deprecated alongside
  ExtractProviderFromPath (-> GetCurrentProvider)
- parseFields delegated to prompts.ParseCommandArgs so extension argument
  parsing and builtin prompt-template parsing share one quote/escape
  grammar; ParseCommandArgs now also splits on tabs (superset of both
  previous tokenizers)

* Unify the two {{variable}} template engines

internal/skills and pkg/kit/template_bridge each had their own grammar:
skills rejected '{{ name }}' (whitespace) but allowed digit-first names;
the bridge was the opposite. A template behaved differently depending on
whether it was loaded as a skill prompt or via the extension API.

internal/skills is now the single engine using the superset grammar
(\{\{\s*(\w+)\s*\}\}); pkg/kit ParseTemplate/RenderTemplate are thin
adapters over it. Expand is now regex-based so whitespace placeholders
expand consistently; missing variables are still left as-is.

* internal/ui: extract switchModel helper for model-switch flow

The model-selector handler (ModelSelectedMsg) and /model slash command
duplicated the full switch sequence (thinking-level fallback, setModel,
display-state update, preference persistence, ModelChange emit) and had
already drifted in ordering. Both now call a single switchModel method.
Display state is still updated directly (no prog.Send from Update).

* extbridge: extract shared BaseContext for extension wiring

cmd/extension_context.go and internal/acpserver/session.go each built a
giant extensions.Context literal, duplicating ~15 delegation closures
(GetContextStats, GetMessages, AppendEntry, options, SetModel core,
Complete, SpawnSubagent, ...) that had to be kept in sync by hand. New
data-access fields had to be wired in both places or ACP-mode extensions
silently got nil function fields.

extbridge.BaseContext now provides the headless half; both call sites
overlay only their UI-specific closures. As a side effect ACP mode gains
previously-missing APIs (state, tree navigation, skills, template
parsing, model resolution) that were nil before. The interactive TUI
keeps its exact SetModel/ReloadExtensions ordering via overrides.

* internal/tools: extract withOAuthRetry and marshalToolResult helpers

ExecuteTool repeated the OAuth-error/re-auth/retry stanza verbatim twice
(sync and task-augmented paths) and the marshal-and-wrap stanza four
times. Both are now single helpers with identical error strings, so a
fix to OAuth retry or error categorization applies everywhere at once.

* internal/ui: extract buildShareFile with defer-based cleanup

handleShareCommand repeated the close/remove/print/return cleanup chain
four times across its temp-file write error paths. File assembly now
lives in buildShareFile with a single deferred cleanup on error.

* cmd: extract flag validation, preference restore, and provider-URL routing from runNormalMode

runNormalMode opened with ~150 lines of policy logic (flag-combination
validation, persisted model/thinking-level preference restoration, and
two subtle --provider-url model-rewrite rules). These are now standalone
functions (validateModeFlags, restorePersistedPreferences,
applyProviderURLRouting) so the routing policy is independently readable
and testable. Behaviour unchanged; ordering preserved.

* fix: address review findings on SDK godoc and nil guard

- pkg/kit: remove internal package paths from exported godoc on
  ParseTemplate and the ToolKind* constants (SDK doc surface must not
  reference internal packages)
- internal/tools: guard marshalToolResult against a nil CallToolResult
  (json.Marshal(nil) succeeds as 'null', then result.IsError panics if
  a client returns nil result with nil error)

Skipped the TreeNode Children deep-copy suggestion: the slice already
comes from TreeManager.GetChildren which returns a fresh copy per call
into a throwaway intermediate, so no internal state is exposed.
2026-06-11 16:13:18 +03:00
Egbert Eich ef072f6e59 Make subagent inherit tools from parent (#51)
While the tool list of the main agent could be controlled by several
options, subagent used to be equipped with all available tools (except
for the subagent tool itself).
With this change the list of tools is taken from the parent, the
subagent tool itself is removed and the remaining tool list is added
to the subagent.

Signed-off-by: Egbert Eich <eich@suse.com>
2026-06-09 16:28:01 +03:00
Ed Zynda 49f8b485be feat(extensions): add OnLLMUsage, SetState, enriched AgentEndEvent (#53) (#54)
* feat(extensions): add OnLLMUsage, SetState, enriched AgentEndEvent (#53)

Three additive primitives to the extension API:

- OnLLMUsage event: per-LLM-call token + cost deltas attributed to the
  specific model/provider used for each round-trip. Derived from the SDK
  StepFinishEvent in the extension bridge. Enables accurate budget
  enforcement between calls instead of only at turn boundaries.

- ctx.SetState / GetState / DeleteState / ListState: session-scoped,
  last-write-wins key-value store backed by a sidecar file
  (<session>.ext-state.json) outside the conversation tree. Reads are
  O(1), writes don't grow the JSONL, and the store is not duplicated on
  fork. State is preserved across hot-reloads.

- Enriched AgentEndEvent: ToolCallCount, ToolNames, LLMCallCount, token
  deltas (input/output/cache-read/cache-write), CostDelta, and
  DurationMs populated by a per-turn aggregator. Existing handlers
  reading only Response/StopReason are unaffected.

Includes unit tests for the state store, LLMUsage registration,
enriched AgentEndEvent, turn aggregator, llmUsageMeta, and sidecar path
derivation. Adds examples/extensions/usage-budget.go demoing all three
primitives together. Documents the additions in README, the docs site
(extensions overview, capabilities, examples), and the kit-extensions
and kit-sdk skill guides.

Fixes #53

* fix(extensions): address review feedback on state store and llmUsageMeta

- Serialize SetState/DeleteState saver invocations through a new saverMu
  so overlapping atomic-rename writes can no longer race on the shared
  .tmp file and persist an older snapshot after a newer one.
- LoadStateFromFile now clears the in-memory store when the sidecar is
  missing or empty, matching the documented "replace … with its
  contents" contract. This makes session-switching safe by preventing
  keys from a prior session leaking into a new one. Tests updated to
  cover both the missing-file and empty-file cases.
- llmUsageMeta now detects Anthropic OAuth credentials and returns
  Cost=0, matching the comment and the existing usage_tracker behavior
  for OAuth users. Mirrors the OAuth detection already used in
  cmd/extension_context.go.
- Document the single-in-flight-turn assumption baked into the
  per-turn aggregator with a clear migration path (per-turn ID) for if
  concurrent turns ever become a supported use case.

* fix(extensions): release saverMu on panic in state store

Extract a runSaver helper that locks saverMu and defers Unlock before
invoking the persistence callback. Without the deferred Unlock, a panic
inside the saver (e.g. disk full mid-write) would leave saverMu held
forever and deadlock the next SetState/DeleteState. Both SetState and
DeleteState now route through the helper. New TestRunner_State_Saver
PanicReleasesSaverMu reproduces the deadlock window with a 2s deadline
and proves the mutex is released after a panic.
2026-06-09 16:18:10 +03:00
Nuno do Carmo febdc530e1 Feat/copilot login (#49)
* feat(auth): add Copilot login

Add experimental GitHub Copilot device login and copilot/* provider support for users with Copilot access but no OpenAI account.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(copilot): use responses for GPT-5

Route Copilot GPT-5 models through the Responses API because gpt-5.5 is not available on /chat/completions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(copilot): honor device flow timing

* docs(copilot): add auth helper docstrings

* fix(auth): address copilot review feedback

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-08 00:21:20 +03:00
Ed Zynda e610bdd2d0 fix(cmd): route prefixed models through custom wire when --provider-url is set
When --provider-url was set with an explicit --model that already carried
a provider prefix (e.g. google/gemma-4-12b served by LM Studio), Kit
honored the prefix and routed through the Google wire protocol instead
of the user-supplied endpoint, producing confusing upstream errors.

- Strip any non-custom provider prefix from --model when --provider-url
  is set, so the request always lands on the OpenAI-compatible custom
  wire pointed at the user's URL.
- Leave behavior unchanged when --provider-url is absent.
- Document the rewrite in www/pages/providers.md.
2026-06-07 22:03:51 +03:00
Ed Zynda 6100e8b3a8 feat(ui): add /retry slash command for resubmitting last user message
- Add PopLastUserMessage() on *App: walks the current tree branch back to
  the parent of the most recent user message, syncs the in-memory store,
  and returns the prompt + image parts for resubmission.
- Register /retry (alias /rt) and wire handleRetryCommand which rebuilds
  the visible ScrollList from the truncated branch before resubmitting
  via Run/RunWithFiles. Mirrors SubmitMsg display path (badges, pending
  prints, stateWorking transition).
- Recovers from transient provider errors (overloaded, timeout) without
  duplicating the user message in context — the failed turn's entries
  become orphaned off-branch rather than being re-sent to the LLM.
- Update help text, AppController interface, and stub controller.
- Add unit tests covering busy/closed/no-session guards, the happy-path
  truncation, and the empty-branch error case.
2026-06-07 18:05:20 +03:00
Ed Zynda 9f125f3400 refactor(ui): standardize all popups on shared PopupList
- Extend PopupList with FullScreen mode, RenderItem callback, and
  external-state setters (SetItems/SetCursor/SetSearch) so any popup
  can reuse the same chrome (border, title, search, scroll, footer).
- Rewrite TreeSelector and SessionSelector as thin PopupList wrappers,
  dropping ~500 lines of duplicated rendering. Selector-specific keys
  (filter cycle, scope/named toggles, delete-confirm) are pre-handled;
  everything else delegates to PopupList.
- Migrate the / and @ autocomplete popups in InputComponent to render
  through PopupList, replacing the bespoke renderer.
- Fix /tree and /fork overflow with deep trees: measure tree-art
  prefix width via lipgloss.Width (handles multi-byte box drawing),
  truncate the prefix from the left with an ellipsis when it would
  push text off the row, and collapse multi-line message content to
  a single line so rows never wrap.
- Fix broken selection highlight in /tree, /fork, /sessions: emit a
  plain string from RenderItem for the cursor row so the outer row
  style paints one continuous fg+bg span instead of being shredded
  by mid-row ANSI resets from inner Render calls.
- Center the cursor in the visible window so context is always shown
  above and below the selection.
2026-06-07 17:45:06 +03:00
Ed Zynda 00eab47218 feat(ui): add /edit slash command with fuzzy file picker
- New /edit (alias /ed) opens $EDITOR on a chosen file via tea.ExecProcess
- Typing '/edit ' activates a fuzzy file popup mirroring the @ trigger:
  reuses GetFileSuggestions (git ls-files), supports directory drill-down,
  excludes MCP resources
- Selecting a file auto-submits and runs $EDITOR ($VISUAL preferred);
  on exit prints 'Edited <path>'
- Manual paths supported (~/, relative, absolute); non-existent paths
  pass through so the editor can create them; directories are rejected
- /help updated with the new command
2026-06-07 17:10:34 +03:00
Ed Zynda 06bf6d087a feat(models): resolve SDK default URLs for all registered providers
- Add sdkDefaultBaseURL map covering the 14 npm SDKs that ship a
  hard-coded baseURL (groq, cerebras, mistral, xai, perplexity,
  togetherai, deepinfra, cohere, v0, aihubmix, venice, merge-gateway,
  openrouter, vercel gateway), so providers whose models.dev entry
  omits the api field still auto-route correctly.
- Extend npmToWireProtocol so these thin OpenAI-compatible wrappers
  route through fantasy's openaicompat provider.
- Add resolveTemplatedAPIURL to substitute ${VAR} placeholders for
  cloudflare-workers-ai, databricks, snowflake-cortex from the env,
  with friendly errors that name the missing vars.
- Wire amazon-bedrock and azure-cognitive-services aliases into the
  existing native handlers; add createGoogleVertexProvider for the
  google-vertex case.
- Expose kit.ResolveProviderBaseURL in the public SDK so embedders
  can introspect the effective endpoint before instantiating a Kit.
- Refresh embedded_models.json from models.dev (5113 -> 5121 models;
  139 providers unchanged).
2026-06-07 14:06:05 +03:00
Ed Zynda fd960921ca refactor: address code audit findings across SDK, cmd, and internals
- Remove deprecated GenerateWithLoopAndStreaming and TreeManager
  AppendFantasyMessage / AddFantasyMessages / GetFantasyMessages to
  close the SDK leakage caused by the kit.TreeManager type alias
- Switch extensionAPI method signatures to local Extension* aliases so
  pkg.go.dev signatures no longer expose internal package names
- Bundle runNormalMode dependencies into a runModeDeps struct, shrinking
  the runNonInteractive and runInteractive call sites from 40+ positional
  args to (ctx, deps)
- Add generic subscribeTyped[E Event] helper and collapse ~30 typed OnXxx
  wrappers in pkg/kit/events.go onto it (public signatures unchanged)
- Extract setupBashPipes / interpretBashExit in internal/core/bash.go to
  deduplicate the buffered and streaming execution paths
- Extract resolveAutoRouteAPIKey and wrapProviderErr helpers in
  internal/models/providers.go and uniformly apply them across every
  createXxxProvider site
- Reimplement internal/extensions/watcher.go as a thin wrapper over the
  general-purpose internal/watcher.ContentWatcher, eliminating ~130 LOC
  of duplicated fsnotify logic while preserving the existing test API
- Add ctx.Err() pre-flight checks in executeRead / Write / Edit / Ls so
  cancellation actually short-circuits pure file-IO tools
2026-06-06 19:22:05 +03:00
Ed Zynda 0b651a8df9 build(deps): update dependencies except fantasy
- bump bubbletea v2.0.6 -> v2.0.7, ultraviolet, acp-go-sdk v0.13.0 -> v0.13.5
- bump indirect deps x/exp, charmtone, go-runewidth
- hold fantasy at v0.25.0 (v0.29.1 requires go 1.26.4)
- add no-op Logout method to acpserver.Agent for new acp.Agent interface
2026-06-04 15:48:07 +03:00
Ed Zynda 7315c1dea7 chore(models): update embedded model database from models.dev
- Refresh internal/models/embedded_models.json with latest data
- Add providers: alibaba-token-plan, anyapi, snowflake-cortex
- 139 providers, 5113 models total
2026-06-04 15:35:43 +03:00
Ed Zynda 0313fa03ad fix(ui): show pasted image previews in input and transcript (#48)
* fix(ui): show pasted image previews in input and transcript

The half-block thumbnail preview added in #47 rendered but was clipped
off the bottom of the screen, and submitted images showed only a text
badge in the conversation history.

- Mark the layout dirty when clipboardImageMsg / thumbnailReadyMsg reach
  the parent, so distributeHeight re-measures the now-taller input region
  instead of keeping a stale height that pushed the preview off-screen
- Render thumbnail previews in the transcript after a user message,
  appended as a verbatim ScrollList item (raw ANSI half-blocks would be
  mangled if folded into the word-wrapped user text block)
- Render transcript previews asynchronously via a tea.Cmd so decode +
  resample never blocks the Bubble Tea event loop
- Add regression tests covering the input layout recompute and the
  transcript preview flow

* fix(ui): anchor transcript image preview to its user message

- Insert the async thumbnail preview directly after the originating user
  message (tracked via anchorID) instead of appending, so a streamed
  assistant reply that lands first no longer pushes the preview out of place
- Make the layout regression test deterministic by forcing a truecolor
  profile, avoiding flakes on low-color CI terminals where the thumbnail
  would render empty
- Add tests for anchored insertion and the unknown-anchor append fallback
2026-06-04 15:30:47 +03:00
Ed Zynda d27022bcfb feat(ui): render half-block thumbnails for attached images (#47)
* feat(ui): render half-block thumbnails for attached images (#46)

- Add internal/ui/imagepreview package: Render() draws low-res
  thumbnails using Unicode half-blocks (▀) + truecolor/256-color SGR,
  which survives tmux/zellij (no graphics protocol)
- Cache a rendered thumbnail per pending clipboard image in the input
  component; render once at attach time, never per frame
- Fall back to the existing [N image(s) attached] text pill when the
  terminal lacks truecolor/256-color support
- Document Ctrl+V paste, Ctrl+U clear, and the preview in the docs
  site and README keyboard shortcuts

Fixes #46

* fix(ui): render image thumbnails off the event loop and cap size

- Render thumbnails asynchronously via a tea.Cmd instead of calling
  the decode + resample path synchronously inside Update(), which
  blocked the Bubble Tea event loop
- Add thumbnailReadyMsg + an imageGen generation counter so async
  results land on the correct pendingImages slot and stale renders
  after a clear/re-attach are discarded
- Guard imagepreview.Render against decompression bombs by checking
  DecodeConfig dimensions against a max before full decode

* fix(ui): skip image preview when input width is too small

- Return 0 from thumbCols when width <= 6 so a full-size thumbnail is
  no longer rendered for tiny or uninitialized (width 0) terminals;
  the caller falls back to the text pill
2026-06-04 14:36:39 +03:00
Ed Zynda ae722d520f fix(models): route auto-discovered providers by wire protocol (#41) (#43)
- replace npmToLLMProvider map with npmToWireProtocol (openai/anthropic/google)
- add createAutoRoutedGoogleProvider so @ai-sdk/google proxies work
  (fixes opencode/gemini-* failing with "no LLM provider mapping")
- strip the genai-injected v1beta segment for proxies whose base URL
  already carries a version (e.g. opencode's /zen/v1)
- preserve openai-compat fallback and clearer error for unroutable providers
- document auto-routing in README and providers docs; update CreateProvider godoc
- add regression tests for wire routing and version-path rewriting

Fixes #41
2026-06-02 15:21:48 +03:00
Ed Zynda 7a04bdfeba feat(kit): isolate viper config per Kit instance + add NewAgent (#42)
* feat(kit): isolate viper config per Kit instance + add NewAgent (#40)

- Give each kit.New()/NewAgent() call an isolated *viper.Viper store so
  multiple Kit instances in one process no longer clobber each other's
  config; runtime mutators (SetModel, SetThinkingLevel) touch only the
  owning instance, making subagent spawning and multi-Kit embedding
  race-free
- Thread the per-instance store through internal/config, internal/models
  (ProviderConfig.ConfigStore), internal/kitsetup, and the extension
  runner, with a nil -> process-global fallback so the CLI is unaffected
- Share the global store when Options.CLI != nil to preserve cobra flag
  bindings (also opted in for internal/acpserver)
- Remove viperInitMu; preserve the tri-state IsSet precedence contract
  and sdkDefaultMaxTokens floor
- Add ergonomic NewAgent + functional options (WithModel, WithStreaming,
  Ephemeral, etc.); NewAgent defaults streaming on, opt out via
  WithStreaming(false). New(ctx, *Options) behavior is unchanged
- Add config-isolation regression test and NewAgent/option coverage;
  document NewAgent and per-instance isolation in README

Fixes #40

* docs(sdk): document NewAgent options and per-instance config isolation

- Add "Functional options (NewAgent)" and "Per-instance config isolation"
  sections to the docs site SDK overview, with an options table and a
  "when to use which" constructor comparison
- Cross-reference NewAgent from the SDK options page and correct the now
  per-instance ProviderAPIKey precedence wording
- Document NewAgent + With* helpers and config isolation in pkg/kit/README
  and list NewAgent/Option in the API reference
- Show the NewAgent constructor in the SDK examples getting-started snippet

* fix(kit): correct config loading and isolate ACP sessions

- Isolate each ACP session's config store instead of sharing the global
  viper, preventing per-session SetModel/SetThinkingLevel races; seed the
  root-command flag values (model, thinking-level, provider URL/key) so
  `kit acp -m <model>` is still honored
- Run initConfig for isolated SDK stores by gating on opts.CLI instead of
  v.GetString("model"), which setSDKDefaults always populates and thus
  skipped .kit.yml / KIT_* loading for SDK callers
- Configure KIT_* env overrides unconditionally in initConfig so passing an
  explicit config file no longer disables environment variable support
- Wrap config unmarshal/validate errors with %w to preserve the error chain

* fix(kit): make Options.Streaming a *bool to honor unset

- Change Options.Streaming from bool to *bool so a zero-valued Options no
  longer forces stream=false; New only sets the key when non-nil, letting
  streaming resolve through the precedence chain (env -> config -> default
  true). This also fixes the CLI path, which never set the field
- Mirror the existing sampling-parameter pointer pattern instead of adding
  a separate StreamingSet sentinel, keeping Options internally consistent
- Update WithStreaming/NewAgent, subagent, and ACP callers to the pointer
  form; add regression tests for the nil-default and explicit opt-out paths
- Update SDK docs (README, pkg/kit/README, options page) with the ptrBool
  helper and *bool semantics

* fix(kit): inherit parent provider config in subagents

- Copy the parent's effective provider/runtime config (API key, URL,
  TLS, thinking level, max-tokens, samplers) onto child Options in
  Kit.Subagent. After the per-instance viper isolation, the child's
  isolated store only re-loaded .kit.yml / KIT_*, silently dropping
  config the parent set via programmatic Options or runtime setters
  like SetThinkingLevel
- Preserve the IsSet tri-state for max-tokens and samplers so per-model
  defaults still apply on the child when the parent left them unset
- Add TestInheritProviderConfig covering propagation, unset keys, and
  nil-safety
2026-06-02 14:41:35 +03:00
Sai Karthik 7e4708f511 docs(config): add example support of custom headers for mcp servers (#39)
docs(config): implement suggested improvements
2026-06-02 14:12:01 +03:00
Sai Karthik 1e12102b92 CLI & Config changes to support disabling core tools (#35)
* feat(core): expose no-core-tools via CLI flag and config file

Allow users to disable all built-in core tools (bash, read, write,
edit, grep, find, ls, subagent) without recompiling, using a CLI flag,
environment variable, or .kit.yml config key.

Changes
-------
cmd/root.go
  - Declare noCoreToolsFlag bool alongside noExtensionsFlag.
  - Register --no-core-tools persistent flag with a descriptive help string
    listing the affected tools.
  - Bind the flag to viper key "no-core-tools" so the config file and
    KIT_NO_CORE_TOOLS env var also work (viper's standard precedence:
    CLI flag > env var > config file > default).
  - Set kitOpts.DisableCoreTools = viper.GetBool("no-core-tools") when
    assembling the Options struct in runNormalMode.

pkg/kit/kit.go
  - Add disableCoreTools local variable inside the viperInitMu-protected
    snapshot block, mirroring the noExtensions pattern exactly.
  - Resolve it as opts.DisableCoreTools || viper.GetBool("no-core-tools")
    so the SDK option and the viper key are both respected (OR semantics:
    either source can enable the flag).
  - Pass the resolved disableCoreTools into kitsetup.AgentSetupOptions
    instead of the raw opts.DisableCoreTools, completing the chain.

Usage
-----
  # CLI flag
  kit --no-core-tools

  # Environment variable
  KIT_NO_CORE_TOOLS=true kit

  # .kit.yml config file
  no-core-tools: true

  # SDK (unchanged, was already supported)
  kit.New(ctx, &kit.Options{DisableCoreTools: true})

The downstream path (kitsetup → agent.AgentConfig.DisableCoreTools →
agent.NewAgent nil-tool branch) was already in place and required no
changes.

* docs(readme): document no-core-tools flag, config key, and env var

Update three locations in README.md to reflect the new no-core-tools
control surface introduced in the previous commit:

CLI Reference → Global Flags
  Add --no-core-tools under the Extensions and tools section alongside
  --no-extensions, with a description listing the affected tools.

Configuration → Basic Configuration
  Add no-core-tools: false to the example .kit.yml block so users know
  it is a valid config file key (equivalent to the CLI flag and env var).

Go SDK → With Options
  Expand the DisableCoreTools comment to note that the same behaviour is
  also available via --no-core-tools, KIT_NO_CORE_TOOLS, and
  no-core-tools: true in .kit.yml, making the cross-surface relationship
  explicit for SDK consumers.

* style: gofmt cmd/root.go
2026-05-29 20:33:05 +03:00
Ed Zynda ab2a77c95e feat(sdk): runtime skills and context-file management (#36) (#37)
* feat(sdk): runtime skills and context-file management (#36)

Let SDK consumers add, remove, and replace skills and AGENTS.md-style
context files after Kit construction. Every mutation recomposes the
system prompt and applies it to the agent so the next turn picks up
the new instructions without restarting Kit.

- AddSkill / LoadAndAddSkill / RemoveSkill / SetSkills on *kit.Kit
- AddContextFile / AddContextFileContent / LoadAndAddContextFile /
  RemoveContextFile / SetContextFiles on *kit.Kit
- RefreshSystemPrompt to force a manual recomposition
- agent.SetSystemPrompt / GetSystemPrompt on the internal agent so
  the composed prompt rebuilds the fantasy agent on the next call
- Per-instance runtimeMu guards skills/contextFiles; GetSkills and
  GetContextFiles return defensive snapshots safe for concurrent use
- Capture the resolved basePrompt during New so recomposition keeps
  per-model overrides and --system-prompt file resolution intact
- Skills dedupe by Name; context files dedupe by Path (opaque ID,
  not required to be a real filesystem path)

Tests cover add/remove/set/replace semantics, validation errors,
disk loading round-trips, prompt composition, and an 8-goroutine
race-stress sweep (go test -race clean).

Docs: pkg/kit/README, root README Go SDK section, www sdk/overview
"Runtime skills and context files" section, www sdk/options callout
cross-referencing the new API.

Fixes #36

* fix(agent): synchronize SetSystemPrompt against concurrent rebuilds

- add promptMu to Agent guarding systemPrompt writes and the fantasy
  agent rebuild, fixing a data race when Kit.applyComposedSystemPrompt
  is invoked concurrently
- read systemPrompt under the same lock in GetSystemPrompt
- update the thread-safety stress test to use a non-nil agent so the
  SetSystemPrompt path is actually exercised under -race
2026-05-29 18:44:12 +03:00
Ed Zynda 1e78153b50 ci(release): revert goreleaser parallelism workaround
No longer needed now that fantasy is pinned to v0.25.0 — build memory is
back to ~1GB, well under the runner's 7GB limit.
2026-05-29 17:52:29 +03:00
Ed Zynda a613361969 fix(deps): pin fantasy to v0.25.0 to avoid CLDR compile-memory blowup
fantasy v0.25.1+ bumps kaptinlin/jsonschema to v0.7.14+, which transitively
pulls in github.com/agentable/go-intl. Its internal/cldr/displaynames
package contains a ~143k-line / 5.4MB generated CLDR map literal that
compiles at ~6.7GB RSS, OOM-killing release builds on 7GB GitHub runners.

Pinning fantasy v0.25.0 (jsonschema v0.7.13, go-i18n v0.4.5,
messageformat-go v0.6.0) removes go-intl from the build graph entirely and
restores clean-build peak RSS from ~6.6GB back to ~1GB. Upstream issue filed
against charmbracelet/fantasy.
2026-05-29 17:44:34 +03:00
Ed Zynda 67722b0c24 ci(release): limit goreleaser parallelism to avoid OOM on runner
A single clean cross-build peaks at ~7GB RSS (internal/extensions yaegi
symbol table). goreleaser builds targets in parallel by default, which
exhausts the 7GB ubuntu-latest runner and OOM-kills the build with no
error output. Force --parallelism 1 and cap go compiler with GOFLAGS=-p=2.
2026-05-29 16:42:28 +03:00
Ed Zynda 1a2f6da40f chore(models): refresh embedded models database from models.dev
- Bump provider count from 131 to 136
- Bump model count from 4817 to 4965
2026-05-29 15:09:26 +03:00
Ed Zynda 747f5be099 build(deps): bump all dependencies to latest
- fantasy v0.25.0 -> v0.27.0
- chroma v2.24.1 -> v2.26.1
- mcp-go v0.54.0 -> v0.54.1
- ultraviolet, charmbracelet/x snapshots refreshed
- aws-sdk-go-v2 family, smithy-go v1.25.1 -> v1.26.0
- opentelemetry v1.43.0 -> v1.44.0 (+ otelhttp/otelgrpc v0.69.0)
- google.golang.org/api v0.279.0 -> v0.282.0, genai v1.57.0 -> v1.58.0
- kaptinlin/jsonschema, jsonpointer, messageformat bumps
- golang.org/x/{crypto,net,sys,exp} updates
2026-05-29 11:57:20 +03:00
Ed Zynda d7c4565999 refactor: remove dead code, fix SDK leakage, deduplicate helpers
- Remove unused SetOpenAICredentials/validateOpenAIAPIKey (internal/auth)
- Remove unused SudoPasswordRequiredMetadata/IsSudoPasswordRequiredResult
  (internal/core)
- Add Extension* type aliases in pkg/kit/extension_api.go so the public
  ExtensionAPI interface no longer exposes internal/extensions types
- Extract bridgeObserve generic helper and llmToContextMessages /
  contextMessagesToLLM in pkg/kit/extensions_bridge.go (~150 lines saved)
- Extract parseHeaders and buildOAuthConfig in connection_pool.go to
  deduplicate SSE/Streamable client construction (~60 lines saved)
- Eliminate redundant second buildInteractiveExtensionContext call in
  cmd/root.go; swap print closures on the same context instead
- Replace 'Fantasy' with 'agent' in internal comment (pkg/kit/kit.go)
2026-05-25 13:30:22 +03:00
Ed Zynda bd24f3315c fix(agent): track tool call args per ToolCallID for parallel calls (#33) (#34)
Previously GenerateWithCallbacks stored the most recent tool call's args
in a single shared variable, which got clobbered when a provider emitted
multiple tool_use blocks in a single step. Every OnToolResult callback
then received the args of the last OnToolCall, regardless of which call
it was actually resolving — breaking any downstream UI, log, or trace
that derived its description from the toolArgs parameter.

- Replace the shared currentToolArgs with a map keyed by ToolCallID,
  guarded by a sync.Mutex in case the streaming layer dispatches
  callbacks from multiple goroutines.
- Delete each entry in OnToolResult so the map cannot accumulate
  across steps.
- Add a regression test driving the streaming wrapper with a fake
  fantasy.Agent that emits two parallel tool calls before either
  result, asserting each callback sees its own args.

Fixes #33
2026-05-20 10:37:46 +03:00
Ed Zynda 592f8dc84f chore(models): refresh embedded models.dev snapshot
- Sync internal/models/embedded_models.json from https://models.dev/api.json
- Providers: 114 → 131 (+17)
- Models: 4276 → 4817 (+541)
2026-05-19 15:11:01 +03:00
Ed Zynda 66c4a1eb15 build(deps): bump all dependencies and go directive to 1.26.3
- charm.land/fantasy v0.23.0 -> v0.25.0
- charmbracelet/ultraviolet -> 20260511
- coder/acp-go-sdk v0.12.2 -> v0.13.0
- mark3labs/mcp-go v0.51.0 -> v0.54.0
- kaptinlin/{go-i18n,jsonpointer,jsonschema,messageformat-go} bumps
- golang.org/x/{crypto,net,sys,term,text} minor bumps
- google.golang.org/{api,genai,genproto,grpc} bumps
- charmbracelet/x/exp/{charmtone,slice}, tidwall/gjson, others
- go directive bumped to 1.26.3 (required by fantasy v0.25.0)

No code changes required; build, vet, and race tests all pass.
2026-05-19 13:24:53 +03:00
Ed Zynda 5104477631 perf(session): parallelize session list extraction
Open the /resume session picker faster by extracting per-file metadata
across a GOMAXPROCS-sized worker pool instead of sequentially. Each
extractSessionInfo call is I/O + JSON-parse bound and independent, so
wall time drops roughly proportionally to core count — meaningful for
users with many sessions, where ListSessions + ListAllSessions ran
back-to-back on the UI goroutine before the picker rendered.
2026-05-16 16:19:38 +03:00
Ed Zynda 394a4676a1 fix(app): deliver trailing widget update so layout resets after removal
- Switch NotifyWidgetUpdate from leading-only to leading+trailing edge
  coalescing so a rapid SetWidget→RemoveWidget pair (e.g. emitted by
  subagent-monitor on SubagentEnd) is never silently dropped.
- Without the trailing send the TUI keeps the pre-removal widget
  height, leaving empty rows below the status bar until some other
  event re-renders the layout.
2026-05-16 14:07:58 +03:00
Ed Zynda 30f2bc243d fix(ui): correct mouse selection drift with extension widgets
- Match View() and getItemAndLineAtY() row counts for empty items so
  streaming-reasoning placeholders no longer offset hit-testing by one
  row each (exposed when extension widgets like subagent-monitor shrink
  the scrollback).
- Honor IsLineInRange's endCol=-1 'to end of line' sentinel in
  HighlightLine and ExtractText so the start row of a multi-line drag
  actually renders highlighted and is included in clipboard copies.
- Add regression tests for both invariants in scrolllist and selection.
2026-05-16 13:48:51 +03:00
Ed Zynda 922e246098 feat(prompts): auto-reload prompts and extensions from XDG config
- Add prompts.GlobalDir() resolving $XDG_CONFIG_HOME/kit/prompts/
  (default ~/.config/kit/prompts/) so prompt templates live alongside
  extensions and skills under the same XDG-aligned root.
- LoadAll now discovers templates from both the legacy ~/.kit/prompts/
  and the XDG location; existing legacy paths keep precedence.
- Include GlobalDir() in the prompts/skills file watcher so edits
  under ~/.config/kit/prompts/ hot-reload automatically.
- Surface a visible 'Extensions reloaded.' (or error) message when
  the extension watcher fires, matching /reload-ext feedback.
- Restore examples/extensions/subagent-monitor.go alongside its test
  and update the test load path; previous move left the test broken.
2026-05-15 14:31:51 +03:00
Ed Zynda 32b6376515 chore: move go-edit-lint extension to global scope
- Remove .kit/extensions/go-edit-lint.go from the repo since the
  extension is now installed under ~/.config/kit/extensions/ for
  per-user use across all projects.
2026-05-15 14:18:57 +03:00
Ed Zynda cf194ff89a feat(ui): list loaded extensions in startup banner
- Add ExtensionInfo type and Loaded() method to the public ExtensionAPI
  so SDK consumers can inspect which extensions are active.
- Introduce ui.ExtensionItem and thread ExtensionItems/GetExtensionItems
  through AppModelOptions, mirroring the existing SkillItem pattern.
- Render an [Extensions] row in AddStartupMessageToScrollList showing
  the filename of each loaded extension (with a (N tools) suffix when
  extensions register tools). Falls back to tool count only when items
  are unavailable, and is omitted entirely when no extensions load.
- Refresh the list on /reload-ext via a new refreshExtensionItems hook
  so the banner stays accurate across hot-reloads.
- Add buildExtensionItems helper in cmd/root.go that strips .go and
  resolves subdirectory extensions to their parent dir name, tagging
  each as project or user scope based on cwd.
2026-05-15 14:08:42 +03:00
Ed Zynda 03006425fa cleanup 2026-05-15 13:55:32 +03:00
Ed Zynda a322dfc59a fix(ui): eliminate mouse copy-selection drift during streaming
- Lock viewport scroll while a drag-select is active so highlighted
  content stays under the cursor (SetItems, appendStreamingChunk,
  MouseWheelDown all now honor IsMouseDown).
- HandleMouseDrag defensively clears autoScroll on every update so a
  racy re-enable can't shift the row mid-drag.
- Recompute scrollback yOffset/viewport height on each mouse event
  via currentScrollbackBounds() instead of relying on stale values
  cached during the previous View() pass.
- Account for canceling/ctrlCPressedOnce warning rows in
  distributeHeight and mark layoutDirty when those flags toggle so
  the height budget and mouse origin stay in sync.
- Add ScrollList regression tests covering the three invariants.
2026-05-15 13:30:57 +03:00
Ed Zynda b1387d837e feat(ui): add /copy slash command to copy last message
- Register /copy (alias /cp) in the System command category
- Walk the scrollback to find the last user/assistant/reasoning
  message, skipping transient system messages
- Reuse internal/ui/clipboard.CopyToClipboard for OSC 52 + native
  clipboard support (works over SSH)
- Document the command in /help
2026-05-15 13:06:35 +03:00
Ed Zynda f561f4cfd9 fix(session): order kept messages before post-compact branch in BuildContext
After /compact, BuildContext emitted [summary, post-compact, kept]
which placed an older kept user/assistant turn after the latest
post-compaction turn. This broke user/assistant alternation and caused
the model to respond as if the post-compaction turn never happened on
the next user message.

- Emit kept messages chronologically before post-compaction messages
- Mirror the same order in GetContextEntryIDs so cut-point to entry-ID
  mapping stays aligned across repeat compactions
- Update TestCompactionWithNewMessagesAfterCompaction to assert the
  correct chronological order
2026-05-14 20:42:20 +03:00
Ed Zynda 64caed57d4 fix(sdk): stop leaking fantasy types through pkg/kit.AgentConfig (#30) (#32)
* fix(sdk): stop leaking fantasy types through pkg/kit.AgentConfig (#30)

Replace the alias-based AgentConfig and handler types with SDK-owned
structs and function types. CoreTools / ExtraTools / ToolWrapper now
accept []kit.Tool, and the handler types (ToolCallHandler,
ToolExecutionHandler, ToolResultHandler, ResponseHandler,
StreamingResponseHandler, ToolCallContentHandler) plus SpinnerFunc are
declared in pkg/kit/ with signatures that reference only SDK types.

Consumers no longer need to import charm.land/fantasy to populate an
AgentConfig or assign a handler. go doc pkg/kit AgentConfig output no
longer mentions fantasy.*.

- Add unexported (*AgentConfig).toInternal() to convert at the SDK
  boundary; Tool is still an alias for the underlying tool type, so
  slice and function fields convert without allocation.
- Add agent_config_internal_test.go covering nil receiver, scalar
  fields, tool slices, ToolWrapper invocation, OnMCPServerLoaded, and
  auth/token-factory wiring.
- Add types_test.go cases that populate AgentConfig and SpinnerFunc
  without importing fantasy -- the file compiling is the regression
  proof for the leak.
- Update pkg/kit/README.md Re-exported Types section to record that
  AgentConfig and the handler types are now Kit-owned.

Fixes #30

* fix(sdk): add DebugLogger and MCPTaskConfig to kit.AgentConfig (#30)

The first revision of the SDK-owned AgentConfig dropped two fields that
internal/agent.AgentConfig carried: DebugLogger (tools.DebugLogger) and
MCPTaskConfig (tools.MCPTaskConfig). Restore them with SDK-owned
equivalents and wire them through toInternal().

- Add kit.DebugLogger interface (LogDebug / IsDebugEnabled) mirroring
  tools.DebugLogger. Interface-to-interface assignment is automatic
  because the method sets match.
- Add kit.MCPTaskConfig struct mirroring tools.MCPTaskConfig with SDK
  types (MCPTaskMode, MCPTaskProgressHandler) and a toToolsConfig()
  helper that converts at the SDK boundary.
- Wire both new fields in (*AgentConfig).toInternal().
- Extend agent_config_internal_test.go with cases for both fields.
- Document the additions in pkg/kit/README.md.
2026-05-13 21:10:28 +03:00
Ed Zynda 975c30a773 fix(mcp): surface MCP tool failures as soft errors, not critical aborts (#31)
The MCP adapter previously wrapped any error returned by MCPToolManager.ExecuteTool
into a Go error returned from the fantasy.AgentTool.Run interface. The fantasy
agent loop treats those as critical errors and aborts the entire turn —
discarding all prior reasoning, tool calls, and results.

In practice that meant a single misbehaved MCP server returning a JSON-RPC
"-32602 Invalid params" (e.g. a Zod schema mismatch on the server's input
validation) would kill an in-progress turn after the model had already done
dozens of seconds of useful work, with no way for the model to see the
validation message and self-correct.

This mismatched the contract that native Kit tools follow: native tools
return errors via kit.ErrorResult(...), which become soft tool-result errors
that the model reads and can act on (retry with corrected args, try a
different tool, give up gracefully).

Make the MCP path behave the same way:

  - JSON-RPC protocol errors, transport failures, and server-side schema
    rejections are now returned as fantasy.NewTextErrorResponse(...) with
    err == nil, so the agent loop continues and the model sees the failure
    in-band as a tool result it can reason about.
  - Context cancellation (ctx.Err() != nil) remains a critical error so
    callers can abort turns deterministically. This is the only case where
    bubbling up is correct — the caller intentionally tore the turn down
    and the agent must not keep spinning.
  - Server-side soft errors (CallToolResult{ isError: true }) and the
    happy path are unchanged.

The agent loop's MaxSteps cap already bounds the worst case for a
permanently broken MCP server, so there is no risk of unbounded retries.

Side effect: extracted a tiny mcpExecutor interface for the one method the
adapter uses (ExecuteTool), purely so the adapter is unit-testable in
isolation without standing up a full MCPToolManager + connection pool.

Behavior change note for downstream consumers: code that relied on
host.PromptResult / Stream returning a Go error containing
"mcp tool execution failed" will no longer see those errors — the
failure information is now in the assistant's final response (or in the
OnAfterToolResult / OnToolResult hooks, where IsError will be true).
Context cancellation continues to surface as an error from those calls
as before.

Co-authored-by: space_cowboy <space_cowboy@mark3labs.com>
2026-05-13 20:12:31 +03:00
Ed Zynda 35b9360d64 feat(ui): autocomplete /skill:<name> slash commands
- register loaded skills into the input autocomplete under category
  "Skills" with HasArgs so Enter populates "/skill:name " instead of
  auto-submitting, leaving room for trailing args
- prefix descriptions with [project] or [user] to disambiguate
  colliding skill names across sources
- extend refreshSkillItems to prune & re-add Skills entries on
  ContentReloadEvent, matching the pattern used for prompt templates
  and MCP prompts
- add Description field to ui.SkillItem and populate it from
  kit.Skill.Description in both initial build and hot-reload paths
2026-05-13 15:35:07 +03:00
Ed Zynda 1b8373e133 cleanup 2026-05-12 13:30:30 +03:00
Ed Zynda 1a5e4ce7c5 Merge pull request #29 from mark3labs/fix/27-queued-messages-after-compact
test(app): cover steer-drain branch of releaseBusyAfterCompact
2026-05-08 13:11:45 +03:00
Ed Zynda 8823977612 test(app): cover steer-drain branch of releaseBusyAfterCompact
- Add unexported steerDrainFn test seam on App so unit tests can
  inject fake steer items without standing up a full *kit.Kit
  (Options.Kit is a concrete struct, not an interface).
- releaseBusyAfterCompact now prefers the seam over Kit.DrainSteer
  via a small switch; production behaviour is unchanged when the
  field is nil.
- Add TestReleaseBusyAfterCompact_splicesSteerAheadOfQueue, which
  pre-populates both fake steer items and ordinary queue prompts,
  invokes releaseBusyAfterCompact, and asserts the first dispatched
  prompt is the steer item — proving steer messages retain 'act now'
  priority and that drainQueue is actually launched (the bug from
  #27).
2026-05-08 12:18:52 +03:00
Ed Zynda 24e2ea111c Merge pull request #28 from mark3labs/fix/27-queued-messages-after-compact
fix(app): flush queued messages after /compact completes (#27)
2026-05-08 12:16:28 +03:00
Ed Zynda 31ea80ec4f fix(app): flush queued messages after /compact completes (#27)
- Add releaseBusyAfterCompact() shared deferred tail used by both
  CompactConversation and CompactAsync. It drains the SDK steer
  channel, splices steer items in front of any queued prompts, and
  hands off to drainQueue so messages received during compaction
  are dispatched automatically once compaction finishes.
- Previously, busy was simply cleared on completion and the queue
  sat idle until the user submitted another prompt, which then
  flushed everything together.
- Honor the closed flag so a teardown during compaction discards
  pending items instead of spawning drainQueue against a torn-down
  App.
- Add regression tests covering the queued-flush, idle-empty, and
  closed-during-compact paths.

Fixes #27
2026-05-08 11:30:26 +03:00
Ed Zynda 99f2680c2e Merge pull request #26 from mark3labs/fix/25-system-prompt-file-path
fix(kit): resolve system-prompt file path before PromptBuilder (#25)
2026-05-08 10:54:09 +03:00
Ed Zynda da7e05eb87 fix(cmd): nil-guard CLI when emitting system-prompt notice in quiet mode
SetupCLIForNonInteractive returns nil when --quiet is active, matching
the pre-existing nil checks elsewhere in the same block (e.g. the
buffered debug-message branch). Without this guard the new
'System Prompt loaded' notice panicked on quiet, non-interactive runs.

Discovered via tmux smoke test of the #25 fix.
2026-05-08 10:44:01 +03:00
Ed Zynda a95714a22d fix(kit): resolve system-prompt file path before PromptBuilder (#25)
When system-prompt was a file path (via --system-prompt, config entry,
or SDK Options.SystemPrompt), the path string itself was used as the
base prompt because config.LoadSystemPrompt only ran later in
BuildProviderConfig — by which point viper had been overwritten with
the path-augmented composed text. The LLM received the path instead of
the prompt contents.

- Call config.LoadSystemPrompt on the raw viper value in New() before
  PromptBuilder composes runtime context (AGENTS.md / skills / date).
- Add HasCustomSystemPrompt() and GetSystemPromptSource() so SDK callers
  can inspect prompt state without reaching into viper.
- Display 'System Prompt loaded: <source>' at startup in CLI and TUI
  modes, paralleling the per-server 'MCP server loaded' notice.
- Add regression tests covering both file-path and inline prompt paths.

Fixes #25
2026-05-08 10:39:14 +03:00
Ed Zynda c4a2b0f1a3 Merge pull request #24 from mark3labs/audit-cleanup
refactor: remove dead code and consolidate duplicated extension wiring
2026-05-07 17:46:49 +03:00
Ed Zynda 2016570e2d test: add docstrings to rewritten tests and use t.Setenv
Addresses two CodeRabbit feedback items on PR #24:

* Docstring coverage warning (was 57.14%, threshold 80%): adds godoc
  comments to the four test functions added or substantially rewritten
  in this PR — TestLoadAndSaveManifest, TestAddAndRemoveFromManifest,
  TestFindInManifest, TestHighlightFileTokensInjectsANSI.
* Quick-win nitpick: replaces the manual os.Setenv/os.Unsetenv +
  defer pattern in TestFindInManifest with t.Setenv, which restores
  the env var automatically on cleanup even on panic or t.Fatal.

go test -race ./... still passes.
2026-05-07 13:16:03 +03:00
Ed Zynda d557f4b870 fix(cmd): wrap bare fn refs in extensions.Context as closures
Per AGENTS.md 'Yaegi function field bug', named function/method
references assigned to extensions.Context fields return zero values
across the interpreter boundary. The two SetContext literals in
runNormalMode (now consolidated in buildInteractiveExtensionContext)
inherited 9 bare references that need to be anonymous closure literals:

  PrintBlock, GetChildren, GetAvailableSkills, ParseTemplate,
  RenderTemplate, ParseArguments, SimpleParseArguments,
  ResolveModelChain, CheckModelAvailable

Each is now wrapped as 'func(args) ret { return <orig>(args) }'.
Behaviour unchanged in regular Go; Yaegi extensions that consume these
fields will now see callable closures instead of zero values.

Verified with go test -race ./...
2026-05-07 13:00:06 +03:00
Ed Zynda 65054fe3db gofmt trailing-blank-line cleanup after dead-code removal 2026-05-07 12:34:29 +03:00
Ed Zynda 97d2246375 drop orphan testTypography helper from render tests
The TestUserBlockHighlightsFileTokens test was rewritten to call
HighlightFileTokens directly (UserBlock was deleted in the dead-code
sweep). That left testTypography with no callers, so staticcheck U1000
flagged it.
2026-05-07 12:31:55 +03:00
Ed Zynda 1e12505741 remove unused style.BaseStyle helper 2026-05-07 12:29:59 +03:00
Ed Zynda 6755597c9b extract buildInteractiveExtensionContext helper
The previous runNormalMode contained two nearly-identical 400-line
extensions.Context literal expressions:

  * the startup-time literal (cmd/root.go:853-1307) that buffered
    Print* calls into startupExtensionMessages
  * the runtime literal (cmd/root.go:1311-1605) that routed Print*
    through appInstance.PrintFromExtension

Every other field — Compact, SendMultimodalMessage, the four prompt
factories, all 25+ data-access fields, all four bridge phases — was
duplicated byte-for-byte. Maintainers had to remember to update both
copies whenever an extension Context field was added.

cmd/root.go is now 1463 lines (was 2225). The new helper lives in
cmd/extension_context.go (455 lines, mostly the closures verbatim) and
returns an extensions.Context with every field populated except
Print/PrintInfo/PrintError, which each call site sets afterwards to
match its phase. This preserves AGENTS.md's 'function field bug'
guarantee — all assignments remain anonymous closure literals.

Output of 'kit --version' / 'kit --help' unchanged. Full test suite
passes.
2026-05-07 12:28:18 +03:00
Ed Zynda 45689cb30d extract duplicated subagent + event conversion to internal/extbridge
The same ~40-line block — building a kit.SubagentConfig, wrapping
OnEvent through sdkEventToSubagentEvent, calling kitInstance.Subagent,
and translating the SDK result into extensions.SubagentResult — was
copy-pasted three times:

  * cmd/root.go (interactive TUI Context, line 1148)
  * cmd/root.go (post-SessionStart runtime Context, line 1446)
  * internal/acpserver/session.go (ACP server Context, line 154)

A separate sdkEventToSubagentEvent function was duplicated byte-for-byte
between cmd/root.go and internal/acpserver/session.go.

Both are now consolidated in a new internal/extbridge package which is
the only module-internal home that can legitimately import both
pkg/kit/ (the public SDK) and internal/extensions/. cmd/ and
internal/acpserver/ both import it, so SDK-event-to-extension-event
schema changes only have one site to update.

Also fixes pkg/kit/events.go godoc comment that named the underlying
LLM library, per AGENTS.md 'No Dependency Name Leakage' rule for
exported SDK symbols.

go test -race ./... passes.
2026-05-07 12:23:15 +03:00
Ed Zynda 78570d4188 remove dead code identified by audit
Removes ~600 lines of unreferenced code surfaced by deadcode + manual
audit (none of it reachable from production code paths or test setup):

- internal/models/pool.go: ProviderPool was never wired into kitsetup
  or the agent; the global pool singleton had zero callers.
- internal/ui/debug_logger.go: CLIDebugLogger was unreachable; debug
  routing goes through internal/tools/buffered_logger.go instead.
- internal/ui/tool_approval_input.go: tea.Model never instantiated;
  approvals are handled inline in model.go.
- internal/ui/cli.go: DisplayAssistantMessage / DisplayCancellation /
  GetDebugLogger had zero callers (the *WithModel variant is what
  event_handler.go uses).
- internal/ui/style/enhanced.go: Style{Card,Header,Subheader,Muted,
  Success,Error,Warning,Info} + Create{Separator,ProgressBar} — none
  used. CreateBadge stays (used by model.go).
- internal/ui/style/themes.go: RefreshThemeRegistry — never called.
- internal/ui/block_renderer.go: With{FullWidth,MarginTop,Padding{Left,
  Right},Background,Foreground,Width} — option helpers nobody calls.
- internal/ui/render/blocks.go: UserBlock, ToolBlock — replaced by
  inline rendering elsewhere; the test for UserBlock was rewritten to
  directly exercise HighlightFileTokens (which is what the test really
  cared about).
- internal/ui/commands/commands.go: GetAllCommandNames — no callers.
- internal/ui/message_items.go: NewTextMessageItem,
  NewSystemMessageItem + the entire SystemMessageItem type — model.go
  uses NewStyledMessageItem instead.
- internal/prompts/loader.go: Deduplicate — the loader does dedup
  internally; standalone helper was unused.
- internal/models/cache_options.go: mergeProviderOptions + its
  test-only consumer.
- internal/extensions/installer.go: Installer.GetInstalledPackages —
  intended for a 'kit ext list' command that was never built.
- internal/extensions/manifest.go: saveManifestToScope,
  saveManifestToPath, GetGlobalManifest, GetProjectManifest,
  addEntryToManifest, removeEntryFromManifest — package-level
  duplicates of *Installer methods. Tests rewritten to exercise the
  live Installer methods instead, which fixes a latent path-resolution
  inconsistency between manifestPathForScope and Installer.manifestPath
  (the former hard-coded paths, the latter respects projectGitRoot).
- internal/extensions/subagent.go: SpawnSubagent + helpers
  (generateSubagentID, findKitBinary, subagentJSONOutput). The
  subprocess-spawn implementation is unreachable; production code
  routes through kit.Kit.Subagent (in-process). Types
  (SubagentConfig/Result/Handle/etc.) and the SubagentHandle methods
  remain because they are exposed to extensions via Yaegi symbols and
  the Context.SpawnSubagent field.
- cmd/root.go: LoadConfigWithEnvSubstitution — one-line wrapper around
  kit.LoadConfigWithEnvSubstitution with zero callers.

go test -race ./... passes.
2026-05-07 12:20:08 +03:00
Ed Zynda 7cf38b37ee Merge pull request #23 from mark3labs/fix/18-windows-session-dir-colon
fix(session): strip illegal characters from windows session dir (#18)
2026-05-07 11:13:34 +03:00
Ed Zynda 4ef57eec4e docs(session): correct DefaultSessionDir convention comment
- Stale comment showed ~/.kit/sessions/--<cwd-path>--/ which does not
  match the actual encoding (no leading/trailing dashes)
- Update to reflect the real format and point to encodeCwdForDir for
  full rules
2026-05-05 14:54:20 +03:00
Ed Zynda cbd828e190 fix(session): strip illegal characters from windows session dir (#18)
- Encode cwd via new encodeCwdForDir helper that handles both `/`
  and `\` separators and strips characters illegal in Windows
  directory names (`: < > " | ? *`)
- Fixes session creation on Windows where the drive-letter colon
  produced names like `C:--test` and caused mkdir to fail
- Add regression tests covering Unix paths, Windows drive roots,
  secondary drives, mixed separators, and other illegal chars

Fixes #18
2026-05-05 14:46:36 +03:00
Ed Zynda d304805106 Merge pull request #22 from mark3labs/feat/21-mcp-tasks-mvp
feat(mcp): add MCP Tasks support at the SDK level (#21)
2026-05-04 19:30:15 +03:00
Ed Zynda 6e36053856 fix(mcp): validate tasksMode and inherit task options in Subagent (#21)
Address two review findings on the MCP Tasks PR.

- Config.Validate() now rejects unknown tasksMode values with a clear
  error naming the server and bad value. Without this a typo (e.g.
  "alwasy") was silently downgraded to "auto" by the runtime parser.
- Kit.Subagent() now propagates the parent's six MCP task options
  (mode map, timeout, TTL, poll interval, max poll interval, progress
  callback) onto the child via a new inheritMCPTaskOptions helper.
  Without this, child subagents always saw default polling and no
  progress feedback regardless of parent configuration.

The propagation logic lives in a helper so the test exercises the real
code path instead of duplicating it; future task fields only need to be
added in one place.
2026-05-04 17:06:11 +03:00
Ed Zynda 92eaaf6a59 docs(mcp): document MCP Tasks support (#21)
- README: add tasksMode YAML example and MCP Tasks subsection with
  SDK opt-in snippet
- pkg/kit/README: add MCP Tasks subsection covering MCPTaskMode,
  progress callbacks, and List/Get/Cancel methods
- www/configuration: document the tasksMode server field plus a
  per-mode behaviour table
- www/sdk/options: extend the Compaction & MCP table with the six
  new Options fields and add a top-level MCP Tasks section
- www/sdk/overview: add a brief MCP Tasks section between MCP
  prompts/resources and Context & compaction

All examples verified against the public symbols in pkg/kit/mcp_tasks.go;
docs site builds cleanly via npx tome build.
2026-05-04 17:01:47 +03:00
Ed Zynda e6084b7bd0 feat(mcp): add MCP Tasks support at the SDK level (#21)
Implement Phase 1 of the MCP Tasks spec so long-running tools/call
requests can run asynchronously, survive proxy timeouts, and be
cancelled mid-flight.

- connection pool now advertises mcp.NewTasksCapability() during
  initialize and captures the InitializeResult so callers can detect
  per-server task support
- new MCPServerConfig.TasksMode (auto|never|always, default auto)
  parsed from both new and legacy mcp.json shapes
- ExecuteTool augments tools/call with TaskParams when policy and
  capability allow, polls tasks/get / tasks/result until terminal,
  and best-effort tasks/cancel on context cancellation
- new MCPToolManager methods: SetTaskConfig, ListServerTasks,
  GetServerTask, CancelServerTask
- public SDK surface in pkg/kit: MCPTask, MCPTaskStatus, MCPTaskMode,
  MCPTaskProgress, MCPTaskProgressHandler, plus Options fields
  (MCPTaskMode, MCPTaskTimeout, MCPTaskTTL, MCPTaskPollInterval,
  MCPTaskMaxPollInterval, MCPTaskProgress) and Kit.{List,Get,Cancel}
  MCPTask methods
- works around two upstream mcp-go v0.51.0 parser bugs
  (ParseCallToolResult rejects task responses; ParseTaskResultResult
  looks for content under a non-existent nested key) by decoding the
  wire shape directly via the transport
- defaults to MCPTaskModeAuto so servers that don't advertise task
  support behave exactly as before

Fixes #21
2026-05-04 16:51:09 +03:00
Ed Zynda 34d5abff9c build(deps): update dependencies and implement new acp.Agent methods
- Bump fantasy v0.21.0 -> v0.23.0, mcp-go v0.49.0 -> v0.51.0,
  acp-go-sdk v0.12.0 -> v0.12.2, chroma v2.23.1 -> v2.24.1,
  fsnotify v1.9.0 -> v1.10.1, ultraviolet, AWS SDK, Google API
- Implement CloseSession and ResumeSession on acpserver.Agent to
  satisfy the expanded acp.Agent interface in acp-go-sdk v0.12.2
- Add sessionRegistry.remove helper to support session close
2026-05-04 16:23:12 +03:00
Ed Zynda fc0ddd5f4f update 2026-05-04 15:51:00 +03:00
Ed Zynda 7aa6160c75 updates 2026-05-04 12:10:46 +03:00
Ed Zynda e830bf87ca refactor(models): remove responses API model registration hack
Fantasy v0.21.0 natively includes gpt-5.5 and other newer models in
its responsesModelIDs/responsesReasoningModelIDs lists, making our
workaround unnecessary.

- Delete responses_models.go (go:linkname hack + RegisterResponsesModels)
- Delete responses_models_test.go
- Replace isResponsesAPIModel/isResponsesReasoningModel heuristics with
  direct openai.IsResponsesModel/openai.IsResponsesReasoningModel calls
- Remove RegisterResponsesModels calls from registry init/reload
- Remove hack documentation from AGENTS.md
- Update all deps (fantasy v0.21.0, smithy-go, ultraviolet, etc.)
2026-04-27 09:42:52 +03:00
Ed Zynda 3881d1c28f fix(models): auto-register new OpenAI models for Responses API routing
Fantasy's hardcoded responsesModelIDs list gates whether a model uses
the Responses API or Chat Completions code path. When a new model
(e.g. gpt-5.5) is added via `kit update-models` but fantasy hasn't
been updated yet, the type mismatch between *ResponsesProviderOptions
and *ProviderOptions causes a crash.

- Add isResponsesAPIModel()/isResponsesReasoningModel() helpers that
  supplement fantasy's checks with prefix-based heuristics for modern
  OpenAI model families (gpt-4.1+, gpt-5+, o-series, codex, chatgpt)
- Add RegisterResponsesModels() using go:linkname to append missing
  model IDs from our database into fantasy's internal slices at init
  time and after ReloadGlobalRegistry()
- Replace all direct openai.IsResponsesModel/IsResponsesReasoningModel
  calls in providers.go with the new helpers
- Merge embedded + cached model databases instead of cache-only fallback
- Bump fantasy v0.19.0 -> v0.20.0 to match existing import usage
- Document the technique and model-family update process in AGENTS.md
2026-04-24 15:13:38 +03:00
Ed Zynda 53f6682bd0 refactor(core): remove redundant single-edit mode from edit tool
- Remove top-level old_text/new_text params from edit tool schema
- Make edits array the sole interface; single edits pass 1-item array
- Simplify normalizeEditInput, removing dual-mode branching logic
- Update UI renderer to only read from edits array
- Remove old_text/new_text from bodyKeys in message summarizer
- Update web session HTML to iterate edits array
- Convert all single-edit tests to use Edits array
- Replace mixed-mode test with empty-array validation test
2026-04-23 16:33:55 +03:00
Ed Zynda 996b15c9b9 fix(extensions): return nil error for blocked/disabled tools so LLM sees the reason
Tool blocking via OnToolCall and SetActiveTools returned both a
ToolResponse (IsError=true) and a Go error. Fantasy treats a non-nil
Go error from tool.Run() as a critical failure, aborting the agent
loop without delivering the tool result to the LLM. The model never
saw the block reason and would retry or hallucinate.

- Return nil error for blocked tools (OnToolCall Block=true)
- Return nil error for disabled tools (SetActiveTools)
- Return nil error for extension tool execution failures
- Update tests to assert nil error (IsError response conveys the error)

Fixes #20
2026-04-23 13:13:28 +03:00
Ed Zynda aeb704367c feat(app): update token counts and context fill after every step
- Set context tokens per-step in recordStepUsage instead of waiting
  for turn completion; each step re-sends the full conversation so
  the reported usage monotonically increases
- Add UsageUpdatedEvent to trigger a TUI re-render after each step
  so the status bar reflects updated tokens, cost, and context %
  even during gaps between streaming chunks
- Update test to expect per-step context token updates
2026-04-23 12:56:00 +03:00
Ed Zynda d2e23295b6 perf(ui): cache item heights in ScrollList to eliminate redundant renders
- Add heightCache map to ScrollList, keyed by item ID, avoiding
  repeated Render() calls purely to count lines
- Rewrite GotoBottom() to walk backwards from the end in O(visible)
  instead of two full O(N) forward passes over all items
- Replace all height-only Render() calls in clampOffset(), AtBottom(),
  ScrollBy(), and ScrollPercent() with cached itemHeight() lookups
- Invalidate cache on width changes (SetWidth) and item mutations
  (AppendChunk, AppendStdout/Stderr via InvalidateItemHeight)
- Refresh cache entries in View() from authoritative renders
2026-04-23 12:03:44 +03:00
Ed Zynda e5a13e2e12 feat(sdk): add missing LLM type aliases and remove fantasy dependency leakage
- Add LLMToolResultOutputContentMedia alias (closes gap in tool result types)
- Add LLMToolResultContentType enum and constants (Text, Error, Media)
- Add LLMToolInfo, LLMProviderOptions, LLMProviderMetadata, LLMPrompt aliases
- Replace all fantasy.* references in hooks.go and hooks_test.go with
  SDK-owned aliases, removing the charm.land/fantasy import from both
- Fix gofmt alignment in internal/extensions/symbols.go
- Update SDK skill doc with complete LLM type reference
2026-04-22 21:05:04 +03:00
Ed Zynda 558fb5214f feat(sdk): expose remaining Fantasy lifecycle callbacks as events and hooks
Closes #19.

SDK events (pkg/kit):
- Add 10 new event types: StepStart, StepFinish, TextStart, TextEnd,
  ReasoningStart, Warnings, Source, StreamFinish, Error, Retry
- Add typed convenience subscribers for all 31 event types (20 previously
  required raw Subscribe + type assertion)
- Add OnPrepareStep hook for intercepting/replacing messages between
  steps within a multi-step turn (composes with existing steering)
- Rename OnStreaming to OnMessageUpdate (deprecated alias kept)

Agent internals (internal/agent):
- Add GenerateCallbacks struct replacing 16 positional callback params
- Add GenerateWithCallbacks method; deprecate GenerateWithLoopAndStreaming
- Wire all Fantasy stream callbacks: OnStepStart, OnTextStart/End,
  OnReasoningStart, OnWarnings, OnSource, OnStreamFinish, OnError,
  OnRetry, OnStepFinish (unified step event)
- Compose PrepareStep with steering channel + consumer hook

Extension system (internal/extensions):
- Add 8 new extension events: StepStart, StepFinish, ReasoningStart,
  Warnings, Source, Error, Retry, PrepareStep
- Bridge SDK events to extension runner with Yaegi-safe types (string
  errors, plain int64 token fields, ContextMessage for PrepareStep)

Docs: update README, SDK skill, www/sdk/callbacks, www/sdk/overview
2026-04-22 20:25:06 +03:00
Ed Zynda 61408ed490 fix(sdk): infer ToolResponse.Type for binary data in NewTool/NewParallelTool
- Infer Type="image" for image/* MIME types and Type="media" for all
  other binary content so the downstream framework creates a media
  content block instead of silently discarding Data bytes (#17)
- Extract shared toolOutputToResponse() helper to eliminate duplication
- Add ImageResult() and MediaResult() convenience constructors
- Add LLMToolCall and LLMToolResponse type aliases so SDK consumers
  can call Tool.Run() without importing the underlying framework
- Add 6 regression tests covering image, media, and text responses

Closes #17
2026-04-22 16:58:07 +03:00
Ed Zynda 3cfb6437f9 perf(session,ui): reduce syscalls, allocations, and subprocess spam
- Buffer session JSONL writes with bufio.Writer, flush at sync points;
  ForkToNewSession and AddLLMMessages now batch N entries into ~1 syscall
- Cache lipgloss styles in style.CachedStyles, lazily built and
  invalidated on SetTheme; eliminates ~15 NewStyle() calls per frame in
  hot render paths (reasoning blocks, spinner, tool headers, margins)
- Cache git ls-files results for @file suggestions with 3s TTL; typing
  @filename no longer spawns 3 subprocesses per keystroke
- Use strings.Builder for StreamingMessageItem.content; eliminates O(n²)
  string copying during LLM response streaming
2026-04-22 16:48:17 +03:00
Ed Zynda d33ad4028b fix(kit): enable streaming for subagent child instances
- Set Streaming: true in subagent childOpts to prevent
  viper.Set("stream", false) from polluting global state
- Without this, concurrent subagents and the parent could read
  stale stream=false from viper, causing provider-level issues
  (e.g. Anthropic non-streaming timeouts with extended thinking)
2026-04-22 13:06:37 +03:00
Ed Zynda 307dcd1734 cleanup 2026-04-22 11:56:06 +03:00
Ed Zynda 81240b075e chore: update all deps and fix acp-go-sdk v0.12.0 breaking changes
- Update all Go dependencies (bubbletea v2.0.6, fantasy v0.19.0,
  acp-go-sdk v0.12.0, mcp-go v0.49.0, and transitive deps)
- Replace SetSessionModel with SetSessionConfigOption to match new
  acp-go-sdk Agent interface (union type with ValueId/Boolean variants)
- Add ListSessions stub returning empty list (new required method)
- Refresh embedded_models.json from models.dev/api.json
- Update ACP smoke test: add initialize handshake, session/list,
  session/set_config_option, session/cancel, and fix update parsing
2026-04-22 11:55:40 +03:00
Ed Zynda 9a662d440c fix(ui): reduce TUI visual noise and improve layout
- remove "You" label and icon from user messages, use borderless content block
- remove input title bar ("Enter your prompt...") and hint line
- increase textarea from 3 to 4 rows with top/bottom margin
- hide input hints permanently for a cleaner UI
- match separator colors (use theme.Border for both startup and input dividers)
- make startup separator full terminal width instead of hardcoded 80
- add /help for help hint and pipe separators to status bar
- add printCustomMessage/RenderCustomMessage for custom alert labels
- render /help output as markdown with "Help" alert label
- add Ctrl+V (paste image) to help message keys section
- fix reasoning text wrapping using ANSI-aware lipgloss.Style.Width
- export HighlightFileTokens for cross-package use
2026-04-22 11:41:09 +03:00
Ed Zynda 4ba9d6fab3 feat(events): mirror Fantasy tool input streaming callbacks as Kit events
- Add ToolCallStartEvent, ToolCallDeltaEvent, ToolCallEndEvent to SDK
- Wire Fantasy OnToolInputStart/Delta/End through agent to EventBus
- Add typed convenience subscribers: OnToolCallStart/Delta/End on Kit
- Bridge new events to TUI via ToolCallInputStart/Delta/End app events
- Extend extension system with OnToolCallInputStart/Delta/End handlers
- Add extension event types, API methods, loader wiring, Yaegi symbols
- Update docs: README, SDK skill, extensions skill, www/sdk, www/extensions

Closes #16
2026-04-21 23:28:13 +03:00
Ed Zynda aec0e7cc01 docs: document noOAuth MCP server config field
- Add noOAuth to MCP server fields table in www/pages/configuration.md
- Add pubmed example with noOAuth in README and www config docs
2026-04-21 22:44:27 +03:00
Ed Zynda bac04636bf feat(config): add noOAuth flag to skip OAuth on public MCP servers
- Add NoOAuth field to MCPServerConfig with JSON/YAML support
- Guard OAuth error handling and transport setup with the new flag
- Prevents failed dynamic client registration on servers like PubMed
  that do not support OAuth
2026-04-21 22:24:10 +03:00
Ed Zynda 5f851fd08e fix(ui): require double ctrl+c to quit, matching double-esc pattern
- First ctrl+c clears input and arms quit flag with 3s timeout
- Second ctrl+c within timeout window actually quits
- Show '⚠ Press Ctrl+C again to quit' warning after first press
- Empty input no longer quits immediately on single ctrl+c
- Prompt/overlay states: ctrl+c cancels dialog, re-dispatches to
  main handler for double-press tracking instead of quitting
- Update placeholder, help text, and tests to match new behavior
2026-04-21 22:05:13 +03:00
Ed Zynda f8371836d8 fix(cmd): fix character encoding in OAuth success page
Add charset=utf-8 to Content-Type header and use HTML entity
&#10003; instead of raw Unicode checkmark to prevent garbled
text display in browsers.

Fixes #9
2026-04-21 21:19:51 +03:00
Ed Zynda 74f00244be fix(ui): wrap reasoning blocks to terminal width to prevent clipping
- wrap thinking text in StreamComponent and render.ReasoningBlock
- plumb width through renderer and streaming item paths
- keeps style consistent with user/assistant blocks and avoids cut-off lines
2026-04-21 20:42:53 +03:00
Ed Zynda b5d7fd4f3e update docs 2026-04-21 20:33:32 +03:00
Ed Zynda 5857d40978 cleanup 2026-04-21 20:27:32 +03:00
Ed Zynda 3ff701054a fix(models): add gpt-5.4 reasoning level support with auto-adjustment
Adds 'none' thinking level to support OpenAI gpt-5.4 models which use
'reasoning_effort: none' instead of 'minimal'. Includes validation and
auto-adjustment when switching models with incompatible levels.

- Add ThinkingNone constant mapping to ReasoningEffortNone
- Add IsValidThinkingLevelForModel() with gpt-5.4 detection
- Add SuggestThinkingLevelFallback() for level migration
- Auto-adjust thinking level on model switch with user notification
- Update all docs to include 'none' in valid levels

Fixes #11
2026-04-21 20:19:00 +03:00
Ed Zynda c1dee3ceba feat(cmd): add --set-default flag and improve auth error messages
Add --set-default flag to 'kit auth login' to automatically set the
provider's default model after successful authentication. When no Anthropic
credentials exist but OpenAI credentials are detected, error messages
now suggest using OpenAI with the correct --model flag.

Fixes #9
2026-04-21 19:52:06 +03:00
Ed Zynda 2d9783a44d fix(ui): make ctrl+c clear input before quitting
Change Ctrl+C behavior to match other terminal AI tools (claude, codex, pi):
- First Ctrl+C clears the current input when text is present
- Second Ctrl+C (within 3 seconds) quits the application
- Ctrl+C on empty input quits immediately
- 3-second auto-reset timer clears the 'pressed once' state
- Flag also resets after message submission

Updates placeholder text and help message to reflect new behavior.

Fixes #13
2026-04-21 19:32:48 +03:00
Ed Zynda 88dd216e15 fix(session): prevent circular parent references in tree session
Add defensive validation to detect and prevent cycles in the session tree
parent chain that could occur after compaction or file corruption.

- Add tree_validation.go with cycle detection and parent chain validation
- Validate parent chain before appending messages (AppendMessage)
- Validate firstKeptEntryID exists in AppendCompaction
- Add depth limit and cycle detection to buildTreeNode to prevent infinite recursion
- Log diagnostics on session open to detect existing cycles
- Add tests for cycle detection and graceful handling
2026-04-21 16:24:38 +03:00
Ed Zynda 9e5806ade8 fix(subagent): remove biased model example from tool schema
- Remove vendor-specific model example that could bias LLM selection
- Add minimum recommended timeout guidance to subagent schema
2026-04-21 11:28:32 +03:00
Ed Zynda 50f586ec8f chore(models): update embedded model database from models.dev
Update internal/models/embedded_models.json with the latest snapshot
from https://models.dev/api.json.

- Providers: 111 → 115 (+4)
- Models: 4,191 → 4,259 (+68)
2026-04-21 10:38:23 +03:00
Ed Zynda 8a8e684dff docs(sdk): document MCPAuthHandler and OAuth opt-in behavior
Reflect the refactor that made MCPAuthHandler an explicit, opt-in
dependency for remote MCP OAuth. Four surfaces updated:

- README.md: new 'MCP OAuth (remote MCP servers)' subsection under the
  Go SDK section, outlining the three consumer patterns (nil / CLI /
  custom) and linking to the full options docs.
- pkg/kit/README.md: type cheat-sheet now lists MCPAuthHandler,
  DefaultMCPAuthHandler, and CLIMCPAuthHandler alongside the existing
  MCPTokenStore entries.
- skills/kit-sdk/SKILL.md: Options example annotated with nil-disables-
  OAuth semantics; new 'MCP OAuth Authorization' section precedes the
  existing token-storage section; re-exported types list expanded.
- www/pages/sdk/options.md: Options fields table gains MCPAuthHandler
  row; new top-level 'MCP OAuth Authorization' section with consumer
  matrix, CLI/custom/fully-custom code samples, and a warning callout
  about the OnAuthURL nil-hang footgun.
2026-04-17 15:30:10 +03:00
Ed Zynda 7ef99ac60f refactor(sdk): remove UX policy from MCP OAuth handler
Strip user-facing I/O out of the SDK's OAuth surface so library, daemon,
and web-app embedders are not surprised by port binds or browser opens.

- DefaultMCPAuthHandler no longer calls openBrowser; it exposes an
  OnAuthURL(serverName, authURL) hook and performs no presentation I/O.
- kit.New no longer auto-constructs a default handler when
  Options.MCPAuthHandler is nil. OAuth is opt-in; remote MCP servers
  requiring authorization fail with a clear error if no handler is set.
- CLIMCPAuthHandler owns the CLI policy (browser open + stderr prints)
  by wiring an OnAuthURL closure on the inner DefaultMCPAuthHandler.
- openBrowser is now unexported and colocated with its sole caller; no
  new exported helper is added to the SDK surface.

BREAKING CHANGE: SDK consumers relying on implicit OAuth with a nil
MCPAuthHandler must now pass kit.NewCLIMCPAuthHandler() (or a custom
implementation) explicitly. The kit CLI is unaffected — cmd/root.go
already constructs the handler explicitly.
2026-04-17 15:26:35 +03:00
Ed Zynda a67f514560 chore(models): refresh embedded models.dev database
- update internal/models/embedded_models.json from https://models.dev/api.json
- 110 → 111 providers, 4172 → 4191 models
2026-04-17 12:19:21 +03:00
Ed Zynda b6bb35cb71 Merge pull request #7 from mark3labs/feat/sdk-options-overrides
feat(sdk): expose generation and provider params on Options
2026-04-17 12:15:47 +03:00
Ed Zynda 4e82fac442 fix(fileutil): decouple TestDetectMediaType from system MIME db
TestDetectMediaType/.go fails on CI images (Ubuntu mime-support) where
/etc/mime.types registers '.go → text/x-go', because mime.TypeByExtension
reads those files at init. The test intended to exercise the 'unknown
extension falls through to text/plain' branch but used a real extension,
making the assertion environment-dependent.

Replace '.go' with '.kitsyntheticext', an invented extension that no
system MIME database registers. The fallback path is now exercised
deterministically on any host.
2026-04-17 12:13:28 +03:00
Ed Zynda 5ec2217b0f docs(sdk): document global viper state leakage in New and Options
The SDK applies Options by calling viper.Set on viper's process-global
store, which means two Kits constructed in the same process are not
isolated from each other: the second New overwrites the first's keys,
and downstream readers (SetModel, GetThinkingLevel, BuildProviderConfig)
observe the most recent value.

- Add a 'Global viper state warning' block to the Options godoc
  explaining the leak, the zero-value-does-not-clear gotcha, and
  pointing at viper.Reset() as the migration workaround.
- Add a matching warning to the New godoc so consumers discover the
  constraint from either entry point.
- Detach the viperInitMu godoc (previously lodged inside New's comment
  block) and clarify that the mutex only guards the construction
  window, not instance isolation.
- Add a TODO noting the proper fix: refactor to a per-call viper.New()
  instance so each Kit owns its own config store.
2026-04-17 12:09:13 +03:00
Ed Zynda 8a851723ba style(sdk): gofmt trailing newlines in kit_test.go 2026-04-17 12:07:54 +03:00
Ed Zynda 53b628c5f8 fix(sdk): map hyphenated config keys to KIT_* env vars
- InitConfig now installs a viper env key replacer so keys like
  "max-tokens" bind to KIT_MAX_TOKENS under AutomaticEnv; previously
  hyphenated keys silently missed their documented env overrides.
- Simplify TestNewPreservesIsSetSemantics: with SkipConfig: true no env
  bindings are registered, so the os.Getenv guard and upper() helper
  were dead weight. Remove both and drop the unused helper.
2026-04-17 12:07:29 +03:00
Ed Zynda e1c94cb362 fix(sdk): align SDK max-tokens floor with CLI default (4096 → 8192)
The SDK last-resort MaxTokens floor is applied in kit.New() when
Options.MaxTokens, KIT_MAX_TOKENS, .kit.yml, and per-model defaults
are all unset. It was 4096 (inherited from the old setSDKDefaults
viper default) while the CLI --max-tokens cobra default is 8192.

Bump the floor to 8192 so SDK and CLI callers start from the same
base value before rightSizeMaxTokens runs, then update README,
skills/kit-sdk/SKILL.md, and www/pages/{configuration,sdk/options}.md
to match.
2026-04-17 11:59:49 +03:00
Ed Zynda ecf95b52e1 fix(sdk): preserve IsSet semantics for generation param overrides
Previously setSDKDefaults() registered viper.SetDefault for max-tokens,
temperature, top-p, top-k, frequency/presence-penalty, and thinking-level.
viper.SetDefault makes IsSet() return true, which silently suppressed
per-model defaults (ApplyModelSettings) and automatic right-sizing
(rightSizeMaxTokens) for every SDK-created Kit — and for CLI runs too,
since cmd/root.go routes through kit.New. Effective max-tokens for
claude-sonnet-4-5 was pinned at 4096 instead of 32768.

- Drop SetDefault for all IsSet-sensitive keys; keep only model,
  system-prompt, stream, num-gpu-layers, main-gpu.
- Apply a 4096 max-tokens floor directly on the *models.ProviderConfig
  struct in kit.New() when nothing else resolved a value. Keeps
  viper.IsSet("max-tokens") == false so rightSizeMaxTokens and
  per-model maxTokens overrides still fire.
- Update Options.MaxTokens / ThinkingLevel godoc to describe the real
  precedence chain.
- Strengthen tests: add Temperature subtest; add
  TestNewPreservesIsSetSemantics regression covering all seven keys;
  split TestNewWithProviderOptions into three subtests including
  Options-beats-viper-state and ProviderURL propagation; add
  resetViper helper so subtests don't bleed state.
- Document the new SDK fields (MaxTokens, ThinkingLevel, Temperature,
  TopP, TopK, FrequencyPenalty, PresencePenalty, ProviderAPIKey,
  ProviderURL, TLSSkipVerify) in README, skills/kit-sdk, and the www
  configuration / sdk/options / sdk/overview pages, including a
  dedicated precedence table.
2026-04-17 11:50:45 +03:00
Ed Zynda 0641c92acc feat(sdk): expose generation and provider params on Options
Adds programmatic overrides on kit.Options for the model/provider knobs
that were previously only reachable through viper.Set() — letting SDK
consumers (web apps, services, embedded agents) configure kit fully
in-code without polluting global viper state or shipping .kit.yml.

Generation parameters:
  - MaxTokens         int      (max output tokens per response)
  - ThinkingLevel     string   (off/low/medium/high)
  - Temperature       *float32
  - TopP              *float32
  - TopK              *int32
  - FrequencyPenalty  *float32
  - PresencePenalty   *float32

Sampling params use pointer types so explicit 0 is distinguishable from
unset; nil leaves provider/per-model defaults in place.

Provider configuration:
  - ProviderAPIKey    string
  - ProviderURL       string
  - TLSSkipVerify     bool

Implementation just pushes Options values into viper inside New(),
so all existing downstream code (BuildProviderConfig, SetModel,
modelSettings lookups, runtime model switching) picks them up
uniformly without any new code paths. Tests added for MaxTokens,
ThinkingLevel, and ProviderAPIKey.
2026-04-17 11:24:00 +03:00
Ed Zynda 3bb20f5283 feat(models): surface and prevent silent max-tokens truncation
- Raise --max-tokens default from 4096 to 8192.
- Auto-raise MaxTokens toward the model's catalog Limit.Output (capped at
  32768) when the user hasn't set --max-tokens explicitly and no per-model
  modelSettings override applied. Prevents silent 4k/8k truncation on
  models that support 32k-262k output.
- Surface FinishReasonLength at turn end: the app now subscribes to
  TurnEndEvent and renders a system-message banner explaining the current
  cap, the model's known ceiling, and how to raise it. Previously the TUI
  swallowed 'length' stops, producing 'ghost' truncations.
- Export FinishReason* constants on pkg/kit (Stop, Length, ToolCalls,
  ContentFilter, Error, Other, Unknown) and fix stale comments that used
  Anthropic-style strings.
- Add Kit.MaxTokens() and Kit.MaxOutputLimit() SDK accessors, backed by
  Agent.GetMaxTokens() which correctly returns 0 for providers that
  suppress the param (e.g. Codex OAuth).
- Tests: rightSizeMaxTokens covers 7 paths (cap, raise, preserve,
  explicit flag, nil info, zero limit); handleTurnEnd covers length/
  non-length/nil-sendFn and the fallback message formatter.
- Docs: update configuration.md, cli/flags.md, and kit-extensions skill
  to reflect the new default and behavior.
2026-04-16 23:12:10 +03:00
Ed Zynda 633fa38b2b fix(ui): regenerate spinner frames on theme change
- UpdateTheme() only refreshed typography styles, leaving spinner
  frames rendered with the old theme's colors
- Now calls knightRiderFrames() to rebuild frames with the new
  theme's Primary, Muted, VeryMuted, and MutedBorder colors
2026-04-16 12:32:49 +03:00
Ed Zynda f905cee48c fix(ui): dynamically size slash command name column in popup
- Replace hardcoded nameWidth of 15 with dynamic calculation based on
  the longest command name in the filtered list
- Prevents truncation of longer names like /feature-request and
  /release-tagger that were cut off with ellipsis
- Cap name column to leave at least 20 chars for descriptions
- Add 1 char gap between name and description columns
2026-04-16 12:27:56 +03:00
Ed Zynda 182c10ea1a refactor(ui): improve keybinding ergonomics for terminal multiplexers
- Move thinking toggle from ctrl+t to leader chord (ctrl+x t) to avoid
  conflicts with tmux/zellij tab mode and terminal new-tab shortcuts
- Change scrollback jump from alt+home/alt+end to ctrl+home/ctrl+end
  for better compatibility across SSH and older tmux versions
- Remove ctrl+d as submit alias (enter suffices); avoids EOF convention
  confusion and accidental shell disconnects
- Remove ctrl+a from tree selector filter shortcuts to avoid conflict
  with the common tmux prefix remap (ctrl+o cycle still reaches all
  filter modes)
2026-04-16 12:21:37 +03:00
Ed Zynda fcaa52bf1c fix(extensions): serialize handler calls per-extension to prevent data races
- Add per-extension reentrant mutex to Runner that serializes handler
  invocations from concurrent goroutines (e.g. parallel subagent events)
  while allowing re-entrant calls (handler → EmitCustomEvent → handler)
- Fix subagent-monitor slice aliasing bug: submonEntries[:0] reuses the
  backing array, corrupting entries during in-place filtering
- Pass parent's loaded MCPConfig to child subagents in Kit.Subagent(),
  eliminating concurrent viper map access during parallel kit.New() calls
- Add Options.MCPConfig field so SDK consumers can also skip viper reads
- Add tests for concurrent emit, cross-extension concurrency, and
  re-entrant EmitCustomEvent
2026-04-16 12:11:10 +03:00
Ed Zynda 7e6455732c docs: update documentation for sudo password prompt feature
- README.md: mention interactive sudo password prompt in features
- skills/kit-sdk/SKILL.md: add PasswordPromptEvent to event types table
- www/pages/index.md: update features list with sudo prompt
- www/pages/development.md: update project structure description
- www/pages/sdk/callbacks.md: add complete event types table
2026-04-15 18:06:11 +03:00
Ed Zynda 71301a9035 feat: add interactive sudo password prompt for bash tool
Add core TUI support for handling sudo password prompts when executing
bash commands that require elevated privileges.

- Detect sudo commands and check if credentials are cached (sudo -n)
- Show modal password prompt with masked input (• characters) when needed
- Pipe password via stdin using sudo -S -p '' (no password in command string)
- Password flows through context callbacks, never stored in session history
- Add PasswordPromptHandler to agent and SDK event system
- Add password prompt overlay to TUI with 🔐 icon and hidden input
- Include tests for sudo command detection and rewriting

The password is never persisted to disk - it only exists in memory
during execution and is piped directly to sudo via stdin.
2026-04-15 17:33:03 +03:00
Ed Zynda 0974d37ab2 feat(sdk): support mcp-go in-process transport for MCP servers
- Add InProcessServer field to MCPServerConfig (json:"-", never serialized)
- Add "inprocess" transport type to config, validation, and connection pool
- Add createInProcessClient() using mcp-go client.NewInProcessClient()
- Add Kit.AddInProcessMCPServer() convenience method
- Add Options.InProcessMCPServers for init-time registration
- Export MCPServer type alias (= server.MCPServer) in pkg/kit/types.go
- Add 8 tests covering config, pool, tool manager, and edge cases
- Update SDK README, kit-sdk skill, and www docs
2026-04-15 16:29:07 +03:00
Ed Zynda 398e825df8 docs: update docs for recent features and API additions
- Add smart @ attachments (MIME detection, @mcp:server:uri syntax)
- Add MCP Prompts and Resources SDK APIs to skill and www docs
- Add $+ required variadic placeholder for prompt templates
- Add Ctrl+X e (external editor) and Ctrl+X s (steer) keyboard shortcuts
- Fix stale Ctrl+S references, now Ctrl+X s for mid-turn steering
- Add --frequency-penalty and --presence-penalty CLI flags
- Add per-model settings (modelSettings) to configuration docs
- Add NoExtensions, NoSkills, NoContextFiles, SessionManager,
  MCPTokenStoreFactory to SDK options docs
- Add bridge_demo.go to extension examples
- Add dynamic MCP servers, subagents to SDK overview
2026-04-15 16:02:49 +03:00
Ed Zynda 3c51c20be7 feat(mcp): handle embedded resources in prompt messages
- Extract all MCP content types in prompt expansion: ImageContent,
  AudioContent, EmbeddedResource (text and blob), and ResourceLink
- Add MCPFilePart type to carry decoded binary attachments through
  the tools → SDK → bridge → UI layers
- Inline text resources as fenced code blocks with URI annotation
- Decode image/audio/blob content from base64 into LLMFilePart
  attachments submitted via RunWithFiles
- Render ResourceLink as text annotation for the LLM
- Show attachment badges on user messages (e.g. '1 image(s) attached')
  matching the existing clipboard paste UI pattern
- Log warnings on base64 decode failures instead of silently dropping
2026-04-15 15:23:01 +03:00
Ed Zynda 25410af440 feat: add smart @ attachments with MIME detection and MCP resource support
Phase 1: Smart @ for local files
- ProcessFileAttachments now returns FileAttachmentResult with separate
  ProcessedText and FileParts fields instead of a plain string
- Binary files (images, audio, video, PDFs, etc.) detected via MIME type
  are extracted as multimodal FileParts instead of XML-wrapped text garbage
- detectMediaType() uses extension-based lookup then content sniffing
- isBinaryMediaType() classifies image/*, audio/*, video/*, and specific
  application types as binary
- @mcp:server:uri token format for referencing MCP resources in text
- All 4 submission paths (TUI submit, TUI steer, MCP prompt, CLI) updated
- App.RunOnceWithFiles/RunOnceResultWithFiles/RunOnceWithDisplayAndFiles
  added for non-interactive multimodal submission

Phase 2: MCP resources in @ autocomplete
- MCPToolManager gains loadServerResources(), GetResources(), ReadResource(),
  SubscribeResource(), UnsubscribeResource(), RefreshServerResources()
- MCPResource and MCPResourceContent types for resource metadata/content
- FileSuggestion extended with IsMCPResource, MCPServerName, MCPResourceURI
- InputComponent.SetMCPResourceProvider() wires resource suggestions into
  the @ popup alongside local files
- @ popup merges local file suggestions with MCP resource suggestions,
  sorted by fuzzy match score
- MCP resources display 'mcp:servername' in the popup description
- Selecting an MCP resource inserts @mcp:server:uri format
- ProcessFileAttachments resolves @mcp: tokens via MCPResourceReader callback
- Text resources are XML-wrapped as <resource>; binary resources become
  FileParts for multimodal submission
- Agent, Kit SDK, and cmd/root.go wired end-to-end

Phase 3: Resource subscriptions (foundation)
- SubscribeResource/UnsubscribeResource on MCPToolManager
- onResourcesChanged callback for live refresh (wired but not yet
  triggering UI refresh automatically)
- RefreshServerResources for manual resource list refresh
2026-04-15 13:01:36 +03:00
Ed Zynda 26c9f009f9 refactor: remove fantasy dependency name leaks from SDK surface
- Rename ExtensionToolsAsFantasy -> ExtensionToolsAsLLMTools
- Rename convertKitMessagesToFantasy -> convertToLLMMessages
- Delete GetFantasyProviders, ToFantasyMessages, FromFantasyMessage
- Replace direct fantasy type usage with kit.LLM* aliases in app tests
- Scrub fantasy references from godoc comments across pkg/kit and internal
2026-04-15 12:24:52 +03:00
Ed Zynda e068487ff7 style(ui): fix gofmt alignment in MCPPromptInfo struct 2026-04-15 11:50:33 +03:00
Ed Zynda 0ffb0ba788 refactor(tools): remove fantasy dependency from internal/tools
- Replace fantasy.AgentTool with plain MCPTool struct in MCPToolManager
- Move fantasy adapter from internal/tools to internal/agent as mcpAgentTool
- Add MCPToolManager.ExecuteTool() for framework-agnostic tool execution
- Remove dead fantasy.LanguageModel field from MCPConnectionPool
- Remove MCPToolManager.SetModel() (was only feeding the dead field)

internal/tools is now a pure MCP client library with no LLM framework
dependency. The fantasy-to-MCP bridging is confined to the agent layer
where it belongs.
2026-04-15 11:27:47 +03:00
Ed Zynda 65c6e9f797 refactor(models): decouple TUI progress from SDK dependency tree
- Remove direct internal/ui/progress import from internal/models/providers.go
- Add ProgressReaderFunc callback to ProviderConfig for dependency inversion
- Wire Bubble Tea progress reader via CLIOptions in cmd/root.go
- Add NewProgressReadCloser convenience wrapper in progress package
- SDK consumers (pkg/kit) no longer transitively pull bubbletea, lipgloss
  v2, or bubbles
- Update embedded_models.json from models.dev (110 providers, 4172 models)
2026-04-14 17:17:01 +03:00
Ed Zynda 68d798d2f4 feat(prompts): add $+ required variadic, skip code in placeholders
- Add internal/fences package for detecting markdown code regions
  (fenced blocks and inline code spans) with ReplaceOutside/StripCode
- SubstituteArgs, HasArgPlaceholders, RequiredArgs now skip $
  placeholders inside ``` fences and `inline` code spans
- ProcessFileAttachments skips @file tokens inside code regions
- Add $+ placeholder: expands like $@ but requires at least 1 argument
- Add RequiredArgs() method; expandPromptTemplate validates arg count
  and re-populates input on failure instead of submitting
- Update feature-request, file-issue, new-prompt to use $+
2026-04-14 13:22:10 +03:00
Ed Zynda eefd5565f8 feat(ui): populate input instead of auto-submitting prompts with args
- Add HasArgPlaceholders() method to PromptTemplate to detect , $@,
  $ARGUMENTS, etc. placeholders in template content
- Add HasArgs field to SlashCommand struct
- Set HasArgs when registering prompt templates as slash commands
- In fuzzy finder Enter handler, populate input with command + trailing
  space when HasArgs is true, letting the user type arguments naturally
- Fix potential index bug by capturing selectedCmd before resetting index
2026-04-14 12:46:12 +03:00
Ed Zynda 9d1b8a102e feat(ui): open external $EDITOR via ctrl+x e chord
- Add ctrl+x e leader key chord to open $VISUAL/$EDITOR in a temp file
  pre-populated with the current input text
- On save & quit, replace the input textarea with the edited content
- On error exit (e.g. :cq in vim), silently preserve original input
- Use charmbracelet/x/editor for editor command construction
- Use tea.ExecProcess to suspend/resume the TUI around the editor
- Update input hint text at all width breakpoints to show the shortcut
- Add ctrl+x e to /help output

Closes #5
2026-04-14 12:39:29 +03:00
Ed Zynda f57e045c69 feat(ui): highlight @file tokens in user messages with accent color
- Add highlightFileTokens() to style @file references with theme.Accent + bold
- Apply highlighting in UserBlock() after line wrapping, before herald rendering
- Support both unquoted (@path/to/file) and quoted (@"path with spaces") tokens
- Add tests for highlightFileTokens and UserBlock integration

Closes #6
2026-04-14 12:28:04 +03:00
Ed Zynda eb5da28a15 chore(deps): update all dependencies
- bump go directive to 1.26.2 (required by fantasy v0.17.2)
- fantasy v0.17.1 → v0.17.2
- bubbletea/v2 v2.0.2 → v2.0.5
- lipgloss/v2 v2.0.2 → v2.0.3
- mcp-go v0.47.1 → v0.48.0
- x/ansi v0.11.6 → v0.11.7
- x/term v0.41.0 → v0.42.0
- genai v1.52.1 → v1.54.0
- ultraviolet, x/crypto, x/net, x/text, x/exp, and other indirects
- allow MODEL env override in acp_smoke_test.py
2026-04-14 12:16:22 +03:00
Ed Zynda cd8e2a7654 feat(extensions): expand inline bash in editor for interactive mode
- Add editor interceptor via OnSessionStart so !{...} expansions
  appear in the user message block on screen
- Restrict OnInput handler to non-interactive sources (CLI, script,
  queue) to avoid double expansion
- Extract expand() helper and hoist regex to package level
- Update doc comments and examples
2026-04-14 11:56:41 +03:00
Ed Zynda 64da1caf41 docs(readme): add clickable links to extension examples
- Convert 31 extension example entries from plain code spans to
  Markdown links pointing to their source files
- Link the go-edit-lint.go and tool-logger_test.go references
2026-04-14 11:39:43 +03:00
Ed Zynda 7eaeafff8c fix(mcp): propagate OAuth config for runtime-added servers
- Store authHandler and tokenStoreFactory on Agent struct so
  AddMCPServer() can propagate them to new MCPToolManagers (#3)
- Add OAuthClientID, OAuthClientSecret, OAuthScopes fields to
  MCPServerConfig for servers without dynamic registration (#4)
- Pass OAuth fields from server config to transport OAuthConfig
  in both SSE and Streamable HTTP client creation paths
- Add GetAuthHandler() accessor to MCPToolManager
- Add tests for auth handler propagation and OAuth config parsing

Closes #3, closes #4
2026-04-11 15:24:47 +03:00
Ed Zynda 8ed8d23c73 docs(sdk): update kit-sdk skill with recent API additions
- Add NoSkills, NoExtensions, NoContextFiles options
- Add MCPTokenStoreFactory option and MCP OAuth types
- Add dynamic MCP server management (AddMCPServer, RemoveMCPServer,
  ListMCPServers, MCPServerStatus)
- Add per-model system prompts and generation parameters sections
- Add MCPToolsReady() to tool querying section
- Expand LLMUsage fields to include CacheCreationTokens/CacheReadTokens
- Update FinalUsage and ShouldCompact docs for cache-aware token counting
- Add MCP OAuth types to re-exported types reference
2026-04-11 12:09:51 +03:00
Ed Zynda 2de98d32be fix(ui): accurate context token tracking including cache tokens
- Include all token categories in context fill calculation:
  InputTokens + CacheReadTokens + CacheCreationTokens + OutputTokens
- With Anthropic/kimi prompt caching, InputTokens can be near-zero
  while CacheReadTokens holds the bulk of the context
- Include OutputTokens since assistant output becomes context next turn
- Remove max-only guard in SetContextTokens so context shrinks after
  compaction instead of staying stuck at the high-water mark
- Reset context tokens to 0 after compaction in both SDK and UI layers
- Use real API-reported token counts in ShouldCompact() instead of
  the chars/4 text heuristic which misses system prompts and tool defs
2026-04-10 17:05:47 +03:00
Ed Zynda 83127467c5 feat(sdk): add NoExtensions, NoSkills, and NoContextFiles options
- Add NoExtensions bool to Options, OR with viper fallback
- Add NoSkills bool to Options, guards all skill loading
- Add NoContextFiles bool to Options, skips AGENTS.md discovery
- SDK consumers can disable autoloading without touching viper
2026-04-09 17:07:31 +03:00
Ed Zynda e07c94f49d feat(mcp): add dynamic MCP server loading and unloading
- Add AddServer/RemoveServer to MCPToolManager for runtime server management
- Add RemoveConnection to MCPConnectionPool for per-server teardown
- Add AddMCPServer/RemoveMCPServer/ListMCPServers to Agent and SDK Kit
- Lazily create connection pool so AddServer works without prior LoadTools
- Wire onToolsChanged callback to trigger agent tool list rebuild
- Make MCPToolManager.Close nil-safe when pool was never initialized

Tests:
- Integration tests with real stdio MCP server (Python echo server)
- Agent-level tests using mock LLM model (no API key needed)
- Unit tests for error paths, callbacks, idempotency, nil safety
- SDK type surface tests
2026-04-09 13:54:11 +03:00
Ed Zynda b87146a284 feat(sdk): add MCPTokenStoreFactory for custom OAuth token storage
- Add MCPTokenStoreFactory option to kit.Options allowing SDK consumers
  to provide custom token storage backends for remote MCP servers
- Thread TokenStoreFactory through the full chain: kit.Options →
  kitsetup → agent → MCPToolManager → MCPConnectionPool
- Add createTokenStore() helper on connection pool that delegates to the
  factory or falls back to the default FileTokenStore
- Export MCPTokenStore, MCPToken, MCPTokenStoreFactory, and ErrMCPNoToken
  in pkg/kit/types.go following SDK naming conventions
- Default behavior (file-based storage) is preserved when factory is nil
2026-04-09 13:27:40 +03:00
Ed Zynda 186d9f7f44 fix(ui): route raw fmt.Print calls through proper renderers
- event_handler: route default extension print level through DisplayInfo
  instead of bare fmt.Println for consistent styling and timestamps
- factory: remove orphan fmt.Println("") before system messages; the
  renderer already manages its own spacing
- app: PrintFromExtension non-interactive fallback now respects level,
  writing errors/info to stderr with prefix to keep stdout clean
- app: PrintBlockFromExtension non-interactive fallback writes framed
  blocks to stderr instead of raw text to stdout
2026-04-09 13:00:23 +03:00
Ed Zynda 3a8ffc2104 feat(models): add per-model system prompt support
- Add systemPrompt field to GenerationParams and config structs
- On init, replace default system prompt with per-model prompt when
  user hasn't explicitly set one (via flag, config, or SDK option)
- On model switch, detect per-model prompt and compose it with
  AGENTS.md, skills, and date/cwd context
- Fix viper.IsSet bug: BindPFlag causes IsSet to return true for
  unset flags, so compare against defaultSystemPrompt instead
- Agent.SetModel now updates stored system prompt from config
- Export LoadModelSettingsFromConfig, LoadSystemPromptValue, and
  LookupModelForSettings for use by Kit.SetModel
- Add tests for prompt apply, precedence, file path, and
  modelSettings override
2026-04-09 12:35:00 +03:00
Ed Zynda e54570162e feat(models): add per-model generation parameter defaults
- Add modelSettings config section for attaching generation params
  (temperature, topP, topK, frequencyPenalty, presencePenalty,
  maxTokens, stopSequences, thinkingLevel) to any model by
  provider/model key
- Add params field to customModels definitions for inline defaults
- Change BuildProviderConfig and SetModel to use viper.IsSet so
  unset params remain nil, allowing model-level defaults to apply
- Wire ApplyModelSettings into CreateProvider with priority order:
  CLI flags > global config > modelSettings > customModels params
- Add GenerationParams to ModelInfo in the registry
- Update default config template with modelSettings and customModels
  params examples
2026-04-09 12:07:42 +03:00
Ed Zynda 34bb97a40e chore(deps): update dependencies
- bump mcp-go to v0.47.1
- update cloud auth, otel, and various indirect deps
2026-04-08 20:51:59 +03:00
Ed Zynda f5c1a16f8a feat(session): make compaction create new leaf with no parent
Change compaction behavior so the compaction entry has no parent (empty
ParentID), creating a new root for post-compaction history. This ensures
old compacted messages are not traversed when building LLM context.

- Modify AppendCompaction to create entries with empty ParentID
- Update BuildContext to collect kept messages via FirstKeptEntryID
- Update GetContextEntryIDs with same logic
- Add comprehensive tests for compaction behavior
- Add web viewer support for displaying compaction entries
2026-04-08 18:52:44 +03:00
Ed Zynda b29d7d2166 refactor(acpserver): remove redundant thinking tag parsing
Remove dead code now that pkg/kit transparently handles <thinking> and
 tags at the agent layer. The ACP server no longer needs to:

- Track inThinkingTag state across chunks
- Parse and split reasoning/text from MessageUpdateEvent chunks
- Maintain tag format constants

MessageUpdateEvent now contains clean text, and ReasoningDeltaEvent
contains structured reasoning - no duplicate filtering needed.
2026-04-08 16:55:53 +03:00
Ed Zynda 3ea0db69ea fix(ui): wrap user messages to terminal width
- Add width parameter to UserBlock and apply lipgloss.Wrap() before
  passing content to herald Tip alert
- Subtract 4 from width to account for alert bar prefix and margin
- Pass renderer width from RenderUserMessage to UserBlock
- Mirrors the assistant message wrapping added in e33564c
2026-04-08 15:15:27 +03:00
Ed Zynda 4304a5e899 feat(ui): change steer keybind to Ctrl+X s leader key chord
- Replace single Ctrl+S with Ctrl+X leader prefix followed by "s"
- Add leaderKeyActive flag to AppModel for two-key chord state
- Ctrl+X sets the leader flag; next keypress completes or cancels chord
- Update hint text in input component (adjust width thresholds)
- Update /help command output to reflect new keybind
2026-04-08 15:04:48 +03:00
Ed Zynda 4019c1e4f7 fix(ui): remove character limits from all textarea inputs
- Main message input: 5000 -> unlimited
- Prompt dialog input: 1000 -> unlimited
- Tool approval input: 1000 -> unlimited

Setting CharLimit to 0 disables the limit in Bubble Tea's textarea.
2026-04-08 14:23:34 +03:00
Ed Zynda 30ad7c1d0b feat(sdk): persist session messages incrementally per agent step
- Add StepMessagesHandler callback to agent's GenerateWithLoopAndStreaming
  so callers can persist messages as each step completes
- Wire onStepMessages in Kit.generate() to call session.AppendMessage
  for each step's messages immediately on completion
- Track PersistedMessageCount on GenerateWithLoopResult so runTurn
  skips already-persisted messages in post-generation cleanup
- Tool calls are always persisted as assistant+tool pairs (never orphaned)
- Document concurrency and incremental persistence requirements on
  the SessionManager interface for custom implementations
2026-04-08 14:15:05 +03:00
Ed Zynda e33564c569 fix(ui): wrap assistant messages to terminal width
- ToMarkdown() received a width param but never used it
- Apply lipgloss.Wrap() after herald-md render to break long lines
- Preserves ANSI styles/colors through the wrapping pass
- Fixes overflow for all markdown paths: assistant messages, tool
  bodies, and overlay text
2026-04-08 13:34:33 +03:00
Ed Zynda 5ff28445fd fix(ui): truncate queued and steering message blocks to prevent overflow
- Limit each queued/steering block to 3 visible content lines with ellipsis
- Account for soft-wrapping when counting visual lines
- Truncation is visual only; full text is preserved for scrollback
- Add truncateMessageForBlock helper with wrap-aware line counting
- Add 7 unit tests covering short, exact, overflow, wrapping, and mixed cases
2026-04-08 13:24:26 +03:00
Ed Zynda 13d177e5d0 fix(extensions): use structured logging that respects log levels
Switch from standard log.Printf to charmbracelet/log for extension loading
messages. This ensures DEBUG output only appears when explicitly enabled.

- Remove unconditional WARN log for failed extension loads
- Convert DEBUG loaded extension message to structured log.Debug call
2026-04-08 00:39:21 +03:00
Ed Zynda 3ffc995f27 feat(sdk): add NewTool/NewParallelTool for dependency-free custom tools
- Add ToolOutput struct, TextResult/ErrorResult helpers, and
  ToolCallIDFromContext so SDK consumers can create custom tools
  without importing charm.land/fantasy
- Add NewTool (sequential) and NewParallelTool (concurrent) generic
  constructors with automatic JSON schema generation from struct tags
- Remove dead UpdateUsageFromResponse method and fantasy import from
  internal/ui/cli.go
- Update SDK skill, README, and www/ docs with custom tool examples
  and corrected hook signatures
2026-04-07 22:05:42 +03:00
Ed Zynda b2bd016135 fix(tui): redirect log output to file to prevent TUI corruption
- Add tea.LogToFile in runInteractiveModeBubbleTea to send stdlib log
  output to /tmp/kit/kit.log instead of stderr
- Replace charmbracelet/log with stdlib log in extensions loader,
  runner, watcher, prompts loader, and pkg/kit so all log calls go
  through the redirected stdlib logger
- Leave charmbracelet/log in CLI-only commands (install, acp) and
  acpserver where stderr logging is correct
2026-04-07 21:20:04 +03:00
Ed Zynda 812dedaea2 feat(pkg/kit): add SessionManager interface for custom session backends
Add SessionManager interface to allow pluggable session storage backends.
This enables users to implement custom session managers for databases,
cloud storage, or other persistence mechanisms instead of the default
JSONL file-based TreeManager.

Changes:
- Add SessionManager interface with methods for message storage,
  tree navigation, compaction, and extension data
- Add treeManagerAdapter to wrap existing TreeManager for backward compatibility
- Update Kit struct to use SessionManager interface instead of concrete type
- Add SessionManager option to Options struct
- Update all session-related methods to use interface
- Add documentation for custom SessionManager usage

The default behavior is preserved - when no SessionManager is provided,
Kit automatically uses the TreeManager via the adapter.
2026-04-07 17:41:46 +03:00
Ed Zynda f65b6737f2 feat(sdk): add SkipConfig and DisableCoreTools options
Add two new Options fields for programmatic SDK usage:

- SkipConfig: Skip .kit.yml file loading while still using viper defaults
  and environment variables. Useful for fully programmatic configuration.

- DisableCoreTools: Allow creating agents with 0 tools (chat-only mode) or
  with only custom tools. When true and Tools is empty, no tools are loaded.
  When combined with custom Tools, only those tools are loaded.

Updates documentation in README, pkg/kit/README, skills/kit-sdk/SKILL,
and www/pages/sdk/options.
2026-04-07 17:10:58 +03:00
Ed Zynda 5d45aa196b fix(watcher): remove debug logging that corrupts TUI
Remove charmbracelet/log debug statements from the file watcher that
were writing directly to stderr, corrupting the Bubble Tea terminal UI.

- Remove log.Debug calls for directory operations and file changes
- Remove log.Warn for watcher errors (silently ignore instead)
- Remove the charmbracelet/log import entirely
2026-04-07 16:31:29 +03:00
Ed Zynda debb39f56c fix(ui): show MCP tools in /tools and status bar after async loading
Background MCP tool loading (added in 7e54710) caused tools to not appear
in the UI because tool names and counts were captured at startup before
loading completed. This adds:

- MCPToolsReadyEvent and MCPServerLoadedEvent for progress notifications
- Dynamic GetToolNames/GetMCPToolCount callbacks for live updates
- Per-server status messages as each MCP server finishes loading
- Refresh handlers to update /tools output and status bar when ready
2026-04-07 16:29:09 +03:00
Ed Zynda 7ce6f4fd9e fix(watcher): dynamically watch new subdirectories for skill/prompt reload
- Detect new subdirectory creation in the fsnotify event loop and add
  it to the watcher so files created inside trigger reload events
- Handle cp -r case by checking if new directories already contain
  matching files and scheduling an immediate debounced reload
- Add dirContainsMatchingFiles helper method
- Add tests for both new-subdirectory and copy-with-existing-files cases
2026-04-07 15:01:18 +03:00
Ed Zynda c2f2bdb3d3 feat: auto-reload custom prompts and skills on file change
- Add internal/watcher package with general-purpose ContentWatcher
  using fsnotify, configurable file extensions, and debouncing
- Add ContentReloadEvent and App.NotifyContentReload() for TUI signaling
- Add GetPromptTemplates/GetSkillItems callback fields on AppModelOptions
  following the existing GetExtensionCommands lazy-provider pattern
- Add Kit.ReloadSkills() to re-discover skills from disk
- Wire fsnotify watcher for .kit/prompts/, .kit/skills/, .agents/skills/,
  and global config directories, triggering on .md/.txt changes
- TUI refreshes autocomplete entries and skill list on reload
2026-04-07 14:09:59 +03:00
Ed Zynda 201d14804e fix(ui): prevent double-rendered messages after reasoning-only responses
- Always fire onResponse callback even when response text is empty so
  ResponseCompleteEvent reaches the TUI and resets the StreamComponent
- Check for existing StreamingMessageItem in flushStreamAndPendingUserMessages
  before creating a new StyledMessageItem to avoid duplicate content
- Mark trailing StreamingMessageItem complete on StepComplete, StepCancelled,
  and StepError to freeze live timers and prevent dangling streaming state
2026-04-07 13:52:30 +03:00
Ed Zynda 7e54710d4a perf(agent): load MCP tools asynchronously to speed up startup
Load MCP server tools in the background so the UI appears immediately
instead of blocking until all servers connect. The first LLM call
automatically waits for tools to be ready before proceeding.

Key changes:
- NewAgent() starts MCP loading in a background goroutine and returns
  immediately with core/extension tools only
- GenerateWithLoop() calls ensureMCPTools() to lazily wait and rebuild
  the fantasy agent with full tool set before first LLM call
- Parallelize LoadTools() across all configured MCP servers
- Add WaitForMCPTools() and MCPToolsReady() for status checking
- Refactor SetModel/SetExtraTools to use shared rebuildFantasyAgent()
- Expose async MCP status methods in public SDK
2026-04-07 13:36:10 +03:00
Ed Zynda 88870be4d2 feat: add frequency-penalty and presence-penalty parameters
- Add --frequency-penalty and --presence-penalty CLI flags (0.0-2.0)
- Wire through config, viper, ProviderConfig, and fantasy agent options
- Support in config file, env vars (KIT_FREQUENCY_PENALTY), and SDK
- Pass to Ollama via options map (frequency_penalty, presence_penalty)
- Apply on both initial agent creation and runtime model swap
2026-04-06 10:52:33 +03:00
Ed Zynda 46bf809715 chore(models): update embedded models.json from models.dev
- Providers: 97 -> 109 (+12 new)
- Models: 3039 -> 4156 (+1117 new)
- New providers: alibaba-coding-plan, alibaba-coding-plan-cn, clarifai,
  dinference, drun, llmgateway, perplexity-agent, tencent-coding-plan,
  the-grid-ai, xiaomi-token-plan-ams, xiaomi-token-plan-cn,
  xiaomi-token-plan-sgp
2026-04-06 09:50:43 +03:00
Ed Zynda e19e9642a2 feat(session): include system prompt and model in shared sessions
Add SystemPromptEntry type to capture system prompt, model, and provider
when sharing sessions via /share command. The entry is inserted into the
JSONL after the header and displayed in the web viewer as a collapsible
section with a model badge.

- Add SystemPromptEntry with Content, Model, and Provider fields
- Capture current system prompt and model at share time
- Display in web viewer with collapsible UI and model badge
- Update documentation for /share command
2026-04-04 19:33:02 +03:00
Ed Zynda 32675b8b35 chore(deps): update all go module dependencies
- mcp-go v0.46.0 → v0.47.0
- herald v0.11.0 → v0.13.0
- herald-md v0.2.0 → v0.3.0
- smithy-go v1.24.2 → v1.24.3
- otel v1.42.0 → v1.43.0
- googleapis/gax-go v2.20.0 → v2.21.0
- google.golang.org/api v0.273.1 → v0.274.0
- runewidth v0.0.21 → v0.0.22
- azure-sdk-internal v1.11.2 → v1.12.0
- various aws-sdk-go-v2 sub-modules patched
2026-04-04 18:11:56 +03:00
Ed Zynda aecce001ee feat(mcp): add OAuth support for remote MCP servers
- Add MCPAuthHandler interface at SDK level (pkg/kit/) so all consumers
  (CLI, TUI, SDK embedders) control the OAuth UX through one interface
- Default handler opens system browser + local callback server with PKCE
- CLIMCPAuthHandler wraps default with status messages (stderr pre-TUI,
  system messages via TUI event system once running)
- Always enable OAuth on remote transports (streamable HTTP, SSE) when
  handler is configured; harmless for servers that don't need it
- Dynamic client registration when no client ID is pre-configured
- File-based TokenStore persists tokens to ~/.config/.kit/mcp_tokens.json
  keyed by server URL so users don't re-auth on restart
- Catch OAuthAuthorizationRequiredError at connection init (startup) and
  tool execution (mid-session token expiry), run auth flow, retry once
- Fix error wrapping (%v -> %w) in connection pool so errors.As can
  unwrap through the chain to find OAuth errors
- Thread AuthHandler through MCPToolManager -> AgentConfig ->
  AgentCreationOptions -> AgentSetupOptions -> kit.Options
2026-04-04 17:41:57 +03:00
Ed Zynda 32d73171fd fix(extensions): write manifest Include in single pass and preserve on update
- InstallWithInclude wrote manifest twice via two different code paths,
  with the first write missing Include; unify into shared install() method
  that writes the manifest once with all fields including Include
- Update() now reads the existing manifest entry to preserve Include and
  Installed timestamp instead of constructing a fresh entry from scratch
2026-04-04 17:19:00 +03:00
Ed Zynda 265fd2ec0c fix(extensions): skip _test.go files and non-extension examples/ subdirs
- Filter out _test.go files in findExtensionsInDir, findExtensionsInRepo,
  and ScanForExtensions to prevent Yaegi from loading test files
- Narrow examples/ traversal so only recognized extension directories
  (extensions/, ext/, *-ext/, *-extensions/) are scanned, not arbitrary
  subdirs like examples/sdk/ that import pkg/kit
2026-04-04 16:44:13 +03:00
Ed Zynda efebf2eba6 fix(kit-telegram): add typing indicator and config fallback to global path
- Send sendChatAction("typing") every 4s while agent is processing,
  started on AgentStart and stopped on AgentEnd/SessionShutdown
- configPath() now checks project-local .kit/ first, then falls back
  to ~/.config/kit/kit-telegram.json for cross-project portability
2026-04-04 16:33:08 +03:00
Ed Zynda f7b655ae33 feat(extensions): add Abort, IsIdle, Compact, SendMultimodalMessage, GetSessionUsage to Context
- ctx.Abort(): cancel current agent turn and clear queue without
  injecting a new message (App.Abort + App.IsBusy methods)
- ctx.IsIdle(): check whether the agent is currently processing
- ctx.Compact(CompactConfig): trigger async context compaction with
  OnComplete/OnError callbacks (App.CompactAsync method)
- ctx.SendMultimodalMessage(text, []FilePart): send text+image messages
  to the agent, bridging ext.FilePart to fantasy.FilePart via RunWithFiles
- ctx.GetSessionUsage() SessionUsage: expose aggregated session token
  usage and cost from the UsageTracker

New types: CompactConfig, FilePart, SessionUsage
Wired in both context setups in cmd/root.go with nil-guard defaults
in runner.go and Yaegi symbol exports in symbols.go
2026-04-04 15:01:02 +03:00
Ed Zynda 35982b41ad fix(pkg): transparently handle <think> tags in stream
Move reasoning tag detection from the provider and UI layers into the agent layer. This prevents raw XML tags from leaking into text streams while ensuring structured reasoning events are emitted correctly for all callers.
2026-04-03 13:49:12 +03:00
Ed Zynda 788e3b71fd feat(config): per-model baseUrl and apiKey for custom models
- Add `baseUrl` and `apiKey` fields to CustomModelConfig (config and models packages)
- Store them on ModelInfo so they travel through the registry
- createCustomProvider resolves URL/key from model definition first,
  falling back to global --provider-url / --provider-api-key
- Fix registry initialisation: call ReloadGlobalRegistry() in InitConfig()
  so customModels from config are visible on startup (not just at init time)
- Include custom provider in GetLLMProviders() so custom models appear
  in the /model selector
- Hide the built-in custom/custom stub from the selector when user-defined
  custom models are present
2026-04-03 12:37:14 +03:00
Ed Zynda 3496bc2684 feat(ui): add bordered container and improved styling to session selector
- Add full-width bordered container with rounded border and primary color
- Add max height constraint to prevent terminal overflow
- Improve selection highlighting with inverted colors matching PopupList style
- Change cursor indicator from › to > for consistency
- Add separator lines between header, content, and footer
- Add footer showing current filter mode
2026-04-02 17:20:55 +03:00
Ed Zynda 997c7d15ff fix: include pasted images in steering messages
Steering messages (Ctrl+S during agent work) now carry file attachments
just like queued messages do. Previously, pasted images were silently
dropped when steering.

Changes:
- Add SteerMessage struct with Text and Files fields
- Update steer channel from chan string to chan SteerMessage
- Add SteerWithFiles methods through the stack (UI, app, SDK)
- Update PrepareStep to include files in injected user messages
2026-04-02 17:19:34 +03:00
Ed Zynda 83246e47d5 feat(ui): add bordered container and improved styling to tree selector
- Add full-width bordered container with rounded border and primary color
- Add max height constraint to prevent terminal overflow
- Improve selection highlighting with inverted colors matching PopupList style
- Change cursor indicator from › to > for consistency
- Use MutedBorder for tree lines and Success color for active marker
- Update search display format to match PopupList (
2026-04-02 17:18:16 +03:00
Ed Zynda 50e7b78c33 fix(ui): strip herald CodeBlock padding to fix mouse selection off-by-one
Herald's codeBlockWithLineNumbers() hardcodes PaddingTop(1) and
PaddingBottom(1), adding invisible blank lines with background color
above and below the code content. These padding lines occupy line
indices in the rendered item but are visually indistinguishable from
empty space, causing mouse click coordinates to map to the wrong
content line (consistently 1 row off in tool output blocks).

Strip the padding lines after CodeBlock rendering since the Compose
separator above and Figure caption below already provide adequate
visual spacing.
2026-04-02 16:49:44 +03:00
Ed Zynda b937af3056 refactor(ui): use herald Figure component for grep tool output
Add dedicated renderGrepBody function for the grep tool, replacing the
previous behavior of routing it through renderBashBody. The grep tool now:

- Shows a caption with total match count (e.g., '8 matches' or '1 match')
- Displays truncation info when matches exceed maxLsLines
- Uses consistent Figure component styling with ls, read, find, and bash tools
- Uses 'match/matches' terminology appropriate for grep results
2026-04-02 16:12:48 +03:00
Ed Zynda a5e995c750 refactor(ui): use herald Figure component for find tool output
Add dedicated renderFindBody function for the find tool, replacing the
previous behavior of routing it through renderBashBody. The find tool now:

- Shows a caption with total result count (e.g., '12 results')
- Displays truncation info when results exceed maxLsLines
- Uses consistent Figure component styling with ls, read, and bash tools
2026-04-02 16:11:49 +03:00
Ed Zynda e95e08a699 refactor(ui): use herald Figure component for ls tool output
Apply the same Figure component pattern to the ls tool for consistency
with read and bash tools. The caption now appears below the directory
listing and shows the count of hidden entries when truncated.
2026-04-02 16:10:00 +03:00
Ed Zynda bcaf92f62a refactor(ui): use herald Figure component for read and bash tool output
Replace inline truncation hints and exit code labels with herald's
Figure component. Captions now appear below content and show:

- read: filename • lines X-Y of Z • offset=N to continue
- bash: N more lines • exit code N

This provides consistent visual grouping and cleaner metadata
display for tool output blocks.
2026-04-02 16:09:17 +03:00
Ed Zynda ead4afbfe6 fix(subagent): prevent instant failure from already-dead parent contexts
- Replace detachedWithCancel (goroutine-based) with context.WithoutCancel
  + valuesContext; the old goroutine would fire immediately if the parent
  was already cancelled/deadline-exceeded, causing 'failed after 0s'
- Kit.Subagent() pre-flight: if the incoming ctx is already done, reset
  to context.Background() before applying the subagent timeout
- Both Subagent() error paths now return a non-nil *SubagentResult with
  Elapsed set, so the tool response always shows accurate timing
- Narrow viperInitMu scope in Kit.New(): snapshot viper state + call
  BuildProviderConfig under the lock, then release before SetupAgent /
  MCP loading; parallel subagent spawns no longer serialise on viper I/O
- AgentSetupOptions gains ProviderConfig + scalar fields so SetupAgent
  can skip viper reads when a pre-built config is supplied
- Add subagent_test.go covering the fixed context detachment behaviour
2026-04-02 15:54:47 +03:00
Ed Zynda 685aaf207f feat(extensions): add hot-reload with file watching and /reload-ext command
- Add fsnotify-based file watcher that auto-reloads extensions on .go
  file changes in autoloaded dirs with 300ms debounce
- Add /reload-ext built-in command (alias /re) for manual reload
- Add Agent.SetExtraTools() so extension tools update on reload
  instead of being baked in at agent creation time
- Run reload async via tea.Cmd to avoid prog.Send() deadlock when
  extension handlers call ctx.Print() during SessionStart/Shutdown
- Wire watcher lifecycle into cmd/root.go with graceful shutdown
2026-04-02 15:41:54 +03:00
Ed Zynda 76ff6c9639 style(ui): segment KITT scanner LEDs and center logo text
- Break scanner bar into individual LED segments with single-space gaps
- Center KIT text over the scanner bar (13-space indent for all lines)
- Maintain original 46-char total width for the scanner bar
2026-04-02 15:11:01 +03:00
Ed Zynda 1cf24ee5de fix(core): return error when read tool is used on a directory
- Return an error response guiding the agent to use ls instead
- Remove unused readDirectory helper function
2026-04-02 14:45:33 +03:00
Ed Zynda c9637090fa feat(subagent): return early error for invalid model instead of silent fallback
- Add ValidateModelString() to ModelsRegistry for format, provider,
  and model name validation with typo suggestions
- Validate model in Kit.Subagent() before expensive Kit.New() setup
- Remove silent fallback to parent model on creation failure
- Error propagates as tool result so calling agent can self-correct
- Add registry_test.go covering format, provider, and suggestion cases
2026-04-02 14:45:03 +03:00
Ed Zynda 0ff0ff42ab fix(ui): wrap tool error output in caution alert block
Prevent tool error text from spilling into the surrounding layout
by rendering it inside a herald Caution alert container.
2026-04-02 14:39:29 +03:00
Ed Zynda a4fb32ff2b feat(ui): add reusable PopupList and render /model as overlay
- Add PopupList: generic themed popup with fuzzy search, scrolling,
  keyboard navigation, and centered overlay rendering
- Refactor ModelSelectorComponent to delegate to PopupList instead
  of implementing its own full-screen rendering and input handling
- Render /model selector as a centered overlay on top of the chat
  view instead of replacing the entire screen
- PopupList accepts a pluggable FilterFunc for domain-specific
  fuzzy matching (model selector wires its own scoring)
- Add 11 tests for PopupList covering navigation, search, selection,
  cancellation, filtering, rendering, and edge cases
2026-04-02 14:39:21 +03:00
Ed Zynda 7d2f078111 fix(ui): freeze reasoning counter when last token is processed
- Wire fantasy's OnReasoningEnd callback through the full event chain:
  agent → SDK (ReasoningCompleteEvent) → app → TUI
- Freeze reasoning duration in both StreamComponent and
  StreamingMessageItem as soon as reasoning ends, not when the
  next assistant text chunk arrives
- Fix accent color on duration label in render.ReasoningBlock to
  match the live streaming style (VeryMuted prefix + Accent duration)
2026-04-02 14:18:42 +03:00
Ed Zynda b0b66941ab fix(extensions): batch go-edit-lint per turn and fix OnAgentEnd StopReason docs
- Refactor go-edit-lint to collect edited .go files during the agent
  turn via OnToolResult, then run gopls + golangci-lint once in
  OnAgentEnd instead of after every individual edit/write call
- Use ctx.SendMessage() to inject diagnostics as a follow-up prompt
  when issues are found, replacing the old tool-result rewriting
- Show a green 'all clean' block when no issues are detected
- Fix StopReason docs in skills/kit-extensions/SKILL.md: the value is
  'error' on failure, 'completed' when the LLM returns empty, or the
  raw provider value (e.g. 'stop', 'end_turn') passed through — not
  the previously documented 'completed'/'cancelled'/'error' enum
2026-04-02 14:04:41 +03:00
Ed Zynda cbb7387a72 fix(test): add return after t.Fatal to silence SA5011 nil-deref warnings
- internal/ui/model_test.go: bashItem nil check
- pkg/extensions/test/harness_test.go: footer and result nil checks
2026-04-01 21:24:02 +03:00
Ed Zynda 19430b0ecb chore(ui): remove dead toast and clipboard code
Remove 8 unused exports from clipboard package:
- CopyToClipboardWithMessage, IsClipboardSupported
- ToastMsg, ToastType, ToastInfo, ToastSuccess, ToastWarning, ToastError

These were remnants of a toast notification feature that was never
wired up. No callers exist anywhere in the codebase.
2026-04-01 21:11:00 +03:00
Ed Zynda 8e3cfeede5 fix(ui): correct mouse selection Y-offset for reasoning blocks
The getItemAndLineAtY() method was using item.Height() which returns 0
for reasoning blocks (StreamingMessageItem with role='reasoning') because
their render cache is intentionally never populated (they include a live
duration timer).

This caused all items below a reasoning block to have incorrect Y
coordinates — clicking on the reasoning text would highlight the
assistant text below it instead.

Two fixes:
1. getItemAndLineAtY() now uses renderedHeight() which calls Render()
   and counts lines — matching exactly what View() does. This is the
   single source of truth for item height during hit-testing.

2. StreamingMessageItem.Height() now falls back to Render(0) when
   cachedRender is empty, fixing the same issue for other callers
   (GotoBottom, ScrollBy, clampOffset, etc.).
2026-04-01 18:15:04 +03:00
Ed Zynda 4fa5775974 feat(ui): implement character-level mouse text selection and copy
Implement crush-style mouse text selection with character-level precision,
replacing the previously disabled stub implementation.

Architecture:
- New selection package (internal/ui/selection/) handles all coordinate
  math, word boundary detection, and cell-level ANSI text manipulation
- ScrollList upgraded with proper mouse down/drag/up flow supporting
  single click (character drag), double click (word), triple click (line)
- Model.go wires BubbleTea mouse events through to ScrollList with
  proper viewport Y-offset adjustment for the scrollback area

Key features:
- Character-level selection using ultraviolet ScreenBuffer for ANSI-aware
  cell parsing — correctly handles styled text, emoji, CJK wide chars
- Word selection via UAX#29 Unicode segmentation (clipperhouse/uax29)
- Display-width-aware columns via clipperhouse/displaywidth (not bytes)
- Dual clipboard: OSC 52 (remote terminals) + native (atotto/clipboard)
- Multi-click detection with 400ms threshold and 2px tolerance
- Mouse event throttling via existing MouseModeCellMotion
- Selection cleared on any keypress for clean UX

Dependencies (all already indirect in go.mod):
- github.com/charmbracelet/ultraviolet (ScreenBuffer, cell manipulation)
- github.com/charmbracelet/x/ansi (ANSI strip, StringWidth)
- github.com/clipperhouse/displaywidth (grapheme display width)
- github.com/clipperhouse/uax29/v2 (Unicode word segmentation)
2026-04-01 18:05:48 +03:00
Ed Zynda 4e7d823ee4 feat(ui): make /fork create new session file matching Pi behavior
- Add ForkToNewSession method to create new session with history up to target
- Add NewTreeSelectorForFork showing only user messages (flat list)
- Update performFork to create and switch to new session file
- Update /fork command description in docs and help text

Previously /fork just branched within the same session file like /tree.
Now /fork creates a completely new session file with parent_session reference,
matching Pi's behavior exactly.
2026-04-01 16:10:55 +03:00
Ed Zynda 7a16c76adc fix(ui): trim whitespace when loading session messages to prevent empty blocks
When loading session history, some assistant messages contain text parts
with only whitespace (e.g., single space ' '). These were being rendered
as empty message blocks, causing extra vertical spacing in the UI.

Fix by trimming whitespace from message content before checking if it's
non-empty in renderSessionHistory().

Changes:
- Apply strings.TrimSpace() to user message content before rendering
- Apply strings.TrimSpace() to assistant message content before rendering

This prevents empty/whitespace-only message blocks from being added to
the scrollback when resuming sessions.
2026-04-01 15:11:42 +03:00
Ed Zynda 70a21ee73a refactor(ui): extract shared message rendering functions
Extract pure rendering functions into internal/ui/render/blocks.go
to eliminate code duplication between streaming and historical
message rendering paths.

Changes:
- Create render package with UserBlock, AssistantBlock, ReasoningBlock,
  SystemBlock, ErrorBlock, and ToolBlock functions
- Update MessageRenderer methods to use shared render functions
- Update StreamingMessageItem to use shared render functions
- Reduce ~77 lines of duplicated code across message_items.go and messages.go

All existing tests pass, no functional changes.
2026-04-01 14:59:27 +03:00
Ed Zynda 28d2de8f39 Phase 1: Reorganize UI leaf utilities into subpackages
Moved leaf utility files to subpackages for better organization:
- events.go -> core/ (core message types)
- clipboard.go -> clipboard/ (clipboard operations)
- commands.go -> commands/ (slash commands)
- file_processor.go -> fileutil/ (file attachment processing)
- preferences.go -> prefs/ (theme/model preferences)
- enhanced_styles.go, styles.go, themes.go -> style/ (theming system)

Added exports.go to re-export commonly used types for backward
compatibility. External importers can still use ui.XXX without
changes.

All tests pass, basic smoke test successful.
2026-04-01 13:54:10 +03:00
Ed Zynda 7f192ae850 feat(ui): improve slash command popup contrast with full-width backgrounds
- Change border from MutedBorder to Primary for visibility
- Add full-width background styles for all popup items
- Use inverse colors for selected item (primary bg, background fg)
- Add background to scroll indicators and footer
- Add bottom margin for visual depth/shadow effect
2026-04-01 13:35:20 +03:00
Ed Zynda 9f6746ded9 fix(ui): re-enable auto-scroll on new message submission
Auto-scroll was being disabled when users manually scrolled (mouse wheel,
PgUp, etc.) but never re-enabled. Now it reactivates when submitting a
new message so the conversation view jumps to the bottom to show the
latest content.
2026-04-01 13:29:40 +03:00
Ed Zynda 7514d3a0ff chore(deps): update go and npm dependencies
- github.com/indaco/herald v0.10.0 → v0.11.0
- github.com/indaco/herald-md v0.1.0 → v0.2.0
- google.golang.org/api v0.273.0 → v0.273.1
- google.golang.org/genai v1.52.0 → v1.52.1
- google.golang.org/grpc v1.79.3 → v1.80.0
- gonum.org/v1/gonum v0.16.0 → v0.17.0
- add npm and www package-lock.json files
2026-04-01 13:24:36 +03:00
Ed Zynda c83281a52b docs: add feature-request prompt for GitHub feature requests
Add a dedicated /feature-request prompt that guides users through creating
well-formed feature requests using the GitHub feature_request template.

The prompt focuses on:
- Problem-first description
- Clear motivation and use cases
- Optional proposed implementation
- Conventional commit-style titles (feat: ...)

Usage: /feature-request <description of the feature>
2026-04-01 13:22:14 +03:00
Ed Zynda 4515bb92c2 docs: update file-issue prompt to use GitHub issue templates
The file-issue prompt now references the structured GitHub issue templates
(bug_report, feature_request, documentation) and guides users to use the
--template flag with gh issue create for consistent issue formatting.
2026-04-01 13:21:20 +03:00
Ed Zynda e326b84204 chore: add GitHub issue templates and file-issue prompt
Add structured GitHub issue templates for:
- Bug reports (with reproduction steps, code, component)
- Feature requests (with motivation and proposed implementation)
- Documentation issues

Also add a /file-issue kit prompt for quickly filing issues from the TUI.

The templates enforce conventional commit-style titles and include
checklists to ensure issues are well-formed before submission.
2026-04-01 13:20:43 +03:00
Ed Zynda 1b93049b8e fix(ui): remove j/k navigation from fuzzy selectors
Remove 'j' and 'k' keybindings from model, session, and tree selectors
to allow typing those characters for fuzzy filtering. Navigation now
uses only arrow keys (↑/↓) which matches the existing help text.
2026-04-01 13:11:44 +03:00
Ed Zynda 4912449dda fix(ui): render selectors in alt screen buffer
Fix /resume, /model, and /tree selectors to render in the alternate
screen buffer instead of terminal scrollback. All three selector
components now set AltScreen=true on their tea.View returns.
2026-04-01 13:09:23 +03:00
Ed Zynda b70cce4f34 refactor(ui): remove pre-alt-screen dead code and boilerplate
- Remove scrollbackBuf, appendScrollback(), drainScrollback() and all
  call sites — the entire terminal scrollback pipeline was dead code
  since the alt screen migration
- Remove StreamComponent.render(), renderCache, renderDirty,
  scrollbackFlushedLines, viewContent(), and ConsumeOverflow() body —
  rendering is now handled by StreamingMessageItem in the ScrollList
- Remove SetHeight and ConsumeOverflow from streamComponentIface since
  height is managed by ScrollList and overflow is a no-op
- Remove redundant AltScreen/MouseMode/ReportFocus/KeyboardEnhancements
  boilerplate from 6 child View() methods — parent already sets these
- Convert two orphan appendScrollback calls (extension default text,
  shell command output) to proper ScrollList message items
- Update ~30 stale comments referencing tea.Println and scrollback buffer
2026-04-01 01:13:19 +03:00
Ed Zynda 4c566836b2 refactor(ui): move startup banner into ScrollList, fix /resume rendering
- Render ASCII logo and startup info exclusively in the ScrollList
  instead of printing to stdout/terminal scrollback
- Remove PrintStartupInfo() and move kitBanner() to ui.KitBanner()
- Fix separator spacing: use single pre-rendered item with embedded
  blank lines to avoid left-border artifacts on spacing rows
- Rewrite renderSessionHistory() to populate ScrollList with proper
  MessageItems instead of legacy appendScrollback() calls
- Clear m.messages on /clear, /new, and /resume so the ScrollList
  resets correctly when switching sessions
- Add pendingGotoBottom flag to defer scroll-to-bottom until after
  distributeHeight() recalculates the correct viewport height
- Fix pre-existing test failures: initialize scrollList in test helper,
  update 5 tests from tea.Println assertions to ScrollList checks
2026-04-01 00:39:32 +03:00
Ed Zynda bb3261883a Add visual separator after startup info in ScrollList
Added a horizontal rule (────) with blank lines above and below to
visually separate the startup info from the conversation history.

The separator uses theme.Border color and spans 80 characters, providing
a clear visual break between startup messages and the chat content.

This makes it easier to distinguish where the conversation starts when
scrolling back through history.
2026-03-31 19:07:56 +03:00
Ed Zynda 512d0f16ce Show startup info in ScrollList (alt screen mode)
Added AddStartupMessageToScrollList() method that renders startup info
(model, context, skills, extensions, MCP tools) and extension startup
messages as system messages in the ScrollList.

This ensures startup info is visible and scrollable in alt screen mode,
rather than being printed before BubbleTea starts and becoming hidden
when alt screen takes over.

Changes:
- AppModelOptions: Added StartupExtensionMessages field
- AppModel: Store and render startup messages in Init()
- AddStartupMessageToScrollList(): Renders startup info + extension messages
- cmd/root.go: Pass startupExtensionMessages to NewAppModel

The startup info now appears at the top of conversation history and can
be scrolled back to at any time.
2026-03-31 19:03:21 +03:00
Ed Zynda 8159431ce4 Prevent scrolling past bottom of content in ScrollList
Enhanced clampOffset() to detect when the viewport has scrolled past the
bottom of the content (would show empty space) and automatically reposition
to show the last line of content at the bottom of the viewport.

This prevents the 'floating' effect where multiple PgDn or scroll down
operations would push content off the top while showing blank space below.

The clamping logic:
1. Calculates total content height
2. If content fits in viewport, forces position to top
3. Otherwise, checks if remaining content < viewport height
4. If so, repositions to show exactly the last line at viewport bottom

Also updated clampOffset to use rendered height calculation (handles
non-cached items like reasoning blocks) instead of cached Height().
2026-03-31 18:56:18 +03:00
Ed Zynda 9f9f265fb3 Fix autoscroll for streaming messages (iteratr pattern)
Root cause: GotoBottom() was calculating heights using Height() which returns
0 for non-cached items. Reasoning blocks never cache renders due to live
duration updates, causing incorrect scroll calculations during reasoning →
assistant transitions.

Fix: Calculate heights directly from rendered strings instead of relying on
cached Height() values. This ensures accurate scroll positioning for all
message types.

Changes:
- ScrollList.GotoBottom(): Render items and calculate height from string
- ScrollList.AtBottom(): Same pattern for bottom detection
- appendStreamingChunk(): Call GotoBottom() directly for existing messages
- refreshContent(): Remove redundant GotoBottom() (handled by SetItems)

Tested with 'explore this repo' prompt - autoscroll now works correctly
throughout reasoning and assistant streaming phases.
2026-03-31 18:53:18 +03:00
Ed Zynda 9d38349091 fix: resolve all golangci-lint issues
- Use max() instead of if statement for min value
- Use strings.SplitSeq for efficient iteration
- Use range over int instead of explicit loop counter
- Remove unused functions:
  - InputComponent.renderPopup()
  - AppModel.renderStream()
  - AppModel.renderStreamingBashOutput()
  - AppModel.printCompactResult()
2026-03-31 17:49:25 +03:00
Ed Zynda fec8bac800 refactor: remove fallback from flushStreamContent
StreamingMessageItem must exist when flushing - no fallbacks.
2026-03-31 17:45:35 +03:00
Ed Zynda e76f5f3d45 fix: prevent duplicate text when flushing streaming content before tool calls
flushStreamContent() was creating a new StyledMessageItem when tool calls
started, but we already had a StreamingMessageItem with the same content.

Now we:
- Mark the existing StreamingMessageItem as complete
- Only create a new message as fallback if no streaming item exists

This fixes text duplication when assistant messages precede tool calls.
2026-03-31 17:43:50 +03:00
Ed Zynda 1ad493c5c7 feat: cap streaming bash output height and replace with tool result
- Limit streaming bash output to 20 lines max during live display
- Remove streaming bash item when tool completes
- Replace with truncated tool result block
- Expand background color to full terminal width with proper indentation
- Matches renderBashBody styling (lineIndent + width)

This prevents long-running commands from growing the UI forever while
still showing live output up to a reasonable height.
2026-03-31 17:42:32 +03:00
Ed Zynda ea6ddc8792 feat: integrate streaming bash output into ScrollList
- Add StreamingBashOutputItem to message_items.go
- Update ToolOutputEvent handler to append chunks to bash item in ScrollList
- Remove old renderStreamingBashOutput() that broke layout
- Bash output now streams inline with messages instead of separate section
- Auto-scrolls to bottom during streaming
- Marks bash item complete on ToolResultEvent

Fixes layout breaking when bash commands produce streaming output.
2026-03-31 17:38:03 +03:00
Ed Zynda 6d4e8bcec5 feat: add streaming support for compaction summaries
- Add StreamCallback parameter to compaction.Compact() for streaming text deltas
- Update generateSummary() to use fantasy.Agent.Stream() when callback provided
- Fix compactSplitTurn() to stream both history and turn prefix summaries
- Add SDK event subscription in CompactConversation() goroutine
- Update UI to handle streaming compaction like regular assistant messages
- Compaction summaries now stream word-by-word instead of appearing all at once

Fixes issue where compaction would show incomplete context (e.g. only 'nce')
by ensuring both history summary and turn prefix are streamed to the UI.
2026-03-31 17:33:51 +03:00
Ed Zynda e2ed345280 fix: center slash command popup overlay to prevent bottom overflow
- Move popup rendering from inline (below input) to centered overlay
- Add RenderPopupCentered() method to InputComponent
- Implement overlayContent() helper for line-by-line merging
- Popup now appears in center of screen above all content
- Prevents overflow issues when typing / at bottom of terminal
2026-03-31 16:45:57 +03:00
Ed Zynda e542eb797e fix: freeze reasoning duration counter on transition to assistant text
- Detect role transition in appendStreamingChunk (reasoning → assistant)
- Mark reasoning StreamingMessageItem as complete when assistant text starts
- Duration counter now freezes immediately when reasoning ends
- Add live duration counter that updates during reasoning streaming
- Store startTime and finalDuration for proper counter behavior
2026-03-31 16:40:41 +03:00
Ed Zynda e631fc1b17 feat: add live streaming text to ScrollList viewport
- Create StreamingMessageItem that accumulates chunks and re-renders
- Update StreamChunkEvent/ReasoningChunkEvent to append to StreamingMessageItem
- Enable live streaming display within ScrollList (iteratr-style)
- Mark streaming items as complete on ResponseCompleteEvent
- Reasoning and assistant text now stream in real-time in the viewport
2026-03-31 16:35:43 +03:00
Ed Zynda 290c5a4774 chore: disable select/copy functionality but keep plumbing
Disable the mouse selection and keyboard copy features while keeping
all the supporting code infrastructure:

- Comment out MouseClickMsg, MouseMotionMsg, MouseReleaseMsg handlers
- Comment out keyboard shortcuts (c/y keys) for copying
- Keep all ScrollList selection tracking code
- Keep clipboard utilities (clipboard.go)
- Keep highlighting functions in scrolllist.go

This allows the features to be easily re-enabled later while keeping
the codebase clean for now.
2026-03-31 16:29:01 +03:00
Ed Zynda 287d60c31e feat: add visual selection highlighting with theme colors
Implement visual feedback for text selection in the scrollback:

- Add isLineInSelection() to check if a line is within the current selection
- Add applyHighlight() using the theme's Highlight color for selected lines
- Add applyFocusIndicator() using MutedBorder for focused items
- Update View() to apply highlighting during rendering
- Add getItemAndLineAtY() for precise mouse position tracking
- Track both item index and line index within item for selection

Selection highlighting uses the user's selected theme colors for
consistent visual feedback across all themes.
2026-03-31 16:23:46 +03:00
Ed Zynda 3d45d98895 feat: add crush-style copy+paste support
Implement mouse selection and keyboard copy functionality following
crush's patterns:

- Add clipboard.go with dual-write clipboard support (OSC 52 + system)
- Add CopySelection tracking to ScrollList for text selection
- Implement HandleMouseDown/HandleMouseDrag/HandleMouseUp methods
- Add keyboard shortcuts (c/y) for copying messages
- Mouse click+drag to select text, auto-copy on release
- Toast notifications for copy feedback

Note: Full text extraction from selection requires additional work to
properly extract raw text from styled message content.
2026-03-31 16:19:58 +03:00
Ed Zynda db4be4f9a2 feat: implement full alt screen mode with in-memory scrollback
Add ScrollList component for viewport-based message history with lazy
rendering and offset-based scrolling. Implement MessageItem system for
user, assistant, tool, system, and error messages with pre-rendered
styled content from MessageRenderer.

Key changes:
- ScrollList: height-constrained viewport with itemGap support, padding
  to ensure fixed height for sticky bottom layout
- MessageItem implementations with preRendered content from MessageRenderer
- refreshContent() pattern for efficient ScrollList updates
- Mouse wheel scrolling (3 lines per tick) with auto-scroll behavior
- All message types (user, assistant, tool, system, error, extension)
  properly added to in-memory scrollback
- PgUp/PgDn/Alt+Home/Alt+End keybindings for navigation
- Removed tea.Println() calls for alt screen compatibility
- Sticky bottom layout: input, separator, status bar fixed at bottom

Files added:
- internal/ui/scrolllist.go (ScrollList component)
- internal/ui/message_items.go (MessageItem implementations)

Files modified:
- internal/ui/model.go (main integration)
- internal/ui/*.go (alt screen config for components)
2026-03-31 16:12:30 +03:00
Ed Zynda 80093e69ed remove 2026-03-31 15:08:46 +03:00
Ed Zynda ef519ba517 feat(acpserver): implement session/set_model ACP method
Add SetSessionModel method to the ACP agent, allowing clients to change
the active LLM model for a session at runtime. The method looks up the
session in the registry and delegates to kit.SetModel().

Verified with smoke test: session/set_model now returns success instead
of 'Method not found' error.
2026-03-31 15:05:23 +03:00
Ed Zynda d79eb1f0fa refactor(pkg/kit): use fantasy type aliases for LLM types with clean SDK names
Replace concrete LLMMessage/LLMUsage/LLMResponse/LLMFilePart structs with
type aliases to charm.land/fantasy types, exposing them under clean
LLM-prefixed names. This gives SDK consumers full access to rich message
parts (tool calls, reasoning, tool results) without importing fantasy
directly.

Key changes:
- LLM types are now aliases: LLMMessage=fantasy.Message, etc.
- Added aliases for all part types: LLMTextPart, LLMToolCallPart, etc.
- Re-exported constructors: NewLLMUserMessage, NewLLMSystemMessage
- Removed lossy conversion helpers (llm_convert.go, fantasyMsgsToKit)
- Updated all internal packages to use aliases consistently
- Added ACP smoke test script and prompt template
- Fixed lint issues: unused vars, modernize min() usage
2026-03-31 14:26:49 +03:00
Ed Zynda ac8ee6525d refactor(pkg/kit): replace fantasy type aliases with concrete LLM* structs
Remove charm.land/fantasy from the public API surface of pkg/kit by
replacing the four type aliases with concrete Kit-owned structs:

- LLMMessage  {Role LLMMessageRole, Content string}
- LLMUsage    {InputTokens, OutputTokens, TotalTokens, ...}
- LLMResponse {Content, FinishReason, Usage}
- LLMFilePart {Filename, Data []byte, MediaType}

Add LLMMessageRole type with user/assistant/system/tool constants.

Introduce pkg/kit/llm_convert.go as the single boundary layer where
Kit types convert to/from fantasy types internally. All callers in
pkg/kit, pkg/kit/compaction.go, pkg/kit/extensions_bridge.go, and
internal/app/app.go cross through this layer.

ContextPrepareHook.Messages and ContextPrepareResult.Messages change
from []fantasy.Message to []LLMMessage. extensions_bridge.go drops
its fantasy and strings imports entirely.

internal/app/app_test.go switches &fantasy.Usage{} to &kit.LLMUsage{}.

Add seven new tests in types_test.go covering concrete construction,
role constants, JSON snake_case tags, and round-trip conversion.
2026-03-31 13:44:05 +03:00
Ed Zynda e35e8382d6 fix(app): correct drainQueue QueueUpdatedEvent emission
- Remove always-zero queueLen variable: len() was measured after
  clearing the queue, so it was unconditionally 0 and the variable
  was dead code
- Emit QueueUpdatedEvent{Length: 0} explicitly to make intent clear
- Also emit QueueUpdatedEvent when a second batch is pulled mid-loop;
  previously the queue was silently cleared without notifying the UI,
  leaving queuedMessages stuck in the displayed-queued state forever
2026-03-31 13:19:09 +03:00
Ed Zynda fbb3408a25 chore(prompts): add new-prompt template
/new-prompt <description> scaffolds a new .kit/prompts/ template.
Explains the file format, argument substitution syntax, naming
conventions, and writing guidelines.
2026-03-31 13:04:11 +03:00
Ed Zynda 44fed9a647 chore(prompts): add commit-push prompt template
Provides a /commit-push slash command that reviews git status and diff,
stages all changes, writes a conventional commit message, commits, and
pushes to the current branch.
2026-03-31 13:03:14 +03:00
Ed Zynda e7f11487b9 remove CompactRenderer and --compact flag
The compact display mode was purely a UI concern that added complexity
without providing unique value. Anyone wanting compact-style formatting
can implement it as an extension using the Renderer interface.

- Delete internal/ui/compact_renderer.go
- Remove renderToolBodyCompact and all compact tool body renderers from
  tool_renderers.go
- Simplify NewCLI(debug bool) — drop compact parameter
- Simplify NewStreamComponent(width, modelName) — drop compactMode parameter
- Remove CompactMode from AppModelOptions, app.Options, CLISetupOptions
- Remove Compact from internal/config/config.go
- Remove --compact flag, var, and viper binding from cmd/root.go
- Update format.go: remove CompactRenderer interface compile-time check
  and clean up comments
2026-03-31 13:01:30 +03:00
Ed Zynda 054c417603 fix: render reasoning blocks when resuming sessions
When using /resume to resume a session, reasoning/thinking content
was not being displayed even though it was saved in the session file.

Changes:
- Add RenderReasoningBlock to Renderer interface
- Implement RenderReasoningBlock for MessageRenderer with muted italic
  styling matching live streaming output
- Implement RenderReasoningBlock for CompactRenderer with same styling
- Update renderSessionHistory to render reasoning content before
  assistant message text

Fixes: reasoning blocks now populate correctly when resuming sessions
2026-03-31 10:34:10 +03:00
Ed Zynda 94d62a6ef0 Fix ACP thinking tag parsing to handle format
The Qwen model outputs thinking content wrapped in  tags
(not <thinking>). Updated parseThinkingTags to detect and handle
both formats:
- <thinking>...</thinking> (long format)
-   (short format)

Also removed the hasProperReasoningEvents logic that was preventing
thinking tag parsing from working correctly. Now both ReasoningDeltaEvent
(from models with proper reasoning APIs) and thinking tags in text
(from models like Qwen) are handled together, matching the TUI behavior.
2026-03-30 20:38:49 +03:00
Ed Zynda 91e6dfd2c8 Prevent double-sending of thinking content in ACP
Track whether a model sends proper ReasoningDeltaEvent events. If so,
skip parsing <thinking> tags from text to avoid sending reasoning content
twice (once as proper reasoning, once parsed from text).

Also reset the tracking state at the start of each new prompt turn.
2026-03-30 20:33:46 +03:00
Ed Zynda b6a0c4b44c Add thinking tag parsing for ACP
Parse <thinking>...</thinking> tags from models (Qwen, DeepSeek) that
wrap reasoning content in XML-style tags instead of using proper
reasoning events.

When text chunks contain thinking tags:
- Extract content between tags and send as reasoning/thought updates
- Send content outside tags as regular message text
- Track state across chunks to handle streaming properly

This mirrors the TUI's thinking tag parsing behavior.
2026-03-30 20:30:22 +03:00
Ed Zynda 8eb0fa855a Fix ACP file attachment support
- Implement proper handling for all ACP content block types:
  - ContentBlockText: extracts text content
  - ContentBlockImage: decodes base64 to LLMFilePart
  - ContentBlockAudio: decodes base64 to LLMFilePart
  - ContentBlockResource: handles text and binary embedded resources
  - ContentBlockResourceLink: reads files from disk

- Text files are now included inline in the message (not as FilePart)
  to avoid OpenAI API errors. Only binary files (images, audio, PDFs)
  are sent as FilePart attachments.

- Add fallback MIME types when not provided by client
- Add default prompt text when user attaches files without text
- Add comprehensive debug logging for content extraction
- Enable debug logging in ACP command when --debug flag is used
2026-03-30 20:28:14 +03:00
Ed Zynda 3bf696c546 prompts 2026-03-30 18:30:53 +03:00
Ed Zynda 3e461a0539 chore: unignore .kit/prompts directory 2026-03-30 18:30:21 +03:00
Ed Zynda a2ece01ecf ui: stream overflow lines into terminal scrollback buffer
Previously, when streaming text grew taller than the allocated view
height, the top (older) lines were silently discarded by viewContent().
This meant users could not scroll up to see them.

Now, overflow lines are emitted directly via tea.Println so they land
in the terminal's real scrollback buffer — matching the diagram where
completed text lives in the red scrollback region and the green viewable
area always shows the most recent streaming lines + input/footer.

Key changes:
- StreamComponent: add scrollbackFlushedLines counter and ConsumeOverflow()
  method that returns newly overflowed lines and advances the pointer
- StreamComponent.Reset(): zero the counter between steps
- StreamComponent.GetRenderedContent(): skip already-flushed lines so
  the end-of-step flush doesn't re-emit content already in scrollback
- AppModel.Update(): call ConsumeOverflow() each cycle and emit overflow
  directly via tea.Println (not appendScrollback, to avoid triggering
  drainScrollback's auto-flush guard while streaming is active)
- streamComponentIface: add ConsumeOverflow() to interface
- model_test.go: add stub ConsumeOverflow() to test double
- children_test.go: add 7 unit tests covering ConsumeOverflow and the
  updated GetRenderedContent skip-flushed-lines behaviour
2026-03-30 18:22:03 +03:00
Ed Zynda 623c9fb5ad docs(agents): add BTCA configured resources list to AGENTS.md
Enumerate all 14 external repositories configured in btca.config.jsonc
for easy reference when researching dependencies.
2026-03-30 18:20:43 +03:00
Ed Zynda 139506f336 fix(ui): refresh herald typography on theme change
When users run `/theme <name>`, the alert colors (Tip, Note, Warning, etc.)
now update correctly. Previously, MessageRenderer and StreamComponent cached
herald.Typography instances that weren't refreshed after theme changes.

Changes:
- Added UpdateTheme() method to Renderer interface
- Implemented UpdateTheme() for MessageRenderer to recreate herald typography
- Added no-op UpdateTheme() stub for CompactRenderer (fetches colors fresh)
- Implemented UpdateTheme() for StreamComponent reasoning block renderer
- Modified handleThemeCommand() to notify all renderers of theme changes

This ensures newly rendered messages use the current theme's alert colors.
2026-03-30 17:06:06 +03:00
Ed Zynda 6d424554ad Add KIT logo above startup info in TUI
- Display kitBanner() before PrintStartupInfo() when running Kit normally
- The ASCII art banner with KITT scanner lights now appears at the top
  of the screen, before Model, Context, Skills information
- Maintains consistent styling with the existing usage/help screen
2026-03-30 16:57:27 +03:00
Ed Zynda 5a3d3fdd7d fix: properly handle tags from Qwen/DeepSeek models
Models like Qwen and DeepSeek wrap reasoning content in  ...  XML-like
tags within the regular content field. This was causing the reasoning
text to appear twice - once as a reasoning block and once as regular text.

Changes:

1. Provider hooks (providers.go):
   - Extract reasoning from  tags and emit proper reasoning events
   - Use openai provider directly with custom ExtraContentFunc and
     StreamExtraFunc hooks to parse thinking content

2. Stream filtering (stream.go):
   - Filter out all text content between  and  tags at the
     streaming level to prevent duplicate rendering
   - Track state with inThinkTag flag across stream chunks

3. Message conversion (content.go):
   - Strip any remaining  tags from text content when converting
     from fantasy messages

The regex patterns use string concatenation to avoid XML tag corruption:
  regexp.MustCompile( +  +  +  +  +  +  + )

Fixes duplicate reasoning text when using custom provider with models
that wrap thinking in  tags.
2026-03-30 16:31:58 +03:00
Ed Zynda c91225629d fix: handle custom provider model persistence and bare model names
Two related fixes for --provider-url handling:

1. Don't restore custom/* models from preferences without --provider-url
   - When user runs with --provider-url, model defaults to custom/custom
   - If they switch models, custom/custom gets persisted to preferences
   - On next run without --provider-url, restoring custom/custom fails
   - Now we skip restoring custom/* models when no --provider-url is provided

2. Auto-prefix bare model names with custom/ when --provider-url is set
   - Users often provide just the model name (e.g., qwen3.5-35b-a3b)
   - This failed with 'invalid model format' error
   - Now auto-prefixed with custom/ for OpenAI-compatible endpoints
2026-03-30 16:12:16 +03:00
Ed Zynda 5a71cde5ff fix 2026-03-30 16:05:14 +03:00
Ed Zynda 044d3eb206 style: align Read tool gutter styling with Write tool
- Add block-level indentation (2 spaces) to Read tool output
- Configure herald CodeLineNumber style to use GutterBg background
- Match Write tool's gray gutter appearance
2026-03-30 15:49:57 +03:00
Ed Zynda 80f3a642a3 refactor: migrate markdown rendering from glamour to herald-md
- Replace glamour-based markdown rendering with herald/herald-md
- Update go.mod and go.sum with new dependencies
- Refactor styles.go to use Typography cache instead of TermRenderer
- Update enhanced_styles.go for compatibility
- Update btca.config.jsonc configuration
2026-03-30 15:02:01 +03:00
Ed Zynda 26f0969e3e deps: update all dependencies and refactor Read tool rendering
- Update github.com/indaco/herald v0.9.0 -> v0.10.0
- Update charm.land/bubbles/v2 v2.0.0 -> v2.1.0
- Update AWS SDK v2 packages
- Update google.golang.org/genai v1.51.0 -> v1.52.0
- Update various other dependencies

refactor(ui): use herald.CodeBlock for Read tool output

- Replace manual renderCodeBlock() with herald.CodeBlock()
- Add WithCodeLineNumberOffset() support for correct line numbers
- Extract language hint from file extension for syntax highlighting
- Preserve existing syntax highlighting via WithCodeFormatter()
- Remove unused codeLine struct and renderCodeBlock function
2026-03-30 14:51:23 +03:00
Ed Zynda 4af75901b5 test: add generalized smoke and sanity tests for all example extensions
Add two test files that auto-discover and validate every single-file
extension in examples/extensions/:

- all_extensions_load_test.go: Verifies all 32 extensions load into the
  Yaegi interpreter without errors (syntax, imports, Init signature).

- all_extensions_sanity_test.go: Six generalized sanity checks:
  - Lifecycle: SessionStart → SessionShutdown round-trip
  - CommandSanity: non-empty names/descriptions, no spaces/leading slash,
    non-nil Execute, no duplicates
  - ToolSanity: non-empty names/descriptions, at least one executor,
    valid JSON parameters, no duplicates
  - ZeroValueEvents: all 22 event types fired as zero-value structs
  - WidgetSanity: non-empty IDs, consistent keys, valid placements
  - IdempotentLifecycle: repeated SessionStart/SessionShutdown

Shared extensionFiles() helper auto-discovers extensions so new files
are automatically covered.
2026-03-29 15:12:48 +03:00
Ed Zynda 49ff4c0678 fix: /tree and /fork commands lose context due to leaf reset
performFork() called ClearMessages() after Branch(targetID), but
ClearMessages() calls TreeSession.ResetLeaf() which sets leafID back
to empty — immediately undoing the branch. The in-memory message store
was also never reloaded from the tree session after branching, so the
LLM had zero context.

Add ReloadMessagesFromTree() which clears the store and reloads it
from the tree session's current branch without resetting the leaf
pointer. Use it in performFork() instead of ClearMessages().
2026-03-29 15:02:24 +03:00
Ed Zynda b0802a5c32 fix: properly count existing cache blocks to stay under 4-block limit
The issue was that cache control persisted across turns in conversation
history, causing accumulation beyond Anthropic's 4-block limit.

Changes:
- Count existing cache blocks in message history before adding new ones
- Only add new cache blocks up to the 4-block limit
- Remove tool caching (was adding 1 block per turn)
- Skip messages that already have cache control set

Tested with 5 sequential messages - no errors, proper cache metrics.
2026-03-29 14:48:08 +03:00
Ed Zynda dfe65ca227 chore: remove all Crush references from comments
Remove mentions of Crush from:
- cache_control.go
- agent.go (2 references)
- content.go
- tool_renderers.go
- lsp-diagnostics.go (2 references)
2026-03-29 14:43:51 +03:00
Ed Zynda d4ec756ce5 fix: match Crush's cache_control strategy exactly
Crush's proven 4-block strategy:
1. Last system message (if present)
2. Last 2 conversation messages
3. Last tool definition

This stays exactly at Anthropic's 4-block limit without exceeding it.

Previous implementation could exceed the limit in certain edge cases.
Now matches Crush's battle-tested approach.
2026-03-29 14:42:29 +03:00
Ed Zynda 2971e73ee8 fix: limit Anthropic cache_control blocks to maximum 4
Anthropic API enforces a maximum of 4 blocks with cache_control per request.
The previous implementation could exceed this limit when combining:
- System message caching
- Recent message caching
- Tool definition caching

Changes:
- Add explicit cache block counting (max 4)
- Remove tool cache control to stay under limit
- Prioritize: system message first, then recent messages
- Work backwards from end to cache most recent context first

Fixes: bad request error 'A maximum of 4 blocks with cache_control may be provided'
2026-03-29 14:40:44 +03:00
Ed Zynda 5aa6c9e116 chore: fix all golangci-lint v2 issues
- Fix gofmt formatting issues in 7 files
- Replace atomic.AddUint64 with atomic.Uint64 type (modernize)
- Replace for i := 0; i < count; i++ with for i := range count (modernize)
- Replace strings.Split with strings.SplitSeq (modernize)
- Replace deprecated GetFantasyProviders with GetLLMProviders
- Replace deprecated GetFantasyMessages with GetLLMMessages
- Replace deprecated ConvertFromFantasyMessage with ConvertFromLLMMessage
- Replace deprecated FromFantasyMessage with FromLLMMessage
- Replace deprecated ToFantasyMessages with ToLLMMessages
- Remove 2 unused formatToolArgs functions
2026-03-29 14:36:03 +03:00
Ed Zynda bca08476de chore: fix remaining linting issues in caching code
- Use max() built-in instead of if statement (modernize)
- Remove unused buildAnthropicCacheOptions function
- Remove unused anthropic import
2026-03-29 14:32:28 +03:00
Ed Zynda 6a599d86af chore: fix golangci-lint v2 compatibility
- Upgrade golangci-lint to v2.11.4
- Fix errcheck warnings for os.Setenv/os.Unsetenv in tests
- Use maps.Copy instead of manual loop (modernize lint)
- Add maps import for maps.Copy
2026-03-29 14:31:19 +03:00
Ed Zynda fd6f200659 refactor: clean up self-referential comments in caching code
Remove internal monologue comments that don't add value for readers:
- Remove lengthy explanations of type conflicts that are now resolved
- Remove 'NOTE:' and 'TODO:' comments documenting implementation history
- Remove obvious test comments that just restate what the code does
- Keep only meaningful comments that explain design intent

The code is now cleaner and easier to read without the self-referential
commentary that was useful during development but not for maintenance.
2026-03-29 14:28:29 +03:00
Ed Zynda b295a25946 feat: automatic prompt caching for cost reduction
Implements automatic prompt caching to reduce API costs by 60-90% for
repeated prompts with the same context.

Architecture:
- Provider-level caching for OpenAI (PromptCacheKey)
- Message-level caching for Anthropic (avoids type conflicts)
- Model family detection enables caching regardless of provider

Key Changes:
- Add ModelInfo.Family with SupportsCaching() and CacheType() methods
- Add ProviderConfig.DisableCaching for opt-out
- Implement message-level cache control in agent (like Crush)
  - Last system message gets cache control
  - Last 2 messages get cache control
  - Last tool gets cache control
- Auto-disable caching when thinking is enabled (type conflict avoidance)
- Add KIT_DISABLE_CACHE environment variable for global opt-out

Tested with opencode/claude-sonnet-4-6 showing cacheRead/cacheWrite
tokens in debug output, confirming 60-90% cost savings.

Closes cost optimization for multi-turn conversations.
2026-03-29 14:24:07 +03:00
Ed Zynda f0e4e2f757 refactor: remove Fantasy dependency name leakage from public SDK and docs
Rename public SDK symbols to use generic LLM terminology instead of
exposing the internal dependency name (charm.land/fantasy):

Public API renames (with deprecated wrappers for backward compat):
- ConvertToFantasyMessages() → ConvertToLLMMessages()
- ConvertFromFantasyMessage() → ConvertFromLLMMessage()
- GetFantasyProviders() → GetLLMProviders()

New type alias:
- LLMFilePart = fantasy.FilePart (eliminates need for direct fantasy import)
- PromptResultWithFiles() signature now uses LLMFilePart

Internal renames (with deprecated wrappers):
- ModelsRegistry.GetFantasyProviders() → GetLLMProviders()
- TreeManager.GetFantasyMessages() → GetLLMMessages()
- TreeManager.AppendFantasyMessage() → AppendLLMMessage()
- TreeManager.AddFantasyMessages() → AddLLMMessages()
- Message.ToFantasyMessages() → ToLLMMessages()
- FromFantasyMessage() → FromLLMMessage()
- npmToFantasyProvider → npmToLLMProvider
- isProviderFantasySupported() → isProviderLLMSupported()

All internal callers migrated to new names. ~30 comments updated
to remove Fantasy references across pkg/kit/, internal/agent/,
internal/models/, internal/message/, internal/session/.

Documentation updates:
- AGENTS.md: added Public SDK rules section (no dependency leakage,
  naming conventions, deprecation pattern)
- README.md: removed Fantasy references
- pkg/kit/README.md: full rewrite with current API surface
- skills/kit-sdk/SKILL.md: updated examples and type references
- www/pages/providers.md, www/pages/cli/commands.md: updated
2026-03-29 14:01:57 +03:00
Ed Zynda d25249506a docs: update SKILL.md and README for recent SDK changes
- Add StepUsageEvent and SteerConsumedEvent to event types table
- Add new Extension API section documenting kit.Extensions() sub-API
- Add extension_api.go to Key Files reference list
- Fix Close() error handling in README SDK example
2026-03-29 13:33:19 +03:00
Ed Zynda 971521f534 Group Extension* methods behind ExtensionAPI interface
- Create ExtensionAPI interface with all extension-related methods
- Add extensionAPI type that wraps *Kit and implements the interface
- Add Kit.Extensions() method to access the ExtensionAPI
- Remove ~30 Extension* methods from Kit (breaking SDK change)
- Update all internal callers (cmd/, internal/acpserver/) to use Extensions().Method()
- Extensions themselves unaffected (use kit/ext API via Yaegi)

This cleans up the Kit API surface while maintaining full extension functionality.
2026-03-29 13:19:51 +03:00
Ed Zynda 8c00682367 Rename Fantasy* types to LLM* and remove GenerateResult alias
- FantasyMessage -> LLMMessage
- FantasyUsage -> LLMUsage
- FantasyResponse -> LLMResponse
- Remove confusing GenerateResult = TurnResult alias
- Update documentation in SKILL.md
2026-03-29 13:11:55 +03:00
Ed Zynda 58caf155c1 pkg/: internal cleanups - shared iterator, per-instance skill cache, exported EntryID
kit.go
- Extract iterBranchMessages helper to eliminate ~15 lines of duplicated
  branch-fetch/type-assert boilerplate between GetSessionMessages and
  GetStructuredMessages
- Move skillCache from package-level global to per-Kit field; avoids
  cross-contamination when multiple Kit instances exist in same process

skills.go
- Remove globalSkillCache var and skillCache type definition
- Update DiscoverSkillsForExtension and ClearSkillCache to use m.skillCache
- Remove unused sync import

sessions.go
- Use m.treeSession.EntryID instead of local getEntryID duplicate
- Remove local getEntryID function (was missing LabelEntry, SessionInfoEntry,
  CompactionEntry types that internal/session.TreeManager.EntryID handles)

internal/session/tree_manager.go
- Export entryID -> EntryID so pkg/kit can use it directly
- Update all internal callers to use EntryID

config.go
- Add sync comment for defaultSystemPrompt noting it should be kept in sync
  with CLI default in cmd/root.go

hooks_test.go
- Add newEmptyHookedTool helper for tests that need hookedTool with empty
  hook registries
- Update TestHookedTool_Passthrough and TestHookedTool_InfoDelegates to use
  helper (saves ~6 lines of boilerplate each)
- Merge TestHookRegistry_HasHooks into TestHookRegistry_Unregister (was
  testing same behavior, now just one initial state assertion added)

All changes tested with opencode/kimi-k2.5 exploring the repo in tmux.
6 files changed, 69 insertions(+), 98 deletions(-)
2026-03-29 13:00:33 +03:00
Ed Zynda 3f08bf2424 pkg/kit: SDK quality-of-life improvements
Replace var function aliases with proper func wrappers (types.go)
- ParseModelString, CreateProvider, GetGlobalRegistry, LoadSystemPrompt
  were package-level vars, making them reassignable and rendering oddly
  in go doc. Now plain func wrappers with matching signatures.

Fix Subagent() double-error return convention
- Was returning both (*SubagentResult{Error: err}, err) simultaneously.
  Now returns (nil, err) on failure, consistent with Go conventions.
- Removed SubagentResult.Error field; errors come from the error return.
- Updated all call sites in cmd/root.go, internal/acpserver, and kit.go.

Fix NavigateTo/SummarizeBranch/CollapseBranch string-encoded errors
- All three returned "" or an error string instead of error values,
  making it impossible to distinguish success from failure in SummarizeBranch
  (empty string meant both "no content" and "LLM failed").
- NavigateTo: string -> error
- SummarizeBranch: string -> (string, error)
- CollapseBranch: string -> error
- Updated cmd/root.go bridge closures to use err != nil and err.Error().

Remove duplicate GetSessionFilePath (use GetSessionPath)
- GetSessionPath (sessions.go) and GetSessionFilePath (kit.go) were
  identical. Removed GetSessionFilePath; updated cmd/root.go and
  internal/acpserver to call GetSessionPath directly.
2026-03-29 12:51:04 +03:00
Ed Zynda 9fbbab05f6 pkg/: simplify code without altering public API
events.go
- Delete subagentListenerSet (verbatim duplicate of eventBus); reuse
  *eventBus in SubscribeSubagent and getSubagentListenerSet

hooks.go
- Add early-exit in run() when hooks slice is empty, making all
  hasHooks() guard call sites in kit.go and compaction.go redundant

kit.go
- Remove four if m.X.hasHooks() { m.X.run(...) } outer guards
  (beforeTurn, contextPrepare, afterTurn x2); run() now short-circuits
- Replace goto drained with an idiomatic return inside default: branch
- Replace stdlib log.Printf with charmlog.Debug (charmbracelet/log),
  consistent with the rest of the codebase; remove "log" import

config.go
- Collapse single-element configNames := []string{".kit"} loop into a
  direct viper.SetConfigName call (removes slice, for, break, flag)

auth.go
- Fix GetOpenAIAPIKey: it documented OPENAI_API_KEY env var fallback but
  never called os.Getenv; now it does

compaction.go
- Extract persistAndEmitCompaction helper; eliminates duplicated
  AppendCompaction + events.emit block in compactInternal and
  applyCustomCompaction
- Replace fmt.Errorf("%s", reason) with errors.New(reason)
- Name the 16384 magic number as const defaultReserveTokens

skills.go
- Fix broken double-checked lock in DiscoverSkillsForExtension: the
  read-unlock -> write-lock gap had a TOCTOU race; replaced with a
  single write-lock covering the check and load
- Remove dead nil guard in convertSkills (convertSkill never returns nil)
- Rename convertSkills parameter skills->skillList to avoid shadowing
  the skills package import

extensions_bridge.go
- Delete taskMutex struct (sync.Mutex wrapper with map passed as param);
  replace with inline var taskMu sync.Mutex at the use site
- Simplify AgentEnd double-if into a single combined := declaration

template_bridge.go
- Fix RenderTemplate: use varRegex.ReplaceAllStringFunc instead of
  two-pass strings.ReplaceAll; handles arbitrary whitespace in {{var}}
- Remove dead isFlag function and simplify ParseArguments guard
  (the outer !HasPrefix guard made isFlag always return false)
- Cache matchModelPattern compiled regexps in a sync.Map to avoid
  repeated regexp.Compile on hot streaming paths

pkg/extensions/test/mock.go
- Remove dead local StatusBarEntry type (duplicate of extensions type,
  never referenced)
- Change make([]T, 0) to nil for nine slice fields in NewMockContext

pkg/extensions/test/harness.go
- Remove MustLoad (no callers outside the package)
- Remove extPath field (assigned but never read)
- Remove redundant os.Stat in LoadFile (os.ReadFile already errors)

events_test.go
- Add five missing event types to TestEventTypes table
  (Compaction, ReasoningDelta, ToolOutput, StepUsage, SteerConsumed)
- Expand TestEventOrdering from 11 to 16 events with the same types
- Add a got < 0 assertion to TestEventBusConcurrentSubscribeEmit so the
  test can actually fail rather than only logging
2026-03-29 12:39:19 +03:00
Ed Zynda b0991c7aa6 tui: simplify rendering, fix correctness issues, remove dead code
## Dead code removal
- Delete slash_command_input.go (352 lines, never instantiated)
- Remove FormatCompactLine, StyleCompactSymbol/Label/Content from
  enhanced_styles.go (zero call sites)
- Remove getTheme() alias in messages.go; standardize on GetTheme()
  across compact_renderer.go (8 sites) and tool_renderers.go (14 sites)

## BubbleTea correctness
- Fix child model discards: all m.stream.Update() and m.input.Update()
  calls now store the returned model via type-assertion (13 sites)
- Fix Init(): remove vestigial nil guards; StreamComponent.Init() always
  returns nil so only m.input.Init() is needed
- Fix /clear divergence: remove silent InputComponent /clear handler so
  parent AppModel handles it with the proper system message (one path)

## Architecture / maintainability
- Unify slash-command dispatch from two-pass (exact + prefix) to single
  parse: strings.Cut once, GetCommandByName on name, pass args to
  handleSlashCommand(sc, args); eliminates 3 separate dispatch sites
- Add noopCmd package-level var replacing three inline func()tea.Msg{nil}
  sentinel returns
- Remove stale TAS-15/16/17 comments from interface declarations
- Deduplicate headerProviderForUI / footerProviderForUI in cmd/root.go
  into a shared headerFooterProviderForUI helper (removes ~28 duplicated lines)

## Performance
- Cache glamour.TermRenderer keyed by width in styles.go; invalidate on
  theme change — eliminates full goldmark parser re-init every flush tick
- Add styleMarginBottom1 package-level var replacing 9 per-frame
  lipgloss.NewStyle().MarginBottom(1) allocations
- Add layoutDirty flag: replace 9 distributeHeight() calls in Update()
  with m.layoutDirty=true; flush once in View() — guarantees exactly one
  layout measurement per frame instead of N (reduces double-render)
- Add WidgetUpdateEvent coalescing in app.NotifyWidgetUpdate() via
  atomic.Bool + 16ms debounce; prevents fast extension tickers from
  flooding BubbleTea's message queue with redundant re-render triggers

## Concurrency safety
- Convert all NotifyWidgetUpdate() call sites in cmd/root.go to
  go appInstance.NotifyWidgetUpdate() (16 sites) — eliminates deadlock
  risk when called synchronously from inside BubbleTea's Update() handler
2026-03-29 11:34:16 +03:00
Ed Zynda 9c90563765 refactor: simplify code patterns and reduce duplication
- Extract isShellTool() helper in tool_renderers.go to eliminate
  duplicated shell tool matching logic
- Replace bannedCommands slice with compiled regex in bash.go for
  cleaner security validation
- Extract pathSet helper type in loader.go for reusable path
  deduplication
- Consolidate ac()/acOr() helpers in themes.go for better organization
- Total reduction: ~34 lines across 4 files

All tests pass (go test -race ./...) and build succeeds.
2026-03-29 01:18:27 +03:00
Ed Zynda f36166bee5 rename spawn_subagent tool to subagent; remove redundant toolDisplayNames map
Tool rename (breaking change for ToolName string comparisons in event handlers):
- internal/core/subagent.go: Name field 'spawn_subagent' → 'subagent'
- internal/extensions/wrapper.go: update coreToolKinds map key
- pkg/kit/events.go: update coreToolKinds map key and ToolKindSubagent comment
- pkg/kit/extensions_bridge.go: update three ToolName == ... guards
- internal/ui/tool_renderers.go: update two toolName == ... case guards
- internal/ui/stream.go: remove special-case branch (toolName is now already
  'subagent', so the title-case fallback produces 'Subagent' naturally)

Comments/docs updated everywhere (no logic changes):
- internal/core/tools.go, internal/extensions/api.go, events.go
- pkg/kit/kit.go, tools.go
- examples/extensions/subagent-test.go, kit-telegram/main.go
- README.md, skills/kit-sdk/SKILL.md
- www/pages/advanced/subagents.md, extensions/capabilities.md
- www/pages/index.md, sdk/callbacks.md
- www/public/session/index.html (tracked UI asset)

Redundant toolDisplayNames map removed (item #14):
- internal/ui/messages.go: delete the 7-entry map whose every value was
  identical to what the title-case fallback already produced; simplify
  toolDisplayName() to just the fallback
2026-03-29 00:24:18 +03:00
Ed Zynda 879e81f9b5 remove deprecated API methods: GetExtRunner, GetBufferedLogger, GetAgent, PromptWithCallbacks
These methods have been deprecated since the narrow-accessor and event-
subscriber APIs were introduced. No callers exist in this repository.

- pkg/kit/kit.go: remove GetExtRunner(), GetBufferedLogger(), GetAgent(),
  and PromptWithCallbacks(); update Subscribe() doc comment which still
  mentioned PromptWithCallbacks; tighten section header comment
- pkg/kit/README.md: replace PromptWithCallbacks example with the
  OnToolCall/OnToolResult/OnStreaming subscriber pattern; remove method
  from the quick-reference list
- README.md: same example migration in the SDK section
- www/pages/sdk/callbacks.md: remove the PromptWithCallbacks section
  entirely; the event-based monitoring section that followed it is now
  the lead content
- www/pages/sdk/overview.md: remove PromptWithCallbacks row from the
  prompt-variant table
- skills/kit-sdk/SKILL.md: remove the deprecated legacy callback snippet
2026-03-29 00:05:09 +03:00
Ed Zynda 727b42acfe cleanup: remove unused variable, duplicate condition, and reimplemented stdlib helper
- agent: remove unused currentToolName variable and its compiler-suppressor
  '_ = currentToolName'; currentToolArgs is the field actually used by
  OnToolResult callbacks
- tools/connection_pool: collapse double-nested identical if guard into a
  single check (copy-paste artifact)
- tools/mcp_test: replace hand-rolled contains() helper with strings.Contains;
  add 'strings' import and delete the redundant function
2026-03-29 00:00:33 +03:00
Ed Zynda 4830981570 cleanup: fix dead code, logic bug, duplication, and Unicode fuzzy matching
- config: fix tilde path expansion (filepath.Join result was discarded)
- config: remove dead comment '// base := GetConfigPath()'
- auth: extract oauthTokenExpired/oauthTokenNeedsRefresh helpers to
  eliminate copy-paste duplication across AnthropicCredentials and
  OpenAICredentials
- ui/messages: remove dead RenderToolCallMessage on MessageRenderer
  (not part of Renderer interface, never called)
- ui/compact_renderer: remove dead RenderToolCallMessage on CompactRenderer
  (symmetric duplicate, never called)
- ui/enhanced_styles: remove dead CreateGradientText wrapper
  (one-liner over ApplyGradient, never called)
- ui/fuzzy: fix fuzzyCharacterMatch to use rune iteration instead of
  byte indexing (was silently wrong for multi-byte Unicode input)
- ui/file_suggestions: remove duplicate fuzzyCharMatch; call the now-
  correct shared fuzzyCharacterMatch instead; drop unused utf8 import
- app: replace TODO comment with descriptive note (batch file attachment
  limitation is intentional, not a pending action item)
2026-03-28 23:58:14 +03:00
Ed Zynda dcfebafcc5 fix: correct token usage and cost tracking for multi-step tool calls
This commit fixes several issues with token usage tracking:

1. Fix InputTokens-only validation bug - now checks any token field > 0
   to handle OpenAI-compatible providers where cached prompts result in
   InputTokens=0 while OutputTokens>0

2. Remove per-step context token updates from recordStepUsage() - context
   fill is now set once at turn completion via updateUsageFromTurnResult
   using FinalUsage.InputTokens, preventing display jumps during multi-step
   tool calls

3. Track maximum context seen in SetContextTokens() - prevents the status
   bar from showing decreasing token counts when FinalUsage.InputTokens
   reflects only the last step's input

4. Add comprehensive debug logging for token tracking at key points:
   - StepUsageEvent emission
   - recordStepUsage processing
   - updateUsageFromTurnResult processing

5. Update tests to reflect new behavior:
   - TestRecordStepUsage_updatesTracker: no longer expects context updates
   - TestUpdateUsageFromTurnResult_contextTokensUsesInputOnly: verifies
     InputTokens-only tracking

All tests pass. Token tracking now correctly accumulates costs and shows
monotonically increasing context size.
2026-03-28 17:49:31 +03:00
Ed Zynda 1f5c103667 fix: rock-solid token tracking - /new resets usage, remove estimation for costs
- /new command now properly resets usageTracker stats when starting fresh session
- Remove EstimateAndUpdateUsage fallback in updateUsageFromTurnResult()
- Remove EstimateAndUpdateUsage fallback in UpdateUsageFromResponse()
- Only use actual API-reported tokens for cost tracking (following opencode pattern)
- Estimation is inaccurate and should never be used for billing

Fixes issues with kimi-k2.5 and opencode token tracking where:
1. /new didn't reset token count/cost
2. Tokens never updated correctly due to estimation fallback
2026-03-28 12:15:45 +03:00
Ed Zynda 4caa8ba3dc Bridge SDK features to extension system: tree navigation, skills, templates, model resolution
This commit bridges 4 categories of internal SDK capabilities to the extension
system, enabling extensions like pi-prompt-template-model to be built with
minimal custom code.

New Extension APIs:

Tree Navigation (Phase 1):
- GetTreeNode, GetCurrentBranch, GetChildren - Navigate conversation tree
- NavigateTo - Branch/fork to specific entries
- SummarizeBranch - LLM-based branch summarization
- CollapseBranch - Fresh context primitive for context management

Skill Loading (Phase 2):
- LoadSkill, LoadSkillsFromDir - Load skill files with YAML frontmatter
- DiscoverSkills - Auto-discover from standard locations
- InjectSkillAsContext, InjectRawSkillAsContext - Pre-load skills

Template Parsing (Phase 3):
- ParseTemplate, RenderTemplate - {{variable}} substitution
- ParseArguments, SimpleParseArguments - CLI-style arg parsing (, , )
- EvaluateModelConditional, RenderWithModelConditionals - Model conditionals

Model Resolution (Phase 4):
- ResolveModelChain - Fallback chain resolution
- GetModelCapabilities - Query model specs
- CheckModelAvailable, GetCurrentProvider, GetCurrentModelID

Files Modified:
- internal/extensions/api.go - New types and Context methods
- internal/extensions/symbols.go - Export to Yaegi
- internal/extensions/runner.go - No-op stubs
- pkg/kit/sessions.go - Tree navigation bridge
- pkg/kit/skills.go - Skill loading bridge
- pkg/kit/template_bridge.go - NEW - Template & model resolution
- cmd/root.go - Wire to extension Context

Examples Added:
- conversation-manager.go - Tree nav, branch collapse, fresh context loops
- prompt-templates.go - Frontmatter templates with model switching
- bridge_demo.go - All new APIs demonstration

Documentation Updated:
- README.md - New capabilities and examples
- www/pages/extensions/capabilities.md - Full API docs
- www/pages/extensions/examples.md - New example category
- skills/kit-extensions/SKILL.md - Extension developer docs
2026-03-28 12:00:19 +03:00
Ed Zynda 15ef8ad78b fix theming 2026-03-27 23:41:32 +03:00
Ed Zynda 551f2710d9 refactor(ui): trim leading whitespace from thinking content
Use strings.TrimLeft to remove leading spaces, tabs, and newlines
from thinking/reasoning content for cleaner left alignment.
2026-03-27 21:41:16 +03:00
Ed Zynda 67bda5cad5 refactor(ui): color-code thinking block elements
- Thinking content: Italic + Muted color
- 'Thought for' label: VeryMuted color
- Duration (Xms/Xs): Accent color

Creates visual hierarchy: content > label > duration highlight
2026-03-27 21:39:06 +03:00
Ed Zynda 01d7d754ef refactor(ui): apply subdued color to thinking block text
Wrap italic thinking content with VeryMuted foreground color
for secondary visual hierarchy - less prominent than main response.
2026-03-27 21:37:42 +03:00
Ed Zynda c6304f1e92 refactor(ui): use Italic typography for thinking blocks
Change thinking content from H6 to Italic for more subdued,
secondary visual appearance. Makes reasoning text less prominent
than main assistant responses.
2026-03-27 21:36:21 +03:00
Ed Zynda bc3c733ae3 refactor(ui): use H6 instead of blockquote for thinking blocks
Change thinking/reasoning content from Blockquote to H6 (subtitle)
for cleaner visual styling without left border.
2026-03-27 21:34:26 +03:00
Ed Zynda 428ee2b8be refactor(ui): remove indentation from thinking block footer
Remove PaddingLeft(2) from 'Thought for...' duration text
so it aligns without extra indentation.
2026-03-27 21:32:50 +03:00
Ed Zynda eb1d7fd07e fix(ui): set Tip alert label to "You" for user messages 2026-03-27 21:31:05 +03:00
Ed Zynda 1e3e5cafd3 refactor(ui): use herald Tip alert for user messages
Update RenderUserMessage to use r.ty.Tip() for consistent
herald-based styling with green/success color indicator.
2026-03-27 21:30:33 +03:00
Ed Zynda 0b93e58fb9 fix(ui): correct labels for user and info messages
- Change AlertNote label from "You" to "Info" for system/extension messages
- Update RenderUserMessage to use custom styling with "You" label
- This separates user messages ("You") from info messages ("Info")
2026-03-27 21:23:17 +03:00
Ed Zynda 2bb01ed72c refactor(ui): use herald Note alert for system messages
Update RenderSystemMessage to use r.ty.Note() instead of r.ty.P()
for visual consistency with other herald-based message rendering.
This affects extension PrintInfo output and system messages.
2026-03-27 21:21:49 +03:00
Ed Zynda b6ecc36ea1 refactor(ui): improve message spacing and styling consistency
- Add bottom margin to startup header (KVGroup)
- Add bottom margin to thinking/reasoning blocks
- Fix thinking block footer to appear on new line without extra spacing
- Update spawn_subagent tool output to use bash-style formatting
- Add blank line after extension startup messages for visual separation
2026-03-27 21:15:41 +03:00
Ed Zynda d4f27bc912 revert(ui): restore original Read tool renderer without herald
The herald-based CodeBlock implementation didn't match the custom
styling we had for line numbers and gutters. Restoring the original
renderReadBody and renderCodeBlock functions with:
- Custom line number gutter styling
- Chroma syntax highlighting
- Truncation handling with footer preservation
2026-03-27 20:57:34 +03:00
Ed Zynda f12e195390 refactor(ui): replace custom message rendering with herald typography library
- Replace MessageRenderer with herald-based implementation
- Use herald alerts (Note, Tip, Warning, Caution) for message types
- Use blockquote for thinking/reasoning content
- Use KVGroup for startup info display
- Add margin-bottom to all message types for visual separation
- Simplify Read tool with herald CodeBlock and line numbers
- Add detectLanguage helper for syntax highlighting
- Capture extension startup messages and print after startup banner
- Remove ~200 lines of custom rendering code
2026-03-27 20:54:43 +03:00
Ed Zynda b68b3dd0bf Fix usage widget startup visibility and stop-path updates 2026-03-27 18:21:11 +03:00
Ed Zynda 48521bf76d ui: drop unused tool args from spinner label formatter 2026-03-27 17:54:53 +03:00
Ed Zynda 16df3a738c ui: polish stream/tool tracking comments and event-loop notes 2026-03-27 17:51:41 +03:00
Ed Zynda 9d0b8c8cef ui: simplify stream rendering state and harden stream ticks 2026-03-27 17:49:45 +03:00
Ed Zynda d9326fcf21 fix: auto-initialize extension context in kit.New()
Extensions were being loaded automatically by SetupAgent but the context
was never initialized unless the SDK user explicitly called
SetExtensionContext. This left extensions with a zero-value Context where
all function fields are nil.

Now kit.New() automatically calls SetExtensionContext with minimal defaults
(CWD, Model, Interactive=false) when extensions are loaded. SDK users can
still call SetExtensionContext to override with richer implementations
(TUI callbacks, prompts, etc.).

Combined with the normalizeContext() safety net in the runner, extensions
are now guaranteed to work in SDK mode without explicit context wiring.
2026-03-27 15:54:56 +03:00
Ed Zynda 22c479277e fix: normalize nil Context function fields to no-ops in SetContext
Extensions running via the SDK (without a fully-wired SetExtensionContext
call) would panic with 'reflect.Value.Call: call of nil function' when
calling any ctx method like ctx.PrintBlock().

normalizeContext() now replaces every nil function field in Context with
a safe no-op stub before storing it in the runner, so extension handlers
can never crash on a missing callback regardless of how Kit is embedded.
2026-03-27 15:54:54 +03:00
Ed Zynda 8ae204f12f fix: preserve full content in scrollback by separating render cache from viewport
The StreamComponent was truncating content to fit the viewport height before
caching it in renderCache. This caused GetRenderedContent() to return truncated
content when flushing to scrollback.

Changes:
- render() now caches FULL content without height clamping
- New viewContent() helper applies height clamping only for display
- View() calls both: render() for full content, viewContent() for visible slice

This follows the Pi TUI pattern: full buffer in memory, viewport slicing only
at display time. Long assistant messages are now fully preserved in scrollback.
2026-03-27 12:13:04 +03:00
Ed Zynda 8b1665a4ce feat: add multi-edit support to edit tool
Implement multi-edit functionality matching Pi's approach:
- Add 'edits' array parameter for multiple disjoint replacements
- All edits matched against original content (non-incremental)
- Overlap detection prevents conflicting edits
- Duplicate detection ensures unique matches
- Atomic operations: all succeed or none applied
- Detailed error messages with edit indices (edits[0], etc.)
- Fuzzy matching works with multi-edit mode
- Backward compatible with single-edit mode (old_text/new_text)

Changes:
- internal/core/edit.go: Multi-edit logic, validation, overlap detection
- internal/ui/messages.go: Add 'edits' to body keys
- internal/ui/tool_renderers.go: Render multi-edit diffs
- internal/core/edit_test.go: 9 comprehensive multi-edit tests
2026-03-27 10:34:43 +03:00
Ed Zynda 941f1daf0b fix: correct token/cost double-counting in usage tracker
Remove the StepUsageEvent handler from subscribeSDKEvents. It was
calling UpdateUsage() for every individual tool-calling step as it
streamed, then updateUsageFromTurnResult() called UpdateUsage() again
with TotalUsage (fantasy's own aggregate of all steps). A turn with N
tool calls was counting every token N+1 times.

Fix updateUsageFromTurnResult to use a single, clean code path:
- UpdateUsage() called exactly once per turn using TotalUsage
- SetContextTokens() uses FinalUsage.InputTokens only (not +OutputTokens)
  since input tokens of the last call = actual context window fill;
  output tokens are the response length, not context occupancy
- Estimate fallback no longer early-returns before SetContextTokens

Verified with opencode/kimi-k2.5: cost accumulates linearly across
simple and multi-step tool-calling turns with no double-counting.
anthropic/claude-sonnet-4-6 correctly shows $0.00 for OAuth sessions.
2026-03-26 16:46:48 +03:00
Ed Zynda ab7e2bda61 docs: update documentation for recent features
- Remove subagent-monitor.go references (project-local extension)
- Add customModels configuration documentation
- Document Ctrl+S mid-turn steering feature
- Update /new command description to clarify it creates new session file
- Add auto-cleanup documentation for empty sessions
2026-03-26 16:03:12 +03:00
Ed Zynda 741520927c chore: update dependencies and fix test issues
- Update all Go dependencies to latest versions
- Remove internal/app/usage_test.go (import cycle)
- Add sanitizeToolCallID function to fix message tests
- All tests pass with race detection
2026-03-26 15:56:04 +03:00
Ed Zynda 4c1bda9541 feat: auto-cleanup empty sessions on shutdown and /resume
Empty sessions (no messages) are now automatically cleaned up:

1. On shutdown: When kit exits cleanly, if the current session has no
   messages, the session file is deleted.

2. On /resume: When listing sessions for the resume picker, any empty
   session files are deleted and not shown in the list.

This prevents accumulation of orphaned empty session files when users
start sessions but don't send any messages.

Changes:
- internal/session/tree_manager.go: add IsEmpty() helper
- internal/app/app.go: delete empty session on Close()
- internal/session/store.go: filter and delete empty sessions in listSessionsInDir()
2026-03-26 15:46:51 +03:00
Ed Zynda 3b69b13556 feat: make /new create a new session file like Pi
Change /new behavior to match Pi:
- Create a completely new session file instead of just resetting the leaf
- Previous session is closed and saved (accessible via /resume)
- New session starts with 0 entries, 0 messages - clean slate
- Update help text to reflect new behavior

Key fix: SwitchTreeSession now updates the kit SDK's tree session
reference so messages are persisted to the correct file.

Files changed:
- internal/app/app.go: update kit SDK session reference
- internal/ui/model.go: create new session file on /new
- internal/ui/model_test.go: add SwitchTreeSession stub
2026-03-26 15:41:01 +03:00
Ed Zynda 83a959a379 Clean up dead code from OpenAI Codex OAuth implementation
- Remove unused modelFamily variable in createOpenAICodexProvider
- Remove dead spark handling code (spark is rejected early with error)
- Simplify buildCodexProviderOptions to only handle regular codex models
- Remove redundant comments and simplify code structure
- Net reduction: 31 lines of code
2026-03-26 15:22:16 +03:00
Ed Zynda 3491e05e9e Add clear error message for gpt-codex-spark models
Spark models are not accessible via ChatGPT OAuth and return Cloudflare
'Forbidden' errors. Add early detection and helpful error message directing
users to regular Codex models like 'openai/gpt-5.3-codex' instead.
2026-03-26 15:20:34 +03:00
Ed Zynda 0a54a8aa05 Fix OpenAI Codex model family detection for provider options
Different Codex model families use different API formats:
- gpt-codex-spark: uses standard ProviderOptions (not Responses API)
- gpt-codex, gpt-codex-mini: uses ResponsesProviderOptions

- Add detectCodexModelFamily() to determine model family from name
- Use standard ProviderOptions for spark models
- Use ResponsesProviderOptions for regular codex models
- Conditionally use WithUseResponsesAPI() based on model family

Note: gpt-5.3-codex-spark still gets Cloudflare forbidden error,
may need additional headers or different endpoint.
2026-03-26 15:17:30 +03:00
Ed Zynda 3cb3e5dba1 Fix missing system prompt when switching models in interactive mode
When using /model command to switch models, the system prompt was not being
passed to the new provider config. This caused OpenAI Codex to fail with
"Instructions are required" error.

- Load system prompt using config.LoadSystemPrompt() in SetModel
- Pass SystemPrompt to ProviderConfig when building model config
- This ensures Codex OAuth gets the instructions field it requires
2026-03-26 15:06:32 +03:00
Ed Zynda 31966c469f Skip max_output_tokens for OpenAI Codex OAuth provider
The Codex API doesn't support the max_output_tokens parameter, which was causing
"Unsupported parameter: max_output_tokens" errors.

- Add SkipMaxOutputTokens flag to ProviderResult
- Set flag when creating Codex OAuth provider
- Check flag in agent setup to skip WithMaxOutputTokens option
- This matches pi's behavior of not sending max_tokens to Codex API
2026-03-26 15:04:16 +03:00
Ed Zynda f03625d6e5 Upgrade fantasy to v0.17.1 and fix Codex API instructions parameter
- Upgrade charm.land/fantasy from v0.16.0 to v0.17.1
- Add buildCodexProviderOptions() to pass system prompt as 'instructions'
- The Codex API requires instructions as a top-level field, not as system message
- Set Store=false to prevent server-side conversation storage
- Use ResponsesProviderOptions.Instructions for system prompt
2026-03-26 15:00:10 +03:00
Ed Zynda d06641dc0a Fix OpenAI Codex API endpoint and headers
- Change base URL to /backend-api/codex for correct endpoint path
- Add browser-like User-Agent to avoid Cloudflare blocking
- Add Accept, Accept-Language, Cache-Control headers
- Match pi client headers more closely
2026-03-26 14:55:02 +03:00
Ed Zynda bbf1106e27 Add OpenAI ChatGPT/Codex OAuth authentication alongside Anthropic auth
Implements OAuth authentication for OpenAI ChatGPT Plus/Pro (Codex) similar to pi:

- Add OpenAICredentials type with OAuth and API key support
- Add OpenAI OAuth client with correct endpoints (auth.openai.com)
- Implement PKCE-based OAuth flow with local callback server on :1455
- Add login/logout/status commands for openai provider
- Support both ChatGPT/Codex OAuth tokens (chatgpt.com/backend-api) and
  regular OpenAI API keys (api.openai.com)
- Extract and store ChatGPT account ID from JWT token
- Add custom HTTP transport with required Codex headers:
  - chatgpt-account-id, originator, OpenAI-Beta: responses=experimental
- Update provider selection to use correct endpoint based on auth type

Usage:
  kit auth login openai    # OAuth with ChatGPT account
  kit auth logout openai
  kit auth status

The implementation follows the same patterns as the existing Anthropic OAuth
support, with automatic token refresh and secure credential storage in
~/.config/.kit/credentials.json
2026-03-26 14:50:15 +03:00
Ed Zynda babed03a3d feat: show bash command header in streaming output
When displaying streaming bash output, show the initial command as a
muted header ($ <command>) before the output lines. This helps users
understand what command is currently executing.

Changes:
- Add streamingBashCommand field to AppModel
- Extract command from ToolCallStartedEvent for bash tools
- Render $ <command> header in renderStreamingBashOutput
- Clear command on ToolResultEvent when tool completes
- Add tests for command extraction and cleanup
2026-03-26 14:36:39 +03:00
Ed Zynda 1cd074836f docs: document subagent monitoring events and extension
Update all documentation to include the new OnSubagentStart, OnSubagentChunk,
OnSubagentEnd lifecycle events for monitoring subagents spawned by the main agent:

README.md:
- Update lifecycle events list (20 → 23 events)
- Add subagent-monitor.go to examples list

www/pages/extensions/capabilities.md:
- Update event count (20 → 23)
- Add 3 new subagent events to lifecycle table
- Add 'Monitoring subagents spawned by the main agent' section with
  complete event handler documentation and struct definitions

www/pages/extensions/examples.md:
- Add subagent-monitor.go to Multi-agent section
- Add subagent-monitor_test.go to Development section

www/pages/advanced/subagents.md:
- Add 'Monitoring subagents from extensions' section with complete
  code example and event struct documentation
- Cross-reference subagent-monitor.go example

.agents/skills/kit-extensions/SKILL.md:
- Update lifecycle event count (18 → 21)
- Add Subagent Events section with full handler documentation
- Add event struct definitions (SubagentStartEvent, SubagentChunkEvent,
  SubagentEndEvent)
- Add 'Pattern: Monitoring Subagents with Widgets' complete example
  with Yaegi-safe design notes
2026-03-26 13:41:43 +03:00
Ed Zynda ab3ce260c8 feat: add subagent monitoring extension with horizontal widget layout
Add new extension API hooks for tracking spawned subagents:
- OnSubagentStart, OnSubagentChunk, OnSubagentEnd events
- Extensions bridge for forwarding child subagent events

Create subagent-monitor.go extension:
- Displays horizontally-stacked widgets above input box
- Shows real-time scrolling output from each subagent
- Yaegi-safe: no sync.Mutex, no goroutines, nil-guarded context calls
- Race-free design with on-demand elapsed time calculation

Add comprehensive tests:
- SessionStart, SubagentLifecycle, MultipleSubagents, SessionShutdown

Update symbols.go to export new event types for Yaegi interpreter.
2026-03-26 13:38:06 +03:00
Ed Zynda 8e8cc3946d fix: render steering user message immediately on mid-turn SteerConsumedEvent
When a steer message is consumed mid-turn via PrepareStep, no new
SpinnerEvent{Show: true} fires within that turn, so the message was
stuck in pendingUserPrints indefinitely and never rendered.

Branch the SteerConsumedEvent handler on m.state:
- stateWorking (mid-turn): flush live stream content, then print the
  steering user messages to scrollback immediately via drainScrollback.
- idle/post-turn: keep the existing pendingUserPrints deferral so the
  SpinnerEvent{Show: true} for the next turn orders things correctly.
2026-03-26 12:51:44 +03:00
Ed Zynda e18e36625e fix: route opencode models through correct provider API
Models from the opencode provider (like claude-opus-4-6 and gpt-5.3-codex)
have provider overrides in the models database that specify different npm
packages than the provider's default. The code was ignoring these overrides
and routing all models through openaicompat, causing "bad request" errors.

Changes:
- Added Provider field to modelsDBModel to capture model-specific overrides
- Added ProviderNPM field to ModelInfo registry struct
- Updated autoRouteProvider() to check for model-specific provider overrides
- Fixed URL path handling for anthropic provider (strip /v1 suffix to avoid
  double /v1/v1 paths when using third-party anthropic-compatible APIs)

Fixes routing for:
- opencode/claude-opus-4-6 -> @ai-sdk/anthropic
- opencode/gpt-5.3-codex -> @ai-sdk/openai
2026-03-26 12:44:19 +03:00
Ed Zynda be55bc03f1 Add mid-turn steering with Ctrl+S 2026-03-26 12:10:14 +03:00
Ed Zynda 09919b6307 feat: update token usage after each step in multi-step turns
Previously, token usage and costs were only updated at the end of a complete
turn. For long-running multi-step tool-calling conversations, this meant the
status bar showed stale (or zero) costs during the entire interaction.

Now, after each complete step (tool call + result), the usage tracker is
updated with the actual token counts from that step. This provides real-time
cost accumulation visible in the status bar.

Changes:
- Add StepUsageHandler type and onStepUsage parameter to agent
- Emit StepUsageEvent from kit layer after each step completes
- Handle StepUsageEvent in app layer to update UsageTracker
- Add EventStepUsage constant and StepUsageEvent struct to events

The step usage is additive - each step's tokens are added to the running
session totals, just like the final turn usage was before.
2026-03-25 18:17:48 +03:00
Ed Zynda 7a2de4cc3c fix: update token counting when switching models mid-session
When switching models (e.g., via /model command or ctx.SetModel), the usage
tracker now updates its model info to reflect the new model's:
- Pricing for cost calculations
- Context limits for percentage display
- OAuth status (to show bash costs when using OAuth creds)

Previously, token costs and context percentages continued using the old
model's settings after a switch, causing incorrect display for:
- Users switching from paid to free/OAuth models
- Users switching between models with different pricing

Changes:
- Add UpdateModelInfo() method to UsageTracker
- Call UpdateModelInfo() in both SetModel callbacks (extension and UI)
- Add auth import for OAuth detection in root.go
2026-03-25 18:09:36 +03:00
Ed Zynda acd7fd7f45 feat(ui): add line truncation to bash streaming output
Add width and count truncation to renderStreamingBashOutput to prevent
long-running commands from blowing up the TUI layout:

- Per-line width truncation via truncateLine() (ANSI-aware, matches final
  bash tool renderer behavior)
- Display cap at maxBashLines (20) showing the tail (latest output)
- Truncation hint '...(N more lines above)' when lines are hidden

The buffer still accumulates up to 50 lines for context, but only the
last 20 are rendered during streaming. This is consistent with how the
final bash tool result is displayed.
2026-03-25 18:02:50 +03:00
Ed Zynda 3446f38516 feat(ui): add line truncation to Ls tool renderer
Add maxLsLines (20) constant and truncate Ls output in the TUI to
prevent large directory listings from blowing up the layout. Shows a
'...(N more entries)' hint when truncated, consistent with all other
core tool renderers (Edit, Read, Write, Bash, Subagent).
2026-03-25 17:48:37 +03:00
Ed Zynda db4bb19bac fix: derive diff/code bg colors from active theme instead of hardcoding KITT defaults
- makeTheme() and fileConfigToTheme() now compute DiffInsertBg, DiffDeleteBg,
  DiffEqualBg, DiffMissingBg, CodeBg, GutterBg, and WriteBg by blending the
  theme's own Background with its Success/Error colors, so every theme gets
  properly tinted diff backgrounds.
- Added color derivation helpers: parseHexColor, blendHex, deriveDiffBg.
- File-based themes still allow explicit diff color overrides; derived colors
  are used only as fallbacks.
- formatToolParams() now skips body-content keys (content, old_text, new_text,
  etc.) from the header line regardless of value length, preventing raw
  unformatted code from appearing above the formatted body.
2026-03-25 17:41:37 +03:00
Ed Zynda d1cffb85ef fix: prevent bash tool from hanging on long-running/background processes
- Use process group isolation (Setpgid) so the entire process tree is
  killed on timeout/cancellation, not just the direct child
- Set cmd.Cancel to kill the process group (-pgid) with SIGKILL
- Set cmd.WaitDelay (500ms grace period) to force-close pipes when
  grandchild processes hold them open after the direct child exits
- Convert buffered path from cmd.Run() to explicit pipes + cmd.Start()
  + cmd.Wait() so WaitDelay can properly force-close pipe handles
- Reorder streaming path: cmd.Wait() before wg.Wait() so the WaitDelay
  timer starts when the child exits, not after pipes close
- Add mutex for thread-safe chunk collection in streaming mode
- Add comprehensive tests for timeout, background processes, context
  cancellation, and both buffered/streaming paths
2026-03-24 15:13:35 +03:00
Ed Zynda 329cd4ea4a feat: Add custom models via config file
Allow users to define custom models in ~/.kit.yml under the customModels
section. These models are automatically merged into the custom provider.

Example config:
  customModels:
    my-model:
      name: "My Custom Model"
      reasoning: true
      temperature: true
      cost:
        input: 0.002
        output: 0.004
      limit:
        context: 128000
        output: 32000

Usage:
  kit --model custom/my-model "Hello"
  kit --provider-url "http://localhost:8080" --model custom/my-model "Hello"

Note: When --provider-url is specified without --model, kit defaults to
custom/custom. When --provider-url is specified WITH a custom model from
config, that model is used.

Bug fixes:
- Fixed kit.New() re-loading config file and overriding CLI-specified config
- Fixed models command to reload registry for custom models
2026-03-24 14:19:49 +03:00
Ed Zynda 4e779d576f docs: Add custom provider documentation
Update README.md and www/pages/providers.md to document the new
custom/custom model that auto-loads when --provider-url is specified.
2026-03-24 13:38:52 +03:00
Ed Zynda fc054f50e8 Add custom/custom stub model for --provider-url
When users pass --provider-url without --model, automatically default
to custom/custom instead of the saved model preference. This lets users
point kit at any OpenAI-compatible endpoint without needing a provider/model
pair from the database.

The custom/custom model has:
- Zero cost (input/output = 0)
- 262K context window, 65K output limit
- Reasoning and temperature support
- Routes through openaicompat fantasy provider
2026-03-24 13:28:23 +03:00
Ed Zynda d8f1b32885 Remove --skill flag from skill subcommand to install all skills
The skill command now runs 'npx skills add mark3labs/kit' without
filtering to a single skill, installing both available skills:

1. Extensions - creating Kit extensions
2. SDK - building with the Kit Go SDK
2026-03-23 17:54:53 +03:00
Ed Zynda 1e2a3e2589 fix: preserve completed messages in session on ESC cancellation
Previously, pressing ESC twice to cancel rolled back the entire tree
session to the pre-turn state, discarding the user message, completed
tool call/result pairs, and any streamed response. Content that had
already rendered in the TUI would vanish from the session history.

Now the cancellation path uses the same logic as the non-cancellation
error path: the user message (already persisted before generation) and
any completed step messages (fully-paired tool_use + tool_result from
OnStepFinish) are preserved. Only the in-progress pending message or
tool call is discarded.

This ensures that if a message has rendered in the TUI, it stays in
the history and session.
2026-03-23 17:51:22 +03:00
Ed Zynda c7f43917b1 Add kit-sdk skill for building Go applications with the Kit SDK
Comprehensive reference covering the full pkg/kit API surface:
- Core lifecycle (New, Prompt, Close)
- All 8 prompt method variants
- Event system with 14 event types
- Hook system with 6 interceptor types and priorities
- Tool constructors, bundles, and runtime querying
- Session management (5 modes, branching, discovery)
- Model management and registry lookups
- Context estimation and compaction
- In-process subagents with event streaming
- Authentication, skills, config resolution
- 7 common patterns (scripting, daemon, streaming, etc.)
- Re-exported types reference
2026-03-23 17:27:53 +03:00
Ed Zynda 6a8833a7b1 add crypto-monitor SDK example
Background agent that checks BTC/ETH prices every 30 minutes via the
CoinGecko API and sends desktop notifications through notify-send.
Demonstrates long-running autonomous agents with the Kit SDK.
2026-03-23 16:08:25 +03:00
Ed Zynda 82cbf1d457 move SDK examples from pkg/kit/examples to examples/sdk
Relocate SDK usage examples to the top-level examples directory alongside
extension examples for better discoverability. Add README for the SDK
examples directory.
2026-03-23 16:02:54 +03:00
Ed Zynda ab09d5c9e4 docs: update README and www docs for recent features
- Update lifecycle events count from 18 to 20
- Add OnToolOutput event documentation for streaming tool output
- Add OnCustomEvent to lifecycle events list
- Fix go-edit-lint.go reference (it's project-local, not in examples/)
- Add neon-theme.go to examples list
- Add project-local extension example section
2026-03-22 21:11:18 +03:00
Ed Zynda 2347e0e506 fix: uniform background for thinking output blocks
Add Background(theme.MutedBorder) to all text elements in reasoning blocks: contentStyle, hintStyle, and footer styles. Previously these only specified foreground colors, causing them to inherit the terminal's default background instead of matching the box background.
2026-03-22 20:50:14 +03:00
Ed Zynda 3e1c19442b feat(extensions): expose ToolOutputEvent to extensions API
Extensions can now subscribe to streaming tool output events using
OnToolOutput(), giving them the same power as the internal TUI to
observe and react to tool execution in real-time.

Changes:
- Add ToolOutputEvent struct to extensions API
- Add ToolOutput constant to EventType
- Add OnToolOutput() handler registration method
- Add event bridging from kit to extensions runner
- Export ToolOutputEvent in Yaegi symbols
- Add OnToolOutput() to public SDK (pkg/kit)

Example usage in an extension:
  api.OnToolOutput(func(e ext.ToolOutputEvent, ctx ext.Context) {
    ctx.PrintInfo(fmt.Sprintf("%s: %s", e.ToolName, e.Chunk))
  })
2026-03-22 20:28:30 +03:00
Ed Zynda 3fc0ad906e feat(ui): streaming bash output in TUI
Display streaming bash output in the TUI stream region as it arrives.

Changes:
- Add streaming bash output rendering to renderStream()
- Style stdout with CodeBg, stderr with Error color
- Add streamingMu mutex for thread-safe buffer access
- Clear buffers on ToolResultEvent
- Add ToolOutputEvent to event system (pkg/kit, internal/app)
- Add ToolOutputHandler callback in agent
- Implement streaming mode in bash tool with pipes
- Add tests for accumulation and clearing

The streaming output appears in real-time below the LLM streaming text
while bash commands are executing, with proper synchronization to
prevent race conditions between Update and Render methods.
2026-03-22 20:23:19 +03:00
Ed Zynda f373c34f54 ui: remove strikethrough from diff delete lines for better readability 2026-03-22 20:21:39 +03:00
Ed Zynda 1206837af4 fix(go-edit-lint): run golangci-lint against ./... instead of single file
Running golangci-lint on a single file caused false positives due to
missing package context. Now it analyzes the entire package (./...)
while gopls still provides fast, targeted feedback on the edited file.
2026-03-22 20:19:24 +03:00
Ed Zynda f79601feb1 docs: update README and documentation for recent features
- Document model and thinking level persistence across sessions
- Document Pi-style prompt templates with argument substitution
- Document /share command and session viewer
- Document double-ESC cancellation behavior
- Document improved compaction with file tracking
- Add go-edit-lint.go to extension examples list
- Update CLI flags with --prompt-template and --no-prompt-templates
2026-03-22 19:42:30 +03:00
Ed Zynda eb3219e7ca chore: upgrade all dependencies to latest versions
- Go version: 1.26.0 -> 1.26.1 (required by charm.land/fantasy)
- Major updates:
  - charm.land/fantasy v0.11.1 -> v0.16.0
  - charm.land/lipgloss/v2 v2.0.1 -> v2.0.2
  - github.com/charmbracelet/fang v0.4.4 -> v1.0.0
  - github.com/charmbracelet/glamour v0.10.0 -> v1.0.0
  - github.com/charmbracelet/log v0.4.2 -> v1.0.0
  - github.com/mark3labs/mcp-go v0.44.1 -> v0.45.0
- Plus 30+ indirect dependency updates

All tests pass and build succeeds.
2026-03-22 19:39:53 +03:00
Ed Zynda 7e7632ad3c fix(session): use bufio.Reader instead of Scanner to handle long lines
Replace bufio.Scanner with bufio.Reader in OpenTreeSession to avoid
64KB line length limit. Scanner silently truncates long lines which can
corrupt session data. Reader handles arbitrary line lengths properly.
2026-03-22 19:36:31 +03:00
Ed Zynda 0ef46a75f2 feat: add go-edit-lint extension with TUI diagnostics visibility
- Add go-edit-lint extension that runs gopls and golangci-lint on Go file edits
- Show TUI PrintBlock only when diagnostics are found
- Add explicit prompt to LLM to fix issues when found
- Unignore .kit/extensions/ directory in .gitignore
- Color-coded borders: yellow for single tool issues, red for both
2026-03-22 19:31:50 +03:00
Ed Zynda 7f9a9da40a ui: suppress empty assistant responses in TUI
Instead of displaying 'Finished without output' or '(no output)' messages
when the LLM returns empty content, both RenderAssistantMessage functions
now return empty UIMessages with zero height. This removes confusing
placeholder messages from the scrollback.

Changes:
- internal/ui/messages.go: Return empty message when content is whitespace-only
- internal/ui/compact_renderer.go: Same behavior for compact mode
2026-03-22 19:21:12 +03:00
Ed Zynda 7ff9e84894 fix: resolve golangci-lint issues in prompts package
- Fix gofmt formatting in loader.go and loader_test.go
- Modernize strings.Index to strings.Cut (template.go:162,231)
- Use min() builtin instead of manual if check (template.go:273)
2026-03-22 19:11:52 +03:00
Ed Zynda 017eb99d44 feat: add Pi-style prompt templates
Add user-defined prompt templates that expand into full prompts with
shell-style argument substitution.

Features:
- Templates loaded from ~/.kit/prompts/*.md and .kit/prompts/*.md
- YAML frontmatter support for description
- Argument placeholders: $1, $2, $@, $ARGUMENTS, ${@:N}, ${@:N:L}
- Autocomplete integration (templates appear as /name commands)
- CLI flags: --prompt-template and --no-prompt-templates
- First-match-wins collision handling with logged diagnostics

Example template:
---
description: Review code for issues
---
Review the following code for bugs and security issues.
Focus on $1 specifically.

Usage: /review error handling
2026-03-22 19:09:15 +03:00
Ed Zynda 15a1550205 fix: don't show 'Finished without output' for empty assistant messages
When the assistant returns empty or whitespace-only content after a tool
call, the TUI was showing a 'Finished without output' message block. This
was confusing because the tool output was already displayed.

Now we simply skip rendering the assistant message block entirely when
there's no meaningful content to display.

Changes:
- printAssistantMessage: check strings.TrimSpace(text) !=  instead of text !=
- ResponseCompleteEvent handler: same trim check before printing
2026-03-22 18:49:16 +03:00
Ed Zynda 2d14b3461f feat: cancel tool calls with double-ESC without breaking API pairs
When the user presses ESC twice to cancel during a tool call, the entire
turn is now rolled back instead of persisting partial progress. This
ensures that tool_use and tool_result messages are always sent to the API
as matched pairs, avoiding errors from orphaned tool calls.

Changes:
- Save pre-turn leaf ID before appending user messages
- On context cancellation (double-ESC), branch back to pre-turn leaf
- On other errors (API failures), still persist partial progress
- Update app.go comments to reflect new behavior
2026-03-22 18:44:37 +03:00
Ed Zynda b99aafaeaa fix: correct fuzzy match position mapping and diff generation in edit tool
Two bugs fixed in internal/core/edit.go:

1. fuzzyMatch/mapFuzzyIndex returned wrong byte positions when trailing
   whitespace was stripped during normalization. The old rune-counting
   approach assumed 1:1 mapping between original and normalized strings,
   but whitespace trimming changes string length. This caused the
   replacement splice to cut at wrong boundaries, corrupting files.

   Fix: replaced with normalizeWithMap() that builds an explicit
   byte-position mapping during normalization. Also added ambiguity
   guard — multiple fuzzy matches now return no-match instead of
   silently picking the wrong one.

2. generateDiff marked the entire rest of the file as changed.
   The old code used countNewlines(old[changeIdx:]) to compute the
   diff range, which counted ALL newlines from the change point to EOF.

   Fix: replaced hand-rolled diff with udiff.Unified() (already a
   dependency) for correct standard unified diff output.

Added internal/core/edit_test.go with 33 tests covering fuzzyMatch
position mapping, normalizeWithMap correctness, generateDiff output,
and end-to-end executeEdit scenarios including corruption regression
tests.
2026-03-22 18:02:07 +03:00
Ed Zynda a55f6d3d9a feat: improved compaction with split-turn handling, file tracking, and non-destructive persistence
Rework the compaction system with several improvements modelled after
pi's approach:

Compaction engine (internal/compaction):
- Tool result truncation: cap tool result text at 2000 chars during
  serialisation to keep summarisation requests within token budgets
- Serialize tool calls and reasoning parts (previously only text)
- Split turn handling: when a single turn exceeds the keep budget,
  summarise the turn prefix separately and merge with history summary
- Cumulative file tracking: extract read/modified files from tool calls
  (read, write, edit, grep, find, ls) and carry forward across
  compactions via PreviousCompaction parameter
- Add IsSplitTurn, findTurnStart helpers and CutPoint, ReadFiles,
  ModifiedFiles fields to CompactionResult

Session tree (internal/session):
- New CompactionEntry type records summary, first-kept-entry-id, token
  stats, and file lists without deleting old messages
- BuildContext skips entries before the compaction boundary and injects
  the summary as a system message
- GetContextEntryIDs maps fantasy message indices to entry IDs for
  cut-point resolution
- GetLastCompaction retrieves prior file tracking state

Non-destructive compaction (pkg/kit):
- Compact now appends a CompactionEntry instead of clearing and
  rewriting the session — old messages remain on disk for history
- Extension hook (BeforeCompact) can now provide a custom Summary that
  replaces the LLM-generated one, in addition to cancelling

UI (internal/ui):
- Tree selector renders CompactionEntry nodes with info styling

Events & hooks (pkg/kit):
- CompactionEvent includes ReadFiles and ModifiedFiles
- BeforeCompactResult gains Summary field for extension-provided summaries
- Bridge updated to forward custom summaries from extensions
2026-03-22 17:14:50 +03:00
Ed Zynda 027c2de849 fix: detach subagent context from parent deadline
The spawn_subagent tool was inheriting the parent's context deadline,
causing subagents to be killed prematurely (e.g. after ~120s instead of
the intended 5-minute default).

The parent LLM generation loop's context carries its own deadline which,
via Go's context.WithTimeout semantics (takes the minimum of parent
deadline and new timeout), would always win over the subagent's longer
timeout.

Add a detachedContext type that preserves context values (spawner func,
etc.) and propagates parent cancellation (Ctrl-C) but strips the
deadline. Applied only in the internal tool handler (executeSubagent) so
the public Kit.Subagent() SDK method continues to honor caller-provided
context deadlines.
2026-03-22 17:12:40 +03:00
Ed Zynda d24540693c refactor: simplify max line chars calculation with builtin max() 2026-03-22 14:35:26 +03:00
Ed Zynda f7c8e7757b feat: persist model selection and thinking level across sessions
Model and thinking level choices now survive restarts, matching the
existing theme persistence pattern. Selections are saved to
~/.config/kit/preferences.yml and restored on next launch.

Precedence: CLI flag > config file > saved preference > default

Changes:
- Extended preferences struct with model and thinking_level fields
- Refactored preferences.go to shared load/save helpers (DRY)
- Added SaveModelPreference/LoadModelPreference
- Added SaveThinkingLevelPreference/LoadThinkingLevelPreference
- Persist on /model, model selector, /thinking, and Shift+Tab cycle
- Restore at startup in runNormalMode when no explicit flag/config
- Added modelFlagChanged/thinkingFlagChanged to detect explicit flags
- Comprehensive tests for all preference operations
2026-03-22 13:52:06 +03:00
Ed Zynda 0d5374b17b fix: truncate long lines in tool messages instead of wrapping
Use ANSI-aware truncation (charmbracelet/x/ansi) to prevent ugly line
wrapping in all tool renderers (Bash, Read, Write, Ls, Edit, Subagent).

- Replace byte-length padRight/truncateLine with xansi.StringWidth and
  xansi.Truncate which correctly handle ANSI escape codes and wide chars
- Bash: truncate lines to fit panel width instead of allowing 3x wrapping
- Read/Write: truncate syntax-highlighted lines before lipgloss renders
- Ls: truncate entries before styling
- Compact renderers: use ANSI-aware truncation consistently
2026-03-22 13:41:57 +03:00
Ed Zynda 25f17a104d fix: truncate long individual lines to prevent TUI blow-up
A single very long line (e.g. minified JSON, base64 blob) could wrap
into hundreds of visual rows in the TUI even when within the line-count
and byte-count limits.

Core layer (truncate.go):
- Add defaultMaxLineLen (2000 chars) per-line cap
- Apply truncateLongLines() in both TruncateTail and truncateHead
  before line/byte truncation
- Append '[N chars truncated]' marker to capped lines

UI layer:
- Cap lines in renderBashBody() to width*3 chars before rendering
- Cap lines in shell display handler (model.go) similarly

Add comprehensive tests in truncate_test.go.
2026-03-22 13:31:25 +03:00
Ed Zynda 20125f939b feat: add /share command and session viewer
- Add /share slash command that uploads session JSONL to GitHub Gist
  via the gh CLI and prints a shareable viewer URL
- Add session viewer SPA at www/public/session/index.html served at
  go-kit.dev/session/#GIST_ID
- Viewer supports all message types: text, reasoning/thinking blocks,
  tool calls (bash, read, write, edit, grep, find, ls, spawn_subagent),
  images, model changes, branch summaries, and labels
- Tool-specific rendering with syntax highlighting, diffs, collapsible
  output, and status badges
- Also supports ?url= query param for loading from any JSONL URL
- Dark theme matching Kit brand colors
2026-03-22 13:23:44 +03:00
Ed Zynda d3b67ffd14 feat(ui): render session history on /resume and /import
When a user resumes or imports a session, all conversation messages are
now rendered into the TUI scrollback buffer, giving visual context of
the prior conversation. This includes:

- User messages with original text
- Assistant responses with model name from the session
- Tool calls with name, args, and full output/error status

The implementation does a two-pass walk over the session branch:
1. Builds a toolCallID → {name, args} map from assistant messages
2. Renders each message entry using the existing print helpers

Wired into both the /resume session picker (SessionSelectedMsg handler)
and the /import command handler.
2026-03-22 00:57:57 +03:00
Ed Zynda 915dc066dd docs: update documentation for recent features
- Document theme persistence in themes.md and README.md
- Document session commands (/resume, /export, /import, /name) in commands.md and sessions.md
- Document prompt history (up/down arrows) in commands.md
- Document SubscribeSubagent API in sdk/callbacks.md and advanced/subagents.md
2026-03-21 21:15:27 +03:00
Ed Zynda 3b14814740 feat: persist theme selection across sessions
Theme choices via /theme or ctx.SetTheme() were previously lost on
restart. Now the selected theme name is saved to
~/.config/kit/preferences.yml and restored on next launch.

Precedence: .kit.yml theme > preferences.yml > default (kitt).

- Add internal/ui/preferences.go with atomic save/load
- ApplyTheme() now persists; ApplyThemeWithoutSave() for startup
- Fallback to saved preference in cmd/root.go init()
2026-03-21 21:01:25 +03:00
Ed Zynda a1decf9cff feat: add SubscribeSubagent API for per-tool-call event streaming
Add Kit.SubscribeSubagent(toolCallID, listener) which lets SDK consumers
opt into real-time events from LLM-initiated subagents. Listeners are
keyed by the spawn_subagent tool call ID, which is available in the
ToolCallEvent before the subagent starts.

The typical pattern is:

    kit.OnToolCall(func(e kit.ToolCallEvent) {
        if e.ToolName == "spawn_subagent" {
            kit.SubscribeSubagent(e.ToolCallID, func(child kit.Event) {
                // real-time subagent events
            })
        }
    })

Implementation:
- Thread toolCallID through SubagentSpawnFunc so generate() knows which
  tool call triggered the spawn
- Add subagentListenerSet (per-tool-call event bus) stored in a sync.Map
  on the Kit struct, keyed by toolCallID
- In generate(), wire OnEvent to dispatch to registered listeners only
  when SubscribeSubagent has been called for that tool call
- Listeners are cleaned up automatically when the subagent completes
- No listeners registered = no OnEvent callback = no overhead (the
  default TUI path)
2026-03-21 20:48:40 +03:00
Ed Zynda ec4ac64343 fix: stop re-emitting subagent events onto parent event bus
The core tool spawner in generate() was unconditionally setting OnEvent
to re-emit every child event onto the parent Kit's event bus. This caused
subagent tool calls, streaming text, reasoning, and responses to surface
in the TUI as if they were the parent's own events.

Remove the OnEvent callback from the core tool spawner. The spawn_subagent
tool is a blocking call that returns a summary result — it doesn't need
real-time event streaming. SDK consumers who need real-time subagent events
can call Kit.Subagent() directly with their own OnEvent callback. The
extension and ACP paths are unaffected as they bridge to their own
callbacks independently.
2026-03-21 20:41:28 +03:00
Ed Zynda a95117686e fix: override SHELL env var to bash in command execution
When the user's login shell is nushell, fish, or another non-bash shell,
the SHELL environment variable leaks through to child processes. This
causes tools like tmux to use the wrong shell for pane commands, leading
to failures (e.g. nushell rejects 'sleep 30' because it requires
'sleep 30sec').

Override SHELL to point to the resolved bash binary path in both the
bash tool (internal/core/bash.go) and the TUI shell command handler
(internal/ui/model.go) so child processes always use bash.
2026-03-21 18:47:16 +03:00
Ed Zynda c0880e1ef6 fix: preserve completed tool calls when cancelling with ESC
When pressing ESC twice to cancel an agent turn, completed tool calls
and their results were being discarded along with the in-progress text.
Only the streaming text should be discarded.

The root cause was a chain of two issues:

1. Agent layer (internal/agent/agent.go): Fantasy's Stream() returns
   nil on error, discarding all accumulated step data. Fixed by tracking
   completed step messages via the OnStepFinish callback and returning
   a partial GenerateWithLoopResult alongside the error.

2. App layer (internal/app/app.go): The in-memory message store was
   never synced from the tree session after cancellation. Fixed by
   reloading the store from the tree session (which the SDK's runTurn
   already persists partial progress to).

The existing partial-persistence code in pkg/kit/kit.go runTurn() was
correct but was dead code because the agent layer always returned nil
on error. It now receives the partial result and persists completed
step messages to the tree session as intended.
2026-03-21 18:32:28 +03:00
Ed Zynda 4e66c0b4f7 feat: add session management features (picker, history, /name, /export, /import)
Fill session management gaps compared to pi:
- TUI session picker (/resume, --resume flag) with search, scope/filter
  toggles, delete, right-aligned metadata, and background-highlighted cursor
- Prompt history navigation via up/down arrows (100-entry ring buffer)
- /name <name> command now functional (was stubbed as not implemented)
- /export [path] exports session JSONL to file
- /import <path> loads session from JSONL file
- Remove deprecated unused App.runQueueItem method
2026-03-20 18:08:48 +03:00
Ed Zynda 131ce8f2cc Fix message batching and cancellation persistence
1. Batch queued messages into single agent turn
   - Add PromptResultWithMessages() to SDK for batch submission
   - Rewrite drainQueue() to collect all queued items and submit together
   - This prevents the agent from processing queued messages sequentially

2. Persist tool messages on cancellation
   - When generation is cancelled (double-ESC), persist any completed
     tool calls and results to the session before returning
   - Prevents the agent from re-doing work when user continues

Both issues caused the agent to lose context:
- Batching: Multiple queued messages now submitted as one turn
- Cancellation: Tool results from cancelled turns are preserved
2026-03-20 17:18:07 +03:00
Ed Zynda 3d0f3358cb feat: Update model aliases for Anthropic, OpenAI, and Google Gemini
Update model aliases to point to latest versions based on models.dev:

Anthropic:
- claude-opus-latest, claude-4-opus-latest -> claude-opus-4-6
- claude-sonnet-latest, claude-4-sonnet-latest -> claude-sonnet-4-6
- Add claude-haiku-latest, claude-4-haiku-latest -> claude-haiku-4-5
- Keep existing 3.x aliases

OpenAI (new):
- o1-latest -> o1, o3-latest -> o3, o4-latest -> o4-mini
- gpt-5-latest, gpt-5-chat-latest -> gpt-5.4
- gpt-4-latest, gpt-4 -> gpt-4o
- gpt-3.5-latest, gpt-3.5 -> gpt-3.5-turbo
- codex-latest -> codex-mini-latest

Google Gemini (new):
- gemini-pro-latest, gemini-pro -> gemini-2.5-pro
- gemini-flash-latest, gemini-flash -> gemini-2.5-flash

Also update README and www documentation to reflect new aliases and
update default model references to use claude-sonnet-latest alias.
2026-03-20 14:01:22 +03:00
Ed Zynda 25da02fa65 docs: add extensions/testing page to navigation 2026-03-20 13:48:21 +03:00
Ed Zynda 4ae03aab7c refactor: move extension testing package to pkg/
Move the extension testing package from internal/extensions/test to
pkg/extensions/test to make it publicly importable by external extension
authors.

Changes:
- Moved test package files to pkg/extensions/test/
- Updated all imports from internal/ to pkg/ path:
  - README.md
  - examples/extensions/tool-logger_test.go
  - examples/extensions/extension_test_template.go
  - skills/kit-extensions/SKILL.md
  - www/pages/extensions/testing.md
  - pkg/extensions/test/README.md
  - pkg/extensions/test/harness.go

The test package is now available for external import as:
  github.com/mark3labs/kit/pkg/extensions/test

All tests pass with race detector.
2026-03-20 13:40:15 +03:00
Ed Zynda 93895392e6 docs: add PromptMultiSelect and full SpawnSubagent API to skill guide
Add missing PromptMultiSelect example to Interactive Prompts section.
Replace the minimal subprocess pattern with comprehensive SpawnSubagent
documentation including blocking/background modes, all SubagentConfig
fields, SubagentResult fields, SubagentEvent types, and handle methods.
2026-03-20 13:28:00 +03:00
Ed Zynda 473070e78b docs: add theme API to kit-extensions skill guide (correct file)
Add Themes section to Context API reference with RegisterTheme,
SetTheme, ListThemes examples and ThemeColorConfig field reference.
Add Custom Theme with Slash Command pattern to Common Patterns.
Remove mistakenly committed .agents/skills copy.
2026-03-20 13:24:48 +03:00
Ed Zynda 12268a777f docs: add theme API to kit-extensions skill guide
Add Themes section to Context API reference with RegisterTheme,
SetTheme, ListThemes examples and ThemeColorConfig field reference.
Add Custom Theme with Slash Command pattern to Common Patterns.
2026-03-20 13:21:25 +03:00
Ed Zynda 351c10d814 docs: update SKILL.md with extension testing documentation
Add comprehensive testing documentation to the kit-extensions skill:

- Add code example showing basic test structure with LoadFile(), Emit(), and assertions
- Document key testing patterns (LoadFile vs LoadString, event emission, assertions)
- List 25+ assertion helpers available in test package
- Reference tool-logger_test.go as complete example with 14 test cases
- Add link to internal/extensions/test/ in Key Files section
- Maintain existing CLI testing commands section

The skill now provides complete guidance for testing extensions alongside development.
2026-03-20 13:19:08 +03:00
Ed Zynda 9de3843605 docs: add extension testing documentation
Add comprehensive documentation for the extension testing package:

- README.md: Add 'Testing Extensions' section with basic usage and links
- www/pages/extensions/testing.md: Complete testing guide with:
  - Overview and basic usage
  - Common testing patterns (handlers, tools, widgets, etc.)
  - Available assertions reference table
  - Advanced usage (mock context access, multiple extensions)
  - Best practices and limitations
  - Links to complete examples
- www/pages/extensions/examples.md: Add test examples and template references
- www/pages/extensions/overview.md: Link to testing documentation

All documentation cross-references existing test files and examples.
2026-03-20 13:18:46 +03:00
Ed Zynda 1d5473e111 chore: remove unnecessary test/ ignore rule
Remove the test/ directory ignore rule from .gitignore since
there is no root-level test directory in the project.

The internal/extensions/test/ package remains tracked as expected.
2026-03-20 13:17:22 +03:00
Ed Zynda b6adcf159e feat: add extension testing package
Add comprehensive testing utilities for Kit extensions:

- internal/extensions/test/: New test package with:
  - harness.go: Test harness for loading extensions into Yaegi
  - mock.go: Mock context that records all context interactions
  - assert.go: 20+ assertion helpers (AssertBlocked, AssertWidgetSet, etc.)
  - harness_test.go: 18 comprehensive test examples
  - README.md: Complete documentation with usage examples

- internal/extensions/test_api.go: Helper function for creating test API objects

- examples/extensions/tool-logger_test.go: 14 tests demonstrating real extension testing

- examples/extensions/extension_test_template.go: Copy-and-paste template for extension authors

- .gitignore: Allow internal/extensions/test/ directory

All 93 tests pass including race detector.
2026-03-20 13:16:11 +03:00
Ed Zynda b1da4a28e6 docs: add comprehensive theming documentation
Add dedicated themes page (www/pages/themes.md) covering built-in
themes, custom theme files, config integration, extension API, and
precedence rules. Update README with theming section and feature
listing. Add /theme to CLI commands reference, theme config to
configuration docs, and RegisterTheme/SetTheme/ListThemes to
extension capabilities. Add neon-theme to examples index.
2026-03-20 13:15:20 +03:00
Ed Zynda 95abb6fa6e feat: add extension API for programmatic theme registration and switching
Add RegisterTheme, SetTheme, and ListThemes to the extension Context,
allowing extensions to create custom themes at runtime and switch
between them. Uses ThemeColor/ThemeColorConfig concrete structs (no
interfaces) for Yaegi safety.

Include neon-theme.go example extension demonstrating the API.
2026-03-20 13:03:23 +03:00
Ed Zynda a9970cf346 feat: add /theme command with 22 built-in themes and file-based discovery
Add a theme registry that discovers themes from three sources (in
precedence order): built-in presets, user dir (~/.config/kit/themes/),
and project-local dir (.kit/themes/). Later sources override earlier
ones with the same name.

Ship 22 built-in presets ported from the OpenCode theme collection:
amoled, ayu, catppuccin, dracula, everforest, flexoki, github,
gruvbox, kanagawa, kitt, material, matrix, monokai, nord, one-dark,
rose-pine, solarized, synthwave, tokyonight, vercel, vesper, zenburn.

Add /theme slash command with tab-completion for listing available
themes and switching at runtime. Also fix .yml extension handling
in config.FilepathOr.
2026-03-20 12:54:16 +03:00
Ed Zynda 13060a20f9 feat: add KITT-inspired theme system with unified markdown colors
Replace the Catppuccin color palette with a Knight Rider KITT-inspired
theme — scanner reds, amber dashboard glows, and dark cockpit tones.
No blues or bright greens; the entire palette stays in the warm
red/amber/gray family.

Unify the theme system by folding the standalone MarkdownTheme config
into the main Theme struct, eliminating the separate config path.
Replace all hardcoded lipgloss.Color() calls across input, overlay,
and CLI components with semantic theme references so every color
responds to theme customization.
2026-03-19 18:04:56 +03:00
Ed Zynda adf603e944 fix: add favicon link to index.html 2026-03-19 17:29:35 +03:00
Ed Zynda af486133a5 chore: remove dead code, unexport internal symbols, clean up stale comments
- Remove never-called functions: ListChildSessions, NewMessageEntryFromRaw,
  ProviderPool.Stats/PoolStats, CLI.DisplayToolCallMessage
- Remove deprecated ValidateModel (migrate callers to LookupModel)
- Remove deprecated colon-separated model format shim
- Unexport package-internal symbols: EstimateTokens, GetRequiredEnvVars,
  GeneratePKCE, ErrNoClipboardTool, ThinkingBudgetTokens, NewMessageRenderer
- Remove stale TAS-15/TAS-16 placeholder comments (both fully implemented)
- Fix misleading 'temporary approach' comment in clipboard_darwin.go
- Replace interface{} with any in extension examples
- Simplify auto-commit.go dead variable (CombinedOutput → Run)
2026-03-19 17:25:53 +03:00
Ed Zynda a97cd47ced docs: add GitHub source links to extension examples page 2026-03-19 17:11:14 +03:00
Ed Zynda 68518a2bdb docs: add bun and pnpm as installation options 2026-03-19 17:02:09 +03:00
Ed Zynda fd61db3e12 chore: commit .tome/ as Tome intends, remove CI workaround 2026-03-19 16:48:38 +03:00
Ed Zynda e49066a119 chore: gitignore .tome/ and generate entry in CI instead 2026-03-19 16:46:59 +03:00
Ed Zynda efaff7f44f fix: include .tome/entry.tsx for CI builds 2026-03-19 16:42:04 +03:00
Ed Zynda d3c970b607 chore: set baseUrl to go-kit.dev 2026-03-19 16:30:08 +03:00
Ed Zynda 23254fee64 feat: add static docs site using Tome with GitHub Pages deployment
Scaffold Tome docs site in www/ with 17 pages covering installation,
configuration, CLI reference, extensions, sessions, Go SDK, and advanced
usage. Custom Knight Rider theme (cipher base + red accent, dark mode,
Space Grotesk fonts). GitHub Pages workflow deploys via Bun on push to
master.
2026-03-19 16:27:35 +03:00
Ed Zynda fe072ad2e1 fix: correct README inaccuracies and document missing features
Fix critical errors: MIT license (was Apache 2.0), broken CONTRIBUTING.md
link, wrong session path, nonexistent SDK methods (SaveSession/LoadSession).
Add missing CLI flag (--thinking-level), commands (install, skill),
spawn_subagent tool, 3 lifecycle events, 17 extension examples, 8 extension
capabilities, 7 internal directories, and complete JSON output schema.
2026-03-19 15:49:55 +03:00
Ed Zynda 8840cbfabc feat: show spinner during shell command execution (! and !!)
Shell commands (! and !!) now display the KITT spinner animation while
running, matching the behavior of LLM agent steps and compaction.

On shellCommandMsg: transition to stateWorking and start the spinner.
On shellCommandResultMsg: stop the spinner and return to stateInput.
2026-03-18 17:48:15 +03:00
Ed Zynda a11b41cda4 fix: prevent duplicate spinner tick loops causing double-speed animation
The KITT spinner animation would sometimes run at 2x (or higher) speed
because multiple concurrent tick loops could accumulate in the Bubble Tea
command queue.

The problematic sequence: SpinnerEvent{Show:true} starts tick loop A,
SpinnerEvent{Show:false} sets spinning=false but tick A is still
in-flight, then ToolExecutionEvent restarts spinning and starts tick
loop B. When the stale tick A fires, spinning is true again so it
continues — now two loops advance spinnerFrame simultaneously.

Fix: add a generation counter (uint64) to StreamComponent. Each
streamSpinnerTickMsg carries the generation it was created for. The tick
handler only processes ticks matching the current generation — stale
ticks from previous start/stop cycles are silently discarded.

Generation is bumped on every spinner start, stop, and Reset() to
ensure at most one tick loop is ever active.
2026-03-18 17:44:11 +03:00
Ed Zynda 8b7be8b735 fix: use immediate parent dir for main.go extension names
deriveExtensionName was using the full directory path (e.g.
examples/extensions/kit-telegram) to derive the display name for
main.go extensions, producing verbose names like 'Examples Extensions
Kit Telegram Extension'. Now uses filepath.Base(dir) so only the
immediate parent directory is used, giving 'Kit Telegram Extension'.

Also fix TestLoadExtensions_SkipsBadFiles which was flaky when
globally-installed git packages existed — isolate the test from the
host environment by overriding XDG_CONFIG_HOME, XDG_DATA_HOME, and
the working directory.
2026-03-18 17:36:06 +03:00
Ed Zynda caa6d1c178 Add kit-telegram example extension
- Copy Telegram relay extension from ../kit-telegram
- Add README with quickstart, commands, API reference, and architecture
- Update examples/extensions/README.md with Integrations section and details
2026-03-18 17:14:47 +03:00
Ed Zynda 001156053d chore: untrack .agents and skills-lock.json, update skills SKILL.md 2026-03-18 17:05:03 +03:00
Ed Zynda 54717e32bc refactor: Auto-show multi-select when repo has multiple extensions
Remove --select flag. Multi-select now appears automatically when a repo
contains more than one extension. Add --all flag to skip selection.
2026-03-18 16:53:42 +03:00
Ed Zynda 5b214b9fdf refactor: Use huh for CLI prompts, fix extension discovery in mixed repos
- Replace custom multi-select with huh.NewMultiSelect for kit install --select
- Replace raw bufio prompts in cmd/auth.go with huh.NewConfirm and huh.NewInput
- Fix extension discovery to use opinionated conventions (only scan root,
  extensions/, ext/, examples/extensions/ directories, skip cmd/internal/pkg/)
- Fix loader to use same convention-based scanning for installed git repos
- Fix errcheck lint warning in loader.go
2026-03-18 16:49:48 +03:00
Ed Zynda c5e6ca6e4d feat: Add kit install command for git-based extension distribution
Add comprehensive extension installation system for Kit:

Features:
- kit install <git-url> - Install extensions from git repos
- kit install <url> --local - Install to project .kit/git/ directory
- kit install <url> --select - Interactive selection for multi-extension repos
- kit install <url> --update - Update installed extensions
- kit install <url> --uninstall - Remove installed extensions
- Version pinning via @ref (tags, branches, commits)
- Support multiple URL formats (shorthand, git:, https, ssh)

Implementation:
- internal/extensions/installer.go - Git clone, checkout, validation
- internal/extensions/manifest.go - Package tracking with Include filtering
- internal/extensions/loader.go - Respect Include field when loading
- cmd/install.go - Cobra command with interactive prompts
- PromptMultiSelectConfig API - Multi-select prompts for extensions

Storage:
- Global: ~/.local/share/kit/git/<host>/<owner>/<repo>/
- Project: .kit/git/<host>/<owner>/<repo>/
- Manifests: packages.json tracking installed packages

Examples:
- Reorganized examples/extensions/ with README.md
- Added status-tools/ multi-file extension example
- Created comprehensive install guide in SKILL.md

Testing:
- Added installer_test.go with 15+ test cases
- All tests pass, build clean

Closes #extension-distribution
2026-03-18 16:21:31 +03:00
Ed Zynda 419a139137 fix: make TUI responsive for terminal resizing at any dimension
Prevent layout corruption and visual breakage when the terminal is
resized to narrow or short dimensions:

- Status bar: progressively drops middle/right sections instead of
  wrapping to multiple lines (broke height calculation)
- Autocomplete popup: guard against negative widths, truncate names
  before rendering to prevent text wrapping inside fixed-width columns,
  adapt to name-only mode at very narrow widths
- Tree selector: use full height minus chrome instead of halved height,
  guard against negative width in node truncation
- Model selector: rune-aware name truncation preserving provider tags,
  width-adaptive entry rendering
- Overlay dialog: clamp dimensions to terminal bounds instead of using
  fixed minimums that could exceed the terminal
- Input hint, popup footer, and all help text: tiered adaptive variants
  for different terminal widths
- Queued messages: measure actual rendered height instead of fixed
  5-line-per-message estimate
2026-03-18 14:52:43 +03:00
Ed Zynda 7b963624c1 fix: ensure all message blocks appear below previous content in scrollback
tea.Println inserts above BubbleTea's managed region, but after
StepCompleteEvent the previous response stays in the stream component
(managed region). Any subsequent print (tool results, shell commands,
slash output, errors) would appear above that response — out of order.

Introduce a scrollback buffer: all print helpers now buffer rendered
content via appendScrollback(). At the end of each Update cycle,
drainScrollback() combines everything into a single tea.Println. If
the stream component has unflushed content it is auto-prepended, so
new messages always appear below the previous assistant response.
2026-03-18 14:16:37 +03:00
Ed Zynda 66f2ba543b refactor: align message styling with iteratr conventions
Swap user/assistant border colors (user=blue, assistant=mauve), remove
per-message timestamps and username labels, simplify system messages to
borderless muted text with diamond prefix, change tool name color from
peach to blue, and redesign thinking blocks with surface background,
line truncation, and duration footer.
2026-03-17 15:11:33 +03:00
Ed Zynda 6dd052b990 fix: improve input keybindings, user message rendering, and scrollback ordering
- Change newline keybinding from alt+enter to shift+enter across all
  input components (main input, slash command input, prompt overlay)
- Skip markdown rendering for plain-text user messages so newlines are
  preserved without extra paragraph spacing from glamour
- Fix scrollback ordering: defer queued user message printing to
  SpinnerEvent where previous stream content is guaranteed complete,
  combining flush + user message into a single tea.Println call
2026-03-17 14:23:16 +03:00
Ed Zynda ef8628eecc fix: forward subagent events to parent event bus in core spawn_subagent tool
The spawner closure in generate() called m.Subagent() without setting
OnEvent, so child events (tool calls, text streaming, reasoning deltas)
were silently discarded. Wire OnEvent to re-emit on the parent's bus,
matching the behavior already present in the extension SpawnSubagent path.
2026-03-17 13:03:41 +03:00
Ed Zynda 3167222b72 fix: gracefully recover from bad model names in subagents
If the requested model fails (bad name, unsupported provider), fall
back to the parent's model instead of returning a hard error. The
original prompt is prepended with a note so the agent knows which
model is actually running and can adjust future calls.
2026-03-16 13:43:52 +03:00
Ed Zynda e3b37191b1 fix: inherit parent provider for bare model names in subagents
When spawn_subagent is called with a model name like 'claude-haiku'
(no provider prefix), prepend the parent's provider instead of letting
ParseModelString guess. Only full 'provider/model' strings bypass this.
2026-03-16 13:41:02 +03:00
Ed Zynda 41d5f5e0fb feat: add OnEvent callback for real-time subagent event streaming
Add SubagentEvent type to extension API and OnEvent field to
SubagentConfig so extensions can watch subagent tool calls, text
chunks, reasoning deltas, and turn lifecycle events in real time.

The SDK's Kit.Subagent() already had OnEvent via kit.SubagentConfig.
This wires it through to the extension layer with a concrete
SubagentEvent struct (Yaegi-safe) and bridges SDK events to it
in both cmd/root.go and the ACP server.
2026-03-16 13:06:53 +03:00
Ed Zynda 3ad0b3616d fix: surface SubagentSessionID in ToolResultMetadata
The subagent_session_id was already attached to the fantasy response
metadata by internal/core/subagent.go but ToolResultMetadata had no
field for it, so json.Unmarshal silently dropped it. Add the field
so SDK consumers can detect subagent tools and load their sessions.
2026-03-16 13:01:34 +03:00
Ed Zynda 8831b49b51 feat: in-process subagents replace subprocess spawning
Subagents now run as child Kit instances in the same process instead of
spawning a kit binary subprocess. This removes the binary dependency,
eliminates JSON serialization overhead, and enables SDK-only consumers
to use subagents without installing the kit CLI.

- Add Kit.Subagent() method for in-process subagent execution
- Add SubagentConfig/SubagentResult types to the SDK
- Add context-based SubagentSpawnFunc injection so core spawn_subagent
  tool calls back to Kit.Subagent() without an import cycle
- Add SubagentTools() bundle (all core tools minus spawn_subagent)
- Add viperInitMu for thread-safe concurrent kit.New() calls
- Wire extension ctx.SpawnSubagent and ACP server to use in-process
- Child Kit gets parent's model as fallback, in-memory or persisted
  session, and no extensions (preventing recursive loading)
2026-03-16 11:39:59 +03:00
Ed Zynda c94edc929b feat: add rich tool metadata to SDK and extension events (Gaps 1-8)
Thread ToolCallID, ToolKind, ParsedArgs, FileDiff metadata, StopReason,
SessionID, and StructuredMessages across the SDK event bus, extension
wrapper, app bridge, hooks, and ACP server layers.

- Gap 1: ToolCallID from Fantasy's ToolCallContent threaded end-to-end
- Gap 2: ToolKind via static lookup (execute/edit/read/search/agent)
- Gap 3+4: FileDiffInfo with DiffBlocks via fantasy.ToolResponse.Metadata
- Gap 5: StopReason from Fantasy FinishReason on TurnEndEvent/TurnResult
- Gap 6: Subagent sessions now opt-out (NoSession); SessionID in JSON output
- Gap 7: GetStructuredMessages() returns typed ContentParts
- Gap 8: ParsedArgs map[string]any on tool events for convenience

Edit/write tools attach structured diff metadata. ACP server uses real
ToolCallIDs. Extension and SDK events kept in sync with matching fields.
2026-03-16 11:10:05 +03:00
Ed Zynda e49194a0d4 fix(acp): wire extension context so extensions work in ACP mode
Extensions were loaded but non-functional in ACP because
SetExtensionContext was never called. Wire a headless context with
no-op TUI stubs, functional data/model/tool APIs, and emit
SessionStart so extension lifecycle hooks fire during ACP sessions.
2026-03-15 15:29:08 +03:00
Ed Zynda 46b1acf444 fix 2026-03-15 15:10:02 +03:00
Ed Zynda 6a6d201a50 add LSP diagnostics example extension
Adds an extension that starts language servers on demand and surfaces
diagnostics after file edits, following crush's LSP integration pattern.
Hooks into the edit tool lifecycle to diff pre/post diagnostics, display
a persistent widget, and expose lsp_diagnostics/lsp_hover tools plus
/lsp and /lsp-check slash commands.
2026-03-15 14:29:27 +03:00
Ed Zynda 930cbcb4f2 fix: use full GitHub URLs for file references in kit-extensions skill 2026-03-15 13:01:05 +03:00
Ed Zynda 12e1ef2036 skills 2026-03-15 12:55:47 +03:00
Ed Zynda a05da5f3ab fix(auth): support OAuth credentials in ACP mode and auto-refresh tokens
Remove the early ValidateEnvironment gate from CreateProvider that only
checked env vars and --provider-api-key, blocking stored OAuth credentials
from working. Each provider creation function already handles its own auth
resolution with clear error messages.

Update ValidateEnvironment to also check stored Anthropic credentials so
the model selector UI correctly shows Anthropic models for OAuth users.

Add automatic token refresh in oauthTransport so long-lived ACP sessions
survive token renewals. Surface actionable auth error messages in ACP
session creation.

Fix pre-existing staticcheck SA5011 warnings in test files.
2026-03-15 12:38:23 +03:00
Ed Zynda fefbf19b42 fix(acp): default mcpServers to empty array for clients that omit it 2026-03-15 11:57:30 +03:00
Ed Zynda 93905d4d77 fix(acp): remove startup message from stdio output 2026-03-15 11:38:31 +03:00
Ed Zynda 7268ccdf4d perf(ui): throttle stream rendering with chunk coalescing and render cache
Streaming chunks now accumulate in a pending buffer and flush on a 16ms
tick (~60fps) instead of triggering a full markdown re-render on every
chunk. Between flushes, View() returns a cached string — no markdown
parsing, no lipgloss styling, no terminal escape sequence churn. This is
especially impactful for inline rendering (no alt screen) where each
frame requires cursor repositioning across the full view height.
2026-03-15 11:36:04 +03:00
Ed Zynda 9f59fa42dc fix: resolve golangci-lint issues
- Use strings.Cut instead of strings.Index (modernize)
- Remove unused session registry methods (load, remove)
2026-03-14 17:30:36 +03:00
Ed Zynda 8af7ca8455 refactor(ui): simplify tool names in spinner display
Show 'Subagent' instead of 'spawn_subagent' and remove 'Executing' prefix
for cleaner parallel tool status display.
2026-03-14 17:25:40 +03:00
Ed Zynda 424847f0db feat: enable parallel tool execution with multi-tool status display
- Mark read-only core tools as parallel-safe (read, grep, find, ls)
- Mark spawn_subagent as parallel-safe for concurrent task delegation
- Update UI to track multiple active tools during parallel execution
- Display 'Running: tool1, tool2, ...' in spinner for concurrent tools
- Add test for parallel tool execution scenarios

Fantasy already supports parallel execution via ToolInfo.Parallel field.
Tools marked parallel run concurrently (up to 5 at a time).
2026-03-14 17:24:20 +03:00
Ed Zynda 4c126ca41b feat(ui): show clean summary for subagent results instead of raw output
- Add custom renderer for spawn_subagent tool showing status + 3-line preview
- Pass toolArgs through ToolExecutionEvent to show task in spinner
- Display 'Subagent: <task>' during execution instead of generic message
- Compact mode shows concise one-line status summary
2026-03-14 17:04:50 +03:00
Ed Zynda 4bdc4f75cc chore: remove openspec directory 2026-03-09 23:10:15 +03:00
Ed Zynda bbd8975ca0 feat: add first-class subagent support for task delegation
Implement 4-phase subagent system enabling LLM and extensions to spawn,
manage, and orchestrate child Kit instances for parallel task execution.

- Phase 1: SDK API with SpawnSubagent() for extensions
- Phase 2: spawn_subagent core tool for LLM usage
- Phase 3: Session hierarchy with ParentSessionID tracking
- Phase 4: Provider pooling for concurrent model access

New files:
- internal/extensions/subagent.go: SpawnSubagent implementation
- internal/core/subagent.go: Core tool definition
- internal/models/pool.go: Provider pool for concurrency
- examples/extensions/subagent-test.go: Test extension
- openspec/subagent-support.md: Design specification
2026-03-09 23:07:27 +03:00
Ed Zynda e613a07773 feat: add ACP server mode (kit acp)
Implement Agent Client Protocol server allowing ACP-compatible clients
(e.g. OpenCode) to drive Kit as a remote coding agent over stdio.

- internal/acpserver/agent.go: acp.Agent implementation bridging Kit's
  LLM execution, tool system, and event bus to ACP session updates
- internal/acpserver/session.go: session registry mapping ACP sessions
  to persisted Kit JSONL tree sessions
- cmd/acp.go: cobra subcommand wiring stdio JSON-RPC connection
- Add acp-go-sdk dependency, update README with ACP docs
2026-03-09 21:41:10 +03:00
Ed Zynda 1d3b4f8d56 feat: add skill subcommand to install kit-extensions skill via skills.sh 2026-03-09 14:24:09 +03:00
Ed Zynda 118af2e152 fix: clear conflicting temperature/top_p for Anthropic API
Anthropic rejects requests with both temperature and top_p set.
When both are configured (typically from defaults), clear top_p
so temperature takes precedence.
2026-03-09 10:26:41 +03:00
Ed Zynda c46687fc44 fix: pass image file parts through Fantasy agent's Files field
splitPromptAndHistory was extracting only text from the last user
message, discarding FilePart data (clipboard images). The fix extracts
both text and file parts, passing files via AgentStreamCall.Files and
AgentCall.Files so Fantasy includes them in the API request.

Also preserves file parts when BeforeTurn hooks or skill expansion
replace the user message text in runTurn.
2026-03-09 10:26:31 +03:00
Ed Zynda aeaa5368af fix: use max() builtin to satisfy modernize lint 2026-03-08 11:43:37 +03:00
Ed Zynda 4966c0ca2a feat: add clipboard image paste support (Ctrl+V)
Add multimodal image support so users can paste clipboard images into
prompts alongside text. Images are read from the system clipboard via
platform-specific tools and sent as fantasy.FilePart to the LLM API.

- New internal/clipboard package with platform-specific image readers:
  Linux: xclip (X11) with wl-paste (Wayland) fallback
  macOS: osascript with AppKit NSPasteboard
  Magic byte detection for PNG/JPEG/GIF/WebP/BMP/TIFF
- New ImageContent type in message model with full serialization and
  Fantasy bridge support (ImageContent <-> fantasy.FilePart)
- InputComponent handles Ctrl+V (paste image), Ctrl+U (clear images),
  shows attachment indicator, and carries images through submitMsg
- App layer queue upgraded from []string to []queueItem to carry files
  alongside prompts through the drain loop
- Kit SDK gains PromptResultWithFiles() for multimodal user messages
- AppController interface extended with RunWithFiles()
2026-03-08 11:37:21 +03:00
Ed Zynda f3ea18ae3a feat: add thinking model support with configurable reasoning levels
Add extended thinking/reasoning support for Anthropic and OpenAI models:

- ThinkingLevel type (off/minimal/low/medium/high) with token budgets
- Stream reasoning deltas via OnReasoningDelta through SDK→TUI event pipeline
- Render thinking blocks in StreamComponent (muted italic, collapsible)
- ctrl+t toggles thinking visibility, shift+tab cycles thinking level
- /thinking slash command with tab-completion for level names
- --thinking-level CLI flag and config file support
- Map ThinkingLevel to OpenAI ReasoningEffort for Responses API
- Auto-bump Anthropic max_tokens when thinking budget exceeds it
- Fix ResponseCompleteEvent prematurely resetting stream in streaming mode
- Status bar displays current thinking level
2026-03-07 21:27:46 +03:00
Ed Zynda 24ea2c94e3 feat: add OpenAI Responses API support for codex/gpt-5/o3/o4 models
Enable fantasy's Responses API path (WithUseResponsesAPI) for the OpenAI
provider so that models like gpt-5.3-codex, codex-mini-latest, o3, o4-mini,
and other Responses-only models work correctly.

- Enable WithUseResponsesAPI on both createOpenAIProvider and
  createAutoRoutedOpenAIProvider
- Build provider options for reasoning models (reasoning_summary, encrypted
  reasoning content) matching crush's coordinator behaviour
- Thread ProviderOptions from provider creation through to the fantasy agent
  in NewAgent, SetModel, and the SDK Complete path
- Pass generation parameters (Temperature, MaxTokens, TopP, TopK) to the
  fantasy agent for all providers (previously only Ollama)
- Fix extension tool schema for Responses API: parse Parameters JSON Schema
  string into fantasy ToolInfo format, ensure Required is never nil (OpenAI
  rejects null, expects empty array)
2026-03-07 11:03:10 +03:00
Ed Zynda 4577d218d3 feat: add /model slash command with interactive fuzzy-finding selector
Add /model command that allows switching LLM models mid-session.
When invoked without arguments, opens a full-screen selector overlay
showing only models with configured API keys, with inline fuzzy search,
cursor navigation, and current model indicator. When invoked with an
argument (e.g. /model anthropic/claude-haiku-4-5), switches directly.

Also upgrades all Go dependencies to latest versions.
2026-03-06 18:50:32 +03:00
Ed Zynda bd48457b27 fix: resolve golangci-lint modernize and staticcheck warnings 2026-03-06 15:40:29 +03:00
Ed Zynda 84298a0743 fix: add 20-line display truncation for shell command output
Match the tool result renderer behavior — show first 20 lines
with a '...(N more lines)' hint. Full output still goes to
context (with TruncateTail limits) for ! commands.
2026-03-05 19:31:22 +03:00
Ed Zynda 393074447b fix: truncate shell command output in TUI using same limits as core bash tool 2026-03-05 19:24:49 +03:00
Ed Zynda 879723fe90 feat: add ! and !! shell command prefixes (matching pi behavior)
! runs a shell command with output included in LLM context.
!! runs a shell command with output excluded from LLM context.
Adds AddContextMessage to AppController for injecting messages
without triggering an LLM turn.
2026-03-05 19:17:41 +03:00
Ed Zynda 57250a3a3d refactor: remove --prompt flag, positional args are the only way
Drop the --prompt/-p flag entirely. Non-interactive mode is now
triggered by passing positional arguments:

  kit "Explain this"
  kit @file.go "Review this" --json
  kit @a.go @b.go --quiet

Updated extension examples (kit-kit.go, subagent-widget.go) to pass
the prompt as a positional arg. Updated AGENTS.md and README.md.
2026-03-05 19:03:47 +03:00
Ed Zynda 7e1686e572 feat: positional args as primary non-interactive mode, hide --prompt
Positional args are now the main way to run non-interactive mode:

  kit "Explain this codebase"
  kit @code.ts @test.ts "Review these files"
  kit @go.mod "What module?" --quiet

--prompt is hidden but still works for subprocess compat (extensions
spawn kit with --prompt internally). Updated --quiet/--json/--no-exit
error messages to reference the new positional arg pattern.
2026-03-05 19:00:51 +03:00
Ed Zynda 4a8b10cde7 feat: support Pi-style positional @file args
Enables: kit @code.ts @test.ts "Review these files"

Positional args starting with @ are treated as file attachments —
their content is read and prepended to the prompt. Remaining
positional args are joined as the prompt text. Works alongside
--prompt flag (files prepended, extra text appended).
2026-03-05 18:57:00 +03:00
Ed Zynda cc5611eff7 feat: support @file references in non-interactive mode (--prompt) 2026-03-05 18:54:17 +03:00
Ed Zynda 51c70b63a7 feat: add @file autocomplete and context attachment
Type @ in the input to trigger a fuzzy file picker popup. Files are
discovered via git ls-files (with os.ReadDir fallback), scored by
fuzzy match, and displayed in the existing autocomplete popup.

Tab/Enter inserts the selected path; directories keep the popup open
for drilling. On submit, @file tokens are expanded into XML-wrapped
file content before being sent to the agent. No CWD restriction —
supports ~/, ../, and absolute paths.
2026-03-05 18:46:25 +03:00
Ed Zynda c9ee80d98a fix: run before-hook callbacks in goroutines to prevent TUI deadlock
Before-hook callbacks (OnBeforeSessionSwitch, OnBeforeFork) were called
synchronously inside BubbleTea's Update(), so extensions that used
blocking prompts (ctx.PromptConfirm) would deadlock — the channel read
waited for Update() to process the PromptRequestEvent, but Update()
was blocked on that same channel read.

Run hooks in dedicated goroutines and deliver results via SendEvent,
matching the pattern already used by extension slash commands.
2026-03-05 10:34:17 +03:00
Ed Zynda 3ecedcbc2d docs: add comprehensive README with CLI reference, extensions, SDK, and configuration guide 2026-03-03 18:33:42 +03:00
Ed Zynda dbfa410fc1 fix: use strings.Builder instead of string += in loops 2026-03-02 20:25:07 +03:00
Ed Zynda 512ecb92dc cleanup 2026-03-02 20:05:37 +03:00
Ed Zynda aede76d807 feat: add TUI suspend, custom message rendering, and extension hot-reload
- ctx.SuspendTUI(callback): releases terminal for interactive subprocesses
  (vim, shell, htop), automatically restores TUI when callback returns.
  Uses BubbleTea v2 ReleaseTerminal/RestoreTerminal.

- api.RegisterMessageRenderer(config) + ctx.RenderMessage(name, content):
  named render functions for branded/styled extension output. Renderers
  receive content and terminal width, return ANSI-styled strings.

- ctx.ReloadExtensions(): hot-reloads all extensions from disk. Emits
  SessionShutdown to old extensions, reloads source, emits SessionStart
  to new. Event handlers, commands, renderers, shortcuts update immediately.
  TUI command list refreshes via WidgetUpdateEvent. Extension tools are
  NOT updated (baked into agent at creation, documented limitation).

New example extensions: interactive-shell.go, branded-output.go, dev-reload.go
2026-03-02 19:32:19 +03:00
Ed Zynda 9e1df38836 feat: add keyboard shortcuts, tool context, and ToolCallEvent source field
- RegisterShortcut(ShortcutDef, handler) for global keyboard shortcuts
  that fire across all non-modal app states (after ctrl+c, before
  component dispatch). Handlers run in goroutines for safe blocking calls.
- ToolContext with IsCancelled/OnProgress for rich tool execution;
  ExecuteWithContext on ToolDef takes priority over simple Execute.
- Source field on ToolCallEvent (currently "llm", forward-compatible
  with future user-initiated tool calls).
- Fix missing //go:build ignore on context-inject.go.
- Update plan-mode.go to register ctrl+alt+p shortcut.
2026-03-02 19:04:37 +03:00
Ed Zynda 8f5efee837 feat: add session before-hooks (OnBeforeFork, OnBeforeSessionSwitch) and compaction event (OnBeforeCompact)
Add three new extension events that allow extensions to gate destructive
session operations and compaction:

- OnBeforeFork: fires before branching in the tree selector; handler can
  cancel with reason (e.g. dirty-repo guard)
- OnBeforeSessionSwitch: fires before /new resets the session branch;
  handler can cancel with reason
- OnBeforeCompact: fires before context compaction (auto or manual);
  handler receives token stats and IsAutomatic flag, can cancel

Includes SDK hook registry (beforeCompact), extension bridge, UI
callbacks threaded through AppModelOptions, and two example extensions:
- confirm-destructive.go: git dirty check + fork confirmation
- compact-notify.go: compaction notification + auto-compact gating
2026-03-02 16:35:00 +03:00
Ed Zynda a392d3e572 feat: add OnContextPrepare event for context window filtering and injection
Extensions can now register an OnContextPrepare handler that fires after
the context window is built from the session tree and before messages are
sent to the LLM. Handlers receive ContextMessage entries with positional
indices and can filter, reorder, or inject messages. Original messages
referenced by index preserve tool calls, reasoning, and other complex
parts. New context-inject example extension demonstrates injecting a
local .kit/context.md file as an ephemeral system message every turn.
2026-03-02 15:56:08 +03:00
Ed Zynda c40dc2f4fb feat: add argument tab-completion for extension slash commands
Extensions can now provide a Complete function on CommandDef that supplies
argument suggestions. When the user types a command name followed by a space,
the input popup switches to argument-completion mode, calling Complete with
the partial text and displaying matching suggestions.
2026-03-02 15:37:52 +03:00
Ed Zynda 37e82781b1 feat: add OnModelChange event and ctx.Exit(); remove Gap/Pi references from comments 2026-03-02 14:49:51 +03:00
Ed Zynda 23c16bb197 feat: add tool mgmt, model mgmt, options, event bus, LLM completion, steer mode, and 10 example extensions
Phase 2+3 extension API additions:
- Tool management: GetAllTools, SetActiveTools (plan-mode support)
- Model management: SetModel, GetAvailableModels, ModelChangedEvent
- Extension options: RegisterOption, GetOption, SetOption (env/config/default)
- Inter-extension event bus: OnCustomEvent, EmitCustomEvent
- Direct LLM completion: ctx.Complete with streaming/blocking modes
- Steer delivery mode: CancelAndSend for interrupt-and-redirect

New example extensions (10):
- plan-mode.go: read-only exploration with /plan toggle
- summarize.go: conversation summarization via ctx.Complete
- bookmark.go: persistent bookmarks via AppendEntry/GetEntries
- auto-commit.go: auto-commit on exit using last assistant message
- permission-gate.go: confirm dangerous bash commands
- protected-paths.go: block writes to .env, .git/, secrets/
- notify.go: desktop notifications on agent completion
- inline-bash.go: !{cmd} expansion in prompts
- pirate.go: system prompt persona injection
- project-rules.go: load .kit/rules/*.md into system prompt

Always-wrap tools through runner for SetActiveTools disabled-tool checking.
Removed phase1/phase2 test extensions from examples.
2026-03-02 14:31:35 +03:00
Ed Zynda 9449f1fcdf feat: add session management, persistence, editor text, and status bar APIs for extensions
Implement Phase 1 extension API gaps identified in the pi-mono gap analysis:

- Gap 1: Session Management API (GetMessages, GetSessionPath) — read-only
  access to conversation history from extensions
- Gap 2: Session Persistence (AppendEntry, GetEntries) — custom extension
  data survives across session restarts via new ExtensionDataEntry type
- Gap 10: SetEditorText — extensions can pre-fill the input editor
- Gap M3: Keyed Status Bar (SetStatus, RemoveStatus) — multiple extensions
  can place independent entries in the TUI status bar, ordered by priority
2026-03-02 01:33:56 +03:00
Ed Zynda dc59cfc81e feat: add --json output mode for --prompt and update subagent extensions
Add a --json flag that outputs structured JSON (response, model, usage,
messages with typed parts) when used with --prompt. Update kit-kit and
subagent-widget extensions to use --json for cleaner subprocess output
parsing instead of raw text heuristics.
2026-03-01 21:16:34 +03:00
Ed Zynda 8407d924b9 feat: add UIVisibility, GetContextStats APIs and compact tool renderers
- Add ctx.SetUIVisibility() to toggle built-in TUI chrome (startup
  message, status bar, separator, input hint) from extensions
- Add ctx.GetContextStats() returning accurate API-reported token counts
  instead of text-based heuristic; fix event ordering so extension
  handlers see up-to-date conversation state
- Add compact tool body renderers for compact mode: Read/Edit/Write/Ls
  show one-line summaries, Bash shows first 3 lines instead of full
  20-line syntax-highlighted output
- Add minimal.go example extension using UIVisibility + GetContextStats
2026-03-01 15:24:48 +03:00
Ed Zynda 91474af503 fix: remove line-number gutter from ls tool output
Ls output is a plain file list with no line numbers, so the empty
gutter column was wasted space. Give ls its own renderer that shows
a clean list with just the code background.
2026-03-01 13:41:35 +03:00
Ed Zynda e252791b3a ci: move discord notification after both goreleaser and npm publish 2026-03-01 02:15:37 +03:00
Ed Zynda 1880523422 feat: add subagent-widget extension example
Port of Pi's subagent-widget.ts — spawns background Kit subprocesses
with live widgets, conversation continuation, and LLM-accessible tools.
2026-02-28 19:30:25 +03:00
Ed Zynda eeecd5a843 feat: add kit-kit meta-agent extension example
Port of Pi Pi (meta-agent with parallel expert subprocesses) to Kit's
extension system. Includes expert grid widget, query_experts tool,
custom footer, tool renderer, and orchestrator system prompt injection.

Also updates AGENTS.md with Yaegi gotchas, BubbleTea patterns, testing
recipes, and extension architecture notes.

Fixes golangci-lint issues: modernize min/max in overlay.go, replace
deprecated GetExtRunner() with new GetExtensionContext() SDK method,
remove broken --model flag from expert subprocess.
2026-02-28 19:01:07 +03:00
Ed Zynda 7747fc2033 fix: keep vim interceptor active in both modes so Esc returns to normal
The interceptor now stays installed for the entire vim session, handling
both normal and insert modes. Esc switches from insert back to normal,
and /vim toggles the entire interceptor on/off.
2026-02-28 17:54:28 +03:00
Ed Zynda 864230bd0a feat: add editor interceptor system for extensions
Extensions can now intercept key events and wrap the editor's rendered
output via ctx.SetEditor/ctx.ResetEditor, enabling vim-like modal
editing, custom key bindings, and visual decorators.

Key fixes during development:
- Yaegi requires closure wrappers for struct function fields (bare
  function references return zero values across the interpreter boundary)
- SetEditor/ResetEditor use async NotifyWidgetUpdate to avoid deadlocking
  BubbleTea's event loop when called from HandleKey callbacks
- distributeHeight now uses renderInput() to account for interceptor
  Render wrapper in height calculations
2026-02-28 17:46:41 +03:00
Ed Zynda 0de0040e63 feat: add modal overlay dialog system for extensions
Add ctx.ShowOverlay() API that displays modal dialogs with optional
scrollable content, markdown rendering, action buttons, and configurable
positioning. Follows the same channel-based blocking pattern as prompts,
with full Yaegi compatibility via concrete structs.
2026-02-28 16:48:09 +03:00
Ed Zynda 98efaae960 feat: add custom tool rendering for extensions
Extensions can now override how tool calls are displayed in the TUI via
API.RegisterToolRenderer(). Supports custom display name, border color,
background color, header parameter formatting, body rendering, and
optional markdown processing of custom body output.
2026-02-28 16:23:45 +03:00
Ed Zynda 53ae47a1bd feat: add custom header/footer regions for extensions
Extensions can now place persistent header (above stream) and footer
(below status bar) regions via ctx.SetHeader/SetFooter. Single-instance
per slot, reuses WidgetContent/WidgetStyle types and WidgetUpdateEvent
for notifications. Includes thread-safe Runner storage, SDK methods,
UI rendering with height distribution, and example extension.
2026-02-28 14:11:52 +03:00
Ed Zynda 584b215803 feat: add interactive prompt system for extensions (select, confirm, input)
Extensions can now show modal prompts to the user via ctx.PromptSelect,
ctx.PromptConfirm, and ctx.PromptInput. Prompts render inline below the
separator (replacing the input area) and use channel-based sync so the
extension blocks until the user responds. Extension slash commands run in
dedicated goroutines to avoid stalling BubbleTea's Cmd scheduler.
2026-02-28 13:54:00 +03:00
Ed Zynda 3009b5530b feat: add extension widget slots for persistent TUI content
Add a declarative widget system that lets extensions place persistent
content above or below the input area. Widgets survive across agent
turns and are updated via ctx.SetWidget/ctx.RemoveWidget from any
event handler.

All types are concrete structs (Yaegi-safe, no interfaces cross the
interpreter boundary). Widget state lives on the Runner with mutex
protection, and WidgetUpdateEvent triggers BubbleTea re-renders.
2026-02-28 12:16:20 +03:00
Ed Zynda 1309c4bd12 fix: clear orphaned terminal rows after flushing stream content
Issue ClearScreen after tea.Println in flushStreamContent() to force a
full terminal redraw when the view height shrinks. Mirrors pi's
clearOnShrink mechanism — bubbletea's inline renderer doesn't clear
rows below the managed region when the frame gets shorter.
2026-02-28 02:06:39 +03:00
Ed Zynda 2a829fb98f cleanup 2026-02-28 01:01:19 +03:00
Ed Zynda ad07086900 cleanup 2026-02-28 01:01:12 +03:00
Ed Zynda 596eeede2f docs: fix Discord badge to use static label instead of placeholder server ID 2026-02-27 18:51:46 +03:00
Ed Zynda 879ec65609 docs: add CI, release, npm, Go, license, and Discord badges 2026-02-27 18:49:34 +03:00
Ed Zynda 2fce8731e1 docs: strip README to title and logo, mark body as TBD 2026-02-27 18:48:43 +03:00
Ed Zynda c925a69aec fix: remaining staticcheck QF1012 WriteString(Sprintf) violations 2026-02-27 18:42:45 +03:00
Ed Zynda 25a3523c56 fix: use fmt.Fprintf instead of WriteString(Sprintf) (staticcheck QF1012) 2026-02-27 18:40:11 +03:00
Ed Zynda 16ce5f3627 ci: bump golangci-lint to v2.10.1 for Go 1.26 support 2026-02-27 18:36:38 +03:00
Ed Zynda f6ddd0b785 ci: add lint job, npm publish pipeline, and update goreleaser config
- CI: add golangci-lint job, target master branch, use Go 1.26
- Release: add workflow_dispatch for npm-only releases, Discord
  notifications, and npm-publish job for @mark3labs/kit
- Goreleaser: upgrade to v2 schema, lowercase archive names,
  structured changelog groups, release header with install instructions
- npm: add package scaffolding with postinstall binary downloader
2026-02-27 18:27:06 +03:00
Ed Zynda 6d18ded8f9 fix: defer stream flush to prevent blank lines at bottom of TUI
On step completion, keep stream content visible in the view instead of
immediately flushing to scrollback and resetting. The flush+reset in the
same frame shrinks the view height, and bubbletea's inline renderer
leaves orphaned blank lines at the bottom for the rows it no longer
manages.

The deferred flush happens when the next step starts (SpinnerEvent),
right before new content begins streaming.
2026-02-27 18:22:05 +03:00
Ed Zynda d6f8020554 remove legacy hooks system, superseded by extensions 2026-02-27 18:17:32 +03:00
Ed Zynda bc57364017 tui 2026-02-27 18:11:52 +03:00
Ed Zynda 2cf7464e76 combine startup info into single system message block
Merge Context, Skills, and tool counts into one KIT System block
instead of separate styled sections. Add separate MCP and extension
tool counts to Agent, only displaying each when > 0.
2026-02-27 17:19:13 +03:00
Ed Zynda 215a3186ff add /compact command with Pi-style token-based compaction
Rework compaction to match Pi's method:
- Token-based cut point (KeepRecentTokens=20k) instead of fixed message count
- Auto-trigger: contextTokens > contextWindow - reserveTokens (16k default)
- Pi's structured summary prompt (Goal/Progress/Decisions/Next Steps format)
- /compact [instructions] supports custom focus text
- Force compaction on manual request (only gate: >= 2 messages)
- Summary displayed in styled block with sky/cyan border and token stats
- Spinner properly animated during compaction
2026-02-27 17:05:25 +03:00
Ed Zynda 3804daa6fa add Pi-style context loading, skills metadata, default system prompt, and /skill:name expansion
- Add default system prompt (Pi-style) listing core tools and guidelines
- Load AGENTS.md into system prompt as project context if present
- Discover skills from .agents/skills/ (standardized) and .kit/skills/
- Change FormatForPrompt to metadata-only XML (agent reads on demand)
- Compose system prompt: base + AGENTS.md + skills metadata + date/cwd
- Show [Context] and [Skills] sections in startup banner
- Add /skill:name command expansion in runTurn (re-reads file, wraps in <skill> block)
- Add /skill:name to /help with available skill names
2026-02-27 16:02:11 +03:00
Ed Zynda 078daaf8ce remove legacy app layer code: AgentRunner, executeStepLegacy, updateUsage
The app layer now exclusively uses the SDK path (kit.PromptResult).
- Remove AgentRunner interface and Agent field from Options
- Remove executeStepLegacy and updateUsage (SDK handles both)
- Remove Extensions field from Options (SDK owns extension lifecycle)
- Simplify StepCompleteEvent to carry ResponseText string only
- Add PromptFunc test hook to Options for stub-based testing
- Remove dead wrappers from cmd/setup.go (SetupAgent, BuildProviderConfig)
- Update all tests to use stubPrompt/PromptFunc instead of stubAgent
2026-02-27 14:39:48 +03:00
Ed Zynda 199297ce7e remove script command, hook examples, and related dead code
Delete the entire scripting feature (cmd/script.go, tests, examples/scripts/,
examples/hooks/) and clean up all supporting code: ArgsSubstituter, MergeConfigs,
Config.Prompt/NoExit fields, scriptMCPConfig, and HasScriptArgs. Env substitution
(EnvSubstituter, HasEnvVars) is retained as it's used by config loading and hooks.

-2171 lines across 21 files.
2026-02-27 14:11:27 +03:00
Ed Zynda 6aa54d0898 implement Plan 10: make CLI app a full SDK consumer
cmd/root.go now creates a *Kit via kit.New() instead of manually calling
SetupAgent/InitTreeSession. The app's executeStep() delegates to
kit.PromptResult() which handles session persistence, hooks, extension
events, auto-compaction, and the generation loop — eliminating 150+ lines
of duplicated orchestration.

Key changes:
- Add MCPConfig, ShowSpinner, SpinnerFunc, UseBufferedLogger, Debug to
  kit.Options so the CLI can pass through its flags
- Add TurnResult struct and PromptResult() method for rich return data
  (usage stats, conversation messages)
- Bridge extension observation events (AgentStart/End, MessageStart/
  Update/End) through SDK EventBus so extensions work without app-layer
  emission
- Add subscribeSDKEvents() in app layer to convert SDK events to tea.Msg
  for TUI rendering
- Kit.Close() emits SessionShutdown and closes tree session
- Legacy executeStepLegacy() retained for test stubs and script mode
2026-02-27 13:51:08 +03:00
Ed Zynda 35452cc21b remove dead code and add Plan 10 for full app-as-SDK-consumer integration
Delete legacy session files (manager.go, session.go) and unused
ParseModelName() — all orphaned after Plans 00-09. Add Plan 10 to
close all deferred items: app uses kit.New(), executeStep() delegates
to kit.PromptResult(), extension observation events route through SDK
EventBus.
2026-02-27 13:33:19 +03:00
Ed Zynda 470ec43636 add extension hook system with priority ordering and tool interception (Plan 09) 2026-02-27 13:03:26 +03:00
Ed Zynda 6c069907dd add skills & prompts system with auto-discovery and prompt composition (Plan 08) 2026-02-27 12:48:19 +03:00
Ed Zynda 744642f2ee add compaction APIs for context window management (Plan 07) 2026-02-27 12:38:07 +03:00
Ed Zynda 4ecdee7e25 expose auth and model management APIs in SDK, migrate CLI to consume them (Plan 06) 2026-02-27 12:25:33 +03:00
Ed Zynda 4f0cd2ca6e add Steer, FollowUp, PromptWithOptions prompt modes; extract shared generation helpers (Plan 05) 2026-02-27 12:18:30 +03:00
Ed Zynda aeedf00f0f expose tree session management in SDK, remove legacy JSON sessions (Plan 04) 2026-02-27 12:11:17 +03:00
Ed Zynda 8275e217a8 add event/subscriber system with thread-safe EventBus (Plan 03) 2026-02-27 11:56:24 +03:00
Ed Zynda cf1c367713 export 40+ SDK types from pkg/kit for shared type surface (Plan 02) 2026-02-27 11:45:48 +03:00
Ed Zynda d8f40039fe export tools and tool factories with WithWorkDir option (Plan 01)
- Add ToolOption/WithWorkDir functional options pattern to internal/core
- Update all 7 tool constructors to accept ...ToolOption and resolve
  paths relative to the configured working directory
- Create pkg/kit/tools.go with public exports: individual constructors,
  bundles (AllTools, CodingTools, ReadOnlyTools), and WithWorkDir
- Add CoreTools field to AgentConfig/AgentCreationOptions so callers
  can inject custom tool sets instead of hardcoding core.AllTools()
- Add Tools field to kit.Options and GetTools() to kit.Kit
- Fully backward compatible: no-arg calls use os.Getwd() as before
2026-02-27 11:37:46 +03:00
Ed Zynda 9e5cf9dd4c gitignore .task/ directory 2026-02-27 11:27:48 +03:00
Ed Zynda 144f0524d5 add output/ to gitignore 2026-02-27 10:47:56 +03:00
Ed Zynda 626f1105c9 move SDK to pkg/kit, extract shared logic from cmd, relocate main to cmd/kit
Restructure the codebase so the CLI app consumes the SDK rather than
the SDK wrapping CLI internals. This eliminates the circular dependency
(sdk -> cmd -> sdk) and establishes pkg/kit as the canonical API.

Key changes:
- Create pkg/kit/ with InitConfig, SetupAgent, BuildProviderConfig
  extracted from cmd/root.go and cmd/setup.go as parameterized functions
- Move sdk/kit.go -> pkg/kit/kit.go (remove cmd import, use local calls)
- Move sdk/types.go -> pkg/kit/types.go
- Move main.go -> cmd/kit/main.go (standard Go project layout)
- cmd/root.go and cmd/setup.go now delegate to pkg/kit, injecting
  CLI-specific state (quietFlag) via the Quiet field on AgentSetupOptions
- Add setSDKDefaults() for cobra-free SDK usage (viper defaults)
- Fix .gitignore: kit -> /kit (was blocking cmd/kit/ and pkg/kit/)
- Update .goreleaser.yaml, Taskfile.yml, AGENTS.md, contribute/build.sh,
  README.md for new cmd/kit entrypoint and pkg/kit import paths
- Add plans/ with 10 detailed SDK revamp plans and Taskfile.yml
- Delete sdk/ directory entirely
2026-02-27 10:42:27 +03:00
Ed Zynda fb3608326f add unrestricted stdlib symbols to Yaegi interpreter
os/exec lives in yaegi's stdlib/unrestricted package, not the default
stdlib.Symbols. Without it, extensions importing os/exec fail with
'unable to find source'. This is needed for the /run command example
and any extension that spawns subprocesses.
2026-02-27 00:43:31 +03:00
Ed Zynda 6ac8d3983a add SendMessage to extension Context and fix all golangci-lint issues
SendMessage lets extensions inject messages into the conversation and
trigger new agent turns, enabling async patterns like background
subagent execution. It delegates to app.Run() which handles queueing.

CommandDef.Execute now receives Context so commands can access
SendMessage, Print*, and session metadata. The UI layer wraps the
call via runner.GetContext() at the boundary.

Also fixes all 20+ golangci-lint issues across the codebase:
errcheck, modernize (min/max/slices.Contains/SplitSeq/range-over-int),
staticcheck (error string casing), and unused code removal.
2026-02-27 00:41:48 +03:00
Ed Zynda f12950c0b6 btca 2026-02-27 00:26:26 +03:00
Ed Zynda 7a6c0aba61 wire extension slash commands and tools into TUI dispatch pipeline
Extension-registered slash commands are now fully end-to-end:
- Commands appear in autocomplete popup (category: Extensions)
- Commands appear in /help under Extensions section
- Commands dispatch via handleExtensionCommand with argument support
- Command names normalized to /prefix at the cmd layer boundary

Extension-registered tools now show in /tools:
- Agent.GetTools() includes extraTools from extensions
- Previously only core + MCP tools were returned

Also adds RegisterTool and RegisterCommand examples to the
kit extensions init template, and adds .kit/ to .gitignore.
2026-02-27 00:26:06 +03:00
Ed Zynda f42d487214 add Yaegi-based in-process extension system with event handlers, tool/command registration, and styled output
Implement a Pi-style extension system where plain .go files are loaded at
runtime via Yaegi. Extensions register typed event handlers against 13
lifecycle events (tool_call, tool_result, input, before_agent_start, etc.)
using concrete-type-only API methods to avoid Yaegi interface panics.

Key capabilities:
- Tool interception: block calls, modify results (wrapper pattern)
- Input handling: transform or fully handle user input (skip agent)
- System prompt injection via BeforeAgentStartResult
- Custom tool and slash command registration
- Styled output: ctx.Print, PrintInfo, PrintError, PrintBlock
- Legacy hooks.yml compatibility via adapter
- Auto-discovery from ~/.config/kit/extensions/ and .kit/extensions/
- CLI: kit extensions list|validate|init, --no-extensions, -e flags
- 58 unit tests covering runner, loader (Yaegi), wrapper, events
2026-02-27 00:08:48 +03:00
Ed Zynda dd018b65ec add tree-structured session handling and /tree command
Implement pi-style JSONL append-only session management with tree branching:

- TreeManager with id/parent_id tree structure, leaf pointer, and context
  building that walks leaf-to-root for LLM messages
- Auto-discovery by cwd in ~/.kit/sessions/ with session listing
- /tree TUI overlay with ASCII art rendering, filter modes, and navigation
- /fork, /new, /name, /session slash commands for tree operations
- --continue, --resume, --no-session CLI flags
- Default auto-creates a tree session per working directory
2026-02-26 18:47:10 +03:00
Ed Zynda 268f3de34e add tool-specific body rendering with syntax highlighting and truncation
Implement iteratr-style rich tool blocks: side-by-side diffs for Edit
(via go-udiff), syntax-highlighted code blocks for Read (via chroma),
green-tinted write blocks with line numbers and footer, and background-
styled bash output with stderr detection.

All renderers enforce centralized max-line limits (Edit: 20, Read: 20,
Write: 10, Bash: 10) with muted truncation hints.
2026-02-26 18:23:50 +03:00
Ed Zynda 6fdc6f7e5e update KIT banner with KITT red gradient and simplify description 2026-02-26 18:09:26 +03:00
Ed Zynda a3bad94821 unify tool blocks into single header+result rendering
Core tools previously rendered two separate blocks: an 'Executing tool'
header block on ToolCallStartedEvent, then a separate result block on
ToolResultEvent. This merges them into a single unified block that
renders only when the result arrives.

The unified block shows a status icon (checkmark/cross), a friendly
tool display name, inline parameters, and the output body. Border
color indicates success (green) or error (red).
2026-02-26 17:59:45 +03:00
Ed Zynda 3f2a399e47 replace builtin MCP tools with native core tools and custom content blocks
Remove the entire internal/builtin package (bash, fetch, todo, http, fs
servers) and all inprocess/builtin transport support from config and
connection pool.

Add internal/core package with 7 direct fantasy.AgentTool implementations
matching pi's coding agent: bash, read, write, edit, grep, find, ls.
These execute in-process with zero MCP/JSON serialization overhead.

Add internal/message package with crush-inspired custom content blocks:
ContentPart interface with TextContent, ReasoningContent, ToolCall,
ToolResult, and Finish types. Messages carry heterogeneous Parts slices
with type-tagged JSON serialization for persistence and a ToFantasyMessages
bridge for LLM provider integration.

Core tools are always registered on the agent. External MCP servers remain
supported for additional tools, but MCP loading failures are now non-fatal
since core tools guarantee a working baseline.
2026-02-26 17:41:02 +03:00
Ed Zynda 30f368de24 add KITT red ASCII banner to CLI usage output 2026-02-26 17:15:55 +03:00
Ed Zynda 46e1a1d66f docs: add logo and update README tagline 2026-02-26 17:01:58 +03:00
Ed Zynda 7fc94018a9 rename: fork mcphost to kit (github.com/mark3labs/kit)
Rename the entire project from mcphost to kit, including:
- Go module path and all import paths
- SDK type MCPHost -> Kit, file renames mcphost.go -> kit.go
- CLI command name, usage strings, UI labels (KIT in literature)
- Config paths (.mcphost -> .kit), env prefix (MCPHOST_ -> KIT_)
- Data/credential/hooks directory paths
- Remove legacy .mcp config fallbacks
- Session metadata field (mcphost_version -> kit_version)
- MCP client identity name
- Build output, goreleaser binary name
- All documentation, examples, scripts, and test files
2026-02-26 16:59:59 +03:00
Ed Zynda 7483bef1fa refactor: deduplicate setup pipeline and unify non-interactive rendering
Extract shared functions into cmd/setup.go (BuildProviderConfig, SetupAgent,
BuildAppOptions, CollectAgentMetadata, DisplayDebugConfig, SetupCLIForNonInteractive)
eliminating triplicated config/agent/app assembly from root.go, script.go, and
the SDK.

Move the event handler from cmd/script.go into internal/ui/event_handler.go as
CLIEventHandler, shared by both script and --prompt modes. Fix streaming in
non-interactive modes: chunks are now printed to stdout immediately as they
arrive (fmt.Print) instead of being silently buffered until step completion.

The --prompt path switches from RunOnce (no intermediate display) to
RunOnceWithDisplay with CLIEventHandler, gaining streaming, tool call display,
and spinner support that was previously exclusive to script mode.
2026-02-26 16:40:46 +03:00
Ed Zynda c5b75674a3 fix: token usage tracking with fantasy and sticky display across all visual modes
- Fix context percentage: use FinalResponse.Usage (last API call) instead of
  TotalUsage (sum of all tool-calling steps) which overstated context fill level
- Fix token count: display current context window tokens, not cumulative session
  total, so the number and percentage tell a consistent story
- Fix script mode double-counting: app.updateUsage already updates the shared
  tracker before sending StepCompleteEvent, so remove redundant
  UpdateUsageFromResponse call
- Add sticky usage display in TUI: render in View() layout between stream and
  separator instead of tea.Println so it updates in place
- Add usage display for non-interactive --prompt mode (non-quiet)
- Add SetContextTokens to UsageUpdater interface for separating billing tokens
  (TotalUsage) from context utilization (FinalResponse.Usage)
2026-02-26 16:10:43 +03:00
Ed Zynda 41f1198cb6 fix: extract human-readable text from MCP tool results instead of showing raw JSON
Tool results were displayed as nested JSON (Fantasy wrapper + MCP content
structure). Now extractToolResultText unwraps both layers at the source,
so all UI paths receive clean text. Removes the redundant extraction in
script mode since the agent layer handles it.
2026-02-26 15:41:34 +03:00
Ed Zynda 7244485ce2 fix: script mode streaming display and example script formatting
- Accumulate stream chunks in a buffer and flush through
  DisplayAssistantMessageWithModel at boundaries (tool calls, step
  complete), mirroring the TUI's StreamComponent accumulate-and-flush
  strategy. Text accompanying tool calls now renders identically to
  solo assistant responses.

- Fix example-script.sh: add missing --- frontmatter delimiters and
  convert legacy command/args format to new type+command list format
  so Viper YAML parsing works correctly.

- Fix env-substitution-script.sh: add missing execute permission.
2026-02-26 15:30:39 +03:00
Ed Zynda 89b3adcc64 fix: unify script mode with app layer and fix broken display
Script mode had a duplicated agentic loop (runAgenticLoop/runAgenticStep)
that was copied from root.go during the Bubble Tea refactor but left with
broken streaming display and missing hooks integration. The streaming
callback silently accumulated chunks without rendering, and the final
response was skipped because it assumed streaming had already shown it.

- Refactor app.executeStep to accept a generic eventFn callback instead
  of a *tea.Program, decoupling the agent step from Bubble Tea
- Add app.RunOnceWithDisplay for non-TUI callers that need intermediate
  display events (spinner, tool calls, streaming chunks)
- Replace ~300 lines of duplicated code in script.go with a lightweight
  scriptEventHandler that routes app events to CLI display methods
- Fix agent.GenerateWithLoopAndStreaming to use the streaming path when
  any callbacks are provided (fantasy only exposes callbacks on Stream)
- Fix CLI displayContainer to match TUI output (remove extra padding)
- Remove premature usage display during CLI setup
2026-02-26 14:35:36 +03:00
Ed Zynda 9b430f3883 fix: ESC cancel now actually cancels agent response context
Streaming callbacks were not checking ctx.Err(), so the fantasy library
kept processing after the user cancelled. Additionally, context.Canceled
was surfaced as a StepErrorEvent, printing an error instead of silently
ending the turn.

Add ctx.Err() checks to all streaming callbacks so the fantasy stream
loop breaks immediately on cancel. Introduce StepCancelledEvent so the
TUI flushes partial content and returns to input without an error message.
2026-02-26 13:32:38 +03:00
Ed Zynda 8c140b89c2 fix(ui): uniform background fill on user messages with full-height border
Use three-phase rendering for blocks with background color:
1) content with bg + horizontal padding, 2) Place() for vertical
padding with WithWhitespaceStyle, 3) border applied last so it
extends the full block height. Pass background color through to
glamour markdown renderer so inline text inherits the highlight.
Switch Highlight to Catppuccin Mantle for a subtler recessed look.
2026-02-26 13:20:41 +03:00
Ed Zynda d818eddecb fix(ui): embed vertical padding as content to fix background banding
Lipgloss PaddingTop/PaddingBottom adds raw newlines that don't receive
background color styling. Prepend/append newlines to the content string
instead so they are part of the styled content and get the background.
2026-02-26 12:57:22 +03:00
Ed Zynda 711aefba5e fix(ui): remove opposite border to fix background banding on user messages 2026-02-26 12:54:56 +03:00
Ed Zynda a2c8201ec4 fix(ui): add subtle background fill to user messages
Use theme.Highlight (Catppuccin Surface 1) as a background color on
user message blocks for visual distinction from agent messages.
2026-02-26 12:53:47 +03:00
Ed Zynda ee5f41764b fix(ui): remove borders from agent messages, move user border to left with Primary color
Agent messages now render borderless. User messages use a left border
with theme.Primary (Mauve) instead of a right border with Secondary.
2026-02-26 12:51:44 +03:00
Ed Zynda faea00795e fix(ui): remove width-4 from stream component so agent messages span full width 2026-02-26 12:48:14 +03:00
Ed Zynda 948919d60b fix(ui): make all message blocks full width with no horizontal staggering
All content blocks now span the full container width by default.
Removed PlaceHorizontal floating so user and agent messages no longer
stagger left/right. The align option now only determines which border
gets the accent color (left for agent, right for user).
2026-02-26 12:44:00 +03:00
Ed Zynda 2f8be19c83 fix(ui): align left padding across messages and input components
Remove right padding from content blocks and align input components
with message content by matching the border(1) + paddingLeft(2)
pattern used by renderContentBlock.
2026-02-26 12:38:32 +03:00
Ed Zynda ac6d7f8f53 fix(ui): extend queued message blocks to full width 2026-02-26 12:33:47 +03:00
Ed Zynda d6b710542f fix(ui): left-align queued messages and preserve newlines 2026-02-26 12:32:39 +03:00
Ed Zynda f57aaf31d0 fix(ui): render queued messages as styled content blocks with QUEUED badge
Replace inline text+badge with proper bordered content blocks matching
the overall message styling. Each queued message is rendered with a
right-aligned block, muted border, and a QUEUED badge below the text.
2026-02-26 12:30:51 +03:00
Ed Zynda 38e4aa36e6 chore: fix golangci-lint issues
Modernize range-over-int loops, use tagged switch, replace min/max
if-blocks with builtins, remove unused funcs/fields, delete dead
streaming_display.go, and fix ineffectual assignment in tests.
2026-02-26 12:26:29 +03:00
Ed Zynda 69fba663ef fix(ui): use theme colors for spinner, remove text, persist during streaming
Spinner now uses theme.Primary/Muted/VeryMuted/MutedBorder instead of
hardcoded red. Removed 'Thinking...' label and message parameter from
NewSpinner/ShowSpinner/SpinnerFunc. Spinner keeps running alongside
streaming text and only hides on step complete via Reset().
2026-02-26 12:24:17 +03:00
Ed Zynda ee21408546 fix(ui): collapse multi-line queued messages to single line preview 2026-02-26 12:11:47 +03:00
Ed Zynda 4d9bf8a276 fix(ui): resolve queue deadlock and anchor queued messages to input
Fix a deadlock where submitting a message while the agent is streaming
locks the app. App.Run() and App.ClearQueue() were calling prog.Send()
synchronously from within Bubble Tea's Update() loop, blocking when the
internal event channel was full from streaming events.

Run() now returns the queue depth instead of sending events, and
ClearQueue() no longer sends events. The UI layer updates state directly.

Additionally, queued messages are now rendered with a "queued" badge
between the separator and input, anchored in the managed region until
the agent picks them up. Previously they were printed to scrollback
immediately and only a count badge was shown.
2026-02-26 12:05:29 +03:00
Ed Zynda acc8843ec0 chore: add Pi coding agent repo as btca research resource 2026-02-26 11:34:39 +03:00
Ed Zynda d405bc12d2 fix(ui): print startup messages before Bubble Tea takes over
tea.Println from Init() gets pushed above the fold because the View
fills the terminal immediately. Print startup info to stdout before
program.Run() so messages are visible when the TUI appears.
2026-02-26 10:14:32 +03:00
Ed Zynda cd4b5ab851 fix(ui): use tea.Sequence for startup messages to preserve ordering
tea.Batch runs commands concurrently, so multiple tea.Println calls
would either race (dropping messages) or render on one line. Use
tea.Sequence to emit each startup message one at a time.
2026-02-26 10:10:52 +03:00
Ed Zynda 18bedca479 fix(ui): combine startup messages into single tea.Println to prevent race
Multiple tea.Println commands in a tea.Batch can race, causing the
model-loaded message to be dropped. Combine all startup info into a
single system message so only one tea.Println is needed.
2026-02-26 10:08:16 +03:00
Ed Zynda 26b40aa613 fix(ui): restore startup info messages in interactive Bubble Tea TUI
The Bubble Tea refactor stopped calling SetupCLI for interactive mode,
which dropped the model-loaded, loading-message, and tool-count info
that used to appear on startup. Emit those messages from AppModel.Init()
via tea.Println to match the old behaviour.
2026-02-26 10:01:31 +03:00
Ed Zynda c1b3fa1dfc add .iteratr and .opencode tool configuration directories 2026-02-26 09:55:53 +03:00
Ed Zynda 4f96acc217 fix(ui): execute slash commands in TUI instead of sending them to the LLM
The Bubble Tea refactor only wired /quit, /clear, and /clear-queue in
InputComponent; the remaining commands (/help, /tools, /servers, /usage,
/reset-usage) fell through as submitMsg and were forwarded to app.Run()
as regular prompts.

Intercept all recognized slash commands in AppModel.Update before they
reach the app layer, and add print helpers that emit formatted output
via tea.Println. Also create a UsageTracker for interactive mode so
/usage and /reset-usage work correctly.
2026-02-26 09:55:20 +03:00
Ed Zynda 80d5300feb remove all tool calling permission checks and approval infrastructure
Strip the three-layer permission system (hook-based blocking, user approval
dialogs, PostToolUse output suppression) so every tool call executes
unconditionally. This removes ~1100 lines across 14 files including the
--approve-tool-run and --no-hooks CLI flags, ToolApprovalHandler/Func types,
PreToolUse/UserPromptSubmit/PostToolUse/Stop hook firing, HookBlockedEvent,
ToolApprovalNeededEvent, the ApprovalComponent UI, and all associated tests.
2026-02-26 09:47:10 +03:00
Ed Zynda 0c3d240519 fix(ui): print user messages, tool calls, and tool results immediately via tea.Println
The bubbletea refactor was accumulating all messages in StreamComponent
and only printing Response.Content.Text() on step completion, causing
user messages, tool calls, and tool results to be missing from output.

Now only agent streaming text stays live in StreamComponent. Everything
else is printed immediately to scrollback:
- submitMsg: prints rendered user message
- ToolCallStartedEvent: flushes stream text, prints tool call
- ToolResultEvent: prints tool result, restarts spinner
- HookBlockedEvent: prints blocked notice
- ResponseCompleteEvent: prints assistant text (non-streaming mode)
- StepCompleteEvent: flushes remaining stream text
2026-02-26 09:29:19 +03:00
Ed Zynda cf2c026182 refactor(ui): trim CLI to non-TUI helpers only
Remove methods that belonged to the old interactive loop and have no
external callers: HandleSlashCommand, SlashCommandResult, IsSlashCommand,
DisplayHelp, DisplayTools, DisplayServers, ClearMessages, UpdateUsage,
DisplayUsageStats, ResetUsageStats. Slash command dispatch is now owned
by InputComponent in the Bubble Tea TUI; usage tracking is handled
directly by the app layer.

Retain all display methods still needed by non-interactive paths
(cmd/root.go session replay, cmd/script.go, factory.go). Also drop the
now-unused "strings" import. cli.go shrinks from 509 to 329 lines.
2026-02-26 02:32:12 +03:00
Ed Zynda 4450945b4f test(ui): add 48 unit tests for InputComponent, StreamComponent, and ApprovalComponent (TAS-34) 2026-02-26 02:25:42 +03:00
Ed Zynda 977f6c75bd test(ui): add 23 unit tests for AppModel state machine (TAS-33)
Cover state transitions (input->working->approval->input), StepError->input,
ESC cancel flow (single tap sets canceling, double tap calls CancelCurrentStep,
timer expiry resets), queue badge updates, window resize/distributeHeight, and
tea.Println cmd emission on StepCompleteEvent/StepErrorEvent.
2026-02-26 02:22:13 +03:00
Ed Zynda 00771cf291 test(app): add AgentRunner interface and 15 unit tests for App
Introduce AgentRunner interface in options.go so tests can supply stub
agents without a real LLM (backward-compatible; *agent.Agent satisfies it).

Fix three pre-existing deadlock bugs in app.go where sendEvent() was called
while a.mu was held (Run, ClearQueue, drainQueue); the sendEvent comment
explicitly forbids this.

Add app_test.go with 15 tests covering: single/queued Run, FIFO drain
ordering, CancelCurrentStep, cancel-during-approval (ctx unblocks
ToolApprovalFunc), ClearQueue, Close (WaitGroup, idempotent, root-ctx
cancel), step error recovery, ClearMessages, and QueueLength.
2026-02-26 02:17:29 +03:00
Ed Zynda 04a0689775 test(app): add MessageStore unit tests (TAS-31)
17 tests covering Add, Replace, GetAll, Clear, copy isolation,
session.Manager bridge (in-memory, no disk I/O), and race-detector
concurrency smoke test.
2026-02-26 02:09:41 +03:00
Ed Zynda c15ff612b8 refactor(ui): move knightRiderFrames to stream.go, remove unused Spinner variants
- Move knightRiderFrames() from spinner.go to stream.go (its natural owner
  as StreamComponent is now the primary TUI inline spinner)
- Remove unused dotFrames, dotFPS vars and NewThemedSpinner() constructor
- Remove color field from Spinner struct and simplify run() accordingly
- spinner.go shrinks from 150 to 76 lines; Spinner struct retained for
  script.go and cli.go non-TUI paths
2026-02-26 02:07:48 +03:00
Ed Zynda ee66477498 refactor(cmd): delete legacy agentic loop dead code from root.go (TAS-28)
Remove runAgenticStep, runAgenticLoop, runInteractiveLoop, addMessagesToHistory,
replaceMessagesHistory, AgenticLoopConfig, executeStopHook, runNonInteractiveMode,
and runInteractiveMode from root.go. Functions still needed by the script command
are moved to script.go.
2026-02-26 02:04:57 +03:00
Ed Zynda 154d693a8e refactor(ui): remove standalone tea.Program calls from CLI (TAS-27)
Delete GetPrompt, StartStreamingMessage, UpdateStreamingMessage,
GetToolApproval, and finishStreaming from CLI — these each spun up
their own tea.NewProgram and are superseded by the unified AppModel
TUI. Also remove the streamProgram/streamDone fields they relied on.

Update dead-code stubs in cmd/root.go to keep the build clean until
the legacy agentic-loop functions are deleted in TAS-28.
2026-02-26 01:58:25 +03:00
Ed Zynda 371fc04477 feat(ui): propagate WindowSizeMsg dimensions to stream child component
Add SetHeight() to StreamComponent and wire distributeHeight() to call
it so the stream region is clamped to the available terminal rows after
subtracting the fixed separator (1) and input (5) lines. Also propagate
initial dimensions at NewAppModel construction time.
2026-02-26 01:53:59 +03:00
Ed Zynda 6f518b74d6 fix(ui): transition to stateWorking on SpinnerEvent{Show: true} for queued steps
After StepCompleteEvent resets state to stateInput, the next queued step fires
SpinnerEvent{Show: true} before the first output arrives. Without this guard the
parent stayed in stateInput while the agent was running. Now any SpinnerEvent
with Show=true drives the state machine back to stateWorking, ensuring the TUI
correctly reflects the active state for both fresh and queued steps.
2026-02-26 01:50:46 +03:00
Ed Zynda 355198cd3a fix(app): stop queue drain when app is closing
Add a shutdown guard in drainQueue() that checks a.closed and
rootCtx.Err() before dequeuing the next prompt. Previously, calling
Close() mid-drain would cancel the root context but the loop would
still attempt to run subsequent queued prompts against that cancelled
context. Now the loop clears the queue and exits cleanly on shutdown.
2026-02-26 01:46:36 +03:00
Ed Zynda 12bed9b515 feat(app): move usage tracking into app layer (TAS-8)
Add UsageUpdater interface to app/options.go to avoid circular imports,
wire *ui.UsageTracker via Options.UsageTracker in cmd/root.go, and call
updateUsage() after each successful step in both runPrompt() and RunOnce().
2026-02-26 01:45:24 +03:00
Ed Zynda d813c2c3d4 refactor(app): move conversation history management into MessageStore via executeStep
executeStep() now accepts a prompt string instead of a pre-built message slice.
It calls store.Add() for the user message before the agent runs (persisting it
immediately, even on cancellation), builds the full msgs slice via store.GetAll(),
and calls store.Replace() on success with the full updated conversation.

runPrompt() and RunOnce() are simplified accordingly — all history mutations
are now owned by executeStep(), the single code path for both interactive and
non-interactive execution.
2026-02-26 01:39:05 +03:00
Ed Zynda 512aa735bf feat(app): move hook execution into App layer
Fire UserPromptSubmit in Run() before queuing, PreToolUse/PostToolUse in
executeStep() callbacks, and Stop hook in runPrompt()/RunOnce() after each
step. Emit HookBlockedEvent when a hook blocks a prompt or tool call.
2026-02-26 01:36:51 +03:00
Ed Zynda 591d7905d2 refactor(cmd): scope SetupCLI to non-interactive path only
SetupCLI() and its associated debug/CLI display logic now only run when
--prompt is set (non-interactive mode). Interactive mode uses the Bubble
Tea AppModel exclusively and never allocates a legacy CLI.

- Move SetupCLI call inside promptFlag != "" guard
- Remove cli parameter from runInteractiveModeBubbleTea (was unused _)
- Remove cli parameter from runNonInteractiveModeApp (no longer needed)
- Session history display remains guarded by cli != nil, so it correctly
  skips in interactive mode
2026-02-26 01:31:57 +03:00
Ed Zynda 3f2a0aaa92 refactor(cmd): route non-interactive mode through app.RunOnce
Replace the legacy runNonInteractiveMode path (which used runAgenticLoop
and the old CLI spinner/streaming display) with runNonInteractiveModeApp,
which delegates directly to appInstance.RunOnce(ctx, prompt, os.Stdout).

RunOnce never creates a tea.Program, so no intermediate spinner or
tool-call output is produced — satisfying both the normal and --quiet
non-interactive cases with the same codepath.

When --no-exit is set, runNonInteractiveModeApp hands off to the
interactive BubbleTea TUI via runInteractiveModeBubbleTea after the
single step completes.
2026-02-26 01:26:47 +03:00
Ed Zynda c7585b17b8 feat(ui): wire interactive path to unified Bubble Tea TUI
Replace the old SetupCLI/runInteractiveLoop flow with a single
tea.NewProgram(AppModel) + appInstance.SetProgram() call. NewAppModel
now constructs InputComponent and StreamComponent inline so the parent
model is fully wired on construction.
2026-02-26 01:24:24 +03:00
Ed Zynda 302b85ab31 feat(app): create App struct with queue, cancellation, and graceful shutdown
TAS-18: Implements internal/app/app.go with the App orchestrator that satisfies
the ui.AppController interface. Wires all 7 agent callbacks to program.Send()
events, handles interactive approval via ToolApprovalNeededEvent channel, and
drains the prompt queue sequentially in a single background goroutine.

Refactors runNormalMode() in cmd/root.go to construct app.New() after session
messages are loaded, defer appInstance.Close(), and wire ToolApprovalFunc per
mode (AutoApproveFunc for non-interactive, nil for interactive TUI path).
2026-02-26 01:19:20 +03:00
Ed Zynda 7216c86db2 feat(app): add MessageStore for thread-safe conversation history management
Wraps []fantasy.Message with Add, Replace, GetAll, Clear, and Len methods.
Bridges to session.Manager for best-effort on-disk persistence on every mutation.
2026-02-26 01:14:49 +03:00
Ed Zynda 8303ee1dc5 feat(ui): add ApprovalComponent as parent-managed Bubble Tea child model
Refactors tool approval from the standalone ToolApprovalInput (which called
tea.Quit) into ApprovalComponent that returns approvalResultMsg{Approved: bool}
via a tea.Cmd, letting AppModel own the program lifecycle. Wires the
ToolApprovalNeededEvent handler in model.go to construct and display the
component.
2026-02-26 01:12:44 +03:00
Ed Zynda 0cf4e60a78 feat(ui): add StreamComponent as Bubble Tea child model for stream region
Implements TAS-16: refactors the stream display into a self-contained
StreamComponent with a phase state machine (idle/spinner/streaming).
Reuses knightRiderFrames() for the KITT-style spinner, accumulates
streaming chunks, and renders tool call/result events inline.
Reset() clears all state between agent steps.
2026-02-26 01:10:48 +03:00
Ed Zynda a99f11a2f7 feat(ui): add InputComponent with parent-managed lifecycle and slash command routing
Replaces the standalone SlashCommandInput quit-on-submit pattern with a
proper child component (InputComponent) that returns submitMsg as a tea.Cmd.
Slash commands are executed against AppController (/clear, /clear-queue) or
return tea.Quit (/quit) — no os.Exit(). Also registers /clear-queue in the
slash command registry.
2026-02-26 01:08:17 +03:00
Ed Zynda dbf1dd52e1 feat(ui): implement graceful quit via /quit command and ctrl+c in AppModel
Handle /quit slash command (and aliases /q, /exit) in submitMsg handler by
returning tea.Quit before forwarding to the app layer. Add TODO comment in
cmd/root.go marking where defer appInstance.Close() belongs once app.App
exists (TAS-18).
2026-02-26 01:05:27 +03:00
Ed Zynda 6398b3e637 feat(ui): implement StepErrorEvent handling in AppModel
Render step errors above the BT region via tea.Println() using the
existing RenderErrorMessage() renderers, mirroring the StepCompleteEvent
pattern. Reset stream state and transition back to stateInput on error.
2026-02-26 01:02:34 +03:00
Ed Zynda be77e716cb feat(ui): add parent AppModel and TUI-internal event types
Introduce the root Bubble Tea model (AppModel) and the three TUI-internal
message types needed by the unified architecture spec (TAS-10).

- internal/ui/events.go: submitMsg, approvalResultMsg, cancelTimerExpiredMsg
- internal/ui/model.go: AppModel with full stateInput/stateWorking/stateApproval
  state machine, AppController interface for dependency inversion, child
  component interfaces as stubs (TAS-15/16/17), double-tap ESC cancel with 2s
  timer, routing of all 13 app-layer events, tea.Println on StepCompleteEvent,
  stacked View() with separator and queue badge, WindowSizeMsg propagation.
2026-02-26 00:57:59 +03:00
Ed Zynda b23b8db3d6 feat(app): add event types for app-to-TUI communication
Define all 13 event structs in internal/app/events.go that the app layer
sends to the TUI via program.Send(), including streaming, tool call, approval,
spinner, queue, and error events.
2026-02-26 00:50:30 +03:00
Ed Zynda d4b393c95d spec: unified Bubble Tea architecture with single-program child model pattern
Replace micro-program pattern (3 separate tea.NewProgram calls) with a
single persistent program using child model composition. Introduces thick
app layer for agent orchestration, message queueing, and double-tap ESC
cancellation.
2026-02-26 00:46:46 +03:00
Ed Zynda b96fe071ba feat: replace spinner with KITT-style red scanning animation
Drop the points spinner and message label in favor of a Knight Rider
scanner that bounces a red glow across 8 small squares. Frames are
pre-rendered with a 3-level intensity trail (#FF0000, #990000, #440000)
at 14 FPS.
2026-02-26 00:12:25 +03:00
Ed Zynda 676a6b0e63 fix: replace manual ANSI escape sequences with Bubble Tea program for streaming display
Streaming text was flickering because displayContainer() used raw escape
sequences (\033[%dF) to move the cursor and fmt.Println to repaint on
every token. This bypassed Bubble Tea's synchronized output, cursor
hiding, and atomic flush.

Replace the manual approach with a dedicated tea.Program that runs for
the lifetime of each streaming response. BT's renderer now handles all
in-place updates flicker-free. One-shot messages (user, tool, system,
error) continue to use simple fmt.Println since they are never redrawn.
2026-02-26 00:07:27 +03:00
Ed Zynda 443410018c fix: prevent null 'required' in MCP tool schemas breaking OpenAI API
OpenAI strictly validates JSON Schema and rejects 'required': null (expects
an array). When MCP tools had no required fields, the nil []string serialized
as null. Initialize required as []string{} and sanitize nested schemas
recursively to remove null/invalid required fields from MCP server responses.
2026-02-25 22:51:56 +03:00
Ed Zynda 71bdc768be feat: replace catwalk with models.dev, auto-route openai-compatible providers, fix all lint issues
Replace catwalk dependency with direct models.dev integration (97 providers,
3039 models vs catwalk's 22/679). Auto-route @ai-sdk/openai-compatible
providers through fantasy's openaicompat using the api URL from models.dev,
eliminating the need for --provider-url. Add --all flag to 'mcphost models'
to show all providers vs just fantasy-compatible ones.

Fix all 74 golangci-lint issues: errcheck (53), staticcheck SA4006 (24),
SA9003 (2), ST1005 (5), ineffassign (3). Restructure styles.go color
handling into a colorScheme struct to eliminate SA4006 false positives
from new(x) syntax.
2026-02-25 22:51:45 +03:00
Ed Zynda ccef91e69c feat: add 'mcphost models' command to list available models per provider 2026-02-25 22:13:02 +03:00
Ed Zynda 29a46f6f98 feat: advisory model validation, update-models command, and vercel provider
- Make model validation advisory: unknown models pass through to the
  provider API with a stderr warning instead of blocking. Catwalk
  metadata is used for cost tracking and suggestions when available.
- Add LookupModel() as the primary registry API (returns nil for
  unknown models, no error).
- Add 'mcphost update-models' subcommand to refresh the model database
  from a catwalk server (defaults to https://catwalk.charm.sh), a local
  file, or reset to the embedded version. Supports ETag caching.
- Add disk cache layer at ~/.local/share/mcphost/providers.json;
  registry loads cached data first, falls back to embedded.
- Add vercel provider support via fantasy.
- Add io.Closer plumbing to ProviderResult and Agent.Close() for
  providers that hold resources.
2026-02-25 22:09:24 +03:00
Ed Zynda 72fa1ff029 chore: remove dead code and unused token counter package
- Delete dead ESC listener code and bubbletea/time imports from agent
- Remove internal/tokens/ package (empty stubs and trivial estimator)
- Inline token estimation into usage_tracker as unexported helper
- Remove unused EstimateAndUpdateUsageFromText dead method
- Remove 9 unsupported provider env var entries from registry
2026-02-25 20:35:19 +03:00
Ed Zynda e62ce679fe feat: change model notation from provider:model to provider/model
Switch the --model / -m flag format from colon-separated (provider:model)
to slash-separated (provider/model), e.g. anthropic/claude-sonnet-4-5-20250929
or ollama/qwen3:8b. The slash separator is cleaner since model names can
contain colons (ollama tags, bedrock ARNs).

Add centralized ParseModelString() in internal/models/providers.go that all
callers now use. The old colon format is still accepted with a deprecation
warning to stderr for backward compatibility.

Update default model to claude-sonnet-4-5-20250929.
2026-02-25 18:41:49 +03:00
Ed Zynda be9162637b chore: upgrade all dependencies to latest versions 2026-02-25 18:20:08 +03:00
Ed Zynda 0703dd1602 fix: eliminate escape sequence leak from spinner tea.Program instances
Each spinner created a new tea.NewProgram which sent DECRQM queries for
synchronized output mode 2026. When the program exited and restored
cooked terminal mode, the terminal's DECRPM response leaked as visible
^[[?2026;2$y characters. Replace Bubble Tea spinner with a simple
goroutine animation loop writing directly to stderr via lipgloss.
2026-02-25 18:17:25 +03:00
Ed Zynda ce32cea7ee feat: upgrade charmbracelet libs to v2 (bubbletea, lipgloss, bubbles)
Migrate from github.com/charmbracelet/* v1 to charm.land/* v2 vanity imports.

Key changes:
- bubbletea: View() returns tea.View, KeyMsg -> KeyPressMsg, msg.String() matching
- lipgloss: AdaptiveColor replaced with cached dark-bg detection helper
- bubbles/textarea: Styles()/SetStyles() pattern, KeyMap.InsertNewline override
- bubbles/progress: SetWidth(), WithDefaultBlend(), typed Update return
- Input: enter always submits, ctrl+j/alt+enter insert newlines
- User message newlines preserved through glamour via \n -> \n\n conversion
- glamour stays at v1 (no v2 exists)
2026-02-25 17:07:09 +03:00
Ed Zynda d24d49854f Merge branch 'main' of github.com:mark3labs/mcphost 2026-02-25 16:24:33 +03:00
Ed Zynda bf2f44190d btca 2026-02-25 16:24:28 +03:00
Ramiz Polic 191dcea159 chore: update packages to resolve security issues (#154)
Signed-off-by: Ramiz Polic <rpolic@cisco.com>
2026-02-15 19:56:44 +03:00
Ed Zynda a77aab3d90 fix: upgrade sonic to v1.15.0 for Go 1.26 compatibility
Fixes build error with Go 1.26rc1/rc2 caused by undefined GoMapIterator
in sonic v1.14.1.

Closes #152
2026-02-02 11:09:51 +03:00
Shane McDonald c4aa911e3a feat: add Google Vertex AI support for Claude models (#146)
Add support for using Claude models via Google Cloud Vertex AI through
the `google-vertex-anthropic` provider. This enables users who have
Claude access through their Google Cloud account to use mcphost with
Vertex AI authentication.

Changes:
- Add `google-vertex-anthropic` provider case and createVertexAnthropicProvider()
- Support multiple env var names for project/region to match eino-claude:
  - Project: ANTHROPIC_VERTEX_PROJECT_ID, GOOGLE_CLOUD_PROJECT, GCLOUD_PROJECT
  - Region: CLOUD_ML_REGION (defaults to "global" if not set)
- Upgrade eino from v0.5.11 to v0.7.11 (required by eino-claude v0.1.12)
- Migrate schema API from OpenAPI v3 to JSON Schema (eino v0.7.11 change)

Usage:
  # Authenticate with Google Cloud
  gcloud auth application-default login

  # Set required environment variables
  export ANTHROPIC_VERTEX_PROJECT_ID="your-project-id"
  export CLOUD_ML_REGION="us-east5"  # or use default "global"

  # Run mcphost
  mcphost --model google-vertex-anthropic:claude-sonnet-4@20250514

Reference: https://docs.anthropic.com/en/docs/claude-code/google-vertex-ai

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 13:01:18 +03:00
Ed Zynda 7ece291d9a fix anthropic api issue 2026-01-09 17:39:30 +03:00
Ed Zynda f9485cbe59 new models 2026-01-09 17:30:59 +03:00
Ed Zynda c284a20dab upgrade deps 2026-01-09 17:25:53 +03:00
Cory LaNou 1b3a8c171a fix: convert JSON Schema draft-07 exclusive bounds to draft-04 format (#147)
* fix: convert JSON Schema draft-07 exclusive bounds to draft-04 format

Chrome DevTools MCP and other MCP servers use JSON Schema draft-07 where
exclusiveMinimum/exclusiveMaximum are numeric values representing the
actual bounds. However, kin-openapi (OpenAPI 3.0) expects these fields
as booleans that modify the minimum/maximum values (draft-04 format).

This fix recursively processes input schemas to convert:
- exclusiveMinimum: N → minimum: N, exclusiveMinimum: true
- exclusiveMaximum: N → maximum: N, exclusiveMaximum: true

Handles nested schemas in properties, items, additionalProperties,
and schema composition keywords (allOf, anyOf, oneOf, not).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add table-driven tests for JSON Schema draft conversion

Adds comprehensive tests for convertExclusiveBoundsToBoolean():
- Simple exclusiveMinimum/exclusiveMaximum conversion
- Both bounds together
- Already boolean values (draft-04 style, unchanged)
- No exclusive bounds (unchanged)
- Nested properties
- Array items
- allOf composition
- additionalProperties
- Real-world Chrome DevTools MCP schema example
- Invalid JSON handling (returns unchanged)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 16:03:16 +03:00
Ed Zynda 4891f87038 skip unless running locally 2025-12-09 21:27:48 +03:00
Darragh O'Reilly f240b9a8c1 Bump github.com/cloudwego/eino to fix panic (#142)
Fixes panic when using some MCP servers that were auto-generated
from OpenAPI specs. Fixes #141
2025-12-09 18:28:52 +03:00
Darragh O'Reilly cab31935f1 Feat: Add option to require approval before tool execution (#140)
Adds a new CLI option, `--approve-tool-run` (or via config setting),
that when enabled, prompts the user to approve a tool's execution
before it runs.

This option is disabled by default to maintain existing behavior.
2025-12-09 18:28:41 +03:00
Ed Zynda d3cae7c016 remove stale key 2025-12-05 10:25:49 +03:00
Ed Zynda 8c1f548eac update deps 2025-11-14 17:06:07 +03:00
Ed Zynda 63704f55b5 godoc 2025-11-12 16:48:46 +03:00
Ed Zynda d3281a2f01 format 2025-10-16 16:36:12 +03:00
Ed Zynda 1b7cc2ef6e update models 2025-10-16 16:35:52 +03:00
Ed Zynda d56335e807 fix: suppress health check logging to debug output only (#135)
Replace fmt.Printf calls in connection pool health check routines with debug logger calls. Health check messages are now only displayed when the --debug flag is enabled, providing a cleaner terminal output during normal operation while maintaining diagnostic information for troubleshooting.
2025-10-16 16:33:41 +03:00
Ed Zynda 13acf44131 add AGENTS.md 2025-10-16 16:26:19 +03:00
Ed Zynda dec5f08f80 update mcp-go 2025-10-07 10:35:31 +03:00
Ed Zynda 81ec644cb9 Update deps 2025-09-03 15:45:02 +03:00
Ed Zynda f778379dea feat: Add SDK package for programmatic MCPHost usage (#129)
* feat: add SDK package for programmatic MCPHost usage

- Export InitConfig and LoadConfigWithEnvSubstitution from cmd package
- Create sdk package with MCPHost type for programmatic access
- Add Options struct for configuration overrides
- Implement Prompt and PromptWithCallbacks methods
- Add session management (load, save, clear)
- Create type helpers for Message and ToolCall
- Add comprehensive SDK documentation in README
- Include basic and scripting examples
- Add unit tests for SDK functionality

The SDK reuses all existing internal packages and maintains identical
behavior to the CLI, including config loading, environment variables,
and defaults.

* docs: add SDK section to main README with link to detailed documentation

* fix tests

* update CI
2025-09-02 15:42:37 +03:00
Ed Zynda 4709716f5e Let viper handle precedence 2025-09-02 15:10:03 +03:00
Rui Chen 433bdece70 fix --version output (#128)
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-09-02 13:53:37 +03:00
Ed Zynda cc1ab91e64 Update mcp-go 2025-09-02 13:46:31 +03:00
matscalia 4efc2e271c Theme config (#127)
* add theme configuration

* add path for themes (broken)

* md theme relative path not working

* cleanup malpractice

* mid commit

* fix logical error
2025-09-02 10:30:20 +03:00
Ed Zynda 4f2f61c673 Update mcp-go 2025-08-11 20:21:05 +03:00
matscalia bce9221916 Session flag (#123)
* add session flag for convenience

* add short version

* fix logical error

* create session file if not present
2025-08-11 20:13:06 +03:00
matscalia d3c04b12bc add ollama api key (#124) 2025-08-11 20:10:32 +03:00
Ed Zynda 2f428997b3 Use fang 2025-08-11 20:10:20 +03:00
Štefan Baebler 4808ed2138 Upgrade github.com/bytedance/sonic to v1.14.0 for Go 1.25 support (#122)
Fixes #121

$ go get github.com/bytedance/sonic@latest && go mod tidy
go: upgraded github.com/bytedance/sonic v1.13.3 => v1.14.0
go: upgraded github.com/bytedance/sonic/loader v0.2.4 => v0.3.0
2025-08-10 13:49:03 +07:00
Ed Zynda d3f9759eb4 refactor debug messages 2025-08-08 09:32:04 +03:00
Ed Zynda 5d83df457b update models 2025-08-08 08:02:21 +03:00
Shancang 76741ab037 Feature/add connection pool (#120)
* feat: add MCP connection pool with health check and retry mechanism

* feat: add proactive connection health check before tool calls

- Add GetConnectionWithHealthCheck method to perform health check before reusing connections
- Add performHealthCheck method to test connection health with ListTools call
- Modify InvokableRun to use health-checked connections
- This prevents first tool call failure when connection is broken
- Improves user experience by ensuring connections are healthy before tool execution

---------

Co-authored-by: 茂勋 <shancangchen.csc@alibaba-inc.com>
2025-08-05 17:01:12 +03:00
Ed Zynda fe4db1998d feat: add --tls-skip-verify flag for self-signed certificates (#115)
* feat: add --tls-skip-verify flag for self-signed certificates

Adds support for skipping TLS certificate verification when connecting to
providers with self-signed certificates. This is particularly useful for
local Ollama instances secured with HTTPS.

- Add --tls-skip-verify command-line flag with security warnings
- Update ProviderConfig to include TLSSkipVerify field
- Modify HTTP client creation for all providers (Ollama, OpenAI, Anthropic, Google, Azure)
- Create helper functions for TLS-aware HTTP client creation
- Add comprehensive unit tests for TLS skip verify functionality
- Update documentation with usage examples and security warnings

Fixes #113

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

* feat: add TLS skip verify support to script mode

- Add TLSSkipVerify field to Config struct for script frontmatter
- Update script parsing to handle tls-skip-verify in YAML frontmatter
- Pass TLS configuration to model creation in script mode
- Add example script demonstrating TLS skip verify usage
- Update script examples documentation

This allows scripts to specify tls-skip-verify: true in their frontmatter
to connect to providers with self-signed certificates.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

---------

Co-authored-by: opencode <noreply@opencode.ai>
2025-08-05 17:00:58 +03:00
Ed Zynda d30f15269e feat: enhance hooks with LLM feedback capabilities (#112)
* feat: enhance hooks with LLM feedback capabilities

- Add new HookOutput fields for LLM interaction (feedback, context, systemPrompt, modifyInput/Output)
- Implement Continue functionality to gracefully stop sessions from hooks
- Implement SuppressOutput to hide tool results from user display
- Add UserPromptSubmit context injection to provide additional context to LLM
- Update mergeHookOutputs to handle new fields
- Add comprehensive unit tests for new hook output processing
- Create example Python hook demonstrating LLM feedback features

This enhancement allows hooks to:
- Provide feedback and context that reaches the LLM
- Modify tool inputs/outputs before processing
- Control session flow with Continue field
- Suppress output display while still sending to LLM
- Inject system prompts and context for better LLM responses

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

* fix: make tool blocking visible to LLM

When a PreToolUse hook blocks a tool execution, the LLM now receives an error message
indicating the tool was blocked, allowing it to adapt its approach.

Changes:
- Track when tools are blocked by PreToolUse hooks
- Replace tool execution results with error messages when blocked
- Add test to verify blocking functionality
- Add ToolBlockChecker type for future enhancements

This ensures the LLM is aware when its tool calls are blocked by security policies
and can respond appropriately rather than being unaware of the block.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

* refactor: remove unimplemented LLM feedback fields

Removed the following unimplemented fields from HookOutput:
- Feedback
- Context
- SystemPrompt
- ModifyInput
- ModifyOutput

These fields were added speculatively but not fully implemented.
Keeping only the working functionality:
- Continue/StopReason for session control
- SuppressOutput for hiding tool results
- Decision/Reason for blocking tools

The critical tool blocking visibility feature remains intact.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

---------

Co-authored-by: opencode <noreply@opencode.ai>
2025-07-24 15:54:29 +03:00
Ed Zynda 0211e9ff7e update README 2025-07-24 14:03:15 +03:00
Ed Zynda cdc4abfb36 Add comprehensive hooks system for MCPHost lifecycle events (#111)
* Add comprehensive hooks system for MCPHost lifecycle events

Implements a flexible hooks system based on Anthropic Claude Code specification:

- **Hook Events**: PreToolUse, PostToolUse, UserPromptSubmit, Stop
- **Hook Types**: Command execution with JSON input/output
- **Configuration**: XDG-compliant with layered config support
- **Security**: Command validation, timeout controls, safe execution
- **Common Fields**: Consistent session ID, timestamps, model info across all hooks

Key features:
- Hooks receive JSON via stdin and can control flow via stdout
- Pattern matching for tool-specific hooks (regex support)
- Enhanced Stop hook with agent response and metadata
- Centralized session management with consistent IDs
- Built-in examples for logging, validation, and monitoring

This enables users to:
- Log and audit all tool usage and prompts
- Implement custom security policies
- Track usage metrics and model performance
- Integrate with external systems
- Build custom workflows around MCPHost

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

* Enable hooks in script mode

Previously, hooks were only initialized and executed in normal mode but not
in script mode. This was because script mode had its own execution path that
bypassed the hook initialization code.

This fix:
- Adds hook initialization to runScriptMode function
- Creates hook executor with proper session ID and model info
- Passes the hook executor to runAgenticLoop

Now hooks work consistently across all execution modes (normal, script, and
interactive), ensuring uniform behavior for logging, validation, and monitoring.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

* Remove unnecessary hooks.local.yml pattern

The .local.yml pattern adds unnecessary complexity. Users who want project-specific
hooks that aren't committed to git can simply add .mcphost/ to their .gitignore.

This simplifies the hooks configuration loading and makes it clearer that:
- Global user hooks go in ~/.config/mcphost/hooks.yml
- Project-specific hooks go in .mcphost/hooks.yml
- Git ignore management is left to the user

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

* Fix hooks test isolation and add --no-hooks flag

- Fix TestLoadHooksConfig by setting temporary XDG_CONFIG_HOME to prevent loading global hooks
- Add --no-hooks flag to disable all hooks execution across all modes
- Update README with documentation for the new flag
- Add test to verify hooks loading behavior

This allows users to temporarily disable hooks for security or debugging purposes.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>

---------

Co-authored-by: opencode <noreply@opencode.ai>
2025-07-24 13:56:33 +03:00
Ed Zynda 66b7a72281 fix env parsing 2025-07-22 18:53:44 +03:00
Karsten Ohme 4227926dda Sanitize tool arguments to handle empty or malformed input (#110) 2025-07-21 16:52:57 +03:00
Ed Zynda e40e44da48 add fetch_filtered_json tool 2025-07-16 13:59:42 +03:00
Ed Zynda 2fd34068dd fix tests 2025-07-15 14:41:44 +03:00
Ed Zynda df9337d085 fix script frontmatter parsing 2025-07-15 14:27:58 +03:00
Ed Zynda f3fd2822de add fetch_extract tool 2025-07-15 13:43:03 +03:00
Ed Zynda b0761f7ada add fetch_summarize tool 2025-07-15 13:35:18 +03:00
Ed Zynda 8eb5060dc4 Add builtin HTTP functionality (#106)
* Add builtin HTTP functionality

* Add GitHub Actions CI workflow

- Runs tests with race detection on push to main and PRs
- Verifies code generation is up to date
- Uses Go version from go.mod file
- Supports manual workflow dispatch

* Remove verify-codegen job from CI workflow

Simplifies CI to only run tests with race detection
2025-07-10 09:41:51 +03:00
Ed Zynda 5c041394b8 Autocomplete (#105)
* implement autocomplete

* fix

* fix

* remove useless history command

* fix spacing

* fix padding for goodbye

* cleanup
2025-07-09 17:49:13 +03:00
Ed Zynda df29dc1673 fix frontmatter parsing 2025-07-09 12:58:16 +03:00
Ed Zynda a87c772fd0 fix session saving 2025-07-09 11:31:35 +03:00
Ed Zynda 9159f226f4 Smooth UI (#104)
* draft: rewrite single message when streaming (not full terminal)

* having the spinner align better with dots in compact mode

* fix user messages

* handle usage display

* fix formatting

* bash highlighting

---------

Co-authored-by: Nate Woods <big.nate.w@gmail.com>
2025-07-09 00:41:35 +03:00
Ed Zynda ac954068a9 update todos 2025-06-30 17:13:12 +03:00
Ed Zynda 4f524d3b16 Fix compact mode in scripts 2025-06-28 14:36:35 +03:00
Ed Zynda 32c46e4dc3 Fix for anthropic 2025-06-28 14:17:53 +03:00
Ed Zynda 053e6c32b0 Fix for openai (#95) 2025-06-28 13:38:58 +03:00
Ed Zynda ddd7856f9b Refactor: Extract shared code between normal and script modes (#94)
This commit addresses issue #92 by extracting duplicated code between
normal mode (cmd/root.go) and script mode (cmd/script.go) into reusable
factory functions and utilities.

## Changes Made

### New Factory Files
- **internal/agent/factory.go**: Agent creation factory with spinner support
  - `CreateAgent()` function with configurable options
  - `ParseModelName()` utility for model string parsing
  - Spinner function injection to avoid import cycles

- **internal/ui/factory.go**: CLI setup factory with standard configuration
  - `SetupCLI()` function for consistent CLI initialization
  - Usage tracking setup for supported providers
  - Model info and tool count display

- **internal/config/merger.go**: Config loading and merging utilities
  - `LoadAndValidateConfig()` for standard config loading
  - `MergeConfigs()` for script frontmatter merging

### Updated Command Files
- **cmd/root.go**: Refactored to use new factories
  - Replaced ~50 lines of agent creation logic
  - Replaced ~30 lines of CLI setup logic
  - Replaced ~20 lines of config loading logic
  - Added agentUIAdapter to handle interface compatibility

- **cmd/script.go**: Refactored to use new factories
  - Same factory usage as normal mode for consistency
  - Maintained script-specific behavior (no spinners)
  - Improved config merging with frontmatter

## Benefits
- **Reduced code duplication**: ~33 lines of duplicated code eliminated
- **Single source of truth**: Agent creation and CLI setup logic centralized
- **Consistent behavior**: Both modes now use identical underlying logic
- **Easier maintenance**: Changes apply to both modes automatically
- **Better testability**: Factory functions can be unit tested independently
- **Cleaner command files**: Focus on mode-specific logic only

## Testing
- All existing tests pass
- Build verification successful
- Both normal and script modes tested for basic functionality
- Code formatting and linting checks passed

🤖 Generated with [opencode](https://opencode.ai)

Co-authored-by: opencode <noreply@opencode.ai>
2025-06-27 17:41:18 +03:00
Ed Zynda 47718a7fed stream in script mode 2025-06-27 16:40:42 +03:00
Ed Zynda 7b2592bbdc fmt 2025-06-27 16:31:01 +03:00
Ed Zynda 00aa3c6995 Env substitution (#93)
* compact mode

* tweaks

* tweaks

* tweaks

* fix streaming in tool calls

* impl env sub

* fix
2025-06-27 16:30:18 +03:00
Ed Zynda 76dd18030a Compact mode (#91)
* compact mode

* tweaks

* tweaks

* tweaks

* fix streaming in tool calls
2025-06-27 15:56:09 +03:00
Ed Zynda 42af56963f remove redundant info 2025-06-27 12:00:23 +03:00
Ed Zynda be5af5469f fix tool output 2025-06-27 11:54:17 +03:00
Ed Zynda 6a1b2f65d1 fix google/gemini 2025-06-27 11:40:11 +03:00
Ed Zynda acade83e37 fix token tracking 2025-06-27 11:33:44 +03:00
Ed Zynda daf5c41ac9 Fix object schema missing properties error for tools without input parameters (#90)
Fixes #89: Tools created without input properties were causing OpenAI function
calling validation errors with "object schema missing properties" message.

The issue occurred when MCP tools had no input parameters, resulting in OpenAPI
schemas with Type="object" but Properties=nil, which violates OpenAI's function
calling schema requirements.

Changes:
- Add schema validation fix in loadServerTools to ensure object schemas have
  empty properties map when Properties is nil
- Add comprehensive regression test TestIssue89_ObjectSchemaMissingProperties
- Add additional test coverage for tools without properties

The fix ensures backward compatibility while resolving the validation error.
Users no longer need the workaround of adding dummy parameters to their tools.

🤖 Generated with [opencode](https://opencode.ai)

Co-authored-by: opencode <noreply@opencode.ai>
2025-06-26 21:33:36 +03:00
Ed Zynda ec620e4e88 Streaming (#87)
* basic streaming

* fix output

* update

* update

* fix display issue

* update readme

* fmt

* cleanup

* cleanup

* cleanup

* fix
2025-06-26 18:15:17 +03:00
Ed Zynda a66c55e175 Improve Ollama and GPU handling (#85)
* preload ollama models

* fix num-gpu

* add download progress bar for ollama models

* fmt

* reorg
2025-06-26 13:32:18 +03:00
dependabot[bot] 6219b84937 Bump github.com/getkin/kin-openapi from 0.118.0 to 0.131.0 (#83)
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.118.0 to 0.131.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.118.0...v0.131.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-version: 0.131.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-26 11:50:03 +03:00
Ed Zynda 30f720ef7b updates 2025-06-26 10:56:10 +03:00
Ed Zynda b3c26150c7 fix /clear 2025-06-26 10:49:39 +03:00
Ed Zynda 904cbc6b37 Implement saved sessions (#82) 2025-06-25 20:25:14 +03:00
Ed Zynda b56fb3c597 Merge branch 'main' of github.com:mark3labs/mcphost 2025-06-25 18:18:44 +03:00
Ed Zynda 4d543da3b9 version support 2025-06-25 18:18:29 +03:00
Ed Zynda 7d6abbf4a0 UI updates (#80)
* ui updates

* catppuccin

* fmt
2025-06-25 17:24:37 +03:00
Ed Zynda b25bda58ab update README 2025-06-25 15:02:23 +03:00
Ed Zynda a2346721cb Claude max (#78)
* anthropic oauth

* update claude oauth costs
2025-06-25 14:27:19 +03:00
Ed Zynda 0972a1600a fix legacy mcp config 2025-06-25 13:15:09 +03:00
Ed Zynda e5b6e7e123 convert to newer config format automatically 2025-06-25 10:58:14 +03:00
Ed Zynda a46615abb0 cleanup 2025-06-24 17:50:18 +03:00
Ed Zynda 3fcee53836 enhanced script vars 2025-06-24 17:44:26 +03:00
Ed Zynda 84fd21fc42 new builtin servers 2025-06-24 16:53:48 +03:00
Ed Zynda d9983b9524 builtin servers 2025-06-24 16:29:44 +03:00
Ed Zynda 0d6742286b simplify mcp 2025-06-24 15:56:29 +03:00
Ed Zynda ec81875390 Fix nil pointer issue 2025-06-24 09:15:49 +03:00
Ed Zynda 679709d078 Fix deadlocks 2025-06-24 09:12:09 +03:00
Thomas Conté ff49415679 Add Azure OpenAI provider (#75) 2025-06-24 08:47:46 +03:00
Ed Zynda 7686577d9a fix config 2025-06-19 17:48:47 +03:00
Ed Zynda 1f407bf7fb streamable HTTP 2025-06-19 16:16:19 +03:00
Ed Zynda 33df79adb6 skip model name validation with custom provider URL 2025-06-19 15:14:21 +03:00
Ed Zynda d7f4c7a61c fix config loading 2025-06-19 14:42:59 +03:00
Ed Zynda 3faf46ff44 better token tracking and support for more openai models 2025-06-18 15:19:04 +03:00
Ed Zynda cda80f1572 anthropic token tracking 2025-06-18 14:48:19 +03:00
Ed Zynda aa43421b81 rudimentary token tracking 2025-06-18 14:05:30 +03:00
Ed Zynda 9e87977822 add up to date model data 2025-06-18 13:48:08 +03:00
Ed Zynda 78a12147d7 fix config 2025-06-18 11:40:47 +03:00
Ed Zynda 6fb31c16a7 start esc listener first 2025-06-18 11:30:27 +03:00
Ed Zynda 57f11899bf cancellation in non-interactive mode 2025-06-18 11:25:21 +03:00
Ed Zynda 45b9ad9d5c add cancellation 2025-06-18 10:29:47 +03:00
Ed Zynda 34ea275a0c cleanup 2025-06-18 09:41:36 +03:00
Ed Zynda 7ec2c5e9ed cleanup 2025-06-18 09:33:02 +03:00
Ed Zynda 1a5d4ccf8a consolidate 2025-06-17 23:09:29 +03:00
Ed Zynda 354e8a09fb fix config/flags in script 2025-06-17 22:03:21 +03:00
Ed Zynda cd04879d7b fix config/flags 2025-06-17 21:52:18 +03:00
Ed Zynda d273436091 refactor 2025-06-17 20:54:18 +03:00
Ed Zynda 798b1ede0e --no-exit in script mode 2025-06-16 17:49:27 +03:00
Ed Zynda d4910a9164 --no-exit 2025-06-16 17:35:01 +03:00
Ed Zynda 5ac43f1646 fix settings in scripts for real 2025-06-15 14:42:17 +03:00
Ed Zynda 4b23623270 fix settings in scripts 2025-06-15 14:33:41 +03:00
Ed Zynda 21f60e3560 ollama gpu settings 2025-06-13 11:36:10 +03:00
Hyunchul Jung c159972306 feat: support env in MCP-config (#69) 2025-06-13 11:05:30 +03:00
Ed Zynda 824694bacb no truncate in debug mode 2025-06-11 13:30:47 +03:00
Ed Zynda ec5c1ef03e fix normal mode input 2025-06-11 13:27:15 +03:00
Ed Zynda 42eb398148 format 2025-06-11 13:09:51 +03:00
Ed Zynda 4133bbcb60 update readme 2025-06-11 11:59:41 +03:00
Ed Zynda 87274cb960 update readme 2025-06-11 11:57:34 +03:00
Ed Zynda 306fbdb3d0 Standardize (#67)
* Model generation params

* Standardize model URL

* standardize more flags and update max steps check

* fix system prompt handling

* standardize api key handling

* update readme
2025-06-11 11:45:55 +03:00
Ed Zynda e0c20348b4 Scripting (#66)
* Move script to separate command

* Improve scripting

* cleanup

* cleanup

* Arg validation
2025-06-11 10:26:52 +03:00
Ed Zynda 6a362d2f5e string based system prompts 2025-06-10 15:18:06 +03:00
Ed Zynda cff5c2deda tweak 2025-06-10 14:40:21 +03:00
Ed Zynda 67dabdfeb2 send errors back to LLM 2025-06-10 14:32:00 +03:00
Ed Zynda 82b50fbf0e fix gemini 2025-06-10 14:20:40 +03:00
Ed Zynda 57efdb5332 cleanup 2025-06-10 13:44:17 +03:00
Ed Zynda 13ede07ea5 formatting 2025-06-10 01:21:17 +03:00
Ed Zynda 33e3e4fbb0 full config in script mode 2025-06-10 01:20:05 +03:00
Ed Zynda 5959f15f0a more updates 2025-06-09 23:44:01 +03:00
Ed Zynda 6c4bcce030 show spinner when running tool calls 2025-06-09 20:33:20 +03:00
Ed Zynda a88a2fc05c use consistent naming 2025-06-09 20:24:57 +03:00
Ed Zynda 887622b5c4 Enhancements (#64)
* filter tools

* Enhancements
2025-06-09 18:51:06 +03:00
Ed Zynda 45999161e5 Fix tool call content messages (#63)
* Implement non-interactive mode

* fix
2025-06-09 17:39:36 +03:00
Ed Zynda 8f8169a090 Implement non-interactive mode (#62) 2025-06-09 17:02:08 +03:00
Ed Zynda 5d5cb58eb9 Do not buffer tool calls and responses (#61) 2025-06-09 16:34:55 +03:00
Ed Zynda 6bc360d375 Allow yaml (#60) 2025-06-09 16:24:27 +03:00
Ed Zynda e37c7952dd create agent from scratch for better control (#59) 2025-06-09 15:46:17 +03:00
Ed Zynda 8645a8c36a Huge refactor (#58)
* overhaul

* fix ui glitch

* system messages

* use sonnet 4
2025-06-09 14:38:31 +03:00
Ed Zynda 6c8f1114e8 update .gitignore 2025-06-09 12:11:17 +03:00
Ed Zynda 7a81dc3ccf upgrade mcp-go 2025-06-08 13:45:47 +03:00
Stefan M. b4750c5852 Document SSE (#44) 2025-05-13 23:22:59 +03:00
Sebastian 009beb5b64 Quick hack to enable image support returned from tools (#45)
Co-authored-by: spsobole <spsobole@thirdmartini.com>
2025-05-13 23:22:30 +03:00
miyamo2 494851a71f fix: crash when sending tool_result with anthropic and google (#34)
* fix: tool result handling in anthropic

* fix: set the role outside the loop

* fix: role mapping in implementation of google provider and anthropic provider

* fix: simplify role mapping logic and update role constants
2025-04-25 15:13:55 +03:00
Hyunchul Jung beaa623fc5 feat: support system prompt (#29)
* support system prompt for ollama

* support system prompt for openai

* support system prompt for google gemini

* fixed setting for google gemini system-prompt

* support system prompt for anthropic

* edit README

* edit commments

* fix system-prompt flag desc

* modifed var name for systemPromp
2025-04-23 17:39:21 +03:00
Glenn Lewis 4facfd6151 fix: Do not panic when reading malformed properties (#31)
* fix: Do not panic when reading malformed properties

Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>

* Add one more check

Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>

---------

Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>
2025-04-23 17:38:01 +03:00
James Heller c47f090061 Add number and integer types to google type converter (#32) 2025-04-23 17:37:23 +03:00
Daniel Bos 4fe66b099e Fix multiple requests at the same time (#33)
Previously when multiple tool calls were requested by the LLM we got the
error `An assistant message with 'tool_calls' must be followed by tool
messages responding to each 'tool_call_id'.` although the calls were
processed.

Apparently each response needs to be its own message, rather than its
own block.

Closes #7
2025-04-23 17:37:01 +03:00
Roman Gelembjuk ec996fd1e1 Add support of config for SSE servers. Add Authorization header support (#24)
* Add support of config for SSE servers. Add Authorization header support

* Change the format of JSON for SSE servers to be similar to other tools

---------

Co-authored-by: Roman Gelembjuk <Roman Gelembjuk>
2025-04-16 20:18:44 +03:00
mimi3421 c572625257 Add ``omitempty` to `reasoning_content`` in OpenAI message API (#27)
Add ```omitempty``` to ```reasoning_content``` in OpenAI message API. See issue [link](https://github.com/mark3labs/mcphost/issues/23)
2025-04-16 20:18:12 +03:00
Daniel Bos f0be8458aa Increase maximum length of prompt input (#25)
Default maximum length is 400 characters.
2025-04-16 20:17:39 +03:00
Ed Zynda 3076686b18 Add a Discord link\ 2025-04-12 12:55:53 +03:00
Roman Gelembjuk b4057a917a Allow to connect to remote SSE MCP server (#22)
Co-authored-by: Roman Gelembjuk <Roman Gelembjuk>
2025-04-11 23:22:53 +03:00
J.K. f1ed1fcb5c Add explanation about Ollama environment variables (#17)
Co-authored-by: Ed Zynda <ezynda3@users.noreply.github.com>
2025-04-11 23:21:16 +03:00
J.K. d8583a7883 Fix default config file name in help message (#16)
* Fix default config file name

* Update README.md
2025-04-11 23:19:33 +03:00
zku d58251f53e Add Google/Gemini support (#15)
* add google/gemini support

* fix user/model role issues, use chat session

* properly maintain history

* update gemini/google provider, pipe context through runPrompt, minor fixes

* minor formatting fix in usage tooltip

* update readme to showcase Google/Gemini support

---------

Co-authored-by: Ed Zynda <ezynda3@users.noreply.github.com>
2025-04-11 23:19:06 +03:00
Earisty 54ff95c421 add contribute guide and fix openai implement to openai-compatible (#13) 2025-04-11 23:16:43 +03:00
Ed Zynda e614a4d061 Merge branch 'main' of github.com:mark3labs/mcphost 2025-02-23 12:16:30 +03:00
Ed Zynda 32a4831179 use background context 2025-02-23 12:16:17 +03:00
Ed Zynda 9681971ee9 Update README 2025-01-03 07:12:20 +03:00
Ed Zynda c4ed189f48 Setup goreleaser 2025-01-03 07:08:49 +03:00
GargantuaX 88a8c7bf15 support anthropicBaseURL , openaiAPIKey, anthropicAPIKey cli params (#6)
* * support anthropicBaseURL , openaiAPIKey, anthropicAPIKey cli params

* * support anthropicBaseURL , openaiAPIKey, anthropicAPIKey cli params

* opt

* remove the non-English comments

* add new Flags to document
2024-12-27 14:10:29 +03:00
Ed Zynda b32fd143c6 cleanup 2024-12-21 20:28:35 +03:00
Ed Zynda 9a50bcbcbe update versions and formatting 2024-12-20 18:25:10 +03:00
Ed Zynda e1a974511d remove patch version 2024-12-20 18:21:03 +03:00
Ed Zynda 5c9246cd5f Update README 2024-12-20 18:20:05 +03:00
Ed Zynda 5ac7908227 Refactor (#5)
Refactoring so hard!
2024-12-20 18:15:14 +03:00
Ed Zynda e3a64a107d update SDK version 2024-12-18 11:31:27 +03:00
Ed Zynda fa7cbb0b55 Fix ollama 2024-12-11 17:13:41 +03:00
Ed Zynda 0bbe5f15c1 Update deps 2024-12-11 15:36:01 +03:00
Ed Zynda e69bbb8354 Update deps 2024-12-11 15:35:56 +03:00
Ed Zynda 5201b57ec6 Update README 2024-12-10 22:29:08 +03:00
Ed Zynda e7cebe73da Update README and LICENSE 2024-12-10 22:25:15 +03:00
Ed Zynda b79b521911 Add message window to prevent overloading context 2024-12-10 22:19:35 +03:00
Ed Zynda 57fe2c6795 Update styling 2024-12-10 21:52:33 +03:00
Ed Zynda ea68e00db6 Add /history command 2024-12-10 18:08:17 +03:00
Ed Zynda 87ffdec8f0 Add usage stats and handle overloaded errors 2024-12-10 17:40:02 +03:00
Ed Zynda 2d79e5989c Remove anthropic go sdk dependency 2024-12-10 17:32:12 +03:00
Ed Zynda 6b2180ac7c Fix tool check 2024-12-10 16:36:13 +03:00
Ed Zynda 8b0001184f Disable tools for models that do not support it. 2024-12-09 16:46:12 +03:00
Ed Zynda 8350b7a55a Update MCP-Go SDK 2024-12-09 10:12:51 +03:00
Ed Zynda 986f5560c5 Refactor 2024-12-09 00:01:06 +03:00
Ed Zynda 8301aa1a68 Update README 2024-12-08 19:37:30 +03:00
Ed Zynda aca9d750ba update config location and add LICENSE 2024-12-08 19:33:45 +03:00
Ed Zynda d80a14f660 Initial commit 2024-12-08 19:27:53 +03:00
434 changed files with 106262 additions and 17866 deletions
+79
View File
@@ -0,0 +1,79 @@
name: Bug Report
description: Report a bug or issue with Kit
title: "fix: "
labels: ["bug"]
body:
- type: textarea
id: description
attributes:
label: Bug Description
description: What happened? What did you expect to happen?
placeholder: |
The BorderColor field in ToolRenderConfig is documented but never applied
during tool rendering. I expected the tool block to render with my custom
color, but it uses the default styling instead.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: Provide clear steps to reproduce the issue
placeholder: |
1. Create an extension with `api.RegisterToolRenderer(ext.ToolRenderConfig{...})`
2. Set `BorderColor: "#89b4fa"` in the config
3. Run a tool that uses this renderer
4. Observe the border color is not applied
render: markdown
validations:
required: true
- type: textarea
id: code
attributes:
label: Relevant Code / Configuration
description: Paste any code, configuration, or error messages
placeholder: |
```go
api.RegisterToolRenderer(ext.ToolRenderConfig{
ToolName: "bash",
DisplayName: "Shell",
BorderColor: "#a6e3a1", // This is ignored!
Background: "#1e1e2e", // This is ignored!
})
```
render: go
- type: input
id: component
attributes:
label: Affected Component
description: Which part of Kit is affected?
placeholder: e.g., extensions, ui, tool rendering, session management
- type: input
id: version
attributes:
label: Kit Version
description: What version of Kit are you running?
placeholder: e.g., v0.1.0, commit hash, or "main"
- type: textarea
id: context
attributes:
label: Additional Context
description: Any other context, proposed fixes, or related issues
placeholder: |
The issue appears to be in `internal/ui/messages.go:RenderToolMessage()`
which ignores the BorderColor and Background fields from ToolRendererData.
- type: checkboxes
id: terms
attributes:
label: Checklist
options:
- label: I've searched existing issues and this hasn't been reported yet
required: true
- label: I've tested with the latest version of Kit
required: false
+11
View File
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Kit Documentation
url: https://github.com/mark3labs/kit/tree/main/www/pages
about: Check the documentation before filing an issue
- name: Extension Examples
url: https://github.com/mark3labs/kit/tree/main/examples/extensions
about: See working extension examples for reference
- name: Discussions
url: https://github.com/mark3labs/kit/discussions
about: For questions, ideas, or general discussion
+40
View File
@@ -0,0 +1,40 @@
name: Documentation Issue
description: Report missing, incorrect, or unclear documentation
title: "docs: "
labels: ["documentation"]
body:
- type: textarea
id: description
attributes:
label: Documentation Issue
description: What's wrong or missing in the documentation?
placeholder: |
The ToolRenderConfig documentation mentions BorderColor and Background fields,
but the code doesn't actually use them. The docs should either be updated
to reflect reality, or the bug should be fixed.
validations:
required: true
- type: input
id: location
attributes:
label: Documentation Location
description: Where is the affected documentation?
placeholder: e.g., README.md, examples/extensions/tool-renderer-demo.go, pkg/kit docs
- type: textarea
id: suggestion
attributes:
label: Suggested Improvement
description: How should the documentation be improved?
placeholder: |
Add a note that BorderColor and Background are not yet implemented,
or fix the bug and document the correct behavior.
- type: checkboxes
id: terms
attributes:
label: Checklist
options:
- label: I've checked that this documentation issue still exists in the latest version
required: true
@@ -0,0 +1,64 @@
name: Feature Request
description: Suggest a new feature or enhancement for Kit
title: "feat: "
labels: ["enhancement"]
body:
- type: textarea
id: description
attributes:
label: Feature Description
description: What would you like to see added or changed?
placeholder: |
I'd like to be able to customize the border color of tool result blocks
dynamically based on the tool type or result status.
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation / Use Case
description: Why is this feature needed? What problem does it solve?
placeholder: |
When running multiple tools in sequence, it's hard to visually distinguish
between file reads (blue), shell commands (green), and errors (red)
without custom border colors.
validations:
required: true
- type: textarea
id: proposed
attributes:
label: Proposed Implementation
description: How do you think this should work? (optional)
placeholder: |
Extend `ToolRenderConfig` to accept a function that receives the tool
result and returns a color based on the content:
```go
BorderColorFunc: func(result string, isError bool) string {
if isError {
return "#f38ba8"
}
return "#89b4fa"
}
```
render: go
- type: checkboxes
id: alternatives
attributes:
label: Alternatives Considered
options:
- label: I've considered workarounds or alternative approaches
required: false
- type: checkboxes
id: terms
attributes:
label: Checklist
options:
- label: I've searched existing issues and this hasn't been requested yet
required: true
- label: This feature aligns with Kit's design philosophy (TUI-first, extension-based)
required: false
+32
View File
@@ -0,0 +1,32 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- uses: golangci/golangci-lint-action@v7
with:
version: v2.10.1
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: go test -race ./...
+32
View File
@@ -0,0 +1,32 @@
name: Build and Deploy Docs to GitHub Pages
on:
push:
branches: [master]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install Dependencies
working-directory: ./www
run: bun install
- name: Build
working-directory: ./www
run: bun run build
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: www/out
branch: gh-pages
+105
View File
@@ -0,0 +1,105 @@
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: 'Tag to use for npm publish'
required: true
skip_goreleaser:
description: 'Skip goreleaser job (npm-only release)'
type: boolean
default: true
permissions:
contents: write
id-token: write # Required for npm trusted publishing (OIDC)
jobs:
goreleaser:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || !inputs.skip_goreleaser }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
npm-publish:
runs-on: ubuntu-latest
needs: goreleaser
if: ${{ always() && (needs.goreleaser.result == 'success' || needs.goreleaser.result == 'skipped') }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Set version from tag
working-directory: npm
run: |
TAG=${{ inputs.tag || github.ref_name }}
VERSION=${TAG#v}
echo "Setting npm version to $VERSION"
npm version $VERSION --no-git-tag-version
- name: Publish to npm
working-directory: npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
notify:
runs-on: ubuntu-latest
needs: [goreleaser, npm-publish]
if: ${{ always() && (needs.goreleaser.result == 'success' || needs.goreleaser.result == 'skipped') && (needs.npm-publish.result == 'success') }}
steps:
- name: Send Discord Notification
env:
DISCORD_WEBHOOK: ${{ secrets.RELEASES_WEBHOOK }}
TAG_NAME: ${{ inputs.tag || github.ref_name }}
RELEASE_URL: https://github.com/${{ github.repository }}/releases/tag/${{ inputs.tag || github.ref_name }}
run: |
curl -H "Content-Type: application/json" \
-X POST \
-d "{
\"embeds\": [{
\"title\": \"New Release: $TAG_NAME\",
\"description\": \"A new version of kit has been released!\",
\"color\": 5814783,
\"fields\": [
{
\"name\": \"Version\",
\"value\": \"$TAG_NAME\",
\"inline\": true
},
{
\"name\": \"Repository\",
\"value\": \"[kit](https://github.com/${{ github.repository }})\",
\"inline\": true
}
],
\"footer\": {
\"text\": \"Released via GitHub Actions\"
},
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",
\"url\": \"$RELEASE_URL\"
}]
}" \
$DISCORD_WEBHOOK
+17
View File
@@ -0,0 +1,17 @@
.aider*
.task/
.env
.kit/*
!.kit/extensions/
!.kit/prompts/
aidocs/
*.log
/kit
.idea
build/
dist/
contribute/output/
CONTEXT.md
output/
.agents/
skills-lock.json
+9
View File
@@ -0,0 +1,9 @@
version: "2"
linters:
enable:
- modernize
formatters:
enable:
- gofmt
+70
View File
@@ -0,0 +1,70 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
builds:
- main: ./cmd/kit
binary: kit
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
archives:
- formats: [tar.gz]
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
format_overrides:
- goos: windows
formats: [zip]
checksum:
name_template: checksums.txt
release:
header: |
## Installation
```bash
# Bun / npm
bun add -g @mark3labs/kit
# or: npm install -g @mark3labs/kit
# Go install (requires Go 1.26+)
go install github.com/mark3labs/kit/cmd/kit@latest
# Or download from assets below
```
changelog:
sort: asc
use: github
filters:
exclude:
- "^Merge"
- "^merge"
- "^wip"
- "^WIP"
groups:
- title: "Features"
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: "Bug Fixes"
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 1
- title: "Documentation"
regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$'
order: 2
- title: "Maintenance"
regexp: '^.*?(chore|refactor|style|ci|build)(\([[:word:]]+\))??!?:.+$'
order: 3
- title: "Other Changes"
order: 999
@@ -0,0 +1 @@
{"Created":"2026-02-25T21:53:09.725157606Z","name":"iteratr_events","subjects":["iteratr.\u003e"],"retention":"limits","max_consumers":-1,"max_msgs":-1,"max_bytes":-1,"max_age":2592000000000000,"max_msgs_per_subject":-1,"max_msg_size":-1,"discard":"old","storage":"file","num_replicas":1,"duplicate_window":120000000000,"compression":"none","allow_direct":false,"mirror_direct":false,"sealed":false,"deny_delete":false,"deny_purge":false,"allow_rollup_hdrs":false,"consumer_limits":{}}
@@ -0,0 +1 @@
0d94ed0d599988d1
@@ -0,0 +1 @@
{"Created":"0001-01-01T00:00:00Z","Name":"","name":"7UfQJgqo","deliver_policy":"all","ack_policy":"explicit","ack_wait":30000000000,"max_deliver":-1,"filter_subject":"iteratr.unified-bubbletea-architecture.\u003e","replay_policy":"instant","max_waiting":512,"max_ack_pending":1000,"inactive_threshold":5000000000,"num_replicas":0}
@@ -0,0 +1 @@
0a75862a3a998ab5
@@ -0,0 +1 @@
{"Created":"0001-01-01T00:00:00Z","Name":"","name":"EnXXTfbX","deliver_policy":"all","ack_policy":"explicit","ack_wait":30000000000,"max_deliver":-1,"filter_subject":"iteratr.unified-bubbletea-architecture.\u003e","replay_policy":"instant","max_waiting":512,"max_ack_pending":1000,"inactive_threshold":5000000000,"num_replicas":0}
@@ -0,0 +1 @@
3288f873d6615ff9
+1
View File
@@ -0,0 +1 @@
39825
+37
View File
@@ -0,0 +1,37 @@
---
description: Run ACP smoke test against opencode/kimi-k2.5 to verify JSON-RPC stdio works
---
Run the ACP smoke test to verify the Kit ACP server works correctly over JSON-RPC stdio with streaming responses.
## Steps
1. Build the kit binary:
```bash
go build -o output/kit ./cmd/kit
```
2. Run the smoke test Python script against opencode/kimi-k2.5:
```bash
python3 scripts/acp_smoke_test.py
```
3. Verify the output shows:
- `session/new` returns a valid `sessionId`
- `session/prompt` streams `agent_thought_chunk` notifications (reasoning)
- `session/prompt` streams `agent_message_chunk` notifications (response)
- Final result has `stopReason: "end_turn"`
- `✓ SMOKE TEST PASSED` at the end
4. If the test fails, check:
- `output/kit` binary exists and is executable
- `OPENCODE_API_KEY` or `OPENCODE_ZEN_API_KEY` environment variable is set
- `scripts/acp_smoke_test.py` exists
- The model `opencode/kimi-k2.5` is available (`kit models opencode | grep kimi-k2.5`)
5. For testing with a different model, edit the script or set the `MODEL` variable:
```bash
MODEL=anthropic/claude-sonnet-4-5 python3 scripts/acp_smoke_test.py
```
The smoke test exercises the full ACP protocol: session lifecycle, streaming notifications, and tool-free prompt completion.
+146
View File
@@ -0,0 +1,146 @@
---
description: Read-only audit for dead code, duplication, boundary violations, and refactor opportunities
---
Perform a comprehensive **read-only** audit of this repository and report
findings. **Do not edit, rename, or delete any files.** Optional focus / scope
hints from the user: $@
## Scope
If the user supplied focus hints above (a package path, a subsystem name, a
concern like "TUI" or "extensions"), scope the audit accordingly. Otherwise
audit the whole repo, prioritising the highest-traffic packages first
(`cmd/`, `internal/`, `pkg/kit/` for this repo).
## Steps
1. **Map the repo first**:
- `ls` / `find` the top-level layout and list every Go package
- Read `AGENTS.md`, `README.md`, and any `pkg/*/doc.go` to understand the
intended architectural boundaries (SDK vs internal vs TUI vs cmd vs
extension surface)
- Note the public SDK surface (`pkg/kit/`) and any documented invariants
(e.g. "no dependency name leakage", "UI never imports extensions
directly") — these define what counts as a violation
2. **Hunt for dead code**:
- Run `go vet ./...` and capture warnings
- Use `grep` to find exported symbols (`^func [A-Z]`, `^type [A-Z]`,
`^var [A-Z]`, `^const [A-Z]`) and cross-reference call sites. Symbols
with zero non-test references inside the module are suspects
- Check for unreferenced files, `// TODO: remove` markers, commented-out
blocks, and `_ = x` discard patterns
- If `staticcheck`, `deadcode`, or `unused` are available on PATH, run
them and include their output verbatim
- **Do not delete anything** — list candidates with file:line and a
confidence level (high / medium / low)
3. **Find unnecessary duplication**:
- Look for near-identical function bodies, struct shapes, or switch
statements across packages — `grep` for repeated function signatures
and copy-pasted string literals / error messages is a fast first pass
- Distinguish *coincidental* duplication (two things that happen to look
alike but evolve independently) from *unnecessary* duplication (same
intent, drifting in lockstep) — only flag the latter
- For each cluster, propose where the extracted helper should live
(which package, which file) and whether it crosses a boundary
4. **Check concerns / boundary violations**:
- **SDK leakage**: grep `pkg/kit/` for imports of `internal/...` types
in exported signatures, and for dependency-name leakage in exported
names / godoc (e.g. library jargon appearing in `LLM*` types)
- **UI ↔ extensions**: grep `internal/ui/` for any import of
`internal/extensions/` — per AGENTS.md the UI must not import
extensions directly; converters in `cmd/root.go` should bridge them
- **cmd vs internal**: business logic living in `cmd/` that should be
in `internal/` (and vice versa)
- **Cyclic risk**: packages that import each other transitively or that
reach across sibling boundaries unexpectedly
- For each violation, cite the offending import / signature with
file:line
5. **Spot refactor opportunities**:
- Long functions (>80 lines) doing multiple unrelated things
- Deeply nested conditionals that flatten well with early returns
- Repeated `if err != nil { return fmt.Errorf("...: %w", err) }` chains
that could become helpers — but only where the wrapping context is
genuinely uniform
- Structs with too many fields that hint at split responsibilities
- Exported APIs that would be cleaner with options structs / functional
options
- Tests that share setup boilerplate ripe for a helper
- Flag each with: location, current shape (1-2 lines), proposed shape
(1-2 lines), and estimated risk (low / medium / high)
6. **Cross-check against project rules**:
- Re-read `AGENTS.md` "Key Patterns" section and verify nothing in your
findings contradicts the documented gotchas (Yaegi interface ban,
`prog.Send()` from `Update()`, function-field bug, etc.) — if a
"refactor" would reintroduce a known pitfall, drop it from the report
and note why
7. **Write the report** as your final message (do not write it to disk)
structured as:
```
# Code Audit Report
## Summary
- N dead-code candidates
- N duplication clusters
- N boundary violations
- N refactor opportunities
## Dead Code
### High confidence
- path/to/file.go:LINE — symbol — reason
### Medium confidence
...
## Duplication
### Cluster: <short name>
- Sites: file:line, file:line, …
- Suggested home: package/path
- Notes: …
## Boundary Violations
- Rule: <which rule from AGENTS.md / project convention>
- Offender: file:line
- Fix sketch: …
## Refactor Opportunities
- Location: file:line
- Current: …
- Proposed: …
- Risk: low/medium/high
- Why it's worth it: …
## Suggested Next Steps
1. …
2. …
```
8. **End the report with an explicit reminder** that no files were modified,
and recommend the user pick the highest-leverage items to act on
manually (or via a follow-up `/fix-issue` style prompt) rather than
running a sweeping refactor.
## Guidelines
- **Read-only, always**: no `edit`, no `write`, no `git commit`, no `go mod
tidy`. Use only `read`, `grep`, `find`, `ls`, and read-only `bash`
commands (`go vet`, `go build -o /tmp/...`, `staticcheck`, etc.)
- **Cite every finding** with `path/to/file.go:LINE` so the user can jump
straight to it
- **Be honest about confidence**: false positives in a code audit are
expensive — prefer "medium confidence, worth a look" over confidently
wrong claims
- **Quantity isn't quality**: 10 sharp findings beat 100 nitpicks. Cut
anything that's purely stylistic unless it directly causes one of the
four issue categories above
- **Skip generated code** (`*.pb.go`, `*_gen.go`, anything under
`vendor/`) and obvious third-party copies
- **Don't propose architectural rewrites** — stay within the existing
shape of the repo and recommend incremental, reviewable changes
+30
View File
@@ -0,0 +1,30 @@
---
description: Stage, commit, and push changes with an auto-generated conventional commit message
---
Review the current git status and diff, then stage all changes, write a concise conventional commit message, commit, and push to the current branch.
## Steps
1. **Check status**: `git status` — understand what has changed
2. **Review the diff**: `git diff` (and `git diff --cached` if anything is already staged) — read the actual changes
3. **Stage everything**: `git add -A`
4. **Craft the commit message** following Conventional Commits:
- Format: `<type>(<scope>): <short summary>`
- Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `perf`, `build`
- Scope: optional, the subsystem affected (e.g. `ui`, `cmd`, `config`)
- Summary: imperative mood, lowercase, no trailing period, ≤72 chars
- Body: add a blank line then bullet points for non-trivial changes
- Do **not** include "Generated by" or similar noise
5. **Commit**: `git commit -m "<message>"`
6. **Push**: `git push`
## Guidelines
- Read the actual diff — do not guess from filenames alone
- Prefer one well-scoped commit; do not split unless the changes are clearly unrelated
- Keep the subject line under 72 characters
- Use the body to explain *what* and *why*, not *how*
- If there is nothing to commit, say so and stop
$@
+47
View File
@@ -0,0 +1,47 @@
---
description: Open a GitHub PR for the current branch using the repo's PR template
---
Open a GitHub pull request for the current branch, filling out the repository's PR template with a description grounded in the actual commits and diff.
## Steps
1. **Verify the branch is pushed**:
- `git status -sb` and `git log @{u}..HEAD --oneline 2>/dev/null` — if there is no upstream or unpushed commits, run `git push -u origin "$(git branch --show-current)"` first
- If the working tree is dirty, stop and tell the user to commit first (suggest `/commit-push`)
2. **Gather context**:
- `git log origin/main..HEAD --oneline` — list of commits going into the PR
- `git diff origin/main...HEAD --stat` then `git diff origin/main...HEAD` — read the actual changes
- Identify the linked issue (from commit messages, branch name, or extra user input: $@) — capture as `Fixes #N` if applicable
3. **Locate the PR template**:
- Check `.github/pull_request_template.md`, `.github/PULL_REQUEST_TEMPLATE.md`, or `docs/pull_request_template.md`
- If none exists, use a minimal `## Description` / `## Type of Change` / `## Checklist` structure
4. **Draft the PR body** by filling out the template:
- **Description**: 13 short paragraphs explaining *what* changed and *why*, grounded in the diff. Include a brief before/after example for new APIs when useful.
- **Fixes #N**: only if there is a real linked issue
- **Type of Change**: tick the single most accurate box with `[x]` (leave others as `[ ]`)
- **Checklist**: tick items that are genuinely true (style, self-review, tests added, docs updated)
- **Additional Information**: bullet list of added / modified files and any backward-compatibility notes
- Remove template sections explicitly marked "remove if not applicable" (e.g. MCP Spec Compliance) when they don't apply
5. **Write the body to a temp file**: `/tmp/pr-body-<branch-or-issue>.md` — never inline a long body via `--body`, always use `--body-file`
6. **Choose the title**: prefer the subject of the primary commit if it already follows Conventional Commits; otherwise craft one in the same style (`<type>(<scope>): <imperative summary>`, ≤72 chars)
7. **Create the PR**:
```
gh pr create \
--title "<title>" \
--body-file /tmp/pr-body-<...>.md \
--base main \
--head "$(git branch --show-current)"
```
Use the repo's actual default branch if it isn't `main` (`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`)
8. **Report the PR URL** returned by `gh` and stop
## Guidelines
- Read the diff and commit messages — do **not** invent features that aren't in the code
- One PR per logical change; if the branch contains unrelated commits, surface that and ask before continuing
- Keep the description focused on reviewer-relevant information (what / why), not a replay of the diff
- Only check checklist boxes that are actually satisfied; leave the rest unchecked rather than lying
- If `gh` is not authenticated (`gh auth status` fails), stop and tell the user
$@
+86
View File
@@ -0,0 +1,86 @@
---
description: Create a feature request using the GitHub template
---
Create a feature request for the Kit repository. The user wants to request: $@
## Feature Request Template
This prompt uses the `feature_request` GitHub template which requires:
| Field | Required | Purpose |
|-------|----------|---------|
| **Feature Description** | Yes | What should be added or changed |
| **Motivation / Use Case** | Yes | Why is this needed? What problem does it solve? |
| **Proposed Implementation** | No | How do you think this should work? |
## Steps
1. **Understand the request** from the user input: $@
- What capability is missing?
- What would the ideal behavior look like?
2. **Ask clarifying questions** if needed:
- "What problem does this solve for you?"
- "How would you expect this to work?"
- "Are there similar features in other tools you use?"
3. **Craft the title** using conventional format:
- `feat: <short description>`
- Lowercase, imperative mood, ≤72 chars
- Good examples:
- `feat: add keyboard shortcut for clearing input`
- `feat: support custom themes per extension`
- `feat: add fuzzy matching to model selector`
- Bad examples:
- `Feature request: can we have...` (too vague)
- `It would be nice if...` (not imperative)
4. **Build the body** with the template fields:
**Feature Description:**
- Clear statement of what to add/change
- Be specific about the behavior
- Include UI/UX details if relevant
**Motivation / Use Case:**
- What problem does this solve?
- Current workaround (if any) and why it's insufficient
- Who benefits from this feature?
**Proposed Implementation** (optional but helpful):
- High-level approach
- API changes if applicable
- Example usage code
5. **Create the issue**:
```bash
gh issue create --template feature_request --title "feat: ..." --body "..."
```
6. **Confirm success**:
- Show the issue URL and number
- Mention it was created with the feature_request template
## Guidelines
- Focus on the *problem* first, then the solution
- Include concrete examples of how the feature would be used
- Consider edge cases and mention them
- If proposing API changes, show before/after code
- Check if similar features exist in related tools (mention them for reference)
- Align with Kit's philosophy: TUI-first, extension-based, keyboard-driven
## Example
User: `/feature-request I want to be able to customize tool border colors dynamically`
You:
1. Title: `feat: dynamic border colors for tool results based on status`
2. Body:
- **Feature Description**: Allow `ToolRenderConfig` to accept a function that determines border color based on tool result content or status, enabling dynamic visual feedback.
- **Motivation**: When running multiple tools, it's hard to distinguish file reads (blue), shell commands (green), and errors (red) without custom colors per result.
- **Proposed Implementation**: Add `BorderColorFunc` callback that receives `(result string, isError bool)` and returns a color string.
3. Execute: `gh issue create --template feature_request --title "feat: ..." --body "..."`
4. Confirm: Created issue #43 using feature_request template
+100
View File
@@ -0,0 +1,100 @@
---
description: File a GitHub issue using the appropriate template
---
File a GitHub issue for the Kit repository. The user wants to create an issue about: $@
## Issue Templates Available
This repository has structured issue templates. You MUST use the appropriate template:
| Type | Template | Use For |
|------|----------|---------|
| `bug` | `bug_report` | Something is broken, not working as expected |
| `feat` | `feature_request` | New feature, enhancement, improvement |
| `docs` | `documentation` | Missing, incorrect, or unclear documentation |
## Steps
1. **Determine the issue type** from the user input: $@
- Bug → use `--template bug_report`
- Feature → use `--template feature_request`
- Documentation → use `--template documentation`
2. **Ask clarifying questions** if critical info is missing:
- For bugs: "What were you doing when this happened?" (reproduction steps)
- For features: "What problem does this solve?" (motivation)
- For docs: "Where did you look for this information?" (location)
3. **Craft the title** using conventional format:
- `<type>: <short description>`
- Lowercase, imperative mood, ≤72 chars
- Examples:
- `fix: ToolRenderConfig BorderColor ignored during rendering`
- `feat: add keyboard shortcut for clearing input`
- `docs: clarify extension widget lifecycle`
4. **File the issue** using the template:
```bash
# For bugs
gh issue create --template bug_report --title "fix: ..." --body "..."
# For features
gh issue create --template feature_request --title "feat: ..." --body "..."
# For documentation
gh issue create --template documentation --title "docs: ..." --body "..."
```
The template will guide the user through the required fields. You need to provide:
- **Bug reports**: Description, reproduction steps, expected vs actual behavior
- **Feature requests**: Description, motivation/use case, optional proposed implementation
- **Documentation**: Description, location of docs, suggested improvement
5. **Confirm success** by showing:
- The issue URL
- The issue number
- Which template was used
## Template Field Guide
### Bug Report (`bug_report`)
Required fields in the body:
- **Bug Description** - what happened vs expected
- **Steps to Reproduce** - numbered list to recreate the bug
- **Relevant Code** - code snippets, configuration, error messages
- **Component** - which part of Kit (ui, extensions, session, etc.)
- **Version** - Kit version or commit hash
### Feature Request (`feature_request`)
Required fields in the body:
- **Feature Description** - what to add/change
- **Motivation / Use Case** - why this is needed
- **Proposed Implementation** - how it could work (optional)
### Documentation (`documentation`)
Required fields in the body:
- **Documentation Issue** - what's wrong or missing
- **Documentation Location** - file or URL where docs exist
- **Suggested Improvement** - how to fix the docs
## Guidelines
- ALWAYS use `--template <name>` instead of bare `gh issue create`
- Include file paths and line numbers when you know them
- Use triple backticks for code blocks
- Keep the body factual - avoid speculation unless in "Proposed Fix" section
- If you're unsure about technical details, say so in the issue
- For UI bugs, describe what you see vs what you expect
- For API bugs, include the relevant struct/function names
## Example Usage
User: `/file-issue The ToolRenderConfig BorderColor field is documented but never used in rendering`
You:
1. Determine this is a **bug** (documented field doesn't work)
2. Use `--template bug_report`
3. Gather: reproduction steps (register renderer with BorderColor), expected (custom color), actual (default color)
4. Create issue with title `fix: ToolRenderConfig BorderColor and Background fields are ignored`
5. Confirm: Created issue #42 using bug_report template
+61
View File
@@ -0,0 +1,61 @@
---
description: Implement the fix/feature/docs change requested by a GitHub issue
---
Resolve GitHub issue #$1 by reading it, classifying it, and producing the appropriate code or doc change. **Stop once the working tree contains the change** — committing, pushing, and opening a PR are handled by `/commit-push` and `/create-pr`.
## Steps
1. **Fetch the issue**:
- Run: gh issue view $1 --json number,title,body,labels,state,author,comments
- If the issue is closed, stop and ask the user whether to proceed
- Read the **entire** thread including comments — the latest comment often refines the ask
2. **Classify the issue** from labels, title prefix, and body content:
- `bug` / `fix:` → reproduce, then fix
- `enhancement` / `feature` / `feat:` → design, then implement
- `documentation` / `docs:` → locate and update docs
- `question` / `discussion` → answer in a comment, do **not** write code
- Anything else → ask the user how to proceed
3. **Create a working branch** off the default branch:
- `git checkout main && git pull --ff-only`
- Branch name: <type>/$1-<slug> (e.g. `fix/42-borderColor-ignored`, `feat/57-keyboard-clear`, `docs/63-widget-lifecycle`)
4. **Do the work** based on type:
### Bug (`bug` label / `fix:` title)
- Reproduce the failure first (write a failing test if feasible) — if you cannot reproduce, comment on the issue asking for clarification and stop
- Locate the root cause; do not patch symptoms
- Add or extend a regression test that fails before and passes after the fix
- Run `go test ./... -race` and `golangci-lint run`
### Feature (`enhancement` / `feature` label / `feat:` title)
- Re-read the motivation and proposed implementation in the issue body
- For large, ambiguous, or breaking changes, sketch the design in a comment on the issue and wait for sign-off before writing code
- Implement behind sensible defaults; add godoc on every exported symbol
- Add unit tests covering the new behaviour and edge cases
- Update `README.md` / `docs/` if the public surface changed
- Run `go test ./... -race` and `golangci-lint run`
### Documentation (`documentation` label / `docs:` title)
- Open the file/URL referenced in the issue's "Documentation Location"
- Apply the suggested improvement; verify code samples compile (`go build ./...`)
- No tests required, but run `golangci-lint run` if Go files were touched
5. **Report**:
- Branch name (`git branch --show-current`)
- Summary of files changed (`git status -s`) and the diff highlights
- Test/lint results (pass/fail with key output)
- Suggest the next step explicitly:
- `/commit-push` to commit with a Conventional Commit subject (the message should reference `(#$1)` and include `Fixes #$1` so merge auto-closes)
- then `/create-pr $1` to open the pull request
## Guidelines
- This prompt **stops at a clean working tree with the change applied** — do not run `git commit`, `git push`, or `gh pr create`
- If the issue is unclear, post a clarifying comment on the issue and stop; do not guess
- Keep the change scoped to the issue; surface unrelated cleanups separately
- For breaking changes or architecture shifts, propose the design on the issue first and wait for maintainer sign-off
- If the issue is a duplicate or already fixed on `main`, comment with the reference and stop
- Do not close the issue manually — the eventual PR's `Fixes #$1` handles that on merge
+84
View File
@@ -0,0 +1,84 @@
---
description: Scaffold a new prompt template in .kit/prompts/
---
Create a new kit prompt template. The user wants a prompt that does: $@
## What a prompt template is
A prompt template is a `.md` file in `.kit/prompts/` (project-local) or `~/.kit/prompts/` (global).
It becomes a `/slug` slash command in the kit input box — typed as `/filename` with optional arguments.
## File format
```
---
description: One-line description shown in autocomplete
---
Body text of the prompt. Reference user-supplied arguments
with positional placeholders (see "Argument placeholders" below).
```
- **Filename** → slug: `commit-push.md` becomes `/commit-push`
- **Frontmatter**: only `description` is recognised; keep it under ~80 chars
- **Body**: plain markdown; the full text is submitted as the user's message when the template fires
- **Required args**: kit infers required positional args from the highest `$N` it finds *outside* backtick/tilde code fences — a stray `$2` in active prose means kit will refuse to run without 2 arguments
## Argument placeholders
kit performs shell-style substitution before sending the prompt to the model:
- `$1`, `$2`, … — positional arguments (1-indexed)
- `${1}`, `${2}`, … — same, brace form (use when followed by digits/letters: `${1}_suffix`)
- `$@` — all arguments joined by spaces (zero or more, optional)
- `$+` — all arguments, **at least one required**
- `$ARGUMENTS` / `${ARGUMENTS}` — alias for `$@`
- `${@:N}` — args from the Nth onwards (1-indexed, bash-style)
- `${@:N:L}``L` args starting from the Nth
### ⚠️ Critical: code fences and inline code preserve placeholders verbatim
Anything inside triple-backtick fences, `~~~` fences, or single-backtick `inline` code spans is **left untouched** so example code samples don't get corrupted. That means:
- An inline-coded `gh issue view $1` stays literal `$1` in the model's input ❌
- The same command without backticks: gh issue view $1 → expands to `gh issue view 42`
**Rule of thumb:** if you want a placeholder to substitute, keep it outside backticks and fences. If you want a literal `$1` in the output (e.g. teaching the user shell syntax), put it inside backticks.
### Workarounds for "I want it to look like code AND substitute"
1. **Drop the backticks** around just the placeholder portion — the rest can still read as a command line in prose
2. **Use a 4-space-indented code block** instead of a triple-backtick fence — kit only skips backtick/tilde fences, so indentation-style code blocks still get substitution:
git push -u origin "$(git branch --show-current)"
gh pr create --title "fix: ... (#$1)" --base main
3. **Bind once, reference loosely**: put `Issue: $1` at the top in prose, then leave the backticked examples literal — the model will substitute mentally
## Steps
1. **Understand the workflow** the user described in $@ — ask a clarifying question if the intent is ambiguous
2. **Choose a filename**: short, lowercase, hyphen-separated, descriptive (e.g. `code-review.md`)
3. **Write the description**: one sentence, imperative, fits in autocomplete
4. **Decide on arguments**:
- No args needed → omit placeholders entirely
- One required value (issue number, PR url, file path) → use `$1`
- Free-form trailing context → end with a single `$@` line
- Multiple distinct values → use `$1`, `$2`, … and document each at the top
5. **Draft the body**:
- Open with a single sentence stating the goal, weaving in `$1`/`$@` where the value belongs
- Use `## Steps` for multi-step workflows; use plain prose for simple prompts
- Be specific: name commands, flags, and file paths where relevant
- **Audit every backtick and code fence**: any `$N` or `$@` inside them will not expand — was that intentional? If not, apply one of the workarounds above
6. **Write the file** to `.kit/prompts/<slug>.md`
7. **Verify substitution** by mentally (or actually) replacing `$1`/`$@` with a sample value and confirming every reference resolves — and that the prompt's *own* example snippets don't accidentally bump the required-arg count (wrap illustrative `$N` examples in triple-backtick fences, not 4-space indentation, so `RequiredArgs()` ignores them)
8. **Confirm** by showing the final file content and the slash command that activates it (e.g. `/code-review 42`)
## Guidelines
- Keep prompts action-oriented — they should tell kit *what to do*, not just *what to think about*
- Prefer concrete steps over vague instructions
- A prompt that does one thing well beats one that tries to cover every edge case
- If the workflow already exists as a prompt, suggest extending it instead of duplicating
- When in doubt about substitution behaviour, write the file and run `/<slug> testvalue` once to confirm — wrong placement of backticks is the #1 failure mode
+70
View File
@@ -0,0 +1,70 @@
---
description: Semantic version tagging workflow - analyzes commits and tags releases
---
# Release Tagging Workflow
Tag a new version of this Go project following semantic versioning.
## Steps
1. **Fetch remote tags**: `git fetch --tags origin`
2. **Find latest version**: `git tag -l | sort -V | tail -5` to see recent tags
3. **Analyze changes since last tag**:
- `git log <latest-tag>..HEAD --oneline` - list commits
- `git diff <latest-tag>..HEAD --stat` - see file stats
- `git diff <latest-tag>..HEAD --name-only` - see changed files
4. **Determine version bump** (Semantic Versioning):
- **MAJOR (X.0.0)**: Breaking API changes, incompatible modifications
- **MINOR (0.X.0)**: New features, backward-compatible additions
- **PATCH (0.0.X)**: Bug fixes, backward-compatible fixes
Look for indicators:
- `feat:` or `feature:` commits → MINOR
- `fix:` or `bugfix:` commits → PATCH
- `breaking:` or `BREAKING CHANGE:` → MAJOR
- Breaking API changes in `pkg/` or public interfaces → MAJOR
- New commands, flags, or features → MINOR
- Documentation-only changes → PATCH (or skip)
5. **Calculate new version**: Increment appropriate segment, reset lower segments to 0
6. **Draft tag message**:
- Summarize key changes from commits
- Group by type (Features, Fixes, Breaking Changes)
- Keep concise but informative
7. **Create annotated tag**: `git tag -a vX.Y.Z -m "vX.Y.Z - <summary>\n\n<detailed list>"`
8. **Push tag**: `git push origin vX.Y.Z`
## Guidelines
- Always fetch remote tags first to avoid conflicts
- Use annotated tags (`-a`) with descriptive messages
- Follow semver strictly - when in doubt, prefer conservative bump (patch over minor)
- For Go projects, changes to `pkg/` or exported APIs warrant careful version consideration
- If no changes since last tag, suggest skipping the release
- Include commit summaries in the tag message body
## Example Tag Message Format
```
v0.30.1 - Bug fixes for model handling and UI improvements
Fixes:
- Properly handle think tags from Qwen/DeepSeek models
- Handle custom provider model persistence and bare model names
Improvements:
- UI style refactoring and cleanup
```
Wait for the user to confirm the version and message before executing tag commands.
---
$@
+52
View File
@@ -0,0 +1,52 @@
---
description: Audit and update project documentation (README and docs site) for a recent change
---
Review recent code changes, identify all documentation surfaces that should
mention them, and update each one — grounded in the actual diff, not guesses.
## Steps
1. **Identify the change**:
- If the user input ($@) names a commit / PR / branch / topic, use that as the focus
- Otherwise inspect `git log origin/main..HEAD --oneline` and `git diff origin/main...HEAD --stat` to discover what shipped on the current branch
- Read the actual diff (`git diff origin/main...HEAD`) — never document features that aren't in the code
2. **Inventory the doc surfaces**:
- `README.md` at the repo root
- Any docs site (commonly `www/`, `docs/`, `site/`) — list its pages and identify the one(s) most thematically related to the change
- Inline godoc / API reference comments on the new exported symbols
- `CHANGELOG.md` if the project keeps one
- Any `examples/` directory entries that demonstrate the affected area
3. **Audit each surface** with `grep`:
- Search for the names of related existing APIs (e.g. if you added `IterTools`, grep for `ListTools`) to find every page that already discusses the area
- Decide for each hit: does it need a cross-reference, a side-by-side comparison, or to stay untouched?
4. **Decide where new content lives**:
- Prefer extending an existing page over creating a new one
- For a docs site, place new sections near related content (check the page's `## Heading` outline first)
- Skip surfaces that genuinely don't apply (e.g. a server-focused README for a client-only change) and say so explicitly
5. **Draft the updates**:
- Lead with a one-sentence statement of what's new and why
- Show concrete code examples copied from real signatures — verify against the source files
- Include a comparison / "when to use which" table when adding an alternative to an existing API
- Note backwards-compatibility behaviour if relevant
6. **Verify the docs build** before committing:
- For vocs / docusaurus / mkdocs sites, run the local build command (e.g. `npx vocs build`, `mkdocs build`) and fix any MDX/markdown errors
- For godoc, run `go vet ./...` and `go doc <pkg> <Symbol>` to sanity-check rendering
7. **Report**:
- List every file changed and every file deliberately left alone (with a one-line reason)
- Suggest the next step (typically `/commit-push`) — do not auto-commit unless asked
## Guidelines
- Read the diff before writing anything — invented API names erode trust faster than missing docs
- One change per doc commit; keep doc updates separate from code changes when possible
- Match the existing voice and formatting of each surface (headings, code-fence languages, table styles)
- Prefer linking between pages over duplicating content
$@
+120
View File
@@ -0,0 +1,120 @@
# KIT Agent Guidelines
## Build/Test Commands
- **Build**: `go build -o output/kit ./cmd/kit`
- **Test all**: `go test -race ./...`
- **Test single**: `go test -race ./cmd -run TestScriptExecution`
- **Lint**: `go vet ./...`
- **Format**: `go fmt ./...`
## Code Style
- **Imports**: stdlib → third-party → local (blank lines between)
- **Naming**: camelCase (unexported), PascalCase (exported)
- **Errors**: Always check, wrap with `fmt.Errorf("context: %w", err)`
- **Logging**: Use `github.com/charmbracelet/log` structured logging
- **Types**: Prefer `any` over `interface{}`
- **JSON**: snake_case tags with `omitempty` where appropriate
- **Context**: First parameter for blocking operations
## Architecture
- Multi-provider LLM support via `llm.Provider` interface
- MCP client-server for tool integration
- Builtin servers: bash, fetch, todo, fs
- **Extension system** (`internal/extensions/`): Yaegi-interpreted Go, 13 lifecycle events, custom tools/commands/widgets/overlays/editor interceptors
- **TUI** (`internal/ui/`): Bubble Tea v2 parent-child model (`AppModel``InputComponent`, `StreamComponent`, etc.)
- **Decoupling pattern**: `cmd/root.go` has converter functions (e.g. `widgetProviderForUI()`) that bridge `internal/extensions/` types to `internal/ui/` types — the UI never imports extensions directly
- **Public SDK** (`pkg/kit/`): The public-facing Go SDK for embedding Kit as a library. See rules below.
## Public SDK (`pkg/kit/`) Rules
`pkg/kit/` is the **public API surface** consumed by external Go developers. All exported symbols, types, function names, and godoc comments in this package are part of the SDK contract.
### No Dependency Name Leakage
Internal dependency names (e.g. `charm.land/fantasy`, library-specific jargon) **must not** appear in:
- **Exported function/method names** — use generic terms (`LLM`, `Provider`, `Message`) instead of library names
- **Exported type names** — type aliases should use domain names (e.g. `LLMMessage`, not `FantasyMessage`)
- **Godoc comments** on exported symbols — these are visible in `go doc` output and pkg.go.dev
- **Struct field names and tags** on exported types
Using dependency types directly in **function bodies** (private implementation) is fine — that's invisible to SDK consumers.
### Naming Conventions for SDK Symbols
- Type aliases re-exporting dependency types: use `LLM*` prefix (e.g. `LLMMessage`, `LLMUsage`, `LLMResponse`)
- Conversion helpers: use `ConvertToLLM*` / `ConvertFromLLM*` (not the dependency name)
- Provider queries: use `GetLLMProviders` (not `GetFantasyProviders`)
- When wrapping internal methods, the `pkg/kit/` name should be dependency-agnostic even if the `internal/` method still uses the old name
### Deprecation Pattern
When renaming a public SDK symbol, keep the old name as a deprecated wrapper for one release cycle:
```go
// Deprecated: Use NewName instead.
func OldName() { return NewName() }
```
## Key Patterns
### Yaegi (Extension Interpreter) Gotchas
- **No interfaces across boundary**: All extension-facing API types must be concrete structs, never interfaces. Yaegi crashes on interface wrapper generation.
- **Function field bug**: Named function references assigned to struct fields return zero values across the interpreter boundary. Always use anonymous closure literals:
```go
// WRONG: ctx.SetEditor(ext.EditorConfig{HandleKey: myHandler})
// RIGHT: ctx.SetEditor(ext.EditorConfig{HandleKey: func(k, t string) ext.EditorKeyAction { return myHandler(k, t) }})
```
- **Symbol exports**: Every new type exposed to extensions must be added to `internal/extensions/symbols.go`
### BubbleTea Integration
- **No `prog.Send()` from inside `Update()`**: Calling `prog.Send()` synchronously within a BubbleTea `Update()` handler deadlocks the event loop. Use `go appInstance.NotifyWidgetUpdate()` (async goroutine) instead.
- **Height measurement**: `distributeHeight()` in `model.go` must measure using the same render path as `View()`. If an interceptor wraps rendering, measure with the wrapper too, or layout will mismatch.
- **Channel-based prompts**: Extension prompt calls (PromptSelect, etc.) block on a `chan PromptResponse`. Extension slash commands run in dedicated goroutines (not `tea.Cmd`) to avoid stalling BubbleTea's Cmd scheduler.
### Extension State Management
- **Thread-safe maps on Runner**: Widget/header/footer/editor state lives on the Runner with `sync.RWMutex`, queried by UI via callbacks
- **Context function fields**: The `Context` struct uses function fields (`Print func(string)`, `SetWidget func(WidgetConfig)`) wired by closures in `cmd/root.go`
- **Package-level vars in extensions**: Yaegi supports package-level variables captured in closures — this is how extensions maintain state across event callbacks
### Unicode in Widget Text
- Widget content renders through `lipgloss.Style.Render()` which preserves ANSI escape codes
- Use rune-based width calculations (`len([]rune(s))`) not byte length (`len(s)`) when aligning box-drawing characters or multi-byte symbols
## Testing
### Interactive TUI Testing with tmux
Use tmux to test Kit interactively without blocking the agent:
```bash
tmux new-session -d -s kittest -x 120 -y 40 "output/kit -e examples/extensions/my-ext.go --no-session 2>kit_stderr.log"
sleep 3
tmux capture-pane -t kittest -p # read screen
tmux send-keys -t kittest '/command' Enter # send input
tmux kill-session -t kittest # cleanup
```
### Non-Interactive Kit (Subprocess Spawning)
Extensions can spawn Kit as a subprocess for sub-agent patterns:
```bash
kit --quiet --no-session --no-extensions --system-prompt /path/to/prompt.txt --model provider/model "question"
```
Positional args are the prompt. `@file` args attach file content. Key flags: `--quiet` (stdout only, no TUI), `--no-session` (ephemeral), `--no-extensions` (prevent recursive loading), `--system-prompt` (string or file path).
## External Repo Research
- **ALWAYS use `btca`** to search external repos (e.g. iteratr, other reference codebases)
- Never guess or manually search the filesystem for external projects
- Example: `btca ask -r https://github.com/user/repo -q "How does X work?"`
- See `.agents/skills/btca-cli/SKILL.md` for full btca usage
## BTCA Configured Resources
The following external repositories are configured in `btca.config.jsonc` for research:
- bubbletea
- lipgloss
- bubbles
- glamour
- fantasy
- catwalk
- crush
- pi
- iteratr
- yaegi
- acp-go-sdk
- opencode
- herald
- herald-md
-1
View File
@@ -1 +0,0 @@
go-kit.dev
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Mark III Labs, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1078
View File
File diff suppressed because it is too large Load Diff
+145
View File
@@ -0,0 +1,145 @@
version: "3"
vars:
BINARY: kit
OUTPUT_DIR: output
BUILD_FLAGS: -o {{.OUTPUT_DIR}}/{{.BINARY}}
LDFLAGS: -s -w -X main.version=dev
tasks:
default:
desc: Show available tasks
cmds:
- task --list-all
# -----------------------------------------------------------------------
# Build
# -----------------------------------------------------------------------
build:
desc: Build the kit binary
cmds:
- go build {{.BUILD_FLAGS}} -ldflags "{{.LDFLAGS}}" ./cmd/kit
sources:
- "**/*.go"
- go.mod
- go.sum
generates:
- "{{.OUTPUT_DIR}}/{{.BINARY}}"
build-all:
desc: Build for all platforms (linux, darwin, windows)
cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-linux-amd64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=linux GOARCH=arm64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-linux-arm64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=darwin GOARCH=amd64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-darwin-amd64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=darwin GOARCH=arm64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-darwin-arm64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=windows GOARCH=amd64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-windows-amd64.exe -ldflags "{{.LDFLAGS}}" ./cmd/kit
install:
desc: Install kit to $GOPATH/bin
cmds:
- go install -ldflags "{{.LDFLAGS}}" ./cmd/kit
# -----------------------------------------------------------------------
# Test
# -----------------------------------------------------------------------
test:
desc: Run all tests with race detector
cmds:
- go test -race ./...
test-v:
desc: Run all tests (verbose)
cmds:
- go test -race -v ./...
test-short:
desc: Run tests in short mode (skip long-running tests)
cmds:
- go test -race -short ./...
test-pkg:
desc: "Run tests for a specific package (usage: task test-pkg -- ./internal/config)"
cmds:
- go test -race -v {{.CLI_ARGS}}
test-run:
desc: "Run a single test by name (usage: task test-run -- TestScriptExecution)"
cmds:
- go test -race -v ./... -run {{.CLI_ARGS}}
test-cover:
desc: Run tests with coverage report
cmds:
- mkdir -p {{.OUTPUT_DIR}}
- go test -race -coverprofile={{.OUTPUT_DIR}}/coverage.out ./...
- go tool cover -html={{.OUTPUT_DIR}}/coverage.out -o {{.OUTPUT_DIR}}/coverage.html
- echo "Coverage report written to {{.OUTPUT_DIR}}/coverage.html"
# -----------------------------------------------------------------------
# Code quality
# -----------------------------------------------------------------------
lint:
desc: Run golangci-lint and go vet
cmds:
- golangci-lint run ./...
- go vet ./...
fmt:
desc: Format all Go files
cmds:
- go fmt ./...
fmt-check:
desc: Check formatting (fails if files need formatting)
cmds:
- test -z "$(gofmt -l .)" || (echo "Files need formatting:" && gofmt -l . && exit 1)
tidy:
desc: Tidy go.mod and go.sum
cmds:
- go mod tidy
check:
desc: Run all quality checks (fmt, vet, test)
cmds:
- task: fmt-check
- task: lint
- task: test
# -----------------------------------------------------------------------
# Development
# -----------------------------------------------------------------------
dev:
desc: Build and run kit with optional args
cmds:
- task: build
- "{{.OUTPUT_DIR}}/{{.BINARY}} {{.CLI_ARGS}}"
watch:
desc: Watch for changes and rebuild (requires watchexec)
cmds:
- watchexec -e go -r -- task build
clean:
desc: Remove build artifacts
cmds:
- rm -rf {{.OUTPUT_DIR}}
# -----------------------------------------------------------------------
# Release
# -----------------------------------------------------------------------
release-snapshot:
desc: Build a release snapshot (no publish)
cmds:
- goreleaser release --snapshot --clean
release-check:
desc: Validate goreleaser config
cmds:
- goreleaser check
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
{"version":"1.5.2","languages":{"en":{"hash":"en_80f5da5232","wasm":"en","page_count":21}},"include_characters":["_","‿","⁀","⁔","︳","︴","","","","_"]}
File diff suppressed because it is too large Load Diff
-214
View File
@@ -1,214 +0,0 @@
:root {
--pagefind-ui-scale: 0.8;
--pagefind-ui-primary: #034AD8;
--pagefind-ui-fade: #707070;
--pagefind-ui-text: #393939;
--pagefind-ui-background: #ffffff;
--pagefind-ui-border: #eeeeee;
--pagefind-ui-tag: #eeeeee;
--pagefind-ui-border-width: 2px;
--pagefind-ui-border-radius: 8px;
--pagefind-ui-image-border-radius: 8px;
--pagefind-ui-image-box-ratio: 3 / 2;
--pagefind-ui-font: system, -apple-system, ".SFNSText-Regular",
"San Francisco", "Roboto", "Segoe UI", "Helvetica Neue",
"Lucida Grande", sans-serif;
}
[data-pfmod-hidden] {
display: none !important;
}
[data-pfmod-suppressed] {
opacity: 0 !important;
pointer-events: none !important;
}
[data-pfmod-sr-hidden] {
-webkit-clip: rect(0 0 0 0) !important;
clip: rect(0 0 0 0) !important;
-webkit-clip-path: inset(100%) !important;
clip-path: inset(100%) !important;
height: 1px !important;
overflow: hidden !important;
overflow: clip !important;
position: absolute !important;
white-space: nowrap !important;
width: 1px !important;
}
[data-pfmod-loading] {
color: var(--pagefind-ui-text);
background-color: var(--pagefind-ui-text);
border-radius: var(--pagefind-ui-border-radius);
opacity: 0.1;
pointer-events: none;
}
/* Input */
.pagefind-modular-input-wrapper {
position: relative;
}
.pagefind-modular-input-wrapper::before {
background-color: var(--pagefind-ui-text);
width: calc(18px * var(--pagefind-ui-scale));
height: calc(18px * var(--pagefind-ui-scale));
top: calc(23px * var(--pagefind-ui-scale));
left: calc(20px * var(--pagefind-ui-scale));
content: "";
position: absolute;
display: block;
opacity: 0.7;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");
mask-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");
-webkit-mask-size: 100%;
mask-size: 100%;
z-index: 9;
pointer-events: none;
}
.pagefind-modular-input {
height: calc(64px * var(--pagefind-ui-scale));
padding: 0 calc(70px * var(--pagefind-ui-scale)) 0 calc(54px * var(--pagefind-ui-scale));
background-color: var(--pagefind-ui-background);
border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);
border-radius: var(--pagefind-ui-border-radius);
font-size: calc(21px * var(--pagefind-ui-scale));
position: relative;
appearance: none;
-webkit-appearance: none;
display: flex;
width: 100%;
box-sizing: border-box;
font-weight: 700;
}
.pagefind-modular-input::placeholder {
opacity: 0.2;
}
.pagefind-modular-input-clear {
position: absolute;
top: calc(2px * var(--pagefind-ui-scale));
right: calc(2px * var(--pagefind-ui-scale));
height: calc(60px * var(--pagefind-ui-scale));
border-radius: var(--pagefind-ui-border-radius);
padding: 0 calc(15px * var(--pagefind-ui-scale)) 0 calc(2px * var(--pagefind-ui-scale));
color: var(--pagefind-ui-text);
font-size: calc(14px * var(--pagefind-ui-scale));
cursor: pointer;
background-color: var(--pagefind-ui-background);
border: none;
appearance: none;
}
/* ResultList */
.pagefind-modular-list-result {
list-style-type: none;
display: flex;
align-items: flex-start;
gap: min(calc(40px * var(--pagefind-ui-scale)), 3%);
padding: calc(30px * var(--pagefind-ui-scale)) 0 calc(40px * var(--pagefind-ui-scale));
border-top: solid var(--pagefind-ui-border-width) var(--pagefind-ui-border);
}
.pagefind-modular-list-result:last-of-type {
border-bottom: solid var(--pagefind-ui-border-width) var(--pagefind-ui-border);
}
.pagefind-modular-list-thumb {
width: min(30%,
calc((30% - (100px * var(--pagefind-ui-scale))) * 100000));
max-width: calc(120px * var(--pagefind-ui-scale));
margin-top: calc(10px * var(--pagefind-ui-scale));
aspect-ratio: var(--pagefind-ui-image-box-ratio);
position: relative;
}
.pagefind-modular-list-image {
display: block;
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 0;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
border-radius: var(--pagefind-ui-image-border-radius);
}
.pagefind-modular-list-inner {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
margin-top: calc(10px * var(--pagefind-ui-scale));
}
.pagefind-modular-list-title {
display: inline-block;
font-weight: 700;
font-size: calc(21px * var(--pagefind-ui-scale));
margin-top: 0;
margin-bottom: 0;
}
.pagefind-modular-list-link {
color: var(--pagefind-ui-text);
text-decoration: none;
}
.pagefind-modular-list-link:hover {
text-decoration: underline;
}
.pagefind-modular-list-excerpt {
display: inline-block;
font-weight: 400;
font-size: calc(16px * var(--pagefind-ui-scale));
margin-top: calc(4px * var(--pagefind-ui-scale));
margin-bottom: 0;
min-width: calc(250px * var(--pagefind-ui-scale));
}
/* FilterPills */
.pagefind-modular-filter-pills-wrapper {
overflow-x: scroll;
padding: 15px 0;
}
.pagefind-modular-filter-pills {
display: flex;
gap: 6px;
}
.pagefind-modular-filter-pill {
display: flex;
justify-content: center;
align-items: center;
border: none;
appearance: none;
padding: 0 calc(24px * var(--pagefind-ui-scale));
background-color: var(--pagefind-ui-background);
color: var(--pagefind-ui-fade);
border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);
border-radius: calc(25px * var(--pagefind-ui-scale));
font-size: calc(18px * var(--pagefind-ui-scale));
height: calc(50px * var(--pagefind-ui-scale));
cursor: pointer;
white-space: nowrap;
}
.pagefind-modular-filter-pill:hover {
border-color: var(--pagefind-ui-primary);
}
.pagefind-modular-filter-pill[aria-pressed="true"] {
border-color: var(--pagefind-ui-primary);
color: var(--pagefind-ui-primary);
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
-428
View File
@@ -1,428 +0,0 @@
const e={frontmatter:{title:"Commands",description:"Complete reference for all Kit CLI subcommands.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#commands"><span class="icon icon-link"></span></a>Commands</h1>
<h2 id="authentication"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#authentication"><span class="icon icon-link"></span></a>Authentication</h2>
<p>For OAuth-enabled providers like Anthropic.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Start OAuth flow (e.g., anthropic)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] --set-default </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Set provider's default model as system default</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> logout</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Remove credentials for provider</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Check authentication status</span></span></code></pre>
<h2 id="model-database"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-database"><span class="icon icon-link"></span></a>Model database</h2>
<p>Manage the local model database that maps provider names to API configurations.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># List available models (optionally filter by provider)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --all</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Show all providers (not just LLM-compatible)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> update-models</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [source] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Update model database</span></span></code></pre>
<p>The <code>update-models</code> command accepts an optional source argument:</p>
<ul>
<li><em>(none)</em> — update from <a href="https://models.dev">models.dev</a></li>
<li>A URL — fetch from a custom endpoint</li>
<li>A file path — load from a local file</li>
<li><code>embedded</code> — reset to the bundled database</li>
</ul>
<h2 id="extension-management"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-management"><span class="icon icon-link"></span></a>Extension management</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # List discovered extensions</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> validate</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Validate extension files</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Generate example extension template</span></span></code></pre>
<h3 id="installing-extensions-from-git"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installing-extensions-from-git"><span class="icon icon-link"></span></a>Installing extensions from git</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">git-ur</span><span style="color:#24292E;--shiki-dark:#E1E4E8">l</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install extensions from git repositories</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">git-ur</span><span style="color:#24292E;--shiki-dark:#E1E4E8">l</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install to project-local .kit/git/ directory</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">git-ur</span><span style="color:#24292E;--shiki-dark:#E1E4E8">l</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Update an already-installed package</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --uninstall</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">pk</span><span style="color:#24292E;--shiki-dark:#E1E4E8">g</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Remove an installed package</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --all</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install all extensions without prompting</span></span></code></pre>
<h2 id="skills"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#skills"><span class="icon icon-link"></span></a>Skills</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> skill</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install the Kit extensions skill via skills.sh</span></span></code></pre>
<h3 id="skills-cli-flags"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#skills-cli-flags"><span class="icon icon-link"></span></a>Skills CLI flags</h3>
<p>Control which skills are loaded at startup:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Load a specific skill file</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skill</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/skill.md</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Load multiple skill files or directories (flag is repeatable)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skill</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./skill1.md</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skill</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./skill2.md</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Load all skills from a custom directory instead of the default locations</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skills-dir</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /path/to/skills</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Disable all skill loading (auto-discovery and explicit)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-skills</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span></code></pre>
<p>Skills are auto-discovered from <code>~/.config/kit/skills/</code>, <code>.kit/skills/</code>, and <code>.agents/skills/</code> by default. Use <code>--skills-dir</code> to override the project-local search root, or <code>--skill</code> to load files explicitly (which disables auto-discovery). <code>--no-skills</code> suppresses all skill loading regardless of other flags.</p>
<h2 id="interactive-slash-commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#interactive-slash-commands"><span class="icon icon-link"></span></a>Interactive slash commands</h2>
<p>These commands are available inside the Kit TUI during an interactive session:</p>
<table>
<thead>
<tr>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/help</code></td>
<td>Show available commands</td>
</tr>
<tr>
<td><code>/tools</code></td>
<td>List available MCP tools</td>
</tr>
<tr>
<td><code>/servers</code></td>
<td>Show connected MCP servers</td>
</tr>
<tr>
<td><code>/model [name]</code></td>
<td>Switch model or open model selector</td>
</tr>
<tr>
<td><code>/theme [name]</code></td>
<td>Switch color theme or list available themes</td>
</tr>
<tr>
<td><code>/thinking [level]</code></td>
<td>Set thinking level (off, none, minimal, low, medium, high)</td>
</tr>
<tr>
<td><code>/compact [focus]</code></td>
<td>Summarize older messages to free context</td>
</tr>
<tr>
<td><code>/clear</code></td>
<td>Clear conversation</td>
</tr>
<tr>
<td><code>/clear-queue</code></td>
<td>Clear queued messages</td>
</tr>
<tr>
<td><code>/usage</code></td>
<td>Show token usage</td>
</tr>
<tr>
<td><code>/reset-usage</code></td>
<td>Reset usage statistics</td>
</tr>
<tr>
<td><code>/tree</code></td>
<td>Navigate session tree</td>
</tr>
<tr>
<td><code>/fork</code></td>
<td>Fork to new session from an earlier message</td>
</tr>
<tr>
<td><code>/new</code></td>
<td>Start a new session (creates new session file)</td>
</tr>
<tr>
<td><code>/name [name]</code></td>
<td>Set or show session display name</td>
</tr>
<tr>
<td><code>/resume</code></td>
<td>Open session picker to switch sessions (alias: <code>/r</code>)</td>
</tr>
<tr>
<td><code>/session</code></td>
<td>Show session info</td>
</tr>
<tr>
<td><code>/export [path]</code></td>
<td>Export session as JSONL (default: auto-generated path)</td>
</tr>
<tr>
<td><code>/import &lt;path&gt;</code></td>
<td>Import a session from a JSONL file</td>
</tr>
<tr>
<td><code>/share</code></td>
<td>Upload session to GitHub Gist and get a shareable viewer URL</td>
</tr>
<tr>
<td><code>/quit</code></td>
<td>Exit Kit</td>
</tr>
</tbody>
</table>
<h3 id="prompt-history"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#prompt-history"><span class="icon icon-link"></span></a>Prompt history</h3>
<p>Use <strong>↑</strong> and <strong>↓</strong> arrow keys to navigate through previously submitted prompts. Kit keeps the last 100 entries. Consecutive duplicates are skipped.</p>
<h3 id="cancelling-operations"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#cancelling-operations"><span class="icon icon-link"></span></a>Cancelling operations</h3>
<p>Press <strong>ESC twice</strong> to cancel the current operation:</p>
<ul>
<li>During a tool call: rolls back the entire turn to maintain API message pairing</li>
<li>During streaming: stops the response generation</li>
</ul>
<p>This ensures that <code>tool_use</code> and <code>tool_result</code> messages are always sent to the API as matched pairs, avoiding errors from orphaned tool calls.</p>
<h3 id="external-editor"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#external-editor"><span class="icon icon-link"></span></a>External editor</h3>
<p>Press <strong>Ctrl+X e</strong> to open your <code>$VISUAL</code> or <code>$EDITOR</code> 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., <code>:cq</code> in Vim), the original input is preserved.</p>
<h3 id="mid-turn-steering"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#mid-turn-steering"><span class="icon icon-link"></span></a>Mid-turn steering</h3>
<p>Press <strong>Ctrl+X s</strong> 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:</p>
<ul>
<li>Works during streaming output</li>
<li>Sends a steering instruction as a system message</li>
<li>Model continues from the interruption point with the new guidance</li>
</ul>
<p>Example: While the model is writing code, press Ctrl+X s and type "Use async/await instead" to change the implementation approach.</p>
<h3 id="image-attachments"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#image-attachments"><span class="icon icon-link"></span></a>Image attachments</h3>
<p>Attach images to your next prompt straight from the clipboard:</p>
<ul>
<li>Copy an image (e.g. a screenshot) to the system clipboard, then press <strong>Ctrl+V</strong> in the input to attach it.</li>
<li>Press <strong>Ctrl+U</strong> to clear all pending image attachments.</li>
<li>Attachments are sent alongside your text when you submit, and cleared afterward.</li>
</ul>
<p>When a terminal supports color, Kit renders a small low-resolution <strong>thumbnail preview</strong> of each pending image directly in the input, below the <code>[N image(s) attached]</code> indicator, so you can confirm the right image was attached before sending.</p>
<p>The preview is drawn with Unicode half-block characters and ordinary terminal colors — not a graphics protocol — so it renders correctly inside terminal multiplexers like <strong>tmux</strong> and <strong>zellij</strong>. Thumbnails are capped to a small cell box for a glanceable, low-res look.</p>
<ul>
<li>Best fidelity needs a <strong>truecolor</strong> terminal (<code>COLORTERM=truecolor</code>); Kit degrades to 256-color where truecolor is unavailable.</li>
<li>On terminals with neither, the preview is skipped and the <code>[N image(s) attached]</code> text indicator is shown alone.</li>
</ul>
<p>You can also attach image files by referencing them with <code>@path/to/image.png</code> — binary files are auto-detected by MIME type. See <a href="/quick-start">Quick Start</a> for the <code>@</code> attachment syntax.</p>
<h2 id="prompt-templates"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#prompt-templates"><span class="icon icon-link"></span></a>Prompt templates</h2>
<h3 id="creating-templates"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#creating-templates"><span class="icon icon-link"></span></a>Creating templates</h3>
<p>Templates use YAML frontmatter for metadata and support argument placeholders:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">---</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">description</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">Review code for issues</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">---</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Review the following code for bugs and security issues.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Focus on $1 specifically.</span></span></code></pre>
<p>Save to <code>~/.kit/prompts/review.md</code> or <code>.kit/prompts/review.md</code>.</p>
<h3 id="using-templates"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#using-templates"><span class="icon icon-link"></span></a>Using templates</h3>
<p>Templates appear as slash commands:</p>
<pre><code>/review error handling
</code></pre>
<h3 id="argument-placeholders"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#argument-placeholders"><span class="icon icon-link"></span></a>Argument placeholders</h3>
<table>
<thead>
<tr>
<th>Placeholder</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$1</code>, <code>$2</code>, etc.</td>
<td>Individual arguments by position</td>
</tr>
<tr>
<td><code>$@</code>, <code>$ARGUMENTS</code></td>
<td>All arguments joined with spaces (zero or more)</td>
</tr>
<tr>
<td><code>$+</code></td>
<td>All arguments joined with spaces (one or more required)</td>
</tr>
<tr>
<td><code>\${@:N}</code></td>
<td>Arguments from position N onwards</td>
</tr>
<tr>
<td><code>\${@:N:L}</code></td>
<td>L arguments starting at position N</td>
</tr>
</tbody>
</table>
<p>Placeholders inside fenced code blocks (<code>\`\`\`</code>) and inline code spans are ignored, so documentation examples won't be substituted.</p>
<h3 id="cli-flags"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#cli-flags"><span class="icon icon-link"></span></a>CLI flags</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Load a specific template by name</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --prompt-template</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> review</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Disable template loading</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-prompt-templates</span></span></code></pre>
<h2 id="acp-server"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#acp-server"><span class="icon icon-link"></span></a>ACP server</h2>
<p>Run Kit as an <a href="https://agentclientprotocol.com">ACP (Agent Client Protocol)</a> agent server. ACP-compatible clients communicate with Kit over JSON-RPC 2.0 on stdin/stdout.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Start as ACP agent</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --debug</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # With debug logging to stderr</span></span></code></pre>`,headings:[{depth:2,text:"Authentication",id:"authentication"},{depth:2,text:"Model database",id:"model-database"},{depth:2,text:"Extension management",id:"extension-management"},{depth:3,text:"Installing extensions from git",id:"installing-extensions-from-git"},{depth:2,text:"Skills",id:"skills"},{depth:3,text:"Skills CLI flags",id:"skills-cli-flags"},{depth:2,text:"Interactive slash commands",id:"interactive-slash-commands"},{depth:3,text:"Prompt history",id:"prompt-history"},{depth:3,text:"Cancelling operations",id:"cancelling-operations"},{depth:3,text:"External editor",id:"external-editor"},{depth:3,text:"Mid-turn steering",id:"mid-turn-steering"},{depth:3,text:"Image attachments",id:"image-attachments"},{depth:2,text:"Prompt templates",id:"prompt-templates"},{depth:3,text:"Creating templates",id:"creating-templates"},{depth:3,text:"Using templates",id:"using-templates"},{depth:3,text:"Argument placeholders",id:"argument-placeholders"},{depth:3,text:"CLI flags",id:"cli-flags"},{depth:2,text:"ACP server",id:"acp-server"}],raw:`
# Commands
## Authentication
For OAuth-enabled providers like Anthropic.
\`\`\`bash
kit auth login [provider] # Start OAuth flow (e.g., anthropic)
kit auth login [provider] --set-default # Set provider's default model as system default
kit auth logout [provider] # Remove credentials for provider
kit auth status # Check authentication status
\`\`\`
## Model database
Manage the local model database that maps provider names to API configurations.
\`\`\`bash
kit models [provider] # List available models (optionally filter by provider)
kit models --all # Show all providers (not just LLM-compatible)
kit update-models [source] # Update model database
\`\`\`
The \`update-models\` command accepts an optional source argument:
- *(none)* — update from [models.dev](https://models.dev)
- A URL — fetch from a custom endpoint
- A file path — load from a local file
- \`embedded\` — reset to the bundled database
## Extension management
\`\`\`bash
kit extensions list # List discovered extensions
kit extensions validate # Validate extension files
kit extensions init # Generate example extension template
\`\`\`
### Installing extensions from git
\`\`\`bash
kit install <git-url> # Install extensions from git repositories
kit install -l <git-url> # Install to project-local .kit/git/ directory
kit install -u <git-url> # Update an already-installed package
kit install --uninstall <pkg> # Remove an installed package
kit install --all # Install all extensions without prompting
\`\`\`
## Skills
\`\`\`bash
kit skill # Install the Kit extensions skill via skills.sh
\`\`\`
### Skills CLI flags
Control which skills are loaded at startup:
\`\`\`bash
# Load a specific skill file
kit --skill path/to/skill.md "prompt"
# Load multiple skill files or directories (flag is repeatable)
kit --skill ./skill1.md --skill ./skill2.md "prompt"
# Load all skills from a custom directory instead of the default locations
kit --skills-dir /path/to/skills "prompt"
# Disable all skill loading (auto-discovery and explicit)
kit --no-skills "prompt"
\`\`\`
Skills are auto-discovered from \`~/.config/kit/skills/\`, \`.kit/skills/\`, and \`.agents/skills/\` by default. Use \`--skills-dir\` to override the project-local search root, or \`--skill\` to load files explicitly (which disables auto-discovery). \`--no-skills\` suppresses all skill loading regardless of other flags.
## Interactive slash commands
These commands are available inside the Kit TUI during an interactive session:
| Command | Description |
|---------|-------------|
| \`/help\` | Show available commands |
| \`/tools\` | List available MCP tools |
| \`/servers\` | Show connected MCP servers |
| \`/model [name]\` | Switch model or open model selector |
| \`/theme [name]\` | Switch color theme or list available themes |
| \`/thinking [level]\` | Set thinking level (off, none, minimal, low, medium, high) |
| \`/compact [focus]\` | Summarize older messages to free context |
| \`/clear\` | Clear conversation |
| \`/clear-queue\` | Clear queued messages |
| \`/usage\` | Show token usage |
| \`/reset-usage\` | Reset usage statistics |
| \`/tree\` | Navigate session tree |
| \`/fork\` | Fork to new session from an earlier message |
| \`/new\` | Start a new session (creates new session file) |
| \`/name [name]\` | Set or show session display name |
| \`/resume\` | Open session picker to switch sessions (alias: \`/r\`) |
| \`/session\` | Show session info |
| \`/export [path]\` | Export session as JSONL (default: auto-generated path) |
| \`/import <path>\` | Import a session from a JSONL file |
| \`/share\` | Upload session to GitHub Gist and get a shareable viewer URL |
| \`/quit\` | Exit Kit |
### Prompt history
Use **↑** and **↓** arrow keys to navigate through previously submitted prompts. Kit keeps the last 100 entries. Consecutive duplicates are skipped.
### Cancelling operations
Press **ESC twice** to cancel the current operation:
- During a tool call: rolls back the entire turn to maintain API message pairing
- During streaming: stops the response generation
This ensures that \`tool_use\` and \`tool_result\` messages are always sent to the API as matched pairs, avoiding errors from orphaned tool calls.
### External editor
Press **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.
### Mid-turn steering
Press **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:
- Works during streaming output
- Sends a steering instruction as a system message
- Model continues from the interruption point with the new guidance
Example: While the model is writing code, press Ctrl+X s and type "Use async/await instead" to change the implementation approach.
### Image attachments
Attach images to your next prompt straight from the clipboard:
- Copy an image (e.g. a screenshot) to the system clipboard, then press **Ctrl+V** in the input to attach it.
- Press **Ctrl+U** to clear all pending image attachments.
- Attachments are sent alongside your text when you submit, and cleared afterward.
When 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.
The 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.
- Best fidelity needs a **truecolor** terminal (\`COLORTERM=truecolor\`); Kit degrades to 256-color where truecolor is unavailable.
- On terminals with neither, the preview is skipped and the \`[N image(s) attached]\` text indicator is shown alone.
You 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.
## Prompt templates
### Creating templates
Templates use YAML frontmatter for metadata and support argument placeholders:
\`\`\`markdown
---
description: Review code for issues
---
Review the following code for bugs and security issues.
Focus on $1 specifically.
\`\`\`
Save to \`~/.kit/prompts/review.md\` or \`.kit/prompts/review.md\`.
### Using templates
Templates appear as slash commands:
\`\`\`
/review error handling
\`\`\`
### Argument placeholders
| Placeholder | Description |
|-------------|-------------|
| \`$1\`, \`$2\`, etc. | Individual arguments by position |
| \`$@\`, \`$ARGUMENTS\` | All arguments joined with spaces (zero or more) |
| \`$+\` | All arguments joined with spaces (one or more required) |
| \`\${@:N}\` | Arguments from position N onwards |
| \`\${@:N:L}\` | L arguments starting at position N |
Placeholders inside fenced code blocks (\`\` \`\`\` \`\`) and inline code spans are ignored, so documentation examples won't be substituted.
### CLI flags
\`\`\`bash
# Load a specific template by name
kit --prompt-template review
# Disable template loading
kit --no-prompt-templates
\`\`\`
## ACP server
Run 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.
\`\`\`bash
kit acp # Start as ACP agent
kit acp --debug # With debug logging to stderr
\`\`\`
`};export{e as default};
File diff suppressed because one or more lines are too long
-138
View File
@@ -1,138 +0,0 @@
const e={frontmatter:{title:"Development",description:"Build, test, and contribute to Kit.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="development"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#development"><span class="icon icon-link"></span></a>Development</h1>
<h2 id="build-and-test"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#build-and-test"><span class="icon icon-link"></span></a>Build and test</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Build</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> output/kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd/kit</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Run all tests</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -race</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Run a specific test</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -race</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> TestScriptExecution</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Lint</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> vet</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Format</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> fmt</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./...</span></span></code></pre>
<h2 id="project-structure"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#project-structure"><span class="icon icon-link"></span></a>Project structure</h2>
<pre><code>cmd/kit/ - CLI entry point (main.go)
cmd/ - CLI command implementations (root, auth, models, etc.)
pkg/kit/ - Go SDK for embedding Kit
internal/app/ - Application orchestrator (agent loop, message store, queue)
internal/agent/ - Agent execution and tool dispatch
internal/auth/ - OAuth authentication and credential storage
internal/acpserver/ - ACP (Agent Client Protocol) server
internal/clipboard/ - Cross-platform clipboard operations
internal/compaction/ - Conversation compaction and summarization
internal/config/ - Configuration management
internal/core/ - Built-in tools (bash with sudo password prompt, read, write, edit, grep, find, ls)
internal/extensions/ - Yaegi extension system
internal/kitsetup/ - Initial setup wizard
internal/message/ - Message content types and structured content blocks
internal/models/ - Provider and model management
internal/session/ - Session persistence (tree-based JSONL)
internal/skills/ - Skill loading and system prompt composition
internal/tools/ - MCP tool integration
internal/ui/ - Bubble Tea TUI components
examples/extensions/ - Example extension files
npm/ - NPM package wrapper for distribution
</code></pre>
<h2 id="architecture-overview"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#architecture-overview"><span class="icon icon-link"></span></a>Architecture overview</h2>
<p>Kit is built around a few key architectural patterns:</p>
<h3 id="multi-provider-llm-support"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#multi-provider-llm-support"><span class="icon icon-link"></span></a>Multi-provider LLM support</h3>
<p>The <code>llm.Provider</code> interface abstracts different LLM providers. Each provider implements message formatting, tool calling, and streaming for its specific API.</p>
<h3 id="mcp-client-server-model"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#mcp-client-server-model"><span class="icon icon-link"></span></a>MCP client-server model</h3>
<p>External tools are integrated via the Model Context Protocol (MCP). Kit acts as an MCP client, connecting to MCP servers configured in <code>.kit.yml</code>.</p>
<h3 id="extension-system"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-system"><span class="icon icon-link"></span></a>Extension system</h3>
<p>Extensions are Go source files interpreted at runtime by Yaegi. The <code>internal/extensions/</code> package manages loading, symbol export, and lifecycle dispatch. See the <a href="/extensions/overview">Extension System</a> docs for details.</p>
<h3 id="tui-architecture"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#tui-architecture"><span class="icon icon-link"></span></a>TUI architecture</h3>
<p>The interactive terminal UI is built with <a href="https://github.com/charmbracelet/bubbletea">Bubble Tea v2</a>, using a parent-child model where <code>AppModel</code> manages child components (<code>InputComponent</code>, <code>StreamComponent</code>, etc.).</p>
<h3 id="decoupling-pattern"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#decoupling-pattern"><span class="icon icon-link"></span></a>Decoupling pattern</h3>
<p><code>cmd/root.go</code> contains converter functions (e.g., <code>widgetProviderForUI()</code>) that bridge <code>internal/extensions/</code> types to <code>internal/ui/</code> types. The UI never imports the extensions package directly.</p>
<h2 id="contributing"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#contributing"><span class="icon icon-link"></span></a>Contributing</h2>
<p>Contributions are welcome! Please see the <a href="https://github.com/mark3labs/kit/blob/master/contribute/contribute.md">contribution guide</a> for guidelines.</p>
<h2 id="community"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#community"><span class="icon icon-link"></span></a>Community</h2>
<ul>
<li><a href="https://discord.gg/RqSS2NQVsY">Discord</a></li>
<li><a href="https://github.com/mark3labs/kit/issues">GitHub Issues</a></li>
</ul>`,headings:[{depth:2,text:"Build and test",id:"build-and-test"},{depth:2,text:"Project structure",id:"project-structure"},{depth:2,text:"Architecture overview",id:"architecture-overview"},{depth:3,text:"Multi-provider LLM support",id:"multi-provider-llm-support"},{depth:3,text:"MCP client-server model",id:"mcp-client-server-model"},{depth:3,text:"Extension system",id:"extension-system"},{depth:3,text:"TUI architecture",id:"tui-architecture"},{depth:3,text:"Decoupling pattern",id:"decoupling-pattern"},{depth:2,text:"Contributing",id:"contributing"},{depth:2,text:"Community",id:"community"}],raw:`
# Development
## Build and test
\`\`\`bash
# Build
go build -o output/kit ./cmd/kit
# Run all tests
go test -race ./...
# Run a specific test
go test -race ./cmd -run TestScriptExecution
# Lint
go vet ./...
# Format
go fmt ./...
\`\`\`
## Project structure
\`\`\`
cmd/kit/ - CLI entry point (main.go)
cmd/ - CLI command implementations (root, auth, models, etc.)
pkg/kit/ - Go SDK for embedding Kit
internal/app/ - Application orchestrator (agent loop, message store, queue)
internal/agent/ - Agent execution and tool dispatch
internal/auth/ - OAuth authentication and credential storage
internal/acpserver/ - ACP (Agent Client Protocol) server
internal/clipboard/ - Cross-platform clipboard operations
internal/compaction/ - Conversation compaction and summarization
internal/config/ - Configuration management
internal/core/ - Built-in tools (bash with sudo password prompt, read, write, edit, grep, find, ls)
internal/extensions/ - Yaegi extension system
internal/kitsetup/ - Initial setup wizard
internal/message/ - Message content types and structured content blocks
internal/models/ - Provider and model management
internal/session/ - Session persistence (tree-based JSONL)
internal/skills/ - Skill loading and system prompt composition
internal/tools/ - MCP tool integration
internal/ui/ - Bubble Tea TUI components
examples/extensions/ - Example extension files
npm/ - NPM package wrapper for distribution
\`\`\`
## Architecture overview
Kit is built around a few key architectural patterns:
### Multi-provider LLM support
The \`llm.Provider\` interface abstracts different LLM providers. Each provider implements message formatting, tool calling, and streaming for its specific API.
### MCP client-server model
External tools are integrated via the Model Context Protocol (MCP). Kit acts as an MCP client, connecting to MCP servers configured in \`.kit.yml\`.
### Extension system
Extensions 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.
### TUI architecture
The 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.).
### Decoupling pattern
\`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.
## Contributing
Contributions are welcome! Please see the [contribution guide](https://github.com/mark3labs/kit/blob/master/contribute/contribute.md) for guidelines.
## Community
- [Discord](https://discord.gg/RqSS2NQVsY)
- [GitHub Issues](https://github.com/mark3labs/kit/issues)
`};export{e as default};
File diff suppressed because one or more lines are too long
-292
View File
@@ -1,292 +0,0 @@
const t={frontmatter:{title:"Global Flags",description:"Complete reference for all Kit CLI flags.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="global-flags"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#global-flags"><span class="icon icon-link"></span></a>Global Flags</h1>
<p>All flags can be passed to the root <code>kit</code> command.</p>
<h2 id="model-and-provider"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-and-provider"><span class="icon icon-link"></span></a>Model and provider</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--model</code></td>
<td><code>-m</code></td>
<td><code>anthropic/claude-sonnet-latest</code></td>
<td>Model to use (provider/model format)</td>
</tr>
<tr>
<td><code>--provider-api-key</code></td>
<td>—</td>
<td>—</td>
<td>API key for the provider</td>
</tr>
<tr>
<td><code>--provider-url</code></td>
<td>—</td>
<td>—</td>
<td>Base URL for provider API</td>
</tr>
<tr>
<td><code>--tls-skip-verify</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Skip TLS certificate verification</td>
</tr>
</tbody>
</table>
<h2 id="session-management"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-management"><span class="icon icon-link"></span></a>Session management</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--session</code></td>
<td><code>-s</code></td>
<td>—</td>
<td>Open specific JSONL session file</td>
</tr>
<tr>
<td><code>--continue</code></td>
<td><code>-c</code></td>
<td><code>false</code></td>
<td>Resume most recent session for current directory</td>
</tr>
<tr>
<td><code>--resume</code></td>
<td><code>-r</code></td>
<td><code>false</code></td>
<td>Interactive session picker</td>
</tr>
<tr>
<td><code>--no-session</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Ephemeral mode, no persistence</td>
</tr>
</tbody>
</table>
<h2 id="behavior"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#behavior"><span class="icon icon-link"></span></a>Behavior</h2>
<p>These flags control Kit's behavior. When a prompt is passed as a positional argument, Kit runs in non-interactive mode.</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--quiet</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Suppress all output (non-interactive only)</td>
</tr>
<tr>
<td><code>--json</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Output response as JSON (non-interactive only)</td>
</tr>
<tr>
<td><code>--no-exit</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Enter interactive mode after prompt completes</td>
</tr>
<tr>
<td><code>--max-steps</code></td>
<td>—</td>
<td><code>0</code></td>
<td>Maximum agent steps (0 for unlimited)</td>
</tr>
<tr>
<td><code>--stream</code></td>
<td>—</td>
<td><code>true</code></td>
<td>Enable streaming output</td>
</tr>
<tr>
<td><code>--compact</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Enable compact output mode</td>
</tr>
<tr>
<td><code>--auto-compact</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Auto-compact conversation near context limit</td>
</tr>
</tbody>
</table>
<h2 id="extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extensions"><span class="icon icon-link"></span></a>Extensions</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--extension</code></td>
<td><code>-e</code></td>
<td>—</td>
<td>Load additional extension file(s) (repeatable)</td>
</tr>
<tr>
<td><code>--no-extensions</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Disable all extensions</td>
</tr>
<tr>
<td><code>--prompt-template</code></td>
<td>—</td>
<td>—</td>
<td>Load a specific prompt template by name</td>
</tr>
<tr>
<td><code>--no-prompt-templates</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Disable prompt template loading</td>
</tr>
</tbody>
</table>
<h2 id="skills"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#skills"><span class="icon icon-link"></span></a>Skills</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--skill</code></td>
<td>—</td>
<td>—</td>
<td>Load skill file or directory (repeatable)</td>
</tr>
<tr>
<td><code>--skills-dir</code></td>
<td>—</td>
<td>—</td>
<td>Override the project-local skills directory for auto-discovery</td>
</tr>
<tr>
<td><code>--no-skills</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Disable skill loading (auto-discovery and explicit)</td>
</tr>
</tbody>
</table>
<h2 id="generation-parameters"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#generation-parameters"><span class="icon icon-link"></span></a>Generation parameters</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--max-tokens</code></td>
<td>—</td>
<td><code>8192</code></td>
<td>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.</td>
</tr>
<tr>
<td><code>--temperature</code></td>
<td>—</td>
<td><code>0.7</code></td>
<td>Randomness 0.01.0</td>
</tr>
<tr>
<td><code>--top-p</code></td>
<td>—</td>
<td><code>0.95</code></td>
<td>Nucleus sampling 0.01.0</td>
</tr>
<tr>
<td><code>--top-k</code></td>
<td>—</td>
<td><code>40</code></td>
<td>Limit top K tokens</td>
</tr>
<tr>
<td><code>--stop-sequences</code></td>
<td>—</td>
<td>—</td>
<td>Custom stop sequences (comma-separated)</td>
</tr>
<tr>
<td><code>--frequency-penalty</code></td>
<td>—</td>
<td><code>0.0</code></td>
<td>Penalize frequent tokens (0.02.0)</td>
</tr>
<tr>
<td><code>--presence-penalty</code></td>
<td>—</td>
<td><code>0.0</code></td>
<td>Penalize present tokens (0.02.0)</td>
</tr>
<tr>
<td><code>--thinking-level</code></td>
<td>—</td>
<td><code>off</code></td>
<td>Extended thinking level: off, none, minimal, low, medium, high</td>
</tr>
</tbody>
</table>
<h2 id="system"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#system"><span class="icon icon-link"></span></a>System</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--config</code></td>
<td>—</td>
<td><code>~/.kit.yml</code></td>
<td>Config file path</td>
</tr>
<tr>
<td><code>--system-prompt</code></td>
<td>—</td>
<td>—</td>
<td>System prompt text or file path</td>
</tr>
<tr>
<td><code>--debug</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Enable debug logging</td>
</tr>
</tbody>
</table>`,headings:[{depth:2,text:"Model and provider",id:"model-and-provider"},{depth:2,text:"Session management",id:"session-management"},{depth:2,text:"Behavior",id:"behavior"},{depth:2,text:"Extensions",id:"extensions"},{depth:2,text:"Skills",id:"skills"},{depth:2,text:"Generation parameters",id:"generation-parameters"},{depth:2,text:"System",id:"system"}],raw:"\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## Skills\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--skill` | — | — | Load skill file or directory (repeatable) |\n| `--skills-dir` | — | — | Override the project-local skills directory for auto-discovery |\n| `--no-skills` | — | `false` | Disable skill loading (auto-discovery and explicit) |\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.01.0 |\n| `--top-p` | — | `0.95` | Nucleus sampling 0.01.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.02.0) |\n| `--presence-penalty` | — | `0.0` | Penalize present tokens (0.02.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"};export{t as default};
-1
View File
@@ -1 +0,0 @@
:root,:root[data-theme=dark],:root[data-theme=light],html,html[data-theme=dark],#tome-root,#tome-root *,[data-theme],body,div{--bg: #08080a !important;--sf: #0e0e12 !important;--sfH: #141418 !important;--bd: #1a1a22 !important;--tx: #e8e0e0 !important;--tx2: #8a8090 !important;--txM: #6a6070 !important;--ac: #e03030 !important;--acD: rgba(224, 48, 48, .12) !important;--acT: #ff4444 !important;--cdBg: #0a0a0e !important;--cdTx: #c8a0a0 !important;--sbBg: #0a0a0d !important;--hdBg: rgba(8, 8, 10, .92) !important}h1,h2,h3,h4,h5,h6{color:#e8e0e0!important;font-style:normal!important}
-77
View File
@@ -1,77 +0,0 @@
const t={frontmatter:{title:"Kit",description:"Kit is a powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.",hidden:!1,toc:!1,draft:!1},html:`<div style="text-align: center; margin: 2rem 0;">
<img src="/logo.jpg" alt="KIT" style="max-width: 400px; width: 100%; margin: 0 auto; display: block;">
</div>
<p>A powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.</p>
<h2 id="features"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#features"><span class="icon icon-link"></span></a>Features</h2>
<ul>
<li><strong>Multi-Provider LLM Support</strong> — Anthropic, OpenAI, Google Gemini, Ollama, Azure OpenAI, AWS Bedrock, OpenRouter, and more</li>
<li><strong>Built-in Core Tools</strong> — bash (with interactive sudo password prompt), read, write, edit, grep, find, ls, subagent with no MCP overhead</li>
<li><strong>Smart @ Attachments</strong> — Binary files auto-detected via MIME type, MCP resources via <code>@mcp:server:uri</code></li>
<li><strong>MCP Integration</strong> — Connect external MCP servers for expanded capabilities (tools, prompts, and resources)</li>
<li><strong>Extension System</strong> — Write custom tools, commands, widgets, and UI modifications in Go</li>
<li><strong>Interactive TUI</strong> — Rich terminal interface powered by Bubble Tea with streaming, syntax highlighting, and custom rendering</li>
<li><strong>Session Management</strong> — Tree-based conversation history with branching support</li>
<li><strong>Non-Interactive Mode</strong> — Script-friendly positional args with JSON output</li>
<li><strong>ACP Server</strong> — Run Kit as an <a href="https://agentclientprotocol.com">Agent Client Protocol</a> agent over stdio</li>
<li><strong>Go SDK</strong> — Embed Kit in your own applications</li>
</ul>
<h2 id="quick-links"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#quick-links"><span class="icon icon-link"></span></a>Quick links</h2>
<table>
<thead>
<tr>
<th>Resource</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="/installation">Installation</a></td>
<td>Get Kit up and running</td>
</tr>
<tr>
<td><a href="/quick-start">Quick Start</a></td>
<td>Your first Kit session</td>
</tr>
<tr>
<td><a href="/configuration">Configuration</a></td>
<td>Customize Kit for your workflow</td>
</tr>
<tr>
<td><a href="/extensions/overview">Extensions</a></td>
<td>Build custom tools and UI components</td>
</tr>
<tr>
<td><a href="/sdk/overview">Go SDK</a></td>
<td>Embed Kit in your applications</td>
</tr>
</tbody>
</table>`,headings:[{depth:2,text:"Features",id:"features"},{depth:2,text:"Quick links",id:"quick-links"}],raw:`
<div style="text-align: center; margin: 2rem 0;">
<img src="/logo.jpg" alt="KIT" style="max-width: 400px; width: 100%; margin: 0 auto; display: block;" />
</div>
A powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.
## Features
- **Multi-Provider LLM Support** — Anthropic, OpenAI, Google Gemini, Ollama, Azure OpenAI, AWS Bedrock, OpenRouter, and more
- **Built-in Core Tools** — bash (with interactive sudo password prompt), read, write, edit, grep, find, ls, subagent with no MCP overhead
- **Smart @ Attachments** — Binary files auto-detected via MIME type, MCP resources via \`@mcp:server:uri\`
- **MCP Integration** — Connect external MCP servers for expanded capabilities (tools, prompts, and resources)
- **Extension System** — Write custom tools, commands, widgets, and UI modifications in Go
- **Interactive TUI** — Rich terminal interface powered by Bubble Tea with streaming, syntax highlighting, and custom rendering
- **Session Management** — Tree-based conversation history with branching support
- **Non-Interactive Mode** — Script-friendly positional args with JSON output
- **ACP Server** — Run Kit as an [Agent Client Protocol](https://agentclientprotocol.com) agent over stdio
- **Go SDK** — Embed Kit in your own applications
## Quick links
| Resource | Description |
|----------|-------------|
| [Installation](/installation) | Get Kit up and running |
| [Quick Start](/quick-start) | Your first Kit session |
| [Configuration](/configuration) | Customize Kit for your workflow |
| [Extensions](/extensions/overview) | Build custom tools and UI components |
| [Go SDK](/sdk/overview) | Embed Kit in your applications |
`};export{t as default};
File diff suppressed because one or more lines are too long
-88
View File
@@ -1,88 +0,0 @@
const s={frontmatter:{title:"Installation",description:"Install Kit using npm, bun, pnpm, Go, or build from source.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="installation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installation"><span class="icon icon-link"></span></a>Installation</h1>
<h2 id="using-npm--bun--pnpm"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#using-npm--bun--pnpm"><span class="icon icon-link"></span></a>Using npm / bun / pnpm</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mark3labs/kit</span></span></code></pre>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">bun</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mark3labs/kit</span></span></code></pre>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">pnpm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mark3labs/kit</span></span></code></pre>
<h2 id="using-go"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#using-go"><span class="icon icon-link"></span></a>Using Go</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> github.com/mark3labs/kit/cmd/kit@latest</span></span></code></pre>
<h2 id="building-from-source"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#building-from-source"><span class="icon icon-link"></span></a>Building from source</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/mark3labs/kit.git</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kit</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd/kit</span></span></code></pre>
<h2 id="verifying-the-installation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#verifying-the-installation"><span class="icon icon-link"></span></a>Verifying the installation</h2>
<p>After installing, verify Kit is available:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --help</span></span></code></pre>
<h2 id="setting-up-a-provider"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#setting-up-a-provider"><span class="icon icon-link"></span></a>Setting up a provider</h2>
<p>Kit needs at least one LLM provider configured. Set an API key for your preferred provider:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Anthropic (default provider)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ANTHROPIC_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># OpenAI</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> OPENAI_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Google Gemini</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> GOOGLE_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span></span></code></pre>
<p>For OAuth-enabled providers like Anthropic, you can also authenticate interactively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic</span></span></code></pre>
<p>See <a href="/providers">Providers</a> for the full list of supported providers and their configuration.</p>`,headings:[{depth:2,text:"Using npm / bun / pnpm",id:"using-npm--bun--pnpm"},{depth:2,text:"Using Go",id:"using-go"},{depth:2,text:"Building from source",id:"building-from-source"},{depth:2,text:"Verifying the installation",id:"verifying-the-installation"},{depth:2,text:"Setting up a provider",id:"setting-up-a-provider"}],raw:`
# Installation
## Using npm / bun / pnpm
\`\`\`bash
npm install -g @mark3labs/kit
\`\`\`
\`\`\`bash
bun install -g @mark3labs/kit
\`\`\`
\`\`\`bash
pnpm install -g @mark3labs/kit
\`\`\`
## Using Go
\`\`\`bash
go install github.com/mark3labs/kit/cmd/kit@latest
\`\`\`
## Building from source
\`\`\`bash
git clone https://github.com/mark3labs/kit.git
cd kit
go build -o kit ./cmd/kit
\`\`\`
## Verifying the installation
After installing, verify Kit is available:
\`\`\`bash
kit --help
\`\`\`
## Setting up a provider
Kit needs at least one LLM provider configured. Set an API key for your preferred provider:
\`\`\`bash
# Anthropic (default provider)
export ANTHROPIC_API_KEY="sk-..."
# OpenAI
export OPENAI_API_KEY="sk-..."
# Google Gemini
export GOOGLE_API_KEY="..."
\`\`\`
For OAuth-enabled providers like Anthropic, you can also authenticate interactively:
\`\`\`bash
kit auth login anthropic
\`\`\`
See [Providers](/providers) for the full list of supported providers and their configuration.
`};export{s as default};
-240
View File
@@ -1,240 +0,0 @@
const s={frontmatter:{title:"JSON Output",description:"Machine-readable JSON output for scripting and automation.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="json-output"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#json-output"><span class="icon icon-link"></span></a>JSON Output</h1>
<p>Use the <code>--json</code> flag to get structured output for scripting and automation:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Explain main.go"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<h2 id="response-format"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#response-format"><span class="icon icon-link"></span></a>Response format</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "response"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Final assistant response text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "model"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-haiku-latest"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "stop_reason"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"end_turn"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "session_id"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"a1b2c3d4e5f6"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "usage"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "input_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1024</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "output_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">512</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "total_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1536</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "cache_read_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "cache_creation_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "messages"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "role"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"assistant"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "parts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"data"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tool_call"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"data"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"name"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"args"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tool_result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"data"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"name"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="fields"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#fields"><span class="icon icon-link"></span></a>Fields</h2>
<h3 id="top-level"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#top-level"><span class="icon icon-link"></span></a>Top-level</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>response</code></td>
<td>string</td>
<td>The final assistant response text</td>
</tr>
<tr>
<td><code>model</code></td>
<td>string</td>
<td>The model that was used</td>
</tr>
<tr>
<td><code>stop_reason</code></td>
<td>string</td>
<td>Why the model stopped (e.g., <code>end_turn</code>)</td>
</tr>
<tr>
<td><code>session_id</code></td>
<td>string</td>
<td>Session identifier (omitted in <code>--no-session</code> mode)</td>
</tr>
<tr>
<td><code>usage</code></td>
<td>object</td>
<td>Token usage statistics</td>
</tr>
<tr>
<td><code>messages</code></td>
<td>array</td>
<td>Full conversation history</td>
</tr>
</tbody>
</table>
<h3 id="usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#usage"><span class="icon icon-link"></span></a>Usage</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>input_tokens</code></td>
<td>int</td>
<td>Tokens sent to the model</td>
</tr>
<tr>
<td><code>output_tokens</code></td>
<td>int</td>
<td>Tokens generated by the model</td>
</tr>
<tr>
<td><code>total_tokens</code></td>
<td>int</td>
<td>Sum of input and output tokens</td>
</tr>
<tr>
<td><code>cache_read_tokens</code></td>
<td>int</td>
<td>Tokens read from prompt cache</td>
</tr>
<tr>
<td><code>cache_creation_tokens</code></td>
<td>int</td>
<td>Tokens written to prompt cache</td>
</tr>
</tbody>
</table>
<h3 id="message-parts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#message-parts"><span class="icon icon-link"></span></a>Message parts</h3>
<p>Each message contains a <code>parts</code> array with typed entries:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>text</code></td>
<td>Assistant text content</td>
</tr>
<tr>
<td><code>tool_call</code></td>
<td>Tool invocation with name and args</td>
</tr>
<tr>
<td><code>tool_result</code></td>
<td>Tool execution result</td>
</tr>
<tr>
<td><code>reasoning</code></td>
<td>Extended thinking content</td>
</tr>
<tr>
<td><code>finish</code></td>
<td>End-of-turn marker</td>
</tr>
</tbody>
</table>
<h2 id="parsing-in-scripts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#parsing-in-scripts"><span class="icon icon-link"></span></a>Parsing in scripts</h2>
<h3 id="bash--jq"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#bash--jq"><span class="icon icon-link"></span></a>bash + jq</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Count files"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">response</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$result</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> jq</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -r</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '.response'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">tokens</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$result</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> jq</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '.usage.total_tokens'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h3 id="go-sdk"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#go-sdk"><span class="icon icon-link"></span></a>Go SDK</h3>
<p>For Go programs, use the SDK's <code>PromptResult</code> method instead of parsing JSON:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">PromptResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Count files"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result.Response)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result.Usage.TotalTokens)</span></span></code></pre>`,headings:[{depth:2,text:"Response format",id:"response-format"},{depth:2,text:"Fields",id:"fields"},{depth:3,text:"Top-level",id:"top-level"},{depth:3,text:"Usage",id:"usage"},{depth:3,text:"Message parts",id:"message-parts"},{depth:2,text:"Parsing in scripts",id:"parsing-in-scripts"},{depth:3,text:"bash + jq",id:"bash--jq"},{depth:3,text:"Go SDK",id:"go-sdk"}],raw:`
# JSON Output
Use the \`--json\` flag to get structured output for scripting and automation:
\`\`\`bash
kit "Explain main.go" --json --quiet --no-session
\`\`\`
## Response format
\`\`\`json
{
"response": "Final assistant response text",
"model": "anthropic/claude-haiku-latest",
"stop_reason": "end_turn",
"session_id": "a1b2c3d4e5f6",
"usage": {
"input_tokens": 1024,
"output_tokens": 512,
"total_tokens": 1536,
"cache_read_tokens": 0,
"cache_creation_tokens": 0
},
"messages": [
{
"role": "assistant",
"parts": [
{"type": "text", "data": "..."},
{"type": "tool_call", "data": {"name": "...", "args": "..."}},
{"type": "tool_result", "data": {"name": "...", "result": "..."}}
]
}
]
}
\`\`\`
## Fields
### Top-level
| Field | Type | Description |
|-------|------|-------------|
| \`response\` | string | The final assistant response text |
| \`model\` | string | The model that was used |
| \`stop_reason\` | string | Why the model stopped (e.g., \`end_turn\`) |
| \`session_id\` | string | Session identifier (omitted in \`--no-session\` mode) |
| \`usage\` | object | Token usage statistics |
| \`messages\` | array | Full conversation history |
### Usage
| Field | Type | Description |
|-------|------|-------------|
| \`input_tokens\` | int | Tokens sent to the model |
| \`output_tokens\` | int | Tokens generated by the model |
| \`total_tokens\` | int | Sum of input and output tokens |
| \`cache_read_tokens\` | int | Tokens read from prompt cache |
| \`cache_creation_tokens\` | int | Tokens written to prompt cache |
### Message parts
Each message contains a \`parts\` array with typed entries:
| Type | Description |
|------|-------------|
| \`text\` | Assistant text content |
| \`tool_call\` | Tool invocation with name and args |
| \`tool_result\` | Tool execution result |
| \`reasoning\` | Extended thinking content |
| \`finish\` | End-of-turn marker |
## Parsing in scripts
### bash + jq
\`\`\`bash
result=$(kit "Count files" --json --quiet --no-session)
response=$(echo "$result" | jq -r '.response')
tokens=$(echo "$result" | jq '.usage.total_tokens')
\`\`\`
### Go SDK
For Go programs, use the SDK's \`PromptResult\` method instead of parsing JSON:
\`\`\`go
result, err := host.PromptResult(ctx, "Count files")
fmt.Println(result.Response)
fmt.Println(result.Usage.TotalTokens)
\`\`\`
`};export{s as default};
-213
View File
@@ -1,213 +0,0 @@
const s={frontmatter:{title:"Loading Extensions",description:"How Kit discovers and loads extensions.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="loading-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#loading-extensions"><span class="icon icon-link"></span></a>Loading Extensions</h1>
<h2 id="auto-discovery"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#auto-discovery"><span class="icon icon-link"></span></a>Auto-discovery</h2>
<p>Kit automatically discovers and loads extensions from these paths, in order:</p>
<table>
<thead>
<tr>
<th>Path</th>
<th>Scope</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>~/.config/kit/extensions/*.go</code></td>
<td>Global single files</td>
</tr>
<tr>
<td><code>~/.config/kit/extensions/*/main.go</code></td>
<td>Global subdirectory extensions</td>
</tr>
<tr>
<td><code>.kit/extensions/*.go</code></td>
<td>Project-local single files</td>
</tr>
<tr>
<td><code>.kit/extensions/*/main.go</code></td>
<td>Project-local subdirectory extensions</td>
</tr>
<tr>
<td><code>~/.local/share/kit/git/</code></td>
<td>Global git-installed packages</td>
</tr>
<tr>
<td><code>.kit/git/</code></td>
<td>Project-local git-installed packages</td>
</tr>
</tbody>
</table>
<h2 id="explicit-loading"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#explicit-loading"><span class="icon icon-link"></span></a>Explicit loading</h2>
<p>Load extensions by path using the <code>-e</code> flag:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/extension.go</span></span></code></pre>
<p>Load multiple extensions:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ext1.go</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ext2.go</span></span></code></pre>
<h2 id="disabling-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#disabling-extensions"><span class="icon icon-link"></span></a>Disabling extensions</h2>
<p>Disable all auto-discovered extensions:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-extensions</span></span></code></pre>
<p>You can combine <code>--no-extensions</code> with <code>-e</code> to load only specific extensions:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-extensions</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> my-extension.go</span></span></code></pre>
<h2 id="installing-from-git"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installing-from-git"><span class="icon icon-link"></span></a>Installing from git</h2>
<p>Install extensions from git repositories using <code>kit install</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Install globally (to ~/.local/share/kit/git/)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/user/my-kit-extension.git</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Install project-locally (to .kit/git/)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/user/my-kit-extension.git</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Update an installed package</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/user/my-kit-extension.git</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Remove</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --uninstall</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> my-kit-extension</span></span></code></pre>
<h2 id="extension-structure"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-structure"><span class="icon icon-link"></span></a>Extension structure</h2>
<h3 id="single-file-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#single-file-extensions"><span class="icon icon-link"></span></a>Single-file extensions</h3>
<p>A single <code>.go</code> file with an <code>Init</code> function:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">//go:build ignore</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit/ext</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // register handlers, tools, commands, etc.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>The <code>//go:build ignore</code> directive prevents the Go toolchain from trying to compile the file as part of a normal build.</p>
<h3 id="subdirectory-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#subdirectory-extensions"><span class="icon icon-link"></span></a>Subdirectory extensions</h3>
<p>For more complex extensions, create a directory with a <code>main.go</code> entry point:</p>
<pre><code>.kit/extensions/my-extension/
├── main.go # Must contain Init(api ext.API)
├── helpers.go # Additional source files
└── config.go
</code></pre>
<h3 id="package-level-state"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#package-level-state"><span class="icon icon-link"></span></a>Package-level state</h3>
<p>Yaegi supports package-level variables captured in closures. This is the standard way to maintain state across event callbacks:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit/ext</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> callCount </span><span style="color:#D73A49;--shiki-dark:#F97583">int</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnToolCall</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> callCount</span><span style="color:#D73A49;--shiki-dark:#F97583">++</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetFooter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">HeaderFooterConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Content: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WidgetContent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Text: fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sprintf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Tools called: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, callCount),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>`,headings:[{depth:2,text:"Auto-discovery",id:"auto-discovery"},{depth:2,text:"Explicit loading",id:"explicit-loading"},{depth:2,text:"Disabling extensions",id:"disabling-extensions"},{depth:2,text:"Installing from git",id:"installing-from-git"},{depth:2,text:"Extension structure",id:"extension-structure"},{depth:3,text:"Single-file extensions",id:"single-file-extensions"},{depth:3,text:"Subdirectory extensions",id:"subdirectory-extensions"},{depth:3,text:"Package-level state",id:"package-level-state"}],raw:`
# Loading Extensions
## Auto-discovery
Kit automatically discovers and loads extensions from these paths, in order:
| Path | Scope |
|------|-------|
| \`~/.config/kit/extensions/*.go\` | Global single files |
| \`~/.config/kit/extensions/*/main.go\` | Global subdirectory extensions |
| \`.kit/extensions/*.go\` | Project-local single files |
| \`.kit/extensions/*/main.go\` | Project-local subdirectory extensions |
| \`~/.local/share/kit/git/\` | Global git-installed packages |
| \`.kit/git/\` | Project-local git-installed packages |
## Explicit loading
Load extensions by path using the \`-e\` flag:
\`\`\`bash
kit -e path/to/extension.go
\`\`\`
Load multiple extensions:
\`\`\`bash
kit -e ext1.go -e ext2.go
\`\`\`
## Disabling extensions
Disable all auto-discovered extensions:
\`\`\`bash
kit --no-extensions
\`\`\`
You can combine \`--no-extensions\` with \`-e\` to load only specific extensions:
\`\`\`bash
kit --no-extensions -e my-extension.go
\`\`\`
## Installing from git
Install extensions from git repositories using \`kit install\`:
\`\`\`bash
# Install globally (to ~/.local/share/kit/git/)
kit install https://github.com/user/my-kit-extension.git
# Install project-locally (to .kit/git/)
kit install -l https://github.com/user/my-kit-extension.git
# Update an installed package
kit install -u https://github.com/user/my-kit-extension.git
# Remove
kit install --uninstall my-kit-extension
\`\`\`
## Extension structure
### Single-file extensions
A single \`.go\` file with an \`Init\` function:
\`\`\`go
//go:build ignore
package main
import "kit/ext"
func Init(api ext.API) {
// register handlers, tools, commands, etc.
}
\`\`\`
The \`//go:build ignore\` directive prevents the Go toolchain from trying to compile the file as part of a normal build.
### Subdirectory extensions
For more complex extensions, create a directory with a \`main.go\` entry point:
\`\`\`
.kit/extensions/my-extension/
├── main.go # Must contain Init(api ext.API)
├── helpers.go # Additional source files
└── config.go
\`\`\`
### Package-level state
Yaegi supports package-level variables captured in closures. This is the standard way to maintain state across event callbacks:
\`\`\`go
package main
import "kit/ext"
var callCount int
func Init(api ext.API) {
api.OnToolCall(func(_ ext.ToolCallEvent, ctx ext.Context) {
callCount++
ctx.SetFooter(ext.HeaderFooterConfig{
Content: ext.WidgetContent{
Text: fmt.Sprintf("Tools called: %d", callCount),
},
})
})
}
\`\`\`
`};export{s as default};
File diff suppressed because one or more lines are too long
-56
View File
@@ -1,56 +0,0 @@
const e={frontmatter:{title:"Extension System",description:"Overview of Kit's Go-based extension system.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="extension-system"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-system"><span class="icon icon-link"></span></a>Extension System</h1>
<p>Extensions are Go source files interpreted at runtime via <a href="https://github.com/traefik/yaegi">Yaegi</a>. They can add custom tools, slash commands, widgets, keyboard shortcuts, and intercept lifecycle events — all without recompiling Kit.</p>
<h2 id="minimal-extension"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#minimal-extension"><span class="icon icon-link"></span></a>Minimal extension</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">//go:build ignore</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit/ext</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSessionStart</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetFooter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">HeaderFooterConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Content: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WidgetContent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Text: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Custom Footer"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>Run it with:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> examples/extensions/minimal.go</span></span></code></pre>
<h2 id="how-extensions-work"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#how-extensions-work"><span class="icon icon-link"></span></a>How extensions work</h2>
<ol>
<li>Kit discovers extension files from <a href="/extensions/loading">auto-discovery paths</a> or explicit <code>-e</code> flags</li>
<li>Each <code>.go</code> file is loaded into a Yaegi interpreter with access to the <code>kit/ext</code> package</li>
<li>Kit calls the <code>Init(api ext.API)</code> function in each extension</li>
<li>The extension registers callbacks, tools, commands, and UI components via the <code>api</code> and <code>ctx</code> objects</li>
</ol>
<h2 id="key-concepts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#key-concepts"><span class="icon icon-link"></span></a>Key concepts</h2>
<h3 id="the-api-object"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#the-api-object"><span class="icon icon-link"></span></a>The <code>API</code> object</h3>
<p>Passed to <code>Init()</code>, the <code>API</code> object is used to register lifecycle event handlers and static components:</p>
<ul>
<li><strong>Lifecycle handlers</strong> — <code>api.OnSessionStart(...)</code>, <code>api.OnToolCall(...)</code>, etc.</li>
<li><strong>Tools</strong> — <code>api.RegisterTool(ext.ToolDef{...})</code></li>
<li><strong>Commands</strong> — <code>api.RegisterCommand(ext.CommandDef{...})</code></li>
<li><strong>Shortcuts</strong> — <code>api.RegisterShortcut(ext.ShortcutDef{...}, handler)</code></li>
<li><strong>Tool renderers</strong> — <code>api.RegisterToolRenderer(ext.ToolRenderConfig{...})</code></li>
<li><strong>Message renderers</strong> — <code>api.RegisterMessageRenderer(ext.MessageRendererConfig{...})</code></li>
<li><strong>Options</strong> — <code>api.RegisterOption(ext.OptionDef{...})</code></li>
</ul>
<h3 id="the-context-object"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#the-context-object"><span class="icon icon-link"></span></a>The <code>Context</code> object</h3>
<p>Passed to event handlers, the <code>Context</code> object provides runtime access to Kit's state and UI:</p>
<ul>
<li><strong>Output</strong> — <code>ctx.Print(...)</code>, <code>ctx.PrintInfo(...)</code>, <code>ctx.PrintError(...)</code></li>
<li><strong>UI components</strong> — <code>ctx.SetWidget(...)</code>, <code>ctx.SetHeader(...)</code>, <code>ctx.SetFooter(...)</code>, <code>ctx.SetStatus(...)</code></li>
<li><strong>Editor</strong> — <code>ctx.SetEditor(...)</code>, <code>ctx.ResetEditor()</code></li>
<li><strong>Prompts</strong> — <code>ctx.PromptSelect(...)</code>, <code>ctx.PromptConfirm(...)</code>, <code>ctx.PromptInput(...)</code></li>
<li><strong>Overlays</strong> — <code>ctx.ShowOverlay(...)</code></li>
<li><strong>Messages</strong> — <code>ctx.SendMessage(...)</code>, <code>ctx.GetMessages()</code></li>
<li><strong>Model</strong> — <code>ctx.SetModel(...)</code>, <code>ctx.GetAvailableModels()</code></li>
<li><strong>Tools</strong> — <code>ctx.GetAllTools()</code>, <code>ctx.SetActiveTools(...)</code></li>
<li><strong>Context stats</strong> — <code>ctx.GetContextStats()</code></li>
<li><strong>Session data</strong> — <code>ctx.AppendEntry(...)</code>, <code>ctx.GetEntries(...)</code> (append-only, in conversation tree)</li>
<li><strong>Session state</strong> — <code>ctx.SetState(...)</code>, <code>ctx.GetState(...)</code>, <code>ctx.DeleteState(...)</code>, <code>ctx.ListState()</code> (last-write-wins, sidecar file)</li>
<li><strong>Subagents</strong> — <code>ctx.SpawnSubagent(...)</code></li>
<li><strong>LLM completion</strong> — <code>ctx.Complete(...)</code></li>
<li><strong>Custom events</strong> — <code>ctx.EmitCustomEvent(...)</code></li>
</ul>
<p>See <a href="/extensions/capabilities">Capabilities</a> for full details on each component type, and <a href="/extensions/testing">Testing</a> for writing tests for your extensions.</p>`,headings:[{depth:2,text:"Minimal extension",id:"minimal-extension"},{depth:2,text:"How extensions work",id:"how-extensions-work"},{depth:2,text:"Key concepts",id:"key-concepts"},{depth:3,text:"The API object",id:"the-api-object"},{depth:3,text:"The Context object",id:"the-context-object"}],raw:'\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(...)` (append-only, in conversation tree)\n- **Session state** — `ctx.SetState(...)`, `ctx.GetState(...)`, `ctx.DeleteState(...)`, `ctx.ListState()` (last-write-wins, sidecar file)\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'};export{e as default};
-797
View File
@@ -1,797 +0,0 @@
const s={frontmatter:{title:"Go SDK",description:"Embed Kit in your Go applications.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="go-sdk"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#go-sdk"><span class="icon icon-link"></span></a>Go SDK</h1>
<p>The <code>pkg/kit</code> package lets you embed Kit as a library in your Go applications.</p>
<h2 id="installation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installation"><span class="icon icon-link"></span></a>Installation</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> github.com/mark3labs/kit/pkg/kit</span></span></code></pre>
<h2 id="basic-usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#basic-usage"><span class="icon icon-link"></span></a>Basic usage</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">context</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/pkg/kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> context.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Background</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Create Kit instance with default configuration</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> host, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> log.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Fatal</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(err)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> defer</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Close</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Send a prompt</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> response, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Prompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"What is 2+2?"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> log.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Fatal</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(err)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0"> println</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(response)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="functional-options-newagent"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#functional-options-newagent"><span class="icon icon-link"></span></a>Functional options (<code>NewAgent</code>)</h2>
<p>For simple programmatic setups, <code>kit.NewAgent</code> offers an ergonomic
functional-options front door over <code>kit.New</code>. Streaming is <strong>enabled by
default</strong>; pass <code>kit.WithStreaming(false)</code> to opt out.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">NewAgent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithModel</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-sonnet-4-5-20250929"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithSystemPrompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"You are a helpful assistant."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithMaxTokens</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">8192</span><span style="color:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithThinkingLevel</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"medium"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Ephemeral</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(), </span><span style="color:#6A737D;--shiki-dark:#6A737D">// in-memory session, no persistence</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> log.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Fatal</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(err)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">defer</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Close</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<p>Available options:</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Sets</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>WithModel(string)</code></td>
<td><code>Options.Model</code> (provider/model format)</td>
</tr>
<tr>
<td><code>WithSystemPrompt(string)</code></td>
<td><code>Options.SystemPrompt</code> (inline text or file path)</td>
</tr>
<tr>
<td><code>WithStreaming(bool)</code></td>
<td><code>Options.Streaming</code> (default <code>true</code> under <code>NewAgent</code>)</td>
</tr>
<tr>
<td><code>WithMaxTokens(int)</code></td>
<td><code>Options.MaxTokens</code></td>
</tr>
<tr>
<td><code>WithThinkingLevel(string)</code></td>
<td><code>Options.ThinkingLevel</code></td>
</tr>
<tr>
<td><code>WithTools(...Tool)</code></td>
<td><code>Options.Tools</code> (replaces the default set)</td>
</tr>
<tr>
<td><code>WithExtraTools(...Tool)</code></td>
<td><code>Options.ExtraTools</code> (adds alongside defaults)</td>
</tr>
<tr>
<td><code>WithProviderAPIKey(string)</code></td>
<td><code>Options.ProviderAPIKey</code></td>
</tr>
<tr>
<td><code>WithProviderURL(string)</code></td>
<td><code>Options.ProviderURL</code></td>
</tr>
<tr>
<td><code>WithConfigFile(string)</code></td>
<td><code>Options.ConfigFile</code></td>
</tr>
<tr>
<td><code>WithDebug()</code></td>
<td><code>Options.Debug = true</code></td>
</tr>
<tr>
<td><code>Ephemeral()</code></td>
<td><code>Options.NoSession = true</code></td>
</tr>
</tbody>
</table>
<p>Options are applied in order, so later options override earlier ones. <code>Option</code>
is a plain <code>func(*Options)</code>, so you can define your own. For advanced
configuration not covered by the helpers (custom MCP config, in-process MCP
servers, session backends, MCP task tuning) construct an <code>Options</code> value
explicitly and call <code>kit.New</code>.</p>
<h3 id="when-to-use-which"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#when-to-use-which"><span class="icon icon-link"></span></a>When to use which</h3>
<table>
<thead>
<tr>
<th>Constructor</th>
<th>Use when</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>kit.NewAgent(ctx, ...Option)</code></td>
<td>Quick programmatic setups; you only need the common fields. Streaming defaults on.</td>
</tr>
<tr>
<td><code>kit.New(ctx, *Options)</code></td>
<td>You need fields without a <code>With*</code> helper (<code>MCPConfig</code>, <code>InProcessMCPServers</code>, <code>SessionManager</code>, MCP task tuning, etc.), or you already hold an <code>Options</code> value.</td>
</tr>
</tbody>
</table>
<h2 id="per-instance-config-isolation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#per-instance-config-isolation"><span class="icon icon-link"></span></a>Per-instance config isolation</h2>
<p>Each <code>kit.New</code> / <code>kit.NewAgent</code> call owns an <strong>isolated configuration store</strong>,
so constructing multiple Kit instances in the same process is safe: setting the
model, thinking level, or generation parameters on one never affects another,
and runtime mutators (<code>SetModel</code>, <code>SetThinkingLevel</code>) only touch the owning
instance. This makes subagent spawning and multi-Kit embedding race-free with
no external synchronization required.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">a, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">NewAgent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithThinkingLevel</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"low"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">b, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">NewAgent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithThinkingLevel</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"high"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">a.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetThinkingLevel</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"medium"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// a.GetThinkingLevel() == "medium"; b.GetThinkingLevel() is still "high"</span></span></code></pre>
<h2 id="multi-turn-conversations"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#multi-turn-conversations"><span class="icon icon-link"></span></a>Multi-turn conversations</h2>
<p>Conversations retain context automatically across calls:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Prompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"My name is Alice"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">response, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Prompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"What's my name?"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// response: "Your name is Alice"</span></span></code></pre>
<h2 id="additional-prompt-methods"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#additional-prompt-methods"><span class="icon icon-link"></span></a>Additional prompt methods</h2>
<p>The SDK provides several prompt variants:</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Prompt(ctx, message)</code></td>
<td>Simple prompt, returns response string</td>
</tr>
<tr>
<td><code>PromptWithOptions(ctx, message, opts)</code></td>
<td>With per-call options</td>
</tr>
<tr>
<td><code>PromptResult(ctx, message)</code></td>
<td>Returns full <code>TurnResult</code> with usage stats</td>
</tr>
<tr>
<td><code>PromptResultWithFiles(ctx, message, files)</code></td>
<td>Multimodal with file attachments</td>
</tr>
<tr>
<td><code>Steer(ctx, instruction)</code></td>
<td>System-level steering without user message</td>
</tr>
<tr>
<td><code>FollowUp(ctx, text)</code></td>
<td>Continue without new user input</td>
</tr>
</tbody>
</table>
<h2 id="custom-tools"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#custom-tools"><span class="icon icon-link"></span></a>Custom tools</h2>
<p>Create custom tools with <code>kit.NewTool</code>. The JSON schema is auto-generated from the input struct — no external dependencies required:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WeatherInput</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> City </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> \`json:"city" description:"City name"\`</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">weatherTool </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">NewTool</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"get_weather"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Get current weather for a city"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">input</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WeatherInput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) (</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolOutput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">TextResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"72°F, sunny in "</span><span style="color:#D73A49;--shiki-dark:#F97583"> +</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> input.City), </span><span style="color:#005CC5;--shiki-dark:#79B8FF">nil</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ExtraTools: []</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Tool</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{weatherTool},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<p>Struct tags control the schema:</p>
<ul>
<li><code>json:"name"</code> — parameter name</li>
<li><code>description:"..."</code> — description shown to the LLM</li>
<li><code>enum:"a,b,c"</code> — restrict valid values</li>
<li><code>omitempty</code> — marks the parameter as optional</li>
</ul>
<p>Return values:</p>
<table>
<thead>
<tr>
<th>Helper</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>kit.TextResult(s)</code></td>
<td>Successful text result</td>
</tr>
<tr>
<td><code>kit.ErrorResult(s)</code></td>
<td>Error result (LLM sees it as a tool error)</td>
</tr>
<tr>
<td><code>kit.ImageResult(s, data, mediaType)</code></td>
<td>Image result with binary data (e.g. <code>"image/png"</code>)</td>
</tr>
<tr>
<td><code>kit.MediaResult(s, data, mediaType)</code></td>
<td>Non-image media result (e.g. <code>"audio/mpeg"</code>)</td>
</tr>
</tbody>
</table>
<p>Binary data (images, audio, etc.) in <code>ToolOutput.Data</code> is automatically forwarded to the LLM when <code>MediaType</code> is set. For advanced use, return a <code>kit.ToolOutput</code> struct directly with <code>Data</code>, <code>MediaType</code>, and <code>Metadata</code> fields.</p>
<p>Use <code>kit.NewParallelTool</code> for tools that are safe to run concurrently. Use <code>kit.ToolCallIDFromContext(ctx)</code> to retrieve the LLM-assigned call ID for logging or tracing.</p>
<h2 id="generation--provider-overrides"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#generation--provider-overrides"><span class="icon icon-link"></span></a>Generation &amp; provider overrides</h2>
<p>SDK consumers can configure generation parameters and provider endpoints
entirely in-code via <code>Options</code>, without touching <code>.kit.yml</code> or <code>viper.Set()</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Model: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-sonnet-4-5-20250929"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MaxTokens: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">16384</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 0 = auto-resolve (env → config → per-model → floor)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ThinkingLevel: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"high"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#6A737D;--shiki-dark:#6A737D">// "off" | "none" | "minimal" | "low" | "medium" | "high"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Temperature: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ptrFloat32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">0.2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">), </span><span style="color:#6A737D;--shiki-dark:#6A737D">// nil = provider/per-model default</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ProviderAPIKey: os.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Getenv</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"MY_SECRET"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">), </span><span style="color:#6A737D;--shiki-dark:#6A737D">// overrides pre-existing viper state</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ProviderURL: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://proxy.internal/v1"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ptrFloat32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">v</span><span style="color:#D73A49;--shiki-dark:#F97583"> float32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">*float32</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#D73A49;--shiki-dark:#F97583">return</span><span style="color:#D73A49;--shiki-dark:#F97583"> &amp;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">v }</span></span></code></pre>
<p>See <a href="/sdk/options#generation-parameters">Options</a> for the full field reference,
including <code>TopP</code>, <code>TopK</code>, <code>FrequencyPenalty</code>, <code>PresencePenalty</code>, and <code>TLSSkipVerify</code>.</p>
<h2 id="event-system"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#event-system"><span class="icon icon-link"></span></a>Event system</h2>
<p>Subscribe to events for monitoring:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">unsubscribe </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnToolCall</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">event</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Tool called:"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, event.Name)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">defer</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> unsubscribe</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnToolResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">event</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolResultEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Tool result:"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, event.Name)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnMessageUpdate</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">event</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MessageUpdateEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(event.Chunk)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h2 id="model-management"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-management"><span class="icon icon-link"></span></a>Model management</h2>
<p>Switch models at runtime:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetModel</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"openai/gpt-4o"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">info </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetModelInfo</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">models </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetAvailableModels</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<h2 id="dynamic-mcp-servers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#dynamic-mcp-servers"><span class="icon icon-link"></span></a>Dynamic MCP servers</h2>
<p>Add and remove MCP servers at runtime:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">n, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AddMCPServer</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"github"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MCPServerConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Command: []</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"npx"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"-y"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"@modelcontextprotocol/server-github"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Loaded </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> tools</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, n)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">err </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">RemoveMCPServer</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"github"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">servers </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListMCPServers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() </span><span style="color:#6A737D;--shiki-dark:#6A737D">// []kit.MCPServerStatus</span></span></code></pre>
<h3 id="in-process-mcp-servers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#in-process-mcp-servers"><span class="icon icon-link"></span></a>In-process MCP servers</h3>
<p>Register mcp-go servers running in the same process — zero subprocess overhead:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/mcp-go/mcp</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/mcp-go/server</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">mcpSrv </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> server.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">NewMCPServer</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-tools"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"1.0.0"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> server.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithToolCapabilities</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">mcpSrv.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AddTool</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(mcp.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">NewTool</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"search_docs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> mcp.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithDescription</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Search documentation"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> mcp.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WithString</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"query"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, mcp.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Required</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">), searchHandler)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// At init time</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> InProcessMCPServers: </span><span style="color:#D73A49;--shiki-dark:#F97583">map</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MCPServer</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "docs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: mcpSrv,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Or at runtime</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">n, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AddInProcessMCPServer</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"docs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, mcpSrv)</span></span></code></pre>
<h2 id="runtime-skills-and-context-files"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#runtime-skills-and-context-files"><span class="icon icon-link"></span></a>Runtime skills and context files</h2>
<p>Kit auto-discovers skills and <code>AGENTS.md</code>-style context files during <code>New()</code>,
but multi-tenant hosts (chatbots, web services, per-user agents) often need
to swap these <strong>after</strong> construction. The runtime mutators below recompose
the system prompt and apply it to the agent so the next turn picks up the
updated instructions — no restart, no file shuffling.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Add a programmatic skill — no file on disk required.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AddSkill</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Skill</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Name: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"polite-french"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Description: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Respond in French and always greet the user."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Content: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Always reply in French. Open every response with 'Bonjour'."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Or load one from disk.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadAndAddSkill</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/var/skills/refund-policy.md"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Project context (AGENTS.md equivalents): inline content from a DB...</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AddContextFileContent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sprintf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"session://</span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/AGENTS.md"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, userID),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> rulesFromDB,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// ...or load from disk.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadAndAddContextFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/etc/agents/tenant-acme.md"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Remove individually when a session ends.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">RemoveSkill</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"polite-french"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">RemoveContextFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sprintf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"session://</span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/AGENTS.md"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, userID))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Or replace the whole set in one call.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetSkills</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(activeSkillsForUser)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetContextFiles</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(activeContextForUser)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Inspect current state (snapshot copies — safe to mutate).</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">skills </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetSkills</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">ctxFiles </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetContextFiles</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<p>Key points:</p>
<ul>
<li><strong>Auto-refresh.</strong> Every <code>Add*</code> / <code>Remove*</code> / <code>Set*</code> call recomposes the system
prompt against the captured base prompt (preserving per-model overrides and
<code>--system-prompt</code> resolution) and pushes the result onto the agent. Call
<code>host.RefreshSystemPrompt()</code> only if you mutate state through a different
path and need to force a re-render.</li>
<li><strong>Dedup keys.</strong> Skills dedupe by <code>Name</code>; context files dedupe by <code>Path</code>.
Re-adding the same key replaces the entry instead of appending a duplicate.</li>
<li><strong>Path is opaque.</strong> <code>ContextFile.Path</code> does not have to point at a real file
— it's only used for dedup and for the <code>Instructions from: &lt;Path&gt;</code> header
injected into the prompt. URIs like <code>session://user-123/AGENTS.md</code> work fine.</li>
<li><strong>Thread safety.</strong> All readers and mutators are safe to call concurrently
from multiple goroutines; the underlying state is guarded by an internal
<code>RWMutex</code>.</li>
<li><strong>Init-time options still apply.</strong> <code>Options.Skills</code>, <code>Options.SkillsDir</code>,
<code>Options.NoSkills</code>, and <code>Options.NoContextFiles</code> continue to control the
startup set; the runtime API mutates from whatever state <code>New()</code> produced.
See <a href="/sdk/options#skills--configuration">SDK options</a>.</li>
</ul>
<h2 id="mcp-prompts-and-resources"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#mcp-prompts-and-resources"><span class="icon icon-link"></span></a>MCP prompts and resources</h2>
<p>Query prompts and resources exposed by connected MCP servers:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// List and expand prompts</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">prompts </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListMCPPrompts</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetMCPPrompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"server"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"prompt-name"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">map</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"key"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"value"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// List and read resources</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">resources </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListMCPResources</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">content, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ReadMCPResource</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"server"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"file:///path"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h2 id="mcp-tasks-long-running-tools"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#mcp-tasks-long-running-tools"><span class="icon icon-link"></span></a>MCP tasks (long-running tools)</h2>
<p>Kit advertises <a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks">MCP task support</a>
during <code>initialize</code>, so cooperating servers can return a <code>taskId</code> immediately
and let Kit poll <code>tasks/get</code> / <code>tasks/result</code> until the operation completes.
This avoids HTTP/SSE proxy timeouts on long tools and gives you clean
cancellation via context.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MCPTaskMode: </span><span style="color:#D73A49;--shiki-dark:#F97583">map</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MCPTaskMode</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "build-server"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: kit.MCPTaskModeAlways,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MCPTaskProgress: </span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">p</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MCPTaskProgress</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> log.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, p.TaskID, p.Status)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Inspect / cancel in-flight tasks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">tasks, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListMCPTasks</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"build-server"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">_, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">CancelMCPTask</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"build-server"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, tasks[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">].TaskID)</span></span></code></pre>
<p>Defaults to <code>MCPTaskModeAuto</code> per server, so any existing MCP server keeps
its previous synchronous behaviour. See <a href="/sdk/options#mcp-tasks">SDK options → MCP Tasks</a>
for the full surface.</p>
<h2 id="context-and-compaction"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#context-and-compaction"><span class="icon icon-link"></span></a>Context and compaction</h2>
<p>Monitor and manage context usage:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">tokens </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">EstimateContextTokens</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">stats </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetContextStats</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ShouldCompact</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Compact</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">""</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="in-process-subagents"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#in-process-subagents"><span class="icon icon-link"></span></a>In-process subagents</h2>
<p>Spawn child Kit instances without subprocess overhead:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Subagent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Prompt: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Analyze the test files"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Model: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-haiku-3-5-20241022"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> NoSession: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Timeout: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> time.Minute,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<p>See <a href="/sdk/options">Options</a>, <a href="/sdk/callbacks">Callbacks</a>, and <a href="/sdk/sessions">Sessions</a> for more details.</p>`,headings:[{depth:2,text:"Installation",id:"installation"},{depth:2,text:"Basic usage",id:"basic-usage"},{depth:2,text:"Functional options (NewAgent)",id:"functional-options-newagent"},{depth:3,text:"When to use which",id:"when-to-use-which"},{depth:2,text:"Per-instance config isolation",id:"per-instance-config-isolation"},{depth:2,text:"Multi-turn conversations",id:"multi-turn-conversations"},{depth:2,text:"Additional prompt methods",id:"additional-prompt-methods"},{depth:2,text:"Custom tools",id:"custom-tools"},{depth:2,text:"Generation &amp; provider overrides",id:"generation--provider-overrides"},{depth:2,text:"Event system",id:"event-system"},{depth:2,text:"Model management",id:"model-management"},{depth:2,text:"Dynamic MCP servers",id:"dynamic-mcp-servers"},{depth:3,text:"In-process MCP servers",id:"in-process-mcp-servers"},{depth:2,text:"Runtime skills and context files",id:"runtime-skills-and-context-files"},{depth:2,text:"MCP prompts and resources",id:"mcp-prompts-and-resources"},{depth:2,text:"MCP tasks (long-running tools)",id:"mcp-tasks-long-running-tools"},{depth:2,text:"Context and compaction",id:"context-and-compaction"},{depth:2,text:"In-process subagents",id:"in-process-subagents"}],raw:`
# Go SDK
The \`pkg/kit\` package lets you embed Kit as a library in your Go applications.
## Installation
\`\`\`bash
go get github.com/mark3labs/kit/pkg/kit
\`\`\`
## Basic usage
\`\`\`go
package main
import (
"context"
"log"
kit "github.com/mark3labs/kit/pkg/kit"
)
func main() {
ctx := context.Background()
// Create Kit instance with default configuration
host, err := kit.New(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer host.Close()
// Send a prompt
response, err := host.Prompt(ctx, "What is 2+2?")
if err != nil {
log.Fatal(err)
}
println(response)
}
\`\`\`
## Functional options (\`NewAgent\`)
For simple programmatic setups, \`kit.NewAgent\` offers an ergonomic
functional-options front door over \`kit.New\`. Streaming is **enabled by
default**; pass \`kit.WithStreaming(false)\` to opt out.
\`\`\`go
host, err := kit.NewAgent(ctx,
kit.WithModel("anthropic/claude-sonnet-4-5-20250929"),
kit.WithSystemPrompt("You are a helpful assistant."),
kit.WithMaxTokens(8192),
kit.WithThinkingLevel("medium"),
kit.Ephemeral(), // in-memory session, no persistence
)
if err != nil {
log.Fatal(err)
}
defer host.Close()
\`\`\`
Available options:
| Option | Sets |
|--------|------|
| \`WithModel(string)\` | \`Options.Model\` (provider/model format) |
| \`WithSystemPrompt(string)\` | \`Options.SystemPrompt\` (inline text or file path) |
| \`WithStreaming(bool)\` | \`Options.Streaming\` (default \`true\` under \`NewAgent\`) |
| \`WithMaxTokens(int)\` | \`Options.MaxTokens\` |
| \`WithThinkingLevel(string)\` | \`Options.ThinkingLevel\` |
| \`WithTools(...Tool)\` | \`Options.Tools\` (replaces the default set) |
| \`WithExtraTools(...Tool)\` | \`Options.ExtraTools\` (adds alongside defaults) |
| \`WithProviderAPIKey(string)\` | \`Options.ProviderAPIKey\` |
| \`WithProviderURL(string)\` | \`Options.ProviderURL\` |
| \`WithConfigFile(string)\` | \`Options.ConfigFile\` |
| \`WithDebug()\` | \`Options.Debug = true\` |
| \`Ephemeral()\` | \`Options.NoSession = true\` |
Options are applied in order, so later options override earlier ones. \`Option\`
is a plain \`func(*Options)\`, so you can define your own. For advanced
configuration not covered by the helpers (custom MCP config, in-process MCP
servers, session backends, MCP task tuning) construct an \`Options\` value
explicitly and call \`kit.New\`.
### When to use which
| Constructor | Use when |
|-------------|----------|
| \`kit.NewAgent(ctx, ...Option)\` | Quick programmatic setups; you only need the common fields. Streaming defaults on. |
| \`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. |
## Per-instance config isolation
Each \`kit.New\` / \`kit.NewAgent\` call owns an **isolated configuration store**,
so constructing multiple Kit instances in the same process is safe: setting the
model, thinking level, or generation parameters on one never affects another,
and runtime mutators (\`SetModel\`, \`SetThinkingLevel\`) only touch the owning
instance. This makes subagent spawning and multi-Kit embedding race-free with
no external synchronization required.
\`\`\`go
a, _ := kit.NewAgent(ctx, kit.WithThinkingLevel("low"))
b, _ := kit.NewAgent(ctx, kit.WithThinkingLevel("high"))
a.SetThinkingLevel(ctx, "medium")
// a.GetThinkingLevel() == "medium"; b.GetThinkingLevel() is still "high"
\`\`\`
## Multi-turn conversations
Conversations retain context automatically across calls:
\`\`\`go
host.Prompt(ctx, "My name is Alice")
response, _ := host.Prompt(ctx, "What's my name?")
// response: "Your name is Alice"
\`\`\`
## Additional prompt methods
The SDK provides several prompt variants:
| Method | Description |
|--------|-------------|
| \`Prompt(ctx, message)\` | Simple prompt, returns response string |
| \`PromptWithOptions(ctx, message, opts)\` | With per-call options |
| \`PromptResult(ctx, message)\` | Returns full \`TurnResult\` with usage stats |
| \`PromptResultWithFiles(ctx, message, files)\` | Multimodal with file attachments |
| \`Steer(ctx, instruction)\` | System-level steering without user message |
| \`FollowUp(ctx, text)\` | Continue without new user input |
## Custom tools
Create custom tools with \`kit.NewTool\`. The JSON schema is auto-generated from the input struct — no external dependencies required:
\`\`\`go
type WeatherInput struct {
City string \`json:"city" description:"City name"\`
}
weatherTool := kit.NewTool("get_weather", "Get current weather for a city",
func(ctx context.Context, input WeatherInput) (kit.ToolOutput, error) {
return kit.TextResult("72°F, sunny in " + input.City), nil
},
)
host, _ := kit.New(ctx, &kit.Options{
ExtraTools: []kit.Tool{weatherTool},
})
\`\`\`
Struct tags control the schema:
- \`json:"name"\` — parameter name
- \`description:"..."\` — description shown to the LLM
- \`enum:"a,b,c"\` — restrict valid values
- \`omitempty\` — marks the parameter as optional
Return values:
| Helper | Description |
|--------|-------------|
| \`kit.TextResult(s)\` | Successful text result |
| \`kit.ErrorResult(s)\` | Error result (LLM sees it as a tool error) |
| \`kit.ImageResult(s, data, mediaType)\` | Image result with binary data (e.g. \`"image/png"\`) |
| \`kit.MediaResult(s, data, mediaType)\` | Non-image media result (e.g. \`"audio/mpeg"\`) |
Binary 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.
Use \`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.
## Generation & provider overrides
SDK consumers can configure generation parameters and provider endpoints
entirely in-code via \`Options\`, without touching \`.kit.yml\` or \`viper.Set()\`:
\`\`\`go
host, _ := kit.New(ctx, &kit.Options{
Model: "anthropic/claude-sonnet-4-5-20250929",
MaxTokens: 16384, // 0 = auto-resolve (env → config → per-model → floor)
ThinkingLevel: "high", // "off" | "none" | "minimal" | "low" | "medium" | "high"
Temperature: ptrFloat32(0.2), // nil = provider/per-model default
ProviderAPIKey: os.Getenv("MY_SECRET"), // overrides pre-existing viper state
ProviderURL: "https://proxy.internal/v1",
})
func ptrFloat32(v float32) *float32 { return &v }
\`\`\`
See [Options](/sdk/options#generation-parameters) for the full field reference,
including \`TopP\`, \`TopK\`, \`FrequencyPenalty\`, \`PresencePenalty\`, and \`TLSSkipVerify\`.
## Event system
Subscribe to events for monitoring:
\`\`\`go
unsubscribe := host.OnToolCall(func(event kit.ToolCallEvent) {
fmt.Println("Tool called:", event.Name)
})
defer unsubscribe()
host.OnToolResult(func(event kit.ToolResultEvent) {
fmt.Println("Tool result:", event.Name)
})
host.OnMessageUpdate(func(event kit.MessageUpdateEvent) {
fmt.Print(event.Chunk)
})
\`\`\`
## Model management
Switch models at runtime:
\`\`\`go
host.SetModel(ctx, "openai/gpt-4o")
info := host.GetModelInfo()
models := host.GetAvailableModels()
\`\`\`
## Dynamic MCP servers
Add and remove MCP servers at runtime:
\`\`\`go
n, err := host.AddMCPServer(ctx, "github", kit.MCPServerConfig{
Command: []string{"npx", "-y", "@modelcontextprotocol/server-github"},
})
fmt.Printf("Loaded %d tools\\n", n)
err = host.RemoveMCPServer("github")
servers := host.ListMCPServers() // []kit.MCPServerStatus
\`\`\`
### In-process MCP servers
Register mcp-go servers running in the same process — zero subprocess overhead:
\`\`\`go
import (
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
mcpSrv := server.NewMCPServer("my-tools", "1.0.0",
server.WithToolCapabilities(true),
)
mcpSrv.AddTool(mcp.NewTool("search_docs",
mcp.WithDescription("Search documentation"),
mcp.WithString("query", mcp.Required()),
), searchHandler)
// At init time
host, _ := kit.New(ctx, &kit.Options{
InProcessMCPServers: map[string]*kit.MCPServer{
"docs": mcpSrv,
},
})
// Or at runtime
n, _ := host.AddInProcessMCPServer(ctx, "docs", mcpSrv)
\`\`\`
## Runtime skills and context files
Kit auto-discovers skills and \`AGENTS.md\`-style context files during \`New()\`,
but multi-tenant hosts (chatbots, web services, per-user agents) often need
to swap these **after** construction. The runtime mutators below recompose
the system prompt and apply it to the agent so the next turn picks up the
updated instructions — no restart, no file shuffling.
\`\`\`go
// Add a programmatic skill — no file on disk required.
host.AddSkill(&kit.Skill{
Name: "polite-french",
Description: "Respond in French and always greet the user.",
Content: "Always reply in French. Open every response with 'Bonjour'.",
})
// Or load one from disk.
host.LoadAndAddSkill("/var/skills/refund-policy.md")
// Project context (AGENTS.md equivalents): inline content from a DB...
host.AddContextFileContent(
fmt.Sprintf("session://%s/AGENTS.md", userID),
rulesFromDB,
)
// ...or load from disk.
host.LoadAndAddContextFile("/etc/agents/tenant-acme.md")
// Remove individually when a session ends.
host.RemoveSkill("polite-french")
host.RemoveContextFile(fmt.Sprintf("session://%s/AGENTS.md", userID))
// Or replace the whole set in one call.
host.SetSkills(activeSkillsForUser)
host.SetContextFiles(activeContextForUser)
// Inspect current state (snapshot copies — safe to mutate).
skills := host.GetSkills()
ctxFiles := host.GetContextFiles()
\`\`\`
Key points:
- **Auto-refresh.** Every \`Add*\` / \`Remove*\` / \`Set*\` call recomposes the system
prompt against the captured base prompt (preserving per-model overrides and
\`--system-prompt\` resolution) and pushes the result onto the agent. Call
\`host.RefreshSystemPrompt()\` only if you mutate state through a different
path and need to force a re-render.
- **Dedup keys.** Skills dedupe by \`Name\`; context files dedupe by \`Path\`.
Re-adding the same key replaces the entry instead of appending a duplicate.
- **Path is opaque.** \`ContextFile.Path\` does not have to point at a real file
— it's only used for dedup and for the \`Instructions from: <Path>\` header
injected into the prompt. URIs like \`session://user-123/AGENTS.md\` work fine.
- **Thread safety.** All readers and mutators are safe to call concurrently
from multiple goroutines; the underlying state is guarded by an internal
\`RWMutex\`.
- **Init-time options still apply.** \`Options.Skills\`, \`Options.SkillsDir\`,
\`Options.NoSkills\`, and \`Options.NoContextFiles\` continue to control the
startup set; the runtime API mutates from whatever state \`New()\` produced.
See [SDK options](/sdk/options#skills--configuration).
## MCP prompts and resources
Query prompts and resources exposed by connected MCP servers:
\`\`\`go
// List and expand prompts
prompts := host.ListMCPPrompts()
result, _ := host.GetMCPPrompt(ctx, "server", "prompt-name", map[string]string{"key": "value"})
// List and read resources
resources := host.ListMCPResources()
content, _ := host.ReadMCPResource(ctx, "server", "file:///path")
\`\`\`
## MCP tasks (long-running tools)
Kit advertises [MCP task support](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)
during \`initialize\`, so cooperating servers can return a \`taskId\` immediately
and let Kit poll \`tasks/get\` / \`tasks/result\` until the operation completes.
This avoids HTTP/SSE proxy timeouts on long tools and gives you clean
cancellation via context.
\`\`\`go
host, _ := kit.New(ctx, &kit.Options{
MCPTaskMode: map[string]kit.MCPTaskMode{
"build-server": kit.MCPTaskModeAlways,
},
MCPTaskProgress: func(p kit.MCPTaskProgress) {
log.Printf("%s: %s", p.TaskID, p.Status)
},
})
// Inspect / cancel in-flight tasks
tasks, _ := host.ListMCPTasks(ctx, "build-server")
_, _ = host.CancelMCPTask(ctx, "build-server", tasks[0].TaskID)
\`\`\`
Defaults to \`MCPTaskModeAuto\` per server, so any existing MCP server keeps
its previous synchronous behaviour. See [SDK options → MCP Tasks](/sdk/options#mcp-tasks)
for the full surface.
## Context and compaction
Monitor and manage context usage:
\`\`\`go
tokens := host.EstimateContextTokens()
stats := host.GetContextStats()
if host.ShouldCompact() {
result, err := host.Compact(ctx, nil, "")
}
\`\`\`
## In-process subagents
Spawn child Kit instances without subprocess overhead:
\`\`\`go
result, err := host.Subagent(ctx, kit.SubagentConfig{
Prompt: "Analyze the test files",
Model: "anthropic/claude-haiku-3-5-20241022",
NoSession: true,
Timeout: 2 * time.Minute,
})
\`\`\`
See [Options](/sdk/options), [Callbacks](/sdk/callbacks), and [Sessions](/sdk/sessions) for more details.
`};export{s as default};
-406
View File
@@ -1,406 +0,0 @@
const s={frontmatter:{title:"Providers",description:"Supported LLM providers and model configuration.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="providers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#providers"><span class="icon icon-link"></span></a>Providers</h1>
<p>Kit supports a wide range of LLM providers through a unified <code>provider/model</code> string format.</p>
<h2 id="supported-providers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#supported-providers"><span class="icon icon-link"></span></a>Supported providers</h2>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Prefix</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Anthropic</strong></td>
<td><code>anthropic/</code></td>
<td>Claude models (native, prompt caching, OAuth)</td>
</tr>
<tr>
<td><strong>OpenAI</strong></td>
<td><code>openai/</code></td>
<td>GPT models</td>
</tr>
<tr>
<td><strong>GitHub Copilot</strong></td>
<td><code>copilot/</code></td>
<td>Copilot models through GitHub device login (experimental)</td>
</tr>
<tr>
<td><strong>Google</strong></td>
<td><code>google/</code> or <code>gemini/</code></td>
<td>Gemini models</td>
</tr>
<tr>
<td><strong>Ollama</strong></td>
<td><code>ollama/</code></td>
<td>Local models</td>
</tr>
<tr>
<td><strong>Azure OpenAI</strong></td>
<td><code>azure/</code></td>
<td>Azure-hosted OpenAI</td>
</tr>
<tr>
<td><strong>AWS Bedrock</strong></td>
<td><code>bedrock/</code></td>
<td>Bedrock models</td>
</tr>
<tr>
<td><strong>Google Vertex</strong></td>
<td><code>google-vertex-anthropic/</code></td>
<td>Claude on Vertex AI</td>
</tr>
<tr>
<td><strong>OpenRouter</strong></td>
<td><code>openrouter/</code></td>
<td>Multi-provider router</td>
</tr>
<tr>
<td><strong>Vercel AI</strong></td>
<td><code>vercel/</code></td>
<td>Vercel AI SDK models</td>
</tr>
<tr>
<td><strong>Custom</strong></td>
<td><code>custom/</code></td>
<td>Any OpenAI-compatible endpoint</td>
</tr>
<tr>
<td><strong>Auto-routed</strong></td>
<td>any</td>
<td>Any provider from the models.dev database</td>
</tr>
</tbody>
</table>
<h2 id="model-string-format"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-string-format"><span class="icon icon-link"></span></a>Model string format</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">provider/model</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Standard format</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">anthropic/claude-sonnet-latest</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">openai/gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">copilot/gpt-5.5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">ollama/llama3</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">google/gemini-2.5-flash</span></span></code></pre>
<h2 id="model-aliases"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-aliases"><span class="icon icon-link"></span></a>Model aliases</h2>
<p>Kit provides aliases for commonly used models:</p>
<h3 id="anthropic-claude"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#anthropic-claude"><span class="icon icon-link"></span></a>Anthropic Claude</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-opus-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-opus-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-sonnet-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-haiku-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-haiku-4-5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-4-opus-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-opus-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-4-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-sonnet-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-4-haiku-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-haiku-4-5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-7-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-7-sonnet-20250219</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-5-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-5-sonnet-20241022</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-5-haiku-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-5-haiku-20241022</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-opus-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-opus-20240229</span></span></code></pre>
<h3 id="openai-gpt"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#openai-gpt"><span class="icon icon-link"></span></a>OpenAI GPT</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">o1-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> o1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">o3-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> o3</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">o4-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> o4-mini</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-5-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-5.4</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-5-chat-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-5.4</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-4-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-4</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-3.5-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-3.5-turbo</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-3.5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-3.5-turbo</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">codex-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> codex-mini-latest</span></span></code></pre>
<h3 id="google-gemini"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#google-gemini"><span class="icon icon-link"></span></a>Google Gemini</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-pro-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-pro</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-flash-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-flash</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-flash</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-flash</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-pro</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-pro</span></span></code></pre>
<h2 id="specifying-a-model"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#specifying-a-model"><span class="icon icon-link"></span></a>Specifying a model</h2>
<p>Via CLI flag:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai/gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ollama/llama3</span></span></code></pre>
<p>Via config file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">model</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">anthropic/claude-sonnet-latest</span></span></code></pre>
<p>Via environment variable:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> KIT_MODEL</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"google/gemini-2.0-flash-exp"</span></span></code></pre>
<h2 id="authentication"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#authentication"><span class="icon icon-link"></span></a>Authentication</h2>
<h3 id="api-keys"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#api-keys"><span class="icon icon-link"></span></a>API keys</h3>
<p>Set the appropriate environment variable for your provider:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ANTHROPIC_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> OPENAI_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> GOOGLE_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span></span></code></pre>
<p>Or pass it directly:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --provider-api-key</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "sk-..."</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai/gpt-4o</span></span></code></pre>
<h3 id="oauth"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#oauth"><span class="icon icon-link"></span></a>OAuth</h3>
<p>For providers that support OAuth:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Anthropic OAuth</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # ChatGPT/Codex OAuth</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> copilot</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # GitHub Copilot device login (experimental)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Check authentication status</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> logout</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> copilot</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Remove credentials</span></span></code></pre>
<p>The experimental <code>copilot/</code> provider requires an active GitHub Copilot subscription
and uses GitHub device login; no OpenAI account or OpenAI API key is required.</p>
<h3 id="custom-provider-url"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#custom-provider-url"><span class="icon icon-link"></span></a>Custom provider URL</h3>
<p>For self-hosted or proxy endpoints:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --provider-url</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "https://my-proxy.example.com/v1"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai/gpt-4o</span></span></code></pre>
<p>When <code>--provider-url</code> is set with an explicit <code>--model</code>, Kit routes through the
<code>custom</code> (OpenAI-compatible) wire and strips any provider prefix from the model
name. So <code>openai/gpt-4o</code>, <code>google/gemma-4-12b</code>, and bare <code>gpt-4o</code> all resolve
to the same endpoint — Kit treats <code>--provider-url</code> as authoritative about <em>where</em>
to send the request, and the model string as just the upstream model id.</p>
<p>This avoids name collisions when a local server (LM Studio, Ollama, vLLM, ...)
happens to expose a model whose name matches a known cloud provider.</p>
<p>When <code>--provider-url</code> is provided without <code>--model</code>, Kit automatically defaults to <code>custom/custom</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --provider-url</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "http://localhost:8080/v1"</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span></span></code></pre>
<p>The <code>custom/custom</code> model has zero cost, 262K context window, and supports reasoning. It routes through the <code>openaicompat</code> provider and accepts any OpenAI-compatible API endpoint.</p>
<p>Optionally set <code>CUSTOM_API_KEY</code> environment variable or use <code>--provider-api-key</code> for endpoints requiring authentication.</p>
<h2 id="auto-routed-providers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#auto-routed-providers"><span class="icon icon-link"></span></a>Auto-routed providers</h2>
<p>Any provider in the <a href="https://models.dev">models.dev</a> database can be used with the
standard <code>provider/model</code> format, even without a dedicated native integration. Kit
auto-routes the request through the matching <strong>wire protocol</strong> — the actual API
shape the provider speaks — rather than requiring a per-provider code path:</p>
<table>
<thead>
<tr>
<th>Wire protocol</th>
<th>npm package (models.dev)</th>
<th>Transport used</th>
</tr>
</thead>
<tbody>
<tr>
<td>OpenAI (Responses API)</td>
<td><code>@ai-sdk/openai</code></td>
<td>OpenAI</td>
</tr>
<tr>
<td>OpenAI (chat completions)</td>
<td><code>@ai-sdk/openai-compatible</code></td>
<td>OpenAI-compatible</td>
</tr>
<tr>
<td>Anthropic</td>
<td><code>@ai-sdk/anthropic</code></td>
<td>Anthropic</td>
</tr>
<tr>
<td>Google Gemini</td>
<td><code>@ai-sdk/google</code></td>
<td>Google</td>
</tr>
</tbody>
</table>
<p>The provider's <code>api</code> URL from the database is used as the base URL. A provider
whose npm package isn't recognized but that has an <code>api</code> URL falls back to the
OpenAI-compatible wire.</p>
<p>Because routing follows the wire protocol, aggregator/proxy providers work across
<strong>all</strong> of their models — including ones they re-flavor onto a different protocol
via a per-model override. For example, an aggregator that proxies Claude, GPT,
<em>and</em> Gemini routes them to the Anthropic, OpenAI, and Google transports
respectively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> opencode/claude-haiku-4-5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # → Anthropic wire</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> opencode/gpt-5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # → OpenAI wire</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> opencode/gemini-3.5-flash</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # → Google wire</span></span></code></pre>
<p>Provide the provider's API key the same way as any other — via its environment
variable (e.g. <code>OPENCODE_API_KEY</code>) or <code>--provider-api-key</code>.</p>
<h2 id="model-database"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-database"><span class="icon icon-link"></span></a>Model database</h2>
<p>Kit ships with a local model database that maps provider names to API configurations. You can manage it with:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # List available models</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Filter by provider</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --all</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Show all providers</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> update-models</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Update from models.dev</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> update-models</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> embedded</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Reset to bundled database</span></span></code></pre>`,headings:[{depth:2,text:"Supported providers",id:"supported-providers"},{depth:2,text:"Model string format",id:"model-string-format"},{depth:2,text:"Model aliases",id:"model-aliases"},{depth:3,text:"Anthropic Claude",id:"anthropic-claude"},{depth:3,text:"OpenAI GPT",id:"openai-gpt"},{depth:3,text:"Google Gemini",id:"google-gemini"},{depth:2,text:"Specifying a model",id:"specifying-a-model"},{depth:2,text:"Authentication",id:"authentication"},{depth:3,text:"API keys",id:"api-keys"},{depth:3,text:"OAuth",id:"oauth"},{depth:3,text:"Custom provider URL",id:"custom-provider-url"},{depth:2,text:"Auto-routed providers",id:"auto-routed-providers"},{depth:2,text:"Model database",id:"model-database"}],raw:`
# Providers
Kit supports a wide range of LLM providers through a unified \`provider/model\` string format.
## Supported providers
| Provider | Prefix | Description |
|----------|--------|-------------|
| **Anthropic** | \`anthropic/\` | Claude models (native, prompt caching, OAuth) |
| **OpenAI** | \`openai/\` | GPT models |
| **GitHub Copilot** | \`copilot/\` | Copilot models through GitHub device login (experimental) |
| **Google** | \`google/\` or \`gemini/\` | Gemini models |
| **Ollama** | \`ollama/\` | Local models |
| **Azure OpenAI** | \`azure/\` | Azure-hosted OpenAI |
| **AWS Bedrock** | \`bedrock/\` | Bedrock models |
| **Google Vertex** | \`google-vertex-anthropic/\` | Claude on Vertex AI |
| **OpenRouter** | \`openrouter/\` | Multi-provider router |
| **Vercel AI** | \`vercel/\` | Vercel AI SDK models |
| **Custom** | \`custom/\` | Any OpenAI-compatible endpoint |
| **Auto-routed** | any | Any provider from the models.dev database |
## Model string format
\`\`\`bash
provider/model # Standard format
anthropic/claude-sonnet-latest
openai/gpt-4o
copilot/gpt-5.5
ollama/llama3
google/gemini-2.5-flash
\`\`\`
## Model aliases
Kit provides aliases for commonly used models:
### Anthropic Claude
\`\`\`bash
claude-opus-latest → claude-opus-4-6
claude-sonnet-latest → claude-sonnet-4-6
claude-haiku-latest → claude-haiku-4-5
claude-4-opus-latest → claude-opus-4-6
claude-4-sonnet-latest → claude-sonnet-4-6
claude-4-haiku-latest → claude-haiku-4-5
claude-3-7-sonnet-latest → claude-3-7-sonnet-20250219
claude-3-5-sonnet-latest → claude-3-5-sonnet-20241022
claude-3-5-haiku-latest → claude-3-5-haiku-20241022
claude-3-opus-latest → claude-3-opus-20240229
\`\`\`
### OpenAI GPT
\`\`\`bash
o1-latest → o1
o3-latest → o3
o4-latest → o4-mini
gpt-5-latest → gpt-5.4
gpt-5-chat-latest → gpt-5.4
gpt-4-latest → gpt-4o
gpt-4 → gpt-4o
gpt-3.5-latest → gpt-3.5-turbo
gpt-3.5 → gpt-3.5-turbo
codex-latest → codex-mini-latest
\`\`\`
### Google Gemini
\`\`\`bash
gemini-pro-latest → gemini-2.5-pro
gemini-flash-latest → gemini-2.5-flash
gemini-flash → gemini-2.5-flash
gemini-pro → gemini-2.5-pro
\`\`\`
## Specifying a model
Via CLI flag:
\`\`\`bash
kit --model openai/gpt-4o
kit -m ollama/llama3
\`\`\`
Via config file:
\`\`\`yaml
model: anthropic/claude-sonnet-latest
\`\`\`
Via environment variable:
\`\`\`bash
export KIT_MODEL="google/gemini-2.0-flash-exp"
\`\`\`
## Authentication
### API keys
Set the appropriate environment variable for your provider:
\`\`\`bash
export ANTHROPIC_API_KEY="sk-..."
export OPENAI_API_KEY="sk-..."
export GOOGLE_API_KEY="..."
\`\`\`
Or pass it directly:
\`\`\`bash
kit --provider-api-key "sk-..." --model openai/gpt-4o
\`\`\`
### OAuth
For providers that support OAuth:
\`\`\`bash
kit auth login anthropic # Anthropic OAuth
kit auth login openai # ChatGPT/Codex OAuth
kit auth login copilot # GitHub Copilot device login (experimental)
kit auth status # Check authentication status
kit auth logout copilot # Remove credentials
\`\`\`
The experimental \`copilot/\` provider requires an active GitHub Copilot subscription
and uses GitHub device login; no OpenAI account or OpenAI API key is required.
### Custom provider URL
For self-hosted or proxy endpoints:
\`\`\`bash
kit --provider-url "https://my-proxy.example.com/v1" --model openai/gpt-4o
\`\`\`
When \`--provider-url\` is set with an explicit \`--model\`, Kit routes through the
\`custom\` (OpenAI-compatible) wire and strips any provider prefix from the model
name. So \`openai/gpt-4o\`, \`google/gemma-4-12b\`, and bare \`gpt-4o\` all resolve
to the same endpoint — Kit treats \`--provider-url\` as authoritative about *where*
to send the request, and the model string as just the upstream model id.
This avoids name collisions when a local server (LM Studio, Ollama, vLLM, ...)
happens to expose a model whose name matches a known cloud provider.
When \`--provider-url\` is provided without \`--model\`, Kit automatically defaults to \`custom/custom\`:
\`\`\`bash
kit --provider-url "http://localhost:8080/v1" "Hello"
\`\`\`
The \`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.
Optionally set \`CUSTOM_API_KEY\` environment variable or use \`--provider-api-key\` for endpoints requiring authentication.
## Auto-routed providers
Any provider in the [models.dev](https://models.dev) database can be used with the
standard \`provider/model\` format, even without a dedicated native integration. Kit
auto-routes the request through the matching **wire protocol** — the actual API
shape the provider speaks — rather than requiring a per-provider code path:
| Wire protocol | npm package (models.dev) | Transport used |
|---------------|--------------------------|----------------|
| OpenAI (Responses API) | \`@ai-sdk/openai\` | OpenAI |
| OpenAI (chat completions) | \`@ai-sdk/openai-compatible\` | OpenAI-compatible |
| Anthropic | \`@ai-sdk/anthropic\` | Anthropic |
| Google Gemini | \`@ai-sdk/google\` | Google |
The provider's \`api\` URL from the database is used as the base URL. A provider
whose npm package isn't recognized but that has an \`api\` URL falls back to the
OpenAI-compatible wire.
Because routing follows the wire protocol, aggregator/proxy providers work across
**all** of their models — including ones they re-flavor onto a different protocol
via a per-model override. For example, an aggregator that proxies Claude, GPT,
*and* Gemini routes them to the Anthropic, OpenAI, and Google transports
respectively:
\`\`\`bash
kit --model opencode/claude-haiku-4-5 "Hello" # → Anthropic wire
kit --model opencode/gpt-5 "Hello" # → OpenAI wire
kit --model opencode/gemini-3.5-flash "Hello" # → Google wire
\`\`\`
Provide the provider's API key the same way as any other — via its environment
variable (e.g. \`OPENCODE_API_KEY\`) or \`--provider-api-key\`.
## Model database
Kit ships with a local model database that maps provider names to API configurations. You can manage it with:
\`\`\`bash
kit models # List available models
kit models openai # Filter by provider
kit models --all # Show all providers
kit update-models # Update from models.dev
kit update-models embedded # Reset to bundled database
\`\`\`
`};export{s as default};
-125
View File
@@ -1,125 +0,0 @@
const s={frontmatter:{title:"Quick Start",description:"Get up and running with Kit in minutes.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="quick-start"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#quick-start"><span class="icon icon-link"></span></a>Quick Start</h1>
<h2 id="basic-usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#basic-usage"><span class="icon icon-link"></span></a>Basic usage</h2>
<p>Start an interactive session:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span></span></code></pre>
<p>Run a one-off prompt:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "List files in src/"</span></span></code></pre>
<p>Attach files as context using the <code>@</code> prefix:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @main.go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @test.go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Review these files"</span></span></code></pre>
<p>Binary files (images, audio, PDFs) are automatically detected via MIME type and sent as multimodal attachments. You can also reference MCP resources:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mcp:myserver:file:///data/report.csv</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Summarize this data"</span></span></code></pre>
<p>Use a specific model:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic/claude-sonnet-latest</span></span></code></pre>
<h2 id="non-interactive-mode"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#non-interactive-mode"><span class="icon icon-link"></span></a>Non-interactive mode</h2>
<p>Kit can run as a non-interactive tool for scripting and automation.</p>
<p>Get JSON output:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Explain main.go"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span></span></code></pre>
<p>Quiet mode (final response only, no TUI):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Run tests"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span></span></code></pre>
<p>Ephemeral mode (no session file created):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Quick question"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<h2 id="resuming-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#resuming-sessions"><span class="icon icon-link"></span></a>Resuming sessions</h2>
<p>Continue the most recent session for the current directory:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --continue</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># or</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -c</span></span></code></pre>
<p>Pick from previous sessions interactively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --resume</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># or</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -r</span></span></code></pre>
<h2 id="acp-server-mode"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#acp-server-mode"><span class="icon icon-link"></span></a>ACP server mode</h2>
<p>Kit can run as an <a href="https://agentclientprotocol.com">ACP (Agent Client Protocol)</a> agent server, enabling ACP-compatible clients (such as <a href="https://github.com/sst/opencode">OpenCode</a>) to drive Kit as a remote coding agent over stdio:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Start Kit as an ACP server (JSON-RPC 2.0 on stdin/stdout)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># With debug logging to stderr</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --debug</span></span></code></pre>
<p>The 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.</p>`,headings:[{depth:2,text:"Basic usage",id:"basic-usage"},{depth:2,text:"Non-interactive mode",id:"non-interactive-mode"},{depth:2,text:"Resuming sessions",id:"resuming-sessions"},{depth:2,text:"ACP server mode",id:"acp-server-mode"}],raw:`
# Quick Start
## Basic usage
Start an interactive session:
\`\`\`bash
kit
\`\`\`
Run a one-off prompt:
\`\`\`bash
kit "List files in src/"
\`\`\`
Attach files as context using the \`@\` prefix:
\`\`\`bash
kit @main.go @test.go "Review these files"
\`\`\`
Binary files (images, audio, PDFs) are automatically detected via MIME type and sent as multimodal attachments. You can also reference MCP resources:
\`\`\`bash
kit @mcp:myserver:file:///data/report.csv "Summarize this data"
\`\`\`
Use a specific model:
\`\`\`bash
kit --model anthropic/claude-sonnet-latest
\`\`\`
## Non-interactive mode
Kit can run as a non-interactive tool for scripting and automation.
Get JSON output:
\`\`\`bash
kit "Explain main.go" --json
\`\`\`
Quiet mode (final response only, no TUI):
\`\`\`bash
kit "Run tests" --quiet
\`\`\`
Ephemeral mode (no session file created):
\`\`\`bash
kit "Quick question" --no-session
\`\`\`
## Resuming sessions
Continue the most recent session for the current directory:
\`\`\`bash
kit --continue
# or
kit -c
\`\`\`
Pick from previous sessions interactively:
\`\`\`bash
kit --resume
# or
kit -r
\`\`\`
## ACP server mode
Kit 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:
\`\`\`bash
# Start Kit as an ACP server (JSON-RPC 2.0 on stdin/stdout)
kit acp
# With debug logging to stderr
kit acp --debug
\`\`\`
The 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.
`};export{s as default};
-223
View File
@@ -1,223 +0,0 @@
const e={frontmatter:{title:"Session Management",description:"How Kit persists and manages conversation sessions.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="session-management"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-management"><span class="icon icon-link"></span></a>Session Management</h1>
<p>Kit uses a tree-based session model that supports branching and forking conversations.</p>
<h2 id="session-storage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-storage"><span class="icon icon-link"></span></a>Session storage</h2>
<p>Sessions are stored as JSONL (JSON Lines) files:</p>
<pre><code>~/.kit/sessions/&lt;cwd-path&gt;/&lt;timestamp&gt;_&lt;id&gt;.jsonl
</code></pre>
<p>Path separators in the working directory are replaced with <code>--</code>. For example, <code>/home/user/project</code> becomes <code>home--user--project</code>.</p>
<p>Each 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.</p>
<h2 id="compaction"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#compaction"><span class="icon icon-link"></span></a>Compaction</h2>
<p>When conversations grow long, Kit can compact them to free up context window space. The compaction system:</p>
<ul>
<li><strong>Non-destructive</strong>: Old messages remain on disk for history; only the LLM context is summarized</li>
<li><strong>File tracking</strong>: Tracks which files were read and modified across compactions</li>
<li><strong>Split-turn handling</strong>: Can summarize large single turns by splitting them</li>
<li><strong>Tool result truncation</strong>: Caps tool output during serialization to stay within token budgets</li>
</ul>
<p>Use <code>/compact [focus]</code> to manually compact, or enable <code>--auto-compact</code> to compact automatically near the context limit.</p>
<h2 id="auto-cleanup"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#auto-cleanup"><span class="icon icon-link"></span></a>Auto-cleanup</h2>
<p>Kit automatically cleans up empty sessions on shutdown and when using <code>/resume</code>. A session is considered empty if it has no messages beyond the initial system prompt. This prevents cluttering your sessions directory with unused files.</p>
<p>To start fresh without creating a session file at all, use ephemeral mode:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<h2 id="resuming-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#resuming-sessions"><span class="icon icon-link"></span></a>Resuming sessions</h2>
<h3 id="continue-most-recent"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#continue-most-recent"><span class="icon icon-link"></span></a>Continue most recent</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --continue</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -c</span></span></code></pre>
<h3 id="interactive-picker"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#interactive-picker"><span class="icon icon-link"></span></a>Interactive picker</h3>
<p>Choose from previous sessions interactively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --resume</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -r</span></span></code></pre>
<p>The 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 <code>/resume</code> slash command.</p>
<h3 id="open-a-specific-session"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#open-a-specific-session"><span class="icon icon-link"></span></a>Open a specific session</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --session</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/session.jsonl</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/session.jsonl</span></span></code></pre>
<h2 id="session-commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-commands"><span class="icon icon-link"></span></a>Session commands</h2>
<p>These slash commands are available during an interactive session:</p>
<table>
<thead>
<tr>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/name [name]</code></td>
<td>Set or display the session's display name</td>
</tr>
<tr>
<td><code>/session</code></td>
<td>Show session info (path, ID, message count)</td>
</tr>
<tr>
<td><code>/resume</code></td>
<td>Open the session picker to switch sessions</td>
</tr>
<tr>
<td><code>/export [path]</code></td>
<td>Export session as JSONL (auto-generates path if omitted)</td>
</tr>
<tr>
<td><code>/import &lt;path&gt;</code></td>
<td>Import and switch to a session from a JSONL file</td>
</tr>
<tr>
<td><code>/share</code></td>
<td>Upload session to GitHub Gist and get a shareable viewer URL</td>
</tr>
<tr>
<td><code>/tree</code></td>
<td>Navigate the session tree</td>
</tr>
<tr>
<td><code>/fork</code></td>
<td>Fork to new session from an earlier message (creates new session file)</td>
</tr>
<tr>
<td><code>/new</code></td>
<td>Start a new session (creates new session file)</td>
</tr>
</tbody>
</table>
<h2 id="ephemeral-mode"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#ephemeral-mode"><span class="icon icon-link"></span></a>Ephemeral mode</h2>
<p>Run without creating a session file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<p>This is useful for one-off prompts, scripting, and subagent patterns where persistence isn't needed.</p>
<h2 id="sharing-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#sharing-sessions"><span class="icon icon-link"></span></a>Sharing sessions</h2>
<p>The <code>/share</code> command uploads your session JSONL to GitHub Gist (via the <code>gh</code> CLI) and prints a shareable viewer URL:</p>
<pre><code>/share
</code></pre>
<p>The shared session includes:</p>
<ul>
<li>The <strong>system prompt</strong> that was active during the conversation</li>
<li>The <strong>model</strong> used (e.g., <code>anthropic/claude-sonnet-4-5</code>)</li>
</ul>
<p>The 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.</p>
<p>The viewer is available at <code>https://go-kit.dev/session/#GIST_ID</code> and supports all message types including text, reasoning blocks, tool calls, images, and model changes.</p>
<p>You can also load any JSONL session via URL parameter: <code>https://go-kit.dev/session/?url=https://example.com/session.jsonl</code></p>
<h2 id="preferences-persistence"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#preferences-persistence"><span class="icon icon-link"></span></a>Preferences persistence</h2>
<p>Kit automatically saves your preferences across sessions to <code>~/.config/kit/preferences.yml</code>:</p>
<ul>
<li><strong>Theme</strong> — Set via <code>/theme &lt;name&gt;</code></li>
<li><strong>Model</strong> — Set via <code>/model &lt;name&gt;</code> or the model selector</li>
<li><strong>Thinking level</strong> — Set via <code>/thinking &lt;level&gt;</code> or Shift+Tab cycling</li>
</ul>
<p>These preferences are restored on next launch. Precedence: CLI flag &gt; config file &gt; saved preference &gt; default.</p>`,headings:[{depth:2,text:"Session storage",id:"session-storage"},{depth:2,text:"Compaction",id:"compaction"},{depth:2,text:"Auto-cleanup",id:"auto-cleanup"},{depth:2,text:"Resuming sessions",id:"resuming-sessions"},{depth:3,text:"Continue most recent",id:"continue-most-recent"},{depth:3,text:"Interactive picker",id:"interactive-picker"},{depth:3,text:"Open a specific session",id:"open-a-specific-session"},{depth:2,text:"Session commands",id:"session-commands"},{depth:2,text:"Ephemeral mode",id:"ephemeral-mode"},{depth:2,text:"Sharing sessions",id:"sharing-sessions"},{depth:2,text:"Preferences persistence",id:"preferences-persistence"}],raw:`
# Session Management
Kit uses a tree-based session model that supports branching and forking conversations.
## Session storage
Sessions are stored as JSONL (JSON Lines) files:
\`\`\`
~/.kit/sessions/<cwd-path>/<timestamp>_<id>.jsonl
\`\`\`
Path separators in the working directory are replaced with \`--\`. For example, \`/home/user/project\` becomes \`home--user--project\`.
Each 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.
## Compaction
When conversations grow long, Kit can compact them to free up context window space. The compaction system:
- **Non-destructive**: Old messages remain on disk for history; only the LLM context is summarized
- **File tracking**: Tracks which files were read and modified across compactions
- **Split-turn handling**: Can summarize large single turns by splitting them
- **Tool result truncation**: Caps tool output during serialization to stay within token budgets
Use \`/compact [focus]\` to manually compact, or enable \`--auto-compact\` to compact automatically near the context limit.
## Auto-cleanup
Kit 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.
To start fresh without creating a session file at all, use ephemeral mode:
\`\`\`bash
kit --no-session
\`\`\`
## Resuming sessions
### Continue most recent
\`\`\`bash
kit --continue
kit -c
\`\`\`
### Interactive picker
Choose from previous sessions interactively:
\`\`\`bash
kit --resume
kit -r
\`\`\`
The 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.
### Open a specific session
\`\`\`bash
kit --session path/to/session.jsonl
kit -s path/to/session.jsonl
\`\`\`
## Session commands
These slash commands are available during an interactive session:
| Command | Description |
|---------|-------------|
| \`/name [name]\` | Set or display the session's display name |
| \`/session\` | Show session info (path, ID, message count) |
| \`/resume\` | Open the session picker to switch sessions |
| \`/export [path]\` | Export session as JSONL (auto-generates path if omitted) |
| \`/import <path>\` | Import and switch to a session from a JSONL file |
| \`/share\` | Upload session to GitHub Gist and get a shareable viewer URL |
| \`/tree\` | Navigate the session tree |
| \`/fork\` | Fork to new session from an earlier message (creates new session file) |
| \`/new\` | Start a new session (creates new session file) |
## Ephemeral mode
Run without creating a session file:
\`\`\`bash
kit --no-session
\`\`\`
This is useful for one-off prompts, scripting, and subagent patterns where persistence isn't needed.
## Sharing sessions
The \`/share\` command uploads your session JSONL to GitHub Gist (via the \`gh\` CLI) and prints a shareable viewer URL:
\`\`\`
/share
\`\`\`
The shared session includes:
- The **system prompt** that was active during the conversation
- The **model** used (e.g., \`anthropic/claude-sonnet-4-5\`)
The 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.
The 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.
You can also load any JSONL session via URL parameter: \`https://go-kit.dev/session/?url=https://example.com/session.jsonl\`
## Preferences persistence
Kit automatically saves your preferences across sessions to \`~/.config/kit/preferences.yml\`:
- **Theme** — Set via \`/theme <name>\`
- **Model** — Set via \`/model <name>\` or the model selector
- **Thinking level** — Set via \`/thinking <level>\` or Shift+Tab cycling
These preferences are restored on next launch. Precedence: CLI flag > config file > saved preference > default.
`};export{e as default};
-163
View File
@@ -1,163 +0,0 @@
const s={frontmatter:{title:"SDK Sessions",description:"Session management in the Kit Go SDK.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="sdk-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#sdk-sessions"><span class="icon icon-link"></span></a>SDK Sessions</h1>
<h2 id="automatic-persistence"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#automatic-persistence"><span class="icon icon-link"></span></a>Automatic persistence</h2>
<p>By default, Kit automatically persists sessions to JSONL files. Multi-turn conversations retain context across calls:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Prompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"My name is Alice"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">response, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Prompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"What's my name?"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// response: "Your name is Alice"</span></span></code></pre>
<h2 id="accessing-session-info"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#accessing-session-info"><span class="icon icon-link"></span></a>Accessing session info</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get the current session file path</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">path </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetSessionPath</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get the session ID</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">id </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetSessionID</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get the current model string</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">model </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetModelString</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<h2 id="configuring-sessions-via-options"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#configuring-sessions-via-options"><span class="icon icon-link"></span></a>Configuring sessions via Options</h2>
<p>Session behavior is configured at initialization:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Open a specific session file</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SessionPath: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./my-session.jsonl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Resume the most recent session for the current directory</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Continue: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Ephemeral mode (no file persistence)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> NoSession: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Custom session directory</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SessionDir: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/custom/sessions/"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h2 id="clearing-history"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#clearing-history"><span class="icon icon-link"></span></a>Clearing history</h2>
<p>Clear the in-memory conversation history (does not delete the session file):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ClearSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<h2 id="tree-based-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#tree-based-sessions"><span class="icon icon-link"></span></a>Tree-based sessions</h2>
<p>Kit's session model is tree-based, supporting branching. You can branch from any entry to explore alternate conversation paths:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Access the tree session manager</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">ts </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetTreeSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Branch from a specific entry</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Branch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"entry-id-123"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h2 id="listing-and-managing-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#listing-and-managing-sessions"><span class="icon icon-link"></span></a>Listing and managing sessions</h2>
<p>Package-level functions for session discovery:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// List sessions for a specific directory</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">sessions </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListSessions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/home/user/project"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// List all sessions across all directories</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">all </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListAllSessions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Delete a session file</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">DeleteSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/path/to/session.jsonl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h2 id="custom-session-manager"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#custom-session-manager"><span class="icon icon-link"></span></a>Custom session manager</h2>
<p>For advanced use cases (databases, cloud storage, multi-user apps), implement the <code>SessionManager</code> interface to replace the default JSONL file backend:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SessionManager: myCustomSession,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<p>The interface requires methods for message storage, branching, compaction, extension data, and lifecycle management. See the <a href="https://github.com/mark3labs/kit">SDK skill reference</a> for the complete interface definition.</p>
<p>When using a custom <code>SessionManager</code>, the <code>SessionPath</code>, <code>Continue</code>, and <code>NoSession</code> options are ignored — your manager handles its own storage and session selection.</p>`,headings:[{depth:2,text:"Automatic persistence",id:"automatic-persistence"},{depth:2,text:"Accessing session info",id:"accessing-session-info"},{depth:2,text:"Configuring sessions via Options",id:"configuring-sessions-via-options"},{depth:2,text:"Clearing history",id:"clearing-history"},{depth:2,text:"Tree-based sessions",id:"tree-based-sessions"},{depth:2,text:"Listing and managing sessions",id:"listing-and-managing-sessions"},{depth:2,text:"Custom session manager",id:"custom-session-manager"}],raw:`
# SDK Sessions
## Automatic persistence
By default, Kit automatically persists sessions to JSONL files. Multi-turn conversations retain context across calls:
\`\`\`go
host.Prompt(ctx, "My name is Alice")
response, _ := host.Prompt(ctx, "What's my name?")
// response: "Your name is Alice"
\`\`\`
## Accessing session info
\`\`\`go
// Get the current session file path
path := host.GetSessionPath()
// Get the session ID
id := host.GetSessionID()
// Get the current model string
model := host.GetModelString()
\`\`\`
## Configuring sessions via Options
Session behavior is configured at initialization:
\`\`\`go
// Open a specific session file
host, _ := kit.New(ctx, &kit.Options{
SessionPath: "./my-session.jsonl",
})
// Resume the most recent session for the current directory
host, _ := kit.New(ctx, &kit.Options{
Continue: true,
})
// Ephemeral mode (no file persistence)
host, _ := kit.New(ctx, &kit.Options{
NoSession: true,
})
// Custom session directory
host, _ := kit.New(ctx, &kit.Options{
SessionDir: "/custom/sessions/",
})
\`\`\`
## Clearing history
Clear the in-memory conversation history (does not delete the session file):
\`\`\`go
host.ClearSession()
\`\`\`
## Tree-based sessions
Kit's session model is tree-based, supporting branching. You can branch from any entry to explore alternate conversation paths:
\`\`\`go
// Access the tree session manager
ts := host.GetTreeSession()
// Branch from a specific entry
err := host.Branch("entry-id-123")
\`\`\`
## Listing and managing sessions
Package-level functions for session discovery:
\`\`\`go
// List sessions for a specific directory
sessions := kit.ListSessions("/home/user/project")
// List all sessions across all directories
all := kit.ListAllSessions()
// Delete a session file
kit.DeleteSession("/path/to/session.jsonl")
\`\`\`
## Custom session manager
For advanced use cases (databases, cloud storage, multi-user apps), implement the \`SessionManager\` interface to replace the default JSONL file backend:
\`\`\`go
host, _ := kit.New(ctx, &kit.Options{
SessionManager: myCustomSession,
})
\`\`\`
The 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.
When using a custom \`SessionManager\`, the \`SessionPath\`, \`Continue\`, and \`NoSession\` options are ignored — your manager handles its own storage and session selection.
`};export{s as default};
-315
View File
@@ -1,315 +0,0 @@
const s={frontmatter:{title:"Subagents",description:"Multi-agent orchestration with Kit subagents.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="subagents"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#subagents"><span class="icon icon-link"></span></a>Subagents</h1>
<p>Kit supports multi-agent orchestration through both subprocess spawning and in-process subagents.</p>
<h2 id="subprocess-pattern"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#subprocess-pattern"><span class="icon icon-link"></span></a>Subprocess pattern</h2>
<p>Spawn Kit as a subprocess for isolated agent execution:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Analyze codebase"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-extensions</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic/claude-haiku-latest</span></span></code></pre>
<p>Key flags for subprocess usage:</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--quiet</code></td>
<td>Stdout only, no TUI</td>
</tr>
<tr>
<td><code>--no-session</code></td>
<td>Ephemeral, no persistence</td>
</tr>
<tr>
<td><code>--no-extensions</code></td>
<td>Prevent recursive extension loading</td>
</tr>
<tr>
<td><code>--json</code></td>
<td>Machine-readable output</td>
</tr>
<tr>
<td><code>--system-prompt</code></td>
<td>Custom system prompt (string or file path)</td>
</tr>
</tbody>
</table>
<p>Positional arguments are the prompt. <code>@file</code> arguments attach file content as context.</p>
<h2 id="built-in-subagent-tool"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#built-in-subagent-tool"><span class="icon icon-link"></span></a>Built-in subagent tool</h2>
<p>Kit includes a built-in <code>subagent</code> tool that the LLM can use to delegate tasks to independent child agents:</p>
<pre><code>subagent(
task: "Analyze the test files and summarize coverage",
model: "anthropic/claude-haiku-latest", // optional
system_prompt: "You are a test analysis expert.", // optional
timeout_seconds: 300 // optional, max 1800
)
</code></pre>
<p>Subagents run as separate in-process Kit instances with full tool access (except spawning further subagents, to prevent infinite recursion). They can run in parallel.</p>
<h2 id="extension-subagents"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-subagents"><span class="icon icon-link"></span></a>Extension subagents</h2>
<p>Extensions can spawn subagents programmatically:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SpawnSubagent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Review this code for security issues"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Model: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-sonnet-latest"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SystemPrompt: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"You are a security auditor."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h3 id="monitoring-subagents-from-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#monitoring-subagents-from-extensions"><span class="icon icon-link"></span></a>Monitoring subagents from extensions</h3>
<p>When the LLM (not the extension itself) spawns a subagent using the <code>subagent</code> tool, extensions can monitor its activity in real-time using three lifecycle event handlers:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Track active subagents and display their output</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets </span><span style="color:#D73A49;--shiki-dark:#F97583">map</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentWidget</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Subagent started by the main agent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSubagentStart</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ToolCallID — unique ID for this subagent invocation</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.Task — the task/prompt sent to the subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> NewWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e.ToolCallID, e.Task)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets[e.ToolCallID] </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Config</span><span style="color:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Real-time streaming from subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSubagentChunk</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentChunkEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ToolCallID — matches the start event</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ChunkType — "text", "tool_call", "tool_execution_start", "tool_result"</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.Content — text content</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ToolName — tool name (for tool chunks)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.IsError — true if tool result failed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets[e.ToolCallID]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AddOutput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Config</span><span style="color:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Subagent completed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSubagentEnd</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentEndEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.Response — final response from subagent</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ErrorMsg — error message if subagent failed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets[e.ToolCallID]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MarkComplete</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e.Response, e.ErrorMsg)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Config</span><span style="color:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0"> delete</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(subagentWidgets, e.ToolCallID)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p><strong>Event structs:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SubagentStartEvent</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolCallID </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Unique ID for this subagent invocation</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // The task/prompt sent to subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SubagentChunkEvent</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolCallID </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Matches SubagentStartEvent.ToolCallID</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Task description</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ChunkType </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // "text", "tool_call", "tool_execution_start", "tool_result"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Content </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // For text chunks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolName </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // For tool-related chunks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> IsError </span><span style="color:#D73A49;--shiki-dark:#F97583">bool</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // For tool_result chunks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SubagentEndEvent</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolCallID </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Matches start event</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Task description</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Response </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Final response from subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ErrorMsg </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Error message if failed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>This enables building monitoring widgets that display real-time activity from all subagents spawned by the main agent.</p>
<h2 id="go-sdk-subagents"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#go-sdk-subagents"><span class="icon icon-link"></span></a>Go SDK subagents</h2>
<p>The SDK provides in-process subagent spawning:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Subagent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Summarize the changes in this PR"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Model: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-haiku-latest"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SystemPrompt: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"You are a code reviewer."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Timeout: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> time.Minute,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h3 id="real-time-subagent-events"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#real-time-subagent-events"><span class="icon icon-link"></span></a>Real-time subagent events</h3>
<p>Use <code>SubscribeSubagent</code> to receive real-time events from LLM-initiated subagents (i.e., when the model uses the <code>subagent</code> tool). Register inside an <code>OnToolCall</code> handler using the tool call ID:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnToolCall</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> e.ToolName </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "subagent"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubscribeSubagent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e.ToolCallID, </span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">event</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Event</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> switch</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ev </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> event.(</span><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MessageUpdateEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ev.Chunk) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// streaming text from child</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Child calling: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s\\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ev.ToolName)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolResultEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Child result: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s\\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ev.ToolName)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<p>The listener receives the same event types as <code>Subscribe()</code> (<code>ToolCallEvent</code>, <code>MessageUpdateEvent</code>, <code>ReasoningDeltaEvent</code>, etc.) but scoped to the child agent's activity. Listeners are cleaned up automatically when the subagent completes.</p>
<p>If no listeners are registered for a tool call, no event dispatching overhead is incurred.</p>`,headings:[{depth:2,text:"Subprocess pattern",id:"subprocess-pattern"},{depth:2,text:"Built-in subagent tool",id:"built-in-subagent-tool"},{depth:2,text:"Extension subagents",id:"extension-subagents"},{depth:3,text:"Monitoring subagents from extensions",id:"monitoring-subagents-from-extensions"},{depth:2,text:"Go SDK subagents",id:"go-sdk-subagents"},{depth:3,text:"Real-time subagent events",id:"real-time-subagent-events"}],raw:`
# Subagents
Kit supports multi-agent orchestration through both subprocess spawning and in-process subagents.
## Subprocess pattern
Spawn Kit as a subprocess for isolated agent execution:
\`\`\`bash
kit "Analyze codebase" \\
--json \\
--no-session \\
--no-extensions \\
--quiet \\
--model anthropic/claude-haiku-latest
\`\`\`
Key flags for subprocess usage:
| Flag | Purpose |
|------|---------|
| \`--quiet\` | Stdout only, no TUI |
| \`--no-session\` | Ephemeral, no persistence |
| \`--no-extensions\` | Prevent recursive extension loading |
| \`--json\` | Machine-readable output |
| \`--system-prompt\` | Custom system prompt (string or file path) |
Positional arguments are the prompt. \`@file\` arguments attach file content as context.
## Built-in subagent tool
Kit includes a built-in \`subagent\` tool that the LLM can use to delegate tasks to independent child agents:
\`\`\`
subagent(
task: "Analyze the test files and summarize coverage",
model: "anthropic/claude-haiku-latest", // optional
system_prompt: "You are a test analysis expert.", // optional
timeout_seconds: 300 // optional, max 1800
)
\`\`\`
Subagents run as separate in-process Kit instances with full tool access (except spawning further subagents, to prevent infinite recursion). They can run in parallel.
## Extension subagents
Extensions can spawn subagents programmatically:
\`\`\`go
result := ctx.SpawnSubagent(ext.SubagentConfig{
Task: "Review this code for security issues",
Model: "anthropic/claude-sonnet-latest",
SystemPrompt: "You are a security auditor.",
})
\`\`\`
### Monitoring subagents from extensions
When 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:
\`\`\`go
// Track active subagents and display their output
var subagentWidgets map[string]*SubagentWidget
func Init(api ext.API) {
// Subagent started by the main agent
api.OnSubagentStart(func(e ext.SubagentStartEvent, ctx ext.Context) {
// e.ToolCallID — unique ID for this subagent invocation
// e.Task — the task/prompt sent to the subagent
widget := NewWidget(e.ToolCallID, e.Task)
subagentWidgets[e.ToolCallID] = widget
ctx.SetWidget(widget.Config())
})
// Real-time streaming from subagent
api.OnSubagentChunk(func(e ext.SubagentChunkEvent, ctx ext.Context) {
// e.ToolCallID — matches the start event
// e.ChunkType — "text", "tool_call", "tool_execution_start", "tool_result"
// e.Content — text content
// e.ToolName — tool name (for tool chunks)
// e.IsError — true if tool result failed
widget := subagentWidgets[e.ToolCallID]
if widget != nil {
widget.AddOutput(e)
ctx.SetWidget(widget.Config())
}
})
// Subagent completed
api.OnSubagentEnd(func(e ext.SubagentEndEvent, ctx ext.Context) {
// e.Response — final response from subagent
// e.ErrorMsg — error message if subagent failed
widget := subagentWidgets[e.ToolCallID]
if widget != nil {
widget.MarkComplete(e.Response, e.ErrorMsg)
ctx.SetWidget(widget.Config())
delete(subagentWidgets, e.ToolCallID)
}
})
}
\`\`\`
**Event structs:**
\`\`\`go
type SubagentStartEvent struct {
ToolCallID string // Unique ID for this subagent invocation
Task string // The task/prompt sent to subagent
}
type SubagentChunkEvent struct {
ToolCallID string // Matches SubagentStartEvent.ToolCallID
Task string // Task description
ChunkType string // "text", "tool_call", "tool_execution_start", "tool_result"
Content string // For text chunks
ToolName string // For tool-related chunks
IsError bool // For tool_result chunks
}
type SubagentEndEvent struct {
ToolCallID string // Matches start event
Task string // Task description
Response string // Final response from subagent
ErrorMsg string // Error message if failed
}
\`\`\`
This enables building monitoring widgets that display real-time activity from all subagents spawned by the main agent.
## Go SDK subagents
The SDK provides in-process subagent spawning:
\`\`\`go
result, err := host.Subagent(ctx, kit.SubagentConfig{
Task: "Summarize the changes in this PR",
Model: "anthropic/claude-haiku-latest",
SystemPrompt: "You are a code reviewer.",
Timeout: 5 * time.Minute,
})
\`\`\`
### Real-time subagent events
Use \`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:
\`\`\`go
host.OnToolCall(func(e kit.ToolCallEvent) {
if e.ToolName == "subagent" {
host.SubscribeSubagent(e.ToolCallID, func(event kit.Event) {
switch ev := event.(type) {
case kit.MessageUpdateEvent:
fmt.Print(ev.Chunk) // streaming text from child
case kit.ToolCallEvent:
fmt.Printf("Child calling: %s\\n", ev.ToolName)
case kit.ToolResultEvent:
fmt.Printf("Child result: %s\\n", ev.ToolName)
}
})
}
})
\`\`\`
The 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.
If no listeners are registered for a tool call, no event dispatching overhead is incurred.
`};export{s as default};
-882
View File
@@ -1,882 +0,0 @@
const s={frontmatter:{title:"Testing Extensions",description:"Write unit tests for your Kit extensions using the test package.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="testing-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-extensions"><span class="icon icon-link"></span></a>Testing Extensions</h1>
<p>Kit provides a testing package (<code>github.com/mark3labs/kit/pkg/extensions/test</code>) 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.</p>
<h2 id="overview"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#overview"><span class="icon icon-link"></span></a>Overview</h2>
<p>Extension tests allow you to:</p>
<ul>
<li>Test event handlers without running the interactive TUI</li>
<li>Verify tool/command registration</li>
<li>Assert that context methods (Print, SetWidget, etc.) are called correctly</li>
<li>Test blocking and non-blocking event handling</li>
<li>Simulate user input and tool calls</li>
<li>Verify widget, header, footer, and status bar updates</li>
</ul>
<h2 id="installation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installation"><span class="icon icon-link"></span></a>Installation</h2>
<p>The test package is part of the Kit codebase. Import it in your extension tests:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/pkg/extensions/test</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/internal/extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h2 id="basic-usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#basic-usage"><span class="icon icon-link"></span></a>Basic Usage</h2>
<h3 id="testing-an-extension-file"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-an-extension-file"><span class="icon icon-link"></span></a>Testing an Extension File</h3>
<p>Create a test file alongside your extension (e.g., <code>my-ext_test.go</code>):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/pkg/extensions/test</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/internal/extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestMyExtension</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Create a test harness</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Load your extension</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Emit events and check results</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolName: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my_tool"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Input: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">\`{"key": "value"}\`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Fatalf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"unexpected error: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, err)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Use assertion helpers</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertNotBlocked</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, result)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrinted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"expected output"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-inline-extension-code"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-inline-extension-code"><span class="icon icon-link"></span></a>Testing Inline Extension Code</h3>
<p>For quick tests or edge cases, you can load extension source directly:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestToolBlocking</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> src </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> \`package main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">import "kit/ext"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">func Init(api ext.API) {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> if tc.ToolName == "dangerous" {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> return &amp;ext.ToolCallResult{Block: true, Reason: "not allowed"}</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> }</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> return nil</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> })</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">}</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">\`</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadString</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(src, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Test the tool is blocked</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> result, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolName: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"dangerous"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Input: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"{}"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertBlocked</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, result, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"not allowed"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="common-testing-patterns"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#common-testing-patterns"><span class="icon icon-link"></span></a>Common Testing Patterns</h2>
<h3 id="testing-handler-registration"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-handler-registration"><span class="icon icon-link"></span></a>Testing Handler Registration</h3>
<p>Verify your extension registers the expected handlers:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertHasHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, extensions.ToolCall)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertHasHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, extensions.SessionStart)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertNoHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, extensions.AgentEnd) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// Verify no unexpected handlers</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-tool-registration"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-tool-registration"><span class="icon icon-link"></span></a>Testing Tool Registration</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestTools</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Verify a specific tool is registered</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertToolRegistered</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my_tool"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Or inspect all tools</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">RegisteredTools</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, tool </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#D73A49;--shiki-dark:#F97583"> range</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Tool: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> - </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, tool.Name, tool.Description)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-commands"><span class="icon icon-link"></span></a>Testing Commands</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestCommands</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertCommandRegistered</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"mycommand"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-widgets"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-widgets"><span class="icon icon-link"></span></a>Testing Widgets</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestWidgets</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Trigger event that creates the widget</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Verify widget was set</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetText</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Expected Text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetTextContains</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"partial"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Check widget properties directly</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget, ok </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ok {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Border color: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, widget.Style.BorderColor)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-input-handling"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-input-handling"><span class="icon icon-link"></span></a>Testing Input Handling</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestInput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> result, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">InputEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Text: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"!mycommand"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Source: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"cli"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertInputHandled</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, result, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"handled"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-headers-and-footers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-headers-and-footers"><span class="icon icon-link"></span></a>Testing Headers and Footers</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestHeaderFooter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertHeaderSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertFooterSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Inspect content</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> header </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetHeader</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> header </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Header text: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, header.Content.Text)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-status-bar"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-status-bar"><span class="icon icon-link"></span></a>Testing Status Bar</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestStatus</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AgentEndEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertStatusSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"myext:status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertStatusText</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"myext:status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Ready"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-print-output"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-print-output"><span class="icon icon-link"></span></a>Testing Print Output</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestOutput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{ToolName: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Exact match</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrinted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"exact output"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Partial match</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrintedContains</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"partial"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Styled output</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrintInfo</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"info message"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrintError</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"error message"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-with-prompts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-with-prompts"><span class="icon icon-link"></span></a>Testing with Prompts</h3>
<p>Configure mock prompt results for testing interactive behavior:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestWithPrompts</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Configure what prompts should return</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetPromptSelectResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">PromptSelectResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Value: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"option1"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Index: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Cancelled: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetPromptConfirmResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">PromptConfirmResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Value: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Cancelled: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Now when your extension calls ctx.PromptSelect(), it gets this result</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-complete-session-flow"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-complete-session-flow"><span class="icon icon-link"></span></a>Testing Complete Session Flow</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestFullSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Simulate a complete session</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">BeforeAgentStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AgentStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Multiple tool calls</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Read"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Grep"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Bash"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, tool </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#D73A49;--shiki-dark:#F97583"> range</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{ToolName: tool})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolResultEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{ToolName: tool})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AgentEndEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionShutdownEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Verify final state</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetTextContains</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Complete"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="available-assertions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#available-assertions"><span class="icon icon-link"></span></a>Available Assertions</h2>
<p>The test package provides these assertion helpers:</p>
<h3 id="event-results"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#event-results"><span class="icon icon-link"></span></a>Event Results</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertNotBlocked(t, result)</code></td>
<td>Verify tool was not blocked</td>
</tr>
<tr>
<td><code>AssertBlocked(t, result, reason)</code></td>
<td>Verify tool was blocked with reason</td>
</tr>
<tr>
<td><code>AssertInputHandled(t, result, action)</code></td>
<td>Verify input was handled</td>
</tr>
<tr>
<td><code>AssertInputTransformed(t, result, text)</code></td>
<td>Verify input was transformed</td>
</tr>
</tbody>
</table>
<h3 id="context-interactions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#context-interactions"><span class="icon icon-link"></span></a>Context Interactions</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertPrinted(t, harness, text)</code></td>
<td>Verify exact print output</td>
</tr>
<tr>
<td><code>AssertPrintedContains(t, harness, substring)</code></td>
<td>Verify partial print output</td>
</tr>
<tr>
<td><code>AssertPrintInfo(t, harness, text)</code></td>
<td>Verify PrintInfo was called</td>
</tr>
<tr>
<td><code>AssertPrintError(t, harness, text)</code></td>
<td>Verify PrintError was called</td>
</tr>
<tr>
<td><code>AssertWidgetSet(t, harness, id)</code></td>
<td>Verify widget was set</td>
</tr>
<tr>
<td><code>AssertWidgetNotSet(t, harness, id)</code></td>
<td>Verify widget was not set</td>
</tr>
<tr>
<td><code>AssertWidgetText(t, harness, id, text)</code></td>
<td>Verify widget content</td>
</tr>
<tr>
<td><code>AssertWidgetTextContains(t, harness, id, substring)</code></td>
<td>Verify widget contains text</td>
</tr>
<tr>
<td><code>AssertHeaderSet(t, harness)</code></td>
<td>Verify header was set</td>
</tr>
<tr>
<td><code>AssertFooterSet(t, harness)</code></td>
<td>Verify footer was set</td>
</tr>
<tr>
<td><code>AssertStatusSet(t, harness, key)</code></td>
<td>Verify status was set</td>
</tr>
<tr>
<td><code>AssertStatusText(t, harness, key, text)</code></td>
<td>Verify status text</td>
</tr>
</tbody>
</table>
<h3 id="registration"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#registration"><span class="icon icon-link"></span></a>Registration</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertToolRegistered(t, harness, name)</code></td>
<td>Verify tool registration</td>
</tr>
<tr>
<td><code>AssertCommandRegistered(t, harness, name)</code></td>
<td>Verify command registration</td>
</tr>
<tr>
<td><code>AssertHasHandlers(t, harness, eventType)</code></td>
<td>Verify handlers exist</td>
</tr>
<tr>
<td><code>AssertNoHandlers(t, harness, eventType)</code></td>
<td>Verify no handlers</td>
</tr>
</tbody>
</table>
<h3 id="messaging"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#messaging"><span class="icon icon-link"></span></a>Messaging</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertMessageSent(t, harness, text)</code></td>
<td>Verify SendMessage was called</td>
</tr>
<tr>
<td><code>AssertCancelAndSend(t, harness, text)</code></td>
<td>Verify CancelAndSend was called</td>
</tr>
</tbody>
</table>
<h2 id="helper-functions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#helper-functions"><span class="icon icon-link"></span></a>Helper Functions</h2>
<p>For custom assertions, extract result details:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span><span style="color:#D73A49;--shiki-dark:#F97583">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">tcr </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetToolCallResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tcr </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Block: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="color:#032F62;--shiki-dark:#9ECBFF">, Reason: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, tcr.Block, tcr.Reason)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">ir </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetInputResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">trr </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetToolResultResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result)</span></span></code></pre>
<h2 id="advanced-usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#advanced-usage"><span class="icon icon-link"></span></a>Advanced Usage</h2>
<h3 id="accessing-the-mock-context"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#accessing-the-mock-context"><span class="icon icon-link"></span></a>Accessing the Mock Context</h3>
<p>For custom verification:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">ctx </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get all recorded prints</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">prints </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetPrints</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Check options</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">value </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetOption</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-option"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Verify widget properties</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">widget, ok </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ok </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;&amp;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget.Style.BorderColor </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "#ff0000"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Widget has red border"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Check status entries</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">status, ok </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetStatus</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"myext:status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h3 id="testing-multiple-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-multiple-extensions"><span class="icon icon-link"></span></a>Testing Multiple Extensions</h3>
<p>Each harness is isolated:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness1 </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness1.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"ext1.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness2 </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness2.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"ext2.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Events to one don't affect the other</span></span></code></pre>
<h3 id="running-tests"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#running-tests"><span class="icon icon-link"></span></a>Running Tests</h3>
<p>Run all tests in your extension directory:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> examples/extensions</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -v</span></span></code></pre>
<p>Run with race detector:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -race</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -v</span></span></code></pre>
<p>Run a specific test:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> TestMyExtension</span></span></code></pre>
<h2 id="best-practices"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#best-practices"><span class="icon icon-link"></span></a>Best Practices</h2>
<ol>
<li><strong>Test one behavior per test</strong> — Keep tests focused and readable</li>
<li><strong>Use inline source for edge cases</strong> — <code>LoadString()</code> is great for testing specific scenarios</li>
<li><strong>Use <code>LoadFile()</code> for integration tests</strong> — Tests the actual extension file</li>
<li><strong>Assert on context calls</strong> — Verify your extension interacts with the context correctly</li>
<li><strong>Test both positive and negative cases</strong> — Verify tools are blocked AND allowed appropriately</li>
<li><strong>Test all event handlers</strong> — Make sure all registered handlers work correctly</li>
<li><strong>Use descriptive test names</strong> — <code>TestExtension_BlocksDangerousTools</code> is clearer than <code>Test1</code></li>
</ol>
<h2 id="limitations"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#limitations"><span class="icon icon-link"></span></a>Limitations</h2>
<p>The test harness has these intentional limitations:</p>
<ul>
<li><strong>No TUI rendering</strong> — Widgets are recorded but not rendered visually</li>
<li><strong>Prompts return configured values</strong> — Pre-configure prompt results in tests</li>
<li><strong>Subagents don't spawn real processes</strong> — <code>SpawnSubagent()</code> returns nil/empty results</li>
<li><strong>LLM completions are mocked</strong> — <code>Complete()</code> returns empty responses</li>
<li><strong>Some context methods are no-ops</strong> — <code>Exit()</code>, <code>SetActiveTools()</code>, etc. don't have side effects</li>
</ul>
<p>These limitations focus testing on extension logic rather than the full Kit runtime.</p>
<h2 id="complete-example"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#complete-example"><span class="icon icon-link"></span></a>Complete Example</h2>
<p>See <code>examples/extensions/tool-logger_test.go</code> for a complete example with 14 tests covering:</p>
<ul>
<li>Handler registration</li>
<li>Tool call and result handling</li>
<li>Session lifecycle events</li>
<li>Input commands (<code>!time</code>, <code>!status</code>)</li>
<li>Unknown command handling</li>
<li>Concurrent operations (race condition check)</li>
<li>Real file logging verification</li>
</ul>`,headings:[{depth:2,text:"Overview",id:"overview"},{depth:2,text:"Installation",id:"installation"},{depth:2,text:"Basic Usage",id:"basic-usage"},{depth:3,text:"Testing an Extension File",id:"testing-an-extension-file"},{depth:3,text:"Testing Inline Extension Code",id:"testing-inline-extension-code"},{depth:2,text:"Common Testing Patterns",id:"common-testing-patterns"},{depth:3,text:"Testing Handler Registration",id:"testing-handler-registration"},{depth:3,text:"Testing Tool Registration",id:"testing-tool-registration"},{depth:3,text:"Testing Commands",id:"testing-commands"},{depth:3,text:"Testing Widgets",id:"testing-widgets"},{depth:3,text:"Testing Input Handling",id:"testing-input-handling"},{depth:3,text:"Testing Headers and Footers",id:"testing-headers-and-footers"},{depth:3,text:"Testing Status Bar",id:"testing-status-bar"},{depth:3,text:"Testing Print Output",id:"testing-print-output"},{depth:3,text:"Testing with Prompts",id:"testing-with-prompts"},{depth:3,text:"Testing Complete Session Flow",id:"testing-complete-session-flow"},{depth:2,text:"Available Assertions",id:"available-assertions"},{depth:3,text:"Event Results",id:"event-results"},{depth:3,text:"Context Interactions",id:"context-interactions"},{depth:3,text:"Registration",id:"registration"},{depth:3,text:"Messaging",id:"messaging"},{depth:2,text:"Helper Functions",id:"helper-functions"},{depth:2,text:"Advanced Usage",id:"advanced-usage"},{depth:3,text:"Accessing the Mock Context",id:"accessing-the-mock-context"},{depth:3,text:"Testing Multiple Extensions",id:"testing-multiple-extensions"},{depth:3,text:"Running Tests",id:"running-tests"},{depth:2,text:"Best Practices",id:"best-practices"},{depth:2,text:"Limitations",id:"limitations"},{depth:2,text:"Complete Example",id:"complete-example"}],raw:`
# Testing Extensions
Kit 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.
## Overview
Extension tests allow you to:
- Test event handlers without running the interactive TUI
- Verify tool/command registration
- Assert that context methods (Print, SetWidget, etc.) are called correctly
- Test blocking and non-blocking event handling
- Simulate user input and tool calls
- Verify widget, header, footer, and status bar updates
## Installation
The test package is part of the Kit codebase. Import it in your extension tests:
\`\`\`go
import (
"testing"
"github.com/mark3labs/kit/pkg/extensions/test"
"github.com/mark3labs/kit/internal/extensions"
)
\`\`\`
## Basic Usage
### Testing an Extension File
Create a test file alongside your extension (e.g., \`my-ext_test.go\`):
\`\`\`go
package main
import (
"testing"
"github.com/mark3labs/kit/pkg/extensions/test"
"github.com/mark3labs/kit/internal/extensions"
)
func TestMyExtension(t *testing.T) {
// Create a test harness
harness := test.New(t)
// Load your extension
harness.LoadFile("my-ext.go")
// Emit events and check results
result, err := harness.Emit(extensions.ToolCallEvent{
ToolName: "my_tool",
Input: \`{"key": "value"}\`,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Use assertion helpers
test.AssertNotBlocked(t, result)
test.AssertPrinted(t, harness, "expected output")
}
\`\`\`
### Testing Inline Extension Code
For quick tests or edge cases, you can load extension source directly:
\`\`\`go
func TestToolBlocking(t *testing.T) {
src := \`package main
import "kit/ext"
func Init(api ext.API) {
api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {
if tc.ToolName == "dangerous" {
return &ext.ToolCallResult{Block: true, Reason: "not allowed"}
}
return nil
})
}
\`
harness := test.New(t)
harness.LoadString(src, "test-ext.go")
// Test the tool is blocked
result, _ := harness.Emit(extensions.ToolCallEvent{
ToolName: "dangerous",
Input: "{}",
})
test.AssertBlocked(t, result, "not allowed")
}
\`\`\`
## Common Testing Patterns
### Testing Handler Registration
Verify your extension registers the expected handlers:
\`\`\`go
func TestHandlers(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
test.AssertHasHandlers(t, harness, extensions.ToolCall)
test.AssertHasHandlers(t, harness, extensions.SessionStart)
test.AssertNoHandlers(t, harness, extensions.AgentEnd) // Verify no unexpected handlers
}
\`\`\`
### Testing Tool Registration
\`\`\`go
func TestTools(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Verify a specific tool is registered
test.AssertToolRegistered(t, harness, "my_tool")
// Or inspect all tools
tools := harness.RegisteredTools()
for _, tool := range tools {
t.Logf("Tool: %s - %s", tool.Name, tool.Description)
}
}
\`\`\`
### Testing Commands
\`\`\`go
func TestCommands(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
test.AssertCommandRegistered(t, harness, "mycommand")
}
\`\`\`
### Testing Widgets
\`\`\`go
func TestWidgets(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Trigger event that creates the widget
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
// Verify widget was set
test.AssertWidgetSet(t, harness, "my-widget")
test.AssertWidgetText(t, harness, "my-widget", "Expected Text")
test.AssertWidgetTextContains(t, harness, "my-widget", "partial")
// Check widget properties directly
widget, ok := harness.Context().GetWidget("my-widget")
if ok {
t.Logf("Border color: %s", widget.Style.BorderColor)
}
}
\`\`\`
### Testing Input Handling
\`\`\`go
func TestInput(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
result, _ := harness.Emit(extensions.InputEvent{
Text: "!mycommand",
Source: "cli",
})
test.AssertInputHandled(t, result, "handled")
}
\`\`\`
### Testing Headers and Footers
\`\`\`go
func TestHeaderFooter(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
test.AssertHeaderSet(t, harness)
test.AssertFooterSet(t, harness)
// Inspect content
header := harness.Context().GetHeader()
if header != nil {
t.Logf("Header text: %s", header.Content.Text)
}
}
\`\`\`
### Testing Status Bar
\`\`\`go
func TestStatus(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
_, _ = harness.Emit(extensions.AgentEndEvent{})
test.AssertStatusSet(t, harness, "myext:status")
test.AssertStatusText(t, harness, "myext:status", "Ready")
}
\`\`\`
### Testing Print Output
\`\`\`go
func TestOutput(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
_, _ = harness.Emit(extensions.ToolCallEvent{ToolName: "test"})
// Exact match
test.AssertPrinted(t, harness, "exact output")
// Partial match
test.AssertPrintedContains(t, harness, "partial")
// Styled output
test.AssertPrintInfo(t, harness, "info message")
test.AssertPrintError(t, harness, "error message")
}
\`\`\`
### Testing with Prompts
Configure mock prompt results for testing interactive behavior:
\`\`\`go
func TestWithPrompts(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Configure what prompts should return
harness.Context().SetPromptSelectResult(extensions.PromptSelectResult{
Value: "option1",
Index: 0,
Cancelled: false,
})
harness.Context().SetPromptConfirmResult(extensions.PromptConfirmResult{
Value: true,
Cancelled: false,
})
// Now when your extension calls ctx.PromptSelect(), it gets this result
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
}
\`\`\`
### Testing Complete Session Flow
\`\`\`go
func TestFullSession(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Simulate a complete session
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
_, _ = harness.Emit(extensions.BeforeAgentStartEvent{})
_, _ = harness.Emit(extensions.AgentStartEvent{})
// Multiple tool calls
tools := []string{"Read", "Grep", "Bash"}
for _, tool := range tools {
_, _ = harness.Emit(extensions.ToolCallEvent{ToolName: tool})
_, _ = harness.Emit(extensions.ToolResultEvent{ToolName: tool})
}
_, _ = harness.Emit(extensions.AgentEndEvent{})
_, _ = harness.Emit(extensions.SessionShutdownEvent{})
// Verify final state
test.AssertWidgetTextContains(t, harness, "status", "Complete")
}
\`\`\`
## Available Assertions
The test package provides these assertion helpers:
### Event Results
| Function | Description |
|----------|-------------|
| \`AssertNotBlocked(t, result)\` | Verify tool was not blocked |
| \`AssertBlocked(t, result, reason)\` | Verify tool was blocked with reason |
| \`AssertInputHandled(t, result, action)\` | Verify input was handled |
| \`AssertInputTransformed(t, result, text)\` | Verify input was transformed |
### Context Interactions
| Function | Description |
|----------|-------------|
| \`AssertPrinted(t, harness, text)\` | Verify exact print output |
| \`AssertPrintedContains(t, harness, substring)\` | Verify partial print output |
| \`AssertPrintInfo(t, harness, text)\` | Verify PrintInfo was called |
| \`AssertPrintError(t, harness, text)\` | Verify PrintError was called |
| \`AssertWidgetSet(t, harness, id)\` | Verify widget was set |
| \`AssertWidgetNotSet(t, harness, id)\` | Verify widget was not set |
| \`AssertWidgetText(t, harness, id, text)\` | Verify widget content |
| \`AssertWidgetTextContains(t, harness, id, substring)\` | Verify widget contains text |
| \`AssertHeaderSet(t, harness)\` | Verify header was set |
| \`AssertFooterSet(t, harness)\` | Verify footer was set |
| \`AssertStatusSet(t, harness, key)\` | Verify status was set |
| \`AssertStatusText(t, harness, key, text)\` | Verify status text |
### Registration
| Function | Description |
|----------|-------------|
| \`AssertToolRegistered(t, harness, name)\` | Verify tool registration |
| \`AssertCommandRegistered(t, harness, name)\` | Verify command registration |
| \`AssertHasHandlers(t, harness, eventType)\` | Verify handlers exist |
| \`AssertNoHandlers(t, harness, eventType)\` | Verify no handlers |
### Messaging
| Function | Description |
|----------|-------------|
| \`AssertMessageSent(t, harness, text)\` | Verify SendMessage was called |
| \`AssertCancelAndSend(t, harness, text)\` | Verify CancelAndSend was called |
## Helper Functions
For custom assertions, extract result details:
\`\`\`go
result, _ := harness.Emit(extensions.ToolCallEvent{...})
tcr := test.GetToolCallResult(result)
if tcr != nil {
t.Logf("Block: %v, Reason: %s", tcr.Block, tcr.Reason)
}
ir := test.GetInputResult(result)
trr := test.GetToolResultResult(result)
\`\`\`
## Advanced Usage
### Accessing the Mock Context
For custom verification:
\`\`\`go
ctx := harness.Context()
// Get all recorded prints
prints := ctx.GetPrints()
// Check options
value := ctx.GetOption("my-option")
// Verify widget properties
widget, ok := ctx.GetWidget("my-widget")
if ok && widget.Style.BorderColor == "#ff0000" {
t.Log("Widget has red border")
}
// Check status entries
status, ok := ctx.GetStatus("myext:status")
\`\`\`
### Testing Multiple Extensions
Each harness is isolated:
\`\`\`go
harness1 := test.New(t)
harness1.LoadFile("ext1.go")
harness2 := test.New(t)
harness2.LoadFile("ext2.go")
// Events to one don't affect the other
\`\`\`
### Running Tests
Run all tests in your extension directory:
\`\`\`bash
cd examples/extensions
go test -v
\`\`\`
Run with race detector:
\`\`\`bash
go test -race -v
\`\`\`
Run a specific test:
\`\`\`bash
go test -v -run TestMyExtension
\`\`\`
## Best Practices
1. **Test one behavior per test** — Keep tests focused and readable
2. **Use inline source for edge cases** — \`LoadString()\` is great for testing specific scenarios
3. **Use \`LoadFile()\` for integration tests** — Tests the actual extension file
4. **Assert on context calls** — Verify your extension interacts with the context correctly
5. **Test both positive and negative cases** — Verify tools are blocked AND allowed appropriately
6. **Test all event handlers** — Make sure all registered handlers work correctly
7. **Use descriptive test names** — \`TestExtension_BlocksDangerousTools\` is clearer than \`Test1\`
## Limitations
The test harness has these intentional limitations:
- **No TUI rendering** — Widgets are recorded but not rendered visually
- **Prompts return configured values** — Pre-configure prompt results in tests
- **Subagents don't spawn real processes** — \`SpawnSubagent()\` returns nil/empty results
- **LLM completions are mocked** — \`Complete()\` returns empty responses
- **Some context methods are no-ops** — \`Exit()\`, \`SetActiveTools()\`, etc. don't have side effects
These limitations focus testing on extension logic rather than the full Kit runtime.
## Complete Example
See \`examples/extensions/tool-logger_test.go\` for a complete example with 14 tests covering:
- Handler registration
- Tool call and result handling
- Session lifecycle events
- Input commands (\`!time\`, \`!status\`)
- Unknown command handling
- Concurrent operations (race condition check)
- Real file logging verification
`};export{s as default};
-130
View File
@@ -1,130 +0,0 @@
const s={frontmatter:{title:"Testing with tmux",description:"Test Kit's TUI non-interactively using tmux.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="testing-with-tmux"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-with-tmux"><span class="icon icon-link"></span></a>Testing with tmux</h1>
<p>Kit's interactive TUI can be tested non-interactively using tmux. This is useful for automated testing, CI pipelines, and extension development.</p>
<h2 id="basic-pattern"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#basic-pattern"><span class="icon icon-link"></span></a>Basic pattern</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Start Kit in a detached tmux session</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> new-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 120</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -y</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 40</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "output/kit -e ext.go --no-session 2&gt;kit_stderr.log"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Wait for startup</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Capture the current screen</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Send input</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> send-keys</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/command'</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Enter</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Wait for response</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Capture updated screen</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Cleanup</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kill-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span></span></code></pre>
<h2 id="testing-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-extensions"><span class="icon icon-link"></span></a>Testing extensions</h2>
<p>When testing extensions, the pattern is:</p>
<ol>
<li>Build Kit with your changes</li>
<li>Start Kit in tmux with the extension loaded</li>
<li>Send slash commands or prompts</li>
<li>Capture and verify the screen output</li>
<li>Check stderr logs for errors</li>
</ol>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Build first</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> output/kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd/kit</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Start with extension</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> new-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 120</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -y</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 40</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "output/kit -e examples/extensions/widget-status.go --no-session 2&gt;kit_stderr.log"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Verify widget appears in screen</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Status"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Send a slash command</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> send-keys</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/stats'</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Enter</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Cleanup</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kill-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span></span></code></pre>
<h2 id="tips"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#tips"><span class="icon icon-link"></span></a>Tips</h2>
<ul>
<li>Use <code>-x</code> and <code>-y</code> to set consistent terminal dimensions</li>
<li>Redirect stderr to a log file (<code>2&gt;kit.log</code>) for debugging</li>
<li>Use <code>--no-session</code> to avoid creating session files during tests</li>
<li>Add sufficient <code>sleep</code> between commands for the TUI to render</li>
<li>Use <code>grep</code> on captured pane output to verify specific content</li>
</ul>`,headings:[{depth:2,text:"Basic pattern",id:"basic-pattern"},{depth:2,text:"Testing extensions",id:"testing-extensions"},{depth:2,text:"Tips",id:"tips"}],raw:`
# Testing with tmux
Kit's interactive TUI can be tested non-interactively using tmux. This is useful for automated testing, CI pipelines, and extension development.
## Basic pattern
\`\`\`bash
# Start Kit in a detached tmux session
tmux new-session -d -s kittest -x 120 -y 40 \\
"output/kit -e ext.go --no-session 2>kit_stderr.log"
# Wait for startup
sleep 3
# Capture the current screen
tmux capture-pane -t kittest -p
# Send input
tmux send-keys -t kittest '/command' Enter
# Wait for response
sleep 2
# Capture updated screen
tmux capture-pane -t kittest -p
# Cleanup
tmux kill-session -t kittest
\`\`\`
## Testing extensions
When testing extensions, the pattern is:
1. Build Kit with your changes
2. Start Kit in tmux with the extension loaded
3. Send slash commands or prompts
4. Capture and verify the screen output
5. Check stderr logs for errors
\`\`\`bash
# Build first
go build -o output/kit ./cmd/kit
# Start with extension
tmux new-session -d -s kittest -x 120 -y 40 \\
"output/kit -e examples/extensions/widget-status.go --no-session 2>kit_stderr.log"
sleep 3
# Verify widget appears in screen
tmux capture-pane -t kittest -p | grep "Status"
# Send a slash command
tmux send-keys -t kittest '/stats' Enter
sleep 1
tmux capture-pane -t kittest -p
# Cleanup
tmux kill-session -t kittest
\`\`\`
## Tips
- Use \`-x\` and \`-y\` to set consistent terminal dimensions
- Redirect stderr to a log file (\`2>kit.log\`) for debugging
- Use \`--no-session\` to avoid creating session files during tests
- Add sufficient \`sleep\` between commands for the TUI to render
- Use \`grep\` on captured pane output to verify specific content
`};export{s as default};
-1
View File
@@ -1 +0,0 @@
const e={};export{e as default};
-685
View File
@@ -1,685 +0,0 @@
const s={frontmatter:{title:"Themes",description:"Customize Kit's appearance with built-in themes, custom theme files, and the extension theme API.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#themes"><span class="icon icon-link"></span></a>Themes</h1>
<p>Kit 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.</p>
<h2 id="quick-start"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#quick-start"><span class="icon icon-link"></span></a>Quick start</h2>
<p>Switch themes at runtime with the <code>/theme</code> command:</p>
<pre><code>/theme dracula
/theme catppuccin
/theme kitt
</code></pre>
<p>Run <code>/theme</code> with no arguments to list all available themes.</p>
<p><strong>Theme selections are automatically saved</strong> to <code>~/.config/kit/preferences.yml</code> and restored on next launch. You don't need to add anything to your config file — just <code>/theme &lt;name&gt;</code> and it sticks. This persistence also applies to <strong>model</strong> and <strong>thinking level</strong> selections.</p>
<h2 id="built-in-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#built-in-themes"><span class="icon icon-link"></span></a>Built-in themes</h2>
<table>
<thead>
<tr>
<th>Theme</th>
<th>Style</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>kitt</code></td>
<td>KITT-inspired reds and ambers (default)</td>
</tr>
<tr>
<td><code>catppuccin</code></td>
<td>Soothing pastels (Mocha/Latte)</td>
</tr>
<tr>
<td><code>dracula</code></td>
<td>Purple and cyan dark theme</td>
</tr>
<tr>
<td><code>tokyonight</code></td>
<td>Cool blues with warm accents</td>
</tr>
<tr>
<td><code>nord</code></td>
<td>Arctic, north-bluish palette</td>
</tr>
<tr>
<td><code>gruvbox</code></td>
<td>Retro groove colors</td>
</tr>
<tr>
<td><code>monokai</code></td>
<td>Classic syntax theme</td>
</tr>
<tr>
<td><code>solarized</code></td>
<td>Precision colors for machines and people</td>
</tr>
<tr>
<td><code>github</code></td>
<td>GitHub's light and dark palettes</td>
</tr>
<tr>
<td><code>one-dark</code></td>
<td>Atom One Dark</td>
</tr>
<tr>
<td><code>rose-pine</code></td>
<td>Soho vibes with muted tones</td>
</tr>
<tr>
<td><code>ayu</code></td>
<td>Simple with bright colors</td>
</tr>
<tr>
<td><code>material</code></td>
<td>Material Design palette</td>
</tr>
<tr>
<td><code>everforest</code></td>
<td>Green-focused comfortable theme</td>
</tr>
<tr>
<td><code>kanagawa</code></td>
<td>Dark theme inspired by Katsushika Hokusai</td>
</tr>
<tr>
<td><code>amoled</code></td>
<td>Pure black background, vivid accents</td>
</tr>
<tr>
<td><code>synthwave</code></td>
<td>Retro neon glows</td>
</tr>
<tr>
<td><code>vesper</code></td>
<td>Warm minimalist dark theme</td>
</tr>
<tr>
<td><code>flexoki</code></td>
<td>Inky reading palette</td>
</tr>
<tr>
<td><code>matrix</code></td>
<td>Green-on-black terminal aesthetic</td>
</tr>
<tr>
<td><code>vercel</code></td>
<td>Clean monochrome with blue accents</td>
</tr>
<tr>
<td><code>zenburn</code></td>
<td>Low-contrast, warm dark theme</td>
</tr>
</tbody>
</table>
<p>All themes support both light and dark terminal modes via adaptive colors.</p>
<h2 id="custom-theme-files"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#custom-theme-files"><span class="icon icon-link"></span></a>Custom theme files</h2>
<p>Create a <code>.yml</code>, <code>.yaml</code>, or <code>.json</code> file with color definitions. Kit discovers themes from two directories:</p>
<table>
<thead>
<tr>
<th>Location</th>
<th>Scope</th>
<th>Precedence</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>~/.config/kit/themes/</code></td>
<td>User (global)</td>
<td>Overrides built-ins</td>
</tr>
<tr>
<td><code>.kit/themes/</code></td>
<td>Project-local</td>
<td>Overrides user and built-ins</td>
</tr>
</tbody>
</table>
<h3 id="theme-file-format"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#theme-file-format"><span class="icon icon-link"></span></a>Theme file format</h3>
<p>A theme file defines adaptive color pairs with <code>light</code> and <code>dark</code> hex values. Any field left empty inherits from the default KITT theme.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># ~/.config/kit/themes/my-theme.yml</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Core semantic colors</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">primary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#8839ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cba6f7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">secondary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#04a5e5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89dceb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">success</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#40a02b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#a6e3a1"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">warning</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#df8e1d"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f9e2af"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#d20f39"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f38ba8"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">info</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e66f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89b4fa"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Text and chrome</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">text</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#4c4f69"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cdd6f4"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">muted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#6c6f85"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#a6adc8"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">very-muted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#9ca0b0"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#6c7086"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">background</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#eff1f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e1e2e"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">border</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#acb0be"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#585b70"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">muted-border</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#ccd0da"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#313244"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Semantic roles</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">system</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#179299"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#94e2d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">tool</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fe640b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fab387"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">accent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#ea76cb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f5c2e7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">highlight</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#e6e9ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#181825"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Diff backgrounds</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-insert-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#d5f0d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a3a2a"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-delete-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f5d5d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#3a1a2a"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-equal-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#eceef3"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#232336"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-missing-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#e4e6eb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a1a2e"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Code block backgrounds</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">code-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#eceef3"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#232336"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">gutter-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#e4e6eb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a1a2e"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">write-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#d5f0d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a3a2a"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Markdown and syntax highlighting</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">markdown</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> heading</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e66f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89b4fa"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> link</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e66f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89b4fa"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> keyword</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#8839ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cba6f7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#40a02b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#a6e3a1"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fe640b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fab387"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> comment</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#9ca0b0"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#6c7086"</span></span></code></pre>
<h3 id="partial-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#partial-themes"><span class="icon icon-link"></span></a>Partial themes</h3>
<p>You only need to define the colors you want to change. Unspecified fields fall back to the default theme:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Just override the primary and accent colors</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">primary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF00FF"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">accent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FFFF"</span></span></code></pre>
<h3 id="distributing-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#distributing-themes"><span class="icon icon-link"></span></a>Distributing themes</h3>
<ul>
<li><strong>Personal</strong>: Drop a file in <code>~/.config/kit/themes/</code></li>
<li><strong>Team/project</strong>: Drop a file in <code>.kit/themes/</code> and commit it to version control</li>
<li><strong>Override built-in</strong>: Name your file the same as a built-in (e.g., <code>dracula.yml</code>) and it takes precedence</li>
</ul>
<h2 id="config-file-theme"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#config-file-theme"><span class="icon icon-link"></span></a>Config file theme</h2>
<p>You can also set theme colors directly in <code>.kit.yml</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">theme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> primary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#8839ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cba6f7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF0000"</span></span></code></pre>
<p>Or reference an external theme file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">theme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./themes/my-custom-theme.yml"</span></span></code></pre>
<h2 id="extension-theme-api"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-theme-api"><span class="icon icon-link"></span></a>Extension theme API</h2>
<p>Extensions can register and switch themes programmatically at runtime.</p>
<h3 id="registering-a-theme"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#registering-a-theme"><span class="icon icon-link"></span></a>Registering a theme</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSessionStart</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">RegisterTheme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"neon"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColorConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Primary: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CC00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Secondary: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#0088CC"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FFFF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Success: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00CC44"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FF66"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Warning: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CCAA00"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FFFF00"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Error: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CC0033"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF0055"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Info: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#0088CC"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00CCFF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Text: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#111111"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#F0F0F0"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Background: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#F0F0F0"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#0A0A14"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MdKeyword: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CC00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MdString: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00CC44"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FF66"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MdComment: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#888888"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#555555"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h3 id="switching-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#switching-themes"><span class="icon icon-link"></span></a>Switching themes</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetTheme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"dracula"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h3 id="listing-available-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#listing-available-themes"><span class="icon icon-link"></span></a>Listing available themes</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">names </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListThemes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<h3 id="themecolorconfig-fields"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#themecolorconfig-fields"><span class="icon icon-link"></span></a>ThemeColorConfig fields</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Primary</code></td>
<td>Main brand/accent color</td>
</tr>
<tr>
<td><code>Secondary</code></td>
<td>Secondary accent</td>
</tr>
<tr>
<td><code>Success</code></td>
<td>Success states</td>
</tr>
<tr>
<td><code>Warning</code></td>
<td>Warning states</td>
</tr>
<tr>
<td><code>Error</code></td>
<td>Error/critical states</td>
</tr>
<tr>
<td><code>Info</code></td>
<td>Informational states</td>
</tr>
<tr>
<td><code>Text</code></td>
<td>Primary text</td>
</tr>
<tr>
<td><code>Muted</code></td>
<td>Dimmed text</td>
</tr>
<tr>
<td><code>VeryMuted</code></td>
<td>Very dimmed text</td>
</tr>
<tr>
<td><code>Background</code></td>
<td>Base background</td>
</tr>
<tr>
<td><code>Border</code></td>
<td>Panel borders</td>
</tr>
<tr>
<td><code>MutedBorder</code></td>
<td>Subtle dividers</td>
</tr>
<tr>
<td><code>System</code></td>
<td>System messages</td>
</tr>
<tr>
<td><code>Tool</code></td>
<td>Tool-related elements</td>
</tr>
<tr>
<td><code>Accent</code></td>
<td>Secondary highlight</td>
</tr>
<tr>
<td><code>Highlight</code></td>
<td>Highlighted regions</td>
</tr>
<tr>
<td><code>MdHeading</code></td>
<td>Markdown headings</td>
</tr>
<tr>
<td><code>MdLink</code></td>
<td>Markdown links</td>
</tr>
<tr>
<td><code>MdKeyword</code></td>
<td>Syntax: keywords</td>
</tr>
<tr>
<td><code>MdString</code></td>
<td>Syntax: strings</td>
</tr>
<tr>
<td><code>MdNumber</code></td>
<td>Syntax: numbers</td>
</tr>
<tr>
<td><code>MdComment</code></td>
<td>Syntax: comments</td>
</tr>
</tbody>
</table>
<p>Each field is an <code>ext.ThemeColor</code> with <code>Light</code> and <code>Dark</code> hex strings. Empty fields inherit from the default theme.</p>
<h2 id="precedence-order"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#precedence-order"><span class="icon icon-link"></span></a>Precedence order</h2>
<p>When multiple sources define the same theme name, later sources win:</p>
<ol>
<li>Built-in presets (lowest)</li>
<li>User themes (<code>~/.config/kit/themes/</code>)</li>
<li>Project-local themes (<code>.kit/themes/</code>)</li>
<li>Extension-registered themes (highest)</li>
</ol>
<h3 id="startup-theme-resolution"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#startup-theme-resolution"><span class="icon icon-link"></span></a>Startup theme resolution</h3>
<p>At startup, Kit determines which theme to apply:</p>
<ol>
<li><strong><code>.kit.yml</code> <code>theme:</code> key</strong> — explicit config always wins (highest priority)</li>
<li><strong><code>~/.config/kit/preferences.yml</code></strong> — persisted <code>/theme</code> selection</li>
<li><strong>Default <code>kitt</code> theme</strong> — fallback</li>
</ol>
<p>The preferences file is updated automatically whenever you use <code>/theme</code> or <code>ctx.SetTheme()</code>. It is separate from <code>.kit.yml</code> so it never clobbers your config comments or formatting.</p>
<p>Theme changes via <code>/theme</code> or <code>ctx.SetTheme()</code> take effect immediately on all UI elements, including previously rendered messages.</p>`,headings:[{depth:2,text:"Quick start",id:"quick-start"},{depth:2,text:"Built-in themes",id:"built-in-themes"},{depth:2,text:"Custom theme files",id:"custom-theme-files"},{depth:3,text:"Theme file format",id:"theme-file-format"},{depth:3,text:"Partial themes",id:"partial-themes"},{depth:3,text:"Distributing themes",id:"distributing-themes"},{depth:2,text:"Config file theme",id:"config-file-theme"},{depth:2,text:"Extension theme API",id:"extension-theme-api"},{depth:3,text:"Registering a theme",id:"registering-a-theme"},{depth:3,text:"Switching themes",id:"switching-themes"},{depth:3,text:"Listing available themes",id:"listing-available-themes"},{depth:3,text:"ThemeColorConfig fields",id:"themecolorconfig-fields"},{depth:2,text:"Precedence order",id:"precedence-order"},{depth:3,text:"Startup theme resolution",id:"startup-theme-resolution"}],raw:`
# Themes
Kit 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.
## Quick start
Switch themes at runtime with the \`/theme\` command:
\`\`\`
/theme dracula
/theme catppuccin
/theme kitt
\`\`\`
Run \`/theme\` with no arguments to list all available themes.
**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.
## Built-in themes
| Theme | Style |
|-------|-------|
| \`kitt\` | KITT-inspired reds and ambers (default) |
| \`catppuccin\` | Soothing pastels (Mocha/Latte) |
| \`dracula\` | Purple and cyan dark theme |
| \`tokyonight\` | Cool blues with warm accents |
| \`nord\` | Arctic, north-bluish palette |
| \`gruvbox\` | Retro groove colors |
| \`monokai\` | Classic syntax theme |
| \`solarized\` | Precision colors for machines and people |
| \`github\` | GitHub's light and dark palettes |
| \`one-dark\` | Atom One Dark |
| \`rose-pine\` | Soho vibes with muted tones |
| \`ayu\` | Simple with bright colors |
| \`material\` | Material Design palette |
| \`everforest\` | Green-focused comfortable theme |
| \`kanagawa\` | Dark theme inspired by Katsushika Hokusai |
| \`amoled\` | Pure black background, vivid accents |
| \`synthwave\` | Retro neon glows |
| \`vesper\` | Warm minimalist dark theme |
| \`flexoki\` | Inky reading palette |
| \`matrix\` | Green-on-black terminal aesthetic |
| \`vercel\` | Clean monochrome with blue accents |
| \`zenburn\` | Low-contrast, warm dark theme |
All themes support both light and dark terminal modes via adaptive colors.
## Custom theme files
Create a \`.yml\`, \`.yaml\`, or \`.json\` file with color definitions. Kit discovers themes from two directories:
| Location | Scope | Precedence |
|----------|-------|------------|
| \`~/.config/kit/themes/\` | User (global) | Overrides built-ins |
| \`.kit/themes/\` | Project-local | Overrides user and built-ins |
### Theme file format
A theme file defines adaptive color pairs with \`light\` and \`dark\` hex values. Any field left empty inherits from the default KITT theme.
\`\`\`yaml
# ~/.config/kit/themes/my-theme.yml
# Core semantic colors
primary:
light: "#8839ef"
dark: "#cba6f7"
secondary:
light: "#04a5e5"
dark: "#89dceb"
success:
light: "#40a02b"
dark: "#a6e3a1"
warning:
light: "#df8e1d"
dark: "#f9e2af"
error:
light: "#d20f39"
dark: "#f38ba8"
info:
light: "#1e66f5"
dark: "#89b4fa"
# Text and chrome
text:
light: "#4c4f69"
dark: "#cdd6f4"
muted:
light: "#6c6f85"
dark: "#a6adc8"
very-muted:
light: "#9ca0b0"
dark: "#6c7086"
background:
light: "#eff1f5"
dark: "#1e1e2e"
border:
light: "#acb0be"
dark: "#585b70"
muted-border:
light: "#ccd0da"
dark: "#313244"
# Semantic roles
system:
light: "#179299"
dark: "#94e2d5"
tool:
light: "#fe640b"
dark: "#fab387"
accent:
light: "#ea76cb"
dark: "#f5c2e7"
highlight:
light: "#e6e9ef"
dark: "#181825"
# Diff backgrounds
diff-insert-bg:
light: "#d5f0d5"
dark: "#1a3a2a"
diff-delete-bg:
light: "#f5d5d5"
dark: "#3a1a2a"
diff-equal-bg:
light: "#eceef3"
dark: "#232336"
diff-missing-bg:
light: "#e4e6eb"
dark: "#1a1a2e"
# Code block backgrounds
code-bg:
light: "#eceef3"
dark: "#232336"
gutter-bg:
light: "#e4e6eb"
dark: "#1a1a2e"
write-bg:
light: "#d5f0d5"
dark: "#1a3a2a"
# Markdown and syntax highlighting
markdown:
heading:
light: "#1e66f5"
dark: "#89b4fa"
link:
light: "#1e66f5"
dark: "#89b4fa"
keyword:
light: "#8839ef"
dark: "#cba6f7"
string:
light: "#40a02b"
dark: "#a6e3a1"
number:
light: "#fe640b"
dark: "#fab387"
comment:
light: "#9ca0b0"
dark: "#6c7086"
\`\`\`
### Partial themes
You only need to define the colors you want to change. Unspecified fields fall back to the default theme:
\`\`\`yaml
# Just override the primary and accent colors
primary:
dark: "#FF00FF"
accent:
dark: "#00FFFF"
\`\`\`
### Distributing themes
- **Personal**: Drop a file in \`~/.config/kit/themes/\`
- **Team/project**: Drop a file in \`.kit/themes/\` and commit it to version control
- **Override built-in**: Name your file the same as a built-in (e.g., \`dracula.yml\`) and it takes precedence
## Config file theme
You can also set theme colors directly in \`.kit.yml\`:
\`\`\`yaml
theme:
primary:
light: "#8839ef"
dark: "#cba6f7"
error:
dark: "#FF0000"
\`\`\`
Or reference an external theme file:
\`\`\`yaml
theme: "./themes/my-custom-theme.yml"
\`\`\`
## Extension theme API
Extensions can register and switch themes programmatically at runtime.
### Registering a theme
\`\`\`go
api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
ctx.RegisterTheme("neon", ext.ThemeColorConfig{
Primary: ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
Secondary: ext.ThemeColor{Light: "#0088CC", Dark: "#00FFFF"},
Success: ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
Warning: ext.ThemeColor{Light: "#CCAA00", Dark: "#FFFF00"},
Error: ext.ThemeColor{Light: "#CC0033", Dark: "#FF0055"},
Info: ext.ThemeColor{Light: "#0088CC", Dark: "#00CCFF"},
Text: ext.ThemeColor{Light: "#111111", Dark: "#F0F0F0"},
Background: ext.ThemeColor{Light: "#F0F0F0", Dark: "#0A0A14"},
MdKeyword: ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
MdString: ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
MdComment: ext.ThemeColor{Light: "#888888", Dark: "#555555"},
})
})
\`\`\`
### Switching themes
\`\`\`go
err := ctx.SetTheme("dracula")
\`\`\`
### Listing available themes
\`\`\`go
names := ctx.ListThemes()
\`\`\`
### ThemeColorConfig fields
| Field | Description |
|-------|-------------|
| \`Primary\` | Main brand/accent color |
| \`Secondary\` | Secondary accent |
| \`Success\` | Success states |
| \`Warning\` | Warning states |
| \`Error\` | Error/critical states |
| \`Info\` | Informational states |
| \`Text\` | Primary text |
| \`Muted\` | Dimmed text |
| \`VeryMuted\` | Very dimmed text |
| \`Background\` | Base background |
| \`Border\` | Panel borders |
| \`MutedBorder\` | Subtle dividers |
| \`System\` | System messages |
| \`Tool\` | Tool-related elements |
| \`Accent\` | Secondary highlight |
| \`Highlight\` | Highlighted regions |
| \`MdHeading\` | Markdown headings |
| \`MdLink\` | Markdown links |
| \`MdKeyword\` | Syntax: keywords |
| \`MdString\` | Syntax: strings |
| \`MdNumber\` | Syntax: numbers |
| \`MdComment\` | Syntax: comments |
Each field is an \`ext.ThemeColor\` with \`Light\` and \`Dark\` hex strings. Empty fields inherit from the default theme.
## Precedence order
When multiple sources define the same theme name, later sources win:
1. Built-in presets (lowest)
2. User themes (\`~/.config/kit/themes/\`)
3. Project-local themes (\`.kit/themes/\`)
4. Extension-registered themes (highest)
### Startup theme resolution
At startup, Kit determines which theme to apply:
1. **\`.kit.yml\` \`theme:\` key** — explicit config always wins (highest priority)
2. **\`~/.config/kit/preferences.yml\`** — persisted \`/theme\` selection
3. **Default \`kitt\` theme** — fallback
The 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.
Theme changes via \`/theme\` or \`ctx.SetTheme()\` take effect immediately on all UI elements, including previously rendered messages.
`};export{s as default};
+95
View File
@@ -0,0 +1,95 @@
{
"$schema": "https://btca.dev/btca.schema.json",
"resources": [
{
"type": "git",
"name": "bubbletea",
"url": "https://github.com/charmbracelet/bubbletea",
"branch": "main"
},
{
"type": "git",
"name": "lipgloss",
"url": "https://github.com/charmbracelet/lipgloss",
"branch": "main"
},
{
"type": "git",
"name": "bubbles",
"url": "https://github.com/charmbracelet/bubbles",
"branch": "main"
},
{
"type": "git",
"name": "glamour",
"url": "https://github.com/charmbracelet/glamour",
"branch": "v2-exp"
},
{
"type": "git",
"name": "fantasy",
"url": "https://github.com/charmbracelet/fantasy",
"branch": "main"
},
{
"type": "git",
"name": "catwalk",
"url": "https://github.com/charmbracelet/catwalk",
"branch": "main"
},
{
"type": "git",
"name": "crush",
"url": "https://github.com/charmbracelet/crush",
"branch": "main"
},
{
"type": "git",
"name": "pi",
"url": "https://github.com/badlogic/pi-mono",
"branch": "main",
"searchPaths": [
"packages/coding-agent",
"packages/tui"
]
},
{
"type": "git",
"name": "iteratr",
"url": "https://github.com/mark3labs/iteratr",
"branch": "master"
},
{
"type": "git",
"name": "yaegi",
"url": "https://github.com/traefik/yaegi",
"branch": "master"
},
{
"type": "git",
"name": "acp-go-sdk",
"url": "https://github.com/coder/acp-go-sdk",
"branch": "main"
},
{
"type": "git",
"name": "opencode",
"url": "https://github.com/anomalyco/opencode",
"branch": "dev"
},
{
"type": "git",
"name": "herald",
"url": "https://github.com/indaco/herald",
"branch": "main"
},
{
"type": "git",
"name": "herald-md",
"url": "https://github.com/indaco/herald-md",
"branch": "main"
}
],
"model": "claude-haiku-4-5",
"provider": "opencode"
}
+162
View File
@@ -0,0 +1,162 @@
package cmd
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/charmbracelet/log"
acp "github.com/coder/acp-go-sdk"
"github.com/mark3labs/kit/internal/acpserver"
"github.com/spf13/cobra"
)
var acpCmd = &cobra.Command{
Use: "acp",
Short: "Start Kit as an ACP agent server",
Long: `Start Kit as an ACP (Agent Client Protocol) agent server.
Communicates over stdio (stdin/stdout) using JSON-RPC 2.0 with
newline-delimited JSON, compatible with OpenCode and other ACP clients.
The server exposes Kit's LLM execution, tool system, and session
management via the Agent Client Protocol. Sessions are persisted
to Kit's standard JSONL session files.`,
RunE: runACP,
}
func init() {
rootCmd.AddCommand(acpCmd)
}
func runACP(cmd *cobra.Command, _ []string) error {
// Create the ACP agent implementation.
agent := acpserver.NewAgent()
defer agent.Close()
// Create the stdio connection. The SDK reads JSON-RPC from stdin and
// writes responses to stdout. We wrap stdin with a normalizer that
// fills in optional fields the SDK's generated validation requires
// (e.g. mcpServers) so clients that omit them still work.
conn := acp.NewAgentSideConnection(agent, os.Stdout, newACPNormalizer(os.Stdin))
// Wire the connection back to the agent so it can send session updates.
agent.SetAgentConnection(conn)
// Enable debug logging to stderr if requested.
if debugMode {
conn.SetLogger(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelDebug,
})))
// Also set charmbracelet/log level for acpserver package logging
log.SetLevel(log.DebugLevel)
}
// Wait for either the client to disconnect or a signal.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
select {
case <-conn.Done():
fmt.Fprintln(os.Stderr, "kit: ACP client disconnected")
case sig := <-sigCh:
fmt.Fprintf(os.Stderr, "kit: received %s, shutting down\n", sig)
}
return nil
}
// acpNormalizer wraps an io.Reader carrying newline-delimited JSON-RPC and
// patches incoming messages so that fields the SDK validates as required —
// but that some clients (e.g. Zed) omit — are defaulted. This avoids
// InvalidParams errors without forking the SDK.
type acpNormalizer struct {
scanner *bufio.Scanner
buf bytes.Buffer // leftover bytes from the last normalized line
}
func newACPNormalizer(r io.Reader) *acpNormalizer {
const maxMsg = 10 * 1024 * 1024 // 10 MB, matches SDK buffer
s := bufio.NewScanner(r)
s.Buffer(make([]byte, 0, 1024*1024), maxMsg)
return &acpNormalizer{scanner: s}
}
// Read satisfies io.Reader. It feeds one normalized JSON line (plus newline)
// per underlying scan, buffering across short caller reads.
func (n *acpNormalizer) Read(p []byte) (int, error) {
// Drain any leftover bytes from the previous line first.
if n.buf.Len() > 0 {
return n.buf.Read(p)
}
if !n.scanner.Scan() {
if err := n.scanner.Err(); err != nil {
return 0, err
}
return 0, io.EOF
}
line := n.scanner.Bytes()
normalized := normalizeACPLine(line)
n.buf.Write(normalized)
n.buf.WriteByte('\n')
return n.buf.Read(p)
}
// normalizeACPLine ensures session/new and session/load params contain an
// mcpServers array. Returns the original line unchanged for all other methods.
func normalizeACPLine(line []byte) []byte {
// Quick check: if it already contains mcpServers, nothing to do.
if bytes.Contains(line, []byte(`"mcpServers"`)) {
return line
}
// Only bother parsing if the method could be session/new or session/load.
if !bytes.Contains(line, []byte(`"session/new"`)) &&
!bytes.Contains(line, []byte(`"session/load"`)) {
return line
}
var msg struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
if err := json.Unmarshal(line, &msg); err != nil {
return line
}
if msg.Method != "session/new" && msg.Method != "session/load" {
return line
}
// Patch params to include mcpServers: [].
var params map[string]json.RawMessage
if err := json.Unmarshal(msg.Params, &params); err != nil {
return line
}
if _, ok := params["mcpServers"]; ok {
return line
}
params["mcpServers"] = json.RawMessage(`[]`)
patched, err := json.Marshal(params)
if err != nil {
return line
}
msg.Params = patched
out, err := json.Marshal(msg)
if err != nil {
return line
}
return out
}
+790
View File
@@ -0,0 +1,790 @@
package cmd
import (
"context"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"charm.land/huh/v2"
"github.com/mark3labs/kit/internal/auth"
"github.com/mark3labs/kit/internal/ui"
kit "github.com/mark3labs/kit/pkg/kit"
"github.com/spf13/cobra"
)
// authCmd represents the auth command for managing AI provider authentication.
// This command provides subcommands for login, logout, and status checking
// of authentication credentials for various AI providers, with OAuth support
// for providers like Anthropic and OpenAI.
var authCmd = &cobra.Command{
Use: "auth",
Short: "Manage authentication credentials for AI providers",
Long: `Manage authentication credentials for AI providers.
This command allows you to securely authenticate and manage credentials for various AI providers
using OAuth flows. Stored credentials take precedence over environment variables.
Available providers:
- anthropic: Anthropic Claude API (OAuth)
- openai: OpenAI API (OAuth and API key)
- copilot: GitHub Copilot (GitHub device login)
Examples:
kit auth login anthropic
kit auth login openai
kit auth login copilot
kit auth logout anthropic
kit auth status`,
}
// authLoginCmd represents the login subcommand for authenticating with AI providers.
// It handles OAuth flow for supported providers, opening a browser for authentication
// and securely storing the resulting credentials for future use.
var authLoginCmd = &cobra.Command{
Use: "login [provider]",
Short: "Authenticate with an AI provider using OAuth",
Long: `Authenticate with an AI provider using OAuth flow.
This will open your browser to complete the OAuth authentication process.
Your credentials will be securely stored and will take precedence over
environment variables when making API calls.
Available providers:
- anthropic: Anthropic Claude API (OAuth)
- openai: OpenAI ChatGPT Plus/Pro (Codex OAuth)
- copilot: GitHub Copilot (GitHub device login, experimental)
Flags:
--set-default Set this provider's default model as the system default
Examples:
kit auth login anthropic
kit auth login openai
kit auth login copilot
kit auth login copilot --set-default`,
Args: cobra.ExactArgs(1),
RunE: runAuthLogin,
}
// authLogoutCmd represents the logout subcommand for removing stored authentication credentials.
// This command removes stored API keys or OAuth tokens for specified providers,
// requiring the user to authenticate again or use environment variables.
var authLogoutCmd = &cobra.Command{
Use: "logout [provider]",
Short: "Remove stored authentication credentials for a provider",
Long: `Remove stored authentication credentials for an AI provider.
This will delete the stored API key or OAuth credentials for the specified provider.
You will need to use environment variables or command-line flags for authentication after logout.
Available providers:
- anthropic: Anthropic Claude API
- openai: OpenAI API
- copilot: GitHub Copilot
Example:
kit auth logout anthropic
kit auth logout openai
kit auth logout copilot`,
Args: cobra.ExactArgs(1),
RunE: runAuthLogout,
}
// authStatusCmd represents the status subcommand for checking authentication status.
// It displays which providers have stored credentials, their types (OAuth vs API key),
// creation dates, and expiration status without revealing the actual credentials.
var authStatusCmd = &cobra.Command{
Use: "status",
Short: "Show authentication status for all providers",
Long: `Show the current authentication status for all supported AI providers.
This command displays which providers have stored credentials and when they were created.
It does not display the actual API keys for security reasons.
Example:
kit auth status`,
RunE: runAuthStatus,
}
var (
loginSetDefault bool
)
// defaultModels maps providers to their recommended default models.
// These are used when --set-default flag is passed to auth login.
var defaultModels = map[string]string{
"anthropic": "anthropic/claude-sonnet-4-5-20250929",
"openai": "openai/gpt-5.4",
"copilot": "copilot/gpt-5.5",
}
// setDefaultModelIfRequested sets the default model for the given provider
// if the --set-default flag was provided.
func setDefaultModelIfRequested(provider string) error {
if !loginSetDefault {
return nil
}
model, ok := defaultModels[provider]
if !ok {
return fmt.Errorf("no default model configured for provider: %s", provider)
}
if err := ui.SaveModelPreference(model); err != nil {
return fmt.Errorf("failed to save model preference: %w", err)
}
fmt.Printf("\n✓ Set default model to: %s\n", model)
return nil
}
func init() {
authCmd.AddCommand(authLoginCmd)
authCmd.AddCommand(authLogoutCmd)
authCmd.AddCommand(authStatusCmd)
authLoginCmd.Flags().BoolVar(&loginSetDefault, "set-default", false, "Set this provider's default model as the system default after login")
}
// runAuthLogin dispatches OAuth login to the selected provider.
func runAuthLogin(cmd *cobra.Command, args []string) error {
provider := strings.ToLower(args[0])
switch provider {
case "anthropic":
return loginAnthropic()
case "openai":
return loginOpenAI()
case "copilot":
return loginCopilot(cmd.Context())
default:
return fmt.Errorf("unsupported provider: %s. Available providers: anthropic, openai, copilot", provider)
}
}
func runAuthLogout(cmd *cobra.Command, args []string) error {
provider := strings.ToLower(args[0])
switch provider {
case "anthropic":
return logoutAnthropic()
case "openai":
return logoutOpenAI()
case "copilot":
return logoutCopilot()
default:
return fmt.Errorf("unsupported provider: %s. Available providers: anthropic, openai, copilot", provider)
}
}
func runAuthStatus(cmd *cobra.Command, args []string) error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
fmt.Println("Authentication Status")
fmt.Println("====================")
fmt.Printf("Credentials file: %s\n\n", cm.GetCredentialsPath())
// Check Anthropic credentials
fmt.Print("Anthropic Claude: ")
if hasAnthropicCreds, err := cm.HasAnthropicCredentials(); err != nil {
fmt.Printf("Error checking credentials: %v\n", err)
} else if hasAnthropicCreds {
if creds, err := cm.GetAnthropicCredentials(); err != nil {
fmt.Printf("Error reading credentials: %v\n", err)
} else {
authType := "API Key"
status := "✓ Authenticated"
if creds.Type == "oauth" {
authType = "OAuth"
if creds.IsExpired() {
status = "⚠️ Token expired (will refresh automatically)"
} else if creds.NeedsRefresh() {
status = "⚠️ Token expires soon (will refresh automatically)"
}
}
fmt.Printf("%s (%s, stored %s)\n", status, authType, creds.CreatedAt.Format("2006-01-02 15:04:05"))
}
} else {
fmt.Println("✗ Not authenticated")
// Check if environment variable is set
if os.Getenv("ANTHROPIC_API_KEY") != "" {
fmt.Println(" (ANTHROPIC_API_KEY environment variable is set)")
}
}
// Check OpenAI credentials
fmt.Print("\nOpenAI: ")
if hasOpenAICreds, err := cm.HasOpenAICredentials(); err != nil {
fmt.Printf("Error checking credentials: %v\n", err)
} else if hasOpenAICreds {
if creds, err := cm.GetOpenAICredentials(); err != nil {
fmt.Printf("Error reading credentials: %v\n", err)
} else {
authType := "API Key"
status := "✓ Authenticated"
if creds.Type == "oauth" {
authType = "OAuth (ChatGPT/Codex)"
if creds.IsExpired() {
status = "⚠️ Token expired (will refresh automatically)"
} else if creds.NeedsRefresh() {
status = "⚠️ Token expires soon (will refresh automatically)"
}
}
accountInfo := ""
if creds.Type == "oauth" && creds.AccountID != "" {
accountInfo = fmt.Sprintf(" [%s]", creds.AccountID)
}
fmt.Printf("%s (%s%s, stored %s)\n", status, authType, accountInfo, creds.CreatedAt.Format("2006-01-02 15:04:05"))
}
} else {
fmt.Println("✗ Not authenticated")
// Check if environment variable is set
if os.Getenv("OPENAI_API_KEY") != "" {
fmt.Println(" (OPENAI_API_KEY environment variable is set)")
}
}
// Check GitHub Copilot credentials
fmt.Print("\nGitHub Copilot: ")
if hasCopilotCreds, err := cm.HasCopilotCredentials(); err != nil {
fmt.Printf("Error checking credentials: %v\n", err)
} else if hasCopilotCreds {
if creds, err := cm.GetCopilotCredentials(); err != nil {
fmt.Printf("Error reading credentials: %v\n", err)
} else {
status := "✓ Authenticated"
if creds.IsExpired() {
status = "⚠️ Token expired (will refresh automatically)"
} else if creds.NeedsRefresh() {
status = "⚠️ Token expires soon (will refresh automatically)"
}
fmt.Printf("%s (GitHub OAuth, stored %s)\n", status, creds.CreatedAt.Format("2006-01-02 15:04:05"))
}
} else {
fmt.Println("✗ Not authenticated")
}
fmt.Println("\nTo authenticate with a provider:")
fmt.Println(" kit auth login anthropic")
fmt.Println(" kit auth login openai")
fmt.Println(" kit auth login copilot")
return nil
}
func loginAnthropic() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if already authenticated
if hasAuth, err := cm.HasAnthropicCredentials(); err == nil && hasAuth {
var reauth bool
err := huh.NewConfirm().
Title("You are already authenticated with Anthropic").
Description("Do you want to re-authenticate?").
Affirmative("Yes").
Negative("No").
Value(&reauth).
Run()
if err != nil || !reauth {
fmt.Println("Authentication cancelled.")
return nil
}
}
// Create OAuth client
client := auth.NewOAuthClient()
// Generate authorization URL
fmt.Println("🔐 Starting OAuth authentication with Anthropic...")
authData, err := client.GetAuthorizationURL()
if err != nil {
return fmt.Errorf("failed to generate authorization URL: %w", err)
}
// Display URL and try to open browser
fmt.Println("\n📱 Opening your browser for authentication...")
fmt.Println("If the browser doesn't open automatically, please visit this URL:")
fmt.Printf("\n%s\n\n", authData.URL)
// Try to open browser
auth.TryOpenBrowser(authData.URL)
// Wait for user to complete OAuth flow
fmt.Println("After authorizing the application, you'll receive an authorization code.")
var code string
err = huh.NewInput().
Title("Authorization code").
Description("Paste the code from your browser").
Value(&code).
Run()
if err != nil {
return fmt.Errorf("failed to read authorization code: %w", err)
}
code = strings.TrimSpace(code)
if code == "" {
return fmt.Errorf("authorization code cannot be empty")
}
// Exchange code for tokens
fmt.Println("\n🔄 Exchanging authorization code for access token...")
creds, err := client.ExchangeCode(code, authData.Verifier)
if err != nil {
return fmt.Errorf("failed to exchange authorization code: %w", err)
}
// Store the credentials
if err := cm.SetOAuthCredentials(creds); err != nil {
return fmt.Errorf("failed to store credentials: %w", err)
}
fmt.Println("✅ Successfully authenticated with Anthropic!")
fmt.Printf("📁 Credentials stored in: %s\n", cm.GetCredentialsPath())
fmt.Println("\n🎉 Your OAuth credentials will now be used for Anthropic API calls.")
fmt.Println("💡 You can check your authentication status with: kit auth status")
// Set default model if requested
if err := setDefaultModelIfRequested("anthropic"); err != nil {
return err
}
// Remind users how to set this as default if they didn't use --set-default
if !loginSetDefault {
fmt.Println("\n💡 To set Anthropic as your default model, run:")
fmt.Println(" kit auth login anthropic --set-default")
}
return nil
}
func logoutAnthropic() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if authenticated
hasAuth, err := cm.HasAnthropicCredentials()
if err != nil {
return fmt.Errorf("failed to check authentication status: %w", err)
}
if !hasAuth {
fmt.Println("You are not currently authenticated with Anthropic.")
return nil
}
// Confirm logout
var confirm bool
err = huh.NewConfirm().
Title("Remove Anthropic credentials").
Description("Are you sure you want to remove your stored credentials?").
Affirmative("Yes").
Negative("No").
Value(&confirm).
Run()
if err != nil || !confirm {
fmt.Println("Logout cancelled.")
return nil
}
// Remove credentials
if err := cm.RemoveAnthropicCredentials(); err != nil {
return fmt.Errorf("failed to remove credentials: %w", err)
}
fmt.Println("✓ Successfully logged out from Anthropic!")
fmt.Println("You will need to use environment variables or command-line flags for authentication.")
return nil
}
func loginOpenAI() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if already authenticated
if hasAuth, err := cm.HasOpenAICredentials(); err == nil && hasAuth {
var reauth bool
err := huh.NewConfirm().
Title("You are already authenticated with OpenAI (ChatGPT/Codex)").
Description("Do you want to re-authenticate?").
Affirmative("Yes").
Negative("No").
Value(&reauth).
Run()
if err != nil || !reauth {
fmt.Println("Authentication cancelled.")
return nil
}
}
// Create OAuth client
client := auth.NewOpenAIOAuthClient()
// Generate authorization URL
fmt.Println("🔐 Starting OAuth authentication with OpenAI (ChatGPT/Codex)...")
fmt.Println("This will open your browser to authenticate with your ChatGPT account.")
fmt.Println()
authData, err := client.GetAuthorizationURL()
if err != nil {
return fmt.Errorf("failed to generate authorization URL: %w", err)
}
// Start local callback server
callbackServer, err := startOpenAICallbackServer(authData.State)
if err != nil {
fmt.Printf("⚠️ Could not start local callback server: %v\n", err)
fmt.Println("Falling back to manual code entry.")
}
if callbackServer != nil {
defer callbackServer.Close()
}
// Display URL and try to open browser
fmt.Println("📱 Opening your browser for authentication...")
fmt.Println("If the browser doesn't open automatically, please visit this URL:")
fmt.Printf("\n%s\n\n", authData.URL)
// Try to open browser
auth.TryOpenBrowser(authData.URL)
// Wait for callback or manual input
var code string
if callbackServer != nil {
fmt.Println("Waiting for browser authentication...")
select {
case callbackCode := <-callbackServer.CodeChan:
if callbackCode != "" {
code = callbackCode
fmt.Println("✓ Received authorization code from browser callback.")
}
case <-time.After(2 * time.Minute):
fmt.Println("\n⏱️ Timeout waiting for browser callback.")
callbackServer.Close()
}
}
// If no code from callback, prompt for manual entry
if code == "" {
fmt.Println("\nAfter authorizing, paste the callback URL or authorization code below.")
fmt.Println("(The callback URL will look like: http://localhost:1455/auth/callback?code=...&state=...)")
fmt.Println()
var input string
err = huh.NewInput().
Title("Callback URL or Code").
Description("Paste the full callback URL or just the authorization code").
Value(&input).
Run()
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
input = strings.TrimSpace(input)
if input == "" {
return fmt.Errorf("authorization code cannot be empty")
}
// Parse the input (could be full URL or just code)
parsedCode, parsedState := auth.ParseOpenAIAuthorizationInput(input)
if parsedCode == "" {
return fmt.Errorf("could not extract authorization code from input")
}
// Validate state if provided
if parsedState != "" && parsedState != authData.State {
return fmt.Errorf("state mismatch - possible security issue")
}
code = parsedCode
}
// Exchange code for tokens
fmt.Println("\n🔄 Exchanging authorization code for access token...")
creds, err := client.ExchangeCode(code, authData.Verifier)
if err != nil {
return fmt.Errorf("failed to exchange authorization code: %w", err)
}
// Store the credentials
if err := cm.SetOpenAIOAuthCredentials(creds); err != nil {
return fmt.Errorf("failed to store credentials: %w", err)
}
fmt.Println("✅ Successfully authenticated with OpenAI (ChatGPT/Codex)!")
fmt.Printf("📁 Credentials stored in: %s\n", cm.GetCredentialsPath())
fmt.Printf("👤 Account ID: %s\n", creds.AccountID)
fmt.Println("\n🎉 Your OAuth credentials will now be used for OpenAI API calls.")
fmt.Println("💡 You can check your authentication status with: kit auth status")
// Set default model if requested
if err := setDefaultModelIfRequested("openai"); err != nil {
return err
}
// Remind users how to set this as default if they didn't use --set-default
if !loginSetDefault {
fmt.Println("\n💡 To set OpenAI as your default model, run:")
fmt.Println(" kit auth login openai --set-default")
}
return nil
}
// loginCopilot authenticates GitHub Copilot using GitHub device flow.
func loginCopilot(ctx context.Context) error {
if ctx == nil {
ctx = context.Background()
}
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
if hasAuth, err := cm.HasCopilotCredentials(); err == nil && hasAuth {
var reauth bool
err := huh.NewConfirm().
Title("You are already authenticated with GitHub Copilot").
Description("Do you want to re-authenticate?").
Affirmative("Yes").
Negative("No").
Value(&reauth).
Run()
if err != nil {
return fmt.Errorf("failed to prompt for re-authentication: %w", err)
}
if !reauth {
fmt.Println("Authentication cancelled.")
return nil
}
}
client := auth.NewCopilotOAuthClient()
fmt.Println("🔐 Starting GitHub Copilot authentication...")
fmt.Println("This uses GitHub device login and requires an active GitHub Copilot subscription.")
fmt.Println("Experimental: this uses VS Code Copilot Chat client identifiers.")
fmt.Println()
deviceCode, err := client.StartDeviceFlow(ctx)
if err != nil {
return fmt.Errorf("failed to start GitHub device login: %w", err)
}
fmt.Println("📱 Open this page and enter the code:")
fmt.Printf("\n%s\n\n", deviceCode.VerificationURI)
fmt.Printf("Code: %s\n\n", deviceCode.UserCode)
auth.TryOpenBrowser(deviceCode.VerificationURI)
fmt.Println("Waiting for GitHub authorization...")
githubToken, err := client.PollDeviceToken(ctx, deviceCode)
if err != nil {
return fmt.Errorf("failed to complete GitHub device login: %w", err)
}
fmt.Println("\n🔄 Exchanging GitHub token for Copilot access token...")
creds, err := client.ExchangeGitHubToken(ctx, githubToken)
if err != nil {
return fmt.Errorf("failed to get GitHub Copilot token: %w", err)
}
if err := cm.SetCopilotOAuthCredentials(creds); err != nil {
return fmt.Errorf("failed to store credentials: %w", err)
}
fmt.Println("✅ Successfully authenticated with GitHub Copilot!")
fmt.Printf("📁 Credentials stored in: %s\n", cm.GetCredentialsPath())
fmt.Println("\n🎉 Your GitHub Copilot credentials will now be used for copilot/* models.")
fmt.Println("💡 You can check your authentication status with: kit auth status")
if err := setDefaultModelIfRequested("copilot"); err != nil {
return err
}
if !loginSetDefault {
fmt.Println("\n💡 To set Copilot as your default model, run:")
fmt.Println(" kit auth login copilot --set-default")
}
return nil
}
// callbackServer holds the HTTP server and channel for receiving the OAuth callback
type callbackServer struct {
Server *http.Server
CodeChan chan string
State string
}
// Close shuts down the callback server
func (cs *callbackServer) Close() {
if cs.Server != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = cs.Server.Shutdown(ctx)
}
}
// startOpenAICallbackServer starts a local HTTP server to receive the OAuth callback
func startOpenAICallbackServer(expectedState string) (*callbackServer, error) {
codeChan := make(chan string, 1)
mux := http.NewServeMux()
server := &http.Server{
Addr: "127.0.0.1:1455",
Handler: mux,
}
mux.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) {
// Check state
state := r.URL.Query().Get("state")
if state != expectedState {
http.Error(w, "State mismatch", http.StatusBadRequest)
return
}
code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "Missing authorization code", http.StatusBadRequest)
return
}
// Send code to channel
select {
case codeChan <- code:
default:
}
// Return success page
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head><title>Authentication Successful</title></head>
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
<h1>&#10003; Authentication Successful</h1>
<p>You can close this window and return to the terminal.</p>
</body>
</html>`)
})
// Try to start server
listener, err := net.Listen("tcp", "127.0.0.1:1455")
if err != nil {
return nil, fmt.Errorf("port 1455 not available: %w", err)
}
_ = listener.Close()
go func() {
_ = server.ListenAndServe()
}()
return &callbackServer{
Server: server,
CodeChan: codeChan,
State: expectedState,
}, nil
}
func logoutOpenAI() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if authenticated
hasAuth, err := cm.HasOpenAICredentials()
if err != nil {
return fmt.Errorf("failed to check authentication status: %w", err)
}
if !hasAuth {
fmt.Println("You are not currently authenticated with OpenAI.")
return nil
}
// Confirm logout
var confirm bool
err = huh.NewConfirm().
Title("Remove OpenAI credentials").
Description("Are you sure you want to remove your stored credentials?").
Affirmative("Yes").
Negative("No").
Value(&confirm).
Run()
if err != nil || !confirm {
fmt.Println("Logout cancelled.")
return nil
}
// Remove credentials
if err := cm.RemoveOpenAICredentials(); err != nil {
return fmt.Errorf("failed to remove credentials: %w", err)
}
fmt.Println("✓ Successfully logged out from OpenAI!")
fmt.Println("You will need to use environment variables or command-line flags for authentication.")
return nil
}
func logoutCopilot() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
hasAuth, err := cm.HasCopilotCredentials()
if err != nil {
return fmt.Errorf("failed to check authentication status: %w", err)
}
if !hasAuth {
fmt.Println("You are not currently authenticated with GitHub Copilot.")
return nil
}
var confirm bool
err = huh.NewConfirm().
Title("Remove GitHub Copilot credentials").
Description("Are you sure you want to remove your stored credentials?").
Affirmative("Yes").
Negative("No").
Value(&confirm).
Run()
if err != nil || !confirm {
fmt.Println("Logout cancelled.")
return nil
}
if err := cm.RemoveCopilotCredentials(); err != nil {
return fmt.Errorf("failed to remove credentials: %w", err)
}
fmt.Println("✓ Successfully logged out from GitHub Copilot!")
fmt.Println("You will need to authenticate again with 'kit auth login copilot'.")
fmt.Println("Tip: this removes local credentials only. Revoke the GitHub OAuth grant at https://github.com/settings/applications")
return nil
}

Some files were not shown because too many files have changed in this diff Show More