Files
magpie/internal/library
yetone 92469ee9f9 library, gui: a check for updates lists the skills a repository added beside the library's skills from it, to Add or Ignore (Raven on Discord)
Raven: "check for updates" only updated skills already in the library; when
dontbesilent2025/dbskill or mattpocock/skills adds a skill, magpie never said.
CheckSkills now asks GitHub's git trees API once per repo@ref (ETag-cached, a
304 is free) and only when a skill folder under the installed skills' common
folder (newBase) is neither in the library nor already offered does findNew
fetch the tarball for names and descriptions. library.json's seenSkills keeps,
per repo@ref, the paths the install picker showed and the ones set aside, so
an offered skill is offered once. The Skills page shows them under "Also in
their repositories" with Add (to the agents that have the repo's other skills)
and Ignore, plus Add all / Ignore all (/api/library/skills/{add-new,ignore-new}).

Verified: TestCheckSkillsFindsNewOnes (fake repo: nothing offered after
install, no tarball on a second check; two added skills offered, a nested one
and a .wip one not; Add installs with the repo's agents; Ignore keeps it out
on the next check via 304; remove clears) fails without the change (no
NewSkills / findNew) and passes -race -count=10; TestNewBase. A real run in a
sandbox HOME against GitHub: tdd from mattpocock/skills/skills/engineering
offered its 19 siblings; dbs from dbskill offered nothing (the picker showed
all), 33 with seenSkills wiped, and Add installed dbs-action. GUI:
skill-new-in-repo.test.cjs (Chromium+WebKit, 860/440px, en+zh) fails on
main's library.js and passes; gui-ja, gui-de, click-scroll, library-items,
every-skill, pick-skills, import/remove-all-skills, skill-groups,
skill-repo-groups, github-token, library-all pass. -race on library, gui,
tui; gofmt, vet, darwin/linux/windows builds.
2026-10-07 00:26:54 +08:00
..
test: every package's tests pass on Windows, and none writes outside its sandbox (the user: 把 Windows 上那几个失败的测试修掉) — on a Windows box the suite failed in most packages, and the root package hung for 25 minutes. Tests set HOME, which os.UserHomeDir ignores on Windows (it reads USERPROFILE), so they read and wrote the runner's own home; each now sets USERPROFILE beside HOME, and Devin's, Goose's, Crush's and Claude Desktop's tests set APPDATA/LOCALAPPDATA, where those keep their files there. said() read the command's output only once it returned, and a pipe holds a few KB on Windows, so magpie model --help blocked forever; it reads while the command writes. Unix permission bits (the gateway key store's 0600 too), exec bits and symlinks Windows lacks are checked only off Windows; TOML strings hold Windows paths as literal strings, JSON as marshalled ones; a URL's path is compared as one. The provider and usage packages run in a home of their own (TestMain, as the gateway's already does), since a CLI's identity kept by a refresh still going when its test ended landed in the runner's ~/.config/magpie — and on a Mac in the user's own. Two product fixes came out of it: imagemcp took a file:///C:/… root or image for the path /C:/…, which doesn't exist on Windows, and now makes a Windows path of it (a host for a UNC share); and session discovery could keep a new Cowork session hidden on Linux, where a folder's mtime is a kernel tick and its size doesn't change, so an entry added within the tick of the last listing left both as they were — a listing made within 2s of its folder's last change is read again, as git does for racy files. TestFilePath and TestRacyDirectoryListedAgain fail without the change. On Windows 11 every package passes (32 of 32) and a run of each package in a fresh home leaves nothing in home, APPDATA or LOCALAPPDATA; on Linux (WSL) and macOS go test -tags nogui ./... passes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass.
2026-10-01 23:15:07 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
2026-10-01 23:25:05 +08:00
2026-10-01 23:25:05 +08:00
library: checking skills for updates asks GitHub with a GitHub token, set in Settings → Network and sharing or taken from GITHUB_TOKEN / GH_TOKEN, and a check GitHub limited says until when and that a token raises it (Player on Discord: 资源库检查技能更新时提示 github is limiting request,希望能设置 GitHub token) — the check asks GitHub's API for the last commit to each skill's folder, which GitHub answers 60 times an hour for an address without a token, so a library of a few repositories, checked twice, or a network shared with others, used it up, and the check said only "GitHub is limiting requests from here until 15:04", with nothing to do about it. Now the API's requests carry Authorization: Bearer <token> (5,000 an hour) when there is one: the GitHub token set in Settings → Network and sharing (a new GitHub row: typed masked, saved on its own with POST /api/settings/github-token, then shown as ghp_…0123 with Remove; one with a space is refused in the row, what was typed kept), else GITHUB_TOKEN, else GH_TOKEN, the row saying when one of those is used. gh auth token is not run: it would borrow another app's credential without asking. The token goes to GitHub's API's host alone (withGitHubToken checks the request's host against githubAPI's), never to codeload, raw.githubusercontent or anything else, and Go's client drops it on a redirect to another host; rtk's latest-release lookup on api.github.com carries it too. It is kept in settings.json as the LAN key is: the page is told only the masked one and where it is from (githubTokenMask, githubTokenFrom), a save of the Settings page keeps it, a backup or sync without keys leaves it out, and restoring one without keys keeps this machine's. Each folder's answer is kept with its ETag and asked for again with If-None-Match, so an unchanged one is a 304, which GitHub doesn't count against the limit. A rate limit (403 with the limit spent, a secondary limit's Retry-After, or 429) now says it plainly: without a token, "GitHub's rate limit for requests without a token (60 an hour) is used up until 15:04. Add a GitHub token in Settings → Network and sharing to raise it to 5,000 an hour"; with one, that the token's limit is used up until then; and the check carries limited {until, token} for the page, which says the same in its own words, in English and Chinese, in the status and on each Not checked tag. A token GitHub refuses (401) says which: the one in Settings, or GITHUB_TOKEN / GH_TOKEN. The library's test sandbox clears GITHUB_TOKEN and GH_TOKEN, so no developer's token reaches a fake GitHub. TestGitHubTokenSent (no token: none sent; GH_TOKEN; GITHUB_TOKEN before it; Settings before both; codeload never gets it), TestGitHubTokenOnlyToTheAPI (api.github.com only, not codeload, raw, a look-alike host or the gateway; dropped on a redirect), TestGitHubRateLimitSaid (until when and the token advice without one, not with one, the token never in the error, a secondary limit, a refused token from Settings and from GH_TOKEN), TestGitHubConditionalRequests (If-None-Match, and the 304 answered as the 200 was), TestGitHubTokenBackup (left out without keys, fails without the change; kept on a keyless restore), TestGitHubTokenSetting (set, masked, kept by a Settings save, refused with a space, removed with the environment's then said) and github-token.test.cjs (WebKit, en and zh: the row, a refusal keeping what was typed, saved masked with the token nowhere on the page, removed with GH_TOKEN said, nothing scrolls; a limited check's status and tag say until when, and the token advice only without one) are new; TestCheckSkills, TestSettingsSaveKeepsWhatItDoesNotSend (now with the token) pass. GitHub was never called: httptest stands in for it. settings-sections, search-api, click-scroll, library-toggle-status, library-all, otel, lan-docker, every-skill pass in WebKit. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed on its TempDir cleanup, the known flake).
2026-10-02 21:16:50 +08:00
library: checking skills for updates asks GitHub with a GitHub token, set in Settings → Network and sharing or taken from GITHUB_TOKEN / GH_TOKEN, and a check GitHub limited says until when and that a token raises it (Player on Discord: 资源库检查技能更新时提示 github is limiting request,希望能设置 GitHub token) — the check asks GitHub's API for the last commit to each skill's folder, which GitHub answers 60 times an hour for an address without a token, so a library of a few repositories, checked twice, or a network shared with others, used it up, and the check said only "GitHub is limiting requests from here until 15:04", with nothing to do about it. Now the API's requests carry Authorization: Bearer <token> (5,000 an hour) when there is one: the GitHub token set in Settings → Network and sharing (a new GitHub row: typed masked, saved on its own with POST /api/settings/github-token, then shown as ghp_…0123 with Remove; one with a space is refused in the row, what was typed kept), else GITHUB_TOKEN, else GH_TOKEN, the row saying when one of those is used. gh auth token is not run: it would borrow another app's credential without asking. The token goes to GitHub's API's host alone (withGitHubToken checks the request's host against githubAPI's), never to codeload, raw.githubusercontent or anything else, and Go's client drops it on a redirect to another host; rtk's latest-release lookup on api.github.com carries it too. It is kept in settings.json as the LAN key is: the page is told only the masked one and where it is from (githubTokenMask, githubTokenFrom), a save of the Settings page keeps it, a backup or sync without keys leaves it out, and restoring one without keys keeps this machine's. Each folder's answer is kept with its ETag and asked for again with If-None-Match, so an unchanged one is a 304, which GitHub doesn't count against the limit. A rate limit (403 with the limit spent, a secondary limit's Retry-After, or 429) now says it plainly: without a token, "GitHub's rate limit for requests without a token (60 an hour) is used up until 15:04. Add a GitHub token in Settings → Network and sharing to raise it to 5,000 an hour"; with one, that the token's limit is used up until then; and the check carries limited {until, token} for the page, which says the same in its own words, in English and Chinese, in the status and on each Not checked tag. A token GitHub refuses (401) says which: the one in Settings, or GITHUB_TOKEN / GH_TOKEN. The library's test sandbox clears GITHUB_TOKEN and GH_TOKEN, so no developer's token reaches a fake GitHub. TestGitHubTokenSent (no token: none sent; GH_TOKEN; GITHUB_TOKEN before it; Settings before both; codeload never gets it), TestGitHubTokenOnlyToTheAPI (api.github.com only, not codeload, raw, a look-alike host or the gateway; dropped on a redirect), TestGitHubRateLimitSaid (until when and the token advice without one, not with one, the token never in the error, a secondary limit, a refused token from Settings and from GH_TOKEN), TestGitHubConditionalRequests (If-None-Match, and the 304 answered as the 200 was), TestGitHubTokenBackup (left out without keys, fails without the change; kept on a keyless restore), TestGitHubTokenSetting (set, masked, kept by a Settings save, refused with a space, removed with the environment's then said) and github-token.test.cjs (WebKit, en and zh: the row, a refusal keeping what was typed, saved masked with the token nowhere on the page, removed with GH_TOKEN said, nothing scrolls; a limited check's status and tag say until when, and the token advice only without one) are new; TestCheckSkills, TestSettingsSaveKeepsWhatItDoesNotSend (now with the token) pass. GitHub was never called: httptest stands in for it. settings-sections, search-api, click-scroll, library-toggle-status, library-all, otel, lan-docker, every-skill pass in WebKit. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed on its TempDir cleanup, the known flake).
2026-10-02 21:16:50 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library: a set of instructions added, or given text, while the one in use is empty is the one the agents read, and the page says which set they read (Fate on Discord: a shared set was made and Claude Code ticked, and ~/.claude/CLAUDE.md didn't change) — the agents switched on read only the set in use (the round button), and Fate's was the Default, which was empty, so nothing was written; ticking Claude Code said "it gets the shared instructions once there are some" while a set with text sat beside it. Now a set created, or a set given text, while the one in use is empty becomes the one in use (a set in use with text is left as it is). The Agents head says "They read {set}" (· Empty when it is), an open set not in use has a Switch the agents to this set button (it sends the text typed with it), an agent switched on while the set in use is empty and another has text says it reads the empty one and to switch, and a set added while the one in use is empty says the agents read it. zh/ja/de strings added. magpie still writes its own marked block into CLAUDE.md, keeping the user's text, and takes it out when the agent is switched off. TestInstructionSetTakesAnEmptyOnesPlace (sandbox HOME: CLAUDE.md gets the block after "# Mine", is "# Mine\n" again when off; a set in use with text is kept; emptied, the next set with text takes over) and library-instr-sets.test.cjs (WebKit, en and zh) fail on the old code; the library-*.test.cjs and gui-de pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 01:48:57 +08:00
library: a float in a Codex server's entry is written back as a float, and inf and dates as TOML has them (Lullaby on Discord: an internal MCP server that worked configured in Codex looped with its tool arguments lost after it was taken into the Library; they suspected tool_args / strict) — every key Codex reads for a server (codex-rs/config/src/mcp_types.rs RawMcpServerConfig: command, args, env, env_vars with { name, source } tables, cwd, url, bearer_token(_env_var), http_headers, env_http_headers, http_headers_helper, environment_id, auth, oauth.*, scopes, oauth_resource, startup_timeout_sec/_ms, tool_timeout_sec, enabled, required, startup_readiness, supports_parallel_tool_calls, tool_input_schema_max_bytes, omit_tools_from, default_tools_approval_mode, enabled/disabled_tools, tools.<tool>.approval_mode/output_token_limit, name) was checked through ImportServer and a SaveServer that adds an argument and gives the server to Claude Code too: all of them come back with their values, args with JSON and spaces in them verbatim, tables written inline. The one change was tomlValue writing a float with fmt.Sprint, so tool_timeout_sec = 120.0 came back the integer 120 (Codex reads either, as serde's f64 takes an integer, so this is not the reporter's loop), and +Inf, which isn't TOML, for inf; a date or time the user put in an entry went back as a string. tomlFloat now keeps a float one (".0" where it has no point or exponent; inf, -inf, nan), and go-toml's LocalDate/LocalTime/LocalDateTime and time.Time are written as TOML's own. Grok Build's entries go through the same writer. TestCodexServerKeysSurviveTheLibrary (the fixture above, compared value and type for every key) fails without the change (tool_timeout_sec float64 → int64) and passes; TestTOMLValueKeepsTypes (floats, an exponent, inf, a local date, time and date-time, an offset date-time) fails without it (+Inf doesn't parse) and passes. -race -count=10 on them with TestServerEveryFormat, TestServerKeepsUsersKeys and TestPutCodex*; go test -tags nogui ./internal/library ./internal/edit, go vet, gofmt -l, linux and windows (nogui) builds pass. The Library's MCP sign-in proxy (gateway /mcp/{name}) relays the body untouched. Not tried with the reporter's server; what broke their tool calls is not found here.
2026-10-06 18:22:41 +08:00
library: Discover finds an MCP server by its address — a repository, page, endpoint or package pasted in (lc on Discord: https://github.com/crystaldba/postgres-mcp in Library › MCP › 发现 said 没有与 … 匹配的结果) — MarketServers matched featured servers on their words and asked the MCP Registry with the search as typed, and the registry's search is a part of a server's name (checked: "postgres mcp" lists 0, "crystaldba" 0), so a URL found nothing anywhere. A search that reads as an address (address: a scheme, git@, www., host/path, owner/repo or @scope/pkg; words with a space, or a bare word, are searched as before) is normalised (normAddr: lower case, no scheme, user, www., query, fragment, trailing slash or .git; npmjs.com/package/x, pypi.org/project/x, hub.docker.com/r/x and docker.io/x are the package, an image's tag and a package's @version dropped) and matched (addrScore: equal, under it, or a file under one's repository; owner/repo on any host; never a bare host or an owner's page standing for every repository) against each server's addresses: a featured one's page, endpoint and package; a registry one's name, websiteUrl, repository.url, remotes' URLs and packages' identifiers. The registry is asked for the names the address suggests (addrTerms: the repository's or package's name, then its owner's; a site's name for an endpoint, context7 for mcp.context7.com/mcp), and what it lists at the address comes first and alone, marked match; when nothing is, what it lists for the first name is shown, as that search would. Searched against the live registry: github.com/Eszetael/postgres-mcp-hardened.git, ghcr.io/eszetael/postgres-mcp-hardened:0.1.10, github.com/upstash/context7, https://mcp.context7.com/mcp (featured Context7), npmjs.com/package/@playwright/mcp (featured Playwright), github.com/modelcontextprotocol/servers (the reference servers) and an endpoint of ai.getvda's each find theirs. crystaldba/postgres-mcp itself isn't in the registry (no name, repository or package of it in the 124 servers a "postgres" search pages through), so it shows the 12 others called postgres-mcp, and above them "None of these is at {q} — they're found by its name." (以下都不在 {q},是按它的名字找到的。; "Nothing listed is at {q}." 没有列出位于 {q} 的服务器。 when none) with "+ Add it yourself…" (+ 自己添加…): the market's answer carries custom (CustomAt) when the search is an address nothing found is at, and the button opens the existing new-server form filled in — named for the repository (postgres), an endpoint's URL as HTTP, npx -y / uvx for an npm or PyPI page. TestAddress, TestAddrScore and TestMarketFindsByAddress (a fake registry that searches names: by repository found through its owner's name, featured by endpoint, repository, package and a repository folder, registry by endpoint, by name with one to add by hand, words as before) and market-address.test.cjs (webkit, en and zh: the note over the name matches, the form named postgres with no command, an endpoint's URL in it, words that match nothing as before; fails on the old library.js) pass, as does market-have. go vet ./internal/..., builds (-tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./internal/library/... ./internal/gui/... under a temp HOME pass. Not tried in the app.
2026-10-04 07:54:08 +08:00
library: Discover finds an MCP server by its address — a repository, page, endpoint or package pasted in (lc on Discord: https://github.com/crystaldba/postgres-mcp in Library › MCP › 发现 said 没有与 … 匹配的结果) — MarketServers matched featured servers on their words and asked the MCP Registry with the search as typed, and the registry's search is a part of a server's name (checked: "postgres mcp" lists 0, "crystaldba" 0), so a URL found nothing anywhere. A search that reads as an address (address: a scheme, git@, www., host/path, owner/repo or @scope/pkg; words with a space, or a bare word, are searched as before) is normalised (normAddr: lower case, no scheme, user, www., query, fragment, trailing slash or .git; npmjs.com/package/x, pypi.org/project/x, hub.docker.com/r/x and docker.io/x are the package, an image's tag and a package's @version dropped) and matched (addrScore: equal, under it, or a file under one's repository; owner/repo on any host; never a bare host or an owner's page standing for every repository) against each server's addresses: a featured one's page, endpoint and package; a registry one's name, websiteUrl, repository.url, remotes' URLs and packages' identifiers. The registry is asked for the names the address suggests (addrTerms: the repository's or package's name, then its owner's; a site's name for an endpoint, context7 for mcp.context7.com/mcp), and what it lists at the address comes first and alone, marked match; when nothing is, what it lists for the first name is shown, as that search would. Searched against the live registry: github.com/Eszetael/postgres-mcp-hardened.git, ghcr.io/eszetael/postgres-mcp-hardened:0.1.10, github.com/upstash/context7, https://mcp.context7.com/mcp (featured Context7), npmjs.com/package/@playwright/mcp (featured Playwright), github.com/modelcontextprotocol/servers (the reference servers) and an endpoint of ai.getvda's each find theirs. crystaldba/postgres-mcp itself isn't in the registry (no name, repository or package of it in the 124 servers a "postgres" search pages through), so it shows the 12 others called postgres-mcp, and above them "None of these is at {q} — they're found by its name." (以下都不在 {q},是按它的名字找到的。; "Nothing listed is at {q}." 没有列出位于 {q} 的服务器。 when none) with "+ Add it yourself…" (+ 自己添加…): the market's answer carries custom (CustomAt) when the search is an address nothing found is at, and the button opens the existing new-server form filled in — named for the repository (postgres), an endpoint's URL as HTTP, npx -y / uvx for an npm or PyPI page. TestAddress, TestAddrScore and TestMarketFindsByAddress (a fake registry that searches names: by repository found through its owner's name, featured by endpoint, repository, package and a repository folder, registry by endpoint, by name with one to add by hand, words as before) and market-address.test.cjs (webkit, en and zh: the note over the name matches, the form named postgres with no command, an endpoint's URL in it, words that match nothing as before; fails on the old library.js) pass, as does market-have. go vet ./internal/..., builds (-tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./internal/library/... ./internal/gui/... under a temp HOME pass. Not tried in the app.
2026-10-04 07:54:08 +08:00
library: MCP servers, skills and instructions are kept once in magpie and given to any agent from one place, with a market to find and add them (#29). Each agent reads MCP servers and skills from files of its own and in a format of its own, so one server was set up again in Claude Code, Codex, Gemini CLI, OpenCode, Pi, Goose, Cursor, Copilot CLI and Crush. The Library view keeps each once: a server is added (stdio, http or sse, its env and headers) and switched on per agent with its icon, and magpie writes it into each agent's config in that agent's format, leaving what the agent has of its own as it is; a server or skill an agent already has is shown, to bring into the library. A skill is a folder with a SKILL.md, kept in the library from GitHub (owner/repo, or a link to a folder in one; updated from it later) or linked from a folder on this machine, and linked into each agent's skills folder. The instructions every agent reads (CLAUDE.md, AGENTS.md, GEMINI.md…) can be kept as one too. The market offers checked MCP servers first, then the official MCP Registry's, filled in from what the registry says each needs (keys asked for in a form, never guessed), and skills.sh's most installed skills with what each says it is for; each shows its real icon (the vendor's own, else its GitHub owner's), fetched through magpie only for the icons the market or the library gave out, and an added server or skill keeps its icon in the library. What magpie changes in an agent's files is backed up first
2026-09-25 14:11:17 +08:00
library: each MCP server in the Library shows whether it works — a dot and a few words beside its name (12 tools, needs sign-in, can't start: uvx not found, exited (1), no answer, connection refused, the server's HTTP status), the whole reason (the server's last stderr line, the status line) in the tooltip; checked once a session when the MCP list opens, and again by clicking it (Discord, lc: a status display for MCP servers in the Library page) — POST /api/library/mcp/check {names, fresh} runs library.CheckServers: up to four at once, 15 s each, by really connecting. A stdio server is started with its command, args and env (proc.Command + StartTree, so the whole tree is ended after, Windows too), sent initialize (2025-06-18, clientInfo magpie), notifications/initialized and tools/list over newline JSON; a missing command is "not found", an exit before the reply gives its code and last stderr line. A Streamable HTTP server is POSTed the same with Accept: application/json, text/event-stream, a JSON or SSE reply read, Mcp-Session-Id and the negotiated MCP-Protocol-Version carried, magpie's own sign-in token sent when it has one and the session DELETEd after; 401/403 is "needs sign-in" (OAuth when WWW-Authenticate says so). A legacy SSE server is read through its endpoint event. A server without tools/list (-32601) is fine with 0 tools. Results are kept in memory for the session, keyed on the server's definition and sign-in state, so an edited server is checked again; nothing is written to any agent's config. The GUI keeps a status per server name, paints it on the row (a grid in .who so .who > .name stays as other tests read it), a checking state pulses, the click stops propagation so it neither opens the server nor scrolls, and the strings are in i18n.js (zh). TestCheckStdio (ok with 3 tools after a junk stdout line and a notification, exit 3 with its stderr, not found, timeout ending the server and its child), TestCheckHTTP (JSON and SSE replies with the session and token carried, OAuth 401, key 403, 404, broken JSON, timeout, refused), TestCheckSSE and TestCheckServersOnce (cached, fresh, re-run on a changed env, no file under HOME changed) use a fake server re-executed from the test binary and httptest, -race; library-mcp-health.test.cjs (en and zh: one check for all servers on opening, texts, tooltips, dot not border, row height, no re-check on returning, a click re-checks only that server without opening it or moving the page, no <select>) fails on the old library.js and passes in webkit with the other Library tests. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against real MCP servers from a user's config.
2026-10-04 06:44:51 +08:00
library: a remote MCP server signed in once in magpie is used by every agent without signing in again (#615) — a streamable-HTTP server behind OAuth (Neon, Notion, Supabase) had each agent run its own sign-in. The server's editor now has Sign in: magpie finds the authorization server from the server's 401 (RFC 9728 protected-resource metadata, RFC 8414/OIDC metadata, else the 2025-03 /authorize, /token, /register at its origin), registers itself (RFC 7591, a 127.0.0.1 redirect), opens the browser with PKCE S256 and the resource, and keeps the tokens in mcp-signins.json beside its other account secrets (0600, never the keychain). While signed in, the agents are given the gateway's /mcp/<name> for that server, without the Authorization the library has for it; the gateway passes POST (JSON or event stream), the GET stream and DELETE on with the Mcp-Session-Id and magpie's token, renews it 2 minutes before it runs out and once on a 401, one renewal per server at a time, and a sign-in the server won't renew is answered 502 "sign in to it again" (never a 401 the agent would act on). Signing out, a new URL or an SSE server gives the agents the server's own address as before; a rename keeps the sign-in and removing the server drops it. /mcp/ is local-only or LAN-shared with the key, and refuses a request with an Origin or a Host not naming this computer, so a web page can't borrow the sign-in through the browser (form posts, DNS rebinding), and isn't counted as busy or key-limited. WSL agents are given their distro's gateway address. The row says Signed in or Sign-in ran out; zh strings added. Tests: TestSignIn, TestSignInWithoutMetadata, TestSignInPage, TestNothingToSignIn, TestTokenRenewsBeforeItRunsOut (8 calls, 1 renewal), TestRenewRefused, TestRenewRefusedForGood, TestMCPProxy, TestMCPProxyStaysHere, TestMCPProxyNotForWebPages (fails without the Origin/Host check), TestSignedInServerGoesThroughMagpie against a local fake OAuth+MCP server; mcp-signin.test.cjs (webkit, en and zh). Not tried against a real Neon/Notion/Supabase sign-in. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-03 09:37:39 +08:00
library: a remote MCP server signed in once in magpie is used by every agent without signing in again (#615) — a streamable-HTTP server behind OAuth (Neon, Notion, Supabase) had each agent run its own sign-in. The server's editor now has Sign in: magpie finds the authorization server from the server's 401 (RFC 9728 protected-resource metadata, RFC 8414/OIDC metadata, else the 2025-03 /authorize, /token, /register at its origin), registers itself (RFC 7591, a 127.0.0.1 redirect), opens the browser with PKCE S256 and the resource, and keeps the tokens in mcp-signins.json beside its other account secrets (0600, never the keychain). While signed in, the agents are given the gateway's /mcp/<name> for that server, without the Authorization the library has for it; the gateway passes POST (JSON or event stream), the GET stream and DELETE on with the Mcp-Session-Id and magpie's token, renews it 2 minutes before it runs out and once on a 401, one renewal per server at a time, and a sign-in the server won't renew is answered 502 "sign in to it again" (never a 401 the agent would act on). Signing out, a new URL or an SSE server gives the agents the server's own address as before; a rename keeps the sign-in and removing the server drops it. /mcp/ is local-only or LAN-shared with the key, and refuses a request with an Origin or a Host not naming this computer, so a web page can't borrow the sign-in through the browser (form posts, DNS rebinding), and isn't counted as busy or key-limited. WSL agents are given their distro's gateway address. The row says Signed in or Sign-in ran out; zh strings added. Tests: TestSignIn, TestSignInWithoutMetadata, TestSignInPage, TestNothingToSignIn, TestTokenRenewsBeforeItRunsOut (8 calls, 1 renewal), TestRenewRefused, TestRenewRefusedForGood, TestMCPProxy, TestMCPProxyStaysHere, TestMCPProxyNotForWebPages (fails without the Origin/Host check), TestSignedInServerGoesThroughMagpie against a local fake OAuth+MCP server; mcp-signin.test.cjs (webkit, en and zh). Not tried against a real Neon/Notion/Supabase sign-in. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-03 09:37:39 +08:00
wsl: background work never touches a WSL distro that has stopped since it was last seen running — it booted it again (TJHHHH on Discord: while they were repairing WSL, magpie kept starting it). Detection already listed only running distros, but the running set was kept for a minute (wslListAge), agents built while a distro ran kept its home and read its files from their Get/Sync/Check/Reached/Routed closures later, the sessions listing kept a distro's Running for 30s+ and read its \\wsl.localhost files, and wslrun.Find reused a tool it had found in a distro for a minute and ran wsl.exe -d on it. Any wsl.exe -d or \\wsl.localhost read boots a stopped distro, so a wsl --shutdown was undone within a minute, and the distro, running again, kept it going. Now wsl.exe -l --running -q (it starts nothing; UTF-16 with or without BOM, or UTF-8 under WSL_UTF8) is asked again after 3s, and a distro not in it is not touched: a WSL agent's Get gives the last values seen, Sync/Check/Reached do nothing and Routed/Joined/Beside/Added say no; sessions aren't listed or read from it (wslCold says cold); wslrun.Find (Claude subscription sign-in and usage) doesn't reuse or run a tool there; the Library gives skills to no target in it and its MCP probe says it isn't running. An explicit action still starts it: setting a WSL agent's field, a Set on a running agent, resuming a WSL session. TestWSLStoppedSinceNotOpened (agent, sessions), TestWSLStoppedSinceNoTarget (library) and TestFindStoppedSince (wslrun) fail without the change and pass; TestUp covers the -l --running parse. On the Windows box (10.0.0.132) a test binary that had seen magpie-fedora running ran Values/Check/Reached/wslDistros/wslAgents/sessions.List/wslrun.Find against it for 4 rounds while it was stopped: it stayed Stopped and wsl -l -v was unchanged; wslrun and agent WSL tests pass there. No distro was terminated for the check (magpie-desktop is in use, the stopped ones aren't ours). go test ./... (nogui), -race on the WSL tests, go vet, windows and linux (nogui) builds pass.
2026-10-06 14:25:41 +08:00
library: a float in a Codex server's entry is written back as a float, and inf and dates as TOML has them (Lullaby on Discord: an internal MCP server that worked configured in Codex looped with its tool arguments lost after it was taken into the Library; they suspected tool_args / strict) — every key Codex reads for a server (codex-rs/config/src/mcp_types.rs RawMcpServerConfig: command, args, env, env_vars with { name, source } tables, cwd, url, bearer_token(_env_var), http_headers, env_http_headers, http_headers_helper, environment_id, auth, oauth.*, scopes, oauth_resource, startup_timeout_sec/_ms, tool_timeout_sec, enabled, required, startup_readiness, supports_parallel_tool_calls, tool_input_schema_max_bytes, omit_tools_from, default_tools_approval_mode, enabled/disabled_tools, tools.<tool>.approval_mode/output_token_limit, name) was checked through ImportServer and a SaveServer that adds an argument and gives the server to Claude Code too: all of them come back with their values, args with JSON and spaces in them verbatim, tables written inline. The one change was tomlValue writing a float with fmt.Sprint, so tool_timeout_sec = 120.0 came back the integer 120 (Codex reads either, as serde's f64 takes an integer, so this is not the reporter's loop), and +Inf, which isn't TOML, for inf; a date or time the user put in an entry went back as a string. tomlFloat now keeps a float one (".0" where it has no point or exponent; inf, -inf, nan), and go-toml's LocalDate/LocalTime/LocalDateTime and time.Time are written as TOML's own. Grok Build's entries go through the same writer. TestCodexServerKeysSurviveTheLibrary (the fixture above, compared value and type for every key) fails without the change (tool_timeout_sec float64 → int64) and passes; TestTOMLValueKeepsTypes (floats, an exponent, inf, a local date, time and date-time, an offset date-time) fails without it (+Inf doesn't parse) and passes. -race -count=10 on them with TestServerEveryFormat, TestServerKeepsUsersKeys and TestPutCodex*; go test -tags nogui ./internal/library ./internal/edit, go vet, gofmt -l, linux and windows (nogui) builds pass. The Library's MCP sign-in proxy (gateway /mcp/{name}) relays the body untouched. Not tried with the reporter's server; what broke their tool calls is not found here.
2026-10-06 18:22:41 +08:00
library: ZCode and Pi get a project's MCP servers too, and a project can be removed keeping what magpie wrote into it (#514, follow-up to #500: zcode支持项目级mcp添加 / pi也支持项目级mcp / 删除项目但保留mcp/skill) — a project's servers went only to Claude Code, Codex, Gemini CLI, Cursor and OpenCode, and Remove took every skill and server magpie had put in the folder away, so a project given some once had to stay in magpie for good. ZCode's project file is <project>/.zcode/config.json, servers under mcp.servers as in its user-wide ~/.zcode/cli/config.json (zcode.z.ai/en/docs/mcp-services, "Configuration Files and Default Load Paths": workspace scope, read before the user's, "enable": false the user's to set), written with fmtZCode (type, command/args/env or url/headers); Pi's is <project>/.pi/mcp.json, mcpServers as Pi's own MCP reads them once the project is trusted (pi.dev/docs/latest/mcp, "Configure servers"), written with fmtPiNative (command/args/env, url/headers, no type; SSE refused, as Pi refuses it), whatever extension the user's own Pi file is written for. Both are merged as the other agents' are: the user's keys, servers and comments stay, a server of the same name that isn't magpie's is left alone and said, a file magpie made (.pi/mcp.json) goes in the .gitignore and goes again once empty, and ZCode's user-wide file isn't touched. The page learns a project file's own SSE limit (projectNoSSE), so Pi's chip is greyed for an SSE server in a project even when its user-wide file (pi-mcp-adapter's) takes SSE; the trust note now names Pi beside Codex. The Remove dialog of a project magpie put anything in has "Keep its skills and MCP servers" / 保留项目里的技能和 MCP 服务器 (a checkbox row in the library's pick style, no coloured left border): checked, the text says what stays and Remove posts projects/remove with keep, and RemoveProject(dir, keep) only takes the project off the list — skills (links still following the library's), servers in the agents' files and magpie's .gitignore lines stay as they are, and nothing magpie does later (a sync, the server removed from the library) touches them; unchecked it removes as before. A one-off add without a project first (pick a folder from a server or skill) is not in this change. TestProjectServersZCodePi (ZCode's file with the user's model, server, enable:false and key kept beside magpie's three; Pi's file made in its shape, the SSE server refused with its reason, listed in .gitignore; ZCode's user-wide file unchanged; removing takes out only magpie's and the file it made) and TestRemoveProjectKeep (every file byte for byte after the removal, after a sync and after the server leaves the library) fail without the change, as does project-remove-keep.test.cjs (WebKit, and Chromium through a temp copy on plain chromium.launch(); en and zh: the box unchecked, its text, no stripe, pressing it scrolls nothing and swaps the text, Remove posts keep: true and says they were kept; a project with nothing written has no box and posts as before) against HEAD's assets. project-servers, click-scroll, library-items, library-all pass; menu-scroll and old-webkit fail the same on HEAD. No live config was read or written. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... (under a temp HOME) pass (TestFXInState failed on a TempDir cleanup race twice in the full gui run, passes alone and on the rerun; untouched).
2026-10-02 14:10:55 +08:00
library: a remote MCP server signed in once in magpie is used by every agent without signing in again (#615) — a streamable-HTTP server behind OAuth (Neon, Notion, Supabase) had each agent run its own sign-in. The server's editor now has Sign in: magpie finds the authorization server from the server's 401 (RFC 9728 protected-resource metadata, RFC 8414/OIDC metadata, else the 2025-03 /authorize, /token, /register at its origin), registers itself (RFC 7591, a 127.0.0.1 redirect), opens the browser with PKCE S256 and the resource, and keeps the tokens in mcp-signins.json beside its other account secrets (0600, never the keychain). While signed in, the agents are given the gateway's /mcp/<name> for that server, without the Authorization the library has for it; the gateway passes POST (JSON or event stream), the GET stream and DELETE on with the Mcp-Session-Id and magpie's token, renews it 2 minutes before it runs out and once on a 401, one renewal per server at a time, and a sign-in the server won't renew is answered 502 "sign in to it again" (never a 401 the agent would act on). Signing out, a new URL or an SSE server gives the agents the server's own address as before; a rename keeps the sign-in and removing the server drops it. /mcp/ is local-only or LAN-shared with the key, and refuses a request with an Origin or a Host not naming this computer, so a web page can't borrow the sign-in through the browser (form posts, DNS rebinding), and isn't counted as busy or key-limited. WSL agents are given their distro's gateway address. The row says Signed in or Sign-in ran out; zh strings added. Tests: TestSignIn, TestSignInWithoutMetadata, TestSignInPage, TestNothingToSignIn, TestTokenRenewsBeforeItRunsOut (8 calls, 1 renewal), TestRenewRefused, TestRenewRefusedForGood, TestMCPProxy, TestMCPProxyStaysHere, TestMCPProxyNotForWebPages (fails without the Origin/Host check), TestSignedInServerGoesThroughMagpie against a local fake OAuth+MCP server; mcp-signin.test.cjs (webkit, en and zh). Not tried against a real Neon/Notion/Supabase sign-in. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-03 09:37:39 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library: RTK says when rtk isn't on the PATH the agents get, and puts it there when asked (#601, Moody-Sin: magpie好像没有正确安装RTK — Pi said "RTK disabled: rtk binary not found in PATH" while the Library showed RTK working) — rtk's hooks run it by name (Pi's extension probes rtk --version and turns itself off when that fails; Claude Code's and Codex's are rtk hook …; the commands they rewrite to are rtk git status and so on), but magpie looked for rtk in more places than an agent's PATH: ~/.local/bin and ~/.cargo/bin, which the app adds to its own PATH whether the user's shell has them or not (rtk's own install.sh puts it in ~/.local/bin and only warns when that isn't on PATH), and WinGet\Links on Windows, which winget doesn't always link into. So rtk was "installed", the agents were switched on with it by its whole path, and none of them could run it. Now the PATH a terminal opened now has is read — the login shell's (proc.LoginPath, the one UserPath already asks for, kept a minute) or on Windows the registry's (moved from provider's registryPath) — and when rtk isn't in it the RTK card says so, with what Put RTK on PATH will do, and each agent that has rtk is tagged Not on PATH (不在 PATH 中). Put RTK on PATH (magpie library rtk path) links rtk into the first of ~/.local/bin, ~/bin, /opt/homebrew/bin, /usr/local/bin, linuxbrew's bin, ~/.cargo/bin that is on that PATH and writable, or on Windows adds rtk's own folder to the user's PATH in the registry (a link needs Developer Mode, and a copy is left behind when winget upgrades rtk; winget's package folder doesn't change across upgrades) and tells Explorer; with no such folder it says which folder to add to PATH in the shell profile — shell profiles are never edited. rtk is also found in winget's package folder (WinGet\Packages\rtk-ai.rtk_*), and rtk's script upgrades a linked rtk where the link points, not over the link. TestRTKOffPath and library-rtk-path.test.cjs (en, zh) fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet, go test -tags nogui -count=1 ./... under a temp HOME pass but the known TestFXInState flake. The Windows registry path is built, not run on Windows.
2026-10-03 02:38:17 +08:00
library: RTK says when rtk isn't on the PATH the agents get, and puts it there when asked (#601, Moody-Sin: magpie好像没有正确安装RTK — Pi said "RTK disabled: rtk binary not found in PATH" while the Library showed RTK working) — rtk's hooks run it by name (Pi's extension probes rtk --version and turns itself off when that fails; Claude Code's and Codex's are rtk hook …; the commands they rewrite to are rtk git status and so on), but magpie looked for rtk in more places than an agent's PATH: ~/.local/bin and ~/.cargo/bin, which the app adds to its own PATH whether the user's shell has them or not (rtk's own install.sh puts it in ~/.local/bin and only warns when that isn't on PATH), and WinGet\Links on Windows, which winget doesn't always link into. So rtk was "installed", the agents were switched on with it by its whole path, and none of them could run it. Now the PATH a terminal opened now has is read — the login shell's (proc.LoginPath, the one UserPath already asks for, kept a minute) or on Windows the registry's (moved from provider's registryPath) — and when rtk isn't in it the RTK card says so, with what Put RTK on PATH will do, and each agent that has rtk is tagged Not on PATH (不在 PATH 中). Put RTK on PATH (magpie library rtk path) links rtk into the first of ~/.local/bin, ~/bin, /opt/homebrew/bin, /usr/local/bin, linuxbrew's bin, ~/.cargo/bin that is on that PATH and writable, or on Windows adds rtk's own folder to the user's PATH in the registry (a link needs Developer Mode, and a copy is left behind when winget upgrades rtk; winget's package folder doesn't change across upgrades) and tells Explorer; with no such folder it says which folder to add to PATH in the shell profile — shell profiles are never edited. rtk is also found in winget's package folder (WinGet\Packages\rtk-ai.rtk_*), and rtk's script upgrades a linked rtk where the link points, not over the link. TestRTKOffPath and library-rtk-path.test.cjs (en, zh) fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet, go test -tags nogui -count=1 ./... under a temp HOME pass but the known TestFXInState flake. The Windows registry path is built, not run on Windows.
2026-10-03 02:38:17 +08:00
library: RTK says when rtk isn't on the PATH the agents get, and puts it there when asked (#601, Moody-Sin: magpie好像没有正确安装RTK — Pi said "RTK disabled: rtk binary not found in PATH" while the Library showed RTK working) — rtk's hooks run it by name (Pi's extension probes rtk --version and turns itself off when that fails; Claude Code's and Codex's are rtk hook …; the commands they rewrite to are rtk git status and so on), but magpie looked for rtk in more places than an agent's PATH: ~/.local/bin and ~/.cargo/bin, which the app adds to its own PATH whether the user's shell has them or not (rtk's own install.sh puts it in ~/.local/bin and only warns when that isn't on PATH), and WinGet\Links on Windows, which winget doesn't always link into. So rtk was "installed", the agents were switched on with it by its whole path, and none of them could run it. Now the PATH a terminal opened now has is read — the login shell's (proc.LoginPath, the one UserPath already asks for, kept a minute) or on Windows the registry's (moved from provider's registryPath) — and when rtk isn't in it the RTK card says so, with what Put RTK on PATH will do, and each agent that has rtk is tagged Not on PATH (不在 PATH 中). Put RTK on PATH (magpie library rtk path) links rtk into the first of ~/.local/bin, ~/bin, /opt/homebrew/bin, /usr/local/bin, linuxbrew's bin, ~/.cargo/bin that is on that PATH and writable, or on Windows adds rtk's own folder to the user's PATH in the registry (a link needs Developer Mode, and a copy is left behind when winget upgrades rtk; winget's package folder doesn't change across upgrades) and tells Explorer; with no such folder it says which folder to add to PATH in the shell profile — shell profiles are never edited. rtk is also found in winget's package folder (WinGet\Packages\rtk-ai.rtk_*), and rtk's script upgrades a linked rtk where the link points, not over the link. TestRTKOffPath and library-rtk-path.test.cjs (en, zh) fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet, go test -tags nogui -count=1 ./... under a temp HOME pass but the known TestFXInState flake. The Windows registry path is built, not run on Windows.
2026-10-03 02:38:17 +08:00
library: RTK says when rtk isn't on the PATH the agents get, and puts it there when asked (#601, Moody-Sin: magpie好像没有正确安装RTK — Pi said "RTK disabled: rtk binary not found in PATH" while the Library showed RTK working) — rtk's hooks run it by name (Pi's extension probes rtk --version and turns itself off when that fails; Claude Code's and Codex's are rtk hook …; the commands they rewrite to are rtk git status and so on), but magpie looked for rtk in more places than an agent's PATH: ~/.local/bin and ~/.cargo/bin, which the app adds to its own PATH whether the user's shell has them or not (rtk's own install.sh puts it in ~/.local/bin and only warns when that isn't on PATH), and WinGet\Links on Windows, which winget doesn't always link into. So rtk was "installed", the agents were switched on with it by its whole path, and none of them could run it. Now the PATH a terminal opened now has is read — the login shell's (proc.LoginPath, the one UserPath already asks for, kept a minute) or on Windows the registry's (moved from provider's registryPath) — and when rtk isn't in it the RTK card says so, with what Put RTK on PATH will do, and each agent that has rtk is tagged Not on PATH (不在 PATH 中). Put RTK on PATH (magpie library rtk path) links rtk into the first of ~/.local/bin, ~/bin, /opt/homebrew/bin, /usr/local/bin, linuxbrew's bin, ~/.cargo/bin that is on that PATH and writable, or on Windows adds rtk's own folder to the user's PATH in the registry (a link needs Developer Mode, and a copy is left behind when winget upgrades rtk; winget's package folder doesn't change across upgrades) and tells Explorer; with no such folder it says which folder to add to PATH in the shell profile — shell profiles are never edited. rtk is also found in winget's package folder (WinGet\Packages\rtk-ai.rtk_*), and rtk's script upgrades a linked rtk where the link points, not over the link. TestRTKOffPath and library-rtk-path.test.cjs (en, zh) fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet, go test -tags nogui -count=1 ./... under a temp HOME pass but the known TestFXInState flake. The Windows registry path is built, not run on Windows.
2026-10-03 02:38:17 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library, gui, cli: skills can be given to the agents as copies rather than links, for every agent or one (#896, ADabbler) — the Library linked each skill into the agents (a copy only where it couldn't link: Windows without the right to make links, WSL, Claude Desktop), and some want each agent to have a folder of its own. Skills › under the list, "Give skills as: Links | Copies" sets the library's way (library.json copySkills), and By agent gives an agent its own way (skillHow: Library's way / Links / Copies; Claude Desktop and WSL only ever take copies and have no switch). A copy is the whole skill folder with the .magpie-library marker, made by the same swap as before (.name.magpie-next, the old one set aside), so it is known for magpie's: taking a skill away removes magpie's copy and never a folder of the user's by that name. Switching makes each enabled skill again in place: a link becomes a copy; a copy becomes a link (made beside it and swapped in; a copy edited by hand is kept with the backups first). Where a link can't be made (Windows without the right) it stays a copy, as before. A copy follows the library's skill on every sync (at start, and after any change in the Library) as before; one that differs now (the library's folder edited by hand) is marked "Copy out of date" on the skill, saying in which agents, and a click copies it again. CLI: magpie library skill how link|copy [agent], magpie library skill how default <agent>. en/zh/ja/de. TestSkillHow (temp HOME: links at first, bad ways refused, copies marked, Read's how and copySkills, behind after a hand edit and cleared by a sync, codex its own way, an edited copy kept in the backups, back to the library's way, the user's own same-name folder and own skill untouched both ways, a copy removed with its skill) fails without the change; TestSkillCopyFollowsTheLibrary and TestSkillCopySwapSurvivesAnUndeletableFile now ask for copies rather than leave a copy where links are given, which is made a link again now. skill-how.test.cjs (WebKit, en and zh: the bar, Links/Copies posted with the note and status, the out-of-date tag with its agents and a click syncing without opening the skill, By agent's sheet with Claude Desktop's copies-only row and no switch, an agent's way posted and back to the library's, By agent (1), no select, no scroll, no left border) fails without it. Every Library and skill GUI test and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 19:46:55 +08:00
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00
library: an agent whose folder is a file isn't written, and skills reach 13 more agents with one link in ~/.agents/skills for those that share it (neiko on Discord: with DeepSeek Harness not installed the Library kept failing on "open ~/.dsh/AGENTS.md: not a directory" and ~/.dsh/config.yaml; inaction: the skills list is short of the Agents page, Kimi Code has none). An agent counted as there when its folder path existed or its command was on PATH, never asking whether the path was a folder — a file at ~/.dsh, or another dsh, made DeepSeek Harness look installed. Now an agent whose folder is a file (or under one) isn't detected (agent.Taken; Goose's, Cindy's and Claude Desktop's own checks want a folder too), a Library target leaves out each place a file stands in the way of, one with none left isn't listed, and an agent given no MCP server with none of magpie's isn't read. Skills: Kimi Code, Goose and Cindy get ~/.agents/skills (Kimi's ~/.config/agents/skills when there, as Kimi reads it first; never ~/.kimi/skills, which would hide Claude Code's from it); Command Code, Devin, Hermes, Droid, Cline, Qoder, Qoder CN, Grok, WorkBuddy, Hanako and fx their own skills folder; Kimi, Goose and Grok a project's .agents/skills. The shared folder is filled first, and an agent that reads ~/.agents/skills besides its own (Codex, Gemini CLI, OpenCode, Crush, DeepSeek Harness and those above) gets no second link to a skill already there. Tested with new taken_test.go (neiko's two errors without the change) and agent_skills_test.go (skills folders, written once, a shared skill imported for Kimi), in sandbox HOMEs; library-items and shared-skills 24/24 in Chromium and WebKit; go vet, go build (-tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. Not tried with real Kimi Code, Goose or Cindy.
2026-09-30 14:40:41 +08:00
wsl: background work never touches a WSL distro that has stopped since it was last seen running — it booted it again (TJHHHH on Discord: while they were repairing WSL, magpie kept starting it). Detection already listed only running distros, but the running set was kept for a minute (wslListAge), agents built while a distro ran kept its home and read its files from their Get/Sync/Check/Reached/Routed closures later, the sessions listing kept a distro's Running for 30s+ and read its \\wsl.localhost files, and wslrun.Find reused a tool it had found in a distro for a minute and ran wsl.exe -d on it. Any wsl.exe -d or \\wsl.localhost read boots a stopped distro, so a wsl --shutdown was undone within a minute, and the distro, running again, kept it going. Now wsl.exe -l --running -q (it starts nothing; UTF-16 with or without BOM, or UTF-8 under WSL_UTF8) is asked again after 3s, and a distro not in it is not touched: a WSL agent's Get gives the last values seen, Sync/Check/Reached do nothing and Routed/Joined/Beside/Added say no; sessions aren't listed or read from it (wslCold says cold); wslrun.Find (Claude subscription sign-in and usage) doesn't reuse or run a tool there; the Library gives skills to no target in it and its MCP probe says it isn't running. An explicit action still starts it: setting a WSL agent's field, a Set on a running agent, resuming a WSL session. TestWSLStoppedSinceNotOpened (agent, sessions), TestWSLStoppedSinceNoTarget (library) and TestFindStoppedSince (wslrun) fail without the change and pass; TestUp covers the -l --running parse. On the Windows box (10.0.0.132) a test binary that had seen magpie-fedora running ran Values/Check/Reached/wslDistros/wslAgents/sessions.List/wslrun.Find against it for 4 rounds while it was stopped: it stayed Stopped and wsl -l -v was unchanged; wslrun and agent WSL tests pass there. No distro was terminated for the check (magpie-desktop is in use, the stopped ones aren't ours). go test ./... (nogui), -race on the WSL tests, go vet, windows and linux (nogui) builds pass.
2026-10-06 14:25:41 +08:00
wsl: background work never touches a WSL distro that has stopped since it was last seen running — it booted it again (TJHHHH on Discord: while they were repairing WSL, magpie kept starting it). Detection already listed only running distros, but the running set was kept for a minute (wslListAge), agents built while a distro ran kept its home and read its files from their Get/Sync/Check/Reached/Routed closures later, the sessions listing kept a distro's Running for 30s+ and read its \\wsl.localhost files, and wslrun.Find reused a tool it had found in a distro for a minute and ran wsl.exe -d on it. Any wsl.exe -d or \\wsl.localhost read boots a stopped distro, so a wsl --shutdown was undone within a minute, and the distro, running again, kept it going. Now wsl.exe -l --running -q (it starts nothing; UTF-16 with or without BOM, or UTF-8 under WSL_UTF8) is asked again after 3s, and a distro not in it is not touched: a WSL agent's Get gives the last values seen, Sync/Check/Reached do nothing and Routed/Joined/Beside/Added say no; sessions aren't listed or read from it (wslCold says cold); wslrun.Find (Claude subscription sign-in and usage) doesn't reuse or run a tool there; the Library gives skills to no target in it and its MCP probe says it isn't running. An explicit action still starts it: setting a WSL agent's field, a Set on a running agent, resuming a WSL session. TestWSLStoppedSinceNotOpened (agent, sessions), TestWSLStoppedSinceNoTarget (library) and TestFindStoppedSince (wslrun) fail without the change and pass; TestUp covers the -l --running parse. On the Windows box (10.0.0.132) a test binary that had seen magpie-fedora running ran Values/Check/Reached/wslDistros/wslAgents/sessions.List/wslrun.Find against it for 4 rounds while it was stopped: it stayed Stopped and wsl -l -v was unchanged; wslrun and agent WSL tests pass there. No distro was terminated for the check (magpie-desktop is in use, the stopped ones aren't ours). go test ./... (nogui), -race on the WSL tests, go vet, windows and linux (nogui) builds pass.
2026-10-06 14:25:41 +08:00