This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
YAI is dotcommander/yai — an AI CLI tool for piping command output through LLMs. It reads stdin, prepends a prompt, sends to an LLM API, and prints the response (optionally formatted as Markdown). Supports OpenAI, Anthropic, Google, Cohere, Ollama, and Azure OpenAI.
The binary is named yai and the Go module path is github.com/dotcommander/yai.
go build -o ./yai . # build (version from git VCS info)
go build -ldflags "-X main.Version=v0.2.0 -X main.CommitSHA=$(git rev-parse HEAD)" -o ./yai . # build with explicit version
go test ./... # all tests
go test -run TestFoo ./... # single test
go test -v -cover -timeout=30s ./... # CI-style
golangci-lint run # lint (config in .golangci.yml)The built binary is ./yai (listed in .gitignore as yai), symlinked to $GOBIN/yai or $HOME/go/bin/yai for PATH access.
The app is a Bubble Tea program. The main model is Yai in internal/tui/yai.go. State machine flow:
startState → configLoadedState → requestState → responseState → doneState
Init()resolves cache/conversation detailsreadStdinCmdreads piped inputstartCompletionCmdcalls the agent to resolve model/config and start streamingreceiveCompletionStreamCmditerates the stream, appending chunks to outputappendToOutputrenders Markdown via Glamour into a viewport for TTY output
Each LLM provider implements stream.Client and stream.Stream (defined in internal/stream/stream.go):
| Package | Provider | SDK |
|---|---|---|
internal/provider |
OpenAI-compatible + Anthropic + Google + Azure | charm.land/fantasy |
The internal/proto package defines the shared Message, Request, Chunk, and ToolCall types used across all providers.
- Metadata index: JSONL append-only log at
~/.config/yai/history/conversations/index.jsonl(managed byinternal/storage.DB). Events areupsertordelete. Auto-compacts when ops exceed thresholds. - Payload cache: Sharded JSON files under
~/.config/yai/history/conversations/<2-char-prefix>/<id>.json(managed byinternal/storage/cache). Legacy flat files are still readable. - Conversations are identified by SHA-1 IDs (see
internal/storage/id.go).
internal/config/config.godefinesConfig,API,Modelstructs- Settings file:
~/.config/yai/yai.yml(templated from embeddedconfig_template.yml) - Environment override:
YAI_prefix (parsed viacaarlos0/env) - CLI flags + routing: defined in
internal/cmd/using cobra/pflag - Roles/system prompts: loadable from strings, URLs, or
file://paths (internal/config/load.go)
internal/mcp/service.go supports MCP tool servers (stdio, SSE, HTTP) configured in the settings YAML under mcp-servers. Tools are discovered at request time and passed to the LLM. Tool calls are dispatched back through MCP clients.
| File | Purpose |
|---|---|
main.go |
CLI entry (thin wrapper over internal/cmd) |
internal/cmd/root.go |
Cobra root + flag wiring + routing |
internal/tui/yai.go |
Bubble Tea model, streaming/render orchestration |
internal/agent/service.go |
Model resolution, auth, request assembly, stream start |
internal/agent/errors.go |
Provider error normalization + retry/fallback decisions |
internal/config/config.go |
Config structs + YAML/env parsing + defaults |
internal/config/load.go |
Role/system prompt loading (string/url/file://) |
internal/storage/db.go |
JSONL conversation metadata store |
internal/storage/cache/ |
Conversation payload cache |
internal/mcp/service.go |
MCP server integration |
internal/present/styles.go |
Lipgloss styling helpers |
internal/tui/anim.go |
Loading animation |
- Settings file path is
~/.config/yai/yai.yml. - Keep exactly one
mainpackage entrypoint (currentlymain.go). Having bothmain.goandcmd/yai/main.gocreates duplicatemainpackages and can causego install ./...to produce conflicting binaries. - Roles can be loaded from
~/.config/yai/roles/: filename (minus extension) is the role name; non-YAML files are loaded as file content, and.yml/.yamlfiles parse as a string or list of strings. - Conversation cache base path defaults to
~/.config/yai/history, with conversations under~/.config/yai/history/conversations. - Conversation metadata index is stored as JSONL at
~/.config/yai/history/conversations/index.jsonl. - Conversation payload files are sharded by 2-char ID prefix under
~/.config/yai/history/conversations/<prefix>/<id>.json. - Legacy flat payload files at
~/.config/yai/history/conversations/<id>.jsonare still readable/deletable for migration compatibility. - models.dev provider/model sync is configured locally with script at
~/.config/yai/bin/models-dev-refresh.sh, launch agent at$HOME/Library/LaunchAgents/dev.yai.modelsdev-refresh.plist, running every 12 hours (StartInterval=43200), outputting to~/.config/yai/cache/models.dev.api.jsonand~/.config/yai/cache/models.dev.providers-models.json. - Fantasy routing covers OpenAI-compatible APIs plus native providers for
anthropic,google,azure,azure-ad,openrouter,vercel, andbedrock;cohereandollamauseopenaicompatrouting. - Fantasy bridge maps Google
thinking-budgetvia provider options. - Fantasy bridge forwards
request.Uservia provider options for Fantasy-routed OpenAI (openai/azure) and OpenAI-compatible APIs. - Fantasy bridge forwards
max-completion-tokensvia OpenAI provider options foropenai/azure/azure-ad. stopis still present in yai config/request, but the current FantasyCallAPI (v0.8.1) has no direct stop-sequences field, so stop sequences are not currently forwarded by the bridge.- When
stopis configured andquietis false, yai prints a runtime warning to stderr once per run to make the no-op behavior explicit. - Fantasy stream
warningsevents are now surfaced to users (stderr, non-quiet) once per unique message via bridge warning dedupe +DrainWarnings(). - Agent request assembly now strips
temp/topp/topkfor reasoning model names (gpt-5*,o1*,o3*,o4*) to avoid unsupported-setting warnings and empty-turn behavior in chat. - Fantasy
ProviderExecutedtool calls are skipped by yai MCP execution to avoid duplicate local tool invocation. - Provider retry fallback now uses Fantasy-native
ProviderError.IsRetryable()andErrorTitleForStatusCode(...)instead of custom 500/default branching. - HTTP 429 handling now also goes through the same Fantasy retryability path (no special-case branch), with reason text derived from
ErrorTitleForStatusCode(...). - Non-retryable provider errors now also prefer Fantasy
ErrorTitleForStatusCode(...)for user-facing reason text. - Unauthorized (401) provider errors now also flow through Fantasy status-title mapping (no custom invalid-key branch).
- Retry wait timing now uses Fantasy
RetryWithExponentialBackoffRespectingRetryHeaders(single-step) for provider errors soretry-afterheaders are honored. - Current
charm.land/fantasyversion is v0.12.2; provider set isanthropic,azure,bedrock,google,openai,openaicompat,openrouter,vercel;cohereandollamauseopenaicompatrouting. - Version is injected via ldflags (
-X main.Version,-X main.CommitSHA) in CI/release builds. Localgo buildderives version from Go's embedded VCS info (git tag + commit + dirty state). The fallback chain is: ldflags →debug.ReadBuildInfo().Main.Version→dev-<sha>[-dirty]. - Local dev binary is symlinked:
$GOBIN/yai(or$HOME/go/bin/yai) →<project-root>/yai. Aftergo build -o ./yai ., the PATH binary is updated automatically. - User runs yai in Ghostty; prioritize terminal-compatibility for chat UI behavior (stable footer, predictable redraw/scroll behavior).
yai upgraderunsgo install github.com/dotcommander/yai@latestto upgrade in-place.