flashlite
v0.1.3
Published
flashlite — a personal agentic coding CLI
Readme
flashlite
A personal agentic coding CLI — Claude Code / pi style — for any OpenAI- or Anthropic-compatible chat endpoint. Streaming chat REPL plus an agent loop with filesystem tools for creating and organizing projects, reading/editing code, searching, copying assets, and removal, plus terminal commands for builds, tests, and other requested work. Opt-in guarded auto runs arbitrary terminal commands without prompts, but still asks before network access, installs, sudo, paths outside the project, and destructive Git operations — and refuses obviously catastrophic deletion/disk commands. A separate unguarded auto mode permits whole-drive file access and every command; only recognizable catastrophic actions ask.
TypeScript on Node ≥ 24, run straight from source via Node's native type stripping:
zero build step, zero runtime dependencies (only typescript + @types/node as
devDependencies for typechecking). macOS only.
Setup
npm install # devDependencies only (typescript + @types/node)
npm run typecheck # tsc --noEmit
npm test # node --test — the full suite is offline
# put the CLI (command `flash`) on your PATH (no root needed):
mkdir -p ~/.local/bin
ln -s "$PWD/scripts/flashlite-wrapper.sh" ~/.local/bin/flash # or copy it; chmod +xOn first interactive launch, flash asks for your endpoint URL and then your API key
with masked input, saving both to ~/.config/flashlite/config.json (chmod 600). Each
local user provides their own endpoint and key; nothing is bundled with the npm package
or baked into the binary. Keys are never printed, logged, or committed — not even
masked. Non-interactive runs require an existing config file (or --base-url).
Configuration is validated before
use: auto_approve must be a JSON boolean (the string "false" cannot enable it),
max_output_tokens must be an integer from 256–65536,
and tool_result_budget_bytes from 0–4,000,000 (0 disables tool-result elision).
Invalid fields produce value-free warnings and fall back to defaults; permission
fields fail closed. Base URLs must use HTTP(S) and cannot contain login credentials,
query strings or fragments.
flash --check # connectivity + model catalog diagnostic
flash # the REPL
flash -p "explain this code: …" # text-only one-shot; no tools or workspace access
flash --agent -p "inspect this repo" # headless tools, read-only by default
flash --agent --auto -p "fix the failing tests and verify" # trusted project; guarded commands
flash --agent --auto-approve -p "update the README" # file changes only
flash --absolute # unguarded auto: whole drive + every command; no gate
flash --auto # guarded auto; network/installs/sudo/outside/destructive still ask
flash --auto-approve # file-only auto; every terminal command still asks
flash --model <id> --debugReal-work workflows
The agent is instructed to inspect relevant files, make focused changes, run authorized
validation, diagnose failures, retry, and report what was actually verified. Tool
schemas explain scope, approval and supported follow-up options instead of treating
bounded output as a dead end. Applicable AGENTS.md (or legacy CLAUDE.md) files are
loaded from the nearest repository root down to the launch directory, attributed to
their source/scope. Outside
a repository, only the launch directory is considered. Sources are regular, non-symlink
files, bounded to 16 KiB each / 32 KiB total; nested guidance must be inspected when
working in a deeper subtree. Project instructions cannot override host permissions or
credential protection.
For automation, --agent -p explicitly enables tools. It defaults to read-only,
ignoring saved auto_approve permissions. Add --auto-approve for file changes or
--auto for file changes plus guarded commands. Actions without approval
are denied as tool results, never an unattended approval prompt; denied/failed tools
or unfinished turns produce a nonzero exit code. Headless runs save private transcripts
and retain completed-action evidence. They use bounded tool rounds but currently do
not fetch model context windows or auto-compact; keep automated tasks scoped. Unguarded
auto is interactive-only. Ordinary -p remains text-only, not a repo-inspection mode.
The REPL
- Terminal UI: chat scrolls above a bottom-anchored panel, with the selected model
on its top divider, the orange
❯input between dividers, and a labeled status row below. Your input echoes with its orange❯chevron; streamed assistant replies start with a subtle●marker. Tool calls are grouped by name only within a single model-proposed batch, not across later calls. The layout uses a flat light yellow accent (#ffe066) for branding, models, menus, and availability, with orange (#ff9f40) for keywords. The status row names every token (mode/effort); the effort value warms from light yellow to red as the level rises, and the drive-wide mode badge readsunguarded auto. A resting status word is omitted from the footer; success states stay green and warnings amber/red, and Shift+Tab cycles the permission mode. Command menus and views open immediately below the input, outside the scrolling conversation. While a command is open, the normal status row is hidden so only its controls remain. Reasoning text is hidden; answers and in-flow tool activity cards stay in chat. The compact header shows only the working directory. Pipes and very small terminals keep a simple layout;--no-colordisables styling and animation. SetFLASHLITE_THEME=lightfor white/light terminals: the warm palette darkens and switches to explicit true-color (no bright9x, no2faint) so every text role keeps ≥4.5:1 contrast;darkis the default and unchanged. Restartflashafter source changes — existing sessions do not hot-reload. - Context meter: the bottom bar shows conversation context usage as a color-coded
percent with token counts (e.g.
ctx 42% 42k/100k) — accent up to 70%, yellow 70–85%, red above 85%; missing or disabled context shows nothing. The context amount is always kept visible, even on narrow terminals — the status truncates around it. - Dynamic slash commands: type
/to open a live filtered palette below the input. ↑/↓ selects, Tab completes, Enter runs, and Esc dismisses./modelopens the model picker;/model <id>still sets directly and persists the default./compactsummarizes older complete turns while retaining the latest user turn; the append-only session log stores a checkpoint so resumed conversations use the compacted context./settingsoffers token saver (off/low/high/custom), auto-compaction, and boost (on/off). Off uses the router's reported output and context limits (falling back to saved budgets); low/high reduce output, context and replay budgets and compact earlier. Max output and max context controls appear only while a token saver is on; auto-compaction is always available. Custom keeps saved budgets and lets you edit the replay budget. Boost keeps the same model, reasoning effort and token budgets. For substantial requests it starts independent read-only code/UI investigation or research helpers while the main agent works. Finished findings arrive at safe model-round boundaries so the main agent can avoid duplicate investigation; remaining helpers are awaited before concluding. Focused validation stays with the main agent. Small tasks skip delegation. Boost tints the model name,ϟglyph and composer rules orange. While the model works, a highlight sweeps the full model bar and the lower composer rule; at rest the bar holds its static orange. SetFLASHLITE_REDUCED_MOTION=1(orNO_COLOR) to disable motion, and animations never run in piped output. Flashlite already uses fast context checks, bounded replay and parallel tool calls in every mode; Boost cannot force faster provider inference. Presets do not overwrite advanced config values. Shift+Alt+Tab toggles Boost in the interactive editor. Settings also include the local API key, default model, reasoning effort, permission default, endpoint and Memory entry. Pick Memory to append a global prompt for the next turn. Context checks use exact counting near the limit where supported and estimates otherwise. Key entry is masked and never echoed; credentials come only from the local config./modepicksask,files, guardedauto, or unguardedauto;/mode autoand/mode unguardedswitch directly, and Shift+Tab cycles the mode from the prompt. Modes are session-only./clearresets the visible conversation and starts a fresh transcript;/exitquits. Command notices and controls stay below the input; agent answers and tool cards stay in chat. Pipes and tiny terminals use plain command output. - Anchored scrolling and selection: Page Up/Down scroll conversation history while the input stays fixed, even during a streaming answer. New output does not pull you away from the history you are reading. Page Down returns live; submitting a new turn also returns live. Open command panels/pickers receive these keys instead. Display history retains the latest 10,000 logical lines; full transcripts stay on disk. Mouse reporting stays disabled so normal click-and-drag text selection and copy work without a modifier key. The mouse wheel uses your terminal's native scrolling.
- Effort:
/effortopens a picker forauto,low,medium,high,xhigh,max;/effort highsets directly. The choice is shown in the bar and saved asreasoning_effort.flash --effort highoverrides it for this run, including-p.autoomits an explicit API setting. Chat Completions usesreasoning_effort; Messages uses thinking budgets (2,048 / 4,096 / 8,192 / 16,384 / 32,000 tokens), raising totalmax_tokenswhen necessary to leave at least 4,096 tokens for the answer. Supported levels depend on the model/router; a rejection is reported, not silently downgraded. DeepSeek is the exception: its picker offers only the levels the model accepts (auto,low,high,max;auto,high,maxforpro) and the wire value is coerced (medium/xhigh→high) so an unsupported level is never sent. Signed thinking blocks are retained for tool continuations, never shown in chat. - Live agent cards: the thinking indicator updates in place in the chat area, above
the composer, not in the bottom status strip. It is transient (not transcript/history
content), disappears when the answer starts, and stays hidden while viewing older
chat. Hidden model reasoning remains hidden. Tool activity updates a card in the
conversation, rather than pinning it to the composer; file writes/edits show pending
code as arguments stream, then update that same card to success/denied/error. An edit
over a readable file renders as a line-numbered unified diff — context rows,
+/−rows and a+N −Mchip — with the code token-colored by the file's language; when the target can't be read (a new file, a partial stream) it falls back to a plain− old/+ newblock. The finished card keeps the+N −Mcount. Completed cards remain in chat history, and later replies follow them. Previews do not execute changes: writes follow the selected permission mode. Completed cards name what a tool acted on beside its timing — e.g.✓ read_file 2x · src/a.ts · src/b.ts · 1.2sand✗ run_command · npm test · 2m 05s— and denied runs say what was denied; live queued cards show the subject too (e.g.● read_file src/a.ts). - Image attachments: Ctrl+V reads an image from the macOS clipboard and queues
it for your next prompt; your typed draft stays intact. Copy an image, or capture a
screenshot directly to the clipboard with Ctrl+Cmd+Shift+4, then Ctrl+V in
flash. Cmd+V still handles ordinary terminal text paste. An empty/text-only clipboard produces a notice, not a message to the agent. Clipboard access occurs only on this explicit shortcut, never in the background or via agent tools. Pending filenames appear in the footer. Type your question and Enter, or Enter alone to send an image-only turn. Ctrl+X removes pending attachments;/clearremoves both queued attachments and images already in conversation history. Images stay in history across tool rounds and follow-up questions. Failed requests retain the original user message and images in history once; send a continuation to retry without reattaching. Use an image-capable model: router/model support varies, and unsupported images are reported rather than silently dropped.- Up to 4 images per prompt, 10 MiB each, 20 MiB total. Clipboard PNG is preserved; screenshot TIFF is converted to PNG using AppKit. No resizing or HEIC conversion. TIFF input is bounded to 40 MiB / 40 million pixels before PNG conversion.
flash --image ./shot.png -p "Explain this screenshot"works in one-shot mode; repeat--imagefor multiple files. Without-p, images are queued in the REPL. Local files support PNG, JPEG, WebP, GIF, quoted paths,~/, and absolute paths.- No image slash commands. Type
/for the palette or/helpfor shortcuts and recovery. Clipboard images are local snapshots; nothing is uploaded until you submit a prompt. Remote image URLs and Finder file-copy clipboard references are not supported. - Clipboard access uses macOS's built-in
/usr/bin/osascriptwith fixed AppKit/JXA code viaexecFile(no shell, packages, native compilation, or temporary image files). Reads time out after 10 seconds and Ctrl+C cancels; failed/cancelled reads keep the queue intact. Shortcuts are disabled during turns, permission prompts, model pickers and command panels. - UI and Markdown transcripts show filenames/metadata only. Private JSONL transcripts (mode 0600) retain the encoded image bytes, so they can be much larger and include private image content. No clipboard payload is printed to the terminal.
- Context warnings use exact Messages token counting when available, otherwise a rough 2,048-token allowance per image; actual image tokens vary by model and resolution.
- Live typing and steering: the composer stays editable while the AI streams or
runs tools. Enter queues your text as steering; muted grey message rows above
the composer show what's pending, with the count in the existing footer. The latest
three messages are visible (earlier ones are summarized); long rows use an ellipsis.
Smaller terminals reduce this preview to preserve chat and the anchored composer.
Messages are delivered in order at the next safe model boundary,
after all results from an in-flight tool batch. A queued message received during
a final answer starts another response in the same turn. It does not interrupt a
running request or undo tools already executing — Ctrl+C stops the turn.
Pending previews update in place and disappear when sent. Applied messages use the
ordinary user-message styling, with no “steer:” prefix, and are included in the same
active workflow/transcript. Delivering them does not stop or restart the agent or a
running terminal command. A provider request already in flight cannot accept new
input retroactively; the next continuation receives the messages after tool results.
Pending text is display-only.
Unsubmitted drafts survive turn completion and temporarily yield to permission
questions, then return afterward. Permission answers are not steering. Ctrl+C
during approval cancels rather than approving the write. Unapplied steering on
cancellation, error or the tool-round cap returns to the editor for an explicit
retry; multiple restored messages are joined in order. Slash commands work during
a turn:
/model,/effort, and/modeopen their usual controls while the AI continues. Model/effort changes apply to the next turn; current requests and tool continuations retain their original settings, and usage/transcripts remain attributed to the active turn's model. Cancelling a command does not cancel the agent. Permission questions take priority over open command controls./clearcancels and awaits the active turn before resetting history/queued steering;/exitstops both. Clipboard shortcuts remain idle-only. - Agent loop: no fixed tool-round cap — the turn runs until the model stops requesting tools (bounded by the context budget, output limits and auto-compaction).
- Tool-result budget: to keep replay tokens bounded, older tool results are elided from
the request the model sees once their combined size exceeds
tool_result_budget_bytes(default 8,192; advanced value in config.json, 0 disables). The newest result is always sent whole; older ones are replaced with a short[earlier tool result elided …]stub that records the original size. This trims only the replayed request — canonical history, the transcript and tool-call/result pairing stay intact, and the elision is idempotent across rounds. - Multi-turn context: each turn appends the user message and the assistant answer to
the conversation, so follow-ups continue the same context. The router only concatenates
a prior turn's chain of thought when the request carries
tools; this CLI follows that rule — OpenAIreasoning_contentis retained on the assistant message but replayed only while tools are present (so tool continuations keep continuity), and tool-less requests omit it. Model reasoning is never shown in chat or written to the Markdown log; it stays in the private JSONL transcript. "Steering" is separate: it queues a message delivered at the next safe boundary within an active turn. File and terminal approvals appear in a bordered panel above the composer with a separate choice row, not as text in the normal input bar. File changes accepty= once,a= all file changes (this session),n= deny, Esc = deny. A blank Enter re-prompts.--auto-approveor"auto_approve": trueskips file-write prompts only./mode autoor--autoruns arbitrary terminal commands, still asking before the five guarded-auto risk categories. Inaskandfilesmodes, all terminal commands show the full command/cwd/timeout and require their own explicity/yes; onlyy/yesauthorizes, a blank Enter re-prompts, and Esc/Ctrl+C/EOF deny. Unguarded auto skips file and command prompts entirely, except recognizable catastrophic commands./mode askrevokes automatic grants; queued auto/unguarded commands and unguarded file mutations cannot start after revocation. Running commands need cancellation to stop; file operations also recheck cancellation/authorization at bounded checkpoints. Tool batches have four workers; approvals are asked just-in-time (one prompt at a time, in call order) so an early tool can run while later calls still await approval, and results keep their call order. After the last permitted batch, one tools-disabled request lets the model evaluate its results. Truncated/incomplete proposals never execute; unmatched/duplicate batch IDs stop with an explicit interruption rather than corrupting the next request. Round exhaustion, context exhaustion, invalid calls and model truncation have distinct recoverable notices—not invented success. Completed tool evidence survives failures. Interactive context checks reserve actual output/thinking capacity. Auto-compaction can summarize older exchanges within a long active task, retaining current instructions and the latest two complete exchanges (including required signed thinking). Attempts are bounded and recounted; if retained context still cannot fit, the turn stops instead of knowingly sending an overfull request. Fallback token counts remain estimates. - Keys: Enter submits, arrows walk history, Ctrl+C interrupts (aborts a streaming
turn), Ctrl+D exits. Esc backs out or cancels — one-shot prompts, menus and
approvals (a denied command or file change is never authorized). Ctrl+O
expands/collapses the latest completed tool batch's
output in place; the expanded view shares the chat viewport across calls, with
omitted lines indicated (full results remain in the private transcript).
Full output also remains in the private session transcript. The agent can start
read-only background helpers (roles: ui-ux, code, minor, oracle, reviewer, coder and
researcher) while it keeps working; their finished findings are folded back into the
conversation at safe model-round boundaries. Boost starts the same helpers
automatically for substantial requests. All helpers are read-only: they can inspect
project files but never edit them or run shell commands. Researchers can read public
HTTPS text/HTML/JSON pages without a network prompt; local/private endpoints and
redirects are blocked. Running helpers stay listed above the input; finished ones
linger for a couple of seconds, then disappear.
Ctrl+R opens a bash-style reverse history search: typing narrows,
Ctrl+R or ↑ walk older matches, ↓ newer, Enter accepts the match back into your draft (Enter
again submits), and Esc restores your exact pre-search draft. Multi-line bracketed pastes
submit as one turn. The model picker
navigates with ↑/↓ (or
j/k), confirms with Enter, cancels with Esc or Ctrl+C. - Markdown: answers render as markdown —
**bold**,`code`, fenced code blocks (light yellow),#headings, list markers, and[links](url)(URL dropped). Italic is intentionally unsupported (*/_collide with identifiers). Fenced code with a recognized language (ts,js,jsx,json,bash,sh,zsh,python,go,rust,yaml,toml) is token-colored live and in one-shot — keywords orange, strings green, numbers yellow, comments dim — while prose stays default. Unknown or absent languages keep the flat light-yellow accent, and fence openers show their language tag in orange. Transcripts keep the raw markdown; styling is display-only. Partial prose, bold/code spans, and code-block content appear as chunks arrive, without waiting for a newline or Ctrl+C. Only ambiguous Markdown prefixes/links use bounded lookahead. Streamed replies discard oversized leading/trailing blank gaps and use one blank line between prose paragraphs; code-block whitespace is preserved. - Sessions: every turn appends to
~/.config/flashlite/sessions/— a markdown log plus a JSONL transcript. Resume the newest saved conversation withflash --resume latest, a specific one withflash --resume <session-id>, or use/sessionsfrom the REPL to select one. New sessions record their canonical workspace; resuming from a different folder is blocked rather than silently retargeting paths. Legacy sessions require explicit workspace acknowledgement (CLI:--acknowledge-legacy-workspace). Permissions are never restored from a transcript. Validated context and recoverable checkpoints retain completed actions even after a later provider failure. Transcript errors do not undo execution; a later successful JSONL write checkpoints unsaved context. If logging keeps failing, evidence is retained only in the running process, not guaranteed on disk. Future turns append to the same private transcript;/clearstarts fresh. - Model IDs auto-route to the right wire dialect:
anthropic/*speaks the Messages API; everything else speaks Chat Completions. Bareclaude-*andgpt-*/grok-*/deepseek-*also work; other bare IDs ask for the full provider-prefixed ID from/model.
Custom commands, memory, hooks, todos, and undo
Five extension points build on the same tool/approval engine:
- Custom slash commands. Drop markdown files in
~/.config/flashlite/commands/(global) or<project>/.flashlite/commands/(project; shadows global by name). The filename is the command (review.md→/review); the body is a prompt template.$1..$9substitute positional arguments and$ARGSsubstitutes them all. Optional frontmatter setsdescription(shown in the/palette) andargument-hint. No shell interpolation runs — the body is prompt text, not a script. - Path completion. Tab completes
@filementions and bare paths relative to the working directory (directories get a trailing/); an unambiguous match resolves fully, an ambiguous one extends to the longest common prefix. - Memory.
~/.config/flashlite/memory.md(global) and<project>/.flashlite/memory.md(project) are loaded into the system prompt (bounded, treated as data). The/settingsMemory entry appends a global memory prompt; project memory can be edited directly. - Todos. The agent can call
todo_writeto publish a task list (pending/in_progress/completed/cancelled); it renders live in a dock strip with☐/◐/✓/✗and clears on/clear. - Undo. Before a mutating file tool runs, its target's original bytes are captured; a
turn's captures form a checkpoint.
/undoreverts the most recent turn's file changes (created files are removed, changed files restored). Shell side effects are never captured — this only reverses the file tools. - Hooks + notifications. Executable scripts named
PreToolUse,PostToolUse,Notification, orStopin~/.config/flashlite/hooks/or<project>/.flashlite/hooks/receive one JSON object on stdin.PreToolUsemay veto a call with{"decision":"block","reason":"…"}; hook errors fail open. A ~15s+ turn also fires a macOS notification.
Tool policy
Tools are available in the interactive agent REPL. Launch flash from the folder you
want it to work in. In ask, files, and guarded auto, filesystem-tool paths
are relative to that folder and confined there: absolute paths, ~, parent-directory
escapes, and symlink escapes are refused. In unguarded auto, tools can use
absolute paths, ~/, parent paths and external symlink parents across the drive,
wherever your current macOS account has permission. Terminal commands are separately
authorized (explicitly, by guarded auto, or by unguarded auto) and are
not sandboxed. Their starting directory is validated, but their effects can extend
to other files on your PC and the network. Ordinary -p is chat-only; explicit
--agent -p uses the same tools with the noninteractive approval policy above.
| Tool | What it does | Approval |
| --- | --- | --- |
| read_file | Read text, with UTF-8-safe byte pages up to 256 KiB and continuation metadata | No |
| read_files | Read 1–20 text files with per-file errors and a shared 256 KiB content budget | No |
| file_info | Inspect type, size, permissions and modification time; no file content | No |
| list_dir | Sorted directory pages using offset and limit (up to 500 per page) | No |
| find_files | Find paths by literal substring, optionally files/directories only | No |
| grep | Stream literal text search in a file or directory with bounded snippets and honest partial-result metadata | No |
| write_file | Create/overwrite text, including missing parents, up to 1 MiB | Yes |
| edit_file | Replace a nonempty, unique exact match; resulting file up to 1 MiB | Yes |
| make_dir | Create folders and missing parents | Yes |
| copy_path | Copy a file (including binary assets) or directory tree | Yes |
| move_path | Move/rename a file or directory | Yes |
| remove_path | Permanently delete a file/directory (no recovery store; use git or a copy) | Yes |
| run_command | Noninteractive terminal command with bounded output/time | Explicit yes; guarded auto for arbitrary commands except five risk categories; unguarded auto for everything |
File mutations marked Yes ask in ask mode and run automatically in files/auto/unguarded.
Neither file approve-all nor --auto-approve grants terminal authorization.
copy_path and move_path never overwrite an existing destination.
Copy/move/remove preflight trees with a 5,000-entry / 100 MiB limit. Tree
operations reject symlinks and special files rather than silently omitting entries.
Outside unguarded auto they also reject .git internals;
mutations reject symlink paths (even links staying inside the project), the working
directory itself, and .git internals. Unguarded auto lifts those project/protected-name
path guards, but write/edit still refuse final symlinks and non-regular files, and
tree operations still refuse symlink entries. Text tools reject NUL-containing binary
files. Write/edit may overwrite according to the selected permission mode; read before
changing an existing file. Aborted/revoked copies roll back their newly-created destination
contents; an individual OS copy syscall cannot be interrupted, and newly-created parent
folders may remain. File paging/search is not snapshot-isolated from concurrent rewrites.
find_files caps at 500 results / 5,000 inspected entries; grep caps at 200 results /
5,000 inspected entries (including directories). Both skip symlinks. Outside unguarded
auto they also skip .git, dependencies, common build directories and
.DS_Store; unguarded searches include those names, without dropping traversal/output
caps. Find patterns are literal, not globs or regular expressions.
Large text files can be inspected without increasing context arbitrarily:
read_file(path, offset_bytes?, max_bytes?) returns ordinary text for a complete small
file, or a JSON page with byte positions, total bytes and next_offset_bytes for a
large/ranged read. Page content is UTF-8-safe; continue using the returned offset.
list_dir(path?, offset?, limit?) sorts entries and reports the next offset when more
remain. Pages are not filesystem snapshots: restart after directory changes. A listing
that reaches its 100,000-entry scan bound is explicitly partial, without a misleading
continuation offset for an unscanned remainder.
grep can search a single regular file as well as a directory. Its default scan
budget is 16 MiB (configurable with max_scan_bytes up to 100 MiB), and results use
bounded snippets with byte positions. max_output_bytes controls the aggregate result
budget up to 64 KiB, including partial-result notices; max_results is capped at 200.
For a single file, pass the reported next_start_byte as start_byte; inspect matching ranges
with read_file. Directory traversal caps/skipped files are explicitly reported:
partial search is not an exhaustive “no matches.” Narrow the directory scope when
traversal is capped. Search may read up to 16.5 KiB of preceding context and 16 KiB of
unsearched suffix context for snippets/credential redaction; matches and continuation
stay within the forward scan budget. Read-page limits bound source bytes, not JSON
escaping/redaction overhead. Binary/special files remain unsupported by text tools.
Mutations from concurrent tool batches are serialized per project in this process. Confinement is a path guard, not an OS sandbox against another process changing the filesystem concurrently; avoid moving project folders or changing symlinks mid-operation.
Removal is permanent. remove_path deletes with fs.rm; there is no recovery
store, list_removed/restore_file, or automatic purge — git (or a copy made first) is
the only recovery. A shell rm is likewise a real deletion, and redirects/tee can
clobber a file. These file-tool protections are not an OS sandbox: an approved terminal
command can delete files the tools guard.
Terminal commands
run_command(command, cwd?, timeout_ms?) uses macOS /bin/zsh -f -c, with ignored
stdin and piped stdout/stderr, not an interactive terminal. cwd must be an existing
project directory (default project root), except unguarded auto also permits existing
directories elsewhere on the drive. The default timeout is 60 seconds, maximum
5 minutes; commands are limited to 16 KiB and captured output to a combined 1 MiB.
Overflow terminates the command and reports truncation, not unlimited collection.
Results include status, exit code/signal, stdout/stderr and truncation; failure is
reported faithfully. The live card shows lifecycle only, not raw command output.
In ask/file-only mode, every command gets a separate approval. Guarded auto runs
arbitrary commands without a prompt except for five risk categories (below); unguarded
auto runs every command except recognizable catastrophic deletion/disk destruction.
When a command needs approval, its full text,
canonical cwd and effective timeout appear in chat before the anchored yes/no question;
control/invisible characters and backslashes are escaped visibly rather than executed
by the terminal. Known router-key-bearing commands are rejected before display.
Commands use a minimal environment allowlist (standard path/home/user/temp/locale
variables, TERM=dumb), not ambient credential variables. Known router credentials
are redacted from captured output before model/history/transcript use. This is not
comprehensive secret isolation: commands can still read local configuration or make
network requests. Review what you approve. /bin/zsh -f disables ordinary user rc
loading; it does not isolate system startup behavior.
Guarded auto
Enable with flash --auto, /mode auto, or the /mode picker. It is opt-in for this
session, never saved as a default. files mode and legacy auto_approve remain file-only;
--auto, --auto-approve, and --absolute are mutually exclusive.
Guarded auto is trust-by-default: arbitrary commands and pipelines run without a
prompt, including unknown executables, interpreters, builds, tests and file mutations.
Exactly five categories still ask, and none can be hidden behind a wrapper, pipeline,
env assignment or literal sh -c body:
- Network access —
curl,wget,nc,ssh,scp,rsync,git fetch|pull|push|clone|remote, and similar. - Installs / package managers —
npm install|ci|add|…,pnpm/yarn add,brew,pip,apt,cargo install,go get,npx, and similar. - Privilege escalation —
sudo,doas,su,pkexec. - Paths outside the project — absolute paths,
~, parent (..) traversal, and symlink escapes. - Destructive Git —
git reset --hard,git clean -f…,git push --force,git branch -D.
Obviously catastrophic deletion or disk commands (recursive deletion of a filesystem
root, home or volume; diskutil erase; mkfs; dd/redirection to a raw disk) are
refused outright, not offered. Syntax the classifier cannot reduce to inspectable
words — command substitution, backticks, globs, here-docs, eval/source, subshells
and background jobs — fails closed and asks. Paths must resolve inside the project,
plus the CLI's own ~/.config/flashlite tree for reads. cp/mv never overwrite.
Guarded auto trusts arbitrary commands and project code. A classifier is not proof of safety, this is not an OS sandbox, and buildings/tests can execute arbitrary code with side effects. There is an external filesystem race between inspection and execution; command text cannot provide isolation. Do not enable auto in an untrusted repository. Turning auto off revokes queued grants, including after turning it on again, but does not interrupt work already running.
Unguarded auto
Enable with flash --absolute, /mode unguarded, or the /mode picker. It takes effect
immediately with a one-line notice — there is no typed acknowledgement or disclaimer
gate. A /mode switch applies to the session; the /config default-mode picker can save
it as the launch default (written to config as unguarded, never absolute). An
interactive terminal is required, and --absolute is rejected with -p, --check, or
piped input/output.
Unguarded auto has no approval prompts and no guarded-auto ask list. Ordinary rm,
builds, installs, Git changes, elevated, unknown and opaque commands all run. File tools
can read/change paths across the drive, including absolute paths and ~/, and commands
may start outside the project. The mode badge reads unguarded auto. Use only when you
trust the task and the files/code the agent will encounter.
The only exceptional action-confirmation gate recognizes very large irreversible
operations: recursive deletion of a filesystem root, whole home/user tree, named
system root or whole mounted volume (including whole-tree glob variants); destructive
diskutil erase/partition/APFS-delete operations; formatting a raw disk with
mkfs/newfs; dd writing a raw disk; or shell output redirected to a raw disk.
Recognized literal sudo/env wrappers, compound commands and sh/zsh/bash -c
bodies are checked too. These show the exact command/canonical cwd/timeout and require
an explicit yes for that command only, without disabling unguarded auto. Denial,
cancellation or revocation prevents that command from starting.
This is a best-effort recognizer, not a safety guarantee: it does not inspect script files, aliases, arbitrary expansions, remote code or general interpreter programs, and it does not make opaque commands safe. Unrecognized actions still run without a prompt in unguarded auto; do not rely on this gate to protect your machine.
This is current-account access, not automatic root/sudo or a bypass of macOS privacy permissions. No interactive password entry is provided. Tool byte/tree limits, regular-file and symlink-tree rules, no-overwrite copy/move and permanent-deletion guards, bounded command output/time and cancellation remain. Commands can still perform permanent deletion. Known router credentials are rejected in commands and redacted from command output and unguarded file-tool results/errors before history; this is not comprehensive secret isolation or a network sandbox.
Unguarded auto is session-only, never saved or enabled by auto_approve. /mode ask
disables it; queued unguarded commands/mutations recheck their mode generation before
executing. Turning it back on cannot revive old grants. Running commands are not
interrupted by a mode change; use Ctrl+C to stop the active turn. File tools recheck
live authorization at bounded checkpoints and before commits; revoked copies roll back
new destination data.
Command cancellation
Ctrl+C, /clear, /exit, timeout and output overflow terminate the process group,
with TERM→KILL escalation. Normal shell completion also cleans up ordinary background
descendants. There is no persistent-background-job or interactive-input API; programs
requiring a TTY or password entry may fail. Descendants that detach into another
process group/session can escape cleanup; no sandbox guarantee is made. Commands
serialize with project mutations in this process and recheck cancellation before
queued execution starts. Tool results remain paired with calls after cancellation,
so continuing the conversation does not send broken tool history.
For example: “Create a small website in site/, copy my assets into it, and run its
tests.” In ask mode, the agent creates the structure with approval and asks before
running tests. In guarded auto, the project work runs without per-call prompts, but a
network install or sudo still asks. Package installs and downloads are performed
through authorized commands, not silently through filesystem tools.
Development
src/index.ts entry, flags, bootstrap test/ node:test suites
src/client.ts RouterClient (HTTP, retry) scripts/ SSE harness (live)
src/adapters/ openai.ts, anthropic.ts
src/tools.ts file tools + confinement src/loop.ts agent round driver
src/repl.ts REPL shell src/lineinput.ts raw-mode editor
src/sessions.ts transcripts
src/markdown.ts terminal markdown rendererLive wire-shape verification:
node scripts/sse-harness.ts --model <id> --dialect openai|anthropic|auto [--tools] [--save test/fixtures/x.sse]Manual E2E checklist
- [ ]
flash --checkgreen;npm testgreen;npm run typecheckclean - [ ]
/model→ arrow-key picker → streamed chat answers (markdown-rendered) - [ ] multi-tool task (create → edit → grep) exercising y / a / n approvals
- [ ] Page Up/Down scroll chat with input anchored, including mid-stream; mouse drag selects text
- [ ] Ctrl+V queues a clipboard screenshot without submitting; Ctrl+X clears pending images
- [ ] text Cmd+V still works;
/palette has no image commands;/helpopens shortcuts - [ ]
/clearstarts a fresh transcript; session files exist - [ ] type during streaming; Enter queues steering and leaves tool/result ordering valid
- [ ] an unfinished draft survives a permission question and turn completion
- [ ] Ctrl+C mid-stream prints
*[stopped]*and restores any unapplied steering - [ ]
flash -p "hi"one-shot;echo "…" | flashpiped mode works - [ ] bad key exits with the 401 hint;
FLASHLITE_BASE_URLpointing at a dead port reports a network error, not a crash - [ ]
flash --debugshows frame sizes and retry metadata without message content or credentials
Notes
- Zero runtime deps —
src/andscripts/use Node built-ins only. - Never print an API key — not even masked. Real keys live only in the local config.
- Diagnostic logging never includes message content. Private transcripts live under
~/.config/…/sessions/; normal UI/one-shot output shows the requested answer.--debugreports metadata only, never raw SSE payloads.
