Skip to content

Releases: sergenes/scootcli

scoot 0.14.2

Choose a tag to compare

@sergenes sergenes released this 25 Sep 14:50
b671248
  • A malformed tool call no longer poisons a session. When a turn was cut off at the model's output limit, its tool-call arguments could 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

Choose a tag to compare

@sergenes sergenes released this 24 Sep 03:15
6000c5b
  • The model list no longer floods the screen. With several providers, /model and scoot models dumped 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, and scoot 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

Choose a tag to compare

@sergenes sergenes released this 20 Sep 01:04
3e68044
  • 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-1 or --model groq/.... Keys are XAI_API_KEY and GROQ_API_KEY; each provider's base URL is overridable with SCOOT_XAI_BASE_URL / SCOOT_GROQ_BASE_URL. The default preferred_models are a best-effort ordering; the live scoot models list 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.
  • test and review presets. scoot test [path] runs the tests and fixes failures; scoot review [path] -m "..." reviews read-only. They join explain and edit.
  • 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

Choose a tag to compare

@sergenes sergenes released this 11 Sep 15:53
6a26c4e
  • 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 ⚒ N tool-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, else XDG_CONFIG_HOME/scoot, else ~/.config/scoot. The hooks path is unchanged for the common cases; saved keys and preferences now honor XDG_CONFIG_HOME when it is set, and fall back to reading the legacy ~/.config/scoot so 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

Choose a tag to compare

@sergenes sergenes released this 11 Sep 03:29
0653158
  • Accurate turn usage. turn_end.usage, one-shot --json usage, and --verbose now 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; /reset now 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 /status and 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; set SCOOT_VISION_MODEL to 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

Choose a tag to compare

@sergenes sergenes released this 10 Sep 03:02
3258448
  • One-shot --json is 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, and SCOOT_IMAGE_MAX_BYTES now give a clear config error with sensible bounds instead of a raw traceback on a bad value, and AGENTS.md is 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 by communicate() 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

Choose a tag to compare

@sergenes sergenes released this 08 Sep 13:48
5251784

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 .env could set any SCOOT_* 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/.env and 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.json ran 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 untrust withdraws it. /hooks says whether the project file is trusted, and an untrusted one is named at start (REPL, one-shot, and a headless notice). A deny from any hook now wins over an allow from another (an early project allow used to hide a global deny), a project hook's allow no 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_shell runs as you, and the denylist is an accident guard. The denylist now recognises rm -r -f, rm --recursive --force, git -C repo push, and git --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 .env was 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 approve message without an id, 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 uppercase A for the session-wide choice, but every keypress was lowercased, so A meant "once". A now keeps its case; every other key is still case-insensitive.
  • --root decides which .env applies. The project .env was searched from the launch directory, then the root was switched, so scoot --root ~/other carried the launch directory's settings into the other project. The search now starts at the root.
  • Bounded reads (R15). read_file refuses 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-labels it falls back to a plain ❯ line.
  • Session ids are file names (R17). /forget ../x could 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

Choose a tag to compare

@sergenes sergenes released this 07 Sep 22:53
60f1bbe

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 keep and /worktree merge used to read a failed commit (a rejected hook, a missing identity) as "no changes" and force-remove the worktree with the files in it. finish now 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 or discard. 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_file without a string content is an error, not an emptied file (an explicit "" still empties it). edit_file refuses 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 yolo no longer turns --approval always into yolo; 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 emits status: 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.pyz now exits with main()'s status (a handled error used to exit 0) and contains the package only; CI checks both.
  • A blueprint. docs/blueprint.md is 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

Choose a tag to compare

@sergenes sergenes released this 07 Sep 13:56
e01d62a
  • A model per folder. /model X and scoot --model X now remember the choice for the folder you are in, so each project can start on its own model; /model X everywhere sets the model for every folder without a choice of its own, /model forget drops the current folder's choice, /model lists both saved layers, and /status says 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; --root picks 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

Choose a tag to compare

@sergenes sergenes released this 07 Sep 13:31
60787dc
  • --model is remembered. Starting the REPL with scoot --model provider/model now saves the choice like /model does, so a plain scoot in 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.