* Add German translation and harden paths against unset env vars
Add a German locale to the GUI, tray menu, usage alerts and website home page, and refuse to start when the home folder is unset or relative so nothing is written under the working folder; the pi plugin no longer adopts the user's PI_CODING_AGENT_DIR, and an inherited APPIMAGE no longer selects portable mode. Scripts refuse to run from the wrong folder or empty a shared out folder, and a new internal/testenv sandboxes every test package from the user's real files, which also fixes the Claude picker and Termux installer tests on Windows.
* Makefile: know magpie's folder by its go.mod, so paths with spaces work
The check compared $(CURDIR) with $(dir $(abspath …)), which make splits on spaces, so a checkout under 'My Projects' or 'John Doe' refused every target. It now reads go.mod relative to the working folder and wants magpie's module there: make -C and a symlinked Makefile work, make -f from another folder still stops before any recipe runs.
* i18n: German for the strings main added since, with a parity test
75 strings main added (group order, patterns, slow-start wait, usage caps, RTK savings …) get their German, and two that main dropped go. gui-de.test.cjs checks, as gui-ja does, that de has every zh string with its placeholders and tags, and that the window reads in German when picked or on a German system.
* appdir: ignore relative folder variables when reading, keep them for children
CheckEnv no longer unsets anything. magpie reads the XDG folders, APPDATA/LOCALAPPDATA and every agent's folder variable through appdir.Getenv/LookupEnv, which pass over a relative value; the environment stays as it is, so the agents magpie starts get the variable as the user set it. That covers what proc.UserPath lends from the login shell too, which is set after main's check and reached the desktop app unchecked. On Windows a path rooted on the current drive (\Users\x, Git Bash's /c/x) counts as usable, and magpie version and help answer without a home, before run makes any folder.
* Sandbox the tests main added since
internal/wslrun gets testenv's TestMain, and four tests main added that set HOME alone set their home with testenv.SetHome, so on Windows they don't read the runner's profile through USERPROFILE.
* agentenv: PI_CONFIG_DIR and OPENCODE_DB take relative names on purpose
omp puts $PI_CONFIG_DIR under the home and OpenCode a relative $OPENCODE_DB in its data folder, so neither depends on the working folder: appdir no longer passes them over.
* i18n: German for the Library's multi-select and System One's decision API
The 19 strings main added since (#791's skill multi-select, the decision API snippets) get their German; the one main replaced (four APIs → five APIs) goes.
* appdir: tidy the import into magpie's group; say what Go makes of /c/x
The appdir import joins the group of magpie's own imports where a file has one. rooted's comment now says that Go takes a Git Bash path like /c/x as \c\x on the current drive, not C:\x.
* plugin,gui: a stalled stream can't wedge the host, stale reads can't draw, and the browser suite runs in CI
internal/plugin: the host has one reader of the child's stdout, and it handed
each message to its call by sending on that call's channel. A caller that
stopped draining a streamed body (a slow HTTP client on a plugin-backed chat
stream) filled the channel; the reader then blocked, and every other call
through the host -- all plugin providers and accounts, moved built-ins
included -- stopped being dispatched until the stalled stream was drained. A
second call's answer arrived only after the first was drained.
Each call now queues its head and chunks in a bounded backlog (4 MiB of
chunks, 4096 of them) drained by its own reader, so the host's reader never
waits on one consumer; a call whose consumer stops reading is given up on
alone, with the limit as why. Aborts go through a goroutine too (abortLoop),
so a child that stopped reading its stdin can't make an abort wait either.
host_test.go covers the stalled consumer, the bounds and the abort path.
internal/gui: loadUsage() and refreshUsage() both read the Overview's
usage?period=, and neither checked after the await that its answer was still
the newest: a 24 h answer landing after a 7 d one drew the old period's
figures under the new period's name, and leaving the tab could still write the
Overview's cost into the shared header. Both now claim one shared read
generation before the request and draw only when it is still the newest and
the page, tab and period are unchanged. providersWhileFetching() also stopped
for good after one failed poll or reload; it now asks again.
usage-stale-answer.test.cjs and providers-fetching-retry.test.cjs lift the
real functions out of app.js and drive them with a faked api/page/timers, so
they need no browser; usage-refresh-flight.test.cjs gains the browser case for
the same race.
Makefile/.github: `make test-ui` runs every internal/gui/tests/*.test.cjs
through Node's test runner -- auto-discovered, UI_TEST_CONCURRENCY files at a
time (default 2) -- and a new `ui` job in Test runs it on ubuntu-latest with
Playwright's Chromium and WebKit. A missing browser fails the job; it never
skips to a pass. internal/gui/tests/README.md points at `make test-ui` instead
of a hand-kept command line.
Verified: node --test on the two new files 21/21; go test -count=1
./internal/plugin/ ok; go test -race -count=1 ./internal/plugin/ ok; go vet
./internal/plugin/ and go build ./... clean; node --check on the changed JS;
actionlint on the workflow.
Not verified: the full browser suite is not green yet. `make test-ui` is 1641
passed / 70 failed of 1711 across 225 files, and 12 of the 19 failing files
fail the same way on a pristine main (a known baseline: a fixture boots the
page in web mode while asserting the desktop window's narrow behaviour). The
`ui` job will be red until those baselines are dealt with -- nothing is
skipped or masked -- and the job has not yet run on a GitHub-hosted runner.
* plugin: distinguish stalled readers from large responses
* plugin: clarify best-effort abort delivery
* termux: build natively and run plugins on Bun 1.4.2
Select nogui for Android builds, open browser and OAuth links with
termux-open-url, and write an executable Termux:Boot script that starts
the gateway with shell-quoted paths.
Bun 1.3 has no official Android build. Upgrade the default plugin runtime
to 1.4.2, add the official Android arm64 and amd64 download targets, and
update SHA-256 checksums for every supported platform.
Document Termux installation, startup and source updates.
Validation on Termux arm64:
- Native build and go vet pass.
- A fresh config downloads and verifies Android Bun 1.4.2 and loads a
test plugin.
- Boot startup can be enabled and disabled.
- The Web page, authenticated state API, unauthenticated API rejection,
gateway healthcheck and Web quit action work.
- Browser argument preservation, boot path quoting and permissions, and
plugin tests pass.
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a10014-56c1-73d3-b3ee-2ea569713989
* site: install Android builds in Termux
Detect Termux through TERMUX_VERSION or its PREFIX and select the Android
CLI asset instead of a Linux binary. Install into PREFIX/bin by default,
keep MAGPIE_BIN_DIR overrides, and suggest the Web or terminal interface
without Linux desktop setup instructions.
When the release has no Android asset, explain how to build from source.
Add android/arm64 to release-cli and stop the build loop on failure.
Document the installer and its release-asset requirement.
Shell syntax and all 11 site tests pass, covering platform selection,
custom paths, missing assets, checksum rejection and existing Linux
installation paths. The Android arm64 release build succeeds on Termux;
with a simulated release feed, the installer verifies and installs that
binary into PREFIX/bin and the installed command runs successfully.
Amp-Thread-ID: https://ampcode.com/threads/T-01a10014-56c1-73d3-b3ee-2ea569713989
Co-authored-by: Amp <amp@ampcode.com>
* ci: check Android releases and the installer
Build both linux/amd64 and android/arm64 through make release-cli in the
CLI test matrix. Run the site and installer tests in the Linux job with
Node 22, and check the installer's shell syntax.
The release workflow in yetone/magpie-releases already calls the source
Makefile's release-cli target, uploads dist/magpie-cli-*, and generates
checksums for every artifact. The Android target added to TARGETS is
therefore included without changing that workflow.
Replace the full Termux README section with a short platform note beside
the Linux and Windows installation requirements, removing duplicate
commands, the fork URL and temporary release availability details.
Both CLI release builds, all 11 site tests, shell syntax checks and
actionlint validation pass locally.
Amp-Thread-ID: https://ampcode.com/threads/T-01a10014-56c1-73d3-b3ee-2ea569713989
Co-authored-by: Amp <amp@ampcode.com>
---------
Co-authored-by: Amp <amp@ampcode.com>
The Windows app cross-compiles (WebView2, no cgo) as a GUI program with an
icon, version info and a DPI-aware manifest; run from a terminal with a
command it borrows the console. The Linux app links GTK 3 and WebKitGTK
4.1. Off the Mac the app updates its own binary from its own build:
downloaded in the background, swapped in on restart or quit, and the
relaunched process waits for the old one before taking the gateway. The
window leaves room for traffic lights only on a Mac.
The product, module path (github.com/yetone/magpie), binaries, env vars
(MAGPIE_ADDR, MAGPIE_DEV_UI, MAGPIE_THEME, MAGPIE_DEBUG), config and cache
folders (~/.config/magpie, ~/.cache/magpie), bundle id (com.yetone.magpie),
gateway provider id and token, and every UI string now say magpie.
On first run magpie copies ~/.config/dial and ~/.cache/dial to the new
folders when they don't exist yet (copy, not move, so a still-running dial
keeps working; the old folders can be deleted by hand later).
The logo is a small round magpie in profile: one path for the header and
the tray, and the app tile adds the white belly and the blue-green tail.
The header mark is drawn in ink rather than the accent colour, which
fought the text in light mode.
fswatch swallows SIGINT, so the shell went round again instead of
exiting; a trap now stops the child and leaves. The loop also kills
only its own dial-dev rather than every one on the machine.
The editor grid's 1fr column had an auto minimum, so a long endpoint
error pushed the dialog past the window edge.
Every model option now names its vendor's bundled logo: a models.dev
provider id maps through the presets, and model families (claude, gpt,
gemini, deepseek, grok, kimi, glm, qwen, mistral, minimax) are recognised
by id for lists that have no provider, such as Copilot's. Claude Code's
aliases and own models carry the Claude mark, Codex's the OpenAI mark,
Copilot's and Cursor's auto their own. The two-letter monogram fallback
is gone: an option without a known vendor keeps an empty slot, and a
custom provider shows a plain outline glyph. A foreign ANTHROPIC_BASE_URL
now names the Claude Code group instead of being appended to the first
row's note.
Window header content moves 1.5pt down to sit level with the traffic
lights. Dev builds also serve the UI over plain HTTP when DIAL_DEV_UI is
set (make dev uses 127.0.0.1:3427) so it can be checked in a browser.
Wails v3 tray panel and window, TUI and CLI. Switches model, effort and
provider for Claude Code, Codex, Gemini CLI, OpenCode, Pi, Goose, Cursor,
Copilot CLI and Crush with format-preserving config edits. Providers page
and registry, stored keys, live vendor model lists, profiles.
Co-Authored-By: Claude <noreply@anthropic.com>