Releases: sergenes/scootcli
Release list
scoot 0.14.2
- A malformed tool call no longer poisons a session. When a turn was cut off at the model's output limit, its tool-call
argumentscould be left as truncated, invalid JSON. scoot tolerated that locally, but it was replayed verbatim on every later request, so a provider that validates the JSON (an OpenAI-compatible or Copilot-style gateway) rejected the whole request and even a trivial next prompt failed until you reset. Outbound tool-call arguments are now sanitized for the OpenAI and OpenAI-compatible wires: a missing, empty, or unparseable value is sent as{}, the call is never dropped so it still pairs with its result, and stored history is left untouched. The Anthropic wire already did this.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.14.1
- The model list no longer floods the screen. With several providers,
/modelandscoot modelsdumped every model at once, so in the dock or a tmux pane you could only see the bottom of an unscrollable list. Each provider is now capped (the active model shown first, then… +N more),/model <provider>lists one provider in full, andscoot models --provider <name>does the same on the command line.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.14.0
- Two more providers: xAI (Grok) and Groq. Both are OpenAI Chat Completions-compatible registry rows, so they work through the existing wire, auth, and base-URL machinery:
scoot auth set xai/scoot auth set groq, then--model xai/grok-code-fast-1or--model groq/.... Keys areXAI_API_KEYandGROQ_API_KEY; each provider's base URL is overridable withSCOOT_XAI_BASE_URL/SCOOT_GROQ_BASE_URL. The defaultpreferred_modelsare a best-effort ordering; the livescoot modelslist is authoritative. - Date/time badges on each turn. A dim date/time line sits above your prompt, and a dim badge follows the reply with the finish time and how long the turn took, like
Wed Sep 19 · 22:07 (30s). Interactive only, shown on a TTY unless--no-labels; headless and one-shot output are unchanged. testandreviewpresets.scoot test [path]runs the tests and fixes failures;scoot review [path] -m "..."reviews read-only. They joinexplainandedit.- The setup help lists every hosted provider. The "no provider is ready" guidance is now built from the registry, so openai, anthropic, xai, and groq all appear (and a future provider shows up automatically) instead of a hardcoded openai/anthropic pair.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.13.0
- The status bar keeps up during a long turn. It was redrawn only at the prompt, so context, tokens, and message count looked frozen through a multi-step turn. It now repaints after each streamed model call and each tool run, and shows a per-turn
⚒ Ntool-call counter (reset every turn, so it never desyncs across resume). Interactive only; headless and the machine interfaces are unchanged. - Long turns compact before they overflow. Compaction was reactive and REPL-only; a single large turn could exceed the model's window. A context check now runs before each model request in the shared loop (REPL, headless, and one-shot), keeping the most recent turn verbatim, summarizing what came before, and bounding the text sent for summarization. The summarization call is counted in usage and cost.
- One config directory, resolved the same way everywhere.
.env, hooks, saved keys, and preferences now resolve their directory identically:SCOOT_CONFIG_DIR, elseXDG_CONFIG_HOME/scoot, else~/.config/scoot. The hooks path is unchanged for the common cases; saved keys and preferences now honorXDG_CONFIG_HOMEwhen it is set, and fall back to reading the legacy~/.config/scootso no one has to re-authenticate.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.12.0
- Accurate turn usage.
turn_end.usage, one-shot--jsonusage, and--verbosenow report the whole turn's tokens, not just the last model call. - Per-model cost survives resume; reset clears it. Per-model token totals are saved with the session, so a resumed session shows its real cost instead of zero;
/resetnow clears the cumulative accounting instead of carrying it forward. - Vision and classifier calls are accounted. Image-description and router-classifier calls now count toward session usage and cost, so
/statusand the machine cost fields are complete. - Auto vision stays on the selected provider. When the vision model is
auto, scoot prefers a vision-capable model from the selected model's own provider, so a local session no longer silently sends an image to a hosted provider; setSCOOT_VISION_MODELto override. - Image descriptions keep their number and the prompt is left intact. Skipping an oversized image no longer renumbers the rest, so the
[Image N]labels keep matching the badges; detecting an image path no longer collapses the prompt's line breaks or unescapes backslashes in the surrounding text. - Tighter test isolation. The test fixture clears every scoot, provider-key, and proxy variable (both cases) and removes its temp directory afterward.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.11.0
- One-shot
--jsonis exactly one JSON object (R18). The JSON path used the interactive UI, so plan updates and approval prompts printed to stdout before the final object, and a hook-blocked prompt produced no JSON at all. A quiet one-shot UI now sends every diagnostic to stderr and declines an approval it cannot ask, and stdout carries the single result object on every exit path. - Untrusted terminal control sequences are stripped (R21). File diffs, tool output, replayed transcript, and model output (streamed or buffered) had ANSI and control bytes passed through, so a malicious file or reply could recolour the screen or hide an approval preview. Such text is now sanitised before display, keeping tabs and newlines, with split escapes handled across stream chunks.
- The router cannot loop between two dead models (R16). Rules and the classifier ignored the failed-model set, so fallback could alternate A to B to A while each attempt burned a step. Model selection now skips every model that already failed this turn, and fallback is bounded independently of the step count.
- "Allow this path" lasts one call (R20). The one-shot scope choice permanently added the path to the session grants, so "this path" behaved like "this session". It now lasts a single tool call and is asked again next time; the directory and anywhere choices still last the session.
- ripgrep errors are not "no matches". An invalid regex or an I/O failure (ripgrep exit 2 or more) is now surfaced as a search error instead of an empty result.
- Numeric settings are validated.
SCOOT_TIMEOUT,SCOOT_MAX_STEPS,SCOOT_COMPACT_AT, andSCOOT_IMAGE_MAX_BYTESnow give a clear config error with sensible bounds instead of a raw traceback on a bad value, andAGENTS.mdis read with a size cap. - One atomic JSON writer. Credentials, preferences, and sessions share a single writer that writes through a uniquely named temp file and an atomic rename with owner-only permissions; credential updates are no longer truncated in place, and temp names no longer collide between processes.
- Subprocess output is bounded (R15).
run_shell, ripgrep, and hook output were buffered in full bycommunicate()before truncation, so a runaway command could exhaust memory. Each stream is now drained by a reader thread into a buffer capped at 512 KB, with the process-group cancellation and timeout unchanged; a command that never stops printing is killed at the timeout instead of buffering without end.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.10.0
The second release from the outside review of 0.8.1: the findings that change what a repository can make scoot do. The yolo default stays.
- A cloned repository cannot reconfigure you (R02). A project
.envcould set anySCOOT_*variable, including a provider base URL, which sent your own API key to a host of the repository's choosing on the first request, in every approval mode. The project layer now has a narrower allowlist: it may choose the model, effort, and terminal settings, but not base URLs, proxies, the approval mode, the scope, the root, hooks, or scoot's config and state directories. Those keys are ignored from a project file and named in a notice at start; your global~/.config/scoot/.envand the environment still set them. Breaking for anyone who kept a base URL or a proxy in a project.env: move it to the global file. - Project hooks need your trust (R01).
.scoot/hooks.jsonran shell commands at session start in an unfamiliar checkout, before any approval. It now runs only after you review it and run/hooks trust; the trust is a digest of the file, so an edited file asks again, and/hooks untrustwithdraws it./hookssays whether the project file is trusted, and an untrusted one is named at start (REPL, one-shot, and a headlessnotice). Adenyfrom any hook now wins over anallowfrom another (an early projectallowused to hide a globaldeny), a project hook'sallowno longer waives the denylist confirmation (a hook in your own global config still does, so an editor or phone bridge that approves on your behalf keeps working), and provider API key variables are removed from every hook's environment, as the module always claimed. - The README says what is true (R03). The security section no longer claims shell tools are sandboxed: file tools are scoped,
run_shellruns as you, and the denylist is an accident guard. The denylist now recognisesrm -r -f,rm --recursive --force,git -C repo push, andgit --git-dir=x push. - Search cannot read outside the workspace (R07). Without ripgrep, the fallback followed symlinks and scanned hidden files, so a link inside a repository disclosed an external file through an auto-approved tool, and
.envwas searchable. It now walks like ripgrep does by default: no hidden files or directories, no symlinks, regular files only. The workspace map skips symlinks too. - Redaction reaches everything it can (R13). Saved sessions masked key-shaped strings only in top-level message text; tool arguments, list-shaped content, and the text copies inside provider replay items kept them. Redaction is now recursive, and stops only at signed or encrypted replay fields, which the provider rejects when changed. The README states that limit and that streamed output is not filtered.
- Headless answers name their request (R20). An
approvemessage without anid, or with another request's id, used to be accepted, so a queued answer could approve whatever came next. It is now a protocol error and ignored. [A]works (R20). The approval and scope menus advertised uppercaseAfor the session-wide choice, but every keypress was lowercased, soAmeant "once".Anow keeps its case; every other key is still case-insensitive.--rootdecides which.envapplies. The project.envwas searched from the launch directory, then the root was switched, soscoot --root ~/othercarried the launch directory's settings into the other project. The search now starts at the root.- Bounded reads (R15).
read_filerefuses FIFOs and devices, which would block, and reads the cap plus one byte instead of the whole file; an oversized image is rejected by size before it is read. - The user's prompt stands out. A submitted prompt is echoed as a full-width reverse-video band instead of a bold line, so it reads as clearly separate from the assistant's answer. Reverse video swaps the terminal's own colours, so it fits any theme; the band uses the current terminal width, so a resize shows on the next prompt; a multi-line or wrapped prompt becomes stacked bands. Without a TTY, under
NO_COLOR, or with--no-labelsit falls back to a plain❯line. - Session ids are file names (R17).
/forget ../xcould delete a JSON file next to the sessions directory. Ids are validated as basenames at every entry point, a malformed saved record is skipped when listing instead of breaking startup, and records are shape-checked on load.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.9.0
A reliability release, from an outside review of 0.8.1; the R-numbers are the review's finding ids.
- A worktree never loses work (R05).
/worktree keepand/worktree mergeused to read a failed commit (a rejected hook, a missing identity) as "no changes" and force-remove the worktree with the files in it.finishnow tells a clean tree, a committed one, and a failed commit apart, retains the worktree and the branch on any Git failure, and keeps the session in it until you fix it ordiscard. Work the agent already committed on the branch is merged too, measured against the recorded base commit. - Entering a worktree moves the scope (R06). The file scope and the hooks now follow the root, so a worktree nested under the original checkout no longer leaves the original in scope; path grants for the old root are dropped.
- Writes that cannot destroy a file (R08).
write_filewithout a stringcontentis an error, not an emptied file (an explicit""still empties it).edit_filerefuses a file that is not UTF-8 instead of silently replacing bytes, keeps CRLF files CRLF while letting the model quote them with plain newlines, and refuses to overwrite a file that changed after it was read. Both tools write through a temp file and an atomic rename that preserves the mode, so a failed write leaves the old file intact. - Failed commands explain themselves (R09). A failing test or build used to reach the model as
ERROR: exit 1; the captured output now follows the error line, so the model fixes the code instead of rerunning the command. - Resume keeps your approval mode (R04). Resuming a session that once ran in
yolono longer turns--approval alwaysintoyolo; the conversation is restored, the permission policy is this run's. - Every tool call gets a result (R11). Aborting or interrupting a batch answers every remaining call with a "not executed" result, and loading a saved session fills in any missing one, so the next request is never rejected for an unanswered tool call. Arguments that are not a JSON object never run a tool with empty arguments.
- A cut-off reply is not an answer (R12). A reply that hit the model's output limit, or a stream that closed before its terminal event, ends the turn as
incomplete: the partial text is shown with a warning, one-shot exits 1, headless emitsstatus: incomplete. Tool calls in such a reply are not executed and the retry is bounded. A 200 stream with no events, or with unreadable events, is an error rather than an empty success. - Stop means stop (R10). Shell commands and hooks run in their own process group, which ESC and the timeout terminate as a whole, with bounded waits and a monotonic deadline; a background grandchild no longer keeps the call blocked. The ESC watcher shuts the socket down before closing it and closes the response, so a stalled read wakes on every platform. Hooks honour the cancel event they were always handed.
- Streaming (R14). Short SSE events are delivered as they arrive instead of waiting for a 1 KB buffer, and a character split across two reads is decoded whole.
- Zipapp (R19).
scoot.pyznow exits withmain()'s status (a handled error used to exit 0) and contains the package only; CI checks both. - A blueprint.
docs/blueprint.mdis the minimal, self-contained spec for building a CLI agent like scoot: components and their contracts, the turn, the tools, the permission rules, the build order, and the acceptance checks.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.8.1
- A model per folder.
/model Xandscoot --model Xnow remember the choice for the folder you are in, so each project can start on its own model;/model X everywheresets the model for every folder without a choice of its own,/model forgetdrops the current folder's choice,/modellists both saved layers, and/statussays where the active model came from (--model,SCOOT_MODEL, this folder, every folder, or the default). The choice saved by 0.8.0 keeps working as the "every folder" layer;--rootpicks up the target folder's model.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.
scoot 0.8.0
--modelis remembered. Starting the REPL withscoot --model provider/modelnow saves the choice like/modeldoes, so a plainscootin that folder next time starts on the same model instead of the default. One-shot prompts and headless runs still leave the saved preference alone.- Resize without losing the dock. Zooming the font or resizing the window (tmux included) used to drop the status bar and the mascot and leave the input on the wrong row until the next keystroke, with a stale copy behind. scoot now handles
SIGWINCH: the editor wakes up at once, asks the terminal where the cursor went, scrolls the transcript's tail up if the smaller screen swallowed it, and redraws region, bar, and input in one write; a resize during a running turn is repaired the same way. Works for shrink, grow (tmux pulling history back), width-only changes, and wrapped multi-row input.
Install: pipx install scootcli && pipx ensurepath, or curl -fsSL https://raw.githubusercontent.com/sergenes/scootcli/main/install.sh | bash, or download scoot.pyz below. Upgrade: pipx upgrade scootcli.