- 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
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.
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.
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.
- 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
- 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
- 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
- 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
- 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
- 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
- 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
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
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
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.
- 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
- 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
- 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)
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.
- 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
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
- 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 $+
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
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
- 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
- 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
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
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.
- 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
- 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
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
- 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 (
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.
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
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
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.
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.
- 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