Files
magpie/internal/plugin
volcanic fc248e381f plugins: the folder a plugin lives in is picked, not typed, and its page reads that folder's README (#411)
Settings → Plugins adds a plugin by npm package name or by a path, and the
path had to be typed in full: the market's field was the only way in, with
nothing to pick the folder from. And a plugin added from a folder had no page
of its own — its README comes from plugin.Readme, which answers only for an
npm package name ("%q isn't an npm package name"), so opening one said "npm
didn't answer" and offered an npm link for a folder that npm has never heard
of.

The field now has the system's folder picker beside it, and a folder plugin's
page shows the README that folder carries.

- POST /api/plugins/choose asks Windows.ChooseFolder, which the app answers
  with the system's panel — the Library's "Choose a project" already used it,
  and a page served by another magpie forwards it to the magpie it is served
  by. pluginsState carries "picker" beside "bun", false for `magpie web`: a
  browser has no such panel (webHost.ChooseFolder refuses it), and the page is
  then left with the field to type into, as before.
- plugin.Readme takes a path: folderReadme reads README.md whatever its case,
  else a markdown or a text one named beside it, and says so when the folder
  has none rather than answering with npm's silence. Updated is that file's
  mtime, and the text is capped at 200 KB as npm's answer is.
- A folder plugin's page drops what only npm can say: no npm link, no version,
  downloads or license, and a failure names the folder rather than npm. Its
  row and its title name the folder it was added from (opencode-mine, not
  /Users/me/plugins/opencode-mine).

TestReadmeFromFolder (internal/plugin) fails without the change: with a
README.txt and an unsuffixed README beside it, the README.md is still the one
read; ReadMe.MD is too, and through file:// and a trailing .; a folder with
none is an error, as is one that isn't there.
TestPluginsPickerWhereItCan (internal/gui) fails without the change: the
picker is offered for the app, not for `magpie web` nor for no host at all.
plugin-local.test.cjs (new; Chromium and WebKit, en and zh) fails without the
change: the picker is beside the field and titled as such, a click asks for it
and what it answers is what the field holds and takes the focus, what the
field holds is what Install posts, the Installed row names the folder rather
than its path, its page renders that folder's README with no npm link, and a
market that offers no picker draws no button (english and Chinese).

Not only the faked page: a sandbox `magpie web` in a HOME of its own, with a
plugin added by path, read that folder's README.md — its mtime as Updated —
and listed it by the folder's name.

go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows
go build, go test -tags nogui ./internal/gui/ pass; plugin-market,
plugin-signin and plugin-local pass in Chromium and WebKit, en and zh.
TestPluginProxy (internal/plugin) and the hermes tests (internal/agent,
internal/profile) fail on main on this machine too — its own proxy and a
hermes install it finds — and are unrelated.

