s-app-scoder-agent 0.9.3 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +615 -638
  2. package/dist/{guardrails.js → agent/guardrails.js} +1 -1
  3. package/dist/agent/interaction.js +70 -0
  4. package/dist/agent/mcp-bridge.js +136 -0
  5. package/dist/agent/prompt-context.js +83 -0
  6. package/dist/agent/run-loop.js +318 -0
  7. package/dist/agent/session-persistence.js +61 -0
  8. package/dist/agent/tool-executor.js +158 -0
  9. package/dist/agent/types.js +88 -0
  10. package/dist/agent/verification.js +99 -0
  11. package/dist/agent.js +239 -1002
  12. package/dist/cli/args.js +230 -0
  13. package/dist/cli/init.js +79 -0
  14. package/dist/cli/main.js +211 -0
  15. package/dist/cli/mcp-command.js +185 -0
  16. package/dist/cli/repl.js +77 -0
  17. package/dist/cli/run-once.js +26 -0
  18. package/dist/{tui/commands.js → commands/definitions.js} +6 -16
  19. package/dist/commands/registry.js +260 -0
  20. package/dist/{config.js → config/config.js} +1 -1
  21. package/dist/{prompt-builder.js → context/prompt-builder.js} +12 -1
  22. package/dist/{prompt.js → context/prompt.js} +111 -9
  23. package/dist/{skills.js → context/skills.js} +41 -2
  24. package/dist/index.js +5 -798
  25. package/dist/io/console-reporter.js +24 -0
  26. package/dist/io/reporter.js +1 -0
  27. package/dist/mcp/connection.js +227 -0
  28. package/dist/mcp/env.js +8 -0
  29. package/dist/{mcp.js → mcp/manager.js} +3 -515
  30. package/dist/mcp/sse.js +28 -0
  31. package/dist/mcp/transport-http.js +140 -0
  32. package/dist/mcp/transport-stdio.js +98 -0
  33. package/dist/mcp/types.js +11 -0
  34. package/dist/media/image.js +149 -0
  35. package/dist/{server.js → server/server.js} +1 -1
  36. package/dist/{cost.js → session/cost.js} +1 -1
  37. package/dist/{session.js → session/session.js} +1 -1
  38. package/dist/{version.js → shared/version.js} +1 -1
  39. package/dist/tools/apply-patch.js +62 -0
  40. package/dist/tools/edit-file.js +29 -0
  41. package/dist/tools/file-edit-common.js +124 -0
  42. package/dist/tools/list-files.js +64 -0
  43. package/dist/tools/path-safety.js +75 -0
  44. package/dist/tools/read-file.js +64 -0
  45. package/dist/tools/registry.js +297 -0
  46. package/dist/tools/run-command.js +192 -0
  47. package/dist/tools/schemas.js +8 -0
  48. package/dist/tools/search.js +237 -0
  49. package/dist/tools/task-tool.js +48 -0
  50. package/dist/tools/types.js +1 -0
  51. package/dist/tools/write-file.js +34 -0
  52. package/dist/tui/app.js +59 -532
  53. package/dist/tui/ask-user-modal.js +2 -1
  54. package/dist/tui/at-complete.js +2 -93
  55. package/dist/tui/charset.js +41 -0
  56. package/dist/tui/chat-views.js +72 -16
  57. package/dist/tui/chrome.js +11 -5
  58. package/dist/tui/hooks/use-command-host.js +26 -0
  59. package/dist/tui/hooks/use-event-queue.js +69 -0
  60. package/dist/tui/hooks/use-keybinds.js +87 -0
  61. package/dist/tui/hooks/use-scroll.js +38 -0
  62. package/dist/tui/hooks/use-session-resume.js +47 -0
  63. package/dist/tui/hooks/use-submit.js +106 -0
  64. package/dist/tui/image.js +2 -149
  65. package/dist/tui/input.js +1 -1
  66. package/dist/tui/markdown/ast.js +5 -0
  67. package/dist/tui/markdown/diagram.js +239 -0
  68. package/dist/tui/markdown/inline-render.js +24 -0
  69. package/dist/tui/markdown/inline.js +93 -0
  70. package/dist/tui/markdown/parse.js +139 -0
  71. package/dist/tui/markdown/render.js +146 -0
  72. package/dist/tui/markdown/table.js +106 -0
  73. package/dist/tui/markdown.js +32 -379
  74. package/dist/tui/permission-modal.js +2 -1
  75. package/dist/tui/selectors.js +157 -0
  76. package/dist/tui/session-picker.js +4 -3
  77. package/dist/tui/sidebar.js +130 -15
  78. package/dist/tui/state.js +47 -118
  79. package/dist/tui/text-width.js +139 -0
  80. package/dist/tui/theme.js +7 -2
  81. package/dist/{build.js → workspace/build.js} +1 -1
  82. package/dist/workspace/file-index.js +93 -0
  83. package/package.json +5 -1
  84. package/dist/tools.js +0 -1223
  85. package/dist/tui/command-registry.js +0 -477
  86. package/dist/tui/review.js +0 -64
  87. package/dist/tui/transcript.js +0 -145
  88. /package/dist/{agents-guide.js → context/agents-guide.js} +0 -0
  89. /package/dist/{agents.js → context/agents.js} +0 -0
  90. /package/dist/{compaction.js → context/compaction.js} +0 -0
  91. /package/dist/{context.js → context/context.js} +0 -0
  92. /package/dist/{memory.js → context/memory.js} +0 -0
  93. /package/dist/{repo-map.js → context/repo-map.js} +0 -0
  94. /package/dist/{ui.js → io/terminal.js} +0 -0
  95. /package/dist/{llm.js → llm/llm.js} +0 -0
  96. /package/dist/{model-catalog.js → llm/model-catalog.js} +0 -0
  97. /package/dist/{provider-anthropic.js → llm/provider-anthropic.js} +0 -0
  98. /package/dist/{provider-registry.js → llm/provider-registry.js} +0 -0
  99. /package/dist/{retry.js → llm/retry.js} +0 -0
  100. /package/dist/{mcp-import.js → mcp/mcp-import.js} +0 -0
  101. /package/dist/{permissions.js → security/permissions.js} +0 -0
  102. /package/dist/{sandbox.js → security/sandbox.js} +0 -0
  103. /package/dist/{shell-scanner.js → security/shell-scanner.js} +0 -0
  104. /package/dist/{logger.js → session/logger.js} +0 -0
  105. /package/dist/{steering.js → session/steering.js} +0 -0
  106. /package/dist/{constants.js → shared/constants.js} +0 -0
  107. /package/dist/{json-schema.js → shared/json-schema.js} +0 -0
  108. /package/dist/{webfetch.js → tools/webfetch.js} +0 -0
  109. /package/dist/{diagnostics.js → workspace/diagnostics.js} +0 -0
  110. /package/dist/{diff.js → workspace/diff.js} +0 -0
  111. /package/dist/{snapshot.js → workspace/snapshot.js} +0 -0
  112. /package/dist/{watch.js → workspace/watch.js} +0 -0
package/README.md CHANGED
@@ -1,644 +1,621 @@
1
1
  # scoder — Autonomous Coding Agent CLI
2
2
 
