A minimal coding agent powered by LLMs with TUI. Simple, opinionated, hackable.
Goal: Achieve self-bootstrapping — use minicode to develop minicode itself.
- ink-based TUI — React for CLIs, full terminal UI with streaming, thinking display, and context usage bar
- Tool use — read, write, edit, bash, agent (sub-agent delegation), activate_skill
- Multi-agent — spawn parallel sub-agents, switch views with Ctrl+O
- Multi-provider — Anthropic, Zhipu, or any Anthropic-compatible API via
model@providerspec - Session persistence — auto-save, resume, rename, per-project isolation
- Smart compression — LLM-based conversation summarization at configurable token threshold
- Extended thinking — configurable thinking budget with dimmed streaming display
- Permission modes — manual (per-tool), yolo (allow all), auto (LLM decides)
- Project prompts — global (
~/.minicode/AGENTS.md) and per-project (./AGENTS.md) prompt files - Skills — extendable workflow automation via agentSkills.io format
- Headless mode — non-TUI operation for scripting and automation
-
Create config at
~/.minicode/config.json:{ "providers": { "anthropic": { "apiKey": "sk-ant-...", "baseURL": "https://api.anthropic.com", "models": { "claude-sonnet-4-5": {}, "claude-opus-4": { "contextLength": 200000 } } } }, "model": "claude-sonnet-4-5@anthropic", "compressionThreshold": 0.8, "thinking": true, "thinkingTokens": 20000, "promptFile": "AGENTS.md" }Model specifier format:
model@provider. Each provider can define multiple models with per-model overrides (e.g.contextLength).Priority: CLI
--model>MODELenv var > configmodelfield. -
Install and run:
bun install bun run dev # Development mode (Bun native TS/TSX) bun run build # Compile to dist/ bun run start # Run built version
minicode # Start TUI with new session
minicode "list files" # Start with initial prompt
minicode --model glm-4.7@zhipu # Override model
minicode --session my-project # Use named session
minicode --resume # Resume most recent session
minicode -H "ls -la" # Headless mode (no TUI)
minicode -H --perm yolo "ls" # Headless with permission modeModel priority: CLI --model > MODEL env var > config model field.
| Command | Description |
|---|---|
/new [name] |
Create new session |
/resume |
List sessions (arrow keys + Enter) |
/resume <name> |
Load specific session |
/rename <name> |
Rename current session |
/compress |
Compress conversation history |
/clear |
Clear history and start fresh |
/effort [level] |
Set thinking effort (low|medium|high|xhigh|max) |
/plan |
Generate executable plan from discussion |
/test |
Run a simple test across all tools |
/skills |
List available skills |
/model |
Switch model/provider via UI |
/exit |
Quit (or Ctrl+C) |
Permission mode: Shift+Tab cycles between manual/yolo/auto
When the main agent delegates sub-tasks via the agent tool, sub-agents run in parallel. Switch between agent views with Ctrl+O.
src/
├── cli.tsx # CLI entry point — renders App, parses args, starts TUI/headless
├── tui.tsx # Top-level TUI App component + hooks
├── headless.ts # Headless (non-TUI) mode runner
├── args.ts # CLI argument parsing (yargs)
├── agent.ts # Agent class with tool execution loop
├── messages.ts # MessageStore (API + display message model)
├── config.ts # Multi-provider config loader
├── tui/
│ ├── Message.tsx # Message display component by role
│ ├── MessageList.tsx # Scrollable message list
│ ├── Header.tsx # App header with model/session info
│ ├── StatusBar.tsx # Bottom status bar with token usage
│ ├── InputArea.tsx # Main input area with mode switching
│ ├── ModalPrompter.tsx # Modal input prompts
│ ├── inputs.tsx # Input component variants (modal, inline, password)
│ ├── tool-display.tsx # Tool call/result rendering
│ └── store.tsx # TUI state management with useReducer
├── commands/
│ └── index.ts # CommandRegistry class + builtin slash commands
├── skills/
│ └── index.ts # SkillRegistry class + builtin skills
├── llm/
│ └── anthropic.ts # AnthropicClient — streaming + non-streaming
├── services/
│ ├── agent-registry.ts # Multi-agent coordination
│ ├── permission.ts # PermissionService (manual/yolo/auto)
│ ├── token-manager.ts # Token tracking + compression triggers
│ └── compression-service.ts # LLM-based conversation summarization
├── tools/
│ ├── index.ts # ToolDef interface + registerTools helper
│ ├── registry.ts # ToolRegistry (register/get/getAll)
│ ├── read.ts # File reading
│ ├── write.ts # File writing
│ ├── edit.ts # Surgical text replacement
│ ├── bash.ts # Command execution
│ ├── agent.ts # Sub-agent delegation tool
│ └── activate_skill.ts # Skill activation tool
└── utils/
├── diff.ts # Unified diff generation
├── display.ts # DisplayAdapter + CallbackDisplay + ConsoleDisplay
├── prompts.ts # Global (~/.minicode/AGENTS.md) and project prompt loading
├── session.ts # SessionManager (v1/v2 persistence) — module singleton
├── session-display.ts # Legacy v1 session → display message conversion
└── logger.ts # pino-based session-scoped logging
- User input → Agent adds message to
MessageStore, sendsstore.toLLMMessages()to LLM - LLM responds with text + tool_use blocks (streamed to TUI via
CallbackDisplayor stdout in headless mode) - Text/thinking streams incrementally; tool_use blocks added to store; tools execute sequentially
- Tool results are pushed as a single user turn with
tool_resultblocks - Store changes fire
onChange→ TUI re-renders fromstore.toDisplayMessages() - Session auto-saved (v2 format) after each exchange
- Token usage tracked; progress bar in status bar; auto-compresses when exceeding threshold
- Sub-agents spawned via
agenttool, managed byAgentRegistry; switch with Ctrl+O
Create a file in src/tools/ implementing the ToolDef interface:
import React from 'react';
import { Text } from 'ink';
import type { ToolDef } from './index.js';
export const myTool: ToolDef = {
name: 'my_tool',
description: 'What it does',
input_schema: { /* JSON Schema */ },
requiresPermission: true, // optional: gate behind PermissionService
formatCall: (args) => <Text>MyTool({JSON.stringify(args)})</Text>,
formatResult: (output, input) => <Text dimColor>done</Text>,
execute: async (args, context) => {
// context.registry — AgentRegistry for sub-agent access
// context.config — parent AgentConfig
return { output: 'result for LLM' };
}
};Then register in src/tools/index.ts.
- Two message layers —
MessageParam[]for LLM API,DisplayMessage[]for UI (store.toDisplayMessages()) - Display adapters —
CallbackDisplayfor TUI (hooks into React state),ConsoleDisplayfor headless/fallback,RecordDisplayfor testing - Registry pattern — Tools and commands use
Map<string, T>withregister()/get()/getAll() - Skill system — agentSkills.io format: directory with
SKILL.md(YAML frontmatter + body), loaded fromskillsDir - Permission modes —
manual(per-tool prompt),yolo(allow all),auto(LLM decides) - Session isolation — Per-project using MD5 hash of cwd (
~/.minicode/sessions/<hash>/)
MIT