12 Commits
Author SHA1 Message Date
yetone 4225ba1efb site: a partner's /go link and the home page's sponsor card say they came from magpie, as ref=magpie (yetone)
goTarget adds ref=magpie to the partner's key page or website, keeping the
address's own query and fragment; an address that names its own ref (a
referral code) keeps it as it is. The home page's sponsor card links to
yylx.io/?ref=magpie.

Run: node --test site/*.test.mjs (27 pass), with new cases for the query,
the fragment and a partner's own ref.
2026-10-10 14:18:05 +08:00
yetone 34f2078db8 site: a partner's website and key links go through usemagpie.ai/go, which counts the click; fetches of the partner list are counted too
The app's own partner counts only come from users who leave the stats on,
so a sponsor's reach and clicks are now also counted where the server sees
them. /api/partners hands out each partner's website and keysUrl (and a
region's) as usemagpie.ai/go/<id>/site|keys[/<region>]; /go sends a
"magpie partner go" event to PostHog (partner, link, region, country) and
302s to partners.js's own URL. One in ten list fetches sends "magpie
partners fetch" with weight 10 while someone is listed. Events carry a
random id each, no install id or address. Ended entries leave the list but
/go still resolves them, since added providers keep the links. Privacy text
updated in README (en, zh), docs/reference.md and the site docs (en, zh, ja).

Run: node --test site/*.test.mjs (27 pass); a scratch copy with a fake
partner served rewritten links, 302'd /go to the real URLs and posted both
events; PostHog's /i/v0/e/ answered 200 to the payload shape.
2026-10-10 13:48:26 +08:00
yetone 10578ce683 providers: the add sheet lists Partners first, above Subscriptions, under a heading that says they sponsor magpie; who they are comes from usemagpie.ai/api/partners (site/partners.js), so a paid listing changes with a deploy, not a release (yetone)
The app fetches the list behind the call every 6 hours (10 minutes after a
failure), never waiting, and keeps it in the cache folder. Each entry is
checked: its own id, never a built-in preset's; a name and an endpoint;
https only; at most 12; no decision API, no-key or user endpoint. Its logo
goes through FetchIcon. A partner is a preset to the rest of magpie, and
one that has left still is to the providers added from it, unsponsored.
The window lists a partner in the languages it names only, with its tagline
in the page's language; `magpie presets` and the TUI list them first too.
MAGPIE_PARTNERS moves the list, off lists none.

Run: go test -tags nogui -race ./internal/provider ./internal/gui
./internal/tui . (ok); TestPartners* -race -count=3; node --test
internal/gui/tests/partners.test.cjs add-sheet gui-ja gui-de i18n-keys in
Chromium and WebKit (en/zh/ja/de, 900 and 440px); node --test
site/worker.test.mjs site/install.test.mjs; the built binary in a sandbox
HOME against a local feed: listed, an http entry left out, listed from the
cache with the site down, `provider add` from it.
2026-10-08 22:52:30 +08:00
yetone bd917e7a9a site: the docs pages take the reader's language and remember the one picked, as the home page does (owner: 为啥不能自动识别用户语言以及自动记住用户上次的选择?) — / already sent a browser preferring Chinese, Japanese or German to /zh/, /ja/ or /de/ and kept the language picked in the menu (the lang cookie), but /docs/start and the other English docs pages were English for everyone and ignored the cookie. Now an English docs page (/docs/<page>, with or without .html) sends a browser whose language the docs are written in (zh, ja) to /docs/<lang>/<page>, query kept, with Vary: Accept-Language, Cookie; lang=en (picking English in the menu) keeps it English, German has no docs and stays English, a page not written in that language yet stays, and /docs/zh/… and /docs/ja/… are never moved. The test fails on the old worker. Under wrangler dev: zh-CN and lang=zh go to /docs/zh/start, intent, plugins; ja to /docs/ja/…; en and zh-CN with lang=en stay; /docs/zh/start stays for everyone. node --test worker.test.mjs passes. 2026-10-05 03:33:03 +08:00
tobitege 21702d01ff Add German translation; harden paths against unset env vars (#770)
* 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.
2026-10-04 23:30:56 +08:00
yetone c96a4ba9ac site: /download links work again — since the release notes change (e5f8ff60) latest() returns { rel, degraded }, and /download still read .assets off that, so every download link (/download, /download/mac, /download/linux, /download/windows…) answered 500 (Cloudflare 1101) while /api/latest worked. /download now takes the release from got.rel. A test for /download/linux (302 to the asset) and an unknown platform (404) fails on the old code and passes; node --test site/*.test.mjs passes. 2026-10-03 17:20:58 +08:00
yetone 96e3ef6948 site: the update feed reads only what this worker kept in the edge cache — e5f8ff60 changed the shape of the cached newest release and release list but kept their names, so the entries the previous worker had put there were read as the new shape and /api/latest and /api/notes answered 500 until they ran out (up to 5 minutes after the deploy). The cache names now carry a version (__v2/), a hit is used only in the shape expected, and a cache that can't be read or written is a miss, not an error. A worker test seeds the old entries (and malformed new ones): it fails on e5f8ff60 and passes. node --test site/ passes. 2026-10-03 17:17:37 +08:00
yetone e5f8ff6026 site, gui: release notes survive GitHub limiting usemagpie.ai, and a failure to get them says so, apart from a release without any (#661) — /api/notes answered 503 {"error":"no releases"} with Cache-Control public, max-age=300 whenever GitHub's API limited the worker's shared IP, and /api/latest fell back to the release page's redirect with notes "" (kept 300 s too), so the update window had nothing to show. The worker now: never keeps an error (503 is no-store); takes the notes from GitHub's releases feed (releases.atom, not API-limited) when the API says no, turning its HTML back into the markdown the app draws and putting back the <!-- lang:zh --> marker GitHub strips (before the first Chinese heading after an English one); keeps the last whole answer for 30 days in the edge cache and serves it (merged with the feed's newer releases) when GitHub fails; keeps a degraded answer 60 s instead of 300; and sends a GITHUB_TOKEN secret to the API where one is set (none is yet). In the app, /api/whatsnew says error (and the release page's url) when the notes couldn't be had, and Settings' What's new opens a dialog saying "Couldn't load the release notes" with Try again and "Open the release page" (through the app), or "No release notes provided." with the release page and no retry for a version with none, instead of a passing toast; en/zh. Tests that fail on the old code and pass: site/worker.test.mjs (API limited: notes from the feed in either language, briefly kept; all of GitHub down: 503 no-store, then the last whole answer; the token sent), TestWhatsNewTellsFailureFromNone, and whats-new-failed.test.cjs (WebKit, en/zh: failure with retry and link, retry showing the notes, none without retry, no click moves the page). The feed parser was tried on the real releases.atom (v0.1.730–739: English and Chinese split as the API's bodies are). go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... under a temp HOME, node --test site/ and whats-new.test.cjs pass. 2026-10-03 17:12:48 +08:00
yetone 3c79152eb4 site: docs in Japanese (/docs/ja), and contents that work on a phone (owner: 文档也要支持日语,另外,文档要有友好的目录,在手机端也要有友好的目录) — /docs/ja/{start,intent,plugins,import} translate the English pages; every docs page gets the home page's language menu (English / 中文 / 日本語, the lang cookie) and a ja hreflang, /docs/ja opens the guide, and /ja/'s docs links go to the Japanese pages. docs.js (shared, replacing each page's inline scrollspy) builds the contents: every docs page, the current one's sections under it, the section being read marked with a pill (no left border); the list is sticky and scrolls on its own. Under 900px, where the contents were simply hidden (and under 600px the nav links too, so a phone had no way around the docs), a bar under the nav names the section being read and opens the contents as a menu that closes on a pick, a tap outside or Escape. The nav links give way to the contents under 1000px, long inline code wraps under 1100px, and .doc keeps .wrap's side padding (601-1168px ran the text to the screen's edge). node --test worker.test.mjs passes (/docs/ja redirect added); WebKit at 320-1280, light and dark, en/zh/ja: nothing scrolls sideways, the menu opens, jumps and closes, the bar follows the reading position. 2026-10-03 11:26:23 +08:00
yetone 4b5d6d725f site: the home page in Japanese at /ja/ too, picked from a language menu (the owner: 官网的日文也加上) — i18n.js gains a ja dictionary for every marked string, LANGS saying which languages have docs of their own (zh; /ja/ links the English docs); / sends a browser whose Accept-Language prefers Japanese to /ja/ as it does Chinese to /zh/, until a language is picked. The nav's 中文/EN link is now a globe button opening a menu of English, 中文 and 日本語, the current one checked (Escape or a click outside closes it), with hreflang ja alternates in the head; the page's scripts take T(en, zh, ja), so the routing demo, copy button, download label and theme titles speak Japanese on it, and Japanese text is set in Japanese faces (Hiragino Sans, Yu Gothic, Noto Sans CJK JP) rather than the Chinese ones. Also fixes the routing demo's stat labels in every language: since 8efd7d8 wrapped each in its own span, .rt-stats span matched the inner ones too and the labels were written into the wrong places ("errors your agent saw" twice); it now takes .rt-stats > span. Checked in WebKit: every demo string has three languages, each tab's labels are right in en/zh/ja, the menu marks and switches the language and sets the cookie, and /, /zh/ and /ja/ don't scroll sideways at 320, 390 or 768. worker.test.mjs covers the ja dictionary and the ja redirects. 2026-10-03 02:35:59 +08:00
yetone 8efd7d8027 site: the home page in Chinese at /zh/, and made for phones (the owner: magpie官网加上多语言支持,另外官网要对移动端友好) — index.html stays the one English source, each string marked data-i18n / data-i18n-attr; the worker serves /zh/ from it with i18n.js's dictionary put in by HTMLRewriter (<html lang>, docs links to /docs/zh/…, canonical and hreflang alternates), and / sends a browser whose Accept-Language prefers Chinese there until a language is picked with the nav's 中文/EN link (the lang cookie; Vary: Accept-Language, Cookie). The routing demo, copy button, download label and theme titles speak Chinese on it; the zh docs' home links go to /zh/. On phones the nav's links fold into a menu (≤860px; Escape, a click outside or on a link closes it, aria-expanded kept), Discord and GitHub move into it under 560px, the footer wraps, the routing tabs wrap, and nothing scrolls sideways at 320, 390, 768 or 1280 in either language (WebKit, light and dark). worker.test.mjs checks every marked key has its Chinese and the dictionary has none unused, and the / redirect by Accept-Language and cookie. 2026-10-03 01:09:21 +08:00
yetone aa42142ea5 update: What's new and the waiting update's notes follow the app's language (freecss on Discord: 更新后的公告的语言是不是应该设置里面的走) — the release notes were English alone. A release's notes are to carry a Simplified Chinese translation below a <!-- lang:zh --> marker (the magpie-releases workflow, changed separately); usemagpie.ai's /api/latest and /api/notes take ?lang=zh for the Chinese where a release has it, and give anything else, an app from before included, the English alone, everything above the marker. The page asks /api/whatsnew, /api/update and /api/update/check in its own language (?lang=); magpie asks the site in it, keeps What's new's notes per language, and asks again for the waiting update's notes when a page's language isn't the one they are held in (at most once a minute); a check by itself asks in the pages' last language, else the app's, as the tray menu has it. Notes that come back whole (a site not yet deployed) are cut in the app too; notes without the marker (every release so far) are English, in either language. The worker must be deployed (npx wrangler deploy in site/) before the workflow change ships, or apps would get both languages. TestInLang, TestFeedsAskInLang, TestAskedLang, TestWhatsNewInPageLang, TestUpdateNotesInPageLang, site/worker.test.mjs (node --test site/worker.test.mjs), whats-new.test.cjs's language check (Chromium and WebKit, en and zh); update-check, update-hide, update-auto, sync-update pass in WebKit. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeRunEndsWhenTheClientLeaves flaked once under the full run, passes alone; gateway untouched). 2026-10-02 11:31:11 +08:00