3
- **Current version: 0.9.2**
4
-
5
- [![CI](https://github.com/nbhson/app-scoder-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/nbhson/app-scoder-agent/actions/workflows/ci.yml)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
- [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
8
- [![npm](https://img.shields.io/badge/npm-s--app--scoder--agent-orange)](https://www.npmjs.com/package/s-app-scoder-agent)
9
-
10
- **scoder** is a command-line coding agent that autonomously writes, edits, and builds code until your project compiles without errors. Inspired by *Cline*, *Claude Code*, and *OpenCode*, but runs as a single standalone CLI tool.
11
-
12
- ## Installation
13
-
14
- **Requirements:** Node.js ≥ 20
15
-
16
- ### Install from npm (Recommended)
17
-
18
- ```bash
19
- npm install -g s-app-scoder-agent
20
- ```
21
-
22
- ### Install from GitHub
23
-
24
- ```bash
25
- npm install -g github:nbhson/app-scoder-agent
26
- ```
27
-
28
- ### From local clone
29
-
30
- ```bash
31
- git clone https://github.com/nbhson/app-scoder-agent.git
32
- cd app-scoder-agent
33
- npm install && npm run build && npm install -g .
34
- ```
35
-
36
- ### Try without installing
37
-
38
- ```bash
39
- npx s-app-scoder-agent "your task here"
40
- ```
41
-
42
- ## Quick Start
43
-
44
- ```bash
45
- # One-time setup wizard — writes .scoderrc.json (base URL, key, model, build cmd)
46
- scoder init
47
-
48
- # …or set your API key manually
49
- export SCODER_API_KEY=sk-... # any OpenAI-compatible key (OpenAI, Groq, OpenRouter, Ollama, …)
50
- export OPENAI_BASE_URL=https://api.openai.com/v1 # optional, for custom endpoints
51
-
52
- # Run a task
53
- scoder "add a ping function to src/network.ts"
54
-
55
- # Interactive REPL (supports /help, /clear, /compact, /cost, /model, /skills, /skill, /mcp, /review, /permission)
3
+ **Current version: 0.9.7**
4
+
5
+ [![CI](https://github.com/nbhson/app-scoder-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/nbhson/app-scoder-agent/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
8
+ [![npm](https://img.shields.io/badge/npm-s--app--scoder--agent-orange)](https://www.npmjs.com/package/s-app-scoder-agent)
9
+
10
+ **scoder** is a command-line coding agent that autonomously writes, edits, and builds code until your project compiles without errors. Inspired by *Cline*, *Claude Code*, and *OpenCode*, but runs as a single standalone CLI tool.
11
+
12
+ ## Installation
13
+
14
+ **Requirements:** Node.js ≥ 20
15
+
16
+ ### Install from npm (Recommended)
17
+
18
+ ```bash
19
+ npm install -g s-app-scoder-agent
20
+ ```
21
+
22
+ ### Install from GitHub
23
+
24
+ ```bash
25
+ npm install -g github:nbhson/app-scoder-agent
26
+ ```
27
+
28
+ ### From local clone
29
+
30
+ ```bash
31
+ git clone https://github.com/nbhson/app-scoder-agent.git
32
+ cd app-scoder-agent
33
+ npm install && npm run build && npm install -g .
34
+ ```
35
+
36
+ ### Try without installing
37
+
38
+ ```bash
39
+ npx s-app-scoder-agent "your task here"
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ # One-time setup wizard — writes .scoderrc.json (base URL, key, model, build cmd)
46
+ scoder init
47
+
48
+ # …or set your API key manually
49
+ export SCODER_API_KEY=sk-... # any OpenAI-compatible key (OpenAI, Groq, OpenRouter, Ollama, …)
50
+ export OPENAI_BASE_URL=https://api.openai.com/v1 # optional, for custom endpoints
51
+
52
+ # Run a task
53
+ scoder "add a ping function to src/network.ts"
54
+
55
+ # Interactive REPL (supports /help, /clear, /compact, /cost, /model, /skill, /mcp, /permission)
56
56
  scoder
57
57
 
58
58
  # Full-screen TUI with rich agent display
59
59
  scoder tui
60
- ```
61
-
62
- ## How It Works
63
-
64
- ```
65
- You (task) → LLM plans → Tools execute code → Build runs → Success? → Done
66
- ↑ |
67
- └── Build failed? ─┘ (auto-loop)
68
- ```
69
-
70
- **Key constraint:** scoder runs autonomously without stopping for confirmation prompts. If the build fails, it reads the error, patches the code, and retries automatically — until the build passes or the max-step limit is reached.
71
-
72
- ## Tool Set
73
-
74
- | Tool | Description |
75
- |------|-------------|
76
- | `read_file` | Read a file with line numbers |
77
- | `write_file` | Overwrite/create a file |
78
- | `edit_file` | Find-and-replace (unique match required) |
79
- | `list_files` | Recursive directory listing |
80
- | `run_command` | Execute any shell command (subject to permission policy) |
81
- | `web_fetch` | Fetch a public HTTP(S) URL → readable text (HTML→text, JSON pretty-printed) |
82
- | `write_todos` | Live plan for multi-step tasks (checkbox list, kept current) |
83
- | `load_skill` | Load a SKILL.md into context when the task matches its description |
84
- | `mcp__<server>__<tool>` | Tools from configured MCP servers (routed transparently) |
85
-
86
- File writes (`write_file` / `edit_file`) are symlink-safe: paths are resolved via `realpath` and cannot escape the working directory, even through a malicious symlink. `read_file` refuses files larger than 2MB with guidance to use `grep`/`head`/`tail`.
87
-
88
- ### Web access (`web_fetch`)
89
-
90
- The agent can read the internet through the built-in `web_fetch` tool — it fetches exact URLs (it cannot search) and returns readable plain text: HTML is converted to text (scripts/styles stripped, entities decoded, title extracted), JSON is pretty-printed, binary content is refused. The system prompt instructs the agent *when to fetch* (external/API docs, URLs in error messages, current versions, changelogs) and when not to (answer available locally).
91
-
92
- Safety defaults:
93
-
94
- - **SSRF protection** — `localhost`, private and link-local addresses (10.x, 172.16/12, 192.168.x, 127.x, 169.254.x, IPv6 ULA/link-local) are blocked. Set `SCODER_WEB_ALLOW_PRIVATE=1` to allow local dev servers.
95
- - Only `http`/`https`; up to 5 redirects — every hop is re-validated against the private-address block list.
96
- - 30 s timeout, SIGINT-abortable; body capped at 512 KB; extracted text capped at 20k chars (configurable via `max_chars`).
97
- - HTML is converted to plain text (no JS execution); images/archives/PDFs are refused.
98
- - Read-only — the tool never writes locally, so it is allowed even in `readonly` permission mode.
99
-
100
- ## Configuration
101
-
102
- ### Environment variables
103
-
104
- | Variable | Default | Description |
105
- |----------|---------|-------------|
106
- | `SCODER_API_KEY` | — | API key (required) |
107
- | `SCODER_MODEL` | `gpt-4o-mini` | Model name |
108
- | `SCODER_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible endpoint |
109
- | `SCODER_BUILD_CMD` | auto-detected | Build/compile command |
110
- | `SCODER_MAX_STEPS` | `40` | Max loop iterations before giving up |
111
- | `SCODER_PERMISSION` | `auto` | Permission mode: `auto` \| `ask` \| `readonly` |
112
- | `SCODER_STREAM` | `1` | `0` to disable streaming LLM output |
113
- | `SCODER_SERVER_TOKEN` | — | `scoder serve` only: require `Authorization: Bearer <token>` |
114
- | `SCODER_WEB_ALLOW_PRIVATE` | — | Set to `1` to let `web_fetch` reach localhost/private addresses (local dev) |
115
- | `SCODER_NO_APIKEY_WARNING` | — | Set to `1` to suppress the apiKey-in-rc warning |
116
-
117
- ### Per-project config file (.scoderrc.json)
118
-
119
- For any OpenAI-compatible endpoint, just point `baseUrl`, `apiKey`, and `model` at it — no code changes needed. Create `.scoderrc.json` in your project root (see `.scoderrc.example.json`):
120
-
121
- ```json
122
- {
123
- "baseUrl": "https://api.groq.com/openai/v1",
124
- "apiKey": "gsk_...",
125
- "model": "llama-3.3-70b-versatile"
126
- }
127
- ```
128
-
129
- Then simply run `scoder "your task"` — values are picked up automatically.
130
-
131
- ### Global config (~/.scoder/config.json)
132
-
133
- Machine-wide defaults shared by **all** projects: `mcpServers`, `model`, `baseUrl`, `permission`, `permissions` lists, `modelPricing`… The project `.scoderrc.json` wins field-by-field; `mcpServers` and lists merge per-key (project entries override same-named global ones). API keys are safe here — the file lives in your home dir, outside any repo.
134
-
135
- #### All fields
136
-
137
- | Field | Default | Description |
138
- |---|---|---|
139
- | `baseUrl` | `https://api.openai.com/v1` | OpenAI-compatible endpoint (must support `/chat/completions` with tool calling) |
140
- | `apiKey` | — | API key (⚠️ prefer `SCODER_API_KEY` env var, see warning below) |
141
- | `model` | `gpt-4o-mini` | Model name served by the endpoint |
142
- | `buildCmd` | auto-detected | Build/compile command (stop condition) |
143
- | `maxSteps` | `40` | Max autonomous loop iterations |
144
- | `stream` | `true` | Stream LLM output token-by-token |
145
- | `provider` | auto-detected | `openai` \| `anthropic` |
146
- | `permission` | `auto` | `auto` \| `ask` \| `readonly` |
147
- | `mcpServers` | — | MCP servers — stdio (`command`) hoặc HTTP (`url`) (see below) |
148
- | `permissions` | — | `{ mode, allowCommands, denyCommands }` |
149
-
150
- #### Precedence
151
-
152
- Values are merged from highest to lowest priority — your `.scoderrc.json` value only applies when no flag/env is set:
153
-
154
- 1. **CLI flags** — `-m`, `--base-url`, `-k`, `--provider`, `-B`
155
- 2. **Environment variables** — `SCODER_API_KEY` / `OPENAI_API_KEY`, `SCODER_BASE_URL`, `SCODER_MODEL`, …
156
- 3. **`.scoderrc.json`** (or `.scoderrc`) in the project root, merged over the global `~/.scoder/config.json` — project values win field-by-field; `mcpServers`/lists/pricing merge per-key
157
- 4. Built-in defaults
158
-
159
- > 💡 If your config in `.scoderrc.json` seems ignored, check `echo $SCODER_API_KEY` — env vars override the file.
160
-
161
- #### Examples for common providers
162
-
163
- ```jsonc
164
- // Local Ollama
165
- {
166
- "baseUrl": "http://localhost:11434/v1",
167
- "apiKey": "ollama",
168
- "model": "qwen2.5-scoder:32b"
169
- }
170
- ```
171
-
172
- ```jsonc
173
- // DeepSeek
174
- {
175
- "baseUrl": "https://api.deepseek.com/v1",
176
- "apiKey": "sk-...",
177
- "model": "deepseek-chat"
178
- }
179
- ```
180
-
181
- #### Notes
182
-
183
- - Models starting with `claude-`, or a `baseUrl` containing `api.anthropic.com`, automatically switch to the native Anthropic provider — no `provider` field needed.
184
- - If an `apiKey` is present in the rc file, scoder prints a warning on every run. Silence it with `SCODER_NO_APIKEY_WARNING=1`.
185
-
186
- > ⚠️ **Security:** never commit `apiKey` in `.scoderrc.json` — add the file to `.gitignore` and prefer `SCODER_API_KEY` env var or the `-k` flag. A pre-commit check is available at `scripts/check-secrets.mjs`.
187
-
188
- ## Permissions & Safety
189
-
190
- scoder ships with a layered permission system for `run_command`:
191
-
192
- | Mode | Behavior |
193
- |------|----------|
194
- | `auto` (default) | Autonomous — runs shell commands without confirmation (original behavior) |
195
- | `ask` | Prompts before each shell command not covered by `allowCommands` |
196
- | `readonly` | Shell commands are disabled entirely; the agent can only read/list files. MCP tools are also disabled (they bypass the permission policy) |
197
-
198
- Layered checks, in order:
199
-
200
- 1. **Catastrophic deny list** — `rm -rf /`, `dd of=/dev/...`, fork bombs, `curl | sh`, etc. are **always denied**, in every mode.
201
- 2. **`denyCommands`** (config) — always denied.
202
- 3. **Session allowlist** — commands you approved with `[a] always allow` in this session.
203
- 4. **`allowCommands`** (config) — auto-approved.
204
- 5. **Mode** — `auto` runs everything else; `ask` prompts; `readonly` denies.
205
-
206
- In `ask` mode the prompt offers `[y] yes / [a] always allow "<prefix>" / [n] no / [q] quit`. With `-y` (non-interactive), commands needing confirmation are denied instead of hanging CI.
207
-
208
- ## Skills (SKILL.md)
209
-
210
- scoder can load reusable instruction packs ("skills") into the conversation. A skill is a directory with a `SKILL.md` (YAML-lite frontmatter + markdown body):
211
-
212
- ```
213
- .scoder/skills/code-review/SKILL.md # project-level
214
- ~/.scoder/skills/code-review/SKILL.md # global (project overrides on name clash)
215
- ```
216
-
217
- ```markdown
218
- ---
219
- name: code-review
220
- description: Review code for bugs, security issues, and style
221
- allowed-tools: read_file, grep
222
- license: MIT
223
- when-to-use: Run after any multi-file change before committing
224
- ---
225
- Read the current diff. Check for: null derefs, injection risks, ...
226
- ```
227
-
228
- - **Advertised to the model** — every run's system prompt lists skill names + descriptions; the agent reads a matching `SKILL.md` itself via `read_file` when a task fits
229
- - **Explicit load** — `/skill <name>` stages the skill's content; it is injected as context before your next task
230
- - **`/skills`** lists everything discovered (both scopes)
231
- - **Metadata (Anthropic convention)** — `description` is required (a skill without one is skipped with a warning); optional `allowed-tools` (comma-separated tool list, informational — enforcement goes through permission rules), `license`, `when-to-use`
232
- - **Validation warnings** — duplicate names across scopes warn (project wins); missing `description` skips the skill; warnings surface in the log/notice stream
233
- - **Hot-reload** — `.scoderrc` and `.scoder/skills/` are watched (debounced); a skill added mid-session is advertised on the next turn, no restart needed
234
-
235
- ## Slash commands (REPL + TUI)
236
-
237
- | Command | Description |
238
- |---|---|
239
- | `/help` | command list |
240
- | `/plan` | show the current plan; `/plan done\|start <id\|n>` to mark steps |
241
- | `/agent` | list/select primary agents (build/plan/review/custom) |
242
- | `/init` | create an AGENTS.md template in this project |
243
- | `/sessions` | list saved sessions; `/sessions resume <id\|last>` |
244
- | `/diff` | show diffs: `/diff turn\|session\|branch` (TUI) |
245
- | `/theme` | switch palette (TUI) |
246
- | `/clear` | reset conversation + agent context |
247
- | `/compact` | summarize older history now (manual compaction) |
248
- | `/cost` | session token usage |
249
- | `/model` | active model/provider/build |
250
- | `/skills` | list discovered skills |
251
- | `/skill <name>` | stage a skill for the next task |
252
- | `/mcp` | MCP servers + tools; exec one directly: `/mcp <server> <tool> [json\|text]` or `/mcp mcp__server__tool [json\|text]` — non-JSON text binds to the tool's first string parameter |
253
- | `/review <prompt>` | Prompt gate (prompt-gate MCP): LLM rewrites your prompt, you pick original/rewritten/update/cancel — only the approved prompt is used |
254
- | `/permission <auto\|ask\|readonly>` | switch permission mode at runtime |
255
- | `/undo` | revert files (and optionally trim the turn) (TUI) |
256
- | `/commands` | list all commands (builtin + custom) |
257
- | `/export [md\|json]` | export session transcript |
258
- | `/search <query>` | search the transcript |
259
- | `/exit` | quit |
260
-
261
- In the TUI, **Tab** completes commands (and `/skill` names, `/mcp <server> <tool>` arguments), **↑/↓** recall sent-message history.
262
-
263
- ### Context compaction
264
-
265
- Long runs are summarized automatically when history reaches `contextWindow × trigger` (default **80%**). Mechanical trimming only starts later, at **95%** of the window, so compaction gets the first chance to preserve task state. `/compact` runs the same summarization immediately (useful mid-session or when auto-compaction is disabled).
266
-
267
- ```jsonc
268
- // .scoderrc.json — all fields optional
269
- "compaction": {
270
- "trigger": 0.8, // fraction of the model window that triggers compaction
271
- "keep": 0.15, // recent-history fraction kept verbatim
272
- "buffer": 0.15, // extra recent-history fraction never summarized
273
- "contextWindow": 128000 // override the model-catalog window
274
- }
275
- // "compaction": false disables automatic compaction (mechanical trim only)
276
- ```
277
-
278
- `/compact` remains available even when automatic compaction is disabled.
279
-
280
- ### @file mentions
281
-
282
- Prefix any path with `@` in your task to inline its content into the agent's context:
283
-
284
- ```bash
285
- scoder "fix the bug in @src/agent.ts"
286
- scoder "refactor the handlers in @src/handlers/ and update @README.md"
287
- ```
288
-
289
- - `@src/file.ts` — file content is injected into the task; `@"path with spaces.ts"` for paths containing spaces
290
- - `@src/dir/` — directory listing injected instead
291
- - Emails (`bob@example.com`) are never treated as mentions; missing paths produce a visible note the LLM can react to; paths are contained to the working directory
292
-
293
- ## Planning tool
294
-
295
- For multi-step tasks the agent writes a live plan with `write_todos` (`[x]` completed · `[~]` in progress · `[ ]` pending) — rendered inline in the TUI and included in session state.
296
-
297
- ## Guardrails (Guardrail Engine)
298
-
299
- The autonomous loop is wrapped by a configurable rule engine (`src/guardrails.ts`) — every rule is a pure function over the step context that returns a decision (`inject` a reminder / `finish` / `verify-build` / `trim`), and every triggered decision is logged to the JSONL log (`type: "guardrail"`) and surfaced as an `AgentEvent` for frontends:
300
-
301
- | Rule | What it does |
302
- |---|---|
303
- | `wrap-up` | Injects a converge-now reminder 2 steps before the budget ends |
304
- | `chat-done` | Ends conversational turns (marker-based) without a build |
305
- | `completion-claim` | Never trusts "the task is complete" — forces an independent build verification |
306
- | `text-only-streak` | Ends the run after 2 consecutive tool-less steps with no file changes (the "chào" failure mode) |
307
- | `continue-nudge` | Nudges plain-text answers back to work (no build demand when no files changed — suggests the `<<CHAT_DONE>>` exit instead) |
308
- | `doom-loop` | Intervenes after 3× identical consecutive tool-call batches |
309
- | `build-stop` | Ends the run when the exact build command succeeds; re-verifies on failure |
310
- | `history-trim` | Char-budget history trimming with whole-turn drops + old-tool-result pruning |
311
-
312
- Thresholds are configurable via `GuardrailOptions` (`textOnlyStreakLimit`, `doomLoopRepeats`, `wrapUpStepsBeforeEnd`) without touching the loop, and custom rules can be added via `GuardrailEngine.register()`.
313
-
314
- ## Usage & cost metrics
315
-
316
- `scoder cost` reads the JSONL logs and reports per session and total: LLM calls, tokens (in/out/cached), estimated cost, **steps, average LLM latency, tool calls, guardrail interventions, and the session success rate**:
317
-
318
- ```
319
- 20261031-110353-d034 calls=1 in=4,042 out=88 cost≈$0.0022 | steps=1 llm-avg=7.5s tools=1 guardrails=1 ✓success
320
- TOTAL (1 session(s)) ... | success-rate=1/1
321
- ```
322
-
323
- The system prompt is versioned (`PROMPT_VERSION` in `src/prompt.ts`) and stamped into every session log (`type: "prompt_version"`) so prompt quality can be compared across versions.
324
-
325
- ## MCP Servers (stdio / HTTP)
326
-
327
- scoder can use tools from [MCP](https://modelcontextprotocol.io) servers configured in `.scoderrc.json`:
328
-
329
- ```json
330
- {
331
- "mcpServers": {
332
- "filesystem": {
333
- "command": "npx",
334
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
335
- },
336
- "github": {
337
- "command": "npx",
338
- "args": ["-y", "@modelcontextprotocol/server-github"],
339
- "env": { "GITHUB_TOKEN": "{env:GITHUB_TOKEN}" }
340
- },
341
- "remote": {
342
- "url": "https://mcp.example.com/mcp",
343
- "headers": { "authorization": "Bearer {env:MCP_TOKEN}" }
344
- }
345
- }
346
- }
347
- ```
348
-
349
- - Tools are exposed to the LLM as `mcp__<server>__<tool>` (no name collisions with built-ins)
350
- - Servers connect at agent start; a failing server only logs a warning — the agent keeps running with its built-in tools
351
- - `{env:VAR}` substitution keeps secrets out of config files (both `env` and `headers`)
352
- - **Transports**: `command` → stdio (line-delimited JSON-RPC); `url` → Streamable HTTP (POST JSON-RPC, JSON or SSE responses, `Mcp-Session-Id` echo). Force with `"type": "stdio" | "http"`
353
- - **Resilience**: a dead stdio server reconnects with exponential backoff (max 5 attempts) and refreshes its tool list; HTTP calls are stateless and retry on the next call
354
- - Built-in tools always take priority over MCP tools with colliding names
355
- - ⚠️ **MCP tools bypass the built-in permission system** (no command classification, no diff preview). In `readonly` mode they are therefore disabled entirely; in `auto`/`ask` mode they run unsandboxed — only add MCP servers you trust.
356
-
357
- ### MCP governance
358
-
359
- Each server supports `enabled`, `trust` (`auto`/`ask`/`readonly`/`deny`), `tools.include`/`exclude`
360
- (globs), and `timeoutMs`. `readonly` servers are never spawned. Import existing configs with
361
- `scoder mcp import`. MCP tool output is flagged `untrusted` and wrapped with an `[UNTRUSTED]`
362
- marker; audit entries are written to the JSONL log.
363
-
364
- ### Prompt gate (`/review`)
365
-
366
- If the [`prompt-gate`](https://github.com/nbhson/s-universal-prompt-gate-mcp) MCP server is configured (`~/.scoder/config.json`), `/review <prompt>` runs the gated rewrite flow — like OpenCode's `review-and-select-prompt`:
367
-
368
- ```
369
- /review phân tích project
370
- → [1] light [2] balanced [3] detailed ← you pick the rewrite level
371
- → ORIGINAL vs REWRITTEN shown side by side
372
- → [1] original [2] rewritten [3] update <changes> [4] cancel
373
- → ✔ prompt approved: <the exact text you approved>
374
- ```
375
-
376
- - **You approve exactly what you read** — the approved text is echoed verbatim from the server's pending result, never re-typed by the model or you
377
- - `[3] update` loops: describe changes, the prompt is rewritten again, review repeats until you pick original/rewritten/cancel
378
- - Cancel approves nothing; LLM errors fail loudly (no silent fallback to the original)
379
-
380
- ## Sessions
381
-
382
- Every run auto-saves its state (messages, steps, build result) to `.scoder/sessions/<id>.json` after each step. If the run is interrupted (Ctrl+C, crash, max steps), you can continue where it left off:
383
-
384
- ```bash
385
- scoder list # list saved sessions
386
- scoder resume last # continue the most recent session
387
- scoder resume <id> "follow-up" # resume + add a new instruction
388
- ```
389
-
390
- Sessions older than 7 days are pruned automatically on the next run. Use `--no-save` to disable (e.g. in CI).
391
-
392
- ## Streaming
393
-
394
- scoder streams everything it can, live, as it happens:
395
-
396
- - **LLM tokens** — assistant text renders character-by-character (OpenAI-compatible SSE and native Anthropic streaming). If a provider doesn't support streaming, scoder automatically falls back to non-streaming requests. Disable with `SCODER_STREAM=0`.
397
- - **Tool output** — `run_command` and build output print live (dimmed block) while the command runs, instead of appearing only after it finishes. Live output is capped at 20k chars; the full output still goes to the LLM.
398
- - **SIGINT-safe** — Ctrl+C kills the running command immediately; streamed rendering never blocks the event loop.
399
- - Output honors `NO_COLOR` and degrades to plain text when piped (CI-friendly).
400
-
401
- ## Full-Screen TUI (`scoder tui`)
402
-
403
- **Mission Control for the build loop** — a terminal UI built with [Ink](https://github.com/vadimdemedes/ink) (React for terminals), driven by the same `AgentEvent` stream the HTTP API exposes:
404
-
405
- ```bash
406
- scoder tui # start an interactive full-screen session
407
- scoder tui "fix the bug" # auto-submit an initial task
408
- ```
409
-
410
- Features:
411
-
412
- - **Alternate screen buffer** (vim/htop style) — the app owns the full screen: the header stays pinned at the top, input + status bar at the bottom, the chat pane scrolls internally, and your terminal content is restored on exit. Terminals without alt-screen support (`TERM=dumb`, non-TTY) automatically render inline instead.
413
- - **Build Rail** (pinned top bar, single row) — brand, build lamp (`✓ ok` / `✗ fail` / `no build yet`), permission mode, and a center region that shows either the phase breadcrumb (`plan › explore › edit › build › verify`) or the latest guardrail notice spanning the full available width (e.g. `⚠ trả lời text chưa dùng tool — nhắc tiếp tục`)
414
- - **Sidebar (≥92 cols)** — SESSION (model + workdir, no permission line), METRICS (step fuel gauge, tokens, tok/s, ctx input/limit, cost, elapsed, guardrail count), PHASES, BUILD, MCP, PLAN (live checklist at the bottom; resets on each new user message)
415
- - **Chat pane** — your messages with a full colored border + `you ▸` label, streaming assistant text with a live block cursor (`▋`), and every tool call with live stdout/stderr preview (`✔/✘` status, spinner while running, wall-clock duration per tool), prefixed with a flight-recorder step number (`03▸`)
416
- - **Header row 2** — (no longer present; phase breadcrumb + metrics moved into the sidebar / status bar to avoid duplication)
417
- - **Diff cards** (`✎`) — every file write/edit renders as a unified diff (`+` green / `-` red / `@@` cyan hunk headers); the same diff also flows out of the HTTP SSE stream
418
- - **Status bar** — model, token usage (input/output/cached), scroll position, keybind hints
419
- - **Slash commands** — `/help`, `/clear`, `/compact`, `/cost`, `/model`, `/skills`, `/skill`, `/mcp`, `/review`, `/permission`, `/exit` (identical to the REPL)
420
- - **Multi-turn** — keep typing follow-ups after a task finishes; the conversation context carries over (same as the REPL)
421
- - **Scrollback** — `PgUp`/`PgDn` page in 0.8-viewport steps, `Home`/`End` jump to top/bottom, `Esc` returns to the live tail (press again to abort); rendering is capped at the last 200 items for smooth scrolling on long runs
422
- - **Keybinds** — `Enter` send · `PgUp`/`PgDn` scroll · `Esc` reset scroll / abort · `Ctrl+C` quit
423
- - Non-TTY environments (CI, pipes) automatically fall back to the classic REPL
424
- - `permission: "ask"` is not supported in the TUI (no readline prompts on the alt screen) — it falls back to `auto`
425
-
426
- ## HTTP Server Mode (`scoder serve`)
427
-
428
- Run scoder as a local HTTP service — submit tasks programmatically and consume progress as Server-Sent Events:
429
-
430
- ```bash
431
- scoder serve # http://127.0.0.1:0 (random port, printed at startup)
432
- scoder serve --port 4114 # fixed port
433
- SCODER_SERVER_TOKEN=secret scoder serve --host 127.0.0.1
434
- ```
435
-
436
- | Endpoint | Method | Description |
437
- |----------|--------|-------------|
438
- | `/api/health` | GET | Liveness: `{ ok, running, runs }` |
439
- | `/api/runs` | POST | Start a run: `{ "task": "...", "workdir?", "buildCmd?", "model?", "provider?", "maxSteps?", "permission?" }` → `202 { id, eventsUrl }` |
440
- | `/api/runs` | GET | List runs with status/timing |
441
- | `/api/runs/<id>` | GET | Run status + final result |
442
- | `/api/runs/<id>/events` | GET | **SSE stream** — full event replay, then live, closes on finish |
443
- | `/api/runs/<id>/abort` | POST | Abort a running run (equivalent to SIGINT) |
444
-
445
- SSE event types: `run_start`, `step`, `llm_delta` (assistant text), `tool_call`, `tool_output` (live stdout/stderr chunks), `tool_result`, `llm_usage`, `build`, `finish`. The stream replays every event from the beginning, then stays live, and closes after `finish` — safe to (re)connect at any time.
446
-
447
- Notes:
448
-
449
- - Runs are always non-interactive; `permission: "ask"` is rejected — use `auto` or `readonly`.
450
- - Binds `127.0.0.1` by default. Set `SCODER_SERVER_TOKEN` to require `Authorization: Bearer <token>` on every request.
451
- - Max 4 concurrent runs (extra requests get `429`); per-run overrides: `workdir`, `buildCmd`, `model`, `provider`, `maxSteps`, `permission`.
452
-
453
- ## Logging
454
-
455
- Enable a structured JSONL event log with `--log-file` (or `SCODER_LOG_FILE=1`, or `SCODER_LOG_FILE=/path/to/log.jsonl`):
456
-
457
- ```bash
458
- scoder --log-file "fix the failing test"
459
- cat .scoder/logs/<session-id>.jsonl | jq 'select(.type == "error")'
460
- ```
461
-
462
- Events: `step` (with `durationMs` LLM latency + tool-call count), `tool` (args, ok, elapsedMs, denied reason), `build`, `guardrail` (rule/action/reason for every Guardrail Engine decision), `prompt_version`, `finish`. One JSON object per line — pipe to `jq` or any log consumer.
463
-
464
- ## Reliability
465
-
466
- scoder is designed for unattended use. Key guarantees:
467
-
468
- - **Request timeouts** — LLM calls have a 120s timeout; on timeout, the agent retries or fails gracefully (never hangs indefinitely).
469
- - **SIGINT handling** — pressing Ctrl+C cancels the in-flight LLM request immediately, kills the running shell command (SIGTERM → SIGKILL), saves the session, and exits with status 130. You can resume with `scoder resume last`.
470
- - **Graceful retries** — transient LLM API failures (HTTP 429/5xx, network errors) are retried with exponential backoff and jitter (up to 3 retries, honoring `Retry-After`). Client errors (401/403/404) fail fast.
471
- - **Double-build prevention** — when the agent itself runs the build command, scoder reuses that result instead of running the build twice.
472
- - **Build diff preview** — run `scoder --permission ask` to see a unified diff before every file write/edit.
473
-
474
- > **Design note:** the build command (stop condition) always runs without a permission prompt, even in `readonly` mode — the agent must be able to verify its stop condition. Builds are invoked as-is from your config (`buildCmd`) or auto-detection, never invented by the LLM. Shell commands *chosen by the LLM* (`run_command` tool) go through the full permission policy above.
475
-
476
- ### Auto-detected build commands
477
-
478
- scoder detects the build command automatically:
479
-
480
- | Project type | Detected command |
481
- |---|---|
482
- | `package.json` with `scripts.build` | `npm run build` |
483
- | `Makefile` | `make` |
484
- | `go.mod` | `go build ./...` |
485
- | `Cargo.toml` | `cargo build` |
486
- | `tsconfig.json` | `npx tsc --noEmit` |
487
- | `pyproject.toml` | `python -m compileall .` |
488
-
489
- ## CLI Options
490
-
491
- ```
492
- scoder [options] "<task>" Run a task autonomously until the build passes
493
- scoder Start interactive session
494
- scoder init Setup wizard — writes .scoderrc.json (+ connection test)
495
- scoder tui [task] Full-screen terminal UI (Mission Control)
496
- scoder serve [--port] [--host] HTTP server mode (see above)
497
- scoder --help | --version Help and version
498
- ```
499
-
500
- | Flag | Description |
501
- |------|-------------|
502
- | `-m <model>` | Model to use |
503
- | `--base-url <url>` | OpenAI-compatible API base URL |
504
- | `-k <key>` | API key |
505
- | `-B, --build <cmd>` | Build command (stop condition) |
506
- | `-d, --dir <path>` | Working directory |
507
- | `-s, --max-steps <n>` | Max iterations (default 40) |
508
- | `--permission <mode>` | `auto` \| `ask` \| `readonly` shell-command policy (default `auto`) |
509
- | `-y` | Non-interactive mode (denies commands needing confirmation) |
510
- | `--provider <name>` | `openai` \| `anthropic` (env SCODER_PROVIDER; auto-detected) |
511
- | `--agent <name>` | Primary agent to run (build/plan/review/custom) |
512
- | `--no-save` | Disable session auto-save to `.scoder/sessions/` |
513
- | `--no-color` | Disable colored output |
514
- | `--no-motion` | Disable animations |
515
- | `--plain` | Plain (monochrome) output |
516
- | `--high-contrast` | High-contrast theme |
517
- | `--log-file` | Write a structured JSONL event log to `.scoder/logs/` |
518
- | `--port <n>` | (`serve`) TCP port, `0` = random free port |
519
- | `--host <addr>` | (`serve`) Bind address (default `127.0.0.1`) |
520
-
521
- ## Examples
522
-
523
- ```bash
524
- # Run a task (auto-saves session to .scoder/sessions/)
525
- scoder -B "npm test" "fix the null pointer in UserService"
526
-
527
- # Resume after a crash or interrupt
528
- scoder resume last "now add unit tests for the fix"
529
-
530
- # Interactive mode with permission guard
531
- scoder --permission ask "npm install express and wire up a route"
532
-
533
- # Custom LLM (Groq, Ollama, vLLM, OpenRouter…)
534
- scoder --base-url https://api.groq.com/openai/v1 -k $GROQ_KEY \
535
- "add input validation to the CLI"
536
-
537
- # Anthropic (auto-detected when model starts with 'claude-')
538
- scoder -m claude-sonnet-4-5 "refactor the parser module"
539
-
540
- # CI: non-interactive, no session saving
541
- scoder --no-save -y "run the full migration"
542
-
543
- # Run as an HTTP service and consume progress via SSE
544
- scoder serve --port 4114 &
545
- curl -s -X POST localhost:4114/api/runs -H 'Content-Type: application/json' \
546
- -d '{"task": "add input validation to the CLI"}'
547
- curl -N http://127.0.0.1:4114/api/runs/<id>/events # live progress
548
-
549
- # Fetch external docs during a task (agent decides when, via web_fetch)
550
- scoder "upgrade express to v5 — check the official migration guide first"
551
- ```
552
-
553
- ## Architecture
554
-
555
- ```
556
- ┌─────────────────────────────────────────────┐
557
- │ CLI (src/index.ts) │
558
- │ arg parsing, REPL, serve, tui, SIGINT │
559
- └───────┬─────────────────────┬───────────────┘
560
- │ │
561
- ┌───────▼───────────┐ ┌──────▼─────────────────────┐
562
- │ TUI (src/tui/) │ │ Agent (src/agent.ts) │
563
- │ Ink/React alt- │ │ Closed-loop orchestrator │
564
- │ screen UI, │─▶│ + Guardrail Engine │
565
- │ renders the same │ │ (src/guardrails.ts) │
566
- │ AgentEvent stream│ │ emits AgentEvent │
567
- └───────────────────┘ └──┬──────────┬──────────────┘
568
- │ │
569
- │ └────────────────────┐
570
- ▼ ▼
571
- ┌───────────────────────────────┐ ┌──────────────────────────┐
572
- │ LLM Client (src/llm.ts) │ │ Server (src/server.ts) │
573
- │ OpenAI-compatible + native │ │ HTTP API + SSE │
574
- │ Anthropic provider │ │ runs in background │
575
- └──────────────┬────────────────┘ └──────────────────────────┘
576
- ▼
577
- ┌────────────────────────────────────────┐
578
- │ Tool Registry (src/tools.ts) │
579
- │ + web_fetch (src/webfetch.ts) │
580
- └──────────────┬─────────────────────────┘
581
- ▼
582
- ┌───────────────────────────────┐
583
- │ Build Checker (src/build.ts) │
584
- │ run build → exit 0 = stop │
585
- └───────────────────────────────┘
586
- ```
587
-
588
- ## Integration Testing
589
-
590
- ```bash
591
- npm test # runs: build → unit tests (node --test) → e2e closed-loop → MCP smoke
592
- npm run lint # eslint flat config (typescript-eslint + eqeqeq, prefer-const...)
593
- npm run typecheck # tsc --noEmit
594
- ```
595
-
596
- Test suites (run with `npm test` — auto-discovers `test/*.test.mjs`):
597
- - `test/permissions.test.mjs` — catastrophic patterns, mode classification, allowlist word-boundary, timeout-denies
598
- - `test/permission-prompt.test.mjs` — interactive prompt timeout behavior
599
- - `test/tools.test.mjs` — safePath traversal, symlink escape, file-size cap, write/edit/list, live `run_command` output streaming
600
- - `test/webfetch.test.mjs` — HTML→text, JSON, redirects, SSRF guard, binary refusal, truncation
601
- - `test/server.test.mjs` — HTTP API: run lifecycle via SSE, abort, auth, live `tool_output` events
602
- - `test/tui.test.mjs` — full-screen TUI in a real PTY (via `test/pty-run.py`): closed loop render + slash commands (`/help`, `/cost`) + clean exit
603
- - `test/tui-state.test.mjs` — event reducer unit tests: diff items, build lamp, abort marking, slash-command parsing & help formatting
604
- - `test/review-flow.test.mjs` — `/review` prompt-gate wiring: both pending stages, approved/cancelled outcomes, verbatim `rewritten_prompt` echo (security)
605
- - `test/core.test.mjs` — unified diff, retry backoff, context trimming, @file mention expansion
606
- - `test/config.test.mjs` — env/rc/cli precedence, logFile mapping, permission lists
607
- - `test/session.test.mjs` — session store, smart prune, resolve
608
- - `test/cost.test.mjs` — token usage & cost aggregation, session metrics (latency/success/guardrails)
609
- - `test/guardrails.test.mjs` — Guardrail Engine: rule registry, default rules, configurable thresholds, decision logging, priority order
610
- - `test/prompt.test.mjs` — system prompt versioning (`PROMPT_VERSION`) + dynamic assembly
611
- - `test/anthropic.test.mjs` — message translation + native SSE streaming
612
- - `test/llm.test.mjs` — OpenAI-compatible chat + SSE streaming + redaction + wire-format normalization
613
- - `test/run-e2e.mjs` — closed-loop integration (mock LLM server)
614
- - `test/run-mcp-test.mjs` — MCP smoke (mock stdio server)
615
-
616
- ## Exit Codes
617
-
618
- | Code | Meaning |
619
- |------|---------|
620
- | `0` | Task completed — build passed |
621
- | `1` | Build failed after max steps |
622
- | `2` | Fatal error (no API key, network error, etc.) |
623
-
624
- ## Documentation
625
-
626
- See [docs/](docs/) for detailed guides:
627
-
628
- | Doc | Description |
629
- |-----|-------------|
630
- | [Installation](docs/installation.md) | Install from npm, GitHub, or local clone |
631
- | [Configuration](docs/configuration.md) | Environment variables, config file, permissions |
632
- | [Commands](docs/commands.md) | CLI commands & flags reference |
633
- | [Architecture](docs/architecture.md) | System design & module overview |
634
- | [TUI Guide](docs/tui.md) | Full-screen terminal UI |
635
- | [Guardrails](docs/guardrails.md) | Safety & progress engine |
636
- | [MCP Integration](docs/mcp.md) | Model Context Protocol servers |
637
- | [HTTP API](docs/api.md) | Server mode & SSE API |
638
- | [Sessions](docs/sessions.md) | Session persistence & resume |
639
- | [Skills](docs/skills.md) | SKILL.md instruction packs |
640
- | [Testing](docs/testing.md) | Test suites & coverage |
641
-
642
- ## License
643
-
644
- MIT
60
+ ```
61
+
62
+ ## How It Works
63
+
64
+ ```
65
+ You (task) → LLM plans → Tools execute code → Build runs → Success? → Done
66
+ ↑ |
67
+ └── Build failed? ─┘ (auto-loop)
68
+ ```
69
+
70
+ **Key constraint:** scoder runs autonomously without stopping for confirmation prompts. If the build fails, it reads the error, patches the code, and retries automatically — until the build passes or the max-step limit is reached.
71
+
72
+ ## Tool Set
73
+
74
+ | Tool | Description |
75
+ |------|-------------|
76
+ | `read_file` | Read a file with line numbers |
77
+ | `write_file` | Overwrite/create a file |
78
+ | `edit_file` | Find-and-replace (unique match required) |
79
+ | `list_files` | Recursive directory listing |
80
+ | `run_command` | Execute any shell command (subject to permission policy) |
81
+ | `web_fetch` | Fetch a public HTTP(S) URL → readable text (HTML→text, JSON pretty-printed) |
82
+ | `write_todos` | Live plan for multi-step tasks (checkbox list, kept current) |
83
+ | `load_skill` | Load a SKILL.md into context when the task matches its description |
84
+ | `mcp__<server>__<tool>` | Tools from configured MCP servers (routed transparently) |
85
+
86
+ File writes (`write_file` / `edit_file`) are symlink-safe: paths are resolved via `realpath` and cannot escape the working directory, even through a malicious symlink. `read_file` refuses files larger than 2MB with guidance to use `grep`/`head`/`tail`.
87
+
88
+ ### Web access (`web_fetch`)
89
+
90
+ The agent can read the internet through the built-in `web_fetch` tool — it fetches exact URLs (it cannot search) and returns readable plain text: HTML is converted to text (scripts/styles stripped, entities decoded, title extracted), JSON is pretty-printed, binary content is refused. The system prompt instructs the agent *when to fetch* (external/API docs, URLs in error messages, current versions, changelogs) and when not to (answer available locally).
91
+
92
+ Safety defaults:
93
+
94
+ - **SSRF protection** — `localhost`, private and link-local addresses (10.x, 172.16/12, 192.168.x, 127.x, 169.254.x, IPv6 ULA/link-local) are blocked. Set `SCODER_WEB_ALLOW_PRIVATE=1` to allow local dev servers.
95
+ - Only `http`/`https`; up to 5 redirects — every hop is re-validated against the private-address block list.
96
+ - 30 s timeout, SIGINT-abortable; body capped at 512 KB; extracted text capped at 20k chars (configurable via `max_chars`).
97
+ - HTML is converted to plain text (no JS execution); images/archives/PDFs are refused.
98
+ - Read-only — the tool never writes locally, so it is allowed even in `readonly` permission mode.
99
+
100
+ ## Configuration
101
+
102
+ ### Environment variables
103
+
104
+ | Variable | Default | Description |
105
+ |----------|---------|-------------|
106
+ | `SCODER_API_KEY` | — | API key (required) |
107
+ | `SCODER_MODEL` | `gpt-4o-mini` | Model name |
108
+ | `SCODER_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible endpoint |
109
+ | `SCODER_BUILD_CMD` | auto-detected | Build/compile command |
110
+ | `SCODER_MAX_STEPS` | `40` | Max loop iterations before giving up |
111
+ | `SCODER_PERMISSION` | `auto` | Permission mode: `auto` \| `ask` \| `readonly` |
112
+ | `SCODER_STREAM` | `1` | `0` to disable streaming LLM output |
113
+ | `SCODER_SERVER_TOKEN` | — | `scoder serve` only: require `Authorization: Bearer <token>` |
114
+ | `SCODER_WEB_ALLOW_PRIVATE` | — | Set to `1` to let `web_fetch` reach localhost/private addresses (local dev) |
115
+ | `SCODER_NO_APIKEY_WARNING` | — | Set to `1` to suppress the apiKey-in-rc warning |
116
+
117
+ ### Per-project config file (.scoderrc.json)
118
+
119
+ For any OpenAI-compatible endpoint, just point `baseUrl`, `apiKey`, and `model` at it — no code changes needed. Create `.scoderrc.json` in your project root (see `.scoderrc.example.json`):
120
+
121
+ ```json
122
+ {
123
+ "baseUrl": "https://api.groq.com/openai/v1",
124
+ "apiKey": "gsk_...",
125
+ "model": "llama-3.3-70b-versatile"
126
+ }
127
+ ```
128
+
129
+ Then simply run `scoder "your task"` — values are picked up automatically.
130
+
131
+ ### Global config (~/.scoder/config.json)
132
+
133
+ Machine-wide defaults shared by **all** projects: `mcpServers`, `model`, `baseUrl`, `permission`, `permissions` lists, `modelPricing`… The project `.scoderrc.json` wins field-by-field; `mcpServers` and lists merge per-key (project entries override same-named global ones). API keys are safe here — the file lives in your home dir, outside any repo.
134
+
135
+ #### All fields
136
+
137
+ | Field | Default | Description |
138
+ |---|---|---|
139
+ | `baseUrl` | `https://api.openai.com/v1` | OpenAI-compatible endpoint (must support `/chat/completions` with tool calling) |
140
+ | `apiKey` | — | API key (⚠️ prefer `SCODER_API_KEY` env var, see warning below) |
141
+ | `model` | `gpt-4o-mini` | Model name served by the endpoint |
142
+ | `buildCmd` | auto-detected | Build/compile command (stop condition) |
143
+ | `maxSteps` | `40` | Max autonomous loop iterations |
144
+ | `stream` | `true` | Stream LLM output token-by-token |
145
+ | `provider` | auto-detected | `openai` \| `anthropic` |
146
+ | `permission` | `auto` | `auto` \| `ask` \| `readonly` |
147
+ | `mcpServers` | — | MCP servers — stdio (`command`) hoặc HTTP (`url`) (see below) |
148
+ | `permissions` | — | `{ mode, allowCommands, denyCommands }` |
149
+
150
+ #### Precedence
151
+
152
+ Values are merged from highest to lowest priority — your `.scoderrc.json` value only applies when no flag/env is set:
153
+
154
+ 1. **CLI flags** — `-m`, `--base-url`, `-k`, `--provider`, `-B`
155
+ 2. **Environment variables** — `SCODER_API_KEY` / `OPENAI_API_KEY`, `SCODER_BASE_URL`, `SCODER_MODEL`, …
156
+ 3. **`.scoderrc.json`** (or `.scoderrc`) in the project root, merged over the global `~/.scoder/config.json` — project values win field-by-field; `mcpServers`/lists/pricing merge per-key
157
+ 4. Built-in defaults
158
+
159
+ > 💡 If your config in `.scoderrc.json` seems ignored, check `echo $SCODER_API_KEY` — env vars override the file.
160
+
161
+ #### Examples for common providers
162
+
163
+ ```jsonc
164
+ // Local Ollama
165
+ {
166
+ "baseUrl": "http://localhost:11434/v1",
167
+ "apiKey": "ollama",
168
+ "model": "qwen2.5-scoder:32b"
169
+ }
170
+ ```
171
+
172
+ ```jsonc
173
+ // DeepSeek
174
+ {
175
+ "baseUrl": "https://api.deepseek.com/v1",
176
+ "apiKey": "sk-...",
177
+ "model": "deepseek-chat"
178
+ }
179
+ ```
180
+
181
+ #### Notes
182
+
183
+ - Models starting with `claude-`, or a `baseUrl` containing `api.anthropic.com`, automatically switch to the native Anthropic provider — no `provider` field needed.
184
+ - If an `apiKey` is present in the rc file, scoder prints a warning on every run. Silence it with `SCODER_NO_APIKEY_WARNING=1`.
185
+
186
+ > ⚠️ **Security:** never commit `apiKey` in `.scoderrc.json` — add the file to `.gitignore` and prefer `SCODER_API_KEY` env var or the `-k` flag. A pre-commit check is available at `scripts/check-secrets.mjs`.
187
+
188
+ ## Permissions & Safety
189
+
190
+ scoder ships with a layered permission system for `run_command`:
191
+
192
+ | Mode | Behavior |
193
+ |------|----------|
194
+ | `auto` (default) | Autonomous — runs shell commands without confirmation (original behavior) |
195
+ | `ask` | Prompts before each shell command not covered by `allowCommands` |
196
+ | `readonly` | Shell commands are disabled entirely; the agent can only read/list files. MCP tools are also disabled (they bypass the permission policy) |
197
+
198
+ Layered checks, in order:
199
+
200
+ 1. **Catastrophic deny list** — `rm -rf /`, `dd of=/dev/...`, fork bombs, `curl | sh`, etc. are **always denied**, in every mode.
201
+ 2. **`denyCommands`** (config) — always denied.
202
+ 3. **Session allowlist** — commands you approved with `[a] always allow` in this session.
203
+ 4. **`allowCommands`** (config) — auto-approved.
204
+ 5. **Mode** — `auto` runs everything else; `ask` prompts; `readonly` denies.
205
+
206
+ In `ask` mode the prompt offers `[y] yes / [a] always allow "<prefix>" / [n] no / [q] quit`. With `-y` (non-interactive), commands needing confirmation are denied instead of hanging CI.
207
+
208
+ ## Skills (SKILL.md)
209
+
210
+ scoder can load reusable instruction packs ("skills") into the conversation. A skill is a directory with a `SKILL.md` (YAML-lite frontmatter + markdown body):
211
+
212
+ ```
213
+ .scoder/skills/code-review/SKILL.md # project-level
214
+ ~/.scoder/skills/code-review/SKILL.md # global (project overrides on name clash)
215
+ ```
216
+
217
+ ```markdown
218
+ ---
219
+ name: code-review
220
+ description: Review code for bugs, security issues, and style
221
+ allowed-tools: read_file, grep
222
+ license: MIT
223
+ when-to-use: Run after any multi-file change before committing
224
+ ---
225
+ Read the current diff. Check for: null derefs, injection risks, ...
226
+ ```
227
+
228
+ - **Advertised to the model** — every run's system prompt lists skill names + descriptions; the agent reads a matching `SKILL.md` itself via `read_file` when a task fits
229
+ - **Explicit load** — `/skill <name>` stages the skill's content; it is injected as context before your next task
230
+ - **Bare `/skill`** lists everything discovered (both scopes)
231
+ - **Metadata (Anthropic convention)** — `description` is required (a skill without one is skipped with a warning); optional `allowed-tools` (comma-separated tool list, informational — enforcement goes through permission rules), `license`, `when-to-use`
232
+ - **Validation warnings** — duplicate names across scopes warn (project wins); missing `description` skips the skill; warnings surface in the log/notice stream
233
+ - **Hot-reload** — `.scoderrc` and `.scoder/skills/` are watched (debounced); a skill added mid-session is advertised on the next turn, no restart needed
234
+
235
+ ## Slash commands (REPL + TUI)
236
+
237
+ | Command | Description |
238
+ |---|---|
239
+ | `/help` | command list (builtin + custom) |
240
+ | `/agent` | list/select primary agents (build/plan/review/custom) |
241
+ | `/init` | create an AGENTS.md template in this project |
242
+ | `/theme` | switch palette (TUI) |
243
+ | `/clear` | reset conversation + agent context |
244
+ | `/compact` | summarize older history now (manual compaction) |
245
+ | `/cost` | session token usage |
246
+ | `/model` | active model/provider/build |
247
+ | `/skill [name]` | list discovered skills; with a name, stage it for the next task |
248
+ | `/mcp` | MCP servers + tools; exec one directly: `/mcp <server> <tool> [json\|text]` or `/mcp mcp__server__tool [json\|text]` — non-JSON text binds to the tool's first string parameter |
249
+ | `/permission <auto\|ask\|readonly>` | switch permission mode at runtime |
250
+
251
+ Sessions are managed with `scoder list` / `scoder resume` (CLI) or the **Ctrl+S** session picker (TUI). Quit with `exit`, `quit`, or **Ctrl+C**.
252
+
253
+ In the TUI, **Tab** completes commands (and `/skill` names, `/mcp <server> <tool>` arguments), **↑/↓** recall sent-message history.
254
+
255
+ ### Context compaction
256
+
257
+ Long runs are summarized automatically when history reaches `contextWindow × trigger` (default **80%**). Mechanical trimming only starts later, at **95%** of the window, so compaction gets the first chance to preserve task state. `/compact` runs the same summarization immediately (useful mid-session or when auto-compaction is disabled).
258
+
259
+ ```jsonc
260
+ // .scoderrc.json — all fields optional
261
+ "compaction": {
262
+ "trigger": 0.8, // fraction of the model window that triggers compaction
263
+ "keep": 0.15, // recent-history fraction kept verbatim
264
+ "buffer": 0.15, // extra recent-history fraction never summarized
265
+ "contextWindow": 128000 // override the model-catalog window
266
+ }
267
+ // "compaction": false disables automatic compaction (mechanical trim only)
268
+ ```
269
+
270
+ `/compact` remains available even when automatic compaction is disabled.
271
+
272
+ ### @file mentions
273
+
274
+ Prefix any path with `@` in your task to inline its content into the agent's context:
275
+
276
+ ```bash
277
+ scoder "fix the bug in @src/agent.ts"
278
+ scoder "refactor the handlers in @src/handlers/ and update @README.md"
279
+ ```
280
+
281
+ - `@src/file.ts` — file content is injected into the task; `@"path with spaces.ts"` for paths containing spaces
282
+ - `@src/dir/` — directory listing injected instead
283
+ - Emails (`bob@example.com`) are never treated as mentions; missing paths produce a visible note the LLM can react to; paths are contained to the working directory
284
+
285
+ ## Planning tool
286
+
287
+ For multi-step tasks the agent writes a live plan with `write_todos` (`[x]` completed · `[~]` in progress · `[ ]` pending) — rendered inline in the TUI and included in session state.
288
+
289
+ ## Guardrails (Guardrail Engine)
290
+
291
+ The autonomous loop is wrapped by a configurable rule engine (`src/guardrails.ts`) — every rule is a pure function over the step context that returns a decision (`inject` a reminder / `finish` / `verify-build` / `trim`), and every triggered decision is logged to the JSONL log (`type: "guardrail"`) and surfaced as an `AgentEvent` for frontends:
292
+
293
+ | Rule | What it does |
294
+ |---|---|
295
+ | `wrap-up` | Injects a converge-now reminder 2 steps before the budget ends |
296
+ | `chat-done` | Ends conversational turns (marker-based) without a build |
297
+ | `completion-claim` | Never trusts "the task is complete" — forces an independent build verification |
298
+ | `text-only-streak` | Ends the run after 2 consecutive tool-less steps with no file changes (the "chào" failure mode) |
299
+ | `continue-nudge` | Nudges plain-text answers back to work (no build demand when no files changed — suggests the `<<CHAT_DONE>>` exit instead) |
300
+ | `doom-loop` | Intervenes after 3× identical consecutive tool-call batches |
301
+ | `build-stop` | Ends the run when the exact build command succeeds; re-verifies on failure |
302
+ | `history-trim` | Char-budget history trimming with whole-turn drops + old-tool-result pruning |
303
+
304
+ Thresholds are configurable via `GuardrailOptions` (`textOnlyStreakLimit`, `doomLoopRepeats`, `wrapUpStepsBeforeEnd`) without touching the loop, and custom rules can be added via `GuardrailEngine.register()`.
305
+
306
+ ## Usage & cost metrics
307
+
308
+ `scoder cost` reads the JSONL logs and reports per session and total: LLM calls, tokens (in/out/cached), estimated cost, **steps, average LLM latency, tool calls, guardrail interventions, and the session success rate**:
309
+
310
+ ```
311
+ 20261031-110353-d034 calls=1 in=4,042 out=88 cost≈$0.0022 | steps=1 llm-avg=7.5s tools=1 guardrails=1 ✓success
312
+ TOTAL (1 session(s)) ... | success-rate=1/1
313
+ ```
314
+
315
+ The system prompt is versioned (`PROMPT_VERSION` in `src/prompt.ts`) and stamped into every session log (`type: "prompt_version"`) so prompt quality can be compared across versions.
316
+
317
+ ## MCP Servers (stdio / HTTP)
318
+
319
+ scoder can use tools from [MCP](https://modelcontextprotocol.io) servers configured in `.scoderrc.json`:
320
+
321
+ ```json
322
+ {
323
+ "mcpServers": {
324
+ "filesystem": {
325
+ "command": "npx",
326
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
327
+ },
328
+ "github": {
329
+ "command": "npx",
330
+ "args": ["-y", "@modelcontextprotocol/server-github"],
331
+ "env": { "GITHUB_TOKEN": "{env:GITHUB_TOKEN}" }
332
+ },
333
+ "remote": {
334
+ "url": "https://mcp.example.com/mcp",
335
+ "headers": { "authorization": "Bearer {env:MCP_TOKEN}" }
336
+ }
337
+ }
338
+ }
339
+ ```
340
+
341
+ - Tools are exposed to the LLM as `mcp__<server>__<tool>` (no name collisions with built-ins)
342
+ - Servers connect at agent start; a failing server only logs a warning — the agent keeps running with its built-in tools
343
+ - `{env:VAR}` substitution keeps secrets out of config files (both `env` and `headers`)
344
+ - **Transports**: `command` → stdio (line-delimited JSON-RPC); `url` → Streamable HTTP (POST JSON-RPC, JSON or SSE responses, `Mcp-Session-Id` echo). Force with `"type": "stdio" | "http"`
345
+ - **Resilience**: a dead stdio server reconnects with exponential backoff (max 5 attempts) and refreshes its tool list; HTTP calls are stateless and retry on the next call
346
+ - Built-in tools always take priority over MCP tools with colliding names
347
+ - ⚠️ **MCP tools bypass the built-in permission system** (no command classification, no diff preview). In `readonly` mode they are therefore disabled entirely; in `auto`/`ask` mode they run unsandboxed — only add MCP servers you trust.
348
+
349
+ ### MCP governance
350
+
351
+ Each server supports `enabled`, `trust` (`auto`/`ask`/`readonly`/`deny`), `tools.include`/`exclude`
352
+ (globs), and `timeoutMs`. `readonly` servers are never spawned. Import existing configs with
353
+ `scoder mcp import`. MCP tool output is flagged `untrusted` and wrapped with an `[UNTRUSTED]`
354
+ marker; audit entries are written to the JSONL log.
355
+
356
+ ## Sessions
357
+
358
+ Every run auto-saves its state (messages, steps, build result) to `.scoder/sessions/<id>.json` after each step. If the run is interrupted (Ctrl+C, crash, max steps), you can continue where it left off:
359
+
360
+ ```bash
361
+ scoder list # list saved sessions
362
+ scoder resume last # continue the most recent session
363
+ scoder resume <id> "follow-up" # resume + add a new instruction
364
+ ```
365
+
366
+ Sessions older than 7 days are pruned automatically on the next run. Use `--no-save` to disable (e.g. in CI).
367
+
368
+ ## Streaming
369
+
370
+ scoder streams everything it can, live, as it happens:
371
+
372
+ - **LLM tokens** — assistant text renders character-by-character (OpenAI-compatible SSE and native Anthropic streaming). If a provider doesn't support streaming, scoder automatically falls back to non-streaming requests. Disable with `SCODER_STREAM=0`.
373
+ - **Tool output** — `run_command` and build output print live (dimmed block) while the command runs, instead of appearing only after it finishes. Live output is capped at 20k chars; the full output still goes to the LLM.
374
+ - **SIGINT-safe** — Ctrl+C kills the running command immediately; streamed rendering never blocks the event loop.
375
+ - Output honors `NO_COLOR` and degrades to plain text when piped (CI-friendly).
376
+
377
+ ## Full-Screen TUI (`scoder tui`)
378
+
379
+ **Mission Control for the build loop** — a terminal UI built with [Ink](https://github.com/vadimdemedes/ink) (React for terminals), driven by the same `AgentEvent` stream the HTTP API exposes:
380
+
381
+ ```bash
382
+ scoder tui # start an interactive full-screen session
383
+ scoder tui "fix the bug" # auto-submit an initial task
384
+ ```
385
+
386
+ Features:
387
+
388
+ - **Alternate screen buffer** (vim/htop style) — the app owns the full screen: the header stays pinned at the top, input + status bar at the bottom, the chat pane scrolls internally, and your terminal content is restored on exit. Terminals without alt-screen support (`TERM=dumb`, non-TTY) automatically render inline instead.
389
+ - **Build Rail** (pinned top app bar — brand row over a thin hairline divider) — brand, build lamp (`✓ ok` / `✗ fail` / `no build yet`), permission mode, and a center region that shows either the phase breadcrumb (`plan › explore › edit › build › verify`) or the latest guardrail notice spanning the full available width (e.g. `⚠ trả lời text chưa dùng tool — nhắc tiếp tục`)
390
+ - **Sidebar (≥100 cols, 48 wide)** — SESSION (model + workdir, no permission line), METRICS (step fuel gauge, tokens, tok/s, ctx input/limit, cost, elapsed, guardrail count), PHASES, BUILD, MCP, PLAN (live checklist at the bottom; resets on each new user message)
391
+ - **Chat pane** — your messages with a left accent border + `you ▸` label (no heavy background block), streaming assistant text with a live block cursor (`▋`), and every tool call with live stdout/stderr preview (`✔/✘` status, spinner while running, wall-clock duration per tool), prefixed with a flight-recorder step number (`03 ▸`)
392
+ - **Header row 2** — (no longer present; phase breadcrumb + metrics moved into the sidebar / status bar to avoid duplication)
393
+ - **Diff cards** (`✎`) — every file write/edit renders as a unified diff (`+` green / `-` red / `@@` cyan hunk headers); the same diff also flows out of the HTTP SSE stream
394
+ - **Status bar** — model, token usage (input/output/cached), scroll position, keybind hints
395
+ - **Slash commands** — `/help`, `/clear`, `/compact`, `/cost`, `/model`, `/skill`, `/mcp`, `/permission` (identical to the REPL; `/theme` is TUI-only)
396
+ - **Multi-turn** — keep typing follow-ups after a task finishes; the conversation context carries over (same as the REPL)
397
+ - **Scrollback** — `PgUp`/`PgDn` page in 0.8-viewport steps, `Home`/`End` jump to top/bottom, `Esc` returns to the live tail (press again to abort); rendering is capped at the last 200 items for smooth scrolling on long runs
398
+ - **Keybinds** — `Enter` send · `PgUp`/`PgDn` scroll · `Esc` reset scroll / abort · `Ctrl+C` quit
399
+ - Non-TTY environments (CI, pipes) automatically fall back to the classic REPL
400
+ - `permission: "ask"` is not supported in the TUI (no readline prompts on the alt screen) — it falls back to `auto`
401
+
402
+ ## HTTP Server Mode (`scoder serve`)
403
+
404
+ Run scoder as a local HTTP service — submit tasks programmatically and consume progress as Server-Sent Events:
405
+
406
+ ```bash
407
+ scoder serve # http://127.0.0.1:0 (random port, printed at startup)
408
+ scoder serve --port 4114 # fixed port
409
+ SCODER_SERVER_TOKEN=secret scoder serve --host 127.0.0.1
410
+ ```
411
+
412
+ | Endpoint | Method | Description |
413
+ |----------|--------|-------------|
414
+ | `/api/health` | GET | Liveness: `{ ok, running, runs }` |
415
+ | `/api/runs` | POST | Start a run: `{ "task": "...", "workdir?", "buildCmd?", "model?", "provider?", "maxSteps?", "permission?" }` → `202 { id, eventsUrl }` |
416
+ | `/api/runs` | GET | List runs with status/timing |
417
+ | `/api/runs/<id>` | GET | Run status + final result |
418
+ | `/api/runs/<id>/events` | GET | **SSE stream** — full event replay, then live, closes on finish |
419
+ | `/api/runs/<id>/abort` | POST | Abort a running run (equivalent to SIGINT) |
420
+
421
+ SSE event types: `run_start`, `step`, `llm_delta` (assistant text), `tool_call`, `tool_output` (live stdout/stderr chunks), `tool_result`, `llm_usage`, `build`, `finish`. The stream replays every event from the beginning, then stays live, and closes after `finish` — safe to (re)connect at any time.
422
+
423
+ Notes:
424
+
425
+ - Runs are always non-interactive; `permission: "ask"` is rejected — use `auto` or `readonly`.
426
+ - Binds `127.0.0.1` by default. Set `SCODER_SERVER_TOKEN` to require `Authorization: Bearer <token>` on every request.
427
+ - Max 4 concurrent runs (extra requests get `429`); per-run overrides: `workdir`, `buildCmd`, `model`, `provider`, `maxSteps`, `permission`.
428
+
429
+ ## Logging
430
+
431
+ Enable a structured JSONL event log with `--log-file` (or `SCODER_LOG_FILE=1`, or `SCODER_LOG_FILE=/path/to/log.jsonl`):
432
+
433
+ ```bash
434
+ scoder --log-file "fix the failing test"
435
+ cat .scoder/logs/<session-id>.jsonl | jq 'select(.type == "error")'
436
+ ```
437
+
438
+ Events: `step` (with `durationMs` LLM latency + tool-call count), `tool` (args, ok, elapsedMs, denied reason), `build`, `guardrail` (rule/action/reason for every Guardrail Engine decision), `prompt_version`, `finish`. One JSON object per line — pipe to `jq` or any log consumer.
439
+
440
+ ## Reliability
441
+
442
+ scoder is designed for unattended use. Key guarantees:
443
+
444
+ - **Request timeouts** — LLM calls have a 120s timeout; on timeout, the agent retries or fails gracefully (never hangs indefinitely).
445
+ - **SIGINT handling** — pressing Ctrl+C cancels the in-flight LLM request immediately, kills the running shell command (SIGTERM → SIGKILL), saves the session, and exits with status 130. You can resume with `scoder resume last`.
446
+ - **Graceful retries** — transient LLM API failures (HTTP 429/5xx, network errors) are retried with exponential backoff and jitter (up to 3 retries, honoring `Retry-After`). Client errors (401/403/404) fail fast.
447
+ - **Double-build prevention** — when the agent itself runs the build command, scoder reuses that result instead of running the build twice.
448
+ - **Build diff preview** — run `scoder --permission ask` to see a unified diff before every file write/edit.
449
+
450
+ > **Design note:** the build command (stop condition) always runs without a permission prompt, even in `readonly` mode — the agent must be able to verify its stop condition. Builds are invoked as-is from your config (`buildCmd`) or auto-detection, never invented by the LLM. Shell commands *chosen by the LLM* (`run_command` tool) go through the full permission policy above.
451
+
452
+ ### Auto-detected build commands
453
+
454
+ scoder detects the build command automatically:
455
+
456
+ | Project type | Detected command |
457
+ |---|---|
458
+ | `package.json` with `scripts.build` | `npm run build` |
459
+ | `Makefile` | `make` |
460
+ | `go.mod` | `go build ./...` |
461
+ | `Cargo.toml` | `cargo build` |
462
+ | `tsconfig.json` | `npx tsc --noEmit` |
463
+ | `pyproject.toml` | `python -m compileall .` |
464
+
465
+ ## CLI Options
466
+
467
+ ```
468
+ scoder [options] "<task>" Run a task autonomously until the build passes
469
+ scoder Start interactive session
470
+ scoder init Setup wizard — writes .scoderrc.json (+ connection test)
471
+ scoder tui [task] Full-screen terminal UI (Mission Control)
472
+ scoder serve [--port] [--host] HTTP server mode (see above)
473
+ scoder --help | --version Help and version
474
+ ```
475
+
476
+ | Flag | Description |
477
+ |------|-------------|
478
+ | `-m <model>` | Model to use |
479
+ | `--base-url <url>` | OpenAI-compatible API base URL |
480
+ | `-k <key>` | API key |
481
+ | `-B, --build <cmd>` | Build command (stop condition) |
482
+ | `-d, --dir <path>` | Working directory |
483
+ | `-s, --max-steps <n>` | Max iterations (default 40) |
484
+ | `--permission <mode>` | `auto` \| `ask` \| `readonly` shell-command policy (default `auto`) |
485
+ | `-y` | Non-interactive mode (denies commands needing confirmation) |
486
+ | `--provider <name>` | `openai` \| `anthropic` (env SCODER_PROVIDER; auto-detected) |
487
+ | `--agent <name>` | Primary agent to run (build/plan/review/custom) |
488
+ | `--no-save` | Disable session auto-save to `.scoder/sessions/` |
489
+ | `--no-color` | Disable colored output |
490
+ | `--no-motion` | Disable animations |
491
+ | `--plain` | Plain (monochrome) output |
492
+ | `--high-contrast` | High-contrast theme |
493
+ | `--log-file` | Write a structured JSONL event log to `.scoder/logs/` |
494
+ | `--port <n>` | (`serve`) TCP port, `0` = random free port |
495
+ | `--host <addr>` | (`serve`) Bind address (default `127.0.0.1`) |
496
+
497
+ ## Examples
498
+
499
+ ```bash
500
+ # Run a task (auto-saves session to .scoder/sessions/)
501
+ scoder -B "npm test" "fix the null pointer in UserService"
502
+
503
+ # Resume after a crash or interrupt
504
+ scoder resume last "now add unit tests for the fix"
505
+
506
+ # Interactive mode with permission guard
507
+ scoder --permission ask "npm install express and wire up a route"
508
+
509
+ # Custom LLM (Groq, Ollama, vLLM, OpenRouter…)
510
+ scoder --base-url https://api.groq.com/openai/v1 -k $GROQ_KEY \
511
+ "add input validation to the CLI"
512
+
513
+ # Anthropic (auto-detected when model starts with 'claude-')
514
+ scoder -m claude-sonnet-4-5 "refactor the parser module"
515
+
516
+ # CI: non-interactive, no session saving
517
+ scoder --no-save -y "run the full migration"
518
+
519
+ # Run as an HTTP service and consume progress via SSE
520
+ scoder serve --port 4114 &
521
+ curl -s -X POST localhost:4114/api/runs -H 'Content-Type: application/json' \
522
+ -d '{"task": "add input validation to the CLI"}'
523
+ curl -N http://127.0.0.1:4114/api/runs/<id>/events # live progress
524
+
525
+ # Fetch external docs during a task (agent decides when, via web_fetch)
526
+ scoder "upgrade express to v5 — check the official migration guide first"
527
+ ```
528
+
529
+ ## Architecture
530
+
531
+ Domain-oriented source layout — see [src/README.md](src/README.md), [docs/architecture.md](docs/architecture.md) and the [refactor plan](plan/REFACTOR-PLAN.md).
532
+
533
+ ```
534
+ ┌─────────────────────────────────────────────┐
535
+ │ CLI (src/index.ts → src/cli/) │
536
+ │ arg parsing, REPL, serve, tui, SIGINT │
537
+ └───────┬─────────────────────┬───────────────┘
538
+ │ │
539
+ ┌───────▼───────────┐ ┌──────▼─────────────────────┐
540
+ │ TUI (src/tui/) │ │ Agent (src/agent.ts + src/agent/) │
541
+ │ Ink/React alt- │ │ Closed-loop orchestrator │
542
+ │ screen UI, │─▶│ + Guardrail Engine │
543
+ │ renders the same │ │ (src/agent/guardrails.ts) │
544
+ │ AgentEvent stream│ │ emits AgentEvent │
545
+ └───────────────────┘ └──┬──────────┬──────────────┘
546
+ │ │
547
+ │ └────────────────────┐
548
+ ▼ ▼
549
+ ┌───────────────────────────────┐ ┌──────────────────────────┐
550
+ │ LLM (src/llm/) │ │ Server (src/server/) │
551
+ │ OpenAI-compatible + native │ │ HTTP API + SSE │
552
+ │ Anthropic provider │ │ runs in background │
553
+ └──────────────┬────────────────┘ └──────────────────────────┘
554
+ ▼
555
+ ┌────────────────────────────────────────┐
556
+ │ Tool Registry (src/tools/) │
557
+ │ + web_fetch (src/tools/webfetch.ts) │
558
+ └──────────────┬─────────────────────────┘
559
+ ▼
560
+ ┌───────────────────────────────┐
561
+ │ Build Checker (src/workspace/build.ts) │
562
+ │ run build → exit 0 = stop │
563
+ └───────────────────────────────┘
564
+ ```
565
+
566
+ ## Integration Testing
567
+
568
+ ```bash
569
+ npm test # runs: build → unit tests (node --test) → e2e closed-loop → MCP smoke
570
+ npm run lint # eslint flat config (typescript-eslint + eqeqeq, prefer-const...)
571
+ npm run typecheck # tsc --noEmit
572
+ ```
573
+
574
+ Test suites (run with `npm test` — auto-discovers `test/*.test.mjs`):
575
+ - `test/permissions.test.mjs` — catastrophic patterns, mode classification, allowlist word-boundary, timeout-denies
576
+ - `test/permission-prompt.test.mjs` — interactive prompt timeout behavior
577
+ - `test/tools.test.mjs` — safePath traversal, symlink escape, file-size cap, write/edit/list, live `run_command` output streaming
578
+ - `test/webfetch.test.mjs` — HTML→text, JSON, redirects, SSRF guard, binary refusal, truncation
579
+ - `test/server.test.mjs` — HTTP API: run lifecycle via SSE, abort, auth, live `tool_output` events
580
+ - `test/tui.test.mjs` — full-screen TUI in a real PTY (via `test/pty-run.py`): closed loop render + slash commands (`/help`, `/cost`) + clean exit
581
+ - `test/tui-state.test.mjs` — event reducer unit tests: diff items, build lamp, abort marking, slash-command parsing & help formatting
582
+ - `test/core.test.mjs` — unified diff, retry backoff, context trimming, @file mention expansion
583
+ - `test/config.test.mjs` — env/rc/cli precedence, logFile mapping, permission lists
584
+ - `test/session.test.mjs` — session store, smart prune, resolve
585
+ - `test/cost.test.mjs` — token usage & cost aggregation, session metrics (latency/success/guardrails)
586
+ - `test/guardrails.test.mjs` — Guardrail Engine: rule registry, default rules, configurable thresholds, decision logging, priority order
587
+ - `test/prompt.test.mjs` — system prompt versioning (`PROMPT_VERSION`) + dynamic assembly
588
+ - `test/anthropic.test.mjs` — message translation + native SSE streaming
589
+ - `test/llm.test.mjs` — OpenAI-compatible chat + SSE streaming + redaction + wire-format normalization
590
+ - `test/run-e2e.mjs` — closed-loop integration (mock LLM server)
591
+ - `test/run-mcp-test.mjs` — MCP smoke (mock stdio server)
592
+
593
+ ## Exit Codes
594
+
595
+ | Code | Meaning |
596
+ |------|---------|
597
+ | `0` | Task completed — build passed |
598
+ | `1` | Build failed after max steps |
599
+ | `2` | Fatal error (no API key, network error, etc.) |
600
+
601
+ ## Documentation
602
+
603
+ See [docs/](docs/) for detailed guides:
604
+
605
+ | Doc | Description |
606
+ |-----|-------------|
607
+ | [Installation](docs/installation.md) | Install from npm, GitHub, or local clone |
608
+ | [Configuration](docs/configuration.md) | Environment variables, config file, permissions |
609
+ | [Commands](docs/commands.md) | CLI commands & flags reference |
610
+ | [Architecture](docs/architecture.md) | System design & module overview |
611
+ | [TUI Guide](docs/tui.md) | Full-screen terminal UI |
612
+ | [Guardrails](docs/guardrails.md) | Safety & progress engine |
613
+ | [MCP Integration](docs/mcp.md) | Model Context Protocol servers |
614
+ | [HTTP API](docs/api.md) | Server mode & SSE API |
615
+ | [Sessions](docs/sessions.md) | Session persistence & resume |
616
+ | [Skills](docs/skills.md) | SKILL.md instruction packs |
617
+ | [Testing](docs/testing.md) | Test suites & coverage |
618
+
619
+ ## License
620
+
621
+ MIT