wrosecode 0.0.0-stage → 1.0.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 WROSECODE contributors
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,693 @@
1
- # Temporary Holding Version
1
+ # WROSECODE
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
+ A terminal coding agent written in Rust. Build with `cargo install --path .` or `cargo build --release`. Run `wrosecode` from a project directory, or `wrosecode 'your task'` for one turn. Set `ANTHROPIC_API_KEY` for the Anthropic provider. For an OpenAI-compatible server, set `WROSECODE_OPENAI_URL` and run `wrosecode --provider openai-compat --model MODEL`.
4
+
5
+ ## Install and run
6
+
7
+ ```bash
8
+ cargo build --release
9
+ install -Dm755 target/release/wrosecode "$HOME/.local/bin/wrosecode"
10
+ wrosecode --permission yolo
11
+ ```
12
+
13
+ For a fully static Linux binary with no glibc dependency, run
14
+ `./scripts/build-musl.sh` (it needs `rustup target add
15
+ x86_64-unknown-linux-musl` and `musl-tools` for `musl-gcc`) and install
16
+ `target/x86_64-unknown-linux-musl/release/wrosecode` instead.
17
+
18
+ ## Install from npm
19
+
20
+ Install globally:
21
+
22
+ ```bash
23
+ npm install -g wrosecode
24
+ ```
25
+
26
+ Run the CLI:
27
+
28
+ ```bash
29
+ wrosecode
30
+ ```
31
+
32
+ Run without a permanent installation:
33
+
34
+ ```bash
35
+ npx wrosecode
36
+ ```
37
+
38
+ Update:
39
+
40
+ ```bash
41
+ npm install -g wrosecode@latest
42
+ ```
43
+
44
+ Uninstall:
45
+
46
+ ```bash
47
+ npm uninstall -g wrosecode
48
+ ```
49
+
50
+ The npm package is a small launcher; it downloads the matching native binary
51
+ from the [GitHub Releases](https://github.com/whiterose717/WROSECODE-CLI/releases)
52
+ for this package version and verifies the binary against its SHA-256 sidecar
53
+ before installation. Supported targets are Linux x86_64/aarch64, macOS
54
+ x86_64/aarch64, and Windows x86_64/aarch64. Node.js 18 or newer is required
55
+ for the npm installer; Rust and Cargo are not required for npm users.
56
+
57
+ Source: <https://github.com/whiterose717/WROSECODE-CLI>
58
+
59
+ ### Maintainer release process
60
+
61
+ Push a version-matched tag such as `v1.0.0`. The GitHub Actions release
62
+ workflow builds all six supported native targets, creates a SHA-256 sidecar
63
+ for each binary, validates the full set, then creates or updates the GitHub
64
+ Release. Publish to npm only after that workflow succeeds and all twelve
65
+ assets are visible on the release page.
66
+
67
+ Rust was chosen for low input latency, predictable memory use, a single native binary, and direct terminal control. The architecture combines a differential TUI, streamed provider adapters, durable sessions, MCP tools, repository maps, skills, permission tiers, and parallel tool execution.
68
+
69
+ ## Quick start
70
+
71
+ ```bash
72
+ export ANTHROPIC_API_KEY=sk-ant-... # or a local server: WROSECODE_OPENAI_URL + --provider openai-compat
73
+ wrosecode # interactive session in the current directory
74
+ wrosecode 'inventory the attack surface' # one turn, then exit
75
+ wrosecode --permission yolo # skip approval prompts (see Permissions)
76
+ ```
77
+
78
+ Worth touching in the first session:
79
+
80
+ ```text
81
+ /ctf point the agent at a challenge file, URL, or CTFd task
82
+ /providers add, key, and health-check models without editing files
83
+ Ctrl+T cycle thinking levels (off → max) Ctrl+D eight-panel dashboard
84
+ /help every command, every keybinding
85
+ /resume pick a past session (the start screen lists the three most recent)
86
+ ```
87
+
88
+ Sessions persist under `~/.wrosecode/sessions`; model profiles and keys live
89
+ in `~/.wrosecode/providers.toml` (keys display as `••••last4` and are typed
90
+ through a masked prompt).
91
+
92
+ ## CTF dashboard
93
+
94
+ The first view is an opencode-style start screen: a large `WROSECODE` wordmark
95
+ with a smooth colour gradient (truecolor where the terminal advertises it, 256
96
+ or 16 colours otherwise, plain text under `NO_COLOR`/`--no-color`), carrying the
97
+ live spinner, the session ID, the challenge category, and the speed tier the
98
+ current thinking level maps to. Under it an info panel shows the provider and
99
+ model, thinking level and resource budget, sandbox and approval modes, working
100
+ directory, mode, harness, git branch, and MCP and skill counts — followed by
101
+ provider health with a `/providers` hint, a rotating tip (`/` commands, `@`
102
+ files, `ctrl+t` thinking, `ctrl+d` dashboard, `ctrl+o` output), the three most
103
+ recent sessions with one-line `resume` suggestions, and a `/ctf` hint. It only
104
+ draws while the transcript is still empty — as soon as you type or a turn
105
+ produces output it folds back into a slim `WROSECODE v…` header so the
106
+ conversation gets the screen. Below 80 columns the block art collapses to a
107
+ single `WROSECODE v…` line.
108
+
109
+ Example terminal view at 110×36 (session ID and project path anonymized):
110
+
111
+ ```
112
+ ╦ ╦╔╗ ╭─╮╔═╗╔═╗╭─╮╭─╮╔═╗╔═╗ ⠇ session ses_<session-id> · [misc] · paint 13ms
113
+ ║ ║╠╩╗│ │╠═╝╠═╗│ │ │║ ║╠═╗
114
+ ╚═╝╚═╝╰─╯╚═╝╚═╝╰─╯╰─╯╚═╝╚═╝ WROSECODE v1.0.0 · FAST
115
+ anthropic / claude-sonnet-5 · think medium · up to 3 agents · sandbox none · ask · /path/to/project
116
+ BUILD · claude · [misc] · git master* · 0 mcp · 636 skills
117
+ 2 connected · 10 need setup · /providers
118
+ tip · @ files
119
+ ctf wrosecode ctf <file|dir|url> · or /ctf here
120
+ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
121
+ TRANSCRIPT 1 entry
122
+ INFO WROSECODE v1.0.0 · session ses_<session-id>
123
+ Type a request · /help commands · Ctrl+P palette
124
+ Tab build/plan · [ ] thinking · PgUp/PgDn scroll · /clear
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+ Ready · session ses_<session-id> · claude-sonnet-5 · think:medium · tok 0/0 · cache 0% · 4s · step 0
146
+ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────
147
+ │ >
148
+ ```
149
+
150
+ The rows above the rule are the start screen — wordmark, info panel, provider
151
+ health, rotating tip, recent sessions, and the `/ctf` hint — and they are what
152
+ the spinner animates in. The wordmark carries `TURBO` / `FAST` / `SMART` /
153
+ `DEEP` plus the worker count that tier actually allows (1 / 3 / 5 / 10).
154
+
155
+ The transcript owns the full terminal width: there is no always-on token or
156
+ timeline panel. Token, latency, and cost counters are still tracked
157
+ internally and surface in the status line, in `/stats` on demand, in
158
+ `~/.wrosecode/live.json`, and in the closing `── RESULT ─` receipt. The
159
+ bottom pane shows the subagent tree — only in-flight work, newest last, with
160
+ `+N more running` when it overflows — and finished subagents collapse to one
161
+ `✓ N subagents done` line. The bottom status
162
+ line leads with the run state, session ID, model, and `think:` label (the level name, or
163
+ `auto→medium` while the auto controller drives it), then tokens, cache
164
+ hit rate, elapsed time, tool step, sandbox and approval, speed tier and
165
+ workers, cost, and tool counts — for example
166
+ ` Ready · session ses_1791153855-1574184-0 · claude-sonnet-5 · think:medium · tok 0/0 · cache 0% · 4s · step 0 ·
167
+ none/ask · FAST · 3w · cost n/a · tools 0:0`. During a turn it swaps in an
168
+ animated spinner, the queue note, and the live timeout and tool progress. The
169
+ input box below it is an open-bottomed `┌──` / `│ >` frame that grows with
170
+ the draft.
171
+
172
+ File magic and extensions categorize common web, pwn, crypto, reverse
173
+ engineering, and forensics tasks.
174
+
175
+ A found flag raises a `🚩 FLAG ALERT` banner under the header and survives until
176
+ the next turn or `/clear`. It also emits a terminal bell (`\x07`, disabled with
177
+ `alert_bell = false`) and a desktop notification when `notify-send` is available,
178
+ so a flag found in a background pane is not missed.
179
+
180
+ Every model response and tool result is scanned for `flag{...}`, `CTF{...}`, `picoCTF{...}`, and `HTB{...}`. The patterns live in `config.toml` under `[ctf]`; `auto_copy` controls the clipboard copy. New flags are highlighted and appended to `.ctf/flags.log`. Optional CTFd submission is enabled with `auto_submit = true` in `config.toml`, or from the environment:
181
+
182
+ ```bash
183
+ export WROSECODE_CTFD_AUTO_SUBMIT=1 # overrides config.toml
184
+ export CTFD_URL=https://ctf.example
185
+ export CTFD_TOKEN=your-token
186
+ export CTFD_CHALLENGE_ID=42
187
+ ```
188
+
189
+ CTF skills cover recon, web, pwn, crypto, reverse engineering, forensics, stego, OSINT, and network analysis. Independent model tool calls run concurrently; `delegate_task` supports provider and model routing. Identical read-only calls use the in-memory result cache, while file edits invalidate cached results.
190
+
191
+ ### Keyboard controls
192
+
193
+ | Key | Action |
194
+ | --- | --- |
195
+ | `↑` / `↓` | Scroll the transcript whenever the input box is empty |
196
+ | `PageUp` / `PageDown` | Move the transcript a full viewport at a time (at the real page size, not a fixed five lines) |
197
+ | `Home` / `End` | Jump to the top or bottom of the transcript when the input is empty; otherwise move the caret |
198
+ | `Ctrl+Home` / `Ctrl+End` | Always jump to the top or bottom of the transcript |
199
+ | `Esc`, then `j` / `k` | Enter navigation mode and scroll with the single keys; `Space`/`b` page down/up |
200
+ | `Shift+Enter` | Insert a newline |
201
+ | `Enter` | Submit the prompt or selected command; during a turn it queues the message for after the reply |
202
+ | `Ctrl+K` | Clear input |
203
+ | `Ctrl+U` | Clear input when there is text, otherwise scroll half a page up |
204
+ | `Ctrl+D` | Delete the character under the caret when there is text; with an empty prompt, open the live eight-panel dashboard (`Esc` or `Ctrl+D` closes it) |
205
+ | `Ctrl+W` | Delete the word before the caret |
206
+ | `Ctrl+A` / `Ctrl+E` | Jump to the start / end of the line |
207
+ | `Alt+←` / `Alt+→` (also `Alt+B` / `Alt+F`) | Jump a word left / right |
208
+ | `Ctrl+L` | Clear the visible transcript and redraw (history and conversation context are kept; `/clear --context` drops the context too) |
209
+ | `Ctrl+T` | Cycle the thinking level (`off` → `low` → `medium` → `high` → `max` → `auto`) |
210
+ | `Ctrl+Shift+C` | Copy latest output |
211
+ | `[` / `]` | Step the thinking level down or up (same cycle as `Ctrl+T`) |
212
+ | `@` at the start of a prompt | Open the file-mention picker (`Mention files`); `Esc` closes it |
213
+ | Bracketed paste | A paste of 4+ lines collapses into `[Pasted N lines]`: `Enter` inserts it, `Esc` discards it |
214
+ | `Tab` | Complete the current word (paths, slash commands, tool names) when the input is not empty; otherwise switch build and plan modes |
215
+ | `Ctrl+C` | With text typed: clear the input. At an empty prompt: arm a ~2s exit window (`press Ctrl+C again to exit`); a second press inside it quits. During a turn it cancels the turn instead of closing the shell (which also arms the exit window, so Ctrl+C, Ctrl+C stops a run and exits) |
216
+ | Mouse wheel | Scroll the transcript (smooth scrolling follows `smooth_scroll_lines`) |
217
+
218
+ While scrolled up, the transcript header shows `· ↓ N new lines (End to jump)`; each new line arriving is counted there until `End` returns to the bottom. Multi-line drafts grow the input box to four rows and then scroll with the caret. Prompt history (last 500 entries, consecutive duplicates collapsed) persists to `~/.wrosecode/history` and is recalled with `↑`/`↓`.
219
+
220
+ Shell calls have a 30 second timeout and are terminated if stuck. Panics and turn errors are written to `.ctf/errors.log`; the TUI keeps provider errors visible with a recovery suggestion. One-shot runs print a computed usage report, and `--summary json` emits a machine-readable `TaskComplete` object.
221
+
222
+ ### Thinking levels
223
+
224
+ Six levels decide how much reasoning each provider call spends: `off`, `low`, `medium`, `high`, `max`, and `auto`. `Ctrl+T` cycles them, `[` / `]` step down and up, `/think <level>` sets one directly (bare `/think` prints the current level and the list), and `--think <level>` fixes the startup default. The mode resolves `--think` → the provider profile's `think` key in `~/.wrosecode/providers.toml` (so a model can carry its own default) → `[agent] think` in `config.toml` → the legacy numeric `[agent] thinking_level`. Each concrete level maps to the provider's native control — Anthropic extended-thinking `budget_tokens` (1024 / 2048 / 4096 / 6144 for low / medium / high / max, clamped to 8191) or an OpenAI-style `reasoning_effort` (`low` / `medium` / `high`, with `max` reported as `high`) — and a per-model `think_map` on the profile overrides that with either a string or a token count per level. A provider that rejects the parameter retries once without it, records `think control ignored` in the transcript, and marks the status bar `think:… (ignored)`.
225
+
226
+ `auto` is a controller rather than a strength: it starts at `medium`, escalates one concrete level after three consecutive failing steps (the transcript shows `think: medium → high (no progress ×3)`), drops back one level once progress returns, and settles a trivial read/answer task at `low`. The status bar shows it as `auto→high` — mode plus the live level. The dashboard's Thinking panel reports the previous turn's model time and reasoning tokens (`last turn 4.2s · reasoning 1280 tok`).
227
+
228
+ ## Advanced dashboard and persistence
229
+
230
+ The terminal shows two live panes: the full-width transcript on top and the subagent tree below it (in-flight work only, capped, with finished work collapsed to one line). Clicking a transcript row expands or collapses its tool cell. `Ctrl+T` now cycles the thinking level; for colours `/theme` opens a picker over the palettes — dark, light, solarized, Dracula, Nord, and Mono — and `/theme NAME` applies one directly (`/theme` on an unknown name lists what exists). Your own palettes drop into `~/.wrosecode/themes/*.toml` as five hex colours (`text`, `muted`, `accent`, `status`, `background`, with an optional `name`); a file that fails to parse, has bad hex, or reuses a built-in name is skipped. `NO_COLOR` (any non-empty value) or `--no-color` turns colour off everywhere: the gradient wordmark becomes plain text and every frame renders without colour codes. The shell paints its first frame — gradient wordmark, info panel with provider, thinking, sandbox, approval, git, MCP and skill counts, provider health, a rotating tip, recent sessions, and the `/ctf` hint — before provider setup, skill discovery, MCP reconnects, and session restore, so startup never waits on the network: the measured time is stamped next to the session line (`· paint 9ms`), reported by `/debug` as `First paint`, and must stay inside a 50 ms budget. `Ctrl+P` opens fuzzy command search; `Ctrl+F` and `Ctrl+R` open the flag and prompt pickers with the same filter-as-you-type behaviour. The renderer targets 60 frames per second while writing only changed terminal rows.
231
+
232
+ Sessions checkpoint to SQLite WAL every ten seconds at `~/.wrosecode/state.db`, while portable JSON session files remain available. Every session gets a stable `ses_…` ID, shown in the status line and in `/sessions` with a one-line summary and age; resuming restores conversation history, provider, and model. Resume or fork work with:
233
+
234
+ ```bash
235
+ wrosecode --session SESSION_ID
236
+ wrosecode -s SESSION_ID
237
+ wrosecode --session SESSION_ID --fork
238
+ wrosecode --session SESSION_ID --export-session session.json
239
+ wrosecode --import-session session.json
240
+ ```
241
+
242
+ Token counts, model latency percentiles, cache traffic, and costs come from provider usage. Configure prices in `pricing.toml` or `~/.wrosecode/pricing.toml`. Press `Ctrl+E` or run `/export` to write `metrics.json`, `metrics.csv`, and `latency.csv` (a latency histogram with one row per bucket: `bucket_upper_ms,count`). Set `OTEL_EXPORTER_OTLP_ENDPOINT` to export the same counters to an OTLP HTTP collector. Pass `--trace` to log per-step timings (API latency, tool time, slow renders) as NDJSON to `~/.wrosecode/trace.log`.
243
+
244
+ Rate-limited provider replies (HTTP 429 / quota errors) are classified separately
245
+ from other failures, counted in the dashboard (`WARN 429s n`), and exported with
246
+ the rest of the metrics.
247
+
248
+ ## Live dashboard and transcript
249
+
250
+ With an empty prompt, `Ctrl+D` (or `/dashboard`) replaces the split view with
251
+ the live eight-panel dashboard: Processes, Thinking, Timeline, Plan, Tokens &
252
+ Cost, Files, CTF, and Budget. `←`/`→` move between panels, `↑`/`↓` (or
253
+ `j`/`k`) move the selection inside Processes and Files, `Enter` opens the
254
+ focused panel as full output (a selected process shows its live tail, a
255
+ selected file its `git diff`), `Ctrl+C` / `Ctrl+K` send SIGINT / SIGKILL to the
256
+ selected process, and `Esc` / `Ctrl+D` returns to the shell. `/stats` prints
257
+ the same eight panels as plain text.
258
+
259
+ Captured at 110×36 straight from a real run:
260
+
261
+ ```
262
+ DASHBOARD · BUILD/anthropic/claude-sonnet-5/misc · ready
263
+ ▌PROCESSES ───────────────────────────────────────────│ TOKENS & COST ──────────────────────────────────────
264
+ ▌ no tracked processes │ in 0 · out 0 · reason 0
265
+ │ cache 0% · read 0 · write 0
266
+ │ tok ·
267
+ │ cost n/a · saved ~0 tok
268
+ │
269
+ │
270
+ │
271
+ │
272
+ │
273
+ │ FILES ──────────────────────────────────────────────
274
+ │ +0 -0 · 0 changed
275
+ │
276
+ THINKING ────────────────────────────────────────────│
277
+ level 5/20 medium · FAST · ready · idle │
278
+ thinking 0.0s · acting 0.0s · waiting 6.9s │
279
+ turns 0 · last turn · │
280
+ last turn 0.0s · reasoning 0 tok │
281
+ TIMELINE ────────────────────────────────────────────│
282
+ model 0.0s · tools 0.0s · wait 6.9s · wall 6s │
283
+ parallel ×0 · steps 0 · fails 0 │ CTF ────────────────────────────────────────────────
284
+ tok · │ [misc] · tools 0 (0 failed)
285
+ recent: — │ coverage: 0 files · 0 searches · 0 shells
286
+ PLAN ────────────────────────────────────────────────│ flags 0
287
+ no plan yet (update_plan) │ checker: unverified
288
+ │
289
+ │
290
+ │
291
+ │ BUDGET ─────────────────────────────────────────────
292
+ │ ░░░░░░░░░░ $? / n/a (unlimited)
293
+ │ steps 0 (no cap)
294
+ │ wall 6s · flags 0
295
+ │ errors 0 · status ready
296
+ │
297
+ ←→ panel · ↑↓/jk select · Enter detail · Ctrl+C/K signal · Esc/Ctrl+D close
298
+ ```
299
+
300
+ Transcript rows are cells rather than raw lines: each tool shows a one-line
301
+ header with its elapsed time and a preview of the first three and last two
302
+ output lines, failed cells auto-expand with their exit code, nested subagent
303
+ calls draw as a `├─` / `└─` tree, and reasoning opens as a dimmed
304
+ `Thinking… 2.1s` cell that freezes into `Thought n.ns` when the model answers.
305
+ Press `Enter` (or `Ctrl+O`) to expand the cell at the top of the viewport;
306
+ explorations group under a collapsed `▸ Explored 3 files · 1 search` line.
307
+ Every finished task ends with a `── RESULT ─ {status}` block: verification
308
+ state, answer, proof, the model/tools/wait time split, steps, tokens, cache
309
+ rate, and cost.
310
+
311
+ The same dashboard runs outside the TUI:
312
+
313
+ ```bash
314
+ # Attach to a running session: fresh live.json (≤10s) → /v1/status → offline
315
+ wrosecode dashboard
316
+
317
+ # One plain frame, no ANSI — pipe-friendly
318
+ wrosecode dashboard --once --offline
319
+
320
+ # Headless run whose final JSON is the dashboard snapshot
321
+ wrosecode exec --json 'enumerate the challenge'
322
+ ```
323
+
324
+ While idle, a running TUI rewrites `~/.wrosecode/live.json` about once a
325
+ second (schema `wrosecode/live-v1`) — that file is what the attach client
326
+ reads when no web server is up, and `GET /v1/status` serves the identical
327
+ snapshot when the session runs `--web`.
328
+
329
+ ## Terminal compatibility
330
+
331
+ The TUI decides how much terminal control to take from the environment instead of
332
+ assuming a full xterm:
333
+
334
+ | Condition | Effect |
335
+ | --- | --- |
336
+ | `TERM` empty or `dumb` | No alternate screen, no mouse capture |
337
+ | VS Code / Cursor integrated terminal (`TERM_PROGRAM=vscode`, `VSCODE_PID`, `VSCODE_CWD`) | Mouse capture stays off so the terminal's native wheel scroll and text selection keep working |
338
+ | Zellij (`ZELLIJ` set) or `WROSECODE_FULL_REDRAW=1` | Every frame repaints fully (multiplexers composite their own grid and keep stale glyphs otherwise) |
339
+ | `WROSECODE_NO_ALT_SCREEN=1` | Run inline instead of switching to the alternate screen |
340
+ | `NO_COLOR` set to a non-empty value, or `--no-color` on the command line | No colour anywhere: the gradient wordmark falls back to plain text and frames emit no colour codes |
341
+
342
+ `mouse_capture` in `config.toml` opts into mouse handling:
343
+
344
+ ```toml
345
+ [ui]
346
+ mouse_capture = "off" # "off" (default), "on", or "auto" (off unless WROSECODE_MOUSE=1)
347
+ alert_bell = true # before flag notifications
348
+ ```
349
+
350
+ The default keeps the terminal's native text selection: drag to select and
351
+ copy with the terminal's own shortcut or menu, with no mouse reporting to
352
+ fight it. Set `mouse_capture = "on"` (or `WROSECODE_MOUSE=1`) only if you
353
+ want click-to-expand cells and wheel scrolling inside the app — every mouse
354
+ gesture already has a keyboard equivalent (`Enter`/`Ctrl+O` to expand,
355
+ `PageUp`/`PageDown`/`Home`/`End` to scroll, arrows + `Enter` in the
356
+ dashboard).
357
+
358
+ ## Advanced CTF workflow
359
+
360
+ The detector loads regexes from `config.toml`, checks plain output, ROT13, hexadecimal, and Base64 variants, and uses entropy to select encoded candidates. New flags pass through a checker before optional rate limited CTFd submission. `Ctrl+F` opens flag history and `/writeup` creates a Markdown evidence report.
361
+ While a picker is open, typing fuzzy-filters the list (`pdng` finds
362
+ `flag{padding...}`), `↑`/`↓` move, `Enter` copies the selected flag, and `Esc`
363
+ closes. `Ctrl+R` does the same for prompt history.
364
+
365
+ Long engagements also need to remember what has *not* been tried yet: `/coverage`
366
+ opens a persistent checklist in `.wrosecode/coverage.json` — add scope items,
367
+ tick them off as they are verified, and the `coverage` tool lets the agent
368
+ update the same list while it works. The file survives sessions, so skipped
369
+ areas stay visible instead of quietly falling out of the engagement.
370
+
371
+ ```bash
372
+ # CTFd operations
373
+ wrosecode ctfd list
374
+ wrosecode ctfd scoreboard
375
+ wrosecode ctfd download 42
376
+ wrosecode ctfd submit 42 'flag{answer}'
377
+ wrosecode ctfd verify 42 'flag{answer}' # exit 0 verified, 2 rejected, 1 inconclusive
378
+
379
+ # Race the primary model against other configured models
380
+ wrosecode --provider vyce --model deepseek-v4.1 \
381
+ --race openai:gpt-4o-mini --race ollama:qwen3 \
382
+ 'solve this challenge and verify the flag'
383
+ ```
384
+
385
+ ### CTF autopilot
386
+
387
+ `wrosecode ctf <file|dir|url|description>` hands the target to an autonomous
388
+ loop (headless; in the TUI it is `/ctf`, and mentioning a flag, CTF, or
389
+ challenge offers it):
390
+
391
+ ```bash
392
+ # Triage → category hypothesis → playbooks → verify, or a loud budget stop
393
+ wrosecode ctf chal.pcap --budget 60
394
+ wrosecode ctf chal.txt --flag-format 'SECRET\{[^}]+\}' --category forensics
395
+ wrosecode ctf https://challenge.example --remote challenge.example:443 --parallel 2
396
+ ```
397
+
398
+ Every run starts by printing the allowlist it will work inside
399
+ (`SCOPE files … · hosts …`, `TOOLS installed/missing`, `GUESS <category>`),
400
+ writes `ctf-notes.md` (challenge, scope, triage, assumptions, dead ends —
401
+ dead ends are re-read by the stuck handler so nothing is retried), and loads
402
+ the matching `skills/ctf-*` playbook. Out-of-scope writes are denied with the
403
+ reason handed back to the model instead of executed. Flags are `✔ flag
404
+ verified` only when the value re-derives from an artifact (or a decode chain);
405
+ a model claim alone is `⚠ candidate, unverified`.
406
+
407
+ The stop condition is never silent: hitting the budget prints
408
+ `BUDGET EXHAUSTED (steps|tokens|seconds)` plus `Tried:` / `Learned:` /
409
+ `Next:`, and every run leaves `writeups/<challenge>.md`. Exit codes are `0`
410
+ verified, `2` unsolved (budget or stop), `1` error.
411
+
412
+ Project playbooks load from `skills/`, `.ctf/skills/`, and `~/.wrosecode/skills/`. Delegated tasks accept stable `task_id` values and can resume completed results. Thinking levels allocate runtime concurrency as follows: 0–3 uses one worker, 4–7 uses up to three, 8–12 uses up to five, and 13–20 allows the configured swarm limit.
413
+
414
+ The tool runtime includes MCP, Burp raw request import/export, optional Playwright screenshots, in-memory and Redis result caches, and an optional Qdrant writeup archive. Start the supporting services with `docker compose up -d`.
415
+
416
+ ## Web and automation
417
+
418
+ ```bash
419
+ # Local REST API and browser dashboard
420
+ wrosecode --provider vyce --model deepseek-v4.1 --web
421
+
422
+ # Headless automation with machine-readable completion data
423
+ wrosecode --headless --summary json 'analyze the supplied challenge'
424
+ ```
425
+
426
+ The web server binds to `127.0.0.1:7878` by default. See `API.md` before exposing it through a reverse proxy.
427
+
428
+ `--events PATH` (or `--events -` for stdout) writes a framed NDJSON submission stream for embedding clients: a `start` frame with the session and prompt, `step` frames for tool begins/ends, thinking-level changes, plans, flag hits, and usage, `text` frames carrying streamed tokens, and a final `result` frame with the answer, verification status, usage, and model-turn count. Frames are flushed as they happen and the `result` frame is always last, so a host application can drive and render a session without parsing the transcript:
429
+
430
+ ```bash
431
+ wrosecode --headless --events run.jsonl --summary json 'analyze the supplied challenge'
432
+ jq -r 'select(.type == "text") | .delta' run.jsonl # live answer stream
433
+ ```
434
+
435
+ `src/events.rs` is the formatter; `docs/ref-notes.md` records it against Codex's submission/event protocol.
436
+
437
+ ## Shell sandbox
438
+
439
+ `[sandbox]` in `config.toml` decides where `shell` calls run:
440
+
441
+ | `engine` | Behaviour |
442
+ | --- | --- |
443
+ | `none` (default) | Run the command on the host, exactly like before. |
444
+ | `docker` | Run it inside a container. The project directory is bind-mounted at `/workspace`, so file edits made by other tools stay visible. |
445
+
446
+ ```toml
447
+ [sandbox]
448
+ engine = "docker"
449
+ image = "wrosecode-sandbox:latest"
450
+ persistent = true # one warm container reused across calls (no cold start)
451
+ name = "wrosecode-sandbox"
452
+ network = "bridge"
453
+ memory = "2g"
454
+ cpus = "2.0"
455
+ auto_build = false # docker build -f Dockerfile.sandbox when the image is missing
456
+ ```
457
+
458
+ Build the analysis image once:
459
+
460
+ ```bash
461
+ docker build -f Dockerfile.sandbox -t wrosecode-sandbox:latest .
462
+ ```
463
+
464
+ `engine = "docker"` never falls back silently: if the docker CLI, the daemon, or
465
+ the image is missing, the tool call fails with a message that tells you which of
466
+ the three to fix. `persistent = true` keeps one `sleep infinity` container alive
467
+ and `docker exec`s into it, so there is no per-call cold start.
468
+
469
+ Whatever the engine, `shell` is resource-boxed: the host path runs the command
470
+ in its own process group, so a timeout or a cancelled turn kills the whole
471
+ group — grandchildren included — stdout and stderr are tail-capped at 256 KiB
472
+ while the command runs so a flooding pipe cannot pin memory, and a background
473
+ daemon cannot hold the result open. The container path adds an in-container
474
+ `timeout -k 5` (skipped automatically on images without coreutils) so killing
475
+ the docker client on timeout cannot strand the workload inside the container.
476
+
477
+ ```bash
478
+ /sandbox # status: engine, container, image
479
+ /sandbox up # warm the container now
480
+ /sandbox down # stop and remove it
481
+ ```
482
+
483
+ `auto_build = true` runs `docker build -f Dockerfile.sandbox -t <image> .` the
484
+ first time the image is missing, then falls back to `docker pull`.
485
+
486
+ ### Desktop control (opt-in)
487
+
488
+ The `computer` tool drives this machine's desktop with `screenshot`, `click`,
489
+ `type`, `key`, and `scroll`, but it ships disabled — until you opt in, the
490
+ schema is not even advertised to the model. Enable it for one run with
491
+ `--computer`, or permanently with `[tools] computer = true` in `config.toml`.
492
+ Screenshots probe `grim`, `gnome-screenshot`, `scrot`, `import`, and
493
+ `screencapture` in that order; input actions go through `xdotool`. Every call
494
+ is launched as a literal argument vector (never through a shell string),
495
+ screenshot paths are confined to the project root, and approval follows the
496
+ normal mutating-tool policy: `ask` prompts for each call, `auto-safe` and
497
+ `yolo` run it, and plan mode refuses it.
498
+
499
+ ## Run the agent in a container
500
+
501
+ Build the image that carries `wrosecode` itself plus the challenge toolchain:
502
+
503
+ ```bash
504
+ docker build -t wrosecode-sandbox .
505
+ docker run --rm -it -v "$PWD:/workspace" \
506
+ -e ANTHROPIC_API_KEY wrosecode-sandbox --permission yolo
507
+ ```
508
+
509
+ With an interactive terminal, `wrosecode` opens a full-screen chat UI showing the conversation, tool activity, status, and a persistent input box. Anthropic text appears as it streams; the OpenAI-compatible backend displays its completed response. The UI redraws only changed lines, renders just the visible slice of a long transcript, and pins itself to the bottom while new output arrives (the header shows `· ↑N lines` when you have scrolled away). Type `/` at the start of the input to open the command palette. Keep typing to filter, use the arrow keys to select, press Enter to run, or Esc to close. `Tab` switches between build and read-only plan modes. Use `PageUp`/`PageDown` to scroll, `Up`/`Down` for prompt history. Messages typed while a turn is running are kept: they are queued and sent after the reply, and `Ctrl+C` during a turn cancels just that turn (a second `Ctrl+C` within ~2s exits). One-shot calls and piped input keep plain text output. The UI displays permission requests and accepts `y` or `n`; `Ctrl+C` there denies the request and stays in the session.
510
+
511
+ The `delegate_task` tool can select a different `provider` and `model` for a child task. Supported provider names are `anthropic`, `openai-compat`, `ollama`, and `lm-studio`.
512
+
513
+ Attach an MCP stdio server for one run with `--mcp-bin PATH` and repeated `--mcp-arg ARG` flags. `/mcps add` saves additional servers in `~/.wrosecode/mcps.toml`; they reconnect at startup. The same file takes remote servers over the streamable-HTTP transport: give the entry a `url = "https://…/mcp"` instead of a `bin`, plus an optional `[servers.headers]` table for `Authorization` or API keys — `/mcps add` prompts for the URL when you prefer the TUI. The client posts JSON-RPC, keeps the server's `Mcp-Session-Id`, and accepts either a JSON body or an SSE stream for replies. Tools are exposed to the model as `mcp__SERVER__TOOL`.
514
+
515
+ The standalone `install.sh` and `install.ps1` scripts accept
516
+ `WROSECODE_RELEASE_BASE`; npm users do not need to set it because the npm
517
+ installer derives the versioned GitHub Release URL from package metadata.
518
+
519
+ ## Configuration
520
+
521
+ `config.toml` in the project root is the single source of truth for the tunables;
522
+ `config.schema.json` describes it for editor validation. Every section and every
523
+ key is optional — a missing file, a missing section, or a typo falls back to the
524
+ built-in defaults rather than failing to start. CLI flags always win.
525
+
526
+ | Section | Purpose |
527
+ | --- | --- |
528
+ | `[ui]` | Theme, verbosity, alternate screen, `mouse_capture`, `alert_bell`, scroll step. |
529
+ | `[agent]` | Thinking mode (`think`), legacy level (`thinking_level`), worker ceiling, shell timeout, retries, permission tier, budget, planner model for `plan` mode (`planner = "provider/model"`), aider-style auto-commit of edited paths (`auto_commit`, default on). |
530
+ | `[ctf]` | Flag regexes, clipboard copy, CTFd auto-submit. |
531
+ | `[cache]` | `memory` or `redis`, plus Redis and Qdrant URLs. |
532
+ | `[sandbox]` | Where `shell` calls run (`none` or `docker`) and the container settings. |
533
+ | `[lsp]` | Language-server diagnostics appended to file edits (`enabled`, `wait_ms`, per-language `commands`). |
534
+ | `[tools]` | Opt-in extra tool surfaces: `computer` enables desktop control (off by default; the `--computer` flag turns it on for one run). |
535
+
536
+ `cargo test` parses the shipped `config.toml` and asserts every key lands where
537
+ it should, so a section cannot silently stop being read again.
538
+
539
+ ## Tests
540
+
541
+ ```bash
542
+ cargo fmt --check
543
+ cargo clippy --all-targets -- -D warnings
544
+ cargo test --all-targets
545
+ cargo test --release benchmarks -- --nocapture # speed budgets (also in ./scripts/check.sh and CI)
546
+ ```
547
+
548
+ Three layers run in that one command:
549
+
550
+ | Layer | Where | What it proves |
551
+ | --- | --- | --- |
552
+ | Unit | `src/**` modules | Flag detection corpus, metrics/histogram math, terminal profile rules, CTFd verdict classification, config parsing |
553
+ | CLI | `tests/cli.rs` | The argument surface stays documented: `--version`, `--help`, unknown flags, `ctfd verify --help` |
554
+ | End to end | `tests/e2e_headless.rs`, `tests/e2e_ctfd.rs` | A real `wrosecode` process against a local mock OpenAI-compatible server and a mock CTFd: tool round trips, `--summary json` usage numbers, `--until` exit codes, `ctfd verify` retries and exit codes |
555
+
556
+ The end-to-end tests bind `127.0.0.1` on an ephemeral port, point the child
557
+ process at a temporary `HOME`, and never touch the network or a real provider.
558
+
559
+ The release-mode budgets in `src/benchmarks.rs` (output shaping, SSE
560
+ draining, repo-map build) fail on regressions; `./scripts/benchmark.sh`
561
+ records test-suite latency and corpus size to `.ctf/reports/benchmark.json`.
562
+
563
+ ## Permissions
564
+
565
+ `--permission ask` prompts for mutating tool calls. `auto-safe` runs them automatically except risky shell commands. `yolo` removes normal prompts. A destructive shell-command blocklist prompts in every tier. `/plan` changes to read-only mode; `/build` returns to edit mode. The shell blocklist is a convenience guard, not an isolation boundary — for real isolation set `[sandbox] engine = "docker"` below, which moves every `shell` call into a container.
566
+
567
+ Tool output, web content, and file text are untrusted data: scope and approval checks inspect structured tool-call arguments before dispatch, so result prose cannot change policy, approvals, or scope. Credential-shaped values and configured provider keys are redacted from displayed and persisted copies — transcript cells, streamed text, dashboard processes, events NDJSON, session JSON/SQLite payloads, crash reports, and memory facts — while the live model context keeps full fidelity.
568
+
569
+ ## Skills and memory
570
+
571
+ Skills are `SKILL.md` files with YAML frontmatter:
572
+
573
+ ```yaml
574
+ ---
575
+ name: my-skill
576
+ description: What it helps with
577
+ fork: true
578
+ keywords: [kubernetes, kubectl]
579
+ ---
580
+ Instructions for the agent.
581
+ ```
582
+
583
+ Skills load from bundled `skills/`, project `skills/`, `~/.wrosecode/skills/`, and `--skills DIR`, with later paths overriding names. Skill packages install from a git URL or a directory — `/skills install <url#ref | path>`, or `--install-skill` headless — into `~/.wrosecode/skills/`, versioned in `~/.wrosecode/packages.json`; `/skills list` and `/skills uninstall <pkg>` manage them. `keywords:` (a YAML list or a comma-separated string) makes a skill fire on its own: a short knowledge snippet loads as soon as one of those whole words appears in the prompt, without the skill name being mentioned — the keyword gate sits below the name match, so a prompt naming the skill still wins. Use `#fact text` for project memory and `#!fact text` for personal memory. `/memory` lists saved facts. Matching facts are recalled automatically. Obvious secret patterns are redacted on save.
584
+
585
+ `/harness minimal|swe|claude` switches prompt style without clearing history. `wrosecode acp` starts a basic ACP stdio agent.
586
+
587
+ ## Your own commands and agents
588
+
589
+ Markdown files with YAML frontmatter extend the shell without recompiling anything. Commands load from `.wrosecode/commands/*.md` (project) and `~/.wrosecode/commands/` (personal); the project file wins on a name clash:
590
+
591
+ ```markdown
592
+ ---
593
+ description: Commit and push the branch
594
+ category: Project
595
+ ---
596
+ Review the working tree, write one imperative commit message, commit, and push $ARGUMENTS.
597
+ ```
598
+
599
+ `/ship the fix` expands `$ARGUMENTS` and runs the body as a normal turn — in the TUI and in `wrosecode --headless '/ship the fix'`. Your commands appear in `/help` and the `Ctrl+P` palette, and typing `/` completes them.
600
+
601
+ Agents load from `.wrosecode/agents/*.md` and `~/.wrosecode/agents/`:
602
+
603
+ ```markdown
604
+ ---
605
+ description: Reviews diffs, never edits
606
+ mode: plan
607
+ thinking: deep
608
+ ---
609
+ Always quote the file and line you are criticising.
610
+ ```
611
+
612
+ `wrosecode --agent reviewer 'review the diff'` activates one for a run; in the TUI it is listed in `/agents`. Its body is injected into every system prompt, `mode:` picks `build`, `plan`, or `general`, and `thinking:` sets the level. `/build`, `/plan`, or `/agents` on a built-in drops the custom agent.
613
+
614
+ ## Recipes
615
+
616
+ A recipe is a saved multi-step workflow: a YAML (or Markdown) file with parameters and an ordered list of prompt and command steps, in the spirit of goose's recipes. `.wrosecode/recipes/` holds the project's recipes, `~/.wrosecode/recipes/` the personal ones, and the project file wins a name clash.
617
+
618
+ ```yaml
619
+ name: audit
620
+ description: Two-step audit
621
+ version: "1"
622
+ params:
623
+ target:
624
+ description: host to audit
625
+ default: localhost
626
+ steps:
627
+ - prompt: "Recon {{target}} and list open ports."
628
+ - command: "echo ports-open-for-{{target}}"
629
+ - prompt: "Given {{prev}}, write the one-line verdict."
630
+ ```
631
+
632
+ `params` may also be a plain list of required names. `{{key}}` placeholders take the resolved parameter values (defaults fill in, an unknown key is an error), `{{prev}}` is the previous step's output, and `{{steps}}` is the accumulated history — bounded, oldest output dropped first. A Markdown recipe uses frontmatter for `name`/`description`/`params` and its body as a single prompt step.
633
+
634
+ ```bash
635
+ wrosecode recipe list
636
+ wrosecode recipe run audit --set target=attackme.local
637
+ ```
638
+
639
+ `recipe run` behaves like any other headless run: it prints the answer, honours `--summary json`, `--goal`, `--until`, `--until-cmd`, `--events`, and exits `2` when the verification flags say the answer was wrong. Prompt steps run as ordinary turns — full tool use, permissions, and steering apply — while command steps are the recipe author's own shell commands, run with the project's shell timeout. In the TUI, `/recipe` opens a picker, asks for each parameter the recipe does not default, and streams every step; `/recipe NAME` skips the picker.
640
+
641
+ ## Slash commands
642
+
643
+ `/help` shows the full command list, generated from the same registry as the palette.
644
+
645
+ | Area | Commands |
646
+ | --- | --- |
647
+ | Agents | `/agents`, `/build`, `/plan`, `/harness`, `/skills` |
648
+ | Providers | `/connect`, `/providers`, `/models`, `/mcps`, `/mcp` |
649
+ | Session | `/new`, `/sessions`, `/resume`, `/fork`, `/history`, `/tree`, `/move`, `/editor`, `/memory`, `/greet`, `/debug`, `/stats`, `/dashboard`, `/verbosity`, `/think`, `/theme`, `/compact`, `/export`, `/help`, `/exit`, `/quit` |
650
+ | Project | `/init`, `/diff`, `/review`, `/commit`, `/issues`, `/rmslop`, `/undo`, `/redo`, `/add`, `/drop`, `/recipe`, `/coverage`, `/flags`, `/writeup`, `/ctf`, `/sandbox` |
651
+
652
+ `/providers` opens a searchable provider screen with the built-in entries and their connection status. Enter configures or selects a provider; `a` adds, `e` edits, `d` deletes any provider (built-ins included — deleting the active one switches to another configured provider first), and `t` tests a connection. `/connect` opens the custom provider form directly; `/connect openai` offers "Sign in with ChatGPT" (browser loopback or device-code OAuth, tokens stored owner-only at `~/.wrosecode/credentials/openai.json`) next to the plain API-key path. The API key field is masked. `/models` lists models across connected providers and switches to the picked one.
653
+
654
+ Provider definitions are saved in `~/.wrosecode/providers.toml` without keys. Keys go to the OS keyring when available, otherwise to `~/.wrosecode/auth.json` with mode `0600`. Environment variables override stored keys. Custom provider keys can use `WROSECODE_NAME_API_KEY` (replace dashes in the name with underscores), or a saved `env:VARIABLE` reference. Custom providers support `openai_compat` and `anthropic` APIs; both support streamed replies and tool calls. Each profile can also set `think` (its default thinking level) and `think_map` (per-level provider values) — see *Thinking levels* above. Providers that need cloud-specific signing or URLs can use an OpenAI-compatible gateway as their base URL.
655
+
656
+ The same registry is available without the TUI:
657
+
658
+ ```bash
659
+ wrosecode providers list
660
+ printf '%s\n' "$MY_API_KEY" | wrosecode providers add my-gateway \
661
+ --base-url https://llm.example.com/v1 --stdin --model my-model
662
+ wrosecode providers set-key my-gateway --api-key env:MY_API_KEY
663
+ wrosecode providers edit my-gateway --model other-model
664
+ wrosecode providers test my-gateway
665
+ wrosecode --provider my-gateway 'Explain this project'
666
+ wrosecode providers remove my-gateway
667
+ ```
668
+
669
+ OAuth sign-in (ChatGPT-style) is available alongside API keys. It needs a
670
+ client ID from your own OAuth client registration — nothing first-party is
671
+ bundled:
672
+
673
+ ```bash
674
+ wrosecode providers add openai --base-url https://api.openai.com/v1 \
675
+ --model gpt-5 --oauth --oauth-client-id YOUR_CLIENT_ID
676
+ wrosecode providers add openai --base-url https://api.openai.com/v1 \
677
+ --model gpt-5 --oauth --device --oauth-client-id YOUR_CLIENT_ID
678
+ ```
679
+
680
+ The browser flow opens the issuer's authorize URL and waits on a short-lived
681
+ `127.0.0.1:1455` callback (paste the redirect URL instead when the browser
682
+ cannot reach this machine); `--device` prints a code to enter at the
683
+ issuer's verification page instead. Tokens land owner-only (`0600`) at
684
+ `~/.wrosecode/credentials/<name>.json`, the profile records
685
+ `auth_method = "oauth"`, and refresh happens automatically before use.
686
+
687
+ Use `--stdin` or `--api-key env:VARIABLE` to keep keys out of shell history. You can add repeated `--header K=V` options for non-secret custom headers.
688
+
689
+ `/new` archives the current conversation to `~/.wrosecode/sessions/` and begins a new one. Long conversations are summarized automatically before the next request once they pass 180 000 characters of history (`WROSECODE_COMPACT_CHARS` changes the threshold), with the newest request carried through the cut verbatim so the ask you just made can never be summarized away; `/compact` runs that summary immediately and reports how much context it freed. `/sessions` (or `/resume`) resumes a saved conversation, and `/move NAME` renames the current session. `/fork [name]` keeps the current session on disk and continues in a copy of it — both sides share the history up to the fork and diverge from there — and `--session ID --fork` does the same from a headless run. `/fork <n> [name]` (and headless `--fork-at N`) branches from an earlier message instead, keeping only the first n messages; `/history` prints every message with the number `/fork <n>` would branch at, and a partial fork rebuilds its visible transcript to match the shortened context. `/tree` draws the resulting forest, indenting each fork under its parent and marking the session you are in. Every run, headless or not, is written to both the SQLite store (`--session`) and the JSON files under `~/.wrosecode/sessions/` that the pickers list. `/editor` opens `$EDITOR` (default `vi`) on a temporary prompt; saving and exiting submits it.
690
+
691
+ `/init` creates `.wrosecode/project.toml` and `WROSECODE.md` in the current repository without replacing existing files. `AGENTS.md` files from the repository root up through its parent directories are loaded with `WROSECODE.md` and injected into every system prompt (8 KB per file, 32 KB per chain). `/diff` includes tracked and untracked changes. `/review` asks the model for findings without applying changes. `/commit` shows the diff, proposes a message, allows edits, and requires an explicit confirmation before staging and committing. `/issues` reads open GitHub or GitLab issues from the `origin` remote; private repositories can use `GITHUB_TOKEN` or `GITLAB_TOKEN`. `/rmslop` lists untracked scratch files and empty directories created by this session's file tool, then requires confirmation before deleting them. Every batch of `write_file`, `edit_file`, and `apply_patch` calls first captures the pre-edit bytes of exactly those paths under `.wrosecode/snapshots/undo/`; `/undo` restores the newest snapshot (removing files the edit created) and keeps the replaced tree on the redo stack, `/redo` reverses that, and a failed turn that changed nothing discards its own snapshot. Git is never touched, so snapshots work in a repository you did not initialise.
692
+
693
+ `/add PATH` pins a project file into every prompt: its contents ride along in the system prompt (capped at 6 000 characters per file and 24 000 in total) so the model never has to re-read it, `/add` alone lists the pins, and `/drop PATH` (or `/drop all`) removes them. The pin list is stored with the session, so `/sessions` and `--session` restore it, and headless runs accept the same thing as a repeatable `--add PATH` flag. For `/`-less prompting the repo map ranks files by query relevance blended with a PageRank over cross-file symbol references and stops at a 1 024-token budget.
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const pkg = require('../package.json');
6
+ const lock = require('../package-lock.json');
7
+ const cargoToml = fs.readFileSync(path.resolve(__dirname, '../Cargo.toml'), 'utf8');
8
+ const installer = require('./install.js');
9
+
10
+ const root = path.resolve(__dirname, '..');
11
+ const errors = [];
12
+ const expectedRepo = 'https://github.com/whiterose717/WROSECODE-CLI.git';
13
+ const expectedHomepage = 'https://github.com/whiterose717/WROSECODE-CLI#readme';
14
+ const expectedBugs = 'https://github.com/whiterose717/WROSECODE-CLI/issues';
15
+ const expectedFiles = [
16
+ 'npm/wrosecode.js',
17
+ 'npm/install.js',
18
+ 'npm/check-publish.js',
19
+ 'LICENSE',
20
+ 'README.md'
21
+ ];
22
+ const requiredAssets = [
23
+ ['linux', 'x64', 'wrosecode-linux-x86_64'],
24
+ ['linux', 'arm64', 'wrosecode-linux-aarch64'],
25
+ ['darwin', 'x64', 'wrosecode-darwin-x86_64'],
26
+ ['darwin', 'arm64', 'wrosecode-darwin-aarch64'],
27
+ ['win32', 'x64', 'wrosecode-windows-x86_64.exe'],
28
+ ['win32', 'arm64', 'wrosecode-windows-aarch64.exe']
29
+ ];
30
+
31
+ function requireCondition(condition, message) {
32
+ if (!condition) errors.push(message);
33
+ }
34
+
35
+ requireCondition(pkg.name === 'wrosecode', 'package name must be "wrosecode".');
36
+ requireCondition(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(pkg.version || ''), 'package version must use semantic versioning.');
37
+ requireCondition(pkg.version === lock.version && lock.packages?.['']?.version === pkg.version, 'package-lock.json version does not match package.json.');
38
+ requireCondition(new RegExp(`^version\\s*=\\s*"${pkg.version.replaceAll('.', '\\.')}"$`, 'm').test(cargoToml), 'Cargo.toml version does not match package.json.');
39
+ requireCondition(typeof pkg.description === 'string' && pkg.description.trim().length > 0, 'package description is required.');
40
+ requireCondition(pkg.license === 'MIT', 'package license must be MIT.');
41
+ requireCondition(fs.existsSync(path.join(root, 'LICENSE')) && fs.readFileSync(path.join(root, 'LICENSE'), 'utf8').startsWith('MIT License'), 'MIT LICENSE file is missing or invalid.');
42
+ requireCondition(Array.isArray(pkg.keywords) && pkg.keywords.length > 0, 'at least one package keyword is required.');
43
+ requireCondition(pkg.engines?.node === '>=18', 'Node.js engine must be >=18.');
44
+ requireCondition(pkg.bin?.wrosecode === 'npm/wrosecode.js', 'wrosecode bin must point to npm/wrosecode.js.');
45
+ requireCondition(pkg.repository?.type === 'git' && pkg.repository?.url === expectedRepo, `repository.url must be ${expectedRepo}.`);
46
+ requireCondition(pkg.homepage === expectedHomepage, `homepage must be ${expectedHomepage}.`);
47
+ requireCondition(pkg.bugs?.url === expectedBugs, `bugs.url must be ${expectedBugs}.`);
48
+ requireCondition(JSON.stringify(pkg.files) === JSON.stringify(expectedFiles), 'package files allowlist must contain only the five approved npm distribution files.');
49
+
50
+ for (const relativePath of expectedFiles) {
51
+ const file = path.join(root, relativePath);
52
+ requireCondition(fs.existsSync(file) && fs.statSync(file).isFile(), `required package file is missing: ${relativePath}`);
53
+ }
54
+
55
+ const launcherPath = path.join(root, 'npm/wrosecode.js');
56
+ if (fs.existsSync(launcherPath)) {
57
+ const launcher = fs.readFileSync(launcherPath, 'utf8');
58
+ requireCondition(launcher.startsWith('#!/usr/bin/env node\n'), 'npm/wrosecode.js must have the Node.js executable shebang.');
59
+ }
60
+
61
+ const installerPath = path.join(root, 'npm/install.js');
62
+ let installerSource = '';
63
+ if (fs.existsSync(installerPath)) {
64
+ installerSource = fs.readFileSync(installerPath, 'utf8');
65
+ for (const [platform, arch, expectedAsset] of requiredAssets) {
66
+ requireCondition(installer.assetName(platform, arch) === expectedAsset, `installer asset mapping is incorrect for ${platform}/${arch}.`);
67
+ }
68
+ const previousOverride = process.env.WROSECODE_RELEASE_BASE;
69
+ delete process.env.WROSECODE_RELEASE_BASE;
70
+ try {
71
+ requireCondition(installer.releaseBase(pkg) === `https://github.com/whiterose717/WROSECODE-CLI/releases/download/v${pkg.version}`, 'installer release URL must resolve from repository metadata and package version.');
72
+ } catch (error) {
73
+ errors.push(`installer cannot derive the release URL: ${error.message}`);
74
+ } finally {
75
+ if (previousOverride === undefined) delete process.env.WROSECODE_RELEASE_BASE;
76
+ else process.env.WROSECODE_RELEASE_BASE = previousOverride;
77
+ }
78
+ requireCondition(installerSource.includes("protocol !== 'https:'") && installerSource.includes("createHash('sha256')") && installerSource.includes('actual !== expected'), 'installer must enforce HTTPS and SHA-256 verification.');
79
+ requireCondition(!/(?:YOUR_GITHUB|YOUR_REPOSITORY|example\.com|placeholder|TODO_RELEASE_URL)/i.test(installerSource), 'installer contains a placeholder release URL or TODO.');
80
+ }
81
+
82
+ const readmePath = path.join(root, 'README.md');
83
+ if (fs.existsSync(readmePath)) {
84
+ const readme = fs.readFileSync(readmePath, 'utf8');
85
+ requireCondition(readme.includes(expectedRepo.replace(/\.git$/, '')), 'README must link to the actual GitHub repository.');
86
+ requireCondition(!/(?:<YOUR_GITHUB_REPOSITORY_URL>|github\.com\/(?:example|your-org|your-user)\/)/i.test(readme), 'README contains a placeholder repository URL.');
87
+ for (const command of [
88
+ 'npm install -g wrosecode',
89
+ 'wrosecode',
90
+ 'npx wrosecode',
91
+ 'npm install -g wrosecode@latest',
92
+ 'npm uninstall -g wrosecode'
93
+ ]) requireCondition(readme.includes(command), `README is missing the npm command: ${command}`);
94
+ }
95
+
96
+ // Scan only files that npm is explicitly permitted to publish. Never emit a
97
+ // matching line or credential value in diagnostics.
98
+ const credentialPatterns = [
99
+ /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/,
100
+ /\b(?:gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{40,}|npm_[A-Za-z0-9]{30,}|AKIA[0-9A-Z]{16})\b/,
101
+ /\bsk-(?:ant|proj|live|svc)?-[A-Za-z0-9_-]{32,}\b/i,
102
+ /\b(?:ANTHROPIC_API_KEY|OPENAI_API_KEY|NPM_TOKEN|GITHUB_TOKEN|CTFD_TOKEN)\s*[:=]\s*["']?([^\s"'#]{24,})/i
103
+ ];
104
+ for (const relativePath of expectedFiles) {
105
+ const file = path.join(root, relativePath);
106
+ if (!fs.existsSync(file) || !fs.statSync(file).isFile()) continue;
107
+ const content = fs.readFileSync(file, 'utf8');
108
+ if (credentialPatterns.some((pattern) => pattern.test(content))) {
109
+ errors.push(`possible hard-coded credential detected in npm package file: ${relativePath}`);
110
+ }
111
+ }
112
+
113
+ async function verifyPublishedRelease() {
114
+ const endpoint = `https://api.github.com/repos/whiterose717/WROSECODE-CLI/releases/tags/v${pkg.version}`;
115
+ let response;
116
+ try {
117
+ response = await fetch(endpoint, {
118
+ headers: {
119
+ accept: 'application/vnd.github+json',
120
+ 'x-github-api-version': '2022-11-28',
121
+ 'user-agent': 'wrosecode-npm-publish-check'
122
+ },
123
+ signal: AbortSignal.timeout(20_000)
124
+ });
125
+ } catch (error) {
126
+ errors.push(`could not verify GitHub Release v${pkg.version}: ${error.message}`);
127
+ return;
128
+ }
129
+ if (!response.ok) {
130
+ errors.push(`GitHub Release v${pkg.version} is not publicly available (HTTP ${response.status}); push the matching tag and wait for the release workflow before publishing.`);
131
+ return;
132
+ }
133
+
134
+ let release;
135
+ try {
136
+ release = await response.json();
137
+ } catch {
138
+ errors.push('GitHub returned invalid release metadata; cannot verify native download assets.');
139
+ return;
140
+ }
141
+ requireCondition(release.tag_name === `v${pkg.version}` && !release.draft && !release.prerelease, `GitHub Release v${pkg.version} must be published (not draft or prerelease).`);
142
+ const availableAssets = new Set((release.assets || []).map((asset) => asset.name));
143
+ const missingAssets = requiredAssets.flatMap(([, , asset]) => [asset, `${asset}.sha256`]).filter((asset) => !availableAssets.has(asset));
144
+ requireCondition(missingAssets.length === 0, `GitHub Release v${pkg.version} is missing assets: ${missingAssets.join(', ')}.`);
145
+ }
146
+
147
+ if (errors.length) {
148
+ console.error('npm publish preflight failed:');
149
+ for (const error of errors) console.error(`- ${error}`);
150
+ process.exit(1);
151
+ }
152
+
153
+ verifyPublishedRelease().then(() => {
154
+ if (errors.length) {
155
+ console.error('npm publish preflight failed:');
156
+ for (const error of errors) console.error(`- ${error}`);
157
+ process.exitCode = 1;
158
+ return;
159
+ }
160
+ console.log(`npm publish preflight passed for ${pkg.name}@${pkg.version}; matching public release assets are present.`);
161
+ }).catch((error) => {
162
+ console.error(`npm publish preflight failed: ${error.message}`);
163
+ process.exitCode = 1;
164
+ });
package/npm/install.js ADDED
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs/promises');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+
8
+ const MAX_BINARY_BYTES = 300 * 1024 * 1024;
9
+ const PLATFORM_ASSETS = {
10
+ 'linux:x64': 'wrosecode-linux-x86_64',
11
+ 'linux:arm64': 'wrosecode-linux-aarch64',
12
+ 'darwin:x64': 'wrosecode-darwin-x86_64',
13
+ 'darwin:arm64': 'wrosecode-darwin-aarch64',
14
+ 'win32:x64': 'wrosecode-windows-x86_64.exe',
15
+ 'win32:arm64': 'wrosecode-windows-aarch64.exe'
16
+ };
17
+
18
+ function assetName(platform = process.platform, arch = process.arch) {
19
+ const name = PLATFORM_ASSETS[`${platform}:${arch}`];
20
+ if (!name) {
21
+ throw new Error(`Unsupported platform ${platform}/${arch}. Supported targets: Linux, macOS, and Windows on x86_64 or aarch64.`);
22
+ }
23
+ return name;
24
+ }
25
+
26
+ function releaseBase(pkg) {
27
+ if (process.env.WROSECODE_RELEASE_BASE) return process.env.WROSECODE_RELEASE_BASE.replace(/\/+$/, '');
28
+
29
+ const configured = pkg.repository?.url;
30
+ if (typeof configured !== 'string') throw new Error('package.json is missing repository.url; cannot locate the native release.');
31
+ const normalized = configured.replace(/^git\+/, '').replace(/\.git$/, '');
32
+ let repository;
33
+ try {
34
+ repository = new URL(normalized);
35
+ } catch {
36
+ throw new Error('package.json repository.url is not a valid HTTPS GitHub URL.');
37
+ }
38
+ if (repository.protocol !== 'https:' || repository.hostname.toLowerCase() !== 'github.com' || repository.pathname.toLowerCase() !== '/whiterose717/wrosecode-cli') {
39
+ throw new Error('package.json repository.url must identify https://github.com/whiterose717/WROSECODE-CLI.git.');
40
+ }
41
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(pkg.version)) throw new Error(`Invalid package version: ${pkg.version}`);
42
+ return `${repository.origin}${repository.pathname}/releases/download/v${pkg.version}`;
43
+ }
44
+
45
+ function userCacheBinaryPath(version, platform = process.platform) {
46
+ const binaryName = platform === 'win32' ? 'wrosecode.exe' : 'wrosecode';
47
+ if (platform === 'win32') {
48
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
49
+ return path.join(localAppData, 'WROSECODE', version, binaryName);
50
+ }
51
+ const cacheHome = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
52
+ return path.join(cacheHome, 'wrosecode', version, binaryName);
53
+ }
54
+
55
+ async function fetchHttps(url) {
56
+ const parsed = new URL(url);
57
+ if (parsed.protocol !== 'https:') throw new Error('Release downloads must use HTTPS.');
58
+ const response = await fetch(parsed, { signal: AbortSignal.timeout(120_000), redirect: 'follow' });
59
+ if (!response.ok) throw new Error(`Download failed (${response.status} ${response.statusText}): ${parsed}`);
60
+ if (new URL(response.url).protocol !== 'https:') throw new Error('Release download redirected to a non-HTTPS URL.');
61
+ return response;
62
+ }
63
+
64
+ async function readLimited(response, limit) {
65
+ if (!response.body) throw new Error('Release server returned an empty response body.');
66
+ const reader = response.body.getReader();
67
+ const chunks = [];
68
+ let size = 0;
69
+ while (true) {
70
+ const { done, value } = await reader.read();
71
+ if (done) break;
72
+ size += value.byteLength;
73
+ if (size > limit) {
74
+ await reader.cancel();
75
+ throw new Error('Downloaded release asset exceeds the allowed size.');
76
+ }
77
+ chunks.push(Buffer.from(value));
78
+ }
79
+ return Buffer.concat(chunks, size);
80
+ }
81
+
82
+ async function installBinary() {
83
+ // Explicit override is intended for local builds and pack/install smoke tests.
84
+ if (process.env.WROSECODE_BINARY) return;
85
+
86
+ const pkg = require('../package.json');
87
+ const base = releaseBase(pkg);
88
+ const name = assetName();
89
+ const assetUrl = `${base}/${name}`;
90
+
91
+ const binary = await readLimited(await fetchHttps(assetUrl), MAX_BINARY_BYTES);
92
+ if (binary.length === 0) throw new Error(`Release asset is empty: ${name}`);
93
+
94
+ const checksumBytes = await readLimited(await fetchHttps(`${assetUrl}.sha256`), 1024);
95
+ const checksumText = checksumBytes.toString('utf8').trim();
96
+ const checksumMatch = /^([a-fA-F0-9]{64})(?:\s+\*?([^\s]+))?$/.exec(checksumText);
97
+ if (!checksumMatch || (checksumMatch[2] && path.basename(checksumMatch[2]) !== name)) {
98
+ throw new Error(`Invalid SHA-256 sidecar for ${name}.`);
99
+ }
100
+ const expected = checksumMatch[1].toLowerCase();
101
+ const actual = crypto.createHash('sha256').update(binary).digest('hex');
102
+ if (actual !== expected) throw new Error(`SHA-256 verification failed for ${name}.`);
103
+
104
+ const binDir = path.join(__dirname, 'bin');
105
+ const destination = path.join(binDir, process.platform === 'win32' ? 'wrosecode.exe' : 'wrosecode');
106
+ try {
107
+ await writeBinary(destination, binary);
108
+ } catch (error) {
109
+ // Global installs may be root-owned while npm lifecycle scripts are
110
+ // disabled. Keep the executable in the user's cache in that case.
111
+ if (!['EACCES', 'EPERM', 'EROFS'].includes(error.code)) throw error;
112
+ await writeBinary(userCacheBinaryPath(pkg.version), binary);
113
+ }
114
+ }
115
+
116
+ async function writeBinary(destination, binary) {
117
+ const destinationDir = path.dirname(destination);
118
+ const temporary = `${destination}.tmp-${process.pid}`;
119
+ await fs.mkdir(destinationDir, { recursive: true, mode: 0o700 });
120
+ try {
121
+ await fs.writeFile(temporary, binary, { mode: 0o755, flag: 'wx' });
122
+ if (process.platform !== 'win32') await fs.chmod(temporary, 0o755);
123
+ try {
124
+ await fs.rename(temporary, destination);
125
+ } catch (error) {
126
+ if (process.platform !== 'win32' || !['EEXIST', 'EPERM', 'ENOTEMPTY'].includes(error.code)) throw error;
127
+ await fs.rm(destination, { force: true });
128
+ await fs.rename(temporary, destination);
129
+ }
130
+ } finally {
131
+ await fs.rm(temporary, { force: true });
132
+ }
133
+ }
134
+
135
+ if (require.main === module) {
136
+ installBinary().catch((error) => {
137
+ console.error(`wrosecode: native binary installation failed: ${error.message}`);
138
+ process.exitCode = 1;
139
+ });
140
+ }
141
+
142
+ module.exports = { assetName, releaseBase, userCacheBinaryPath };
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawn, spawnSync } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const pkg = require('../package.json');
10
+ const { userCacheBinaryPath } = require('./install.js');
11
+ const packagedBinary = path.join(__dirname, 'bin', process.platform === 'win32' ? 'wrosecode.exe' : 'wrosecode');
12
+ const cachedBinary = userCacheBinaryPath(pkg.version);
13
+ let binary = process.env.WROSECODE_BINARY || [packagedBinary, cachedBinary].find((candidate) => fs.existsSync(candidate)) || null;
14
+
15
+ if (!binary) {
16
+ // npm can be configured to skip lifecycle scripts. Install lazily on first run
17
+ // in that case, but fail instead of launching without a native executable.
18
+ const installer = spawnSync(process.execPath, [path.join(__dirname, 'install.js')], { stdio: 'inherit' });
19
+ if (installer.error) {
20
+ console.error(`wrosecode: could not run native installer: ${installer.error.message}`);
21
+ process.exit(1);
22
+ }
23
+ if (installer.status !== 0) process.exit(installer.status ?? 1);
24
+ binary = [packagedBinary, cachedBinary].find((candidate) => fs.existsSync(candidate)) || null;
25
+ }
26
+
27
+ if (!binary) {
28
+ console.error('wrosecode: native executable is missing after installation. Reinstall the package or set WROSECODE_BINARY to an executable path.');
29
+ process.exit(1);
30
+ }
31
+
32
+ const child = spawn(binary, process.argv.slice(2), {
33
+ stdio: 'inherit',
34
+ windowsHide: true
35
+ });
36
+
37
+ let signalForwarded = false;
38
+ const signalsToForward = process.stdin.isTTY && process.stdout.isTTY ? ['SIGTERM'] : ['SIGINT', 'SIGTERM'];
39
+ for (const signal of signalsToForward) {
40
+ process.on(signal, () => {
41
+ if (signalForwarded || child.exitCode !== null || child.signalCode !== null || child.pid === undefined) return;
42
+ signalForwarded = true;
43
+ try {
44
+ child.kill(signal);
45
+ } catch (error) {
46
+ if (error.code !== 'ESRCH') {
47
+ console.error(`wrosecode: could not forward ${signal} to native process: ${error.message}`);
48
+ }
49
+ }
50
+ });
51
+ }
52
+
53
+ let spawnFailed = false;
54
+ child.once('error', (error) => {
55
+ spawnFailed = true;
56
+ console.error(`wrosecode: could not start native executable: ${error.message}`);
57
+ process.exitCode = 1;
58
+ });
59
+
60
+ child.once('close', (code, signal) => {
61
+ if (spawnFailed) return;
62
+ if (code !== null) {
63
+ process.exitCode = code;
64
+ } else if (signal) {
65
+ process.exitCode = 128 + (os.constants.signals[signal] || 1);
66
+ } else if (process.exitCode === undefined) {
67
+ process.exitCode = 1;
68
+ }
69
+ });
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "wrosecode",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "A terminal coding agent written in Rust",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "cli",
8
+ "coding-agent",
9
+ "developer-tools",
10
+ "terminal",
11
+ "rust"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/whiterose717/WROSECODE-CLI.git"
19
+ },
20
+ "homepage": "https://github.com/whiterose717/WROSECODE-CLI#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/whiterose717/WROSECODE-CLI/issues"
23
+ },
24
+ "bin": {
25
+ "wrosecode": "npm/wrosecode.js"
26
+ },
27
+ "files": [
28
+ "npm/wrosecode.js",
29
+ "npm/install.js",
30
+ "npm/check-publish.js",
31
+ "LICENSE",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "cargo build --release --locked",
36
+ "check": "node --check npm/wrosecode.js && node --check npm/install.js && node --check npm/check-publish.js",
37
+ "postinstall": "node npm/install.js",
38
+ "prepack": "npm run check",
39
+ "prepublishOnly": "npm run check && node npm/check-publish.js"
40
+ }
41
+ }