# phimint **Pronounced** `/ˈfaɪmɪnt/` ("fie-mint", phi + mint) — a terminal AI coding agent built on [phi-agent](https://docs.phiagent.dev/), the agent runtime beneath it. *Not just good at chat — it gets the job done right.* [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Rust](https://img.shields.io/badge/Rust-edition%202024-orange.svg)](https://www.rust-lang.org) [![中文](https://img.shields.io/badge/README-中文-blue.svg)](README_CN.md) ![phimint demo](docs/assets/readme-demo.gif) *phimint needs one API key in `~/.phimint/config.json` — a one-minute step, see [Quick start](#quick-start).* phimint runs the full coding loop — understand the request, read the code, change multiple files, compile and test, iterate on failures. Project page: [hibuka-labs.github.io/phimint](https://hibuka-labs.github.io/phimint/) — English / [中文](https://hibuka-labs.github.io/phimint/zh/). ## Features - **Code cells (CodeAct)** — `code_cell` runs code in a resident Python session: one call is one step of cells sharing one session, so variables, imports and functions survive across calls. Mechanical multi-step work (bulk edits, renaming, data wrangling) rides a short cell script instead of a long chain of single-purpose tool calls. The engine is behind a compile-time kill switch — `--no-default-features` drops it from the binary entirely. - **Streaming TUI** — ratatui + crossterm: fixed bottom composer, token-by-token streaming, inline tool calls, live line-by-line shell output, approval popups, startup banner that detects your terminal's light/dark background. - **Slash commands** — `.claude/skills`-compatible skills: `/skill-name args` injects the skill body into the turn. `/resume` reopens a previous session with an interactive picker. - **External tools** — plug in your own tools behind one contract (WIT + Component Model): MCP servers (`/connect`), user scripts, and wasm plugin packs (`plugin add` / `plugin remove`). `/tools` shows every tool's source, namespace and tier with one-key disable; secrets live only as `${ENV:VAR}` references — never literal on disk; `add_tool` lets the agent write and register a tool for you behind a full confirmation card (argv, absolute path, timeout). - **Pull-based context** — `repo_map` (tree-sitter symbol/structure index) plus `search_content` (ripgrep). The agent searches and reads on demand instead of stuffing the whole repo into context. - **LSP fast inner loop** — lazily started per-language servers (rust-analyzer, typescript-language-server, clangd); `file:line:col` diagnostics in seconds without recompiling. Falls back to shell when a server is missing. - **Multi-agent fan-out, push-based fan-in** — `spawn_agent` sends sub-agents to investigate (read-only by default; write capability is requested per spawn when a task must edit). The main agent gets the **full reports at end of turn** (no polling). The TUI task panel tracks each sub-agent live; a 10-minute per-task hard timeout guarantees delivery. - **Design canvas & `ask_user`** — the agent can *show* you options instead of describing them: 2–4 candidates land as a screen in your browser (layout mockups, option sets, comparisons) and your click comes back as a real user turn; `ask_user` asks concept picks right in the terminal picker. The browser opens only after a one-time consent, busy clicks queue at the turn boundary, and while a screen awaits your pick the run parks — the agent never decides your design choices for you. - **Desktop with consent** — the agent can look at your screen and move the pointer. *Looking* and *moving* never ask; changing the machine asks every time, through the same approval gate the tool path already uses. - **Three approval modes** — `auto` (full auto), `ask` (prompt on writes/risky shell), `deny` (read-only). Sub-agents inherit the main agent's mode: in `ask`, every sub-agent write prompts in a popup that names the requesting sub-agent. - **Guardrails** — reasoning-only spin correction, max-turns nudge, provider truncation guard (lying `finish_reason` tolerance, from agent-base), 16k tool-output cap, and a verify gate: a hand-back with no green compile newer than the last edit is intercepted once, then carries a visible marker. - **Multi-language** — `code-intel` registry (extension → LSP server → build-command fallback): Rust, TypeScript, JavaScript, C, C++ out of the box. - **Modular base** — chat UI components and language intelligence are distilled into their own crates: [`phi-tui`](https://github.com/hibuka-labs/phi-tui) (chat TUI components) and [`code-intel`](https://github.com/hibuka-labs/code-intel) (LSP/repomap/ripgrep cores). phimint keeps only the product shell. ## Quick start Three steps to a running agent: install a prebuilt binary (**no Rust required**), add an API key, run. ### 1. Install Prebuilt binaries ship with every release (macOS / Linux / Windows, arm64 + x64). One-liner (mainland China: use the Gitee line): ```bash curl -fsSL https://github.com/hibuka-labs/phimint/releases/latest/download/install.sh | bash # GitHub curl -fsSL https://gitee.com/chenkangzeng_admin/phimint/releases/download/latest/install.sh | PHIMINT_MIRROR=gitee bash # Gitee (China) ``` Windows (PowerShell): ```powershell irm https://github.com/hibuka-labs/phimint/releases/latest/download/install.ps1 | iex # GitHub $env:PHIMINT_MIRROR='gitee'; irm https://gitee.com/chenkangzeng_admin/phimint/releases/download/latest/install.ps1 | iex # Gitee (China) ``` The installer does the whole job: it puts the binary in `~/.local/bin` (or the first writable directory already on your `PATH`), adds that directory to your shell profile if it has to, verifies the download's checksum, and writes a ready-to-edit `~/.phimint/config.json`. What is left for you is the `api_key` line in that file. No `python3`, no `sudo`, no manual `export PATH=`. Set `PHIMINT_NO_PATH_EDIT=1` if you would rather manage `PATH` yourself, and `PHIMINT_INSTALL_DIR=` to choose the install directory. Or use a package manager: ```bash brew install hibuka-labs/phimint/phimint # macOS / Linux (Homebrew) npm install -g phimint # also: pnpm add -g phimint / yarn global add phimint cargo install phimint # from crates.io ``` Upgrade: `phimint update` for one-liner installs, or `brew upgrade phimint` / `npm install -g phimint@latest` / `cargo install phimint --force` — matching how you installed. Mainland China users can download from [Gitee Releases](https://gitee.com/chenkangzeng_admin/phimint/releases); the updater falls back to the Gitee mirror automatically. Uninstall: `phimint uninstall`. It removes the binary through whichever channel installed it, then deletes `~/.phimint/` — your config (including the API key), sessions, history and notes. `--keep-data` leaves that directory alone, `--yes` skips the prompt. On Windows it also drops the PATH entry the installer added; on Unix it removes the marked PATH block from your shell profile. ### 2. Configure A one-minute step: you need an API key for any supported model provider (OpenAI / Anthropic / DeepSeek / Aliyun / Moonshot / Gemini / Ollama …). The installer already wrote `~/.phimint/config.json` — open it and fill in the `api_key` line. Installed another way? `phimint init` writes the same file, or `phimint config set api_key sk-xxx` does it for you. `phimint config show` prints what phimint resolved, with the key masked. With no usable key — not in the file, not in `--api-key`, not in `PHIMINT_API_KEY` — phimint stops before the first turn and names the file to edit; it never starts a conversation it cannot pay for. If the endpoint turns the key down later (401/403), one notice in the transcript names the file, the masked key and the endpoint, so a dead key is a one-line fix instead of a silent failure. The file is JSON5 — comments and trailing commas welcome: ```json { "base_url": "https://api.openai.com/v1", "api_key": "sk-xxx", "main": "gpt-5.4-mini", "lite": "gpt-4o-mini", "advanced": "o1-preview" } ``` Different provider per tier: ```json { "main": { "model": "gpt-5.4-mini", "base_url": "https://api.openai.com/v1", "api_key": "sk-openai" }, "lite": { "model": "deepseek-chat", "base_url": "https://api.deepseek.com/v1", "api_key": "sk-deepseek" }, "advanced": { "model": "claude-opus-4", "base_url": "https://api.anthropic.com", "api_key": "sk-ant" } } ``` Three tiers, three jobs. `main` is the chat session you talk to. `lite` is the cheap lane: a sub-agent spawn resolves it by default, and the completion judge stays on it even after a `/model` pick — that job is classification, not conversation. An unconfigured `lite` follows `main`, so a one-model setup keeps working. `advanced` is the strong lane for the work that deserves it. | Tier | Serves | |---|---| | `main` | the chat session — the `model` pointer below overrides it | | `lite` | sub-agent spawns by default, and the completion judge | | `advanced` | the strong lane — a spawn can ask for it by name | `/lite ` and `/advanced ` retarget a tier, and the next spawn picks the new name up. `/models` lists what is configured; `/focus` parks the chat on the `lite` tier for the length of a session. `/model` switching is stored as a top-level `model` **selection pointer** — a separate key that overrides `main` while it is present. A switch never rewrites the tier definitions above; only this pointer moves: ```json { "main": "gpt-5.4-mini", "model": "gpt-5.4" } ``` The pointer is a bare model name, or an object carrying only the endpoint fields that differ from this file's defaults (e.g. `"model": { "model": "claude-opus-4", "base_url": "https://api.anthropic.com", "api_key": "sk-ant" }`). Bare `/model` opens a picker over the configured models (with a *Clear selection* row); `/model ` sets one by name. Clearing the selection deletes the pointer and falls back to the configured `main`. `lite` and `advanced` are never touched by a switch — an unconfigured tier follows the effective `main`, which is the pointer while one is set. If a pointer is in force at startup, phimint names it in a boot notice. Or pass everything on the command line (see `config.json.example` for the full schema): ```bash phimint --model gpt-5.4-mini --base-url https://api.openai.com/v1 --api-key sk-xxx ``` `config.json` also carries optional sections: `update` (update channels and manifest endpoints for automated update checks) and `ui.popup` (the `@`/`/` popup band's layout and dimensions). ### 3. Run ```bash cd your-project && phimint ``` Type a task at the composer — e.g. `add a cached get_user function to the lib` — and watch it read the code, edit, compile, and test until green. Keys, approval modes, and a full walkthrough live under [Usage](#usage). ### Run from source Requires a Rust toolchain (edition 2024). Optional: language servers on `PATH` (rust-analyzer, typescript-language-server, clangd) for the LSP fast loop; `cargo` / `npm` / `make` and friends for whatever project you point it at. Everything else comes from crates.io — `cargo build` needs no sibling checkouts. ```bash cargo run # run straight from the source tree cargo run -- -w ../some-project # point at another workspace ``` ## Usage ``` phimint [OPTIONS] [COMMAND] Commands: update upgrade to the latest release (self-replaces standalone installs; brew/npm/cargo installs get the matching upgrade command instead). `phimint update --check` reports only. uninstall remove phimint and the data it left behind. The binary goes back to whichever channel installed it; then ~/.phimint/ (API key, sessions, history, notes) is deleted and the PATH entry the installer added is dropped — the user-PATH entry on Windows, the marked block in your shell profile on Unix. --keep-data leaves ~/.phimint/ alone. --yes skips the prompt. init write the config skeleton without starting the TUI. The installer runs `phimint init --if-missing`; an existing config is never touched. config inspect or edit the config file without starting the TUI. `config show` prints what phimint resolved with the API key masked; `config set ` writes one member (api_key, base_url, protocol, main). For api_key, pass `-` to read the secret from stdin instead of the command line. -w, --workspace workspace directory (default: current directory) --model main model for this run only (overrides the config.json `model` pointer and `main`; never written back to the file) --lite-model lite model (overrides config.json) --advanced-model advanced model (overrides config.json) --base-url API base URL (overrides config.json) --api-key API key (overrides config.json) --protocol API protocol (default: inferred from model) --config config file path (default: ~/.phimint/config.json) --approval auto (default) / ask / deny --shell-timeout-ms shell command timeout (default 120000) --session session ID (default: auto; reuse to append turns) --resume pick a previous session interactively --log-level session.log level (default info) --color-scheme banner colors: auto (default) / dark / light --banner startup banner (default on) --thinking-budget reasoning token budget (default 8192) --reasoning-effort none/low/medium/high/xhigh (default medium) --token-budget context-window token budget (default 210000) --session-retention-days days to keep session history (default 30) --no-update-check skip the startup update check ``` ### Interface & interaction - **Composer & transcript** — fixed bottom composer (multi-line, bracketed paste, CJK double-width aware); output streams token-by-token with markdown rendering. Type `@` for file-path completion, `/` for the skill picker. - **Keys** | Key | Action | |---|---| | `Enter` | send (also jumps to the newest lines) | | `Ctrl+J` | newline (works on every terminal; `Shift+Enter` too where the Kitty keyboard protocol is reported) | | `Up` / `Down` | recall sent inputs (fish-style: your current draft cycles with them) | | `Alt+Up` / `Alt+Down` | move the cursor inside a multi-line draft | | `Shift+Tab` | toggle approval mode (auto ⇄ ask) | | `Ctrl+O` | expand/collapse thinking blocks and long pasted messages | | `Ctrl+V` | paste a clipboard image (screenshot) — a model that cannot see images is refused | | `Ctrl+Y` | copy the last reply | | `Ctrl+C` | context-sensitive: copy selection → cancel run (a draft that produced nothing comes back to the composer) → clear the composer draft → quit (press twice) | | `Ctrl+D` | quit | | `Esc` | clear selection, else clear the composer | | `PageUp` / `PageDown` | scroll the transcript | | `Ctrl+End` | jump to the newest lines | - **Mouse** — wheel scrolls; left-drag selects lines; right-click opens a copy menu. - **Terminal notes** — In macOS Terminal.app, enable **View → Allow Mouse Reporting** for wheel/trackpad scrolling. If it is disabled, use `fn+Up` / `fn+Down` to page through the transcript. Terminal.app does not report the Kitty keyboard protocol, so `Cmd+C` cannot be detected as a copy shortcut; use `Ctrl+C` while a selection is active or the right-click copy menu instead. - **Approvals** | Mode | Behavior | |---|---| | `auto` | everything allowed; write-capable sub-agents write freely too | | `ask` | each write prompts: `y` allow once / `a` allow always / `n` deny | | `deny` | read-only; all writes rejected | `Shift+Tab` toggles `auto` ⇄ `ask` mid-session (the status bar shows the current mode); `--approval` picks the starting mode. ### A typical task ``` you: "add a cached get_user function to the lib" 1. repo_map for structure → search_content to locate → read_file the relevant code 2. edit_file / write_file the change 3. diagnostics for type errors in seconds (LSP fast loop, no recompile) 4. execute_command cargo check / tests → fix → rerun until green 5. summarize the diff and report ``` ### A typical multi-agent run ``` you: "investigate questions X / Y / Z" 1. spawn_agent × 3 read-only sub-agents (each a narrow slice), main agent ends the turn 2. while they run, the main agent does nothing — reports are pushed, not polled 3. reports arrive batched at once (the task panel shows each sub-agent live) 4. the main agent synthesizes the findings and applies the edits itself — or, for independent work, spawns write-capable sub-agents on disjoint file sets ``` ## Tool surface | Tool | What it does | |---|---| | `code_cell` | run code cells in a resident Python session — state (variables, imports, functions) survives across calls; the fast lane for bulk edits, renaming and data wrangling | | `read_file` / `write_file` / `edit_file` / `list_files` | file I/O and surgical edits (`edit_file` is atomic with 4-level matching; phi-kernel-tools) | | `read_image` | read an image the model can actually see — paste a screenshot with Ctrl+V and the agent looks at it (or point it at a file; phi-kernel-tools) | | `execute_command` | shell (builds/tests/anything; streamed line-by-line, timeout + cancel kills the process group; output over the tool budget spills to a file the agent can read back) | | `search_content` | ripgrep content search (regex) | | `repo_map` | tree-sitter symbol/structure index (multi-language, pull-based context) | | `diagnostics` | LSP diagnostics (fast loop without recompiling; degrades to shell) | | `verify` | run the project's own compile-level check (`cargo check`, `tsc --noEmit`) and report the compiler's real exit code — the command comes from the project (its config, then a convention it already uses, then the language default), never from the model; behind it, a gate holds a hand-back until there is a green light newer than the last edit | | `update_plan` | structured checklist for complex tasks (display-only, Codex-style; agent-base) | | `current_model` | the model serving this session (the agent calls it before answering "which model are you") | | `spawn_agent` / `send_message` / `wait_agent` / `list_agents` / `close_agent` | sub-agent primitives (agent-works; preferred pattern: spawn, end turn, await push) | | `add_tool` | the agent writes and registers a tool for you (root-only; full confirmation card before anything lands) | | `canvas_open` / `canvas_push` / `canvas_close` | design canvas — browser screens with clickable options; one-time consent to open the browser ([`phi-canvas`](https://github.com/hibuka-labs/phi-canvas)) | | `ask_user` | terminal picker for concept picks (2–4 candidates, one shot, Esc = skip; you can type a question back instead of picking) | | `/connect` · `/tools` · `plugin add` / `plugin remove` | external tool surfaces: MCP servers, script tools, wasm plugin packs (approval ledger per tool, fail-closed) | ## How it works phimint is a consumer of the [`phi-agent`](https://docs.phiagent.dev/) framework: the agent loop, ReAct, approvals, session event stream and guards all come from the framework (one facade dependency). phimint brings the product shell — system prompt, tool registration, approval wiring, TUI. - **Push-based fan-in** (agent-works) — sub-agent reports are held on completion and injected as a new turn the instant the main agent's turn ends; progress is display-only (never wakes or interrupts). A task over 10 minutes is hard-stopped with an Error result, so a hung child can always wake the parent. Sub-agents are read-only by default — `write_file` / `edit_file` / `execute_command` stay off their tool surface until write capability is requested for that spawn (`tools: "write"`, or a `coder`/`tester` preset). Write-capable children work disjoint file sets under a file lock (`file locked by ` on collision), and in `ask` every child write is confirmed in a popup. - **TUI architecture** — the shell in `phi-agent-tui` runs a background task per agent turn pushing `RuntimeEvent`s over an mpsc channel; the main loop feeds events into the `App` state machine, polls keys and repaints. `src/shell/wire.rs` fills every slot the shell offers, so the wording and the policy stay on this side. Input and events only meet through channels — no shared mutable state, zero intrusion into the agent loop. - **Task panel** — the shell's `task_panel.rs` books sub-agent lifecycle (appears on spawn, flips on done, recycled 3s after completion, deferred while the root is busy or you're looking at the panel). Each sub-agent streams into its own buffer so interleaved output never cuts lines. - **LSP** — `code-intel` lazily starts one server per language, syncs `didOpen`/`didChange`/`didSave` with cached `publishDiagnostics`, pulled by the `diagnostics` tool. The registry picks the server; code-intel knows nothing about phimint. - **Guards** (framework-provided, product-tuned) — reasoning-only spin: two empty turns in a row injects an "act now" nudge and disables thinking; the end-of-turn judge fails open; `MaxTurnsNudgeMiddleware` forces a final answer in the last 3 turns of a 256-turn budget. ## Sessions & observability Every run lands a session directory under `~/.phimint/sessions//`: | File | Contents | |---|---| | `session.log` | tracing log (via log-core) | | `turn_NNN.jsonl` | structured per-turn event stream (tool calls, text deltas, reasoning …) | | `frames.txt` | flip-book of TUI frames (colors stripped, layout kept) for offline UI replay | | `perf.log` | per-frame render-time CSV (performance regression hunting) | | `session_metrics.json` | per-turn token usage and session totals (phi-telemetry) | Re-running with `--session ` appends new turns to the same directory; `--resume` picks one interactively. Composer history lives in `~/.phimint/input-history.jsonl`, next to the `sessions/` directory (owner-only, one global file; `Up` / `Down` recall it per workspace). ## Project structure ``` phimint/ ├── src/ │ ├── main.rs # CLI entry (clap), assembly, banner color detection │ ├── lib.rs # module root — the binary is a thin shell over it │ ├── cli.rs # the CLI surface: every flag and subcommand │ ├── command.rs # shell command parsing + risk classification │ ├── agent.rs # system prompt + tool registration + multi-agent/guard/approval wiring │ ├── agent/ # agent assembly: build.rs, gate.rs (the verify gate), prompt.rs, spawn_resolve.rs │ ├── approval.rs # two layers: ApprovalPolicy gate + decision handler (TUI queue) │ ├── banner.rs # startup banner (light + dark palettes) │ ├── model/ # config, tiers, routing, persistence, the first-run gate │ ├── shell/ # the TUI shell: wire.rs fills phi-agent-tui's slots │ ├── tools/ # application tool shells (cores live in code-intel / phi-kernel-tools) │ │ ├── diagnostics.rs # LSP diagnostics tool │ │ ├── repomap.rs # repo_map tool │ │ ├── ripgrep.rs # search_content tool │ │ └── verify.rs # the compile-level check behind the verify gate │ ├── external/ # the external tool socket: MCP servers, script tools, wasm plugins │ ├── session/ # session store, resume, retention │ ├── ops/ # uninstall, terminal restore, install-source bookkeeping │ ├── update/ # update checker + release manifest │ └── verify_ledger.rs # the gate's evidence ledger: is the green light newer than the edit? └── Cargo.toml ``` ## Development ```bash cargo build # compile cargo test # all tests (unit tests live in standalone *_tests.rs files) cargo clippy # lint ``` Tests are split from business code (module-separated tests): `app.rs`, `render.rs` and friends hold logic only; tests sit in `app_tests.rs` etc., `#[cfg(test)]`-isolated from production builds but still inside the crate, so `pub(crate)` members are reachable. Dependencies are pure crates.io version references. To hack on a sibling crate (phi-agent, phi-tui, …) at the same time, add an uncommitted `[patch.crates-io]` path override (see [CONTRIBUTING.md](CONTRIBUTING.md)). Design notes live in `notes/` (local, not committed); `docs/` holds public-facing documentation and assets. The README demo GIF is rebuilt from a recorded session: `cargo test frame_reel -- --ignored` writes ANSI frames to `target/reel/`, then `python3 scripts/readme_reel.py` renders them to `docs/assets/readme-demo.gif`. ## License MIT — see [LICENSE](LICENSE). ## Contact GitHub Issues — [hibuka-labs/phimint](https://github.com/hibuka-labs/phimint/issues) [中文](README_CN.md)