magicrew-auth 0.3.39 → 0.3.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -75,9 +75,9 @@ curl -s "https://usemagpie.ai/api/latest?channel=stable" | grep -o '"version":"[
75
75
  | 22 | 同一账号有的请求 200、有的 403 `token无效`(`retryable=false`),换个时间又好了 | key 有两条签发路径时 store 与 magpie 各存一把,**静态挑一把必有一半踩空**。0.3.29 起 `keyCandidates()` 把 store 两槽 + `auth.access` 逐把打 chat,命中的写回 store;全被拒才交 402 让 magpie 换号。判据:`GET /v1/models` 对死 key **放行**,逐把探活必须用 `/v1/chat/completions` | CHANGELOG 0.3.29 · `tests/key-failover.mjs` |
76
76
  | 23 | 插件列表里别人有「已自动更新」徽章,magicrew 没有 | magpie 只自动更新包名以 `@magpie-community/` 开头的包(`internal/plugin/updates.go` 的 `Official()` 硬编码前缀);第三方 npm / git 源永远只提示。本插件用 `src/auto-maintenance.mjs` 复刻:读 `plugin-updates.json` 的 `waiting` → 调 `magpie plugin update` → 成功后补 `updated[]`(徽章读它)。判据:`plugin-updates.json` 的 waiting 有 `package:"Magicrew", git:true`;`findMagpieExe()` 能解析到 magpie 可执行文件。⚠ 更新后要重启 magpie 才加载新代码 | CHANGELOG 0.3.30 · `tests/auto-maintenance.mjs` |
77
77
  | 24 | 新装插件后没有任何签到记录,`plugin-checkin.json` 里没有 magicrew | magpie 的 `PluginCheckinOn()`:`pluginCheckins` 没有显式值时默认 **false**(只有 WorkBuddy / Trae / MiniMax / Qoder 有内置开关)。插件只能在加载时把 `settings.json` 的 `pluginCheckins[provider]` 补成 true —— **只在键不存在时补**,用户显式 false 不覆盖。判据:`settings.json` 的 `pluginCheckins` 有 `magicrew` / `magicrew-intl` 键 | CHANGELOG 0.3.30 · `src/auto-maintenance.mjs` |
78
- | 25 | 邀请上级选完,第二天签到后又变回别的号 | 0.3.38 起登录页「邀请上级」一个数字文本框:0=自动(当前滚动码,显示归属账号;无滚动码时显示作者 MYCF711)、1..N=已登录账号(按记录顺序,排除作者码重复号);留空/非法文本回退 0;当前 0 对应的账号仍在后端编号里但提示里隐藏。选中即写 `magicrew-invite-policy-<site>.json`(fixed),`inviteToUse` 优先它;死号自动回落滚动→作者码。判据:`tests/invite-policy.mjs` | CHANGELOG 0.3.38 · `src/hooks/methods.mjs` |
79
- | 26 | 邀请上级的数字文本框有没有「右边实时显示账号」 | magpie 的 `prompts` 只有 `text` / `select`,没有复合控件;对照表只能写进 prompt 的 `message`(每次用 `ask()` 按最新账号清单生成;0 的账号隐藏它对应的 1..N 那一格),保存后回一条「新号挂在 X(码)」说明。要真正的右侧实时刷新得等上游加控件 | CHANGELOG 0.3.36 · `src/hooks/methods.mjs` |
80
- | 27 | 数字方法的账号列表是空的 / 顺序不对 | 列表来自 `magicrew-invite-codes-<site>.json`(签到/登录时 `rollInvite` 记录)+ 作者码;老用户升级后 config 20s 由 `backfillInviteCodes()` 用 plugin-auth 的 JWT 补;账号按码缓存记录顺序(不重排,作者码重复号跳过),所以 1..N 稳定。页面/`page/codes.js` 已在 0.3.36 删除 | CHANGELOG 0.3.36 · `src/store.mjs` |
78
+ | 25 | 邀请上级选完,第二天签到后又变回别的号 | 0.3.40 起登录页「邀请上级」一个数字文本框:0=自动(首次用作者码,之后跟随最后登录 / 签到的滚动码)、1=当前首选账号(magpie 账号页 Make first 的 logins.json `first`);留空/非法文本回退 0。切首选后重新点 1 取新首选。选中即写 `magicrew-invite-policy-<site>.json`(fixed),`inviteToUse` 优先它;死号自动回落滚动→作者码。判据:`tests/invite-policy.mjs` | CHANGELOG 0.3.40 · `src/hooks/methods.mjs` |
79
+ | 26 | 邀请上级的数字文本框有没有「右边实时显示账号」 | magpie 的 `prompts` 只有 `text` / `select`,没有复合控件;提示只有一行「自动=0;首选=1;不填=默认自动」;没有实时右侧显示,保存后回一条「新号挂在首选 X(码)」说明。要真正的右侧实时刷新得等上游加控件 | CHANGELOG 0.3.40 · `src/hooks/methods.mjs` |
80
+ | 27 | 数字方法的账号列表是空的 / 顺序不对 | 首选来自 `~/.config/magpie/logins.json` 的 `first`(Make first 写入),账号的码来自 `magicrew-invite-codes-<site>.json`(签到/登录时 `rollInvite` 记录;老用户升级后 config 20s 由 `backfillInviteCodes()` 用 plugin-auth 的 JWT 补)。页面/`page/codes.js` 已在 0.3.36 删除 | CHANGELOG 0.3.40 · `src/store.mjs` |
81
81
 
