Package Exports
- @robota-sdk/agent-cli
Readme
@robota-sdk/agent-cli
AI coding assistant CLI built on Robota SDK. Loads AGENTS.md/CLAUDE.md for project context and provides a tool-calling REPL with Claude Code-compatible permission modes.
Installation
Requires Node.js 22+.
# Global install
npm install -g @robota-sdk/agent-cli
# Or run directly with npx
npx @robota-sdk/agent-climacOS users: Korean/CJK IME input may crash macOS Terminal.app. Use iTerm2 instead. This is a known Ink + Terminal.app issue shared with Claude Code.
After installing globally, the robota command is available system-wide:
robota # Interactive REPL
robota "prompt" # REPL with initial prompt
robota -p "List all files" # Print mode (one-shot, exit after response)Environment Variables
| Variable | Description | Required |
|---|---|---|
ANTHROPIC_API_KEY |
Anthropic API key for the anthropic provider |
Anthropic only |
DASHSCOPE_API_KEY |
Alibaba Cloud Model Studio key for qwen |
Qwen only |
Set your key before running:
export ANTHROPIC_API_KEY=sk-ant-...Development Setup (Monorepo)
# Build dependencies and CLI
pnpm build:deps
pnpm --filter @robota-sdk/agent-cli buildUsage (Monorepo)
# From monorepo root
cd packages/agent-cli
# Development mode (no build needed)
pnpm dev
# Production mode (requires build)
pnpm start
# With arguments
pnpm dev -- --version
pnpm dev -- --permission-mode plan
pnpm dev -- -p "List all TypeScript files in src/"CLI Flags
robota # Interactive REPL (default mode)
robota "prompt" # REPL with initial prompt
robota -p "prompt" # Print mode (one-shot, exit after response)
robota -c # Continue last session
robota -r <session-id> # Resume session by ID
robota --model <model> # Model override (e.g., claude-sonnet-4-6)
robota --language <lang> # Response language (ko, en, ja, zh)
robota --permission-mode <mode> # plan | default | acceptEdits | bypassPermissions
robota --max-turns <n> # Limit agentic turns per interaction
robota --output-format <fmt> # text | json | stream-json (print mode)
robota --system-prompt <text> # Replace system prompt (print mode)
robota --append-system-prompt <text> # Append to system prompt (print mode)
robota --reset # Delete user settings and exit
robota --check-update # Check npm for a newer CLI version and exit
robota --disable-update-check # Skip interactive startup update check for this run
robota --version # Show versionCLI Updates
Robota can check npm for a newer @robota-sdk/agent-cli version:
robota --check-updateWhen an update is available, Robota prints the npm global install command:
npm install -g '@robota-sdk/agent-cli@latest'Robota does not implement its own updater and does not modify ~/.robota/settings.json for update checks. Interactive startup checks use a user-level operational cache at ~/.robota/update-check.json and can be skipped for one run with --disable-update-check. Print/headless mode (robota -p) does not perform automatic startup update checks so scripted stdout and stderr remain deterministic.
Print Mode Output Formats
Print mode (-p) supports three output formats via --output-format:
| Format | Description |
|---|---|
text |
Plain text response to stdout (default) |
json |
Single JSON object: { type, result, session_id, subtype } |
stream-json |
Newline-delimited JSON with content_block_delta streaming events |
Stdin Pipe
When -p is used without a positional argument and stdin is piped, the CLI reads from stdin:
echo "Explain this error" | robota -p
cat file.ts | robota -p "Review this code" --output-format json
git diff | robota -p "Summarize changes" --output-format stream-jsonFirst-Run Setup
When no usable settings file exists, the CLI prompts for:
- Provider selection from the providers assembled into the CLI binary
- Provider-specific setup fields such as model, base URL, and masked API key
- Response language (ko/en/ja/zh, default: en)
Creates ~/.robota/settings.json. Use robota --reset to return to first-run state.
Provider setup is generated from provider definitions. The default CLI build includes Anthropic, OpenAI-compatible, Gemma, and Qwen providers; other embeddings can inject their own provider definitions.
Non-interactive/headless mode never prompts. Configure a provider ahead of time with robota --configure in an interactive terminal, or use robota --configure-provider <profile> --type <type> ... --set-current.
Built-in Tools
The AI agent can invoke 6 tools:
| Tool | Description | Primary Argument |
|---|---|---|
Bash |
Execute shell commands | command |
Read |
Read file contents with line numbers | filePath |
Write |
Write content to a file | filePath |
Edit |
Replace a string in a file | filePath |
Glob |
Find files matching a pattern | pattern |
Grep |
Search file contents with regex | pattern |
Recent TUI Capabilities
- Provider setup is generated from provider definitions, so the default CLI build can configure Anthropic, OpenAI-compatible, Gemma, and Qwen profiles without provider-specific UI branches.
- Interactive startup can check npm for newer CLI versions; print/headless mode skips startup update checks to keep scripted output deterministic.
- Long-running sessions show provider usage summaries, status activity, background job tree rows, and collapsed command-output transcripts.
- Edit results render as context hunks with markdown-friendly diff blocks.
- Background subagents are real runtime jobs with transcripts and resumable task snapshots.
- Explicit multi-agent requests can use the Agent tool
jobsbatch path through the SDK runtime.
Permission System
Every tool call passes through a three-step permission gate:
- Deny list — if any deny pattern matches, the action is blocked
- Allow list — if any allow pattern matches, the action is auto-approved
- Mode policy — the active permission mode determines the decision
Permission Modes
| Mode | Read/Glob/Grep | Write/Edit | Bash |
|---|---|---|---|
plan |
auto | deny | deny |
default |
auto | approve | approve |
acceptEdits |
auto | auto | approve |
bypassPermissions |
auto | auto | auto |
Changing Mode at Runtime
Use the /mode slash command:
> /mode # Show current mode
> /mode plan # Switch to plan (read-only)
> /mode bypassPermissions # Skip all promptsOr set it at startup:
robota --permission-mode planPermission Patterns
Configure in .robota/settings.json or .robota/settings.local.json:
{
"permissions": {
"allow": ["Bash(pnpm *)", "Bash(git status)", "Read(/src/**)"],
"deny": ["Bash(rm -rf *)", "Write(.env)"]
}
}Pattern syntax: ToolName matches any invocation; ToolName(pattern) matches on the primary argument with shell-style globs (*, **).
Keyboard Controls
| Key | Action |
|---|---|
| Enter | Submit input |
| ESC | Abort current execution (graceful — saves partial response) |
| Ctrl+C | Exit process immediately |
| Up/Down | Navigate visual lines in wrapped multi-line input |
| Arrow keys | Navigate slash command autocomplete, permission prompt |
Paste Handling
Bracketed paste mode (DECSET 2004) is enabled on startup. When pasting multiline text, the input area collapses it into a label: [Pasted text #1 +42 lines]. Multiple pastes are numbered sequentially. The full content is expanded on submit.
Single-line paste is inserted directly as typed text. Terminals without bracketed paste fall back to heuristic detection.
Edit Diff Display
After the Edit tool runs, a DiffBlock component renders the change inline:
✓ Edit(src/provider.ts)
│ src/provider.ts
│ - const DEFAULT_MAX_TOKENS = 4096;
│ + const maxTokens = getModelMaxOutput(modelId);Removed lines appear in red with -, added lines in green with +. Diffs longer than 10 lines show the first 8 + a ... and N more lines summary.
Session Management
The CLI supports continuing, resuming, forking, and naming sessions.
CLI Flags
| Flag | Description |
|---|---|
-c, --continue |
Continue the most recent session |
-r, --resume <id> |
Resume a specific session by ID |
--fork-session <id> |
Fork a session (new session with copied history) |
--name <name> |
Assign a name to the session at startup |
TUI Commands
| Command | Description |
|---|---|
/resume |
List recent sessions and resume one |
/rename <name> |
Rename the current session |
Session Name Display
When a session has a name, it appears in three places:
- Input border — session name shown in the input area border
- Terminal title — updated via ANSI escape sequences
- StatusBar — displayed alongside mode, model, and context usage
Slash Commands
| Command | Description |
|---|---|
/help |
Show available commands |
/clear |
Clear conversation history |
/mode [mode] |
Show or change permission mode |
/model [model] |
Select AI model (confirmation prompt, CLI restarts) |
/language [lang] |
Set response language (ko, en, ja, zh), saves and restarts |
/compact [instructions] |
Compress context window |
/cost |
Show session info |
/context |
Context window details |
/permissions |
Show permission rules |
/plugin [subcommand] |
Plugin management TUI |
/resume |
List recent sessions and resume one |
/rename <name> |
Rename the current session |
/exit |
Exit CLI |
Typing / triggers an autocomplete popup with arrow-key navigation and Esc to dismiss. Tab inserts the highlighted command into the input field without executing — continue typing args or press Enter to execute. Enter selects and executes immediately. Commands with subcommands (e.g., /mode, /model) show a nested submenu. Skill commands discovered from .agents/skills/ and .claude/commands/ appear alongside built-in commands.
Plugin Management
The /plugin command opens an interactive TUI for managing bundle plugins:
| Subcommand | Description |
|---|---|
/plugin install <name> |
Install a plugin from marketplace or local path |
/plugin uninstall <name> |
Remove an installed plugin |
/plugin enable <name> |
Enable a disabled plugin |
/plugin disable <name> |
Disable a plugin without uninstalling |
/plugin list |
List installed plugins with status |
/plugin marketplace |
Browse available plugins from configured sources |
Configuration
Settings are merged in this order, from lowest to highest priority:
~/.robota/settings.json(user global)~/.claude/settings.json(user global, Claude Code compatible).robota/settings.json(project, shared).robota/settings.local.json(local, gitignored).claude/settings.json(project, Claude Code compatible).claude/settings.local.json(local, gitignored, Claude Code compatible)
{
"defaultMode": "default",
"language": "en",
"currentProvider": "qwen",
"providers": {
"qwen": {
"type": "qwen",
"model": "qwen-plus",
"apiKey": "$ENV:DASHSCOPE_API_KEY",
"baseURL": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
},
"gemma": {
"type": "gemma",
"model": "supergemma4-26b-uncensored-v2",
"apiKey": "lm-studio",
"baseURL": "http://localhost:1234/v1"
},
"openai": {
"type": "openai",
"model": "<openai-compatible-model>",
"apiKey": "$ENV:OPENAI_API_KEY"
},
"anthropic": {
"type": "anthropic",
"model": "claude-sonnet-4-6",
"apiKey": "$ENV:ANTHROPIC_API_KEY"
}
},
"permissions": {
"allow": ["Bash(pnpm *)"],
"deny": ["Bash(rm -rf *)"]
}
}currentProvider selects a profile from providers. Qwen Model Studio profiles use type: "qwen" with a DashScope-compatible baseURL; the API key is usually stored as $ENV:DASHSCOPE_API_KEY. Gemma-family LM Studio models use type: "gemma" so Robota can apply Gemma-specific channel-marker projection while still talking to the OpenAI-compatible /v1/chat/completions API through baseURL. Generic OpenAI-compatible profiles use type: "openai" and do not apply provider-specific projection. The legacy single-provider shape remains supported:
{
"provider": {
"name": "anthropic",
"model": "claude-sonnet-4-6",
"apiKey": "$ENV:ANTHROPIC_API_KEY"
}
}Context Discovery
The CLI automatically discovers and loads:
- AGENTS.md — walking up from cwd to filesystem root
- CLAUDE.md — same walk-up discovery
- Project metadata — from
package.json,tsconfig.json
All context is assembled into the system prompt.
Memory Management
- Message windowing — React state keeps the most recent 100 messages. Older messages are dropped from the render tree; full history remains in the session store.
- Tool state cleanup — Completed tool execution states are trimmed to the most recent 50 entries.
- React.memo —
MessageItemusesReact.memoto skip redundant re-renders.
Session Logging
Session logs are written to .robota/logs/{sessionId}.jsonl in JSONL format by default, capturing structured events for diagnostics and replay. Background task lifecycle/progress events are logged there as they happen. Child-process subagents also write append-only transcripts to .robota/logs/{sessionId}/subagents/{agentId}.jsonl, including streaming text deltas while the local provider request is still running.
Resumable session JSON is written to .robota/sessions/{sessionId}.json for the current project and includes messages, UI history, the exact system prompt, registered tool schemas, and background task snapshots. High-frequency streaming chunks stay in JSONL transcript files; the session JSON stores task state and transcript paths.
Architecture
The CLI is a pure TUI layer. All business logic lives in @robota-sdk/agent-sdk's InteractiveSession. useInteractiveSession is the sole React↔SDK bridge, converting SDK events to React state.
bin.ts → cli.ts (arg parsing)
└── ui/render.tsx → App.tsx (thin JSX shell)
├── useInteractiveSession (ONLY React↔SDK bridge)
│ ├── InteractiveSession (SDK)
│ ├── CommandRegistry (SDK, re-exported by CLI)
│ │ ├── BuiltinCommandSource (SDK)
│ │ ├── SkillCommandSource (SDK, discovers from 4 paths)
│ │ └── PluginCommandSource (CLI-local)
│ └── SystemCommandExecutor (SDK)
├── plugin-hooks-merger.ts (merges plugin hooks into SDK config)
├── MessageList.tsx
├── InputArea.tsx (CjkTextInput, bracketed paste, slash detection)
├── StatusBar.tsx (mode, model, context %, message count)
├── PermissionPrompt.tsx (arrow-key Allow/Deny)
├── SlashAutocomplete.tsx (command popup with scroll)
├── DiffBlock.tsx (Edit tool diff display)
├── MenuSelect.tsx (arrow-key menu, Plugin TUI)
├── PluginTUI.tsx (plugin management screen stack)
├── TextPrompt.tsx (text input for Plugin TUI)
└── ConfirmPrompt.tsx (reusable yes/no prompt)Dependencies
| Package | Purpose |
|---|---|
@robota-sdk/agent-sdk |
Session factory, query, config, context |
@robota-sdk/agent-core |
Types (TPermissionMode, TToolArgs) |
@robota-sdk/agent-transport-headless |
Headless runner for print mode (-p) |
ink 7, react 19.2+ |
TUI rendering |
ink-select-input |
Arrow-key selection (permission prompt) |
ink-spinner |
Loading spinner |
chalk |
Terminal colors |
ink-text-input |
Base text input (extended by CjkTextInput) |
marked, marked-terminal |
Markdown parsing and terminal rendering |
cli-highlight |
Syntax highlighting for code blocks |
string-width |
Unicode-aware string width (CJK support) |
Documentation
See docs/SPEC.md for the full specification, architecture details, and design decisions.
License
MIT