s-app-scoder-agent 0.6.0 → 0.8.0
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.
- package/.scoderrc.example.json +97 -0
- package/LICENSE +21 -21
- package/README.md +600 -468
- package/dist/agent.js +841 -197
- package/dist/agents.js +70 -0
- package/dist/compaction.js +114 -0
- package/dist/config.js +178 -12
- package/dist/context.js +119 -14
- package/dist/cost.js +56 -6
- package/dist/guardrails.js +423 -0
- package/dist/index.js +129 -76
- package/dist/json-schema.js +58 -0
- package/dist/llm.js +123 -9
- package/dist/mcp.js +397 -70
- package/dist/model-catalog.js +175 -0
- package/dist/permissions.js +328 -35
- package/dist/prompt-builder.js +47 -0
- package/dist/prompt.js +103 -0
- package/dist/provider-anthropic.js +113 -35
- package/dist/provider-registry.js +116 -0
- package/dist/retry.js +3 -2
- package/dist/sandbox.js +96 -0
- package/dist/server.js +32 -2
- package/dist/session.js +282 -11
- package/dist/shell-scanner.js +112 -0
- package/dist/skills.js +122 -0
- package/dist/snapshot.js +170 -0
- package/dist/steering.js +35 -0
- package/dist/tools.js +513 -19
- package/dist/tui/app.js +505 -198
- package/dist/tui/at-complete.js +93 -0
- package/dist/tui/chat-views.js +146 -0
- package/dist/tui/chrome.js +96 -0
- package/dist/tui/command-registry.js +189 -0
- package/dist/tui/commands.js +200 -2
- package/dist/tui/image.js +149 -0
- package/dist/tui/input.js +214 -0
- package/dist/tui/markdown.js +229 -0
- package/dist/tui/mouse.js +20 -0
- package/dist/tui/permission-modal.js +34 -0
- package/dist/tui/review.js +64 -0
- package/dist/tui/session-picker.js +68 -0
- package/dist/tui/sidebar.js +99 -0
- package/dist/tui/state.js +192 -6
- package/dist/tui/theme.js +90 -0
- package/dist/tui/vim.js +60 -0
- package/dist/watch.js +52 -0
- package/package.json +16 -8
- package/dist/tui-direct.mjs +0 -4
package/README.md
CHANGED
|
@@ -1,468 +1,600 @@
|
|
|
1
|
-
# scoder — Autonomous Coding Agent CLI
|
|
2
|
-
|
|
3
|
-
[](https://github.com/nbhson/app-scoder-agent/actions/workflows/ci.yml)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://github.com/nbhson/app-scoder-agent/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://www.npmjs.com/package/s-app-scoder-agent)
|
|
7
|
+
|
|
8
|
+
**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.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
**Requirements:** Node.js ≥ 20
|
|
13
|
+
|
|
14
|
+
### Install from npm (Recommended)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g s-app-scoder-agent
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Install from GitHub
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g github:nbhson/app-scoder-agent
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### From local clone
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/nbhson/app-scoder-agent.git
|
|
30
|
+
cd app-scoder-agent
|
|
31
|
+
npm install && npm run build && npm install -g .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Try without installing
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx s-app-scoder-agent "your task here"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# One-time setup wizard — writes .scoderrc.json (base URL, key, model, build cmd)
|
|
44
|
+
scoder init
|
|
45
|
+
|
|
46
|
+
# …or set your API key manually
|
|
47
|
+
export SCODER_API_KEY=sk-... # any OpenAI-compatible key (OpenAI, Groq, OpenRouter, Ollama, …)
|
|
48
|
+
export OPENAI_BASE_URL=https://api.openai.com/v1 # optional, for custom endpoints
|
|
49
|
+
|
|
50
|
+
# Run a task
|
|
51
|
+
scoder "add a ping function to src/network.ts"
|
|
52
|
+
|
|
53
|
+
# Interactive REPL (supports /help, /clear, /cost, /model, /skills, /skill, /mcp, /review, /permission)
|
|
54
|
+
scoder
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## How It Works
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
You (task) → LLM plans → Tools execute code → Build runs → Success? → Done
|
|
61
|
+
↑ |
|
|
62
|
+
└── Build failed? ─┘ (auto-loop)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**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.
|
|
66
|
+
|
|
67
|
+
## Tool Set
|
|
68
|
+
|
|
69
|
+
| Tool | Description |
|
|
70
|
+
|------|-------------|
|
|
71
|
+
| `read_file` | Read a file with line numbers |
|
|
72
|
+
| `write_file` | Overwrite/create a file |
|
|
73
|
+
| `edit_file` | Find-and-replace (unique match required) |
|
|
74
|
+
| `list_files` | Recursive directory listing |
|
|
75
|
+
| `run_command` | Execute any shell command (subject to permission policy) |
|
|
76
|
+
| `web_fetch` | Fetch a public HTTP(S) URL → readable text (HTML→text, JSON pretty-printed) |
|
|
77
|
+
| `write_todos` | Live plan for multi-step tasks (checkbox list, kept current) |
|
|
78
|
+
| `load_skill` | Load a SKILL.md into context when the task matches its description |
|
|
79
|
+
| `mcp__<server>__<tool>` | Tools from configured MCP servers (routed transparently) |
|
|
80
|
+
|
|
81
|
+
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`.
|
|
82
|
+
|
|
83
|
+
### Web access (`web_fetch`)
|
|
84
|
+
|
|
85
|
+
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).
|
|
86
|
+
|
|
87
|
+
Safety defaults:
|
|
88
|
+
|
|
89
|
+
- **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.
|
|
90
|
+
- Only `http`/`https`; up to 5 redirects — every hop is re-validated against the private-address block list.
|
|
91
|
+
- 30 s timeout, SIGINT-abortable; body capped at 512 KB; extracted text capped at 20k chars (configurable via `max_chars`).
|
|
92
|
+
- HTML is converted to plain text (no JS execution); images/archives/PDFs are refused.
|
|
93
|
+
- Read-only — the tool never writes locally, so it is allowed even in `readonly` permission mode.
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
### Environment variables
|
|
98
|
+
|
|
99
|
+
| Variable | Default | Description |
|
|
100
|
+
|----------|---------|-------------|
|
|
101
|
+
| `SCODER_API_KEY` | — | API key (required) |
|
|
102
|
+
| `SCODER_MODEL` | `gpt-4o-mini` | Model name |
|
|
103
|
+
| `SCODER_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible endpoint |
|
|
104
|
+
| `SCODER_BUILD_CMD` | auto-detected | Build/compile command |
|
|
105
|
+
| `SCODER_MAX_STEPS` | `40` | Max loop iterations before giving up |
|
|
106
|
+
| `SCODER_PERMISSION` | `auto` | Permission mode: `auto` \| `ask` \| `readonly` |
|
|
107
|
+
| `SCODER_STREAM` | `1` | `0` to disable streaming LLM output |
|
|
108
|
+
| `SCODER_STREAM` | `1` | `0` to disable streaming LLM output |
|
|
109
|
+
| `SCODER_SERVER_TOKEN` | — | `scoder serve` only: require `Authorization: Bearer <token>` |
|
|
110
|
+
| `SCODER_WEB_ALLOW_PRIVATE` | — | Set to `1` to let `web_fetch` reach localhost/private addresses (local dev) |
|
|
111
|
+
| `SCODER_NO_APIKEY_WARNING` | — | Set to `1` to suppress the apiKey-in-rc warning |
|
|
112
|
+
|
|
113
|
+
### Per-project config file (.scoderrc.json)
|
|
114
|
+
|
|
115
|
+
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`):
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"baseUrl": "https://api.groq.com/openai/v1",
|
|
120
|
+
"apiKey": "gsk_...",
|
|
121
|
+
"model": "llama-3.3-70b-versatile"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Then simply run `scoder "your task"` — values are picked up automatically.
|
|
126
|
+
|
|
127
|
+
### Global config (~/.scoder/config.json)
|
|
128
|
+
|
|
129
|
+
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.
|
|
130
|
+
|
|
131
|
+
#### All fields
|
|
132
|
+
|
|
133
|
+
| Field | Default | Description |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `baseUrl` | `https://api.openai.com/v1` | OpenAI-compatible endpoint (must support `/chat/completions` with tool calling) |
|
|
136
|
+
| `apiKey` | — | API key (⚠️ prefer `SCODER_API_KEY` env var, see warning below) |
|
|
137
|
+
| `model` | `gpt-4o-mini` | Model name served by the endpoint |
|
|
138
|
+
| `buildCmd` | auto-detected | Build/compile command (stop condition) |
|
|
139
|
+
| `maxSteps` | `40` | Max autonomous loop iterations |
|
|
140
|
+
| `stream` | `true` | Stream LLM output token-by-token |
|
|
141
|
+
| `provider` | auto-detected | `openai` \| `anthropic` |
|
|
142
|
+
| `permission` | `auto` | `auto` \| `ask` \| `readonly` |
|
|
143
|
+
| `mcpServers` | — | MCP servers — stdio (`command`) hoặc HTTP (`url`) (see below) |
|
|
144
|
+
| `permissions` | — | `{ mode, allowCommands, denyCommands }` |
|
|
145
|
+
|
|
146
|
+
#### Precedence
|
|
147
|
+
|
|
148
|
+
Values are merged from highest to lowest priority — your `.scoderrc.json` value only applies when no flag/env is set:
|
|
149
|
+
|
|
150
|
+
1. **CLI flags** — `-m`, `--base-url`, `-k`, `--provider`, `-B`
|
|
151
|
+
2. **Environment variables** — `SCODER_API_KEY` / `OPENAI_API_KEY`, `SCODER_BASE_URL`, `SCODER_MODEL`, …
|
|
152
|
+
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
|
|
153
|
+
4. Built-in defaults
|
|
154
|
+
|
|
155
|
+
> 💡 If your config in `.scoderrc.json` seems ignored, check `echo $SCODER_API_KEY` — env vars override the file.
|
|
156
|
+
|
|
157
|
+
#### Examples for common providers
|
|
158
|
+
|
|
159
|
+
```jsonc
|
|
160
|
+
// Local Ollama
|
|
161
|
+
{
|
|
162
|
+
"baseUrl": "http://localhost:11434/v1",
|
|
163
|
+
"apiKey": "ollama",
|
|
164
|
+
"model": "qwen2.5-scoder:32b"
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```jsonc
|
|
169
|
+
// DeepSeek
|
|
170
|
+
{
|
|
171
|
+
"baseUrl": "https://api.deepseek.com/v1",
|
|
172
|
+
"apiKey": "sk-...",
|
|
173
|
+
"model": "deepseek-chat"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Notes
|
|
178
|
+
|
|
179
|
+
- Models starting with `claude-`, or a `baseUrl` containing `api.anthropic.com`, automatically switch to the native Anthropic provider — no `provider` field needed.
|
|
180
|
+
- If an `apiKey` is present in the rc file, scoder prints a warning on every run. Silence it with `SCODER_NO_APIKEY_WARNING=1`.
|
|
181
|
+
|
|
182
|
+
> ⚠️ **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`.
|
|
183
|
+
|
|
184
|
+
## Permissions & Safety
|
|
185
|
+
|
|
186
|
+
scoder ships with a layered permission system for `run_command`:
|
|
187
|
+
|
|
188
|
+
| Mode | Behavior |
|
|
189
|
+
|------|----------|
|
|
190
|
+
| `auto` (default) | Autonomous — runs shell commands without confirmation (original behavior) |
|
|
191
|
+
| `ask` | Prompts before each shell command not covered by `allowCommands` |
|
|
192
|
+
| `readonly` | Shell commands are disabled entirely; the agent can only read/list files. MCP tools are also disabled (they bypass the permission policy) |
|
|
193
|
+
|
|
194
|
+
Layered checks, in order:
|
|
195
|
+
|
|
196
|
+
1. **Catastrophic deny list** — `rm -rf /`, `dd of=/dev/...`, fork bombs, `curl | sh`, etc. are **always denied**, in every mode.
|
|
197
|
+
2. **`denyCommands`** (config) — always denied.
|
|
198
|
+
3. **Session allowlist** — commands you approved with `[a] always allow` in this session.
|
|
199
|
+
4. **`allowCommands`** (config) — auto-approved.
|
|
200
|
+
5. **Mode** — `auto` runs everything else; `ask` prompts; `readonly` denies.
|
|
201
|
+
|
|
202
|
+
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.
|
|
203
|
+
|
|
204
|
+
## Skills (SKILL.md)
|
|
205
|
+
|
|
206
|
+
scoder can load reusable instruction packs ("skills") into the conversation. A skill is a directory with a `SKILL.md` (YAML-lite frontmatter + markdown body):
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
.scoder/skills/code-review/SKILL.md # project-level
|
|
210
|
+
~/.scoder/skills/code-review/SKILL.md # global (project overrides on name clash)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```markdown
|
|
214
|
+
---
|
|
215
|
+
name: code-review
|
|
216
|
+
description: Review code for bugs, security issues, and style
|
|
217
|
+
allowed-tools: read_file, grep
|
|
218
|
+
license: MIT
|
|
219
|
+
when-to-use: Run after any multi-file change before committing
|
|
220
|
+
---
|
|
221
|
+
Read the current diff. Check for: null derefs, injection risks, ...
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
- **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
|
|
225
|
+
- **Explicit load** — `/skill <name>` stages the skill's content; it is injected as context before your next task
|
|
226
|
+
- **`/skills`** lists everything discovered (both scopes)
|
|
227
|
+
- **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`
|
|
228
|
+
- **Validation warnings** — duplicate names across scopes warn (project wins); missing `description` skips the skill; warnings surface in the log/notice stream
|
|
229
|
+
- **Hot-reload** — `.scoderrc` and `.scoder/skills/` are watched (debounced); a skill added mid-session is advertised on the next turn, no restart needed
|
|
230
|
+
|
|
231
|
+
## Slash commands (REPL + TUI)
|
|
232
|
+
|
|
233
|
+
| Command | Description |
|
|
234
|
+
|---|---|
|
|
235
|
+
| `/help` | command list |
|
|
236
|
+
| `/clear` | reset conversation + agent context |
|
|
237
|
+
| `/cost` | session token usage |
|
|
238
|
+
| `/model` | active model/provider/build |
|
|
239
|
+
| `/skills` | list discovered skills |
|
|
240
|
+
| `/skill <name>` | stage a skill for the next task |
|
|
241
|
+
| `/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 |
|
|
242
|
+
| `/review <prompt>` | Prompt gate (prompt-gate MCP): LLM rewrites your prompt, you pick original/rewritten/update/cancel — only the approved prompt is used |
|
|
243
|
+
| `/permission <auto\|readonly>` | switch permission mode at runtime |
|
|
244
|
+
| `/exit` | quit |
|
|
245
|
+
|
|
246
|
+
In the TUI, **Tab** completes commands (and `/skill` names, `/mcp <server> <tool>` arguments), **↑/↓** recall sent-message history.
|
|
247
|
+
|
|
248
|
+
### @file mentions
|
|
249
|
+
|
|
250
|
+
Prefix any path with `@` in your task to inline its content into the agent's context:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
scoder "fix the bug in @src/agent.ts"
|
|
254
|
+
scoder "refactor the handlers in @src/handlers/ and update @README.md"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
- `@src/file.ts` — file content is injected into the task; `@"path with spaces.ts"` for paths containing spaces
|
|
258
|
+
- `@src/dir/` — directory listing injected instead
|
|
259
|
+
- 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
|
|
260
|
+
|
|
261
|
+
## Planning tool
|
|
262
|
+
|
|
263
|
+
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.
|
|
264
|
+
|
|
265
|
+
## Guardrails (Guardrail Engine)
|
|
266
|
+
|
|
267
|
+
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:
|
|
268
|
+
|
|
269
|
+
| Rule | What it does |
|
|
270
|
+
|---|---|
|
|
271
|
+
| `wrap-up` | Injects a converge-now reminder 2 steps before the budget ends |
|
|
272
|
+
| `chat-done` | Ends conversational turns (marker-based) without a build |
|
|
273
|
+
| `completion-claim` | Never trusts "the task is complete" — forces an independent build verification |
|
|
274
|
+
| `text-only-streak` | Ends the run after 2 consecutive tool-less steps with no file changes (the "chào" failure mode) |
|
|
275
|
+
| `continue-nudge` | Nudges plain-text answers back to work (no build demand when no files changed — suggests the `<<CHAT_DONE>>` exit instead) |
|
|
276
|
+
| `doom-loop` | Intervenes after 3× identical consecutive tool-call batches |
|
|
277
|
+
| `build-stop` | Ends the run when the exact build command succeeds; re-verifies on failure |
|
|
278
|
+
| `history-trim` | Char-budget history trimming with whole-turn drops + old-tool-result pruning |
|
|
279
|
+
|
|
280
|
+
Thresholds are configurable via `GuardrailOptions` (`textOnlyStreakLimit`, `doomLoopRepeats`, `wrapUpStepsBeforeEnd`) without touching the loop, and custom rules can be added via `GuardrailEngine.register()`.
|
|
281
|
+
|
|
282
|
+
## Usage & cost metrics
|
|
283
|
+
|
|
284
|
+
`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**:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
20261031-110353-d034 calls=1 in=4,042 out=88 cost≈$0.0022 | steps=1 llm-avg=7.5s tools=1 guardrails=1 ✓success
|
|
288
|
+
TOTAL (1 session(s)) ... | success-rate=1/1
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
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.
|
|
292
|
+
|
|
293
|
+
## MCP Servers (stdio / HTTP)
|
|
294
|
+
|
|
295
|
+
scoder can use tools from [MCP](https://modelcontextprotocol.io) servers configured in `.scoderrc.json`:
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"mcpServers": {
|
|
300
|
+
"filesystem": {
|
|
301
|
+
"command": "npx",
|
|
302
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
303
|
+
},
|
|
304
|
+
"github": {
|
|
305
|
+
"command": "npx",
|
|
306
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
307
|
+
"env": { "GITHUB_TOKEN": "{env:GITHUB_TOKEN}" }
|
|
308
|
+
},
|
|
309
|
+
"remote": {
|
|
310
|
+
"url": "https://mcp.example.com/mcp",
|
|
311
|
+
"headers": { "authorization": "Bearer {env:MCP_TOKEN}" }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
- Tools are exposed to the LLM as `mcp__<server>__<tool>` (no name collisions with built-ins)
|
|
318
|
+
- Servers connect at agent start; a failing server only logs a warning — the agent keeps running with its built-in tools
|
|
319
|
+
- `{env:VAR}` substitution keeps secrets out of config files (both `env` and `headers`)
|
|
320
|
+
- **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"`
|
|
321
|
+
- **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
|
|
322
|
+
- Built-in tools always take priority over MCP tools with colliding names
|
|
323
|
+
- ⚠️ **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.
|
|
324
|
+
|
|
325
|
+
### Prompt gate (`/review`)
|
|
326
|
+
|
|
327
|
+
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`:
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
/review phân tích project
|
|
331
|
+
→ [1] light [2] balanced [3] detailed ← you pick the rewrite level
|
|
332
|
+
→ ORIGINAL vs REWRITTEN shown side by side
|
|
333
|
+
→ [1] original [2] rewritten [3] update <changes> [4] cancel
|
|
334
|
+
→ ✔ prompt approved: <the exact text you approved>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
- **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
|
|
338
|
+
- `[3] update` loops: describe changes, the prompt is rewritten again, review repeats until you pick original/rewritten/cancel
|
|
339
|
+
- Cancel approves nothing; LLM errors fail loudly (no silent fallback to the original)
|
|
340
|
+
|
|
341
|
+
## Sessions
|
|
342
|
+
|
|
343
|
+
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:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
scoder list # list saved sessions
|
|
347
|
+
scoder resume last # continue the most recent session
|
|
348
|
+
scoder resume <id> "follow-up" # resume + add a new instruction
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Sessions older than 7 days are pruned automatically on the next run. Use `--no-save` to disable (e.g. in CI).
|
|
352
|
+
|
|
353
|
+
## Streaming
|
|
354
|
+
|
|
355
|
+
scoder streams everything it can, live, as it happens:
|
|
356
|
+
|
|
357
|
+
- **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`.
|
|
358
|
+
- **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.
|
|
359
|
+
- **SIGINT-safe** — Ctrl+C kills the running command immediately; streamed rendering never blocks the event loop.
|
|
360
|
+
- Output honors `NO_COLOR` and degrades to plain text when piped (CI-friendly).
|
|
361
|
+
|
|
362
|
+
## Full-Screen TUI (`scoder tui`)
|
|
363
|
+
|
|
364
|
+
**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:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
scoder tui # start an interactive full-screen session
|
|
368
|
+
scoder tui "fix the bug" # auto-submit an initial task
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Features:
|
|
372
|
+
|
|
373
|
+
- **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.
|
|
374
|
+
- **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`)
|
|
375
|
+
- **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)
|
|
376
|
+
- **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▸`)
|
|
377
|
+
- **Header row 2** — (no longer present; phase breadcrumb + metrics moved into the sidebar / status bar to avoid duplication)
|
|
378
|
+
- **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
|
|
379
|
+
- **Status bar** — model, token usage (input/output/cached), scroll position, keybind hints
|
|
380
|
+
- **Slash commands** — `/help`, `/clear`, `/cost`, `/model`, `/skills`, `/skill`, `/mcp`, `/review`, `/permission`, `/exit` (identical to the REPL)
|
|
381
|
+
- **Multi-turn** — keep typing follow-ups after a task finishes; the conversation context carries over (same as the REPL)
|
|
382
|
+
- **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
|
|
383
|
+
- **Keybinds** — `Enter` send · `PgUp`/`PgDn` scroll · `Esc` reset scroll / abort · `Ctrl+C` quit
|
|
384
|
+
- Non-TTY environments (CI, pipes) automatically fall back to the classic REPL
|
|
385
|
+
- `permission: "ask"` is not supported in the TUI (no readline prompts on the alt screen) — it falls back to `auto`
|
|
386
|
+
|
|
387
|
+
## HTTP Server Mode (`scoder serve`)
|
|
388
|
+
|
|
389
|
+
Run scoder as a local HTTP service — submit tasks programmatically and consume progress as Server-Sent Events:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
scoder serve # http://127.0.0.1:0 (random port, printed at startup)
|
|
393
|
+
scoder serve --port 4114 # fixed port
|
|
394
|
+
SCODER_SERVER_TOKEN=secret scoder serve --host 127.0.0.1
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
| Endpoint | Method | Description |
|
|
398
|
+
|----------|--------|-------------|
|
|
399
|
+
| `/api/health` | GET | Liveness: `{ ok, running, runs }` |
|
|
400
|
+
| `/api/runs` | POST | Start a run: `{ "task": "...", "workdir?", "buildCmd?", "model?", "provider?", "maxSteps?", "permission?" }` → `202 { id, eventsUrl }` |
|
|
401
|
+
| `/api/runs` | GET | List runs with status/timing |
|
|
402
|
+
| `/api/runs/<id>` | GET | Run status + final result |
|
|
403
|
+
| `/api/runs/<id>/events` | GET | **SSE stream** — full event replay, then live, closes on finish |
|
|
404
|
+
| `/api/runs/<id>/abort` | POST | Abort a running run (equivalent to SIGINT) |
|
|
405
|
+
|
|
406
|
+
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.
|
|
407
|
+
|
|
408
|
+
Notes:
|
|
409
|
+
|
|
410
|
+
- Runs are always non-interactive; `permission: "ask"` is rejected — use `auto` or `readonly`.
|
|
411
|
+
- Binds `127.0.0.1` by default. Set `SCODER_SERVER_TOKEN` to require `Authorization: Bearer <token>` on every request.
|
|
412
|
+
- Max 4 concurrent runs (extra requests get `429`); per-run overrides: `workdir`, `buildCmd`, `model`, `provider`, `maxSteps`, `permission`.
|
|
413
|
+
|
|
414
|
+
## Logging
|
|
415
|
+
|
|
416
|
+
Enable a structured JSONL event log with `--log-file` (or `SCODER_LOG_FILE=1`, or `SCODER_LOG_FILE=/path/to/log.jsonl`):
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
scoder --log-file "fix the failing test"
|
|
420
|
+
cat .scoder/logs/<session-id>.jsonl | jq 'select(.type == "error")'
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
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.
|
|
424
|
+
|
|
425
|
+
## Reliability
|
|
426
|
+
|
|
427
|
+
scoder is designed for unattended use. Key guarantees:
|
|
428
|
+
|
|
429
|
+
- **Request timeouts** — LLM calls have a 120s timeout; on timeout, the agent retries or fails gracefully (never hangs indefinitely).
|
|
430
|
+
- **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`.
|
|
431
|
+
- **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.
|
|
432
|
+
- **Double-build prevention** — when the agent itself runs the build command, scoder reuses that result instead of running the build twice.
|
|
433
|
+
- **Build diff preview** — run `scoder --permission ask` to see a unified diff before every file write/edit.
|
|
434
|
+
|
|
435
|
+
> **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.
|
|
436
|
+
|
|
437
|
+
### Auto-detected build commands
|
|
438
|
+
|
|
439
|
+
scoder detects the build command automatically:
|
|
440
|
+
|
|
441
|
+
| Project type | Detected command |
|
|
442
|
+
|---|---|
|
|
443
|
+
| `package.json` with `scripts.build` | `npm run build` |
|
|
444
|
+
| `Makefile` | `make` |
|
|
445
|
+
| `go.mod` | `go build ./...` |
|
|
446
|
+
| `Cargo.toml` | `cargo build` |
|
|
447
|
+
| `tsconfig.json` | `npx tsc --noEmit` |
|
|
448
|
+
| `pyproject.toml` | `python -m compileall .` |
|
|
449
|
+
|
|
450
|
+
## CLI Options
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
scoder [options] "<task>" Run a task autonomously until the build passes
|
|
454
|
+
scoder Start interactive session
|
|
455
|
+
scoder init Setup wizard — writes .scoderrc.json (+ connection test)
|
|
456
|
+
scoder tui [task] Full-screen terminal UI (Mission Control)
|
|
457
|
+
scoder serve [--port] [--host] HTTP server mode (see above)
|
|
458
|
+
scoder --help | --version Help and version
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
| Flag | Description |
|
|
462
|
+
|------|-------------|
|
|
463
|
+
| `-m <model>` | Model to use |
|
|
464
|
+
| `--base-url <url>` | OpenAI-compatible API base URL |
|
|
465
|
+
| `-k <key>` | API key |
|
|
466
|
+
| `-B, --build <cmd>` | Build command (stop condition) |
|
|
467
|
+
| `-d, --dir <path>` | Working directory |
|
|
468
|
+
| `-s, --max-steps <n>` | Max iterations (default 40) |
|
|
469
|
+
| `--permission <mode>` | `auto` \| `ask` \| `readonly` shell-command policy (default `auto`) |
|
|
470
|
+
| `-y` | Non-interactive mode (denies commands needing confirmation) |
|
|
471
|
+
| `--provider <name>` | `openai` \| `anthropic` (env SCODER_PROVIDER; auto-detected) |
|
|
472
|
+
| `--no-save` | Disable session auto-save to `.scoder/sessions/` |
|
|
473
|
+
| `--log-file` | Write a structured JSONL event log to `.scoder/logs/` |
|
|
474
|
+
| `--port <n>` | (`serve`) TCP port, `0` = random free port |
|
|
475
|
+
| `--host <addr>` | (`serve`) Bind address (default `127.0.0.1`) |
|
|
476
|
+
|
|
477
|
+
## Examples
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
# Run a task (auto-saves session to .scoder/sessions/)
|
|
481
|
+
scoder -B "npm test" "fix the null pointer in UserService"
|
|
482
|
+
|
|
483
|
+
# Resume after a crash or interrupt
|
|
484
|
+
scoder resume last "now add unit tests for the fix"
|
|
485
|
+
|
|
486
|
+
# Interactive mode with permission guard
|
|
487
|
+
scoder --permission ask "npm install express and wire up a route"
|
|
488
|
+
|
|
489
|
+
# Custom LLM (Groq, Ollama, vLLM, OpenRouter…)
|
|
490
|
+
scoder --base-url https://api.groq.com/openai/v1 -k $GROQ_KEY \
|
|
491
|
+
"add input validation to the CLI"
|
|
492
|
+
|
|
493
|
+
# Anthropic (auto-detected when model starts with 'claude-')
|
|
494
|
+
scoder -m claude-sonnet-4-5 "refactor the parser module"
|
|
495
|
+
|
|
496
|
+
# CI: non-interactive, no session saving
|
|
497
|
+
scoder --no-save -y "run the full migration"
|
|
498
|
+
|
|
499
|
+
# Run as an HTTP service and consume progress via SSE
|
|
500
|
+
scoder serve --port 4114 &
|
|
501
|
+
curl -s -X POST localhost:4114/api/runs -H 'Content-Type: application/json' \
|
|
502
|
+
-d '{"task": "add input validation to the CLI"}'
|
|
503
|
+
curl -N http://127.0.0.1:4114/api/runs/<id>/events # live progress
|
|
504
|
+
|
|
505
|
+
# Fetch external docs during a task (agent decides when, via web_fetch)
|
|
506
|
+
scoder "upgrade express to v5 — check the official migration guide first"
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Architecture
|
|
510
|
+
|
|
511
|
+
```
|
|
512
|
+
┌─────────────────────────────────────────────┐
|
|
513
|
+
│ CLI (src/index.ts) │
|
|
514
|
+
│ arg parsing, REPL, serve, tui, SIGINT │
|
|
515
|
+
└───────┬─────────────────────┬───────────────┘
|
|
516
|
+
│ │
|
|
517
|
+
┌───────▼───────────┐ ┌──────▼─────────────────────┐
|
|
518
|
+
│ TUI (src/tui/) │ │ Agent (src/agent.ts) │
|
|
519
|
+
│ Ink/React alt- │ │ Closed-loop orchestrator │
|
|
520
|
+
│ screen UI, │─▶│ + Guardrail Engine │
|
|
521
|
+
│ renders the same │ │ (src/guardrails.ts) │
|
|
522
|
+
│ AgentEvent stream│ │ emits AgentEvent │
|
|
523
|
+
└───────────────────┘ └──┬──────────┬──────────────┘
|
|
524
|
+
│ │
|
|
525
|
+
│ └────────────────────┐
|
|
526
|
+
▼ ▼
|
|
527
|
+
┌───────────────────────────────┐ ┌──────────────────────────┐
|
|
528
|
+
│ LLM Client (src/llm.ts) │ │ Server (src/server.ts) │
|
|
529
|
+
│ OpenAI-compatible + native │ │ HTTP API + SSE │
|
|
530
|
+
│ Anthropic provider │ │ runs in background │
|
|
531
|
+
└──────────────┬────────────────┘ └──────────────────────────┘
|
|
532
|
+
▼
|
|
533
|
+
┌────────────────────────────────────────┐
|
|
534
|
+
│ Tool Registry (src/tools.ts) │
|
|
535
|
+
│ + web_fetch (src/webfetch.ts) │
|
|
536
|
+
└──────────────┬─────────────────────────┘
|
|
537
|
+
▼
|
|
538
|
+
┌───────────────────────────────┐
|
|
539
|
+
│ Build Checker (src/build.ts) │
|
|
540
|
+
│ run build → exit 0 = stop │
|
|
541
|
+
└───────────────────────────────┘
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Integration Testing
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
npm test # runs: build → unit tests (node --test) → e2e closed-loop → MCP smoke
|
|
548
|
+
npm run lint # eslint flat config (typescript-eslint + eqeqeq, prefer-const...)
|
|
549
|
+
npm run typecheck # tsc --noEmit
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Test suites (run with `npm test` — auto-discovers `test/*.test.mjs`):
|
|
553
|
+
- `test/permissions.test.mjs` — catastrophic patterns, mode classification, allowlist word-boundary, timeout-denies
|
|
554
|
+
- `test/permission-prompt.test.mjs` — interactive prompt timeout behavior
|
|
555
|
+
- `test/tools.test.mjs` — safePath traversal, symlink escape, file-size cap, write/edit/list, live `run_command` output streaming
|
|
556
|
+
- `test/webfetch.test.mjs` — HTML→text, JSON, redirects, SSRF guard, binary refusal, truncation
|
|
557
|
+
- `test/server.test.mjs` — HTTP API: run lifecycle via SSE, abort, auth, live `tool_output` events
|
|
558
|
+
- `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
|
|
559
|
+
- `test/tui-state.test.mjs` — event reducer unit tests: diff items, build lamp, abort marking, slash-command parsing & help formatting
|
|
560
|
+
- `test/review-flow.test.mjs` — `/review` prompt-gate wiring: both pending stages, approved/cancelled outcomes, verbatim `rewritten_prompt` echo (security)
|
|
561
|
+
- `test/core.test.mjs` — unified diff, retry backoff, context trimming, @file mention expansion
|
|
562
|
+
- `test/config.test.mjs` — env/rc/cli precedence, logFile mapping, permission lists
|
|
563
|
+
- `test/session.test.mjs` — session store, smart prune, resolve
|
|
564
|
+
- `test/cost.test.mjs` — token usage & cost aggregation, session metrics (latency/success/guardrails)
|
|
565
|
+
- `test/guardrails.test.mjs` — Guardrail Engine: rule registry, default rules, configurable thresholds, decision logging, priority order
|
|
566
|
+
- `test/prompt.test.mjs` — system prompt versioning (`PROMPT_VERSION`) + dynamic assembly
|
|
567
|
+
- `test/anthropic.test.mjs` — message translation + native SSE streaming
|
|
568
|
+
- `test/llm.test.mjs` — OpenAI-compatible chat + SSE streaming + redaction + wire-format normalization
|
|
569
|
+
- `test/run-e2e.mjs` — closed-loop integration (mock LLM server)
|
|
570
|
+
- `test/run-mcp-test.mjs` — MCP smoke (mock stdio server)
|
|
571
|
+
|
|
572
|
+
## Exit Codes
|
|
573
|
+
|
|
574
|
+
| Code | Meaning |
|
|
575
|
+
|------|---------|
|
|
576
|
+
| `0` | Task completed — build passed |
|
|
577
|
+
| `1` | Build failed after max steps |
|
|
578
|
+
| `2` | Fatal error (no API key, network error, etc.) |
|
|
579
|
+
|
|
580
|
+
## Documentation
|
|
581
|
+
|
|
582
|
+
See [docs/](docs/) for detailed guides:
|
|
583
|
+
|
|
584
|
+
| Doc | Description |
|
|
585
|
+
|-----|-------------|
|
|
586
|
+
| [Installation](docs/installation.md) | Install from npm, GitHub, or local clone |
|
|
587
|
+
| [Configuration](docs/configuration.md) | Environment variables, config file, permissions |
|
|
588
|
+
| [Commands](docs/commands.md) | CLI commands & flags reference |
|
|
589
|
+
| [Architecture](docs/architecture.md) | System design & module overview |
|
|
590
|
+
| [TUI Guide](docs/tui.md) | Full-screen terminal UI |
|
|
591
|
+
| [Guardrails](docs/guardrails.md) | Safety & progress engine |
|
|
592
|
+
| [MCP Integration](docs/mcp.md) | Model Context Protocol servers |
|
|
593
|
+
| [HTTP API](docs/api.md) | Server mode & SSE API |
|
|
594
|
+
| [Sessions](docs/sessions.md) | Session persistence & resume |
|
|
595
|
+
| [Skills](docs/skills.md) | SKILL.md instruction packs |
|
|
596
|
+
| [Testing](docs/testing.md) | Test suites & coverage |
|
|
597
|
+
|
|
598
|
+
## License
|
|
599
|
+
|
|
600
|
+
MIT
|