flashlite 0.0.0-stage → 0.1.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
@@ -1,3 +1,584 @@
1
- # Temporary Holding Version
1
+ # flashlite
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 personal agentic coding CLI — Claude Code / pi style — for any OpenAI- or
4
+ Anthropic-compatible chat endpoint. Streaming chat REPL plus an agent loop
5
+ with **filesystem tools** for creating and organizing projects, reading/editing code,
6
+ searching, copying assets, and removal, plus **terminal commands** for builds, tests,
7
+ and other requested work. Opt-in **guarded auto** runs arbitrary terminal commands
8
+ without prompts, but still asks before network access, installs, sudo, paths outside
9
+ the project, and destructive Git operations — and refuses obviously catastrophic
10
+ deletion/disk commands. A separate **unguarded auto** mode permits whole-drive file
11
+ access and every command; only recognizable catastrophic actions ask.
12
+
13
+ TypeScript on Node ≥ 24, run straight from source via Node's native type stripping:
14
+ **zero build step, zero runtime dependencies** (only `typescript` + `@types/node` as
15
+ devDependencies for typechecking). macOS only.
16
+
17
+ ## Setup
18
+
19
+ ```bash
20
+ npm install # devDependencies only (typescript + @types/node)
21
+ npm run typecheck # tsc --noEmit
22
+ npm test # node --test — the full suite is offline
23
+
24
+ # put the CLI (command `flash`) on your PATH (no root needed):
25
+ mkdir -p ~/.local/bin
26
+ ln -s "$PWD/scripts/flashlite-wrapper.sh" ~/.local/bin/flash # or copy it; chmod +x
27
+ ```
28
+
29
+ On first interactive launch, `flash` asks for your endpoint URL and then your API key
30
+ with masked input, saving both to `~/.config/flashlite/config.json` (chmod 600). Each
31
+ local user provides their own endpoint and key; nothing is bundled with the npm package
32
+ or baked into the binary. Keys are never printed, logged, or committed — not even
33
+ masked. Non-interactive runs require an existing config file (or `--base-url`).
34
+ Configuration is validated before
35
+ use: `auto_approve` must be a JSON boolean (the string `"false"` cannot enable it),
36
+ `max_output_tokens` must be an integer from 256–65536,
37
+ and `tool_result_budget_bytes` from 0–4,000,000 (0 disables tool-result elision).
38
+ Invalid fields produce value-free warnings and fall back to defaults; permission
39
+ fields fail closed. Base URLs must use HTTP(S) and cannot contain login credentials,
40
+ query strings or fragments.
41
+
42
+ ```bash
43
+ flash --check # connectivity + model catalog diagnostic
44
+ flash # the REPL
45
+ flash -p "explain this code: …" # text-only one-shot; no tools or workspace access
46
+ flash --agent -p "inspect this repo" # headless tools, read-only by default
47
+ flash --agent --auto -p "fix the failing tests and verify" # trusted project; guarded commands
48
+ flash --agent --auto-approve -p "update the README" # file changes only
49
+ flash --absolute # unguarded auto: whole drive + every command; no gate
50
+ flash --auto # guarded auto; network/installs/sudo/outside/destructive still ask
51
+ flash --auto-approve # file-only auto; every terminal command still asks
52
+ flash --model <id> --debug
53
+ ```
54
+
55
+ ## Real-work workflows
56
+
57
+ The agent is instructed to inspect relevant files, make focused changes, run authorized
58
+ validation, diagnose failures, retry, and report what was actually verified. Tool
59
+ schemas explain scope, approval and supported follow-up options instead of treating
60
+ bounded output as a dead end. Applicable `CLAUDE.md` files are loaded from the nearest
61
+ repository root down to the launch directory, attributed to their source/scope. Outside
62
+ a repository, only the launch directory is considered. Sources are regular, non-symlink
63
+ files, bounded to 16 KiB each / 32 KiB total; nested guidance must be inspected when
64
+ working in a deeper subtree. Project instructions cannot override host permissions or
65
+ credential protection.
66
+
67
+ For automation, **`--agent -p` explicitly enables tools**. It defaults to read-only,
68
+ ignoring saved `auto_approve` permissions. Add `--auto-approve` for file changes or
69
+ `--auto` for file changes plus guarded commands. Actions without approval
70
+ are denied as tool results, never an unattended approval prompt; denied/failed tools
71
+ or unfinished turns produce a nonzero exit code. Headless runs save private transcripts
72
+ and retain completed-action evidence. They use bounded tool rounds but currently do
73
+ not fetch model context windows or auto-compact; keep automated tasks scoped. Unguarded
74
+ auto is interactive-only. Ordinary `-p` remains text-only, not a repo-inspection mode.
75
+
76
+ ## The REPL
77
+
78
+ - **Terminal UI:** chat scrolls above a bottom-anchored panel, with the selected model
79
+ on its top divider, the orange `❯` input between dividers, and a labeled status row
80
+ below. Your input echoes with its orange `❯` chevron; streamed assistant replies
81
+ start with a subtle `●` marker. Tool calls are grouped by name only within a single
82
+ model-proposed batch, not across later calls. The layout uses a flat light yellow accent (#ffe066)
83
+ for branding, models, menus, and availability, with orange (#ff9f40) for keywords. The status row names every token (`mode`/`effort`); the effort value
84
+ warms from light yellow to red as the level rises, and the drive-wide mode badge reads
85
+ `unguarded auto`. A resting status word is omitted from the footer; success states stay
86
+ green and warnings amber/red, and **Shift+Tab** cycles the permission mode. Command menus
87
+ and views open immediately **below the input**, outside
88
+ the scrolling conversation. While a command is open, the normal status row is
89
+ hidden so only its controls remain. Reasoning text is hidden; answers and
90
+ in-flow tool activity cards stay in chat. The compact header shows only the
91
+ working directory. Pipes and very small terminals keep a simple layout; `--no-color`
92
+ disables styling and animation. Set `FLASHLITE_THEME=light` for white/light terminals: the
93
+ warm palette darkens and switches to explicit true-color (no bright `9x`, no `2` faint) so
94
+ every text role keeps ≥4.5:1 contrast; `dark` is the default and unchanged. Restart `flash`
95
+ after source changes — existing sessions do not hot-reload.
96
+ - **Context meter:** the bottom bar shows conversation context usage as a color-coded
97
+ percent with token counts (e.g. `ctx 42% 42k/100k`) — accent up to 70%, yellow 70–85%, red above 85%;
98
+ missing or disabled context shows nothing. The context amount is always kept visible,
99
+ even on narrow terminals — the status truncates around it.
100
+ - **Dynamic slash commands:** type `/` to open a live filtered palette below the input.
101
+ ↑/↓ selects, Tab completes, Enter runs, and Esc dismisses. `/model` opens the model
102
+ picker; `/model <id>` still sets
103
+ directly and persists the default. `/compact` summarizes older complete
104
+ turns while retaining the latest user turn; the append-only session log stores a checkpoint
105
+ so resumed conversations use the compacted context. `/settings` offers token saver
106
+ (off/low/high/custom) and boost (on/off) instead of raw token-budget controls. Off
107
+ uses saved budgets; low/high reduce output, context and replay budgets and compact
108
+ earlier. Custom opens the advanced output/context/replay/compaction controls.
109
+ Boost keeps the same model, reasoning effort and token budgets. For substantial
110
+ requests it starts independent read-only code/UI investigation or research helpers
111
+ while the main agent works. Finished findings arrive at safe model-round boundaries
112
+ so the main agent can avoid duplicate investigation; remaining helpers are awaited
113
+ before concluding. Focused validation stays with the main agent.
114
+ Small tasks skip delegation. Boost tints the model name, `ϟ` glyph and composer rules
115
+ orange. While the model works, a highlight sweeps the full model bar and the lower
116
+ composer rule; at rest the bar holds its static orange. Set
117
+ `FLASHLITE_REDUCED_MOTION=1` (or `NO_COLOR`) to disable motion, and animations never
118
+ run in piped output.
119
+ Flashlite already uses fast context checks, bounded replay and parallel tool calls in
120
+ every mode; Boost cannot force faster provider inference. Presets do not overwrite
121
+ advanced config values. **Shift+Alt+Tab** toggles Boost in the interactive editor. Settings also include the local API key,
122
+ default model, reasoning effort, permission default, endpoint and **Memory** entry.
123
+ Pick Memory to append a global prompt for the next turn. Context checks use exact
124
+ counting near the limit where supported and estimates otherwise. Key entry is masked and never echoed; credentials come only from the local config. `/mode` picks `ask`, `files`, guarded `auto`,
125
+ or unguarded `auto`; `/mode auto` and `/mode unguarded` switch directly, and **Shift+Tab**
126
+ cycles the mode from the prompt. Modes are session-only. `/clear` resets the visible conversation and starts
127
+ a fresh transcript; `/exit` quits. Command notices and controls stay below the
128
+ input; agent answers and tool cards stay in chat. Pipes and tiny terminals use plain
129
+ command output.
130
+ - **Anchored scrolling and selection:** Page Up/Down scroll conversation history while
131
+ the input stays fixed, even during a streaming answer. New output does not pull you
132
+ away from the history you are reading. Page Down returns live; submitting a new
133
+ turn also returns live. Open command panels/pickers receive these keys instead.
134
+ Display history retains the latest 10,000 logical lines; full transcripts stay on disk.
135
+ Mouse reporting stays disabled so normal click-and-drag text selection and copy work
136
+ without a modifier key. The mouse wheel uses your terminal's native scrolling.
137
+ - **Effort:** `/effort` opens a picker for `auto`, `low`, `medium`, `high`, `xhigh`, `max`;
138
+ `/effort high` sets directly. The choice is shown in the bar and saved as
139
+ `reasoning_effort`. `flash --effort high` overrides it for this run, including `-p`.
140
+ `auto` omits an explicit API setting. Chat Completions uses `reasoning_effort`; Messages
141
+ uses thinking budgets (2,048 / 4,096 / 8,192 / 16,384 / 32,000 tokens), raising total
142
+ `max_tokens` when necessary to leave at least 4,096 tokens for the answer. Supported
143
+ levels depend on the model/router; a rejection is reported, not silently downgraded.
144
+ DeepSeek is the exception: its picker offers only the levels the model accepts
145
+ (`auto`, `low`, `high`, `max`; `auto`, `high`, `max` for `pro`) and the wire value is
146
+ coerced (`medium`/`xhigh` → `high`) so an unsupported level is never sent.
147
+ Signed thinking blocks are retained for tool continuations, never shown in chat.
148
+ - **Live agent cards:** the thinking indicator updates in place in the chat area, above
149
+ the composer, not in the bottom status strip. It is transient (not transcript/history
150
+ content), disappears when the answer starts, and stays hidden while viewing older
151
+ chat. Hidden model reasoning remains hidden. Tool activity updates a card **in the
152
+ conversation**, rather than pinning it to the composer; file writes/edits show pending
153
+ code as arguments stream, then update that same card to success/denied/error. An edit
154
+ over a readable file renders as a line-numbered unified diff — context rows, `+`/`−`
155
+ rows and a `+N −M` chip — with the code token-colored by the file's language; when the
156
+ target can't be read (a new file, a partial stream) it falls back to a plain `− old`/
157
+ `+ new` block. The finished card keeps the `+N −M` count. Completed
158
+ cards remain in chat history, and later replies follow them. Previews do not execute
159
+ changes: writes follow the selected permission mode. Completed cards name what a tool acted on
160
+ beside its timing — e.g. `✓ read_file 2x · src/a.ts · src/b.ts · 1.2s` and
161
+ `✗ run_command · npm test · 2m 05s` — and denied runs say what was denied; live queued cards show
162
+ the subject too (e.g. `● read_file src/a.ts`).
163
+ - **Image attachments:** **Ctrl+V** reads an image from the macOS clipboard and queues
164
+ it for your next prompt; your typed draft stays intact. Copy an image, or capture a
165
+ screenshot directly to the clipboard with **Ctrl+Cmd+Shift+4**, then Ctrl+V in `flash`.
166
+ **Cmd+V** still handles ordinary terminal text paste. An empty/text-only clipboard
167
+ produces a notice, not a message to the agent. Clipboard access occurs only on this
168
+ explicit shortcut, never in the background or via agent tools.
169
+ Pending filenames appear in the footer. Type your question and Enter, or Enter alone
170
+ to send an image-only turn. **Ctrl+X** removes pending attachments; `/clear` removes
171
+ both queued attachments and images already in conversation history. Images stay in
172
+ history across tool rounds and follow-up questions. Failed requests retain the original
173
+ user message and images in history once; send a continuation to retry without reattaching. Use an **image-capable model**: router/model support varies,
174
+ and unsupported images are reported rather than silently dropped.
175
+ - Up to 4 images per prompt, 10 MiB each, 20 MiB total. Clipboard PNG is preserved;
176
+ screenshot TIFF is converted to PNG using AppKit. No resizing or HEIC conversion.
177
+ TIFF input is bounded to 40 MiB / 40 million pixels before PNG conversion.
178
+ - `flash --image ./shot.png -p "Explain this screenshot"` works in one-shot mode;
179
+ repeat `--image` for multiple files. Without `-p`, images are queued in the REPL.
180
+ Local files support PNG, JPEG, WebP, GIF, quoted paths, `~/`, and absolute paths.
181
+ - No image slash commands. Type `/` for the palette or `/help` for shortcuts and recovery.
182
+ Clipboard images are local snapshots; nothing is uploaded until you submit a prompt.
183
+ Remote image URLs and Finder file-copy clipboard references are not supported.
184
+ - Clipboard access uses macOS's built-in `/usr/bin/osascript` with fixed AppKit/JXA
185
+ code via `execFile` (no shell, packages, native compilation, or temporary image files).
186
+ Reads time out after 10 seconds and Ctrl+C cancels; failed/cancelled reads keep the
187
+ queue intact. Shortcuts are disabled during turns, permission prompts, model pickers
188
+ and command panels.
189
+ - UI and Markdown transcripts show filenames/metadata only. Private JSONL transcripts
190
+ (mode 0600) retain the encoded image bytes, so they can be much larger and include
191
+ private image content. No clipboard payload is printed to the terminal.
192
+ - Context warnings use exact Messages token counting when available, otherwise a rough
193
+ 2,048-token allowance per image; actual image tokens vary by model and resolution.
194
+ - **Live typing and steering:** the composer stays editable while the AI streams or
195
+ runs tools. **Enter** queues your text as steering; muted grey message rows above
196
+ the composer show what's pending, with the count in the existing footer. The latest
197
+ three messages are visible (earlier ones are summarized); long rows use an ellipsis.
198
+ Smaller terminals reduce this preview to preserve chat and the anchored composer.
199
+ Messages are delivered in order at the next safe model boundary,
200
+ after all results from an in-flight tool batch. A queued message received during
201
+ a final answer starts another response in the same turn. It does not interrupt a
202
+ running request or undo tools already executing — **Ctrl+C** stops the turn.
203
+ Pending previews update in place and disappear when sent. Applied messages use the
204
+ ordinary user-message styling, with no “steer:” prefix, and are included in the same
205
+ active workflow/transcript. Delivering them does not stop or restart the agent or a
206
+ running terminal command. A provider request already in flight cannot accept new
207
+ input retroactively; the next continuation receives the messages after tool results.
208
+ Pending text is display-only.
209
+ Unsubmitted drafts survive turn completion and temporarily yield to permission
210
+ questions, then return afterward. Permission answers are not steering. Ctrl+C
211
+ during approval cancels rather than approving the write. Unapplied steering on
212
+ cancellation, error or the tool-round cap returns to the editor for an explicit
213
+ retry; multiple restored messages are joined in order. Slash commands work during
214
+ a turn: `/model`, `/effort`, and `/mode` open their usual controls while
215
+ the AI continues. Model/effort changes apply to the **next turn**; current requests
216
+ and tool continuations retain their original settings, and usage/transcripts remain
217
+ attributed to the active turn's model.
218
+ Cancelling a command does not cancel the agent. Permission questions take priority
219
+ over open command controls. `/clear` cancels and awaits the active turn before
220
+ resetting history/queued steering; `/exit` stops both. Clipboard shortcuts remain
221
+ idle-only.
222
+ - **Agent loop:** no fixed tool-round cap — the turn runs until the model stops requesting
223
+ tools (bounded by the context budget, output limits and auto-compaction).
224
+ - **Tool-result budget:** to keep replay tokens bounded, older tool results are elided from
225
+ the request the model sees once their combined size exceeds `tool_result_budget_bytes`
226
+ (default 8,192; advanced value in config.json, 0 disables). The newest result is always
227
+ sent whole; older ones are replaced with a short `[earlier tool result elided …]` stub that
228
+ records the original size. This trims only the replayed request — canonical history, the
229
+ transcript and tool-call/result pairing stay intact, and the elision is idempotent across
230
+ rounds.
231
+ - **Multi-turn context:** each turn appends the user message and the assistant answer to
232
+ the conversation, so follow-ups continue the same context. The router only concatenates
233
+ a prior turn's chain of thought when the request carries `tools`; this CLI follows that
234
+ rule — OpenAI `reasoning_content` is retained on the assistant message but replayed only
235
+ while tools are present (so tool continuations keep continuity), and tool-less requests
236
+ omit it. Model reasoning is never shown in chat or written to the Markdown log; it stays
237
+ in the private JSONL transcript. "Steering" is separate: it queues a message delivered at
238
+ the next safe boundary *within* an active turn.
239
+ File and terminal approvals appear in a bordered panel above the composer with a
240
+ separate choice row, not as text in the normal input bar. File changes accept
241
+ `y` = once, `a` = all file changes (this session), `n` or Enter = deny.
242
+ `--auto-approve` or `"auto_approve": true` skips **file-write** prompts only.
243
+ `/mode auto` or `--auto` runs arbitrary terminal commands, still asking before the five
244
+ guarded-auto risk categories.
245
+ In `ask` and `files` modes, all terminal commands show the full
246
+ command/cwd/timeout and require their own explicit `y`/`yes`; blank, `a`/`all`, EOF
247
+ and cancellation do not authorize them. Unguarded auto skips file and command prompts
248
+ entirely, except recognizable catastrophic commands.
249
+ `/mode ask` revokes automatic grants;
250
+ queued auto/unguarded commands and unguarded file mutations cannot start after
251
+ revocation. Running commands need cancellation to stop; file operations also recheck
252
+ cancellation/authorization at bounded checkpoints.
253
+ Tool batches have four workers; approvals are asked just-in-time (one prompt at a time,
254
+ in call order) so an early tool can run while later calls still await approval, and
255
+ results keep their call order. After the last
256
+ permitted batch, one tools-disabled request lets the model evaluate its results.
257
+ Truncated/incomplete proposals never execute; unmatched/duplicate batch IDs stop with
258
+ an explicit interruption rather than corrupting the next request. Round exhaustion,
259
+ context exhaustion, invalid calls and model truncation have distinct recoverable
260
+ notices—not invented success. Completed tool evidence survives failures.
261
+ Interactive context checks reserve actual output/thinking capacity. Auto-compaction
262
+ can summarize older exchanges within a long active task, retaining current instructions
263
+ and the latest two complete exchanges (including required signed thinking). Attempts
264
+ are bounded and recounted; if retained context still cannot fit, the turn stops instead
265
+ of knowingly sending an overfull request. Fallback token counts remain estimates.
266
+ - **Keys:** Enter submits, arrows walk history, Ctrl+C interrupts (aborts a streaming
267
+ turn), Ctrl+D exits. **Ctrl+O** expands/collapses the latest completed tool batch's
268
+ output in place; the expanded view shares the chat viewport across calls, with
269
+ omitted lines indicated (full results remain in the private transcript).
270
+ Full output also remains in the private session transcript. `/agents` shows running
271
+ agents and role settings; select a role to launch or configure its model/effort.
272
+ Defaults inherit the current main model and effort at launch; changes affect future
273
+ launches, not running jobs. `/agents <role> <task>` starts one directly, while
274
+ `/agents list`, `/agents show <id>` and `/agents stop <id>` inspect or stop jobs.
275
+ Roles include ui-ux, code, minor, oracle, reviewer, coder and researcher. All agents
276
+ are read-only: they can inspect project files but never edit them or run shell
277
+ commands. Boost-spawned helpers remain read-only too. Researchers can read public
278
+ HTTPS text/HTML/JSON pages without a network prompt; local/private endpoints and
279
+ redirects are blocked. Running and finished agents remain listed above the input.
280
+ **Ctrl+R** opens a bash-style reverse history search: typing narrows,
281
+ Ctrl+R or ↑ walk older matches, ↓ newer, Enter accepts the match back into your draft (Enter
282
+ again submits), and Esc restores your exact pre-search draft. Multi-line bracketed pastes
283
+ submit as one turn. The model picker
284
+ navigates with ↑/↓ (or `j`/`k`), confirms with Enter, cancels with Esc or Ctrl+C.
285
+ - **Markdown:** answers render as markdown — `**bold**`, `` `code` ``, fenced code
286
+ blocks (light yellow), `#` headings, list markers, and `[links](url)` (URL dropped). Italic is
287
+ intentionally unsupported (`*`/`_` collide with identifiers). Fenced code with a recognized
288
+ language (`ts`, `js`, `jsx`, `json`, `bash`, `sh`, `zsh`, `python`, `go`, `rust`, `yaml`, `toml`)
289
+ is token-colored live and in one-shot — keywords orange, strings green, numbers yellow, comments
290
+ dim — while prose stays default. Unknown or absent languages keep the flat light-yellow accent,
291
+ and fence openers show their language tag in orange. Transcripts keep the raw
292
+ markdown; styling is display-only. Partial prose, bold/code spans, and code-block
293
+ content appear as chunks arrive, without waiting for a newline or Ctrl+C. Only
294
+ ambiguous Markdown prefixes/links use bounded lookahead. Streamed replies discard
295
+ oversized leading/trailing blank gaps and use one blank line between prose paragraphs;
296
+ code-block whitespace is preserved.
297
+ - **Sessions:** every turn appends to `~/.config/flashlite/sessions/` — a
298
+ markdown log plus a JSONL transcript. Resume the newest saved conversation with
299
+ `flash --resume latest`, a specific one with `flash --resume <session-id>`, or use
300
+ `/sessions` from the REPL to select one. New sessions record their canonical workspace;
301
+ resuming from a different folder is blocked rather than silently retargeting paths.
302
+ Legacy sessions require explicit workspace acknowledgement (CLI:
303
+ `--acknowledge-legacy-workspace`). Permissions are never restored from a transcript.
304
+ Validated context and recoverable checkpoints retain completed actions even after a
305
+ later provider failure. Transcript errors do not undo execution; a later successful
306
+ JSONL write checkpoints unsaved context. If logging keeps failing, evidence is retained
307
+ only in the running process, not guaranteed on disk. Future turns append to the same
308
+ private transcript; `/clear` starts fresh.
309
+ - Model IDs auto-route to the right wire dialect: `anthropic/*` speaks the Messages
310
+ API; everything else speaks Chat Completions. Bare `claude-*` and
311
+ `gpt-*`/`grok-*`/`deepseek-*` also work; other bare IDs ask for the full
312
+ provider-prefixed ID from `/model`.
313
+
314
+ ## Custom commands, memory, hooks, todos, and undo
315
+
316
+ Five extension points build on the same tool/approval engine:
317
+
318
+ - **Custom slash commands.** Drop markdown files in `~/.config/flashlite/commands/`
319
+ (global) or `<project>/.flashlite/commands/` (project; shadows global by name). The filename
320
+ is the command (`review.md` → `/review`); the body is a prompt template. `$1`..`$9`
321
+ substitute positional arguments and `$ARGS` substitutes them all. Optional frontmatter
322
+ sets `description` (shown in the `/` palette) and `argument-hint`. No shell interpolation
323
+ runs — the body is prompt text, not a script.
324
+ - **Path completion.** Tab completes `@file` mentions and bare paths relative to the working
325
+ directory (directories get a trailing `/`); an unambiguous match resolves fully, an
326
+ ambiguous one extends to the longest common prefix.
327
+ - **Memory.** `~/.config/flashlite/memory.md` (global) and `<project>/.flashlite/memory.md`
328
+ (project) are loaded into the system prompt (bounded, treated as data). The `/settings`
329
+ **Memory** entry appends a global memory prompt; project memory can be edited directly.
330
+ - **Todos.** The agent can call `todo_write` to publish a task list
331
+ (`pending`/`in_progress`/`completed`/`cancelled`); it renders live in a dock strip with
332
+ `☐`/`◐`/`✓`/`✗` and clears on `/clear`.
333
+ - **Undo.** Before a mutating file tool runs, its target's original bytes are captured; a
334
+ turn's captures form a checkpoint. `/undo` reverts the most recent turn's file changes
335
+ (created files are removed, changed files restored). Shell side effects are never
336
+ captured — this only reverses the file tools.
337
+ - **Hooks + notifications.** Executable scripts named `PreToolUse`, `PostToolUse`,
338
+ `Notification`, or `Stop` in `~/.config/flashlite/hooks/` or `<project>/.flashlite/hooks/`
339
+ receive one JSON object on stdin. `PreToolUse` may veto a call with
340
+ `{"decision":"block","reason":"…"}`; hook errors fail open. A ~15s+ turn also fires a
341
+ macOS notification.
342
+
343
+ ## Tool policy
344
+
345
+ Tools are available in the interactive agent REPL. Launch `flash` from the folder you
346
+ want it to work in. In `ask`, `files`, and guarded `auto`, **filesystem-tool paths**
347
+ are relative to that folder and confined there: absolute paths, `~`, parent-directory
348
+ escapes, and symlink escapes are refused. In unguarded `auto`, tools can use
349
+ absolute paths, `~/`, parent paths and external symlink parents across the drive,
350
+ wherever your current macOS account has permission. Terminal commands are separately
351
+ authorized (explicitly, by guarded auto, or by unguarded auto) and are
352
+ **not sandboxed**. Their starting directory is validated, but their effects can extend
353
+ to other files on your PC and the network. Ordinary `-p` is chat-only; explicit
354
+ `--agent -p` uses the same tools with the noninteractive approval policy above.
355
+
356
+ | Tool | What it does | Approval |
357
+ | --- | --- | --- |
358
+ | `read_file` | Read text, with UTF-8-safe byte pages up to 256 KiB and continuation metadata | No |
359
+ | `read_files` | Read 1–20 text files with per-file errors and a shared 256 KiB content budget | No |
360
+ | `file_info` | Inspect type, size, permissions and modification time; no file content | No |
361
+ | `list_dir` | Sorted directory pages using `offset` and `limit` (up to 500 per page) | No |
362
+ | `find_files` | Find paths by literal substring, optionally files/directories only | No |
363
+ | `grep` | Stream literal text search in a file or directory with bounded snippets and honest partial-result metadata | No |
364
+ | `write_file` | Create/overwrite text, including missing parents, up to 1 MiB | Yes |
365
+ | `edit_file` | Replace a nonempty, unique exact match; resulting file up to 1 MiB | Yes |
366
+ | `make_dir` | Create folders and missing parents | Yes |
367
+ | `copy_path` | Copy a file (including binary assets) or directory tree | Yes |
368
+ | `move_path` | Move/rename a file or directory | Yes |
369
+ | `remove_path` | Permanently delete a file/directory (no recovery store; use git or a copy) | Yes |
370
+ | `run_command` | Noninteractive terminal command with bounded output/time | Explicit yes; guarded auto for arbitrary commands except five risk categories; unguarded auto for everything |
371
+
372
+ File mutations marked Yes ask in `ask` mode and run automatically in `files`/`auto`/`unguarded`.
373
+ Neither file approve-all nor `--auto-approve` grants terminal authorization.
374
+
375
+ `copy_path` and `move_path` **never overwrite an existing destination**.
376
+ Copy/move/remove preflight trees with a 5,000-entry / 100 MiB limit. Tree
377
+ operations reject symlinks and special files rather than silently omitting entries.
378
+ Outside unguarded auto they also reject `.git` internals;
379
+ mutations reject symlink paths (even links staying inside the project), the working
380
+ directory itself, and `.git` internals. Unguarded auto lifts those project/protected-name
381
+ path guards, but write/edit still refuse final symlinks and non-regular files, and
382
+ tree operations still refuse symlink entries. Text tools reject NUL-containing binary
383
+ files. Write/edit may overwrite according to the selected permission mode; read before
384
+ changing an existing file. Aborted/revoked copies roll back their newly-created destination
385
+ contents; an individual OS copy syscall cannot be interrupted, and newly-created parent
386
+ folders may remain. File paging/search is not snapshot-isolated from concurrent rewrites.
387
+
388
+ `find_files` caps at 500 results / 5,000 inspected entries; `grep` caps at 200 results /
389
+ 5,000 inspected entries (including directories). Both skip symlinks. Outside unguarded
390
+ auto they also skip `.git`, dependencies, common build directories and
391
+ `.DS_Store`; unguarded searches include those names, without dropping traversal/output
392
+ caps. Find patterns are literal, not globs or regular expressions.
393
+
394
+ Large text files can be inspected without increasing context arbitrarily:
395
+ `read_file(path, offset_bytes?, max_bytes?)` returns ordinary text for a complete small
396
+ file, or a JSON page with byte positions, total bytes and `next_offset_bytes` for a
397
+ large/ranged read. Page content is UTF-8-safe; continue using the returned offset.
398
+ `list_dir(path?, offset?, limit?)` sorts entries and reports the next offset when more
399
+ remain. Pages are not filesystem snapshots: restart after directory changes. A listing
400
+ that reaches its 100,000-entry scan bound is explicitly partial, without a misleading
401
+ continuation offset for an unscanned remainder.
402
+
403
+ `grep` can search a single regular file as well as a directory. Its default scan
404
+ budget is 16 MiB (configurable with `max_scan_bytes` up to 100 MiB), and results use
405
+ bounded snippets with byte positions. `max_output_bytes` controls the aggregate result
406
+ budget up to 64 KiB, including partial-result notices; `max_results` is capped at 200.
407
+ For a single file, pass the reported `next_start_byte` as `start_byte`; inspect matching ranges
408
+ with `read_file`. Directory traversal caps/skipped files are explicitly reported:
409
+ partial search is not an exhaustive “no matches.” Narrow the directory scope when
410
+ traversal is capped. Search may read up to 16.5 KiB of preceding context and 16 KiB of
411
+ unsearched suffix context for snippets/credential redaction; matches and continuation
412
+ stay within the forward scan budget. Read-page limits bound source bytes, not JSON
413
+ escaping/redaction overhead. Binary/special files remain unsupported by text tools.
414
+
415
+ Mutations from concurrent tool batches are serialized per project in this process.
416
+ Confinement is a path guard, not an OS sandbox against another process changing the
417
+ filesystem concurrently; avoid moving project folders or changing symlinks mid-operation.
418
+
419
+ **Removal is permanent.** `remove_path` deletes with `fs.rm`; there is no recovery
420
+ store, `list_removed`/`restore_file`, or automatic purge — git (or a copy made first) is
421
+ the only recovery. A shell `rm` is likewise a real deletion, and redirects/`tee` can
422
+ clobber a file. These file-tool protections are not an OS sandbox: an approved terminal
423
+ command can delete files the tools guard.
424
+
425
+ ### Terminal commands
426
+
427
+ `run_command(command, cwd?, timeout_ms?)` uses macOS `/bin/zsh -f -c`, with ignored
428
+ stdin and piped stdout/stderr, not an interactive terminal. `cwd` must be an existing
429
+ project directory (default project root), except unguarded auto also permits existing
430
+ directories elsewhere on the drive. The default timeout is 60 seconds, maximum
431
+ 5 minutes; commands are limited to 16 KiB and captured output to a combined 1 MiB.
432
+ Overflow terminates the command and reports truncation, not unlimited collection.
433
+ Results include status, exit code/signal, stdout/stderr and truncation; failure is
434
+ reported faithfully. The live card shows lifecycle only, not raw command output.
435
+
436
+ In ask/file-only mode, every command gets a separate approval. Guarded auto runs
437
+ arbitrary commands without a prompt except for five risk categories (below); unguarded
438
+ auto runs every command except recognizable catastrophic deletion/disk destruction.
439
+ When a command needs approval, its full text,
440
+ canonical cwd and effective timeout appear in chat before the anchored yes/no question;
441
+ control/invisible characters and backslashes are escaped visibly rather than executed
442
+ by the terminal. Known router-key-bearing commands are rejected before display.
443
+ Commands use a minimal environment allowlist (standard path/home/user/temp/locale
444
+ variables, `TERM=dumb`), not ambient credential variables. Known router credentials
445
+ are redacted from captured output before model/history/transcript use. This is not
446
+ comprehensive secret isolation: commands can still read local configuration or make
447
+ network requests. Review what you approve. `/bin/zsh -f` disables ordinary user rc
448
+ loading; it does not isolate system startup behavior.
449
+
450
+ ### Guarded auto
451
+
452
+ Enable with `flash --auto`, `/mode auto`, or the `/mode` picker. It is opt-in for this
453
+ session, never saved as a default. `files` mode and legacy `auto_approve` remain file-only;
454
+ `--auto`, `--auto-approve`, and `--absolute` are mutually exclusive.
455
+
456
+ Guarded auto is **trust-by-default**: arbitrary commands and pipelines run without a
457
+ prompt, including unknown executables, interpreters, builds, tests and file mutations.
458
+ Exactly five categories still ask, and none can be hidden behind a wrapper, pipeline,
459
+ env assignment or literal `sh -c` body:
460
+
461
+ - **Network access** — `curl`, `wget`, `nc`, `ssh`, `scp`, `rsync`, `git fetch|pull|push|clone|remote`, and similar.
462
+ - **Installs / package managers** — `npm install|ci|add|…`, `pnpm`/`yarn add`, `brew`, `pip`, `apt`, `cargo install`, `go get`, `npx`, and similar.
463
+ - **Privilege escalation** — `sudo`, `doas`, `su`, `pkexec`.
464
+ - **Paths outside the project** — absolute paths, `~`, parent (`..`) traversal, and symlink escapes.
465
+ - **Destructive Git** — `git reset --hard`, `git clean -f…`, `git push --force`, `git branch -D`.
466
+
467
+ Obviously catastrophic deletion or disk commands (recursive deletion of a filesystem
468
+ root, home or volume; `diskutil` erase; `mkfs`; `dd`/redirection to a raw disk) are
469
+ **refused outright**, not offered. Syntax the classifier cannot reduce to inspectable
470
+ words — command substitution, backticks, globs, here-docs, `eval`/`source`, subshells
471
+ and background jobs — fails closed and asks. Paths must resolve inside the project,
472
+ plus the CLI's own `~/.config/flashlite` tree for reads. `cp`/`mv` never overwrite.
473
+
474
+ **Guarded auto trusts arbitrary commands and project code.** A classifier is not proof
475
+ of safety, this is not an OS sandbox, and buildings/tests can execute arbitrary code with
476
+ side effects. There is an external filesystem race between inspection and execution;
477
+ command text cannot provide isolation. Do not enable auto in an untrusted repository.
478
+ Turning auto off revokes queued grants, including after turning it on again, but does
479
+ not interrupt work already running.
480
+
481
+ ### Unguarded auto
482
+
483
+ Enable with `flash --absolute`, `/mode unguarded`, or the `/mode` picker. It takes effect
484
+ immediately with a one-line notice — there is no typed acknowledgement or disclaimer
485
+ gate. A `/mode` switch applies to the session; the `/config` default-mode picker can save
486
+ it as the launch default (written to config as `unguarded`, never `absolute`). An
487
+ interactive terminal is required, and `--absolute` is rejected with `-p`, `--check`, or
488
+ piped input/output.
489
+
490
+ **Unguarded auto has no approval prompts and no guarded-auto ask list.** Ordinary `rm`,
491
+ builds, installs, Git changes, elevated, unknown and opaque commands all run. File tools
492
+ can read/change paths across the drive, including absolute paths and `~/`, and commands
493
+ may start outside the project. The mode badge reads `unguarded auto`. Use only when you
494
+ trust the task and the files/code the agent will encounter.
495
+
496
+ The only exceptional action-confirmation gate recognizes **very large irreversible
497
+ operations**: recursive deletion of a filesystem root, whole home/user tree, named
498
+ system root or whole mounted volume (including whole-tree glob variants); destructive
499
+ `diskutil` erase/partition/APFS-delete operations; formatting a raw disk with
500
+ `mkfs`/`newfs`; `dd` writing a raw disk; or shell output redirected to a raw disk.
501
+ Recognized literal `sudo`/`env` wrappers, compound commands and `sh`/`zsh`/`bash -c`
502
+ bodies are checked too. These show the exact command/canonical cwd/timeout and require
503
+ an explicit **yes for that command only**, without disabling unguarded auto. Denial,
504
+ cancellation or revocation prevents that command from starting.
505
+
506
+ This is a **best-effort recognizer, not a safety guarantee**: it does not inspect
507
+ script files, aliases, arbitrary expansions, remote code or general interpreter
508
+ programs, and it does not make opaque commands safe. Unrecognized actions still run
509
+ without a prompt in unguarded auto; do not rely on this gate to protect your machine.
510
+
511
+ This is current-account access, **not automatic root/sudo or a bypass of macOS privacy
512
+ permissions**. No interactive password entry is provided. Tool byte/tree limits,
513
+ regular-file and symlink-tree rules, no-overwrite copy/move and permanent-deletion
514
+ guards, bounded command output/time and cancellation remain. Commands can still
515
+ perform permanent deletion. Known router credentials are rejected in commands and
516
+ redacted from command output and unguarded file-tool results/errors before history;
517
+ this is not comprehensive secret isolation or a network sandbox.
518
+
519
+ Unguarded auto is session-only, never saved or enabled by `auto_approve`. `/mode ask`
520
+ disables it; queued unguarded commands/mutations recheck their mode generation before
521
+ executing. Turning it back on cannot revive old grants. Running commands are not
522
+ interrupted by a mode change; use Ctrl+C to stop the active turn. File tools recheck
523
+ live authorization at bounded checkpoints and before commits; revoked copies roll back
524
+ new destination data.
525
+
526
+ ### Command cancellation
527
+
528
+ Ctrl+C, `/clear`, `/exit`, timeout and output overflow terminate the process group,
529
+ with TERM→KILL escalation. Normal shell completion also cleans up ordinary background
530
+ descendants. There is no persistent-background-job or interactive-input API; programs
531
+ requiring a TTY or password entry may fail. Descendants that detach into another
532
+ process group/session can escape cleanup; no sandbox guarantee is made. Commands
533
+ serialize with project mutations in this process and recheck cancellation before
534
+ queued execution starts. Tool results remain paired with calls after cancellation,
535
+ so continuing the conversation does not send broken tool history.
536
+
537
+ For example: “Create a small website in `site/`, copy my assets into it, and run its
538
+ tests.” In ask mode, the agent creates the structure with approval and asks before
539
+ running tests. In guarded auto, the project work runs without per-call prompts, but a
540
+ network install or `sudo` still asks. Package installs and downloads are performed
541
+ through authorized commands, not silently through filesystem tools.
542
+
543
+ ## Development
544
+
545
+ ```
546
+ src/index.ts entry, flags, bootstrap test/ node:test suites
547
+ src/client.ts RouterClient (HTTP, retry) scripts/ SSE harness (live)
548
+ src/adapters/ openai.ts, anthropic.ts
549
+ src/tools.ts file tools + confinement src/loop.ts agent round driver
550
+ src/repl.ts REPL shell src/lineinput.ts raw-mode editor
551
+ src/sessions.ts transcripts
552
+ src/markdown.ts terminal markdown renderer
553
+ ```
554
+
555
+ Live wire-shape verification:
556
+
557
+ ```bash
558
+ node scripts/sse-harness.ts --model <id> --dialect openai|anthropic|auto [--tools] [--save test/fixtures/x.sse]
559
+ ```
560
+
561
+ ## Manual E2E checklist
562
+
563
+ - [ ] `flash --check` green; `npm test` green; `npm run typecheck` clean
564
+ - [ ] `/model` → arrow-key picker → streamed chat answers (markdown-rendered)
565
+ - [ ] multi-tool task (create → edit → grep) exercising y / a / n approvals
566
+ - [ ] Page Up/Down scroll chat with input anchored, including mid-stream; mouse drag selects text
567
+ - [ ] Ctrl+V queues a clipboard screenshot without submitting; Ctrl+X clears pending images
568
+ - [ ] text Cmd+V still works; `/` palette has no image commands; `/help` opens shortcuts
569
+ - [ ] `/clear` starts a fresh transcript; session files exist
570
+ - [ ] type during streaming; Enter queues steering and leaves tool/result ordering valid
571
+ - [ ] an unfinished draft survives a permission question and turn completion
572
+ - [ ] Ctrl+C mid-stream prints `*[stopped]*` and restores any unapplied steering
573
+ - [ ] `flash -p "hi"` one-shot; `echo "…" | flash` piped mode works
574
+ - [ ] bad key exits with the 401 hint; `FLASHLITE_BASE_URL` pointing at a dead port
575
+ reports a network error, not a crash
576
+ - [ ] `flash --debug` shows frame sizes and retry metadata without message content or credentials
577
+
578
+ ## Notes
579
+
580
+ - **Zero runtime deps** — `src/` and `scripts/` use Node built-ins only.
581
+ - **Never print an API key** — not even masked. Real keys live only in the local config.
582
+ - Diagnostic logging never includes message content. Private transcripts live under
583
+ `~/.config/…/sessions/`; normal UI/one-shot output shows the requested answer.
584
+ `--debug` reports metadata only, never raw SSE payloads.