kimiflare 0.9.2 → 0.11.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/README.md CHANGED
@@ -11,36 +11,29 @@
11
11
  </p>
12
12
 
13
13
  <p align="center">
14
- A terminal coding agent powered by <strong><a href="https://developers.cloudflare.com/workers-ai/models/kimi-k2.6/">Kimi-K2.6</a></strong> on Cloudflare Workers AI. Moonshot's 1T-parameter open-source model runs directly on your Cloudflare account. You bring the token, your traffic goes straight to Cloudflare.
14
+ <strong>A terminal coding agent powered by <a href="https://developers.cloudflare.com/workers-ai/models/kimi-k2.6/">Kimi-K2.6</a> on Cloudflare Workers AI.</strong><br>
15
+ Moonshot's 1T-parameter open-source model, running directly on your Cloudflare account.
15
16
  </p>
16
17
 
17
- ```
18
- $ kimiflare
19
- kimiflare · /help for commands · ctrl-c to exit
20
-
21
- › what files are here?
22
- ✓ glob(*)
23
- /Users/you/proj/package.json
24
- /Users/you/proj/src/index.ts
25
- ...
26
-
27
- › add a /health endpoint to server.ts
28
- ✓ read(src/server.ts)
29
- ◐ edit src/server.ts
30
- ─── permission requested ──────────────────
31
- @@ -42,6 +42,10 @@
32
- app.get('/', …)
33
- + app.get('/health', (_, res) => res.json({ ok: true }))
34
- ─────────────────────────────────────────────
35
- [Allow once] [Allow for session] [Deny]
36
- ```
18
+ <p align="center">
19
+ <img src="docs/screenshot.png" alt="kimiflare TUI" width="900">
20
+ </p>
37
21
 
38
- ## Install
22
+ ## Why kimiflare
23
+
24
+ - **262k context window** — Read entire modules, large configs, and full stack traces without the model losing track.
25
+ - **Direct to Cloudflare** — No AI Gateway, no proxy, no OpenAI SDK. Your traffic goes straight to Workers AI from your account.
26
+ - **Plan mode** — Ask the agent to research and produce a plan without touching your filesystem. Review it, then exit plan mode to execute.
27
+
28
+ ## Quick start
39
29
 
40
30
  ```sh
41
31
  npm install -g kimiflare
32
+ kimiflare
42
33
  ```
43
34
 
35
+ On first run, an interactive onboarding wizard asks for your Cloudflare Account ID and API Token. That's it — you're ready.
36
+
44
37
  Or run without installing:
45
38
 