82
82
  **坑 14 的两个附带判据**(都实测过):
83
83
 
package/CHANGELOG.md CHANGED
@@ -1,7 +1,24 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.3.40 — 2026-10-29
4
+
5
+ **邀请上级简化:自动=0;首选=1(0.3.39 补丁)**
6
+
7
+ 按截图页面反馈,不再列 1..N 账号;只看 magpie 账号页「首选」:
8
+
9
+ - **0 / 留空 / 非法文本** = 自动(首次用作者码,之后跟随最后登录 / 签到的滚动码)
10
+ - **1** = 当前首选账号(`logins.json` 的 `first`,Make first 写的那一个)的邀请码
11
+ - 想换:先在 magpie 账号页切首选,再点一次「邀请上级」填 1
12
+ - 没有列表、没有 1..N、没有 -1;首选账号没有码时回退 0 auto
13
+
14
+ `preferredOrg(siteId)` 只读 `logins.json` + `plugin-auth.json` 换 org;
15
+ 提示固定一行「邀请上级:自动=0;首选=1;不填=默认自动」。
16
+
17
+ 验证:`tests/invite-policy.mjs` 19 条(0/1/切换首选/首选无码回退)。
18
+
3
19
  ## 0.3.39 — 2026-10-29
4
20
 
21
+
5
22
  **点开「邀请上级」时提示文本刷新到当时态**
6
23
 
7
24
  0.3.38 靠 `ask()` 动态生成提示;部分宿主/调用路径直接读 `prompts` 时仍是插件
package/README.md CHANGED
@@ -34,7 +34,7 @@
34
34
  - **默认开启自动签到** —— 安装后自动把本 provider 的插件签到设为开(`pluginCheckins` 缺省时补 true;你手动关掉的不会被改回)
35
35
  - **自动更新** —— 复用 magpie 的插件更新机制:发现自己的新版本后调 `magpie plugin update`,并在成功后写「已自动更新」记录;更新完要重启一次 magpie 才会加载新代码
36
36
  - **邀请码自动滚** —— 第一次注册带作者的码,之后每次登录都记住「上一个登录账号」的码,拿到新的就覆盖旧的
37
- - **邀请上级可固定** —— 登录页多一个按钮「邀请上级」:填数字,0=自动(当前滚动码,显示归属账号;没有滚动码时显示作者 MYCF711),1..N=已登录账号;留空 / 非法文本回退 0。对照表在提示里,保存后回一条说明
37
+ - **邀请上级可固定** —— 登录页多一个按钮「邀请上级」:填数字,0=自动(首次用作者码,之后跟随最后登录 / 签到的滚动码),1=当前首选账号;留空 / 非法文本 = 0。切首选账号后再点一次 1 就换成新的首选
38
38
 
39
39
  ---
40
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magicrew-auth",
3
- "version": "0.3.39",
3
+ "version": "0.3.40",
4
4
  "description": "超级麦吉 / MagiCrew for magpie and OpenCode — one package, two providers: letsmagic.cn (国内) and magicrew.ai (international). Password or web-JWT sign-in, the account's own live model list, points balance, newcomer rewards, invite codes.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
