s-app-scoder-agent 0.0.0-stage → 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sơn Nguyễn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,369 @@
1
- # Temporary Holding Version
1
+ # scoder — Autonomous Coding Agent CLI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![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)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+ [![node](https://img.shields.io/node/v/app-scoder-agent)](https://www.npmjs.com/package/app-scoder-agent)
6
+
7
+ **scoder** is a command-line coding agent that autonomously writes, edits, and builds code until your project compiles without errors. It mirrors the workflow of opencode, cline, and Claude Code, but runs as a single standalone CLI tool.
8
+
9
+ ## Installation
10
+
11
+ **Requirements:** Node.js ≥ 18
12
+
13
+ ### Install from GitHub (current method)
14
+
15
+ The package is not yet on the npm registry — install straight from this repo:
16
+
17
+ ```bash
18
+ # Clones, builds (via the prepare script), and installs the `scoder` command globally
19
+ npm install -g github:nbhson/app-scoder-agent
20
+ ```
21
+
22
+ Or manually from a local clone:
23
+
24
+ ```bash
25
+ git clone https://github.com/nbhson/app-scoder-agent.git
26
+ cd app-scoder-agent
27
+ npm install
28
+ npm run build
29
+ npm install -g . # or: npm link
30
+ ```
31
+
32
+ > Upgrading: pull the latest code and re-run `npm install -g .` (or point the global install at the repo again).
33
+
34
+ ### Try without installing
35
+
36
+ ```bash
37
+ npx github:nbhson/app-scoder-agent "your task here"
38
+ ```
39
+
40
+ ### From npm (planned)
41
+
42
+ Once published to the npm registry, installation will be a one-liner:
43
+
44
+ ```bash
45
+ npm install -g app-scoder-agent
46
+ # or run ad-hoc:
47
+ npx app-scoder-agent "your task"
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```bash
53
+ # Set your API key
54
+ export SCODER_API_KEY=sk-... # any OpenAI-compatible key (OpenAI, Groq, OpenRouter, Ollama, …)
55
+ export OPENAI_BASE_URL=https://api.openai.com/v1 # optional, for custom endpoints
56
+
57
+ # Run a task
58
+ scoder "add a ping function to src/network.ts"
59
+
60
+ # Interactive REPL
61
+ scoder
62
+ ```
63
+
64
+ ## How It Works
65
+
66
+ ```
67
+ You (task) → LLM plans → Tools execute code → Build runs → Success? → Done
68
+ ↑ |
69
+ └── Build failed? ─┘ (auto-loop)
70
+ ```
71
+
72
+ **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.
73
+
74
+ ## Tool Set
75
+
76
+ | Tool | Description |
77
+ |------|-------------|
78
+ | `read_file` | Read a file with line numbers |
79
+ | `write_file` | Overwrite/create a file |
80
+ | `edit_file` | Find-and-replace (unique match required) |
81
+ | `list_files` | Recursive directory listing |
82
+ | `run_command` | Execute any shell command (subject to permission policy) |
83
+
84
+ 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`.
85
+
86
+ ## Configuration
87
+
88
+ ### Environment variables
89
+
90
+ | Variable | Default | Description |
91
+ |----------|---------|-------------|
92
+ | `SCODER_API_KEY` | — | API key (required) |
93
+ | `SCODER_MODEL` | `gpt-4o-mini` | Model name |
94
+ | `SCODER_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible endpoint |
95
+ | `SCODER_BUILD_CMD` | auto-detected | Build/compile command |
96
+ | `SCODER_MAX_STEPS` | `40` | Max loop iterations before giving up |
97
+ | `SCODER_PERMISSION` | `auto` | Permission mode: `auto` \| `ask` \| `readonly` |
98
+ | `SCODER_STREAM` | `1` | `0` to disable streaming LLM output |
99
+ | `SCODER_NO_APIKEY_WARNING` | — | Set to `1` to suppress the apiKey-in-rc warning |
100
+
101
+ ### Per-project config file (.scoderrc.json)
102
+
103
+ 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`):
104
+
105
+ ```json
106
+ {
107
+ "baseUrl": "https://api.groq.com/openai/v1",
108
+ "apiKey": "gsk_...",
109
+ "model": "llama-3.3-70b-versatile"
110
+ }
111
+ ```
112
+
113
+ Then simply run `scoder "your task"` — values are picked up automatically.
114
+
115
+ #### All fields
116
+
117
+ | Field | Default | Description |
118
+ |---|---|---|
119
+ | `baseUrl` | `https://api.openai.com/v1` | OpenAI-compatible endpoint (must support `/chat/completions` with tool calling) |
120
+ | `apiKey` | — | API key (⚠️ prefer `SCODER_API_KEY` env var, see warning below) |
121
+ | `model` | `gpt-4o-mini` | Model name served by the endpoint |
122
+ | `buildCmd` | auto-detected | Build/compile command (stop condition) |
123
+ | `maxSteps` | `40` | Max autonomous loop iterations |
124
+ | `stream` | `true` | Stream LLM output token-by-token |
125
+ | `provider` | auto-detected | `openai` \| `anthropic` |
126
+ | `permission` | `auto` | `auto` \| `ask` \| `readonly` |
127
+ | `mcpServers` | — | MCP stdio servers (see below) |
128
+ | `permissions` | — | `{ mode, allowCommands, denyCommands }` |
129
+
130
+ #### Precedence
131
+
132
+ Values are merged from highest to lowest priority — your `.scoderrc.json` value only applies when no flag/env is set:
133
+
134
+ 1. **CLI flags** — `-m`, `--base-url`, `-k`, `--provider`, `-B`
135
+ 2. **Environment variables** — `SCODER_API_KEY` / `OPENAI_API_KEY`, `SCODER_BASE_URL`, `SCODER_MODEL`, …
136
+ 3. **`.scoderrc.json`** (or `.scoderrc`) in the project root — project-local only; there is no global rc file. For a machine-wide default, set the `SCODER_*` env vars in your shell profile instead
137
+ 4. Built-in defaults
138
+
139
+ > 💡 If your config in `.scoderrc.json` seems ignored, check `echo $SCODER_API_KEY` — env vars override the file.
140
+
141
+ #### Examples for common providers
142
+
143
+ ```jsonc
144
+ // Local Ollama
145
+ {
146
+ "baseUrl": "http://localhost:11434/v1",
147
+ "apiKey": "ollama",
148
+ "model": "qwen2.5-scoder:32b"
149
+ }
150
+ ```
151
+
152
+ ```jsonc
153
+ // DeepSeek
154
+ {
155
+ "baseUrl": "https://api.deepseek.com/v1",
156
+ "apiKey": "sk-...",
157
+ "model": "deepseek-chat"
158
+ }
159
+ ```
160
+
161
+ #### Notes
162
+
163
+ - Models starting with `claude-`, or a `baseUrl` containing `api.anthropic.com`, automatically switch to the native Anthropic provider — no `provider` field needed.
164
+ - If an `apiKey` is present in the rc file, scoder prints a warning on every run. Silence it with `SCODER_NO_APIKEY_WARNING=1`.
165
+
166
+ > ⚠️ **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`.
167
+
168
+ ## Permissions & Safety
169
+
170
+ scoder ships with a layered permission system for `run_command`:
171
+
172
+ | Mode | Behavior |
173
+ |------|----------|
174
+ | `auto` (default) | Autonomous — runs shell commands without confirmation (original behavior) |
175
+ | `ask` | Prompts before each shell command not covered by `allowCommands` |
176
+ | `readonly` | Shell commands are disabled entirely; the agent can only read/list files. MCP tools are also disabled (they bypass the permission policy) |
177
+
178
+ Layered checks, in order:
179
+
180
+ 1. **Catastrophic deny list** — `rm -rf /`, `dd of=/dev/...`, fork bombs, `curl | sh`, etc. are **always denied**, in every mode.
181
+ 2. **`denyCommands`** (config) — always denied.
182
+ 3. **Session allowlist** — commands you approved with `[a] always allow` in this session.
183
+ 4. **`allowCommands`** (config) — auto-approved.
184
+ 5. **Mode** — `auto` runs everything else; `ask` prompts; `readonly` denies.
185
+
186
+ 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.
187
+
188
+ ## MCP Servers (stdio)
189
+
190
+ scoder can use tools from [MCP](https://modelcontextprotocol.io) servers configured in `.scoderrc.json`:
191
+
192
+ ```json
193
+ {
194
+ "mcpServers": {
195
+ "filesystem": {
196
+ "command": "npx",
197
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
198
+ },
199
+ "github": {
200
+ "command": "npx",
201
+ "args": ["-y", "@modelcontextprotocol/server-github"],
202
+ "env": { "GITHUB_TOKEN": "{env:GITHUB_TOKEN}" }
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ - Tools are exposed to the LLM as `mcp__<server>__<tool>` (no name collisions with built-ins)
209
+ - Servers connect at agent start; a failing server only logs a warning — the agent keeps running with its built-in tools
210
+ - `{env:VAR}` substitution keeps secrets out of config files
211
+ - Built-in tools always take priority over MCP tools with colliding names
212
+ - ⚠️ **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.
213
+
214
+ ## Sessions
215
+
216
+ 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:
217
+
218
+ ```bash
219
+ scoder list # list saved sessions
220
+ scoder resume last # continue the most recent session
221
+ scoder resume <id> "follow-up" # resume + add a new instruction
222
+ ```
223
+
224
+ Sessions older than 7 days are pruned automatically on the next run. Use `--no-save` to disable (e.g. in CI).
225
+
226
+ ## Streaming
227
+
228
+ LLM output is streamed token-by-token by default (`SCODER_STREAM=0` to disable). If a provider doesn't support streaming, scoder automatically falls back to non-streaming requests.
229
+
230
+ ## Logging
231
+
232
+ Enable a structured JSONL event log with `--log-file` (or `SCODER_LOG_FILE=1`, or `SCODER_LOG_FILE=/path/to/log.jsonl`):
233
+
234
+ ```bash
235
+ scoder --log-file "fix the failing test"
236
+ cat .scoder/logs/<session-id>.jsonl | jq 'select(.type == "error")'
237
+ ```
238
+
239
+ Events: `step` (with token usage), `tool` (args, ok, elapsedMs, denied reason), `build`, `finish`. One JSON object per line — pipe to `jq` or any log consumer.
240
+
241
+ ## Reliability
242
+
243
+ scoder is designed for unattended use. Key guarantees:
244
+
245
+ - **Request timeouts** — LLM calls have a 120s timeout; on timeout, the agent retries or fails gracefully (never hangs indefinitely).
246
+ - **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`.
247
+ - **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.
248
+ - **Double-build prevention** — when the agent itself runs the build command, scoder reuses that result instead of running the build twice.
249
+ - **Build diff preview** — run `scoder --permission ask` to see a unified diff before every file write/edit.
250
+
251
+ > **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.
252
+
253
+ ### Auto-detected build commands
254
+
255
+ scoder detects the build command automatically:
256
+
257
+ | Project type | Detected command |
258
+ |---|---|
259
+ | `package.json` with `scripts.build` | `npm run build` |
260
+ | `Makefile` | `make` |
261
+ | `go.mod` | `go build ./...` |
262
+ | `Cargo.toml` | `cargo build` |
263
+ | `tsconfig.json` | `npx tsc --noEmit` |
264
+ | `pyproject.toml` | `python -m compileall .` |
265
+
266
+ ## CLI Options
267
+
268
+ ```
269
+ scoder [options] "<task>" Run a task autonomously until the build passes
270
+ scoder Start interactive session
271
+ scoder --help | --version Help and version
272
+ ```
273
+
274
+ | Flag | Description |
275
+ |------|-------------|
276
+ | `-m <model>` | Model to use |
277
+ | `--base-url <url>` | OpenAI-compatible API base URL |
278
+ | `-k <key>` | API key |
279
+ | `-B, --build <cmd>` | Build command (stop condition) |
280
+ | `-d, --dir <path>` | Working directory |
281
+ | `-s, --max-steps <n>` | Max iterations (default 40) |
282
+ | `--permission <mode>` | `auto` \| `ask` \| `readonly` shell-command policy (default `auto`) |
283
+ | `-y` | Non-interactive mode (denies commands needing confirmation) |
284
+ | `--provider <name>` | `openai` \| `anthropic` (env SCODER_PROVIDER; auto-detected) |
285
+ | `--no-save` | Disable session auto-save to `.scoder/sessions/` |
286
+
287
+ ## Examples
288
+
289
+ ```bash
290
+ # Run a task (auto-saves session to .scoder/sessions/)
291
+ scoder -B "npm test" "fix the null pointer in UserService"
292
+
293
+ # Resume after a crash or interrupt
294
+ scoder resume last "now add unit tests for the fix"
295
+
296
+ # Interactive mode with permission guard
297
+ scoder --permission ask "npm install express and wire up a route"
298
+
299
+ # Custom LLM (Groq, Ollama, vLLM, OpenRouter…)
300
+ scoder --base-url https://api.groq.com/openai/v1 -k $GROQ_KEY \
301
+ "add input validation to the CLI"
302
+
303
+ # Anthropic (auto-detected when model starts with 'claude-')
304
+ scoder -m claude-sonnet-4-5 "refactor the parser module"
305
+
306
+ # CI: non-interactive, no session saving
307
+ scoder --no-save -y "run the full migration"
308
+ ```
309
+
310
+ ## Architecture
311
+
312
+ ```
313
+ ┌─────────────────────────────────────────────┐
314
+ │ CLI (src/index.ts) │
315
+ │ arg parsing, REPL, SIGINT handling │
316
+ └─────────────────┬───────────────────────────┘
317
+ │
318
+ ┌─────────────────▼───────────────────────────┐
319
+ │ Agent (src/agent.ts) │
320
+ │ Closed-loop orchestrator │
321
+ │ history trimming, error injection │
322
+ └───────┬───────────────────────┬─────────────┘
323
+ │ │
324
+ ┌───────▼───────┐ ┌─────────▼──────────┐
325
+ │ LLM Client │ │ Tool Registry │
326
+ │ (src/llm.ts) │ │ (src/tools.ts) │
327
+ │ OpenAI compat │ │ read/write/run/list │
328
+ └───────────────┘ └──────────────────────┘
329
+ │
330
+ ▼
331
+ ┌─────────────────────────────────────────────┐
332
+ │ Build Checker (src/build.ts) │
333
+ │ run build → exit 0 = stop condition met │
334
+ └─────────────────────────────────────────────┘
335
+ ```
336
+
337
+ ## Integration Testing
338
+
339
+ ```bash
340
+ npm test # runs: lint → typecheck → build → 116 unit tests + e2e + MCP smoke
341
+ npm run lint # eslint flat config (typescript-eslint + eqeqeq, prefer-const...)
342
+ npm run typecheck # tsc --noEmit
343
+ ```
344
+
345
+ Test suites (run with `npm test` — auto-discovers `test/*.test.mjs`):
346
+ - `test/permissions.test.mjs` — catastrophic patterns, mode classification, allowlist word-boundary, timeout-denies
347
+ - `test/permission-prompt.test.mjs` — interactive prompt timeout behavior
348
+ - `test/tools.test.mjs` — safePath traversal, symlink escape, file-size cap, write/edit/list
349
+ - `test/core.test.mjs` — unified diff, retry backoff, context trimming
350
+ - `test/config.test.mjs` — env/rc/cli precedence, logFile mapping, permission lists
351
+ - `test/session.test.mjs` — session store, smart prune, resolve
352
+ - `test/anthropic.test.mjs` — message translation + native SSE streaming
353
+ - `test/llm.test.mjs` — OpenAI-compatible chat + SSE streaming + redaction
354
+ - `test/run-e2e.mjs` — closed-loop integration (mock LLM server)
355
+ - `test/run-mcp-test.mjs` — MCP smoke (mock stdio server)
356
+
357
+ Coverage (lines): **90.3%** overall — see `npm run coverage`.
358
+
359
+ ## Exit Codes
360
+
361
+ | Code | Meaning |
362
+ |------|---------|
363
+ | `0` | Task completed — build passed |
364
+ | `1` | Build failed after max steps |
365
+ | `2` | Fatal error (no API key, network error, etc.) |
366
+
367
+ ## License
368
+
369
+ MIT