Releases: eirikb/gg
179
check_or_update_tool used find() on the glob result, so with several cached entries for the same tool (rat_eq_3.18.0, rat_star_, ...) it only ever checked the alphabetically first one. A hard-pinned =x.y.z entry sorting first made 'update rat -u' report 'Already up to date' while the entry actually being executed (*) was stale.
Now all matching entries are checked/updated, each reported with its version selector (rat@=3.18.0, rat@~3.22, ...) like the all-tools path.
178
Docker Hub rate-limits anonymous pulls from shared GitHub runner IPs, which made test-container jobs fail in 'Initialize containers' before any test ran (and fail-fast then cancelled innocent sibling jobs). The same official images are served by public.ecr.aws/docker/library without those limits. Also set fail-fast: false so one transient pull failure only needs a re-run of that single job.
177
Busybox-style escape hatch for renamed gg.cmd: 'node.cmd gg update' behaves as 'gg.cmd update'. This lets a tool launched via its own applet (e.g. a renamed postmortemthis.cmd) call back into gg through the same .cmd file, via the GG_CMD_PATH it inherits.
The jump-out word is strictly lowercase 'gg' and is stripped uniformly, so 'gg.cmd gg node' also means 'gg.cmd node'. Plain applet dispatch is unchanged.
176
Feature/eirikb/agent cli tools (#262) * Add agent CLI tools: claude, codex, gemini-cli Three coding-agent CLIs from the major labs, all contained in gg's cache (no system-wide installers): - claude (Anthropic): native binary from downloads.claude.ai, no installer. Requires the extension-less-filename fix in bloody_indiana_jones (the asset is a raw binary named 'claude' with no extension, which the old .extension().unwrap() panicked on). - gemini-cli (Google): npm @google/gemini-cli on a gg-managed Node, via a new NpmPackageSpec pattern (mirrors the Ruby gems approach) that installs into npm_home/ in the tool's own cache dir. - codex (OpenAI): npm @openai/codex. NOT GitHub releases -- that page mixes alpha prereleases with many same-prefixed assets (codex-app-server, bundles, .zst), and gg's selector picked codex-app-server-package alpha and then silently fell through to a system /usr/bin/codex. The npm package resolves the right stable native binary per platform via optionalDependencies. npm installs are kept fully self-contained: npm_config_cache and npm_config_devdir are pointed inside the tool's cache dir so nothing leaks to ~/.npm or ~/.cache/node-gyp. Verified end-to-end in an isolated cache+HOME: all three download, install into .cache/gg, and run the cached binary (claude 2.1.168, codex-cli 0.137.0, gemini 0.45.2) with no install-time pollution of HOME. Extracted from feature/eirikb/agent-clis. * Add more agent CLI tools: qwen, kimi, vibe The remaining first-party lab CLIs (by the lab, for their own models): - qwen (Alibaba): npm @qwen-code/qwen-code on a gg-managed Node, same NpmPackageSpec pattern as gemini-cli/codex. - kimi (Moonshot AI): native per-platform binaries from MoonshotAI/kimi-cli GitHub releases. - vibe (Mistral, powered by Devstral): native per-platform binaries from mistralai/mistral-vibe GitHub releases. kimi and vibe both publish several same-platform assets per release (kimi's -onedir bundles, vibe's vibe-acp-* editor-protocol builds), so the os+arch+version sort ties and selection becomes arbitrary -- the same prefix-collision problem codex had on GitHub releases. New excluded_asset_keywords on the GitHub executor filters these out at download-listing time, keeping selection deterministic. xAI, DeepSeek and Z.ai have no official CLIs (community tools only), so this completes the set for now. Verified end-to-end in an isolated cache+HOME: all three download, install into .cache/gg, and run the cached binary (kimi 1.47.0, vibe 2.14.1, qwen 0.17.1). Aliases qwen-code/kimi-cli/mistral-vibe also dispatch correctly.
175
The stage4 binary (reqwest) already reads the conventional proxy env vars, but both bootstrap downloaders ignored them:
-
stage2.ps1 (Windows): Invoke-WebRequest only knows the system WinINET proxy, so pointing https_proxy at an alternative (e.g. unauthenticated corporate) proxy had no effect, and downloads died with errors like "Proxy Authorization Required". Now passes -Proxy plus -ProxyUseDefaultCredentials when https_proxy/all_proxy is set to an http(s) URL.
-
stage3 (Linux/macOS C): always connected directly to the blob host on port 80. Now parses http_proxy/HTTP_PROXY/all_proxy/ALL_PROXY ([http://]host[:port]), connects through the proxy and sends an absolute-URI GET. Non-http schemes (socks) are ignored; credentials are stripped with a notice (no base64 in stage3).
Also check the HTTP status line in stage3: previously a 404/407/502 response body was downloaded and only failed later with a confusing "Hash did not match"; now it fails immediately with "HTTP error NNN". The first read is NUL-terminated before the strstr header parsing, which was relying on luck before.
Tested end-to-end against the live blob store with a real release hash: direct, via a local forwarding proxy (absolute-URI request verified), creds-stripping, socks ignored, unreachable proxy, and 404 on both paths. stage2.ps1 logic verified with pwsh.
174
create_isolated_test_dir() named its temp dir after the current nanosecond timestamp. Under parallel test execution two tests could get the same timestamp, share a dir, and have one delete it while another was still using it -- intermittently failing at fs::remove_dir_all (seen in CI on macos-14).
Switch to tempfile::tempdir() for guaranteed uniqueness and automatic cleanup on drop, dropping the manual remove_dir_all calls. Verified stable across 20 repeated parallel runs.
173
download() streamed the response body regardless of status code, so a rate-limited or forbidden source (403/503 with an HTML/text body) was saved as the archive and only blew up later during extraction with a misleading 'Invalid gzip header' or 'Could not find EOCD' panic.
Check res.status() right after the response and panic with the URL and status code instead. Added a regression test that serves a 403 from a local socket and asserts the download fails with the status message (verified it fails without the fix).
171
- rustls-webpki 0.103.10 -> 0.103.13: DoS via panic on malformed CRL BIT STRING (high), plus two low-severity name-constraint issues
- tar 0.4.45 -> 0.4.46: PAX header desynchronization (medium) - relevant since gg extracts downloaded archives with this crate
- rand 0.8.5 -> 0.8.6 and 0.9.2 -> 0.9.4: unsoundness with custom loggers using rand::rng() (low)
Lockfile-only change; all transitive.
170
stage3 (unix):
- Replace unchecked gethostbyname (NULL deref = segfault on DNS failure, IPv4-only) with getaddrinfo and a connect loop, matching main-win.c. Fixes silent crash on IPv6-only or DNS-less networks.
- Guard HTTP response parsing: missing Content-Length or header terminator no longer NULL-derefs; read errors no longer loop on a dead socket. Both now print an error and clean up stage4.tmp.
stage2.sh:
- Distinguish 'no stage3 binary could execute' (exit 126/127, truly unsupported system) from 'stage3 ran but failed' (network, hash mismatch). The latter now reports a download failure instead of the misleading 'Your system is not supported'.
- Document that glob order intentionally tries aarch64 before x86_64 (Apple Silicon picks native over Rosetta).