CLAI is an AI-powered terminal assistant that turns natural-language requests into shell commands you can inspect, edit, approve, or reject before they run.
clai list files by size
clai how much is 3 times pi
The primary implementation is now Go. It keeps the existing clai <request...> interface, configuration and history locations, and risk controls while moving API clients, orchestration, persistence, terminal UI, and LLM tools into typed Go packages.
Note
This release migrates CLAI's runtime from the former Bash script to a compiled Go binary. Existing configuration, history, command syntax, and shell integration continue to be used; legacy ~/.clai_tools Bash plugins do not. See Migration from the Bash version before removing old files.
- Direct prompts without wrapping the whole request in quotes
- Interactive sessions when
claiis run without a request - OpenAI, OpenAI-compatible, Ollama, Anthropic, and Gemini API adapters
- Structured command, explanation, risk, and missing-variable responses
- Green, amber, and red command risk levels
- Review, edit, approval, and additional danger-zone confirmation
- Live command stdout and stderr
- Optional error analysis after a command fails
- Persistent conversation history with configurable retention
- Opt-in command-result sharing with output truncation
- Hot-loaded MCP tool servers exposed through provider function calling
- A standalone Go Wikipedia server that doubles as a copyable plugin tutorial
- Shell integration for unquoted
*and?in Bash and zsh - Layered
AGENTS.mdguidance and portable Agent Skills with local selection and on-demand documentation
To run a compiled CLAI binary:
- Linux, macOS, or Windows on AMD64 or ARM64
- Bash on
PATH, used to execute approved commands (Git Bash is suitable on Windows) - An API key, provider credential, or non-empty placeholder required by a local service configuration
The Go binary implements HTTP, JSON, and tool handling itself. It does not require curl or jq.
Building from source requires the Go toolchain declared by go.mod: Go 1.27.3 for this branch.
The recommended installer downloads the pre-built release for the current operating system and architecture, verifies it against the release SHA-256 checksum, checks the binary's reported version, and installs it as /usr/local/bin/clai:
curl -fsSL https://raw.githubusercontent.com/merefield/clai/main/install-release.sh | shThe bootstrap requires curl or wget, tar, and either sha256sum (Linux) or shasum (macOS). It uses sudo only when the destination is not writable.
For a user-local installation that does not require sudo:
curl -fsSL https://raw.githubusercontent.com/merefield/clai/main/install-release.sh |
CLAI_BIN_DIR="$HOME/.local/bin" shEnsure $HOME/.local/bin is on PATH if you use that location. To install a particular release reproducibly, replace vX.Y.Z with a tag from Releases:
release_tag=vX.Y.Z
curl -fsSL https://raw.githubusercontent.com/merefield/clai/main/install-release.sh |
sh -s -- --version "$release_tag"On Windows, download and inspect the PowerShell installer, then run it for the current process without changing the machine-wide execution policy:
Invoke-WebRequest https://raw.githubusercontent.com/merefield/clai/main/install-release.ps1 -OutFile install-release.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\install-release.ps1The Unix installer supports --help, --version TAG, and --bin-dir DIR; the PowerShell installer accepts -Version, -BinDir, and -Repository. Both also support the corresponding CLAI_* environment variables.
The Windows installer defaults to %LOCALAPPDATA%\Programs\clai\bin and reports when that directory must be added to PATH. CLAI still invokes Bash for approved commands, so install Git Bash or another Windows Bash and ensure bash.exe is on PATH.
After installation, configure a provider and try a request:
clai setup
clai list files by sizeSetup also offers an optional command name for voice input, such as computer. Press Enter to skip it. On Bash, zsh, and PowerShell, setup checks for existing commands, aliases, functions, and shell keywords, then asks before saving shell integration to your profile. See Calling CLAI by another name.
Clone the repository and run the installer from its root:
git clone https://github.com/merefield/clai.git
cd clai
./install.shThe installer builds ./cmd/clai in a temporary directory and installs the result as /usr/local/bin/clai. It uses sudo only when the destination is not writable.
For a user-local installation:
CLAI_BIN_DIR="$HOME/.local/bin" ./install.shEnsure $HOME/.local/bin is on PATH if you use that location.
For development, build and run without installing:
make build
./clai --version
./clai setup
./clai list files by sizemake install is also available and honours DESTDIR and PREFIX:
make install PREFIX="$HOME/.local"Release archives are configured through GoReleaser for Linux, macOS, and Windows on AMD64 and ARM64. Release binaries report the release tag as their version. Local builds read the maintained version from internal/app/VERSION; the release workflow refuses a tag that does not match it.
Run the setup wizard explicitly:
clai setupclai --setup is an equivalent compatibility form. If no API key is configured, an ordinary invocation starts setup automatically.
The wizard asks for:
- API key or local-service token
- API endpoint
- model
- risk appetite
The defaults are:
api=https://api.openai.com/v1/chat/completions
model=gpt-4.1
risk_appetite=0
use_tools=falseConfiguration is stored in ~/.config/clai.cfg with mode 0600. The API key is stored in that plain-text local file, so protect the account and filesystem that contain it.
Pass a request as separate shell arguments:
clai show the ten largest files under this directoryRun clai without arguments for an interactive session. Enter exit to leave it.
A request containing ? uses question mode and returns an explanation without proposing a command:
clai "how do I show hidden files?"| Command | Purpose |
|---|---|
clai setup |
Run the configuration wizard. |
clai --setup |
Compatibility alias for setup. |
clai --question <question> |
Answer-only mode using question_query, with or without System One; proposed shell commands are not executed. |
clai --show-history |
Render persisted conversation history. |
clai --show-history --verbose |
Include full stored command stdout and stderr. |
clai --clear-history |
Remove persisted conversation history. |
clai --show-results-sharing |
Report whether command results are shared, interpreted, and stored. |
clai --toggle-results-sharing |
Enable or disable command-result sharing and interpretation. |
clai shell-init bash |
Print Bash integration for literal glob characters. |
clai shell-init zsh |
Print zsh integration for literal glob characters. |
clai shell-init pwsh |
Print PowerShell integration (powershell is also accepted). |
clai --version |
Print the CLAI version. |
clai --help |
Print command help. |
Natural-language requests such as clai clear your history are recognized locally for common history-clearing phrases and do not call the model.
CLAI joins all request arguments with spaces, so ordinary prompts do not need surrounding quotes:
clai create a directory named reportsYour parent shell still interprets syntax such as *, ?, $, quotes, redirections, and command substitutions before CLAI starts. Without shell integration, reword, escape, or quote those characters:
clai how much is 3 times pi
clai how much is 3 \* pi
clai "how much is 3 * pi"To support the exact unquoted form, add the appropriate integration to your shell profile:
# ~/.bashrc
eval "$(clai shell-init bash)"# ~/.zshrc
eval "$(clai shell-init zsh)"Start a new shell or reload its profile. Glob characters following clai will then be passed literally:
clai how much is 3 * piThe integration affects only the clai invocation and restores Bash globbing afterwards.
Run clai setup and enter computer (or another lowercase command name) at the optional command-alias prompt. A familiar word can be easier to dictate than clai; test your choice with your voice recognizer, including whether it adds capitalization or punctuation.
Setup checks for conflicts in a fresh shell and asks before updating your shell profile:
- Bash:
~/.bashrcon Linux, or the first existing login profile (~/.bash_profile,~/.bash_login, or~/.profile) on macOS, creating~/.bash_profileif needed. - zsh:
${ZDOTDIR:-$HOME}/.zshrc, includingZDOTDIRset in.zshenv. - PowerShell 7 or Windows PowerShell 5.1: the shell's reported
$PROFILE.CurrentUserAllHosts, so redirected Documents folders and the separate PowerShell editions use the correct location. Setup saves a native alias directly to the executable, preserving PowerShell's normal argument handling and native exit status. Existing UTF-8/UTF-16 profile encodings and line endings are retained; UTF-8 profiles receive a byte-order mark for Windows PowerShell compatibility. Your execution policy must permit loading the profile; setup does not change it.
It saves one managed block and checks for conflicts when the profile loads. Bash/zsh integration preserves unquoted * and ? arguments. PowerShell retains native command parsing: Windows passes those characters literally; on macOS/Linux, quote wildcard characters as you would when calling clai directly (for example, computer how much is 3 '*' pi).
Setup detects a PowerShell parent process on Windows, macOS, and Linux; otherwise it uses the login shell (or an available PowerShell edition on Windows). Set CLAI_SHELL to bash, zsh, pwsh, powershell, or the shell executable's path to select it explicitly. Other shells are left unchanged.
Start a new shell or run the activation command printed by setup (source 'profile-path' in Bash/zsh, . 'profile-path' in PowerShell), then use the new name:
computer show the ten largest files
computer how much is 3 * piRunning setup again lets you keep, replace, or remove the managed alias. Enter keeps the current alias; - removes it after confirmation. Start a new shell after replacing or removing an alias to clear its previous definition.
The saved alias invokes the installed executable by its absolute path. Run setup again if you move the executable. Both names use the same configuration and history.
You can also configure an alias manually. For example, add the following to ~/.bashrc to use ask:
eval "$(clai shell-init bash)"
alias ask='clai'For zsh, add this to ~/.zshrc instead:
eval "$(clai shell-init zsh)"
alias ask='clai'If shell initialization is already configured, add only the alias after it. This order preserves CLAI's handling of unquoted * and ? through the alias.
Start a new shell or reload your profile with source ~/.bashrc (Bash) or source ~/.zshrc (zsh), then pass requests as usual:
ask show the ten largest files
ask how much is 3 * piThe alias forwards the same command-line arguments to CLAI. Both command names use the same configuration and history.
The provider returns a structured response containing:
cmd: the proposed Bash command, or empty for an informational answerinfo: a short explanationrisk:none,reversible change, ordanger zonevariables: values CLAI must collect before showing the final command
If a value is missing, CLAI prompts for it and shell-quotes it before replacing its {{variable_name}} placeholder. CLAI refuses to execute a response that still contains unresolved placeholders.
Suggested commands are displayed by risk:
| Risk | Colour | Meaning |
|---|---|---|
none |
Green | Read-only inspection. |
reversible change |
Amber | A change that is normally undoable. |
danger zone |
Red | Deletion, overwrite, reset, force, or another hard-to-reverse action. |
When confirmation is required, CLAI prompts with execute command? [y/e/t/N]:
yruns the command.elets you replace the proposed command before it runs.texits CLAI and prepares the command for review in your shell, without executing it.- Enter or any other answer cancels.
risk_appetite controls automatic execution after the command and explanation have been displayed:
| Value | Behaviour |
|---|---|
0 |
Confirm every proposed command. |
1 |
Automatically run green commands; confirm amber and red commands. |
2 |
Automatically run green and amber commands; confirm red commands. |
Danger-zone commands always receive the normal confirmation. When confirm_dangerous_commands=true, accepting one produces a second danger zone command, are you sure? [y/N]: prompt.
Approved commands run through bash -o errexit -o pipefail -c. Stdout and stderr stream to the terminal without truncation, while CLAI retains only the most recent 256 KiB of each stream. Cancellation terminates the command's process group, and detached descendants cannot keep CLAI blocked on inherited output pipes. After a failed command in an interactive terminal, CLAI can send the command and its stderr back to the provider to request an explanation and possible repair.
Approved commands inherit terminal input, so prompts from tools such as apt-get can be answered normally after CLAI's own confirmations. On Unix, CLAI hands foreground terminal ownership to the command and restores it and the terminal settings afterward, including on cancellation. Redirected file input is also passed through; when no input file is available, commands receive EOF. Output still streams and is captured as described above; this is not a full pseudo-terminal wrapper for screen-oriented applications.
Risk is model-generated guidance, not a security boundary. Read every proposed or edited command before approving it.
CLAI captures command output through pipes. It supports ordinary input prompts, but it cannot provide the full terminal interface required by applications such as tmux attach, screen editors and interactive process monitors.
CLAI handles this in two ways:
Recognized interactive command ────────────────┐
▼
Other command → run once → terminal error → offer shell handoff
│
t: exit CLAI with the command
N: cancel handoff
Before execution, a conservative local check recognizes simple interactive invocations of tmux, screen, vi/vim/nvim, nano, emacs, less/more, top/htop/btop and watch. Clear batch or inspection forms such as tmux list-sessions, tmux new-session -d (including bundled flags such as -dA), screen -ls, top -b and nvim --headless continue through normal execution. Recognized interactive commands receive a handoff prompt even when risk_appetite would normally allow automatic execution.
For other commands, CLAI checks failed stdout/stderr for specific terminal diagnostics such as not a terminal, not a tty, and Inappropriate ioctl for device. It then offers the same handoff instead of asking the model to repair the error. The command is never retried automatically: earlier steps of a compound command may already have completed, so review the whole command before running it again. Detection is best-effort; unfamiliar syntax, localized diagnostics, and programs that hang or exit successfully without a usable screen may not be detected. You can choose t at any ordinary command confirmation to request a handoff yourself; use risk_appetite=0 to see that confirmation for every command.
Selecting t returns control to your shell, including from a CLAI interactive session. What happens next depends on the integration:
| Shell | Handoff behavior |
|---|---|
| zsh with current integration | The command appears in the next editable prompt. Review it, then press Enter to run it. |
| Bash with current integration | Press Up to retrieve the command from shell history, review it, then press Enter. Bash does not automatically prefill its next prompt. |
| PowerShell, other shells, or no integration | CLAI displays a copyable command and exits. Run it directly in Bash, which is CLAI's command language. |
If the handoff prompt reaches EOF without an answer, CLAI also displays the copyable command and exits; it does not queue a command without a selection.
To enable or refresh the integration, evaluate the output of the updated executable in your current shell and save the same line in your shell profile:
# Bash
eval "$(clai shell-init bash)"# zsh
eval "$(clai shell-init zsh)"An alias installed by clai setup also receives the current integration when you rerun setup and reload its profile. Older saved integration blocks must be refreshed after upgrading. Handoff preparation requires an interactive shell with terminal input/output; redirected output uses the copyable fallback.
The integrated handoff uses a private temporary directory for each invocation, creates the command file without overwriting existing files, and removes it afterward. Wrappers consume the file only when CLAI explicitly signals a successfully prepared handoff for the current invocation. Files from ordinary runs, cancellations and failed writes are ignored, and the private handoff path is not passed to executed commands or MCP servers. Handed-off commands run through bash -o errexit -o pipefail -c '…' to preserve CLAI's Bash semantics in zsh; the wrapper never evaluates the returned text. Multiline commands, control characters and commands exceeding 64 KiB use the copyable fallback. Shell history follows your shell's usual persistence settings. Execution after you press Enter happens outside CLAI's approval, output capture and result-sharing flow.
If system_one_key, system_one_api, and system_one_model are configured, CLAI uses that System One-compatible API for typed intent routing and command-risk auditing. The main LLM still generates commands and explanations, but System One decides whether a request is a command task, question, or history-clear request, and independently audits proposed command risk before risk_appetite can auto-run it. A higher System One risk label overrides the LLM risk label; low-confidence risk audits force a confirmation prompt.
Requests are interpreted by meaning rather than question marks. Questions needing live data, such as what is the time? or how much disk space is free?, should produce a command to obtain the answer and follow the usual risk and confirmation rules. General knowledge and how-to questions can be answered directly without execution. With share_command_results=true, CLAI also interprets the resulting command output. These decisions use System One when configured, or the main LLM otherwise.
CLAI selects its native adapter from the configured api URL:
| Provider | Typical endpoint | Authentication and behavior |
|---|---|---|
| OpenAI Chat Completions | https://api.openai.com/v1/chat/completions |
Bearer token; supports native JSON Schema and local tool calls. |
| OpenAI Responses | https://api.openai.com/v1/responses |
Bearer token; supports native JSON Schema, reasoning effort, and local tool calls through the Responses API. |
| OpenAI-compatible | Provider-specific Chat Completions URL | Bearer token; uses the OpenAI-compatible message and tool format. |
| Ollama | http://localhost:11434/api/chat |
Uses the configured model, disables streaming, and supports Ollama tool-call responses. Keep key non-empty even if the service ignores it. |
| Anthropic | https://api.anthropic.com/v1/messages |
Uses x-api-key and the Messages payload. Local CLAI tools are not sent. |
| Gemini | A generativelanguage.googleapis.com generateContent URL |
Uses x-goog-api-key; the configured model replaces the model segment in the request URL. Local CLAI tools are not sent. |
Provider detection is URL-based. Use /v1/responses for OpenAI models that require or work best with the Responses API. Other URLs use the generic OpenAI-compatible adapter, so use a Chat Completions-compatible endpoint and model unless the provider explicitly implements the Responses request and response shape.
When json_mode=true, CLAI requests provider-enforced structured JSON:
- OpenAI Chat Completions uses JSON Schema through
response_format. - OpenAI Responses uses JSON Schema through
text.format. - Generic OpenAI-compatible endpoints request a JSON object.
- Ollama sends
format: "json". - Anthropic uses structured output configuration.
- Gemini uses a JSON response schema.
For OpenAI Responses, reasoning is sent as reasoning.effort only for known reasoning-model families; CLAI also omits temperature for those reasoning models because they do not accept sampling parameters. For OpenAI Chat Completions, reasoning is sent as reasoning_effort only for known reasoning-model families; generic compatible completion endpoints receive the configured value directly. For Ollama, any non-empty reasoning value enables think: true and also requests JSON output.
Example OpenAI Responses configuration:
key=sk-...
api=https://api.openai.com/v1/responses
model=gpt-5.2
json_mode=true
reasoning=mediumExample Ollama configuration:
key=ollama
api=http://localhost:11434/api/chat
model=gemma4:latest
json_mode=true
reasoning=trueExample System One configuration for TypeSafe Jev:
system_one_key=ts-...
system_one_api=https://api.typesafe.ai/v1/systemone
system_one_model=jev-latestThis walkthrough uses the portable OpenAI CLI example. Install a CLAI build containing this feature first; clai skills --help should display the local context commands. Provider setup is only needed when making a model request, not for inspecting guidance or skills.
-
Add project preferences. Create
AGENTS.mdat your repository root, for example:# Project guidance Prefer concise explanations and show commands before running them. Use the project's documented development and test commands.Add more specific guidance in a nested directory when needed. Invoke CLAI from that directory to apply its instructions. Use
clai context showto check the chain before making a request. -
Choose a skill scope. Use
~/.agents/skills/for skills shared across projects, or<project>/.agents/skills/for a project's skills. A nested project's.agents/skills/applies only when invoking CLAI from that directory or below it. Existing skills in these locations are discovered automatically. -
Install a whole skill folder. For a first installation of the bundled example, run from a CLAI source checkout with no existing
openai-clidestination folder:Bash/zsh:
mkdir -p "$HOME/.agents/skills" cp -R examples/skills/openai-cli "$HOME/.agents/skills/"
PowerShell:
$skillRoot = Join-Path $HOME '.agents/skills' New-Item -ItemType Directory -Path $skillRoot -Force | Out-Null Copy-Item -LiteralPath ./examples/skills/openai-cli -Destination $skillRoot -Recurse
Copy the whole folder, including
references/, rather than justSKILL.md. To keep the skill project-local, use the project's.agents/skills/as the destination instead. No CLAI-specific package installation is needed. -
Inspect before using it. These commands do not call a provider:
clai skills list clai skills show openai-cli clai skills show openai-cli --body clai skills list --all clai context show
showprints the selected source path. Useclai skills validate <that-path>to check an installed skill, orclai skills validate examples/skills/openai-clito check the bundled source.list --allexplains invalid or shadowed copies.--bodyandcontext show --verbosedeliberately display document contents. -
Make a request. After
clai setupif needed, try either local automatic matching or explicit selection:clai list the OpenAI API models I have access to clai --skill openai-cli list my available OpenAI models clai --question --skill openai-cli explain how to list models
An activation line such as
skill: openai-cli (local relevance match)identifies the skill in use. CLAI can then proposeopenai models listthrough its normal review flow. The separateopenaiexecutable and its credentials must already be configured to execute that command. A request such asclai list the files in this directorydoes not activate this skill. -
Use or change skills interactively. Start
clai, then enter:/skills list /skills use openai-cli list my available OpenAI models /skills off /context show exitExplicit selections last for that session.
/skills offclears them and disables automatic activation for the rest of the session, until/skills useselects a skill again. Edit files and make another request to pick up changes;/skills reloadalso performs a fresh scan. Setskills_auto_activate=falsefor explicit-only selection,skills_enabled=falseto disable skills, oragents_enabled=falseto disable AGENTS guidance.
If a skill is missing, inspect clai skills list --all, its filename/frontmatter, the current working directory and the search limits below. If a skill is listed but not selected automatically, improve its description or use --skill. If references are unavailable, the main instructions still work; check the provider's tool-calling support and the documentation path rules. External MCP does not need to be enabled for skill references.
CLAI discovers AGENTS.md instructions locally on every request:
<os.UserConfigDir()>/clai/AGENTS.mdfor global preferences (~/.config/clai/AGENTS.mdon typical Linux systems).- The nearest Git project root, recognized by either a
.gitdirectory or a worktree.gitfile. - Each directory from that root down to the invocation's current directory.
Without a Git root, only the current directory supplies project guidance. Siblings, descendants and ancestors above the root are not searched. In each applicable directory, the first non-empty valid AGENTS.override.md or AGENTS.md is used, in that order. Closer project documents take precedence within the guidance layer; skills supplement those instructions. The user's request remains the task, and CLAI's built-in JSON contract, tool restrictions and approval rules retain priority.
Inspect the chain without a provider key:
clai context show
clai context show --verboseDefault inspection shows paths, scopes, byte counts and diagnostics. --verbose displays file contents. UTF-8 files exceeding a per-file or aggregate budget are omitted with a warning. Existing documents are refreshed on the next request, including in interactive sessions.
CLAI reads the published Agent Skills format. Skills live in directories containing YAML-frontmatter SKILL.md files:
~/.agents/skills/<name>/SKILL.md
<project-root>/.agents/skills/<name>/SKILL.md
<applicable-nested-directory>/.agents/skills/<name>/SKILL.md
Collections may also use grouping folders:
.agents/skills/
├── openai-cli/ # direct skill
│ ├── SKILL.md
│ └── references/commands.md # read only on demand
└── api-tools/ # organizational group, no SKILL.md
└── model-inspection/ # grouped skill
└── SKILL.md # name: model-inspection
Discovery walks each collection breadth-first with sorted directory names. It skips hidden folders and node_modules, and stops descending once a directory contains SKILL.md, so a bundle's examples, scripts and references cannot become accidental skills. Grouping folders do not change a skill's name: select the leaf skill as --skill model-inspection.
The nearest valid project skill wins when names overlap, followed by project-root and user skills. Within one collection, the first valid copy in breadth-first, name-sorted traversal wins: direct children are considered before grouped copies. skills list --all shows shadowed paths. Higher-priority roots are scanned first so user collections cannot exhaust the discovery budget before project overrides. Files in unrelated repositories are not searched. No CLAI-specific installation or copy is needed for skills already in these locations; ~/.codex and ~/.claude are not fallback roots.
Discovery reads only YAML metadata. Names must match their directory, use 1–64 lowercase ASCII letters/digits with single intervening hyphens, and descriptions must contain 1–1024 characters. Standard optional fields are accepted; unknown optional fields are ignored. allowed-tools is metadata and grants no permissions. Only selected skills' Markdown bodies enter the request. The metadata index is bounded separately and shortens descriptions before omitting entries.
Selection uses local name/description matching, without embeddings or an extra model call. Casual topic mentions do not suffice; tied matches produce a diagnostic directing you to explicit selection. Up to two skills activate automatically by default. Explicit selection takes precedence and can name up to eight skills:
clai skills list
clai skills list --all
clai skills show openai-cli
clai skills show openai-cli --body
clai skills validate examples/skills/openai-cli
clai skills reload
clai --skill openai-cli list my available OpenAI models
clai --question --skill openai-cli explain how to list modelsAll inspection commands work without a provider key. list --all includes invalid and shadowed sources. validate checks UTF-8 text and frontmatter within a 1 MiB file limit. reload rebuilds discovery; normal requests also rescan, so file changes do not require restarting CLAI. Interactive equivalents include /skills list, /skills use openai-cli, /skills off and /context show. Selections and deactivation are session-local. Activation prints skill: <name> with its selection reason; set skills_quiet=true to suppress routine notices.
With a tool-capable provider, an active skill with eligible documentation exposes clai_read_skill_reference({"skill":"name","path":"references/guide.md"}). This read-only internal tool works with use_tools=false; it does not enable external MCP tools. Providers without tool calling still receive the selected skill's main instructions and display a notice when references are unavailable.
Eligible references are .md, .txt, .rst and .adoc files at the skill root (except SKILL.md) or within references/. Scripts, assets, hidden files and arbitrary filesystem paths are excluded. Only paths listed at activation can be read. Absolute/drive/UNC paths, traversal, escaping symlinks and non-regular files are rejected. Active skill directories are pinned for the request; repeated reference reads use request-local cached text. Oversized files are rejected with a diagnostic, never executed.
Discovery is limited to six directory levels below each collection root, 2,000 scheduled directories and 512 candidate skills, with at most 512 entries in each scanned directory. Cycles and duplicate directory aliases are skipped; limit and traversal diagnostics are visible. Reference listings allow 32 paths, four nested levels and 256 examined entries; a reference subdirectory with over 128 entries is omitted. Each request permits eight reference calls. YAML frontmatter is capped at 8 KiB. Relative symlinks are accepted only within the applicable allowed root: global configuration directory for AGENTS, project root for project AGENTS, the selected skills collection for skill discovery, or the active skill directory for references. Collection roots must themselves resolve within the home/project scope. Absolute symlinks are unsupported even when their apparent target is inside a permitted root. These are deliberate compatibility limits.
Applicable guidance, the bounded skill metadata index, activated instructions and requested references are sent to the configured response provider. They are untrusted content and cannot change CLAI configuration, execute scripts, grant tool permissions, or bypass command review. They are not supplied separately to System One routing or auditing. Expanded instruction documents are not stored in history, and reference results are redacted before history is saved; model answers and user messages retain normal history behavior. Only use local instructions you are willing to share with that provider.
examples/skills/openai-cli is a portable, optional example. Copy that entire directory into a discovered .agents/skills directory to try it; it is not installed automatically. Its basic model-list command is verified against the official CLI reference, and its bundled reference links to version-aware documentation without fixed model IDs or credentials.
With that skill installed, clai list the OpenAI API models I have access to activates openai-cli and can propose openai models list through the normal approval flow. clai --skill openai-cli ... forces selection. clai list the files in this directory does not activate it.
CLAI creates ~/.config/clai.cfg on first use. It uses the established CLAI key=value format. The config path must be a regular file rather than a directory or symbolic link; CLAI enforces mode 0600 before reading it.
| Key | Default | Purpose |
|---|---|---|
key |
empty | Provider credential. It must be non-empty before a request is made. |
hi_contrast |
false |
Render informational text without the muted italic style. |
expose_current_dir |
true |
Include the current working directory in model context. |
max_history_turns |
10 |
Maximum number of user turns retained in persisted history. |
api |
https://api.openai.com/v1/chat/completions |
Provider endpoint used for requests and adapter detection. |
model |
gpt-4.1 |
Model sent to the provider. |
json_mode |
false |
Ask the provider to enforce CLAI's structured response schema. |
temp |
0.1 |
Sampling temperature. Invalid values fall back to 0.1. |
tokens |
500 |
Maximum requested output tokens. Invalid or non-positive values fall back to 500. |
reasoning |
empty | Optional reasoning-effort value; provider behavior is described above. |
system_one_key |
empty | Optional System One-compatible API credential for typed intent routing and risk auditing. |
system_one_api |
https://api.typesafe.ai/v1/systemone |
System One-compatible HTTPS evaluation endpoint. Used only when key, API, and model are all present. Redirects must remain on the configured HTTPS origin (same host and port). |
system_one_model |
jev-latest |
System One model used for typed judgments. |
use_tools |
false |
Enable external MCP discovery and invocation; the restricted internal skill reference reader is independent. |
share_command_results |
false |
Send bounded command results for immediate model interpretation and retain them for later context. |
result_lines |
20 |
Maximum recent stdout and stderr lines stored for each shared result. |
confirm_dangerous_commands |
true |
Require a second confirmation for danger-zone commands. |
risk_appetite |
0 |
Automatic execution policy from 0 through 2; invalid values fall back to 0. |
exec_query |
empty | Replace the built-in command-generation guidance when set. |
question_query |
empty | Replace answer-only guidance for clai --question <question> or requests routed as questions by System One. Ordinary requests without System One use exec_query, regardless of punctuation. |
error_query |
empty | Replace the built-in error-recovery guidance when set. |
agents_enabled |
true |
Discover applicable AGENTS guidance. |
agents_max_bytes |
32768 |
Aggregate guidance bytes; capped at 4 MiB. |
agents_file_max_bytes |
16384 |
Per-file guidance bytes; capped at 1 MiB. |
skills_enabled |
true |
Discover and activate local Agent Skills. |
skills_auto_activate |
true |
Allow deterministic local relevance matching. |
skills_max_index_chars |
8000 |
Serialized metadata index characters; capped at 65,536. |
skills_max_active |
2 |
Automatically selected skills, from 0 through 8. Explicit selections can still activate up to eight. |
skills_file_max_bytes |
24576 |
Per-selected-SKILL.md bytes, including frontmatter; capped at 1 MiB. |
skills_reference_max_bytes |
16384 |
Per-reference text bytes, capped at 16 KiB. Tool results also retain the registry's 32 KiB serialized-result limit. |
skills_quiet |
false |
Suppress routine skill activation/reference notices. Warnings remain visible. |
Re-run clai setup to change the credential, endpoint, model, or risk appetite. Edit the file directly for the remaining settings.
The default compatible paths are:
| Data | Default path | Override |
|---|---|---|
| Configuration | ~/.config/clai.cfg |
CLAI_CONFIG |
| History | ${XDG_STATE_HOME:-$HOME/.local/state}/clai/history_com.json |
CLAI_HISTORY |
Path overrides are useful for tests or separate profiles:
export CLAI_CONFIG=/tmp/clai-profile/config.cfg
export CLAI_HISTORY=/tmp/clai-profile/history.jsonCLAI persists user and assistant conversation messages as JSON. System prompts are rebuilt for each request and are not retained. History is trimmed by user turn according to max_history_turns.
Inspect or clear it with:
clai --show-history
clai --show-history --verbose
clai --clear-historyCommand stdout, stderr, exit status, and whether the command was edited are not stored by default. Toggle that behavior with:
clai --show-results-sharing
clai --toggle-results-sharingWhen enabled, every executed command is followed by a second model request. CLAI sends the original request together with the command, exit status, edit status, and at most result_lines recent lines and 64 KiB from each stdout and stderr stream. The byte limit also bounds a single unusually long line. CLAI then displays and stores the model's conclusions—for example, whether observed load and memory figures indicate that a machine is overwhelmed.
The interpretation request cannot call tools, and CLAI discards any command, risk, or variables returned in the interpretation response. Both successful and failed command results are interpreted. This adds one provider request, with corresponding latency and cost, for each executed command.
Shared results also become part of later conversation context. Do not enable sharing for commands likely to expose secrets or sensitive data, and treat command output as potentially untrusted content; the interpretation prompt explicitly tells the model not to follow instructions found in stdout or stderr.
History files and newly created state directories use restrictive permissions. Clearing history removes the persisted history file but does not delete the configuration.
CLAI loads tools from independent Model Context Protocol servers at runtime. A tool server is a separate executable, can live in its own repository, and does not require an import, registry edit, CLAI branch, or CLAI rebuild. CLAI uses the official MCP Go SDK over stdin/stdout.
External tools are disabled by default. Opt in by editing ~/.config/clai.cfg:
use_tools=trueWhen use_tools=false, ordinary and interactive requests do not scan MCP manifests, load cached MCP definitions, start tool servers, expose external tool schemas, or execute external tools. The explicit clai tools list|reload and interactive /tools list|reload management commands remain available for inspecting and developing installed tools, but those tools are not exposed to normal requests until enabled. The separate, read-only skill reference reader can operate with external tools disabled.
CLAI scans ${XDG_CONFIG_HOME:-$HOME/.config}/clai/tools.d/*.json. Set CLAI_TOOLS_DIR to override that location. Tool definitions are cached in a private .tool-cache file in that directory, so an unchanged server does not start merely to advertise its tools. The server process starts lazily only if the model actually calls one of its tools, then remains available for the rest of that CLAI process.
The first request after installing or changing a manifest or executable must discover that server's definitions. Executable contents—not only timestamps and file sizes—participate in cache invalidation. CLAI discovers all new or stale servers concurrently and refreshes the cache. Interactive sessions check for those changes before each request. Normal requests through providers without tool-call support skip external-tool loading entirely.
These commands are also available:
clai tools list
clai tools reloadInside an interactive session, use /tools list or /tools reload. tools list uses valid cached definitions and discovers only new or changed servers. tools reload deliberately starts every enabled server and rediscovers all definitions, which is useful while developing a plugin.
Each server's ID namespaces its tools. A server with ID wikipedia and MCP tool lookup is exposed to the LLM as wikipedia__lookup, preventing collisions between independently maintained plugins. Broken manifests and servers produce warnings and are omitted without disabling healthy plugins.
Tool calls are currently available through OpenAI, generic OpenAI-compatible endpoints, and Ollama. Anthropic and Gemini requests do not yet include CLAI tool definitions.
examples/wikipedia/main.go is a complete standalone Go MCP server. It searches Wikipedia, retrieves the best matching introductory plain-text extract, and returns structured title, summary, language, and source URL fields. Its wikipedia.json manifest is deliberately copyable.
Install it independently of the CLAI binary:
make install-wikipedia
clai tools listThe target builds the server as ~/.config/clai/tools.d/wikipedia and installs its private manifest beside it. CLAI will then report successful calls without exposing raw arguments:
Used the Wikipedia tool with query "Margaret Thatcher".
Start a new repository rather than adding the tool to CLAI:
mkdir my-clai-tool
cd my-clai-tool
git init
go mod init example.com/my-clai-tool
go get github.com/modelcontextprotocol/go-sdk/mcpThe essential server shape is:
package main
import (
"context"
"errors"
"io"
"log"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type Arguments struct {
Query string `json:"query" jsonschema:"topic to find"`
}
type Result struct {
Answer string `json:"answer"`
Source string `json:"source"`
}
func lookup(_ context.Context, _ *mcp.CallToolRequest, input Arguments) (*mcp.CallToolResult, Result, error) {
return nil, Result{Answer: "...", Source: "https://example.com/..."}, nil
}
func main() {
server := mcp.NewServer(&mcp.Implementation{Name: "my-service", Version: "1.0.0"}, nil)
mcp.AddTool(server, &mcp.Tool{
Name: "lookup",
Title: "My Service",
Description: "Look up information using My Service.",
}, lookup)
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil &&
!errors.Is(err, io.EOF) && !strings.Contains(err.Error(), "server is closing: EOF") {
log.Print(err)
}
}MCP derives the JSON input and output schemas from the typed structs. Keep stdout exclusively for MCP messages; write diagnostics to stderr. Unit-test API behaviour with an injected HTTP client, as the Wikipedia example does.
Build the executable, place it anywhere you control, and add a manifest under tools.d:
{
"id": "my_service",
"command": "/home/alice/.local/bin/my-clai-tool",
"capabilities": ["network-read"],
"safe_arguments": {
"lookup": ["query"]
},
"timeout_seconds": 20
}command may be absolute or relative to the manifest. Manifests must be regular files that are not group- or world-writable. Commands must be executable regular files and must not be group- or world-writable.
| Manifest field | Purpose |
|---|---|
id |
Required server namespace of up to 32 letters, digits, _, or -. |
command |
Required absolute executable path or path relative to the manifest. |
args |
Optional fixed command arguments. |
environment |
Environment-variable names explicitly inherited from CLAI. |
capabilities |
Descriptive network-read, local-read, or local-write metadata; it is not an OS sandbox. |
safe_arguments |
Per-tool string arguments that may appear in invocation notices. |
timeout_seconds |
Startup, discovery, and per-call timeout from 1 to 300 seconds; defaults to 15. |
enabled |
Optional boolean; defaults to true. |
safe_arguments controls which string arguments CLAI may show in an invocation notice. Omit it for tools whose arguments may be sensitive. CLAI never displays raw arguments automatically.
Commit the tool's source, tests, go.mod, go.sum, README, and an example manifest to its own repository. Publish binaries or let users install with go install; only the local manifest and executable belong on the CLAI machine.
External servers inherit no environment variables by default. A manifest allowlists names whose current values CLAI may pass to that process:
{
"id": "web_crawler",
"command": "/home/alice/.local/bin/my-crawler",
"environment": ["SPECIAL_CRAWLER_API_KEY"],
"capabilities": ["network-read"],
"timeout_seconds": 30
}The Go server reads it normally:
apiKey := os.Getenv("SPECIAL_CRAWLER_API_KEY")Store only the variable name in Git. Supply its value through the shell, systemd, a container secret, or another secret manager. Do not include keys in the MCP schema, invocation summaries, results, logs, URLs, or returned errors.
External servers run with the invoking user's authority and must be treated as trusted executable code. CLAI restricts their inherited environment, enforces manifest and executable permissions, bounds each discovery and call by the manifest timeout, limits encoded results to 32 KiB, and closes started subprocesses on reload or exit. The private definition cache contains schemas and descriptions only; it does not contain environment values, tool results, or API keys.
Caution
Tool results are added to the conversation and may be sent to the configured model provider. Bound server-side network response sizes, return only necessary fields, include source URLs, and treat retrieved page content as untrusted data that may contain prompt injection.
The Go implementation keeps orchestration separate from operating-system and provider boundaries:
| Package | Responsibility |
|---|---|
cmd/clai |
Cobra entry point, signals, help, version, and shell initialization. |
internal/app |
Session orchestration, prompts, structured replies, variables, risk decisions, and error recovery. |
internal/config |
Compatible key=value configuration, defaults, validation, and atomic private writes. |
internal/history |
Compatible JSON history, retention, rendering, clearing, and atomic persistence. |
internal/model |
Shared typed messages, tool calls, replies, risks, and command results. |
internal/provider |
Native HTTP payloads, authentication, response parsing, and provider-specific structured output. |
internal/runner |
Context-aware Bash execution, live output, capture, and result truncation. |
internal/ui |
Inline terminal styling, prompts, secret input, setup, confirmations, and spinner. |
pkg/tool |
Internal adapter contract, validation, provider definitions, dispatch, and result limits. |
internal/mcptools |
Manifest discovery, restricted subprocess environments, MCP sessions, namespacing, reloads, and lifecycle. |
examples/wikipedia |
Copyable standalone Wikipedia MCP server, manifest, and isolated HTTP tests. |
The application depends on interfaces for the provider client and command runner. That keeps orchestration tests deterministic and avoids live network calls or real command execution.
- Cobra supplies the command lifecycle, help, and version behavior while CLAI retains free-form arguments.
- Lip Gloss supplies declarative terminal styling.
golang.org/x/termprovides terminal detection and hidden API-key input.- The official MCP Go SDK supplies the external tool client/server protocol and stdio transport.
- Go's
net/httpandencoding/jsonremain explicit because provider protocols differ enough that a generic REST abstraction would hide important behavior. - A full-screen Bubble Tea application is intentionally out of scope; CLAI keeps its compact inline workflow.
Install Go 1.27.3 and Bats:
sudo apt update
sudo apt install batsAvailable targets:
| Command | Checks performed |
|---|---|
make build |
Build ./clai from ./cmd/clai. |
make test |
Run all Go unit tests. |
make vet |
Run go vet ./.... |
make integration-test |
Run the installer and migration-cleanup Bats suites. |
make check |
Run vet, Go tests, and installer tests. |
make install-wikipedia |
Build and install the optional tutorial MCP server and manifest. |
make cleanup-legacy |
Dry-run the conservative Bash-install cleanup script. |
make clean |
Run go clean and remove the local clai binary. |
Run the race detector separately when changing concurrent or I/O behavior:
go test -race ./...Tests use fake providers and isolated temporary state; they do not make successful live API calls. CI runs the full make check workflow.
GoReleaser builds static Linux, macOS, and Windows archives and a checksum file from tags. Test the release configuration locally without publishing:
goreleaser release --snapshot --clean --skip=publishPushing a semantic-version tag that matches internal/app/VERSION runs the release workflow, repeats the complete cross-platform check suite, and publishes only a GitHub Release with the configured archives and checksums.txt. No package manager, container registry, or announcement publisher is configured. The workflow checks out and validates the selected tag before building, so the binary version and archive names continue to come from that tag.
For a new release, update internal/app/VERSION in the release PR, merge it, then create and push the matching vX.Y.Z tag from that merge commit. Do not maintain the release number in any other source or workflow file.
The Go implementation replaces the former Bash application while preserving the clai request words here interface and established configuration, history, prompt, risk, and shell-initialization behavior. The removed Bash sources, documentation, and tests remain available through Git history; they are not duplicated in the current working tree or release artifacts.
The Bash application had grown into a large program responsible for provider-specific JSON, state persistence, process execution, terminal presentation, and an in-process plugin contract. Go provides clearer package boundaries and compile-time types for those responsibilities, deterministic HTTP and JSON handling, bounded and cancellation-aware command execution, race-tested concurrency, and reproducible native release binaries. Users no longer need curl or jq at runtime.
The migration also replaces sourced Bash plugins with independently versioned MCP server processes. A plugin can now live in its own repository, use its own dependencies and API credentials, be discovered without changing CLAI, and fail without sharing CLAI's shell process. This is an intentional plugin-contract change; legacy ~/.clai_tools/*.sh files are not loaded by the Go version.
| Path or integration | Migration treatment |
|---|---|
~/.config/clai.cfg or CLAI_CONFIG |
Reused. Contains provider settings and must not be deleted. |
${XDG_STATE_HOME:-~/.local/state}/clai/history_com.json or CLAI_HISTORY |
Reused. Conversation history must not be deleted. |
Existing shell alias from clai shell-init |
Reused because it invokes the same clai command name. |
/usr/local/bin/clai or the selected install target |
Reused as the command location and replaced with the Go binary. |
${XDG_CONFIG_HOME:-~/.config}/clai/tools.d or CLAI_TOOLS_DIR |
Current Go MCP plugins; never treated as legacy. |
/usr/local/lib/clai/clai.sh |
Obsolete after the active command has been replaced by the Go binary. |
The three unmodified stock scripts under ~/.clai_tools |
Obsolete. Modified and third-party files are preserved for manual migration or archival. |
The cleanup does not uninstall curl, jq, Bash, or any system package because they may be used by unrelated software.
Optionally create a private config backup, then install the Go version with either the release bootstrap or a source checkout.
The release bootstrap is the quickest migration path. It downloads the binary for the current platform, verifies the release checksum and reported version, and replaces the active /usr/local/bin/clai command:
install -m 0600 ~/.config/clai.cfg ~/.config/clai.cfg.pre-go
curl -fsSL https://raw.githubusercontent.com/merefield/clai/main/install-release.sh | shSet CLAI_BIN_DIR="$HOME/.local/bin" on the sh command as shown in Quickstart when the existing command is installed there. Alternatively, build and install from source:
git clone https://github.com/merefield/clai.git
cd clai
./install.shWhichever installation path you choose, test the Go binary before cleaning anything:
clai --version
clai how much is 3 times piThe legacy cleanup script remains in the repository. If you used the release bootstrap without an existing checkout, clone the repository before continuing. Review the cleanup without changing the filesystem:
./scripts/cleanup-legacy.sh
# or: make cleanup-legacyIf every listed path is expected, apply it:
./scripts/cleanup-legacy.sh --applyThe script refuses to proceed unless the active clai is a native Linux or macOS binary that identifies itself through clai --version, and refuses to remove a legacy payload still targeted by the active command. It removes only files whose SHA-256 hashes match the final stock Bash release, removes directories only when they become empty, and prints every preserved path. Unknown, modified, and third-party legacy plugins remain under ~/.clai_tools for manual porting to MCP or archival.
For non-default old locations, identify both paths explicitly:
CLAI_LEGACY_INSTALL_DIR="$HOME/.local/lib/clai" \
CLAI_LEGACY_TOOLS_DIR="$HOME/.clai_tools" \
./scripts/cleanup-legacy.sh --clai-path "$HOME/.local/bin/clai"Do not apply cleanup until the Go binary has passed your normal workflows. Before cleanup, rollback is simply restoring the old command target; afterwards, retrieve the Bash implementation from Git history if it is needed.
CLAI is a hard fork of bash-ai, which was inspired by Your AI.
CLAI is distributed under the GNU General Public License version 3 or any later version.
