Repository navigation
Releases: jizzel/proxima
Release list
Proxima v1.7.1
What's changed
Fixed
- The REPL crashed at startup when the terminal size was unavailable —
stdout.terminalColumnsthrows aStdoutExceptionrather than returning a default whenever the size cannot be read (output piped to a file, some CI runners, a PTY opened without a window size). Three unguarded call sites — the REPL header and two in the renderer — turned that into an unhandled exception before the first prompt, a hard crash over a cosmetic detail. All three now go through guardedterminalWidth()/terminalHeight()helpers that fall back to 80×24
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.1/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.1/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.7.0
What's changed
Fixed
- The
/modelpicker corrupted the terminal — it redrew by moving the cursor up by the full list length, so once that exceeded the terminal height the cursor could not reach the top and every arrow keypress appended another copy of the list. With ~90 models the picker filled the scrollback with dozens of duplicates. It now renders a scrolling window sized to the terminal, with a… n more · i/totalposition hint, and clears exactly what it drew - Video-generation models offered in the picker —
sora-2andsora-2-proappeared in/modelbut cannot serve/v1/chat/completions, so selecting one failed on first use - Model discovery failed silently — every failure path flattened to an empty list: a non-200 returned
[], any exception returned[], and the caller added anothercatchError. An invalid API key, an unreachable host, and a provider that legitimately serves nothing were indistinguishable, so/modelshowed three Anthropic entries with no hint that OpenAI had returned a 401. Providers now exposediscoverModels(), which keeps the reason, and the picker prints it —⚠ OpenAI: invalid API key (401),⚠ Ollama: Ollama not detected at …. Run: ollama serve.listModels()is unchanged, so nothing else had to move - The active model could be missing from its own picker — the list held only what discovery returned, so when a provider's discovery failed the running model was absent and could not be re-selected after switching away. It is now always included and marked
- The autocomplete panel left a copy in the scrollback on every keystroke — it drew a separator and suggestion rows below the prompt with real newlines, then restored the cursor with
\x1b[s/\x1b[u. Near the bottom of the terminal those newlines scroll the screen, and a cursor restore cannot undo a scroll, so the saved position was stale and each repaint appended another separator and row — the input area walked down the screen as you typed. Replaced with inline completion: the candidate's untyped remainder is shown as dim text after the cursor on the prompt's own line, with a(2/6 · tab)counter when several match. Nothing is ever written below the prompt, so the scroll cannot happen — verified on a PTY at the bottom edge of both a 40-row and a 10-row terminal, where typing now emits zero newlines against the previous 14. Tab accepts the shown completion and cycles on a second press;Entersubmits and the arrow keys mean history, since there is no longer a list to highlight - A terminal resize while the
/modelpicker was open corrupted the display — each redraw recomputed the window from the current height while the cursor still sat below rows drawn at the previous one, so growing the terminal moved the redraw above the old list and shrinking it left stale rows behind; the Enter/Escape cleanup recalculated the same way. The picker now tracks the rows it actually drew and moves and clears by that - The picker overflowed short terminals — the window had a fixed six-row floor, so a six-row terminal or a split pane was drawn eight rows tall, recreating the redraw corruption the scrolling window was added to fix. The window is now derived from the actual height, and below three rows — too short for a header, one row, and the hint — the picker prints the plain list instead. The regression test that missed this restated the formula instead of calling it, and asserted a bound two rows looser than the real one, so it could not fail; it now calls the function and checks every height
Changed
- The
/modelpicker lists current models instead of the whole catalogue — OpenAI returns every generation ever shipped (69 chat-capable ids, 29 of them dated snapshots identical to the alias they duplicate), which made choosing a model a scrolling exercise. The picker now drops dated snapshots and keeps the three newest versions per family, taking it from 72 entries to 17 — one screen, no scrolling. Discovery is unchanged and still live: a new release appears with no code change. Nothing became unreachable —--model openai/gpt-4o,openai_modelin config,/model <id>, and tab completion all still accept any id the endpoint serves. Anthropic's static list and Ollama's locally-pulled tags are already short and are not curated. Two rules keep curation from hiding working models: versions rank within a family rather than across the catalogue (a single ordering comparedllama-3.3againstgpt-5.4and dropped most of a proxy endpoint's catalogue), and an id with no parseable version is kept rather than dropped (o3ando4-miniare current reasoning models, not old generations)
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.0/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.0/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.6.1
What's changed
Fixed
- A credential pasted into a prompt was stored verbatim — found by testing the shipped binary. Masking covered tool arguments, so a key typed into a message sat unmasked in
~/.proxima/sessions/*.json. User and assistant content is now masked — with the bare-scheme rule tightened so it needs a credential-shaped token (a digit, base64/URL punctuation, or mid-token case mixing) rather than mere length, which had turnedimplement basic authentication middlewareintoimplement *** middleware. Tool results are still left alone so--resumereplays real file contents rather than making the model re-run tools to recover them.ToolCall.toJson()is masked for the same reason, closing the last unmasked serialisation boundary - Invalid
--modelvalues reported as internal faults —⚠ Unexpected error: Invalid argument(s): Unknown provider "gemini".read like a crash for what is a configuration mistake. Now⚠ Unknown provider "gemini".with a hint about theprovider/modelform, and the REPL resets the agent loop so the user can switch model and retry - The write critic reviewed an empty patch — it read
contentor apatchargument, butpatch_file's schema isold_str/new_strand no tool has ever had apatchargument. Everypatch_filereview therefore received an empty string and reported "the proposed change is empty or missing" instead of reviewing the edit. The edit is now rendered as a before/after diff git_statusfailed outside a repository — a freshly scaffolded project has no git repo, and the tool threw a hard error for that ordinary condition. It now reportsNot a git repository.so the model can act on it- Every Nth message was silently dropped in long sessions —
iterationCountis a persisted session field that was reset only after a failed turn, so it accumulated across successful ones. Once it reached the cap the loop guard was already false on entry: the body never ran and the user's message, already appended to history, was never sent to the model. The turn then failed, which reset the counter, so the next message worked — the symptom was an occasional message doing nothing at all. Now reset per turn, matching the documented per-request semantics - The iteration cap interrupted normal multi-file work — the default of 10 was tuned for single-file edits, but the system prompt directs the agent to verify every write by reading the file back, so each file costs two iterations. A seven-file scaffold hit the cap four times. Raised to 25, and budget exhaustion is no longer reported as a failure: it returns the new
TaskStatus.budgetExhaustedand goes through a newAgentCallbacks.onNotice, rendered dim rather than red, so API callers can distinguish a turn that ran out of steps from one that actually failed. The message says how to continue and names both config locations. Runaway loops are still bounded, and the stuck and spinning detectors fire long before this
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.1/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.1/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.6.0
What's changed
Added
- Official plugin distribution (SPECS §21.1) —
proxima plugin list | install <name> | remove <name> | update, and/pluginin the REPL. Plugins install from a published catalogue into~/.proxima/plugins/, making them available in every project.pluginis intercepted before flag parsing, because the main parser treats the first positional as a natural-language task — without that,proxima plugin install xwas silently sent to the model as a prompt - Install safety — SHA-256 verified against the catalogue before anything reaches disk, HTTPS-only downloads, and zip-slip rejected: any archive entry resolving outside the plugin directory aborts the install, with validation and extraction sharing one separator-normalised path so Windows-style
..\..\xentries cannot slip past a POSIX check. Plugin names must be a single path component, soplugin remove ../../Documentsis refused rather than deleting an arbitrary directory — and both the CLI and/pluginreport that rejection instead of propagating it, since an uncaught throw ended the REPL session over a typo.plugin.json's declaredexecutableis contained the same way: a declared../../victim.shwould otherwisechmod +xa file outside the plugin, whichPluginLoaderwould then follow and register under the malicious descriptor's name and risk level. An update moves the existing install aside and restores it if the staged rename fails, so a transient error cannot uninstall a working plugin. Execute permissions are restored from the descriptor's declaredexecutable— matching only*.shleft an extensionless entry point unrunnable. Zip-slip and the name/permission handling are not in SPECS §21.1 — a checksum proves the archive matches the catalogue, not that its contents are safe ~expansion in plugin directories (PluginLoader.resolvePluginDir) — config values come from YAML and never pass through a shell, so~/.proxima/pluginspreviously resolved to<workingDir>/~/.proxima/pluginsscripts/build_catalog.py— packages eachplugins/<name>/intoplugin-<name>.zip, computes checksums, and writescatalog.json; wired intorelease.yml, with a descriptor-validation step added toci.yml- Provider and tool test coverage —
AnthropicProvider(33) andOllamaProvider(25) had no HTTP-level tests at all despite Anthropic being the default provider, andtransport.darthad none despite existing to prevent a specific bug class. Plusrun_tests(23),write_plan(10),delegate(7), the plugin installer (22), and the plugin CLI (15)
Fixed
- CI pinned to Dart 3.11.1 — both workflows used
sdk: stable, so a formatter change between SDK releases faileddart format --set-exit-if-changedon all three platforms for code that formatted cleanly against the SDK named inpubspec.yaml. The pin makes formatting reproducible locally; raise it and the pubspec constraint together - Spinning detection interrupted legitimate exploration —
isSpinningfired on any six consecutive read-only calls without checking whether they targeted the same thing, so reading six different files on a small project was indistinguishable from looping. Observed twice in one session on a six-file project, blocking the task both times. It now also requires the window to be repetitive (fewer than four distinct call fingerprints). Three existing tests had encoded the wrong behaviour and were corrected /filesignored reads — it matched onlywrite_file/patch_file/delete_file, so it reported "No files accessed this session" after reading a file, while documented as "files read or written"- The write critic could not see the file path — it received only the tool name and content, so it reported changes as "lacking a file path" while the confirmation panel displayed that path directly above
--resume <missing-id>failed silently — an unknown session id started a blank session with no message, so a typo looked like a successful resume with lost history- OpenAI bypassed its own transport helper — a previous change replaced
transportPost/transportSendwith raw client calls, and the streaming retry had no error guard at all, so a transport failure there escaped as a rawClientException. This is the exact bug classtransport.dartexists to prevent - Inconsistent error classification across providers — Anthropic mapped 403 to
unknownrather thanauth, so a revoked key triggered a pointless fallback to a secondary that would also fail (FallbackProvidertreatsauthas non-retryable). Ollama mapped everything tounknown, leaving retry logic nothing to act on for a rate-limited remote instance. All three providers now classify identically write_planthrew a rawTypeErroron a missing or non-stringcontent, escaping theToolErrortaxonomy the agent loop uses to classify failures
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.0/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.0/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.5.0
What's changed
Added
-
LLM summarization of compacted history (SPECS §19.3, the last outstanding spec item) — when the context budget forces
Compactionto discard the oldest messages,SubagentRunner.runSummarizer()first condenses them into 3–5 bullets covering files touched, changes made, and decisions reached; the summary is pinned to the front of the retained history so the model keeps the gist of what it can no longer see. Previously that context was simply lost.Cost is bounded and opt-out: the call fires only when messages would otherwise be dropped, so an under-budget turn spends nothing.
max_tokensis capped at 512, no tools are sent, and the transcript is bounded both per-message (2000 chars) and in total (24000 chars, newest kept). Room for the summary is reserved before truncation so the injected summary cannot push retained history back over its budget — and only when truncation is actually needed, so a history that already fits is never truncated or summarized. Summaries are cached per span (the agent loop rebuilds context each iteration, so an uncached 10-iteration turn paid for 10 near-identical summaries), their token usage is recorded against the session, the transcript carries secret-maskedtoolInputso files read can be named, and relevance filtering runs before injection so the summary cannot be filtered out. Disable withsummarize_on_compact: false.Every failure mode degrades to the plain truncation already applied — no provider, a provider error, a non-final response, a blank summary, or a throwing summarizer. A summarization failure costs context, never the turn.
Compaction.compact()is nowasyncand takes an optionalHistorySummarizer; with none supplied its behaviour is unchanged. 14 new tests
Fixed
gpt-5.6-*models printed a debug warning on every turn — the unsupported-parameter retry lived only on the non-streaming path, so a model needingreasoning_effort: nonefailed the stream, printed⚠ [debug] Streaming failed, falling back, and recovered silently viacomplete(). Functionally correct but it read as a broken session. The streaming path now performs the same retry in place
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.5.0/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.5.0/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.4.0
What's changed
Added
- Update check on startup (
lib/core/update_checker.dart) — checks GitHub for a newer release at most once per 24 hours, in a fire-and-forget background fetch that never blocks startup and swallows every failure. When an update exists the notice offers the install command, skip-this-version (remembered in~/.proxima/update_check.json, and a later release is still announced), or remind-me-later. Proxima never installs anything itself — it has filesystem and shell access, so self-replacing its binary would make a compromised release channel equivalent to remote code execution; the user is given the command to run. Disable withcheck_for_updates: false; source builds (dev) never check. Version comparison is numeric, so1.10.0correctly beats1.9.0. The notice appears at the first prompt rather than before the header — the check is deliberately not awaited at startup, so consuming it there would read a still-pending result and drop the notice under any real network latency - Shared ignore matcher (
lib/tools/ignore_matcher.dart) —list_files,glob,search,search_symbol, andfind_referencesnow all skip the same paths: built-in defaults (VCS, dependency, build directories, generated Dart files), the working directory's.gitignore, and session patterns from/ignore. Previously there were three inconsistent skip lists — two byte-identical copies in the search tools, a different set inproject_index, and none at all inlist_files,glob, orsearch, sosearchwalkednode_modulesandlist_filesrecursed into.git. Full gitignore syntax is supported including negation (!keep.log), anchoring (/build.sh), directory-only rules (cache/), and**. All five walkers consult it for both files and directories, using working-directory-relative paths so path-qualified rules (vendor/lib/) and file rules (lib/excluded.dart) match — a basename alone cannot. The matcher lives onToolRegistryand is injected as a callback, leaving theProximaTool.executecontract — which plugins implement — unchanged; the agent loop refreshes it each turn so.gitignoreedits and/dirswitches apply without a restart. Clears the last unticked box in SPECS §18 /ignore <pattern>is now enforced — it previously stored and displayed patterns that no tool ever read, despite the README claiming it excluded them from context- Tests for
list_files(11) and the ignore matcher (20), covering gitignore precedence, negation ordering,**semantics, and Windows separators - Tests for the four previously untested tools —
patch_file(12: first-vs-all replacement, backup creation, theBACKUP_PATH:marker/undodepends on, no-write-on-failure),glob(8),search(9), andrun_command(11, including that a blocked command is never executed). SPECS §18 previously ticked these with a "works; no dedicated test file" caveat; every box is now backed by tests
Fixed
-
run_commandandrun_testswere broken on Windows — both hardcodedbash, which on Windows resolves to the WSL launcher; with no distribution installed it returns UTF-16 help text and exit code 1, so every shell command failed in the shippedproxima-windows-x64.exe. Both now usecmd /con Windows andbash -celsewhere, via a single sharedRunCommandTool.shellExecutable/shellArgs -
Timed-out commands are now killed, not orphaned —
run_commandandrun_testsusedProcess.run(...).timeout(...), which abandons the Future but leaves the child process running: a command Proxima reported as timed out kept executing invisibly, which is unacceptable for a tool behind a permission gate. Verified — asleep 2"timed out" at 1s still completed its work 3s later. Both tools now useProcess.startand kill the process on timeout. On Windows the whole tree is killed withtaskkill /T:cmd /c <command>launches the command as a separate process, so killing the shell alone left it running — holding the working directory open and continuing work the user was told had stopped. On POSIXbash -cexecs into the command, so killing the shell suffices -
globmatched nothing on Windows — glob patterns are written with forward slashes but the tool matched them against native separators, so**/*.dartcould never matchlib\nested.dart. Paths are now normalised to forward slashes for both matching and output, so results are consistent across platforms -
Transport errors surface as actionable messages (
lib/providers/transport.dart) —OllamaProviderandAnthropicProviderlet rawClientException/SocketExceptionescape, so a stopped Ollama produced six lines of socket dump instead of the message SPECS §22 promises. All three providers now route HTTP through one shared helper: a refused connection to Ollama readsOllama not detected at <url>. Run: ollama serve, and cloud providers report the unreachable host. Because these are nowLLMError(network), a configuredfallback_modelis finally tried for Ollama and Anthropic too -
Unreachable servers are no longer retried —
agent_loopretried a refused connection up tomaxRetriesLlmtimes and the stream path re-issued each failure viacomplete(), producing six lines for one problem. A server that cannot be reached now reports once and stops -
Per-model parameter support is discovered rather than predicted — the provider guessed from model names which models reject an explicit
temperature, and was wrong repeatedly (gpt-5rejects it,gpt-5.1accepts it). It now sends the request and, on a 400 naming an unsupported parameter, drops that field and retries once. The same mechanism setsreasoning_effort: nonewhen a model refuses function tools while reasoning is active (gpt-5.6-luna,gpt-5.6-sol) -
/modelpicker lists only usable models —babbage-002,davinci-002,gpt-3.5-turbo-instruct, the barechat-latestalias, and all*-codexmodels are excluded; codex models are deprecated or served only by/v1/responsesand 404 on chat. Models are ordered so current families (gpt-5*,gpt-4.1*,o*,gpt-4o*) appear before deprecated ones instead of alphabetically, which previously buriedgpt-4ounder a dozengpt-3.5-turbo-*variants
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.4.0/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.4.0/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.3.0
What's changed
Added
- OpenAI provider (
lib/providers/openai_provider.dart) — third provider alongside Anthropic and Ollama, with native tool calling and SSE streaming.--model openai/gpt-4o,openai_api_key/OPENAI_API_KEY. Handles the four wire-format divergences from Anthropic: the system prompt is sent as arole: "system"message rather than a top-level field; tool schemas nest underfunction/parameters; tool results userole: "tool"withtool_call_id; and assistanttool_calls[].function.argumentsis a JSON-encoded string, not an object.tool_calls[].idis preserved intoToolCall.callId— OpenAI rejects atool_call_idit did not issue, and the agent loop replays it on the next request. Omitstemperaturefor the o-series reasoning models, which accept only their default and reject an explicit value — including0.0— with a 400. Usesmax_completion_tokens(max_tokensis deprecated and rejected byo*models) and setsstream_options.include_usageso streamed responses still report token usage and cost - Injectable
openai_base_url— the same provider serves any endpoint speaking the OpenAI wire format (Groq, Together, OpenRouter, LM Studio) with no new provider class. Azure OpenAI is not supported: it requires anapi-keyheader, a deployment-specific path, and anapi-versionquery parameter - Robust transport handling — a
ClientExceptionfrom an unreachable endpoint is converted toLLMError(LLMErrorKind.network)on both request paths and the mid-stream body, so a configuredfallback_modelis actually tried during a network outage (FallbackProvidercatches onlyLLMError) - Per-model context windows —
capabilities.contextWindowis derived from the selected model rather than a flat figure, becauseContextBuilderbudgets the whole session — including a 10% output allowance — from it. Known OpenAI families match by prefix; anything unrecognised gets a conservative 8K, overridable withopenai_context_window - Provider-aware credential hints — an auth failure names the active provider's environment variable rather than always
ANTHROPIC_API_KEY - Single credential source for subagents — the
critic_on_writesubagent and the agent loop now share one_buildProviderRegistry()helper, so a provider's credentials cannot reach one and not the other - OpenAI pricing in
CostCalculatorfor gpt-4o, gpt-4o-mini, gpt-4-turbo, o3, and o3-mini - HTTP-level provider tests — first use of
package:http/testing.dart'sMockClientin this repo, injected through the existinghttp.Client?constructor seam. 23 tests assert the decoded request body and parsed response, covering all four wire divergences, error-code mapping, SSE streaming, and model discovery. 9 further tests coverProviderRegistrydispatch
Changed
- Model lists are no longer hardcoded in the CLI — the
/modelpicker and tab completion now build their list fromLLMProvider.listModels().OpenAIProviderandOllamaProviderdiscover models live — OpenAI filtering is an exclusion list rather than an allow-list, so chat models from compatible endpoints (Groq, Together, OpenRouter, LM Studio) survive discovery instead of being filtered out;AnthropicProviderreturns a static list. New OpenAI and Ollama releases appear with no code change. Removes the duplicatedSlashCommandHandler.anthropicModelsconst and its copy inrepl.dart. Live fetches run in the background at REPL start, are re-tried in the picker only when stdout is a TTY, and always degrade to[]— the picker still works offline
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.3.0/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.3.0/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.2.0
What's changed
Added
Agent Loop — Spinning Detection
StuckDetector.isSpinning(toolLog, {int window = 6})— second stuck-detection mechanism that fires when the lastwindowcalls are all read-only tools (read_file,list_files,glob,search,search_symbol,find_references,get_imports,git_status,git_diff,git_log). Catches models that loop on reads without ever writing — not caught by the identical-call detector.StuckDetector.spinWindow = 6— public constant exposed for callers that need to slice the log for display.AgentCallbacks.onStuckgainsreasonparameter ('stuck'|'spinning', default'stuck') — lets the renderer show a tailored message for each case; existing callers unaffected.TaskSummaryRenderer.renderStuckdifferentiated message — spinning shows "Agent is reading without making progress — last N calls were all read-only"; stuck shows the existing "Repeated tool calls detected" message.
Improved
plugin_loader.dart— fully async I/O —_loadOneconverted from sync toasync;existsSync→exists,readAsStringSync→readAsString,statSync→stat,list().toList()→await for; prevents blocking the Dart event loop during plugin discovery at startup.find_references_tool.dart— streaming directory walk —_walknow usesawait forinstead ofdir.list().toList(); processes entries one at a time and exits early whenmax_resultsis reached, avoiding loading the full directory listing into memory on large codebases.
Security
- Secret masking before disk writes (
lib/core/secret_masker.dart) — tool arguments are persisted to both~/.proxima/audit.jsonland~/.proxima/sessions/*.json; secret-shaped values are now replaced with***in both. Detects Anthropic (sk-ant-), OpenAI (sk-proj-,sk-), GitHub (gh[porsu]_), AWS (AKIA…), Slack (xox[baprs]-), and JWTs by pattern.Authorizationheaders consume the auth scheme (Bearer/Basic/Token/Digest/APIKey) and the credential after it, so opaque credentials with no provider prefix are masked too. Bare schemes (Bearer/Basic/Digest/APIKey/Token) are matched without anAuthorization:prefix, covering headers passed structurally as{'headers': {'Authorization': 'Token abc'}}. Argument names are normalised (camelCase split, separators collapsed) and matched per segment, soapi-key,x-api-key,apiKey,API_KEY, andAuthorizationare all recognised whileauthorandtokenizerare not. Masking is applied at the serialisation boundary only (AuditLog.record(),Message.toJson(),TaskRecord.toJson()) — never to the live in-memory session, whichAnthropicProviderandCompactionboth read. Terminal output and tool results are deliberately left unmasked so the confirm prompt shows the real command and--resumereplays correctly. Closes the last unmitigated Critical risk in SPECS §22. 46 new tests
Fixed
ollama_base_urlignored by/model—SlashCommandHandlerreadOLLAMA_BASE_URLfrom the environment directly, so a base URL set in~/.proxima/config.yamlwas honoured by the agent loop but ignored by the model picker. The handler now takes an injectableollamaBaseUrl(matching the existingisTtyinjection pattern) wired fromProximaConfig
Documentation
- SPECS §15.3 documents config precedence (YAML overrides environment); new §15.3.1 specifies secret masking; §18 MVP checklist reconciled against the test suite, with
list_files.gitignoresupport explicitly marked not implemented; §22 risk register updated - README gains a "Secret masking" subsection under Data & Privacy and an explicit config-precedence note
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.2.0/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.2.0/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.
Proxima v1.1.0
What's changed
Added
Terminal UX — Rich Feedback & Activity Indicators
-
AgentCallbacks.onIterationStart(iteration, maxIterations)— called at the start of each loop iteration;Rendereruses it to show⠋ Thinking… [N/10]spinner with iteration depth between tool calls -
AgentCallbacks.onToolExecuting(toolCall)— fires immediately before tool execution (after permission grant);Renderershows◆ Reading core/session.dart…activity line -
Animated blink dots on in-progress tool lines — executing line pulses
…→….→…..→…...every 400ms viaTimer.periodicso users see the tool is alive, not stuck -
Context preserved in result lines — the activity label from
onToolExecutingis held in_pendingLabeland reused inonToolResult; result now reads✓ Reading core/session.dart… (1423 lines) 0.3sinstead of✓ read_file (1423 lines) -
Per-tool elapsed time —
_toolStartTimecaptured atonToolExecuting, formatted asmsorsand appended dim to every result line -
Turn elapsed time in usage report —
_turnStartTimeset on iteration 1 viaonIterationStart; appended to the↑N ↓N total: Nline:↑1240 ↓387 total: 14832 4.2s cost: $0.0042 -
onThinkingnow shows⠋ Working…spinner after printing the reasoning line, bridging the gap between reasoning and the first tool executing line -
Clarify styling —
onClarifynow renders? questionwith cyan?instead of raw bold text -
_fmtElapsed(Duration)helper —<1000ms→NNNms,≥1000ms→N.Ns -
Static
showSpinner('Thinking...')calls removed fromrepl.dart; spinner is now driven entirely byonIterationStart -
342 tests total (up from 237 — 105 new tests across
find_references_test.dart,get_imports_test.dart,test/tools/plugin/shell_plugin_test.dart) -
find_referencestool (lib/tools/search/find_references_tool.dart,safe) — finds all usages of a symbol across the codebase using\b<symbol>\bword-boundary matching. Supportspathscoping,file_extensionsfilter,exclude_definitionflag (skips definition lines using the same heuristics assearch_symbol), andmax_results(default 100). Skips.git,node_modules,build,.dart_tool,.pub-cache, and generated files. Output:file:line contentlines followed by aFound N references in M files.summary. -
get_importstool (lib/tools/search/get_imports_tool.dart,safe) — parses all import/require statements from a single file and categorises them by source type. Supports Dart ([dart]/[package]/[local]), JS/TS ([node]/[local]), Python ([stdlib]/[local]), and Go ([stdlib]/[external]).resolve_paths: trueexpands local imports to project-relative paths. Throws typedToolError(notFound)when the file does not exist;ToolError(parseError)for binary files or unsupported extensions. -
Plugin system — shell/binary drop-in tools via
.proxima/plugins/<name>/:ShellPluginTool(lib/tools/plugin/shell_plugin_tool.dart) — wraps any executable as aProximaTool; passes args as JSON on stdin, reads result from stdout, enforces configurabletimeout_seconds; non-zero exit →ToolErrorPluginLoader(lib/tools/plugin/plugin_loader.dart) —load(dirs, workingDir)discovers plugin directories, validatesplugin.json(required fields:name,description,executable,input_schema), checks the executable exists and has the execute bit, parsesrisk_levelstring →RiskLevel(defaults toconfirmfor unrecognised values); logs startup warnings for invalid plugins and never throwsProximaConfig.pluginDirsfield —List<String>, default['.proxima/plugins']; parsed fromplugin_dirsYAML key; included incopyWith()_buildToolRegistry()inrepl.dartis nowasync; loads plugins after all built-ins; silently skips any plugin whose name conflicts with a built-in tool (built-in wins)
Plan Mode
/plan <task>slash command — runs the agent inSessionMode.safewithisPlanMode: true, produces.proxima/plan.md, then shows an arrow-key picker (Execute / Edit / Skip) before executing/executeslash command — executes the saved plan in.proxima/plan.mdwithout re-running the research phase/costslash command — shows current session cost and a cost table for the 10 most recent sessions- Shift+Tab REPL mode cycle — Shift+Tab now cycles through three modes: normal → plan → accept-edits → normal; each press updates the prompt tag (
❯/❯ [plan]/❯ [edits]) and shows a compact tip line below the prompt (⏸ plan mode on/⏵⏵ accept edits on); the previous verbose green print above the prompt is removed - Accept-edits mode (
❯ [edits]) — third cycle state; sets_permissionGate.modetoSessionMode.autotransiently so confirm-level tool calls (writes, commands) execute without the Approve/Deny picker; cycling back to normal restores the configured mode;_session.modeand the session file are unchanged (transient only) _ReplModeenum (normal | plan | acceptEdits) inrepl.dartreplaces the formerbool _planModefield_cycleReplMode()replaces_togglePlanMode(); syncs permission gate and calls_renderer.updateStatus(planMode:, acceptEditsMode:)with no print_modeTip()helper returns the tip string for the active mode (null when normal)ReadLine.readLine()gains optionalString? statusTipparameter; stored as_statusTipfield and folded into_renderPanel()— when no suggestions are visible and a tip is set, one dim tip line is drawn below the prompt; suggestions take priority (tip suppressed while panel is open)ReadLine.readLine()gainsonShiftTabcallback; detectsESC [ Z(standard terminal Shift+Tab), fires the callback, and returnsReadLine.shiftTabSentinel— no history entry, no completion side-effectsReadLine.shiftTabSentinelpublic constant (\x00__shift_tab__) — lets the REPL identify the toggle without a magic string literalProximaSession.isPlanModeruntime-only bool (not persisted) — set byProximaSession.create(..., isPlanMode: true)ProximaSession.cumulativeCostfield — tracks session-total USD cost; persisted to session JSON ascumulative_cost; incremented viarecordCost(double)ContextBuilderinjects a PLAN MODE system-prompt block whensession.isPlanModeis true — instructs the agent to use only read tools, then callwrite_planwith a structured markdown plan (context, step-by-step changes, tests, risks)WritePlanTool(lib/tools/agent/write_plan_tool.dart) —confirm-risk tool that writes agent-produced plans to.proxima/plan.md; supportsdryRun()
Model Persistence
/model <name>now persists the selected model asmodel:in~/.proxima/config.yaml— future sessions start with the last chosen modelProximaConfig.saveDefaultModel(model)static helper — creates~/.proxima/config.yaml(and parent directory) if absent; replaces themodel:line in-place, leaving all other keys untouched
Cost Tracking
CostCalculator.compute(model, usage)— maps known Anthropic model IDs to per-token USD rates; returns0.0for unknown/local modelsCostCalculator.format(cost)— formats USD cost as$0.0000(4 decimal places)AgentCallbacks.onUsageReportextended withturnCostandsessionCostparameters;Rendererappendscost: $N.NNNNto the usage line when non-zeroAgentLoopcomputesturnCostviaCostCalculator.computeand passes it toonUsageReportalongside the session cumulative/statusnow showsCostrow when session cost is non-zero
Search
search_symboltool (lib/tools/search/search_symbol_tool.dart) — searches for symbol definitions (class, function, variable, any) using regex across the working directory; supportspathscoping andkindfiltering; rejects path traversal
Interactive UX — Consistent Arrow-Key Pickers & Status Line
PickerWidget(lib/renderer/picker_widget.dart) — shared synchronous arrow-key single-choice picker; renders▶ Option hintrows with inverse-video highlight; used everywhere a fixed choice list is presented; Escape/Ctrl-C returnsdefaultIndex- Permission prompt → picker —
PermissionPrompt._confirmPrompt()replaced withPickerWidget.pick(options: ['Approve', 'Deny'], ...)with dim hints; high-risk typedCONFIRMpath unchanged - Stuck dialog → picker —
TaskSummaryRenderer.renderStuck()a/csingle-keypress replaced withPickerWidget.pick(options: ['Continue', 'Abort'], defaultIndex: 1)(Abort is the safer default when stuck) /modeno-arg → picker —/modewith no argument opens an arrow-key picker pre-selected to the current mode (safe / confirm / auto with hints); previously showed a dim text line only- Plan approval picker simplified —
_planApprovalPicker()inrepl.dartnow delegates toPickerWidgetinstead of the hand-rolled_renderPlanPicker/_clearPlanPickerhelpers (removed) - Persistent status line after each turn —
Renderer.onUsageReport()calls_printStatusLine()after the token line; showsmodel: <name>always,mode: <name>when non-default (non-confirm),[plan]when plan mode active,[edits]when accept-edits mode active Renderer.updateStatus({model, mode, planMode, acceptEditsMode})— push current state into the renderer; called frominitialize(),_switchModel(),_switchMode(), and_cycleReplMode()ClarifyResponsegainsoptionsfield —List<String> options(default empty); when non-empty the agent loop stays alive:onClarifyWithOptionsshows a picker, the selected answer is injected as a user message, and the loop continues without exitingAgentCallbacks.onClarifyWithOptions(question, options)— new abstract method;Rendererimplements it viaPickerWidget.pick(options: options); all mock callbacks in tests returnFuture.value(0)SchemaValidator._validateClarify()parses optionaloptionsarray fro...
Proxima v1.0.1
What's changed
Added
SessionMode.safenow enforced inPermissionGate— any tool abovesaferisk level is denied without prompt when the session is in safe mode (step 3a, audited assafe_mode_blocked)ToolErrorCodeenum onToolError(notFound | permissionDenied | pathViolation | timeout | parseError | unknown) — tools can now classify errors for actionable LLM feedback_formatToolError()helper inAgentLoop— tool errors now return structured context (Tool: …\nError: …\nSuggestion: …) instead of bare'Error: $e'stringsAgentCallbacks.onUsageReport(turnUsage, cumulative)— called after every completed turn;Rendererdisplays↑N ↓N total: Nas a dim line after each response- System prompt overhauled in
ContextBuilder._buildSystemPrompt(): identity section, 7 ordered operating rules (read-before-write, patch-over-write, verify-after-write, diagnose-before-retry, no-identical-duplicate-calls, safe-mode-tool-list, test-fix-cycle limit), and session state (mode + cumulative token count) - 3 new permission gate tests: safe mode blocks confirm tools, safe mode allows safe tools, deny list pre-empts risk classification
TestOutputParser— parses raw test runner output into structuredTestResult/TestFailureobjects; supports Dart, Jest, Pytest, Cargo, Go; graceful fallback to raw string on unparseable outputrun_teststool now embedsFRAMEWORK:<name>marker in output and returns a concise structured failure summary (test name, file, message) before the raw output; LLM gets actionable context instead of wall-of-text stdout- 15 new parser tests covering all five frameworks and the framework marker extractor
- Critic subagent (
SubagentType.critic) — pre-commit review agent that fires before the permission prompt onwrite_file/patch_fileatconfirmrisk; returnsCriticResultwithapprove | warn | block_suggestionverdict; advisory only, never a hard gate; silent onapprove; displays amber/red note above y/n prompt forwarn/block_suggestion CriticResult,CriticVerdict,CriticIssuetypes added tosubagent_runner.dartSubagentRunner.runCritic()— never throws; graceful fallback toapproveon LLM error or malformed JSON; strips markdown fences; capped at 1024 tokensPermissionGate.criticCallback— optionalCriticCallback?wired in before step 6 (user prompt); only fires for confirm-level write tools, never inautomodePromptCallbackextended with{CriticResult? criticResult}named parametercritic_on_write: trueconfig field in.proxima/config.yaml(default: true)ProximaConfig.criticOnWritefield with yaml keycritic_on_write- Critic wired in
repl.dartusing the active model and a fresh provider per invocation - 7 new critic tests (approve/warn/block_suggestion/malformed JSON/LLM error/system prompt/markdown fence)
ProximaSession.fileCache—Map<String, String>keyed by canonical path; populated by the agent loop after every successfulread_filecallCompaction.deduplicateFileReads()— new Pass 0 that replaces all but the most recentread_filetool result for any given path with[File already in context]; integrated intoCompaction.compact()via optionalfileCacheparam; estimated 15–30% token savings in file-heavy sessionsContextBuilderpassessession.fileCachetoCompaction.compact()- 11 new compaction tests covering deduplication, pruning, and truncation
FallbackProvider(lib/providers/fallback_provider.dart) — wraps a primaryLLMProviderwith a secondary; transparently retries on non-authLLMError; auth errors rethrown immediately;listModels()also falls back; no changes toAgentLoopProviderRegistry.create()accepts optionalfallbackModelstring; wraps primary inFallbackProviderwhen set; best-effort (ignores fallback config errors)ProximaConfig.fallbackModelfield with yaml keyfallback_model(default: null)- Fallback model wired in
repl.dart_getAgentLoop() - 5 new fallback provider tests
Fixed
FallbackProvider.stream()never reached secondary — on primary stream failure the old implementation yielded a done chunk withhasToolUse: false, which the agent loop treated as a successful empty response; secondary was never tried. Fixed by rethrowing the non-authLLMErrorso_streamResponse's catch block falls back tocomplete(), which correctly retries on the secondary- Token usage display appeared before response separator —
onUsageReportwas called beforeonFinalResponse/onClarify, so the dim token line printed above the separator instead of below it. Swapped order: text → separator → usage Compaction.deduplicateFileReads()missing role guard — assumedmessages[i-1]was always an assistant message; added explicitif (assistantMsg.role != MessageRole.assistant) continueguard for malformed history- Critic
diffOrContentfallback was too verbose —toolCall.args.toString()sent the full serialized args map to the critic LLM when neithercontentnorpatchwas present; replaced with''to avoid wasting tokens on irrelevant data - 4 additional tests (streaming fallback, auth rethrow, fallback path, orphaned tool message)
- 237 tests total
Installation
macOS / Linux — install script
curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | shmacOS (Apple Silicon + Intel via Rosetta 2)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.0.1/proxima-macos-arm64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaLinux (x86_64)
curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.0.1/proxima-linux-x64 \
-o /usr/local/bin/proxima && chmod +x /usr/local/bin/proximaWindows (x86_64) — PowerShell
irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iexOr download proxima-windows-x64.exe below and add it to a directory on your PATH.