@@ -30,9 +30,8 @@ import {
30
30
  } from "../api.mjs"
31
31
  import {
32
32
  inviteToUse,
33
- readInvite,
34
33
  liveOrgs,
35
- authorInvite,
34
+ preferredOrg,
36
35
  readAccountCodes,
37
36
  writeInvitePolicy,
38
37
  readSession,
@@ -369,63 +368,29 @@ function inviteTag(c) {
369
368
 
370
369
  // ═══ 邀请上级:登录页一个按钮 + 一个数字文本框 ═══════════════════════
371
370
  //
372
- // 用户要的是「一个文本框就够」:0 = 我的(作者 MYCF711),1..N = 已登录账号
373
- // (按账号名排序),-1 = 自动(跟随最后一个登录 / 签到的号);留空 / 不存在的
374
- // 数字回退 0。
371
+ // 用户要的是「一个文本框就够」:
372
+ // 0 / 留空 / 非法文本 = 自动(首次用作者码,之后跟随最后登录/签到的滚动码)
373
+ // 1 = 当前首选账号(magpie 账号页「首选」那个)的邀请码
374
+ // 没有列表、没有 1..N —— 想换首选就在 magpie 账号页切首选,再点这个按钮。
375
375
  //
376
- // magpie 的 prompts 只有 text / select,没有「右边实时显示对应账号」的复合控件;
377
- // 所以把「0=…;1=…」的对照表写进 message,用户填数字、下一步后回一条已保存说明。
378
- //
379
- // 用 ask(而不是静态 prompts)是为了每次打开都按最新账号清单生成对照表 ——
380
- // 插件加载时 backfill 可能还没跑完。
381
-
382
- function inviteZero(c) {
383
- const codes = readAccountCodes(c.id)
384
- const author = authorInvite(c.id)
385
- const rolling = readInvite(c.id)
386
- const code = rolling || author
387
- // 0 = 老的滚动规则最新那个码;找到归属就显示账号名,否则退作者码
388
- if (rolling && code !== author) {
389
- const owner = Object.keys(codes).find((o) => codes[o]?.code === code)
390
- if (owner) {
391
- const accountId = String(codes[owner].accountId || owner)
392
- return { code, label: accountId, org: owner, accountId }
393
- }
394
- return { code, label: "自动(滚动)", org: "", accountId: "" }
395
- }
396
- return { code: author, label: "自动(作者 MYCF711)", org: "", accountId: "MYCF711" }
397
- }
398
-
399
- function inviteEntries(c) {
400
- const codes = readAccountCodes(c.id)
401
- const author = authorInvite(c.id)
402
- const live = liveOrgs(c.id)
403
- const out = []
404
- let n = 1
405
- for (const [org, v] of Object.entries(codes)) {
406
- if (!v?.code || v.code === author) continue
407
- if (live && live.size && !live.has(org)) continue
408
- out.push({
409
- n: n++,
410
- label: String(v.accountId || org),
411
- code: v.code,
412
- org,
413
- accountId: String(v.accountId || org),
414
- })
376
+ // 0 的自动不需要在这里算:store.inviteToUse 的 auto 分支就是
377
+ // readInvite(site) || AUTHOR_INVITE。1 每次点开时读 logins.json 的 first。
378
+
379
+ function preferredInvite(c) {
380
+ const org = preferredOrg(c.id)
381
+ if (!org) return null
382
+ const v = readAccountCodes(c.id)[org]
383
+ if (!v?.code) return null
384
+ return {
385
+ code: v.code,
386
+ label: String(v.accountId || org),
387
+ org,
388
+ accountId: String(v.accountId || org),
415
389
  }
416
- return out
417
390
  }
418
391
 
419
392
  function inviteMessage(c) {
420
- const zero = inviteZero(c)
421
- const parts = ["邀请上级:填数字后点下一步", `0 = ${zero.label}(${zero.code})`]
422
- for (const e of inviteEntries(c)) {
423
- // 0 指向的那个账号在 1..N 里也有一格,但当前不显示(换了 0 就露出来)
424
- if (e.code === zero.code) continue
425
- parts.push(`${e.n} = ${e.label}(${e.code})`)
426
- }
427
- parts.push("留空 / 非法文本 = 0")
428
- return parts.join(";")
393
+ return "邀请上级:自动=0;首选=1;不填=默认自动"
429
394
  }
430
395
 
431
396
  export function makeMethods(c) {
@@ -682,28 +647,26 @@ export function makeMethods(c) {
682
647
  },
683
648
  async authorize(inputs) {
684
649
  const raw = String(inputs?.invite ?? "").trim()
685
- let n = raw === "" ? 0 : Number(raw)
686
- if (!Number.isInteger(n) || n < 0) n = 0
687
- if (n === 0) {
688
- writeInvitePolicy(c.id, { mode: "auto" })
689
- return { type: "failed", error: "已保存:0 = 自动(新号跟随当前滚动码)。这一步只存设置,不登录、不新增账号。" }
690
- }
691
- const entries = inviteEntries(c)
692
- const pick = entries.find((e) => e.n === n)
693
- if (!pick) {
650
+ const n = raw === "" ? 0 : Number(raw)
651
+ if (n === 1) {
652
+ const pick = preferredInvite(c)
653
+ if (pick) {
654
+ writeInvitePolicy(c.id, {
655
+ mode: "fixed",
656
+ code: pick.code,
657
+ org: pick.org,
658
+ accountId: pick.accountId,
659
+ })
660
+ return {
661
+ type: "failed",
662
+ error: `已保存:新号挂在首选 ${pick.label}(${pick.code})。这一步只存设置,不登录、不新增账号。`,
663
+ }
664
+ }
694
665
  writeInvitePolicy(c.id, { mode: "auto" })
695
- return { type: "failed", error: "数字没有对应账号,已回退 0(自动)。这一步只存设置,不登录、不新增账号。" }
696
- }
697
- writeInvitePolicy(c.id, {
698
- mode: "fixed",
699
- code: pick.code,
700
- org: pick.org,
701
- accountId: pick.accountId,
702
- })
703
- return {
704
- type: "failed",
705
- error: `已保存:新号挂在 ${pick.label}(${pick.code})。这一步只存设置,不登录、不新增账号。`,
666
+ return { type: "failed", error: "当前没有可用的首选账号邀请码,已回退 0(自动)。" }
706
667
  }
668
+ writeInvitePolicy(c.id, { mode: "auto" })
669
+ return { type: "failed", error: "已保存:0 = 自动(新号跟随当前滚动码)。这一步只存设置,不登录、不新增账号。" }
707
670
  },
708
671
  },
709
672
  ]
package/src/store.mjs CHANGED
@@ -156,6 +156,31 @@ export function liveOrgs(siteId) {
156
156
  }
157
157
  }
158
158
 
159
+ /**
160
+ * magpie 账号页上「首选」那个账号的 org(只读 logins.json)。
161
+ *
162
+ * magpie 把首选存在 logins.json 的 first 字段(Make first / 拖到第一行会写),
163
+ * 账号行里的 home 是 plugin-auth.json 的 key,借它换回 org。
164
+ */
165
+ export function preferredOrg(siteId) {
166
+ try {
167
+ const raw = JSON.parse(readFileSync(join(homedir(), ".config", "magpie", "logins.json"), "utf8"))
168
+ const agent = "plugin:" + siteId
169
+ const rows = (Array.isArray(raw) ? raw : []).filter((l) => l && l.agent === agent && l.home)
170
+ if (!rows.length) return ""
171
+ const first =
172
+ rows.find((l) => l.first === true) ||
173
+ rows.filter((l) => Number(l.order) > 0).sort((a, b) => Number(a.order) - Number(b.order))[0] ||
174
+ rows[0]
175
+ const home = String(first?.home || "").trim()
176
+ if (!home) return ""
177
+ const auth = JSON.parse(readFileSync(join(homedir(), ".config", "magpie", "plugin-auth.json"), "utf8"))
178
+ return String(auth?.[home]?.org || "").trim()
179
+ } catch {
180
+ return ""
181
+ }
182
+ }
183
+
159
184
  /** 作者自己的码(「我的」那一项) */
160
185
  export function authorInvite(siteId) {
161
186
  return AUTHOR_INVITE[siteId] || ""