magicrew-auth 0.3.38 → 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 +3 -3
- package/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/hooks/methods.mjs +42 -75
- package/src/store.mjs +25 -0
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.
|
|
79
|
-
| 26 | 邀请上级的数字文本框有没有「右边实时显示账号」 | magpie 的 `prompts` 只有 `text` / `select
|
|
80
|
-
| 27 | 数字方法的账号列表是空的 / 顺序不对 |
|
|
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,35 @@
|
|
|
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
|
+
|
|
19
|
+
## 0.3.39 — 2026-10-29
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
**点开「邀请上级」时提示文本刷新到当时态**
|
|
23
|
+
|
|
24
|
+
0.3.38 靠 `ask()` 动态生成提示;部分宿主/调用路径直接读 `prompts` 时仍是插件
|
|
25
|
+
加载时那一份。现在 `prompts` 改成 **getter**:每次 magpie 点开登录方式时按当时
|
|
26
|
+
的 `magicrew-invite-<site>.json`(0 的滚动码)和账号清单重新生成;`ask()` 保留。
|
|
27
|
+
|
|
28
|
+
验证:`tests/invite-policy.mjs` 22 条(新增 getter 动态刷新)。
|
|
29
|
+
|
|
3
30
|
## 0.3.38 — 2026-10-29
|
|
4
31
|
|
|
32
|
+
|
|
5
33
|
**邀请上级:0 改为「当前滚动码」并按需隐藏重复账号(0.3.36 / 0.3.37 补丁)**
|
|
6
34
|
|
|
7
35
|
0.3.36 把 0 做成了固定作者码;用户指出 0 应该是**老的滚动规则最新那个**:
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
- **默认开启自动签到** —— 安装后自动把本 provider 的插件签到设为开(`pluginCheckins` 缺省时补 true;你手动关掉的不会被改回)
|
|
35
35
|
- **自动更新** —— 复用 magpie 的插件更新机制:发现自己的新版本后调 `magpie plugin update`,并在成功后写「已自动更新」记录;更新完要重启一次 magpie 才会加载新代码
|
|
36
36
|
- **邀请码自动滚** —— 第一次注册带作者的码,之后每次登录都记住「上一个登录账号」的码,拿到新的就覆盖旧的
|
|
37
|
-
- **邀请上级可固定** —— 登录页多一个按钮「邀请上级」:填数字,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.
|
|
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",
|
package/src/hooks/methods.mjs
CHANGED
|
@@ -30,9 +30,8 @@ import {
|
|
|
30
30
|
} from "../api.mjs"
|
|
31
31
|
import {
|
|
32
32
|
inviteToUse,
|
|
33
|
-
readInvite,
|
|
34
33
|
liveOrgs,
|
|
35
|
-
|
|
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
|
-
// 用户要的是「一个文本框就够」:
|
|
373
|
-
//
|
|
374
|
-
//
|
|
371
|
+
// 用户要的是「一个文本框就够」:
|
|
372
|
+
// 0 / 留空 / 非法文本 = 自动(首次用作者码,之后跟随最后登录/签到的滚动码)
|
|
373
|
+
// 1 = 当前首选账号(magpie 账号页「首选」那个)的邀请码
|
|
374
|
+
// 没有列表、没有 1..N —— 想换首选就在 magpie 账号页切首选,再点这个按钮。
|
|
375
375
|
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -671,35 +636,37 @@ export function makeMethods(c) {
|
|
|
671
636
|
type: "api",
|
|
672
637
|
keyless: true,
|
|
673
638
|
label: `${c.SHORT} 邀请上级`,
|
|
674
|
-
|
|
639
|
+
// getter:每次 magpie 取 prompts(点开登录方式时)都按当时滚动码/账号清单重新生成,
|
|
640
|
+
// 不是插件加载时那一份
|
|
641
|
+
get prompts() {
|
|
642
|
+
return [{ type: "text", key: "invite", message: inviteMessage(c), placeholder: "0" }]
|
|
643
|
+
},
|
|
675
644
|
async ask(inputs) {
|
|
676
645
|
if (inputs && Object.prototype.hasOwnProperty.call(inputs, "invite")) return null
|
|
677
646
|
return { type: "text", key: "invite", message: inviteMessage(c), placeholder: "0" }
|
|
678
647
|
},
|
|
679
648
|
async authorize(inputs) {
|
|
680
649
|
const raw = String(inputs?.invite ?? "").trim()
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
+
}
|
|
690
665
|
writeInvitePolicy(c.id, { mode: "auto" })
|
|
691
|
-
return { type: "failed", error: "
|
|
692
|
-
}
|
|
693
|
-
writeInvitePolicy(c.id, {
|
|
694
|
-
mode: "fixed",
|
|
695
|
-
code: pick.code,
|
|
696
|
-
org: pick.org,
|
|
697
|
-
accountId: pick.accountId,
|
|
698
|
-
})
|
|
699
|
-
return {
|
|
700
|
-
type: "failed",
|
|
701
|
-
error: `已保存:新号挂在 ${pick.label}(${pick.code})。这一步只存设置,不登录、不新增账号。`,
|
|
666
|
+
return { type: "failed", error: "当前没有可用的首选账号邀请码,已回退 0(自动)。" }
|
|
702
667
|
}
|
|
668
|
+
writeInvitePolicy(c.id, { mode: "auto" })
|
|
669
|
+
return { type: "failed", error: "已保存:0 = 自动(新号跟随当前滚动码)。这一步只存设置,不登录、不新增账号。" }
|
|
703
670
|
},
|
|
704
671
|
},
|
|
705
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] || ""
|