Not done: the panel itself hasn't been clicked in the app. It is the system's
own, and the call under it is the one the Library's folder chooser already
uses, but the screenshots here are the page with a faked backend underneath
(the app's own picker is offered, `magpie web` has none), plus the real
`magpie web` for the README.
2026-10-01 18:46:56 +08:00
..
plugin: OpenCode's provider plugins sign in to subscriptions in magpie — an npm package OpenCode users install for a plan (its auth hook: loader, methods, oauth authorize/callback, api keys, chat.headers) runs in a Bun host magpie downloads the first time a plugin needs it, speaking JSON-RPC over stdio; its sign-ins are kept in plugin-auth.json (0600). A provider a plugin signs in to is a provider like the others: its models the plugin lists, each spoken to on the API its AI SDK package speaks (anthropic → Messages, openai/azure → Responses, google → Gemini, Copilot's GPT-5+ → Responses, else chat completions), its requests carried by the plugin's fetch to the base URL its loader gives; an id magpie already has is <id>-plugin. CLI: magpie plugin [list|add|rm|update|on|off|login|logout]. App: Settings → Plugins adds, updates, switches off and removes them and says why one didn't load; Add provider → From plugins signs in with the plugin's methods, its questions (picks and checked text), the browser and a pasted code or a key, followed as the built-in sign-ins are; removing the account signs out. The built-in subscriptions stay. TestFakePlugin, TestPluginProvider (chat, Anthropic and Gemini models through the gateway) and TestPluginSignIn (a wrong code fails, the right one signs in, removing signs out, a key) run a fake plugin on Bun; plugin-signin.test.cjs (Chromium, WebKit, en/zh) covers the add sheet and Settings. signin-callback, add-sheet, zed-sub, account-proxy, own-signin-remove, routing-signin, click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked, known flakes under load, pass alone). Not yet tried with a real plugin and account.
2026-09-30 16:44:32 +08:00
providers, sign-in, tray, plugins: four of StringKe's reports on Discord — (1) Removing a provider left its editor open and listed, and a second Remove said there was no such provider: the removal had been made, but moving the agents off its models failed on one agent whose config couldn't be written, and Reseat returned that as the change's error; now such an agent is a move with its error ("still on X, not moved: …", a warning in the panel, a line in the CLI and TUI), the others are moved, and the panel, told a delete failed, re-reads the list and closes the editor when the provider is gone. (2) The sign-in waiting sheet shows the sign-in link itself, selected whole on a click, with a copy button, for a browser other than the one that opens. (3) A subscription with several accounts offers "Account in use" among the menu bar's usages, following the account the gateway goes to first. (4) The Antigravity plugin's sign-in never started: magpie called every plugin's authorize with an inputs object, the way OpenCode's CLI does, and opencode-antigravity-auth then asked its Project ID on the terminal — magpie's stdin — until the minute ran out; inputs now go only to a method with prompts, as OpenCode's TUI passes them (checked against the real package under Bun in a sandbox: the Google sign-in URL comes back at once). TestReseatUnmovable, TestTrayInUse, TestInUseOf, TestAuthorizeNoPrompts, provider-remove.test.cjs, signin-link.test.cjs and tray-inuse.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit, tray-usages, plugin-signin, signin-callback, factory-sub pass.
2026-09-30 20:45:23 +08:00
accounts: on Linux an account put first is read first at once, and on Windows a file being replaced isn't read as none (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样, run on Windows and Linux) — the provider tests run on the Windows test box and in WSL found two bugs of magpie's own files. Linux stamps a file's time from a clock a tick (a few milliseconds) coarse, and filememo took same time and size for the same file: two accounts swapped (Devin, Grok, WorkBuddy, ZCode, Claude…) read back in the old order, so magpie kept using the account the user had just moved down, and a moved-back plugin's accounts read as before. A file written within the last two seconds is now also compared by its bytes. On Windows a file being renamed over refuses to open for a moment ("Access is denied"), and one held open refuses to be renamed over: plugin-auth.json, plugins.json and the plugin providers read as nothing (a usage fetch right after a move showed no cards), logins.json failed to save, and host.js, reading plugin-auth.json as {}, would have written the other accounts away on the next sign-in. New internal/steady waits such a read or rename out (half a second at most, Windows only); filememo, edit.WriteAtomic, writePrivate, Kiro's writes and the plugin store go through it, host.js retries its read and rename, and the plugin store writes its files by a rename. Tests: USERPROFILE beside HOME in the test homes, the fake devin (a shell script) skipped on Windows, plugin-auth.json's mode bits unchecked there. TestReadSameSizeSameTick fails on Linux and TestRenameOverAFileHeldOpen/TestReadAFileHeldOpen on Windows without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; on Linux (WSL) the provider and plugin packages pass whole (13 failed before), on Windows the plugin package and the move/plugin/Grok/Devin/usage tests pass 10 times over (TestMoveToPlugin failed one run in a few before); 15 built-in tests that assume Unix (mode bits, Keychain, shell-script CLIs) still fail there, as before.
2026-10-01 13:27:12 +08:00
accounts: on Linux an account put first is read first at once, and on Windows a file being replaced isn't read as none (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样, run on Windows and Linux) — the provider tests run on the Windows test box and in WSL found two bugs of magpie's own files. Linux stamps a file's time from a clock a tick (a few milliseconds) coarse, and filememo took same time and size for the same file: two accounts swapped (Devin, Grok, WorkBuddy, ZCode, Claude…) read back in the old order, so magpie kept using the account the user had just moved down, and a moved-back plugin's accounts read as before. A file written within the last two seconds is now also compared by its bytes. On Windows a file being renamed over refuses to open for a moment ("Access is denied"), and one held open refuses to be renamed over: plugin-auth.json, plugins.json and the plugin providers read as nothing (a usage fetch right after a move showed no cards), logins.json failed to save, and host.js, reading plugin-auth.json as {}, would have written the other accounts away on the next sign-in. New internal/steady waits such a read or rename out (half a second at most, Windows only); filememo, edit.WriteAtomic, writePrivate, Kiro's writes and the plugin store go through it, host.js retries its read and rename, and the plugin store writes its files by a rename. Tests: USERPROFILE beside HOME in the test homes, the fake devin (a shell script) skipped on Windows, plugin-auth.json's mode bits unchecked there. TestReadSameSizeSameTick fails on Linux and TestRenameOverAFileHeldOpen/TestReadAFileHeldOpen on Windows without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; on Linux (WSL) the provider and plugin packages pass whole (13 failed before), on Windows the plugin package and the move/plugin/Grok/Devin/usage tests pass 10 times over (TestMoveToPlugin failed one run in a few before); 15 built-in tests that assume Unix (mode bits, Keychain, shell-script CLIs) still fail there, as before.
2026-10-01 13:27:12 +08:00