46
39
  ```sh
@@ -49,6 +42,24 @@ npx kimiflare
49
42
 
50
43
  Requires Node.js ≥ 20.
51
44
 
45
+ ## Features
46
+
47
+ | Feature | What it does |
48
+ |---------|-------------|
49
+ | **Plan / Edit / Auto modes** | `plan` blocks all mutating tools for safe research. `edit` (default) prompts per mutating call. `auto` approves everything for trusted tasks. |
50
+ | **Live task panel** | For multi-step work, the agent publishes a task list with progress icons (■ active, ☐ pending, ✓ done), elapsed time, and token deltas. |
51
+ | **14 terminal themes** | dark, light, high-contrast, dracula, nord, one-dark, monokai, solarized-dark/light, tokyo-night, gruvbox-dark/light, catppuccin-mocha, rose-pine. Interactive picker with live preview (`Ctrl+T`). |
52
+ | **Paste collapse** | Large pastes (≥200 chars or ≥2 newlines) collapse to `[pasted N lines #id]`. Full content still goes to the model — scrollback stays clean. |
53
+ | **Type-ahead queue** | Type your next prompt while the model is still working. Queued prompts show as `⏳ …` and fire in order. `Ctrl-C` aborts current + clears queue. |
54
+ | **Auto-compaction** | At ~80% context usage, kimiflare nudges you to run `/compact`. It summarizes older turns into a dense summary, keeping the last 4 turns intact. |
55
+ | **Streaming reasoning** | Toggle the model's chain-of-thought with `/reasoning` or `Ctrl-R`. See how it thinks in real time. |
56
+ | **Live cost tracking** | Status bar shows real-time cost based on Cloudflare pricing: `$0.95/M input`, `$0.16/M cached`, `$4.00/M output`. |
57
+ | **Session persistence** | Every turn is auto-saved. `/resume` lists past sessions (with message counts) in a paginated picker. |
58
+ | **Smart permissions** | Bash session-allow is keyed by the first token (e.g., allow all `git` commands). Write/edit show a unified diff before you approve. |
59
+ | **Project context (`/init`)** | Scans your repo and writes a concise `KIMI.md` — build commands, layout, conventions. Auto-loaded on every launch. |
60
+ | **Co-author auto-append** | Detects `git commit` commands and auto-injects `Co-authored-by: kimiflare <kimiflare@proton.me>`. |
61
+ | **Resilient transport** | Retries Cloudflare capacity errors (code 3040) and 5xx with exponential backoff up to 5 attempts. |
62
+
52
63
  ## Configure
53
64
 
54
65
  Get credentials from Cloudflare:
@@ -79,50 +90,85 @@ chmod 600 ~/.config/kimiflare/config.json
79
90
 
80
91
  ## Usage
81
92
 
93
+ ### Interactive TUI
94
+
82
95
  ```sh
83
- kimiflare # interactive TUI
84
- kimiflare -p "summarize PLAN.md" # one-shot, streams answer to stdout
85
- kimiflare -p "..." --dangerously-allow-all # auto-approve mutating tools (for scripts)
96
+ kimiflare # launch TUI
86
97
  kimiflare --model @cf/moonshotai/kimi-k2.6 # override model
87
- kimiflare --reasoning # (print mode) stream chain-of-thought to stderr
88
98
  ```
89
99
 
90
- Interactive slash commands:
100
+ ### Print mode (one-shot, non-interactive)
101
+
102
+ ```sh
103
+ kimiflare -p "summarize PLAN.md" # stream answer to stdout
104
+ kimiflare -p "..." --dangerously-allow-all # auto-approve mutating tools (for scripts)
105
+ kimiflare -p "..." --reasoning # include chain-of-thought in stderr
106
+ ```
107
+
108
+ ### CLI flags
109
+
110
+ | Flag | Short | Description |
111
+ |------|-------|-------------|
112
+ | `--print <prompt>` | `-p` | One-shot mode: send prompt, stream reply, exit |
113
+ | `--model <id>` | `-m` | Model ID (default: `@cf/moonshotai/kimi-k2.6`) |
114
+ | `--dangerously-allow-all` | — | Auto-approve every permission prompt (print mode only) |
115
+ | `--reasoning` | — | Stream chain-of-thought to stderr (print mode only) |
116
+ | `--version` | `-V` | Show version |
117
+ | `--help` | `-h` | Show help |
118
+
119
+ ## Slash commands
91
120
 
92
- | Command | Effect |
93
- |-----------------------------|---------------------------------------------------------------------------------|
94
- | `/mode edit\|plan\|auto` | Switch mode. `edit` prompts for permission (default), `plan` is read-only research, `auto` auto-approves every tool call. |
95
- | `/plan` `/auto` `/edit` | Shortcuts for the three modes. |
121
+ | Command | Effect |
122
+ |---------|--------|
123
+ | `/mode edit\|plan\|auto` | Switch mode. `edit` prompts for permission (default), `plan` is read-only research, `auto` auto-approves every tool call. |
124
+ | `/plan` `/auto` `/edit` | Shortcuts for the three modes. |
96
125
  | `/thinking low\|medium\|high` | Reasoning effort. `low` = fastest, shallow; `medium` = balanced (default); `high` = deepest, slowest. Saved to config. |
97
- | `/theme NAME` | Switch color scheme: `dark` (default), `light` (bright terminals), `high-contrast`. Saved to config. |
98
- | `/resume` | Pick a past conversation to restore. |
99
- | `/compact` | Summarize older turns to free context. Suggested automatically at ~80% full. |
100
- | `/init` | Scan the repo and write a `KIMI.md` so future agents have project context. |
101
- | `/reasoning` | Toggle chain-of-thought display. |
102
- | `/clear` | Reset the current conversation. |
103
- | `/cost` `/model` `/update` | Info commands. |
104
- | `/logout` | Clear saved credentials. |
105
- | `/help` `/exit` | List commands / quit. |
106
-
107
- Keys: `Shift+Tab` cycles mode · `Ctrl-R` toggles reasoning · `Ctrl-O` toggles verbose tool output · `Ctrl-C` interrupts an in-flight turn (press again to exit) · `↑`/`↓` walks prompt history.
108
-
109
- Editing keys (macOS):
110
-
111
- - `⌥←` / `⌥→` — jump word left/right (also works with `Esc b` / `Esc f`)
112
- - `⌘←` / `⌘→` — jump to start / end of line (in iTerm2's default profile; in Terminal.app you may need to map these to send `Ctrl-A` / `Ctrl-E`)
113
- - `⌥⌫` — delete word backward
114
- - `⌘⌫` — delete to start of line (iTerm2 sends this as `Ctrl-U`; map in Terminal.app if needed)
115
- - `⌥⌦` delete word forward
116
- - `Ctrl-A` / `Ctrl-E` — start / end of line (always works)
117
- - `Ctrl-W` / `Ctrl-U` / `Ctrl-K` delete word backward / to start of line / to end of line
118
-
119
- ### Modes
126
+ | `/theme` | Interactive theme picker with live preview (`Ctrl+T`). Saved to config. |
127
+ | `/theme NAME` | Set theme by name directly. |
128
+ | `/resume` | Pick a past conversation to restore. |
129
+ | `/compact` | Summarize older turns to free context. Suggested automatically at ~80% full. |
130
+ | `/init` | Scan the repo and write a `KIMI.md` so future agents have project context. |
131
+ | `/reasoning` | Toggle chain-of-thought display. |
132
+ | `/clear` | Reset the current conversation. |
133
+ | `/cost` | Show token usage for the current turn. |
134
+ | `/model` | Show current model. |
135
+ | `/update` | Check for updates manually. |
136
+ | `/logout` | Clear saved credentials. |
137
+ | `/help` | List all commands. |
138
+ | `/exit` | Quit. |
139
+
140
+ ## Keyboard shortcuts
141
+
142
+ ### Global
143
+
144
+ | Shortcut | Action |
145
+ |----------|--------|
146
+ | `Ctrl+C` | Interrupt current turn (press again to exit) |
147
+ | `Ctrl+R` | Toggle reasoning display |
148
+ | `Ctrl+O` | Toggle verbose tool output |
149
+ | `Ctrl+T` | Open theme picker |
150
+ | `Shift+Tab` | Cycle mode (edit → plan → auto) |
151
+ | `↑` / `↓` | Walk prompt history |
152
+
153
+ ### Editing (macOS / Linux)
154
+
155
+ | Shortcut | Action |
156
+ |----------|--------|
157
+ | `⌥←` / `⌥→` | Jump word left/right |
158
+ | `⌘←` / `⌘→` | Jump to start / end of line |
159
+ | `⌥⌫` | Delete word backward |
160
+ | `⌘⌫` | Delete to start of line |
161
+ | `⌥⌦` | Delete word forward |
162
+ | `Ctrl+A` / `Ctrl+E` | Start / end of line |
163
+ | `Ctrl+W` / `Ctrl+U` / `Ctrl+K` | Delete word backward / to start / to end of line |
164
+
165
+ ## Modes
120
166
 
121
167
  - **edit** — default. The agent calls tools freely for read-only work; mutating tools (`write`, `edit`, `bash`) pause for your approval.
122
168
  - **plan** — read-only. Mutating tools are hard-blocked. Ask "plan a refactor" and the agent will investigate and produce a plan without touching the filesystem. Exit plan mode to execute.
123
169
  - **auto** — autonomous. Every tool call is auto-approved. Use for trusted, well-scoped tasks.
124
170
 
125
- ### Thinking level (quality vs speed)
171
+ ## Thinking level (quality vs speed)
126
172
 
127
173
  Kimi-K2.6 always reasons, but you can cap the effort:
128
174
 
@@ -132,52 +178,26 @@ Kimi-K2.6 always reasons, but you can cap the effort:
132
178
 
133
179
  Set with `/thinking medium` (persists), or per-launch via `KIMI_REASONING_EFFORT=high`.
134
180
 
135
- ### Type-ahead queue
136
-
137
- You can type the next prompt while the model is still executing. Submitted prompts show up as `⏳ …` and fire in order as each turn completes. `Ctrl-C` aborts the current turn and clears the queue.
138
-
139
- ### Session persistence
140
-
141
- Sessions are saved to `~/.local/share/kimiflare/sessions/` after each turn. `/resume` lists the most recent (with first prompt + message count) so you can pick one up later.
142
-
143
- ### Task panel
144
-
145
- For multi-step requests, the agent can publish a live task list via the `tasks_set` tool. The panel shows progress inline with status icons (`■` active, `☐` pending, `✓` done), elapsed time, and tokens consumed for the current task batch. Press `Ctrl-O` while a turn is running to switch tool output between compact (first line) and verbose (full output) modes.
146
-
147
- ### Paste collapse
148
-
149
- Paste a large block (≥ 200 chars or ≥ 3 newlines in one paste) into the prompt and the input collapses it to `[pasted N lines #id]`. The full content still goes to the model on submit — only the on-screen display and chat history are collapsed, so scrollback doesn't get buried by a wall of code.
150
-
151
- ### Project context (KIMI.md)
152
-
153
- Run `/init` inside a repo and kimiflare scans the project (reads `package.json`, `README`, source layout, etc.) and writes a concise `KIMI.md` at the repo root — project overview, build/test commands, conventions, quirks. On every subsequent launch in that directory, `KIMI.md` (or `KIMIFLARE.md` or `AGENT.md`, whichever exists) is auto-loaded into the system prompt so the agent already "knows" the project. If the file already exists, `/init` refuses so you don't overwrite hand-edited context.
154
-
155
- ## Why
156
-
157
- - **262k context.** Read entire modules without pagination.
158
- - **Native tool use.** File I/O, shell, globs, grep, web fetch — all wired up, with per-call approval for anything mutating.
159
- - **Streaming reasoning + content.** The model's chain-of-thought streams separately; toggle with `/reasoning` or `Ctrl-R`.
160
- - **Pay your own way.** Your Cloudflare account, your credits, your rate limits. `$0.95 / M input`, `$0.16 / M cached input`, `$4.00 / M output`. The bottom status line shows live cost.
161
-
162
181
  ## Tools
163
182
 
164
183
  All tool calls show inline; mutating ones require per-call approval the first time, with an option to allow for the rest of the session.
165
184
 
166
- | Tool | Permission | What it does |
167
- |-------------|------------|--------------|
168
- | `read` | auto | Read a text file (≤ 2MB) with optional line range. |
169
- | `write` | prompt | Create or overwrite a file. Shows a unified diff before you approve. |
170
- | `edit` | prompt | Replace an exact substring. Fails unless `old_string` is unique (or `replace_all=true`). |
171
- | `bash` | prompt | Run a shell command via `bash -lc`. Session-allow is keyed by the first token of the command. |
172
- | `glob` | auto | Match files by pattern (`**/*.ts`), sorted by mtime. |
173
- | `grep` | auto | Regex search. Uses `rg` if installed; falls back to a JS walk. |
174
- | `web_fetch` | auto | Fetch a URL, convert HTML → markdown (≤ 100KB). |
185
+ | Tool | Permission | What it does |
186
+ |------|------------|--------------|
187
+ | `read` | auto | Read a text file (≤ 2MB) with optional line range. |
188
+ | `write` | prompt | Create or overwrite a file. Shows a unified diff before you approve. |
189
+ | `edit` | prompt | Replace an exact substring. Fails unless `old_string` is unique (or `replace_all=true`). |
190
+ | `bash` | prompt | Run a shell command via `bash -lc`. Session-allow is keyed by the first token of the command. |
191
+ | `glob` | auto | Match files by pattern (`**/*.ts`), sorted by mtime. |
192
+ | `grep` | auto | Regex search. Uses `rg` if installed; falls back to a JS walk. |
193
+ | `web_fetch` | auto | Fetch a URL, convert HTML → markdown (≤ 100KB). |
194
+ | `tasks_set` | auto | Publish a live task list for multi-step work. |
175
195
 
176
196
  ## How it works
177
197
 
178
198
  ```
179
199
  ┌───────────────────────────────────────────────────────────┐
180
- │ kimiflare (Node + Ink TUI)
200
+ │ kimiflare (Node.js TUI)
181
201
  user ─▶ │ │
182
202
  │ user msg ─▶ agent loop ─▶ runKimi() ──[POST SSE]──▶ │
183
203
  │ ▲ │
@@ -204,9 +224,23 @@ npm run build
204
224
  npm link # or: ln -s "$PWD/bin/kimiflare.mjs" ~/.local/bin/kimiflare
205
225
  ```
206
226
 
207
- ## Status
227
+ Scripts:
228
+ - `npm run build` — bundle with tsup (`dist/` + `bin/kimiflare.mjs`)
229
+ - `npm run dev` — run via tsx (`tsx src/index.tsx`)
230
+ - `npm run typecheck` — `tsc --noEmit`
231
+ - `npm start` — run compiled bin
232
+
233
+ ## Contributing
234
+
235
+ Contributions are welcome!
208
236
 
209
- Early but functional. Transport + tools + agent loop + print mode are verified end-to-end. Interactive TUI ships modes, themes, thinking levels, session resume, compaction, and type-ahead queue.
237
+ 1. Fork the repository
238
+ 2. Create a branch: `git checkout -b feat/your-feature`
239
+ 3. Make your changes
240
+ 4. Run `npm run typecheck` and `npm run build`
241
+ 5. Commit: `git commit -m "feat: description"`
242
+ 6. Push: `git push origin feat/your-feature`
243
+ 7. Open a Pull Request
210
244
 
211
245
  ## License
212
246
 
package/dist/index.js CHANGED
@@ -489,21 +489,209 @@ function nextMode(m) {
489
489
  function isBlockedInPlanMode(toolName) {
490
490
  return MUTATING_TOOLS.has(toolName);
491
491
  }
492
+ function tokenizeCommand(command) {
493
+ const tokens = [];
494
+ let current = "";
495
+ let inQuote = null;
496
+ for (const ch of command) {
497
+ if (inQuote) {
498
+ if (ch === inQuote) {
499
+ inQuote = null;
500
+ } else {
501
+ current += ch;
502
+ }
503
+ } else if (ch === '"' || ch === "'") {
504
+ inQuote = ch;
505
+ } else if (/\s/.test(ch)) {
506
+ if (current) {
507
+ tokens.push(current);
508
+ current = "";
509
+ }
510
+ } else {
511
+ current += ch;
512
+ }
513
+ }
514
+ if (current) tokens.push(current);
515
+ return tokens;
516
+ }
517
+ function isReadOnlyBash(command) {
518
+ const trimmed = command.trim();
519
+ if (!trimmed) return false;
520
+ if (DANGEROUS_PATTERNS.test(trimmed)) return false;
521
+ const tokens = tokenizeCommand(trimmed);
522
+ if (tokens.length === 0) return false;
523
+ const first = tokens[0];
524
+ let cmdIndex = 0;
525
+ if (first === "cd" && tokens.length >= 3 && tokens[1] && tokens[2] === "&&") {
526
+ cmdIndex = 3;
527
+ }
528
+ const cmd = tokens[cmdIndex];
529
+ if (!cmd) return false;
530
+ const args = tokens.slice(cmdIndex + 1);
531
+ if (cmd === "git") {
532
+ const sub = args[0] ?? "";
533
+ const allowed = GIT_READONLY_SUBCOMMANDS[sub];
534
+ if (allowed === void 0) return false;
535
+ if (allowed === true) return true;
536
+ switch (sub) {
537
+ case "branch":
538
+ return !args.some((a) => /^-[dDmMcC]/.test(a));
539
+ case "stash":
540
+ return args[1] === "list";
541
+ case "remote":
542
+ return args[1] === "-v" || args[1] === "--verbose" || args.length === 1;
543
+ case "tag":
544
+ return args[1] === "-l" || args[1] === "--list" || args.length === 1;
545
+ case "config":
546
+ return args[1] === "--list" || args[1]?.startsWith("--get") === true || args.length === 1;
547
+ default:
548
+ return false;
549
+ }
550
+ }
551
+ const argCheck = COMMANDS_NEEDING_ARG_CHECK[cmd];
552
+ if (argCheck) {
553
+ return argCheck(args);
554
+ }
555
+ return READONLY_COMMANDS.has(cmd);
556
+ }
492
557
  function systemPromptForMode(m) {
493
558
  if (m === "plan") {
494
- return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or bash. Only use read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
559
+ return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or mutating bash commands. You may use read-only bash commands (e.g., git log, git diff, ls, cat) along with read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
495
560
  }
496
561
  if (m === "auto") {
497
562
  return "\n\nAUTO MODE is active. The user has opted into autonomous execution \u2014 every tool call will be auto-approved. Work efficiently, but do not take irreversible destructive actions (rm -rf, git push --force, dropping tables, etc.) without pausing to describe them in chat first. Prefer smaller reversible steps.";
498
563
  }
499
564
  return "";
500
565
  }
501
- var MODES, MUTATING_TOOLS;
566
+ var MODES, MUTATING_TOOLS, DANGEROUS_PATTERNS, GIT_READONLY_SUBCOMMANDS, READONLY_COMMANDS, COMMANDS_NEEDING_ARG_CHECK;
502
567
  var init_mode = __esm({
503
568
  "src/mode.ts"() {
504
569
  "use strict";
505
570
  MODES = ["edit", "plan", "auto"];
506
571
  MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "bash"]);
572
+ DANGEROUS_PATTERNS = /[<>|;&`$]|\$\(|\$\{|&&|\|\||\b&\s*$/;
573
+ GIT_READONLY_SUBCOMMANDS = {
574
+ log: true,
575
+ diff: true,
576
+ status: true,
577
+ show: true,
578
+ blame: true,
579
+ describe: true,
580
+ "rev-parse": true,
581
+ "ls-files": true,
582
+ reflog: true,
583
+ shortlog: true,
584
+ whatchanged: true,
585
+ grep: true,
586
+ branch: false,
587
+ // needs check: block -d/-D/-m/-M/-c/-C
588
+ stash: false,
589
+ // needs check: only allow "list"
590
+ remote: false,
591
+ // needs check: only allow -v
592
+ tag: false,
593
+ // needs check: only allow -l
594
+ config: false
595
+ // needs check: only allow --list/--get
596
+ };
597
+ READONLY_COMMANDS = /* @__PURE__ */ new Set([
598
+ // File system
599
+ "ls",
600
+ "cat",
601
+ "head",
602
+ "tail",
603
+ "pwd",
604
+ "echo",
605
+ "file",
606
+ "stat",
607
+ "readlink",
608
+ "realpath",
609
+ "dirname",
610
+ "basename",
611
+ "wc",
612
+ "sort",
613
+ "uniq",
614
+ "diff",
615
+ "cmp",
616
+ // Search
617
+ "grep",
618
+ "rg",
619
+ "ag",
620
+ "fd",
621
+ // System info
622
+ "ps",
623
+ "df",
624
+ "du",
625
+ "env",
626
+ "printenv",
627
+ "which",
628
+ "whereis",
629
+ "uname",
630
+ "hostname",
631
+ "uptime",
632
+ "free",
633
+ "date",
634
+ "id",
635
+ "whoami",
636
+ "groups",
637
+ // Dev tools (version/info only)
638
+ "node",
639
+ "npx",
640
+ "python3",
641
+ "ruby",
642
+ "perl",
643
+ // Utilities
644
+ "jq",
645
+ "yq",
646
+ "awk",
647
+ "cut",
648
+ "tr",
649
+ "base64",
650
+ "sha256sum",
651
+ "md5sum",
652
+ "shasum",
653
+ "hexdump",
654
+ "xxd",
655
+ "strings",
656
+ "less",
657
+ "more",
658
+ "man",
659
+ "clear",
660
+ "history",
661
+ // Archive inspection
662
+ "zipinfo",
663
+ // Network
664
+ "ping",
665
+ "netstat",
666
+ "ss",
667
+ "lsof"
668
+ ]);
669
+ COMMANDS_NEEDING_ARG_CHECK = {
670
+ find: (args) => !args.some((a) => a === "-delete" || a === "-exec"),
671
+ sed: (args) => !args.some((a) => a === "-i" || a.startsWith("-i")),
672
+ tar: (args) => args[0] === "-tf" || args[0] === "--list",
673
+ unzip: (args) => args[0] === "-l",
674
+ curl: (args) => !args.some((a) => a === "-o" || a === "-O" || a === "-d" || a === "--data" || a.startsWith("-X")),
675
+ wget: (args) => !args.some((a) => a === "-O" || a === "--output-document" || a.startsWith("--post")),
676
+ npm: (args) => ["list", "view", "config"].includes(args[0] ?? "") && !(args[0] === "config" && args[1] && !args[1].startsWith("get") && args[1] !== "list"),
677
+ tsc: (args) => args.every(
678
+ (a) => ["--noEmit", "--version", "--showConfig", "--help", "-h", "--init"].includes(a)
679
+ ),
680
+ eslint: (args) => args.every(
681
+ (a) => ["--version", "--print-config", "--help", "-h"].includes(a) || !a.startsWith("-")
682
+ ),
683
+ prettier: (args) => args.every(
684
+ (a) => ["--version", "--check", "--help", "-h"].includes(a) || !a.startsWith("-")
685
+ ),
686
+ jest: (args) => args.every(
687
+ (a) => ["--version", "--listTests", "--showConfig", "--help", "-h"].includes(a) || !a.startsWith("-")
688
+ ),
689
+ vitest: (args) => args.every(
690
+ (a) => ["--version", "--help", "-h"].includes(a) || !a.startsWith("-")
691
+ ),
692
+ go: (args) => ["version", "env", "list", "mod"].includes(args[0] ?? "") && !(args[0] === "mod" && args[1] && !["graph", "download", "why", "verify"].includes(args[1])),
693
+ cargo: (args) => ["--version", "-V", "check", "test", "metadata"].includes(args[0] ?? "") && !(args[0] === "test" && args.includes("--no-run") === false)
694
+ };
507
695
  }
508
696
  });
509
697
 
@@ -3711,7 +3899,26 @@ function App({ initialCfg, initialUpdateResult }) {
3711
3899
  setUsage(u);
3712
3900
  },
3713
3901
  askPermission: (req) => new Promise((resolve2) => {
3714
- if (modeRef.current === "auto") return resolve2("allow");
3902
+ if (modeRef.current === "auto") {
3903
+ resolve2("allow");
3904
+ return;
3905
+ }
3906
+ if (modeRef.current === "plan" && isBlockedInPlanMode(req.tool.name)) {
3907
+ if (req.tool.name === "bash" && typeof req.args.command === "string" && isReadOnlyBash(req.args.command)) {
3908
+ resolve2("allow");
3909
+ return;
3910
+ }
3911
+ setEvents((e) => [
3912
+ ...e,
3913
+ {
3914
+ kind: "info",
3915
+ key: mkKey(),
3916
+ text: `plan mode blocked ${req.tool.name}; exit plan mode to execute`
3917
+ }
3918
+ ]);
3919
+ resolve2("deny");
3920
+ return;
3921
+ }
3715
3922
  setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
3716
3923
  })
3717
3924
  }
@@ -4106,6 +4313,10 @@ use: /thinking low | medium | high`
4106
4313
  return;
4107
4314
  }
4108
4315
  if (modeRef.current === "plan" && isBlockedInPlanMode(req.tool.name)) {
4316
+ if (req.tool.name === "bash" && typeof req.args.command === "string" && isReadOnlyBash(req.args.command)) {
4317
+ resolve2("allow");
4318
+ return;
4319
+ }
4109
4320
  setEvents((e) => [
4110
4321
  ...e,
4111
4322
  {