mslxdff 0.1.203 → 0.1.207
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/README.md +3 -3
- package/bin/mslxdff.js +1 -1
- package/docs/cli_help_mini.md +146 -0
- package/package.json +38 -38
- package/plugins/README.md +14 -14
- package/plugins/prefer-model.mjs.example +14 -14
- package/src/anthropic/canonical-json.js +55 -0
- package/src/anthropic/count-tokens.js +61 -61
- package/src/anthropic/request-body.js +183 -126
- package/src/anthropic/request-shape.js +60 -0
- package/src/anthropic/session-id.js +58 -0
- package/src/anthropic/shared.js +18 -18
- package/src/anthropic/sse-aggregate.js +56 -56
- package/src/anthropic/tool-media.js +75 -0
- package/src/anthropic/translate.js +296 -287
- package/src/anthropic/web-search.js +361 -361
- package/src/api-kind.js +31 -31
- package/src/autostart.js +288 -288
- package/src/bench/probe.js +1 -1
- package/src/bench/runner.js +1 -1
- package/src/bench/via-probe.js +1 -1
- package/src/bench/via.js +144 -144
- package/src/bench/workbuddy-bench.js +1 -1
- package/src/chat/config.js +11 -11
- package/src/chat/cooling.js +81 -81
- package/src/chat/direct.js +58 -58
- package/src/chat/index.js +6 -6
- package/src/chat/spinner.js +38 -38
- package/src/chat/sse.js +69 -69
- package/src/chat/stats.js +247 -247
- package/src/chat/store.js +48 -48
- package/src/chat/tool-handlers.js +67 -67
- package/src/chat/tools.js +1 -1
- package/src/chat/upstream.js +59 -59
- package/src/chat-pipeline/auto-race.js +158 -158
- package/src/chat-pipeline/empty-turn.js +147 -147
- package/src/chat-pipeline/engine.js +17 -17
- package/src/chat-pipeline/index.js +8 -40
- package/src/chat-pipeline/serial-trial.js +279 -283
- package/src/chat-pipeline/terminal-verdict.js +132 -147
- package/src/chooser.js +54 -54
- package/src/claude-desktop/apply.js +101 -101
- package/src/claude-desktop/context1m.js +38 -38
- package/src/claude-desktop/doctor.js +146 -146
- package/src/claude-desktop/slots.js +152 -152
- package/src/cli/bootstrap.js +1 -1
- package/src/cli/commands/claude-desktop.js +284 -284
- package/src/cli/commands/daemon.js +231 -231
- package/src/cli/commands/model/list-live.js +22 -22
- package/src/cli/commands/model/list-providers.js +76 -76
- package/src/cli/commands/model/list-render.js +77 -77
- package/src/cli/commands/model/list-sort.js +52 -52
- package/src/cli/commands/model/list-tty.js +76 -76
- package/src/cli/commands/model/list.js +166 -166
- package/src/cli/commands/model/live-models.js +24 -24
- package/src/cli/commands/model/stats.js +43 -43
- package/src/cli/commands/model/status.js +47 -47
- package/src/cli/commands/model.js +51 -51
- package/src/cli/commands/provider/add.js +108 -108
- package/src/cli/commands/provider/allowlist.js +99 -99
- package/src/cli/commands/provider/cline-free.js +99 -99
- package/src/cli/commands/provider/cline-login.js +218 -218
- package/src/cli/commands/provider/cline-quota.js +65 -65
- package/src/cli/commands/provider/codearts-login.js +45 -45
- package/src/cli/commands/provider/config.js +119 -119
- package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
- package/src/cli/commands/provider/keys-guard.js +50 -50
- package/src/cli/commands/provider/keys.js +161 -161
- package/src/cli/commands/provider/qoder-checkin.js +81 -81
- package/src/cli/commands/provider/qoder-login.js +102 -102
- package/src/cli/commands/provider/raccoon-login.js +180 -180
- package/src/cli/commands/provider/raccoon-password.js +162 -162
- package/src/cli/commands/provider/raccoon-quota.js +109 -109
- package/src/cli/commands/provider/traework-checkin.js +144 -144
- package/src/cli/commands/provider/traework-login.js +164 -164
- package/src/cli/commands/provider/workbuddy-login.js +153 -153
- package/src/cli/commands/provider/zcode-login.js +77 -77
- package/src/cli/commands/provider/zcode-quota.js +55 -55
- package/src/cli/commands/provider.js +1 -1
- package/src/cli/commands/stats.js +158 -149
- package/src/cli/commands/system.js +261 -261
- package/src/cli/commands/timezone.js +47 -47
- package/src/cli/commands/use-group.js +60 -60
- package/src/cli/commands/workbuddy.js +189 -189
- package/src/cli/format.js +141 -141
- package/src/cli/group-helpers.js +70 -70
- package/src/cli/help.js +80 -80
- package/src/cli/index.js +70 -70
- package/src/cli/interactive.js +121 -121
- package/src/cli/join-core.js +99 -99
- package/src/cli/join-wizard.js +175 -175
- package/src/cli/policy.js +136 -136
- package/src/cli/provider-row.js +126 -126
- package/src/cli/util.js +24 -24
- package/src/compat.js +53 -53
- package/src/events.js +38 -38
- package/src/free-lane.js +131 -131
- package/src/free-watcher.js +63 -63
- package/src/groups.js +262 -262
- package/src/logs.js +195 -195
- package/src/metrics.js +71 -62
- package/src/model-capabilities/context-lookup.js +65 -65
- package/src/model-capabilities/merge.js +142 -142
- package/src/model-trace.js +187 -187
- package/src/models.js +1 -1
- package/src/opencode-identity.js +72 -72
- package/src/peers.js +254 -254
- package/src/plugins.js +107 -107
- package/src/providers/AGENTS.md +49 -0
- package/src/providers/base.js +164 -164
- package/src/providers/classify.js +22 -22
- package/src/providers/cline/allowlist-sync.js +93 -93
- package/src/providers/cline/free-catalog.js +71 -71
- package/src/providers/cline/headers.js +37 -37
- package/src/providers/cline/index.js +130 -130
- package/src/providers/cline/models.js +150 -150
- package/src/providers/cline/usage.js +274 -274
- package/src/providers/codearts/auth-pool.js +148 -148
- package/src/providers/codearts/concurrency.js +34 -34
- package/src/providers/codearts/const.js +65 -65
- package/src/providers/codearts/dpop.js +95 -95
- package/src/providers/codearts/index.js +175 -175
- package/src/providers/codearts/login.js +214 -214
- package/src/providers/codearts/models.js +182 -182
- package/src/providers/codearts/sign.js +96 -96
- package/src/providers/codearts/sse.js +204 -204
- package/src/providers/codearts/stream.js +209 -209
- package/src/providers/codearts/sts.js +124 -124
- package/src/providers/codearts.js +1 -1
- package/src/providers/dispatcher.js +146 -146
- package/src/providers/generic.js +156 -156
- package/src/providers/globalqwenwork/account-store.js +141 -141
- package/src/providers/globalqwenwork/constants.js +73 -73
- package/src/providers/globalqwenwork/crypto.js +2 -2
- package/src/providers/globalqwenwork/http.js +2 -2
- package/src/providers/globalqwenwork/index.js +331 -331
- package/src/providers/globalqwenwork/payload.js +147 -147
- package/src/providers/globalqwenwork/rsa.js +2 -2
- package/src/providers/globalqwenwork/sse.js +2 -2
- package/src/providers/globalqwenwork/stream.js +2 -2
- package/src/providers/globalqwenwork.js +1 -1
- package/src/providers/globalworkbuddy/account-store.js +198 -198
- package/src/providers/globalworkbuddy/auth.js +97 -97
- package/src/providers/globalworkbuddy/catalog.js +138 -138
- package/src/providers/globalworkbuddy/constants.js +73 -73
- package/src/providers/globalworkbuddy/device-auth.js +120 -120
- package/src/providers/globalworkbuddy/errors.js +98 -98
- package/src/providers/globalworkbuddy/headers.js +89 -89
- package/src/providers/globalworkbuddy/models.js +96 -96
- package/src/providers/globalworkbuddy/payload.js +80 -80
- package/src/providers/index.js +5 -5
- package/src/providers/model-id.js +172 -172
- package/src/providers/opencode.js +19 -19
- package/src/providers/openrouter.js +31 -31
- package/src/providers/qoder/account-store.js +106 -106
- package/src/providers/qoder/aggregate.js +60 -60
- package/src/providers/qoder/baseprompt.json +591 -591
- package/src/providers/qoder/checkin.js +160 -160
- package/src/providers/qoder/constants.js +44 -44
- package/src/providers/qoder/encode.js +41 -41
- package/src/providers/qoder/fingerprint.js +38 -38
- package/src/providers/qoder/models.js +77 -77
- package/src/providers/qoder/oauth.js +84 -84
- package/src/providers/qoder/payload.js +162 -162
- package/src/providers/qoder/request.js +27 -27
- package/src/providers/qoder/session.js +134 -134
- package/src/providers/qoder/sticky.js +38 -38
- package/src/providers/qoder/stream.js +116 -116
- package/src/providers/qoder.js +1 -1
- package/src/providers/raccoon/account-store.js +216 -216
- package/src/providers/raccoon/auth.js +67 -67
- package/src/providers/raccoon/chat.js +228 -228
- package/src/providers/raccoon/const.js +180 -180
- package/src/providers/raccoon/credits.js +185 -185
- package/src/providers/raccoon/crypto.js +43 -43
- package/src/providers/raccoon/envelope.js +31 -31
- package/src/providers/raccoon/headers.js +45 -45
- package/src/providers/raccoon/index.js +216 -216
- package/src/providers/raccoon/login.js +279 -279
- package/src/providers/raccoon/models.js +160 -160
- package/src/providers/raccoon/password-login.js +102 -102
- package/src/providers/raccoon/qr-codewords.js +115 -115
- package/src/providers/raccoon/qr.js +236 -236
- package/src/providers/raccoon/sse.js +135 -135
- package/src/providers/responses-channel.js +120 -120
- package/src/providers/share-keys.js +53 -53
- package/src/providers/traework/account-store.js +234 -234
- package/src/providers/traework/chat.js +217 -217
- package/src/providers/traework/checkin.js +77 -77
- package/src/providers/traework/constants.js +41 -41
- package/src/providers/traework/errors.js +52 -52
- package/src/providers/traework/headers.js +79 -79
- package/src/providers/traework/index.js +132 -132
- package/src/providers/traework/market-identity.js +116 -116
- package/src/providers/traework/models.js +98 -98
- package/src/providers/traework/payload.js +86 -86
- package/src/providers/traework/sse.js +184 -184
- package/src/providers/traework/token.js +52 -52
- package/src/providers/traework.js +2 -2
- package/src/providers/workbuddy/account-store.js +186 -186
- package/src/providers/workbuddy/auth.js +158 -158
- package/src/providers/workbuddy/balance.js +86 -86
- package/src/providers/workbuddy/cat-travel.js +125 -125
- package/src/providers/workbuddy/checkin.js +75 -75
- package/src/providers/workbuddy/desktop-info.js +78 -78
- package/src/providers/workbuddy/growth-api.js +67 -67
- package/src/providers/workbuddy/growth-plans.js +63 -63
- package/src/providers/workbuddy/growth.js +181 -181
- package/src/providers/workbuddy/index.js +217 -217
- package/src/providers/workbuddy/models.js +126 -126
- package/src/providers/workbuddy/payload.js +141 -141
- package/src/providers/workbuddy/rotation-log.js +83 -83
- package/src/providers/workbuddy/sanitize-tools.js +61 -61
- package/src/providers/workbuddy/sdk-chat.js +26 -26
- package/src/providers/workbuddy-balance.js +78 -78
- package/src/providers/zcode/account-store.js +129 -129
- package/src/providers/zcode/auth.js +28 -28
- package/src/providers/zcode/chat.js +179 -179
- package/src/providers/zcode/const.js +54 -54
- package/src/providers/zcode/context-shape.js +162 -162
- package/src/providers/zcode/headers.js +93 -93
- package/src/providers/zcode/index.js +124 -124
- package/src/providers/zcode/models.js +65 -65
- package/src/providers/zcode/oauth.js +120 -120
- package/src/providers/zcode/quota.js +176 -176
- package/src/providers/zcode/sse.js +196 -196
- package/src/readline-compat.js +25 -25
- package/src/responses/translate.js +311 -311
- package/src/routes/AGENTS.md +37 -0
- package/src/routes/chat/broadband-handler.js +72 -72
- package/src/routes/chat/empty-turn-report.js +31 -31
- package/src/routes/chat/exhausted-handler.js +87 -87
- package/src/routes/chat/hedge-handler.js +130 -130
- package/src/routes/chat/index.js +1 -1
- package/src/routes/chat/local-handler.js +63 -63
- package/src/routes/chat/peer-handler.js +81 -81
- package/src/routes/chat.js +3 -3
- package/src/routes/fallback.js +88 -88
- package/src/routes/groups-relay.js +214 -214
- package/src/routes/groups.js +122 -122
- package/src/routes/helpers.js +93 -83
- package/src/routes/index.js +147 -147
- package/src/routes/messages-route.js +375 -365
- package/src/routes/peers.js +10 -2
- package/src/routes/relay-queue.js +181 -181
- package/src/routes/relay.js +60 -60
- package/src/routes/responses-route.js +213 -213
- package/src/routes/stream-body.js +84 -84
- package/src/routes/stream-hold.js +337 -337
- package/src/routes/stream-scan.js +366 -366
- package/src/routes.js +3 -3
- package/src/runtime/auto-update.js +113 -113
- package/src/runtime/broadband-stream.js +79 -79
- package/src/runtime/broadband.js +103 -103
- package/src/runtime/group-sync.js +81 -81
- package/src/runtime/keep-warm.js +120 -120
- package/src/runtime/lifecycle-forensics.js +116 -116
- package/src/runtime/lifecycle-log.js +87 -87
- package/src/runtime/log-stamp.js +73 -73
- package/src/runtime/proc-info.js +137 -137
- package/src/runtime/qoder-checkin.js +93 -93
- package/src/runtime/raccoon-checkin.js +126 -126
- package/src/runtime/server-lifecycle.js +189 -189
- package/src/runtime/traework-checkin.js +118 -118
- package/src/runtime/workbuddy-checkin.js +120 -120
- package/src/runtime/workbuddy-growth.js +133 -133
- package/src/state/facade.js +58 -58
- package/src/state/memory.js +162 -162
- package/src/state/merge.js +25 -25
- package/src/state/migrations/cline-unify.js +94 -94
- package/src/state/migrations.js +33 -33
- package/src/state/persist.js +42 -42
- package/src/state/provider-config.js +142 -142
- package/src/state/schemas/group.js +31 -31
- package/src/state/schemas/model.js +94 -94
- package/src/state/schemas/peer.js +31 -31
- package/src/state/schemas/port.js +10 -10
- package/src/state/schemas/provider.js +202 -202
- package/src/state/schemas/timezone.js +70 -70
- package/src/state/schemas/token.js +48 -48
- package/src/state/schemas/use-group.js +102 -102
- package/src/state/store.js +176 -176
- package/src/state.js +1 -1
- package/src/sync-claude-desktop.js +234 -234
- package/src/sync-claude.js +152 -152
- package/src/sync-codex.js +116 -116
- package/src/sync-workbuddy.js +158 -158
- package/src/talk-full-redact.js +129 -129
- package/src/talk-full.js +131 -131
- package/src/talk-log.js +257 -257
- package/src/text-tokens.js +52 -52
- package/src/thinking-effort.js +67 -67
- package/src/time.js +106 -106
- package/src/timeline.js +26 -26
- package/src/transport/index.js +1 -1
- package/src/upstream-engine/index.js +74 -74
- package/src/upstream-engine/mode.js +15 -15
- package/src/upstream-engine/sdk/attempt.js +221 -221
- package/src/upstream-engine/sdk/chat.js +28 -28
- package/src/upstream-engine/sdk/convert.js +192 -192
- package/src/upstream-engine/sdk/diagnose.js +62 -62
- package/src/upstream-engine/sdk/dispatch.js +55 -55
- package/src/upstream-engine/sdk/responses.js +176 -176
- package/src/upstream-engine/sdk/sse.js +166 -166
- package/src/upstream-responses.js +305 -305
- package/src/usage/cache-fields.js +58 -0
- package/src/usage/record.js +109 -106
- package/src/usage/report.js +209 -195
- package/src/v6pool/claim.js +157 -157
- package/src/v6pool/config.js +65 -65
- package/src/v6pool/detect.js +130 -130
- package/src/v6pool/index.js +152 -152
- package/src/v6pool/prefix.js +140 -140
- package/src/v6pool/probe.js +40 -40
- package/src/v6pool/report.js +104 -104
- package/src/v6pool/rotate.js +263 -263
- package/src/warp-pool/config.js +74 -74
- package/src/warp-pool/health.js +106 -106
- package/src/warp-pool/identity.js +141 -141
- package/src/warp-pool/index.js +358 -358
- package/src/warp-pool/keys.js +29 -29
- package/src/warp-pool/nodes.js +95 -95
- package/src/warp-pool/ports.js +160 -160
- package/src/warp-pool/register.js +113 -113
- package/src/warp-pool/report.js +232 -232
- package/src/warp-pool/revive.js +168 -168
- package/src/warp-pool/rotate.js +214 -214
- package/src/warp-pool/strategy.js +43 -43
- package/src/warp-pool/verdict.js +69 -69
- package/src/warp-pool/wireproxy.js +162 -162
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
// mslxdff -provider qoder login — Qoder 设备授权(PKCE + poll),对齐 cline-login/traework-login。
|
|
2
|
-
// 授权后落盘 auths/qoder-<uid>.json + state.json,完成后进程退出。
|
|
3
|
-
import { compatFetch, timeoutSignal } from "../../../compat.js";
|
|
4
|
-
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
5
|
-
import { pkce, hex16, buildLoginUrl, waitForAuth, fetchUserInfo } from "../../../providers/qoder/oauth.js";
|
|
6
|
-
|
|
7
|
-
const REGIONS = ["global", "cn"];
|
|
8
|
-
|
|
9
|
-
export async function handleQoderLogin(id, sub, rest = [], deps = {}) {
|
|
10
|
-
if (normalizeProviderId(id) !== "qoder") return false;
|
|
11
|
-
if (sub !== "login" && sub !== "auth" && sub !== "oauth") return false;
|
|
12
|
-
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
13
|
-
const log = deps.log || ((m) => console.log(m));
|
|
14
|
-
|
|
15
|
-
// 可选参数:--region cn 或 --region global(默认 global)
|
|
16
|
-
const regionIdx = rest.indexOf("--region") >= 0 ? rest.indexOf("--region") : rest.indexOf("-r");
|
|
17
|
-
const region = regionIdx >= 0 ? String(rest[regionIdx + 1] || "global").trim() : "global";
|
|
18
|
-
const normRegion = region === "cn" ? "cn" : "global";
|
|
19
|
-
|
|
20
|
-
const { verifier, challenge } = pkce();
|
|
21
|
-
const nonce = hex16();
|
|
22
|
-
const loginUrl = buildLoginUrl({ region: normRegion, nonce, challenge });
|
|
23
|
-
|
|
24
|
-
log("=".repeat(60));
|
|
25
|
-
log(" Qoder 登录");
|
|
26
|
-
log(` Region: ${normRegion === "cn" ? "中国站 (qoder.com.cn)" : "国际站 (qoder.com)"}`);
|
|
27
|
-
log("=".repeat(60));
|
|
28
|
-
log("");
|
|
29
|
-
log(" 1. 在浏览器打开下面链接,用 Qoder 账号登录(OAuth 授权)");
|
|
30
|
-
log(" 2. 登录成功后会自动完成授权,请等待回显");
|
|
31
|
-
log("");
|
|
32
|
-
log(` ${loginUrl}`);
|
|
33
|
-
log("");
|
|
34
|
-
log("🔄 等待你授权(自动轮询,每 3 秒,最多 10 分钟)...");
|
|
35
|
-
|
|
36
|
-
let bundle;
|
|
37
|
-
try {
|
|
38
|
-
bundle = await waitForAuth({ region: normRegion, nonce, verifier, fetchImpl, log });
|
|
39
|
-
} catch (e) {
|
|
40
|
-
console.error(`\n❌ ${e.message}`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
if (!bundle || !bundle.deviceToken) {
|
|
44
|
-
console.error("\n❌ 未获取到 device token");
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
log(`\n✅ 授权成功!deviceToken=${bundle.deviceToken.slice(0, 12)}...`);
|
|
49
|
-
if (bundle.refreshToken) log(` refreshToken=${bundle.refreshToken.slice(0, 12)}...`);
|
|
50
|
-
|
|
51
|
-
// 取 userinfo
|
|
52
|
-
let user = { uid: "", name: "", email: "", userType: "personal_standard", organizationId: "", organizationName: "" };
|
|
53
|
-
try {
|
|
54
|
-
user = await fetchUserInfo({ region: normRegion, deviceToken: bundle.deviceToken, fetchImpl });
|
|
55
|
-
log(` UID: ${user.uid} 昵称: ${user.name || "(空)"} 邮箱: ${user.email || "(空)"}`);
|
|
56
|
-
} catch (e) {
|
|
57
|
-
log(` ⚠️ userinfo 失败: ${e.message}(使用 device token 前 8 位兜底 uid)`);
|
|
58
|
-
user.uid = bundle.deviceToken.slice(0, 8);
|
|
59
|
-
}
|
|
60
|
-
if (!user.uid) {
|
|
61
|
-
console.error("❌ 无法获取用户标识");
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 落盘
|
|
66
|
-
const { saveQoderAccount } = await import("../../../providers/qoder/account-store.js");
|
|
67
|
-
const save = deps.saveAccount || saveQoderAccount;
|
|
68
|
-
const saved = await save({
|
|
69
|
-
uid: user.uid,
|
|
70
|
-
deviceToken: bundle.deviceToken,
|
|
71
|
-
refreshToken: bundle.refreshToken || "",
|
|
72
|
-
region: normRegion,
|
|
73
|
-
name: user.name,
|
|
74
|
-
email: user.email,
|
|
75
|
-
userType: user.userType,
|
|
76
|
-
organizationId: user.organizationId,
|
|
77
|
-
organizationName: user.organizationName,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
log("");
|
|
81
|
-
log("=".repeat(60));
|
|
82
|
-
log(`✅ 登录完成!`);
|
|
83
|
-
log(` UID: ${user.uid}`);
|
|
84
|
-
log(` 账号数: ${saved.accounts}${saved.updated ? "(旧号凭证已更新)" : ""}`);
|
|
85
|
-
log(` 数据文件: ${saved.file}`);
|
|
86
|
-
log("=".repeat(60));
|
|
87
|
-
// 默认 allowAny on(免费福利供应商,对齐 codearts 惯例)
|
|
88
|
-
try {
|
|
89
|
-
const { saveProviderAllowAnyModels } = await import("../../../state.js");
|
|
90
|
-
saveProviderAllowAnyModels("qoder", true);
|
|
91
|
-
log(" allowAnyModels=on(免费福利供应商默认放行;收紧:mslxdff -provider qoder allowAny off)");
|
|
92
|
-
} catch {}
|
|
93
|
-
|
|
94
|
-
log("下一步:");
|
|
95
|
-
log(" mslxdff -provider qoder models 列模型列表");
|
|
96
|
-
log(" mslxdff -restart(用户终端跑) 重启网关使新账号生效");
|
|
97
|
-
log(" 然后 curl http://localhost:8989/v1/chat/completions -d '{\"model\":\"qoder/auto\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'");
|
|
98
|
-
log("");
|
|
99
|
-
log("多账号:重复 `mslxdff -provider qoder login` 追加,每号独立 uid,对话自动轮换");
|
|
100
|
-
|
|
101
|
-
if (!deps.noExit) process.exit(0);
|
|
102
|
-
return true;
|
|
1
|
+
// mslxdff -provider qoder login — Qoder 设备授权(PKCE + poll),对齐 cline-login/traework-login。
|
|
2
|
+
// 授权后落盘 auths/qoder-<uid>.json + state.json,完成后进程退出。
|
|
3
|
+
import { compatFetch, timeoutSignal } from "../../../compat.js";
|
|
4
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
5
|
+
import { pkce, hex16, buildLoginUrl, waitForAuth, fetchUserInfo } from "../../../providers/qoder/oauth.js";
|
|
6
|
+
|
|
7
|
+
const REGIONS = ["global", "cn"];
|
|
8
|
+
|
|
9
|
+
export async function handleQoderLogin(id, sub, rest = [], deps = {}) {
|
|
10
|
+
if (normalizeProviderId(id) !== "qoder") return false;
|
|
11
|
+
if (sub !== "login" && sub !== "auth" && sub !== "oauth") return false;
|
|
12
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
13
|
+
const log = deps.log || ((m) => console.log(m));
|
|
14
|
+
|
|
15
|
+
// 可选参数:--region cn 或 --region global(默认 global)
|
|
16
|
+
const regionIdx = rest.indexOf("--region") >= 0 ? rest.indexOf("--region") : rest.indexOf("-r");
|
|
17
|
+
const region = regionIdx >= 0 ? String(rest[regionIdx + 1] || "global").trim() : "global";
|
|
18
|
+
const normRegion = region === "cn" ? "cn" : "global";
|
|
19
|
+
|
|
20
|
+
const { verifier, challenge } = pkce();
|
|
21
|
+
const nonce = hex16();
|
|
22
|
+
const loginUrl = buildLoginUrl({ region: normRegion, nonce, challenge });
|
|
23
|
+
|
|
24
|
+
log("=".repeat(60));
|
|
25
|
+
log(" Qoder 登录");
|
|
26
|
+
log(` Region: ${normRegion === "cn" ? "中国站 (qoder.com.cn)" : "国际站 (qoder.com)"}`);
|
|
27
|
+
log("=".repeat(60));
|
|
28
|
+
log("");
|
|
29
|
+
log(" 1. 在浏览器打开下面链接,用 Qoder 账号登录(OAuth 授权)");
|
|
30
|
+
log(" 2. 登录成功后会自动完成授权,请等待回显");
|
|
31
|
+
log("");
|
|
32
|
+
log(` ${loginUrl}`);
|
|
33
|
+
log("");
|
|
34
|
+
log("🔄 等待你授权(自动轮询,每 3 秒,最多 10 分钟)...");
|
|
35
|
+
|
|
36
|
+
let bundle;
|
|
37
|
+
try {
|
|
38
|
+
bundle = await waitForAuth({ region: normRegion, nonce, verifier, fetchImpl, log });
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error(`\n❌ ${e.message}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
if (!bundle || !bundle.deviceToken) {
|
|
44
|
+
console.error("\n❌ 未获取到 device token");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log(`\n✅ 授权成功!deviceToken=${bundle.deviceToken.slice(0, 12)}...`);
|
|
49
|
+
if (bundle.refreshToken) log(` refreshToken=${bundle.refreshToken.slice(0, 12)}...`);
|
|
50
|
+
|
|
51
|
+
// 取 userinfo
|
|
52
|
+
let user = { uid: "", name: "", email: "", userType: "personal_standard", organizationId: "", organizationName: "" };
|
|
53
|
+
try {
|
|
54
|
+
user = await fetchUserInfo({ region: normRegion, deviceToken: bundle.deviceToken, fetchImpl });
|
|
55
|
+
log(` UID: ${user.uid} 昵称: ${user.name || "(空)"} 邮箱: ${user.email || "(空)"}`);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
log(` ⚠️ userinfo 失败: ${e.message}(使用 device token 前 8 位兜底 uid)`);
|
|
58
|
+
user.uid = bundle.deviceToken.slice(0, 8);
|
|
59
|
+
}
|
|
60
|
+
if (!user.uid) {
|
|
61
|
+
console.error("❌ 无法获取用户标识");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 落盘
|
|
66
|
+
const { saveQoderAccount } = await import("../../../providers/qoder/account-store.js");
|
|
67
|
+
const save = deps.saveAccount || saveQoderAccount;
|
|
68
|
+
const saved = await save({
|
|
69
|
+
uid: user.uid,
|
|
70
|
+
deviceToken: bundle.deviceToken,
|
|
71
|
+
refreshToken: bundle.refreshToken || "",
|
|
72
|
+
region: normRegion,
|
|
73
|
+
name: user.name,
|
|
74
|
+
email: user.email,
|
|
75
|
+
userType: user.userType,
|
|
76
|
+
organizationId: user.organizationId,
|
|
77
|
+
organizationName: user.organizationName,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
log("");
|
|
81
|
+
log("=".repeat(60));
|
|
82
|
+
log(`✅ 登录完成!`);
|
|
83
|
+
log(` UID: ${user.uid}`);
|
|
84
|
+
log(` 账号数: ${saved.accounts}${saved.updated ? "(旧号凭证已更新)" : ""}`);
|
|
85
|
+
log(` 数据文件: ${saved.file}`);
|
|
86
|
+
log("=".repeat(60));
|
|
87
|
+
// 默认 allowAny on(免费福利供应商,对齐 codearts 惯例)
|
|
88
|
+
try {
|
|
89
|
+
const { saveProviderAllowAnyModels } = await import("../../../state.js");
|
|
90
|
+
saveProviderAllowAnyModels("qoder", true);
|
|
91
|
+
log(" allowAnyModels=on(免费福利供应商默认放行;收紧:mslxdff -provider qoder allowAny off)");
|
|
92
|
+
} catch {}
|
|
93
|
+
|
|
94
|
+
log("下一步:");
|
|
95
|
+
log(" mslxdff -provider qoder models 列模型列表");
|
|
96
|
+
log(" mslxdff -restart(用户终端跑) 重启网关使新账号生效");
|
|
97
|
+
log(" 然后 curl http://localhost:8989/v1/chat/completions -d '{\"model\":\"qoder/auto\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'");
|
|
98
|
+
log("");
|
|
99
|
+
log("多账号:重复 `mslxdff -provider qoder login` 追加,每号独立 uid,对话自动轮换");
|
|
100
|
+
|
|
101
|
+
if (!deps.noExit) process.exit(0);
|
|
102
|
+
return true;
|
|
103
103
|
}
|
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
// mslxdff -provider raccoon login — 扫码登录(终端二维码 + 轮询)。
|
|
2
|
-
// ⚠ 2026-10 上游改版:二维码必须**先由服务端签发**(POST /api/iam/v1/auth/mp/qrcode),
|
|
3
|
-
// 拿到 qrcode_code + scan_path 才能渲染。旧的「本地自造 code」在新通道下没有服务端会话,
|
|
4
|
-
// 微信扫码后查不到该码 → 手机端报「二维码已过期或已被使用」。详见 src/providers/raccoon/login.js。
|
|
5
|
-
// 该链路匿名可达且**不需要验证码**;成功后落盘 auths/raccoon-<uid>.json(0600)
|
|
6
|
-
// 并把兜底目录只增不减补齐进 allowlist(避免「登录成功却处处 403」)。
|
|
7
|
-
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
8
|
-
import { compatFetch } from "../../../compat.js";
|
|
9
|
-
import {
|
|
10
|
-
buildQrUrl,
|
|
11
|
-
cancelQrSession,
|
|
12
|
-
confirmRaccoonSession,
|
|
13
|
-
createQrSession,
|
|
14
|
-
fetchRaccoonUserInfo,
|
|
15
|
-
pollQrLogin,
|
|
16
|
-
} from "../../../providers/raccoon/login.js";
|
|
17
|
-
import { encodeQr, renderTerminal } from "../../../providers/raccoon/qr.js";
|
|
18
|
-
import { ensureRaccoonDeviceId, saveRaccoonAccount } from "../../../providers/raccoon/account-store.js";
|
|
19
|
-
import { seedRaccoonAllowlist } from "../../../providers/raccoon/models.js";
|
|
20
|
-
import { raccoonTokenFingerprint, resolveRaccoonUid } from "../../../providers/raccoon/auth.js";
|
|
21
|
-
import { claimRaccoonLoginReward, raccoonClaimStatus } from "../../../providers/raccoon/credits.js";
|
|
22
|
-
|
|
23
|
-
const LOGIN_SUBS = new Set(["login", "auth", "oauth"]);
|
|
24
|
-
|
|
25
|
-
const RELOGIN_HINT = " (可重试:mslxdff -provider raccoon login)";
|
|
26
|
-
|
|
27
|
-
export async function handleRaccoonLogin(id, sub, rest = [], deps = {}) {
|
|
28
|
-
const pid = normalizeProviderId(id) || String(id || "").toLowerCase();
|
|
29
|
-
if (pid !== "raccoon") return false;
|
|
30
|
-
// `login --password` 走手机号+密码(多账号免扫码);无该旗标仍是扫码。分派必须在
|
|
31
|
-
// LOGIN_SUBS 判定之前,且只在此处转交 —— 扫码路径本身零改动。
|
|
32
|
-
if (LOGIN_SUBS.has(sub) && rest.some((x) => {
|
|
33
|
-
const s = String(x);
|
|
34
|
-
return s === "--password" || s.startsWith("--password=") || s === "-p" || s === "--phone" || s.startsWith("--phone=");
|
|
35
|
-
})) {
|
|
36
|
-
const { handleRaccoonPasswordLogin } = await import("./raccoon-password.js");
|
|
37
|
-
return handleRaccoonPasswordLogin(id, sub, rest, deps);
|
|
38
|
-
}
|
|
39
|
-
if (!LOGIN_SUBS.has(sub)) return false;
|
|
40
|
-
|
|
41
|
-
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
42
|
-
const log = deps.log || ((m) => console.log(m));
|
|
43
|
-
const exit = deps.exit || ((code) => process.exit(code));
|
|
44
|
-
|
|
45
|
-
log("=".repeat(60));
|
|
46
|
-
log(" Raccoon 登录(商汤小浣熊 · 积分制模型池)");
|
|
47
|
-
log("=".repeat(60));
|
|
48
|
-
log("");
|
|
49
|
-
log(" 1. 用微信「扫一扫」扫描下方二维码,并在微信里点确认(浏览器直接打开会 404)");
|
|
50
|
-
log(" 2. 确认后本机自动继续(CLI 轮询,无需回调)");
|
|
51
|
-
log("");
|
|
52
|
-
|
|
53
|
-
// 签发二维码:这一步失败就没有可扫的码,必须当场说清楚,不能拿一个假码糊过去
|
|
54
|
-
let session;
|
|
55
|
-
try {
|
|
56
|
-
session = deps.qrSession || await createQrSession({ fetchImpl });
|
|
57
|
-
} catch (error) {
|
|
58
|
-
log(`❌ 无法获取二维码:${error?.message ?? error}`);
|
|
59
|
-
log(" (多为网络不通或上游改版;可重试:mslxdff -provider raccoon login)");
|
|
60
|
-
exit(1);
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
const code = deps.qrCode || session.qrcodeCode;
|
|
64
|
-
const url = deps.qrUrl || buildQrUrl(code, session.scanPath);
|
|
65
|
-
|
|
66
|
-
const art = renderTerminal(encodeQr(url));
|
|
67
|
-
if (art) {
|
|
68
|
-
log(art);
|
|
69
|
-
log("");
|
|
70
|
-
}
|
|
71
|
-
log(` 登录链接:${url}`);
|
|
72
|
-
log(` 有效期:${Math.round(session.expiresInMs / 1000)} 秒(过期请重跑本命令)`);
|
|
73
|
-
log("");
|
|
74
|
-
log("🔄 等待扫码确认…");
|
|
75
|
-
|
|
76
|
-
let out;
|
|
77
|
-
try {
|
|
78
|
-
out = await pollQrLogin(code, {
|
|
79
|
-
fetchImpl,
|
|
80
|
-
sleepFn: deps.sleepFn,
|
|
81
|
-
intervalMs: deps.intervalMs ?? session.pollIntervalMs,
|
|
82
|
-
timeoutMs: deps.timeoutMs ?? session.expiresInMs,
|
|
83
|
-
onTick: deps.onTick,
|
|
84
|
-
});
|
|
85
|
-
} catch (error) {
|
|
86
|
-
log("");
|
|
87
|
-
log(`❌ 登录失败:${error?.message ?? error}`);
|
|
88
|
-
log(RELOGIN_HINT);
|
|
89
|
-
exit(1);
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (out.status !== "success") {
|
|
94
|
-
// 非终态失败:回收服务端会话(失败也不打断),并对每种结局给一句人话
|
|
95
|
-
await cancelQrSession(code, { fetchImpl });
|
|
96
|
-
log("");
|
|
97
|
-
if (out.status === "canceled") log("❌ 已在手机端取消登录(未写入任何凭据)");
|
|
98
|
-
else if (out.status === "expired") log("❌ 二维码已过期(上游作废了本次会话)");
|
|
99
|
-
else log("❌ 登录已超时");
|
|
100
|
-
log(RELOGIN_HINT);
|
|
101
|
-
exit(1);
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return await finishRaccoonLogin({ credential: out.credential, fetchImpl, deps, log });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 登录成功后的收尾四步(**扫码与密码两条路共用,单一源**):
|
|
110
|
-
* confirm 会话 → 取用户信息 → 落盘 + 补 allowlist → 领一次性登录奖励 → 打印回执。
|
|
111
|
-
*
|
|
112
|
-
* 抽出来的理由不只是省行数:这四步的顺序有硬约束(必须先 confirm 才能用 token;领奖必须在
|
|
113
|
-
* access_token 刚签发的窗口内),任何一条路径自己重写一遍都会漏掉其中一步且难以察觉。
|
|
114
|
-
*
|
|
115
|
-
* @param {object} a
|
|
116
|
-
* @param {object} a.credential 已签发的凭据(两种登录方式同形)
|
|
117
|
-
* @param {Function} a.fetchImpl
|
|
118
|
-
* @param {object} a.deps 与原 CLI 依赖注入同形(dir/file/deviceId/saveAccount/claimReward/skipReward/env)
|
|
119
|
-
* @param {Function} a.log
|
|
120
|
-
* @param {string} [a.methodNote] 登录方式说明行(如密码登录标注手机号),纯展示
|
|
121
|
-
* @returns {Promise<boolean>} 恒 true(表示「已处理」,交还 CLI 派发链)
|
|
122
|
-
*/
|
|
123
|
-
export async function finishRaccoonLogin({ credential, fetchImpl, deps = {}, log, methodNote = "" }) {
|
|
124
|
-
// 新登录的会话是「未确认」状态:不先 POST /iam/confirm,此后所有业务端点(含下面的领奖励、
|
|
125
|
-
// 以及 daemon 的 chat)都会撞 `400 code 200901 iam_confirm_required`。登录时当场确认一次,
|
|
126
|
-
// 让落盘的凭据从一开始就是可用的;确认失败不阻断(chat.js 有懒重试兜底),只在末尾如实提示。
|
|
127
|
-
const confirmed = await confirmRaccoonSession(credential, { fetchImpl });
|
|
128
|
-
const info = await fetchRaccoonUserInfo(credential, { fetchImpl });
|
|
129
|
-
const uid = resolveRaccoonUid({
|
|
130
|
-
userId: info.userId,
|
|
131
|
-
officeIdentity: info.officeIdentity || credential.office_identity,
|
|
132
|
-
token: credential.access_token,
|
|
133
|
-
});
|
|
134
|
-
const deviceId = deps.deviceId || ensureRaccoonDeviceId({ uid, dir: deps.dir });
|
|
135
|
-
const save = deps.saveAccount || saveRaccoonAccount;
|
|
136
|
-
const saved = await save({
|
|
137
|
-
uid,
|
|
138
|
-
accessToken: credential.access_token,
|
|
139
|
-
refreshToken: credential.refresh_token || "",
|
|
140
|
-
expiresAt: credential.expires_at || "",
|
|
141
|
-
officeIdentity: info.officeIdentity || credential.office_identity || "",
|
|
142
|
-
deviceId,
|
|
143
|
-
name: info.name || "",
|
|
144
|
-
phone: info.phone || "",
|
|
145
|
-
dir: deps.dir,
|
|
146
|
-
file: deps.file,
|
|
147
|
-
});
|
|
148
|
-
const allow = seedRaccoonAllowlist({ file: deps.file });
|
|
149
|
-
|
|
150
|
-
// 一次性新手礼包只能在这儿领:此刻 access_token 刚签发(寿命约 3 小时),是全程唯一稳的窗口。
|
|
151
|
-
// 刻意不交给 daemon 定时去做 —— 那时 token 多半已过期,要续期就得和网关请求抢同一条一次性 refresh_token。
|
|
152
|
-
let reward;
|
|
153
|
-
if (!deps.skipReward) {
|
|
154
|
-
const claim = deps.claimReward || claimRaccoonLoginReward;
|
|
155
|
-
try {
|
|
156
|
-
reward = await claim({ credential, fetchImpl, env: deps.env });
|
|
157
|
-
} catch (e) {
|
|
158
|
-
reward = { status: "error", msg: String(e?.message || e).slice(0, 120) };
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
log("");
|
|
163
|
-
log(`✅ 登录成功!uid=${uid} 昵称=${info.name || "(空)"} token=${raccoonTokenFingerprint(credential.access_token)}…`);
|
|
164
|
-
if (methodNote) log(methodNote);
|
|
165
|
-
log(` 凭据: ${saved.file}`);
|
|
166
|
-
if (!confirmed) log(" ⚠ 会话确认(iam/confirm)暂未成功 —— 不影响使用(调用时会自动重试确认),若持续报错请重跑 login");
|
|
167
|
-
if (allow.added.length) log(` allowlist 已补齐 ${allow.added.length} 个模型:${allow.added.join(", ")}`);
|
|
168
|
-
if (reward) {
|
|
169
|
-
const line = {
|
|
170
|
-
claimed: `🎁 已领取登录奖励 +${reward.points} 分(每号仅此一次,账单已确认入账)`,
|
|
171
|
-
already: ` 登录奖励此前已领过 —— 这家每号只有一次,不是每日签到`,
|
|
172
|
-
phantom: ` ⚠ 上游回执「成功」但账单里没有这笔 —— 实为未到账,可稍后用 -provider raccoon checkin 复核`,
|
|
173
|
-
unverified: ` 已提交领取,但账单暂时查不到,稍后用 -provider raccoon quota 核对`,
|
|
174
|
-
error: ` 领取登录奖励失败:${reward.msg || "未知原因"}`,
|
|
175
|
-
}[reward.status || raccoonClaimStatus(reward)];
|
|
176
|
-
if (line) log(line);
|
|
177
|
-
}
|
|
178
|
-
log(" 下一步: mslxdff -provider raccoon quota 查看积分 · mslxdff -provider raccoon models 查看模型(含积分倍率)");
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
1
|
+
// mslxdff -provider raccoon login — 扫码登录(终端二维码 + 轮询)。
|
|
2
|
+
// ⚠ 2026-10 上游改版:二维码必须**先由服务端签发**(POST /api/iam/v1/auth/mp/qrcode),
|
|
3
|
+
// 拿到 qrcode_code + scan_path 才能渲染。旧的「本地自造 code」在新通道下没有服务端会话,
|
|
4
|
+
// 微信扫码后查不到该码 → 手机端报「二维码已过期或已被使用」。详见 src/providers/raccoon/login.js。
|
|
5
|
+
// 该链路匿名可达且**不需要验证码**;成功后落盘 auths/raccoon-<uid>.json(0600)
|
|
6
|
+
// 并把兜底目录只增不减补齐进 allowlist(避免「登录成功却处处 403」)。
|
|
7
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
8
|
+
import { compatFetch } from "../../../compat.js";
|
|
9
|
+
import {
|
|
10
|
+
buildQrUrl,
|
|
11
|
+
cancelQrSession,
|
|
12
|
+
confirmRaccoonSession,
|
|
13
|
+
createQrSession,
|
|
14
|
+
fetchRaccoonUserInfo,
|
|
15
|
+
pollQrLogin,
|
|
16
|
+
} from "../../../providers/raccoon/login.js";
|
|
17
|
+
import { encodeQr, renderTerminal } from "../../../providers/raccoon/qr.js";
|
|
18
|
+
import { ensureRaccoonDeviceId, saveRaccoonAccount } from "../../../providers/raccoon/account-store.js";
|
|
19
|
+
import { seedRaccoonAllowlist } from "../../../providers/raccoon/models.js";
|
|
20
|
+
import { raccoonTokenFingerprint, resolveRaccoonUid } from "../../../providers/raccoon/auth.js";
|
|
21
|
+
import { claimRaccoonLoginReward, raccoonClaimStatus } from "../../../providers/raccoon/credits.js";
|
|
22
|
+
|
|
23
|
+
const LOGIN_SUBS = new Set(["login", "auth", "oauth"]);
|
|
24
|
+
|
|
25
|
+
const RELOGIN_HINT = " (可重试:mslxdff -provider raccoon login)";
|
|
26
|
+
|
|
27
|
+
export async function handleRaccoonLogin(id, sub, rest = [], deps = {}) {
|
|
28
|
+
const pid = normalizeProviderId(id) || String(id || "").toLowerCase();
|
|
29
|
+
if (pid !== "raccoon") return false;
|
|
30
|
+
// `login --password` 走手机号+密码(多账号免扫码);无该旗标仍是扫码。分派必须在
|
|
31
|
+
// LOGIN_SUBS 判定之前,且只在此处转交 —— 扫码路径本身零改动。
|
|
32
|
+
if (LOGIN_SUBS.has(sub) && rest.some((x) => {
|
|
33
|
+
const s = String(x);
|
|
34
|
+
return s === "--password" || s.startsWith("--password=") || s === "-p" || s === "--phone" || s.startsWith("--phone=");
|
|
35
|
+
})) {
|
|
36
|
+
const { handleRaccoonPasswordLogin } = await import("./raccoon-password.js");
|
|
37
|
+
return handleRaccoonPasswordLogin(id, sub, rest, deps);
|
|
38
|
+
}
|
|
39
|
+
if (!LOGIN_SUBS.has(sub)) return false;
|
|
40
|
+
|
|
41
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
42
|
+
const log = deps.log || ((m) => console.log(m));
|
|
43
|
+
const exit = deps.exit || ((code) => process.exit(code));
|
|
44
|
+
|
|
45
|
+
log("=".repeat(60));
|
|
46
|
+
log(" Raccoon 登录(商汤小浣熊 · 积分制模型池)");
|
|
47
|
+
log("=".repeat(60));
|
|
48
|
+
log("");
|
|
49
|
+
log(" 1. 用微信「扫一扫」扫描下方二维码,并在微信里点确认(浏览器直接打开会 404)");
|
|
50
|
+
log(" 2. 确认后本机自动继续(CLI 轮询,无需回调)");
|
|
51
|
+
log("");
|
|
52
|
+
|
|
53
|
+
// 签发二维码:这一步失败就没有可扫的码,必须当场说清楚,不能拿一个假码糊过去
|
|
54
|
+
let session;
|
|
55
|
+
try {
|
|
56
|
+
session = deps.qrSession || await createQrSession({ fetchImpl });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
log(`❌ 无法获取二维码:${error?.message ?? error}`);
|
|
59
|
+
log(" (多为网络不通或上游改版;可重试:mslxdff -provider raccoon login)");
|
|
60
|
+
exit(1);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
const code = deps.qrCode || session.qrcodeCode;
|
|
64
|
+
const url = deps.qrUrl || buildQrUrl(code, session.scanPath);
|
|
65
|
+
|
|
66
|
+
const art = renderTerminal(encodeQr(url));
|
|
67
|
+
if (art) {
|
|
68
|
+
log(art);
|
|
69
|
+
log("");
|
|
70
|
+
}
|
|
71
|
+
log(` 登录链接:${url}`);
|
|
72
|
+
log(` 有效期:${Math.round(session.expiresInMs / 1000)} 秒(过期请重跑本命令)`);
|
|
73
|
+
log("");
|
|
74
|
+
log("🔄 等待扫码确认…");
|
|
75
|
+
|
|
76
|
+
let out;
|
|
77
|
+
try {
|
|
78
|
+
out = await pollQrLogin(code, {
|
|
79
|
+
fetchImpl,
|
|
80
|
+
sleepFn: deps.sleepFn,
|
|
81
|
+
intervalMs: deps.intervalMs ?? session.pollIntervalMs,
|
|
82
|
+
timeoutMs: deps.timeoutMs ?? session.expiresInMs,
|
|
83
|
+
onTick: deps.onTick,
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
log("");
|
|
87
|
+
log(`❌ 登录失败:${error?.message ?? error}`);
|
|
88
|
+
log(RELOGIN_HINT);
|
|
89
|
+
exit(1);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (out.status !== "success") {
|
|
94
|
+
// 非终态失败:回收服务端会话(失败也不打断),并对每种结局给一句人话
|
|
95
|
+
await cancelQrSession(code, { fetchImpl });
|
|
96
|
+
log("");
|
|
97
|
+
if (out.status === "canceled") log("❌ 已在手机端取消登录(未写入任何凭据)");
|
|
98
|
+
else if (out.status === "expired") log("❌ 二维码已过期(上游作废了本次会话)");
|
|
99
|
+
else log("❌ 登录已超时");
|
|
100
|
+
log(RELOGIN_HINT);
|
|
101
|
+
exit(1);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return await finishRaccoonLogin({ credential: out.credential, fetchImpl, deps, log });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 登录成功后的收尾四步(**扫码与密码两条路共用,单一源**):
|
|
110
|
+
* confirm 会话 → 取用户信息 → 落盘 + 补 allowlist → 领一次性登录奖励 → 打印回执。
|
|
111
|
+
*
|
|
112
|
+
* 抽出来的理由不只是省行数:这四步的顺序有硬约束(必须先 confirm 才能用 token;领奖必须在
|
|
113
|
+
* access_token 刚签发的窗口内),任何一条路径自己重写一遍都会漏掉其中一步且难以察觉。
|
|
114
|
+
*
|
|
115
|
+
* @param {object} a
|
|
116
|
+
* @param {object} a.credential 已签发的凭据(两种登录方式同形)
|
|
117
|
+
* @param {Function} a.fetchImpl
|
|
118
|
+
* @param {object} a.deps 与原 CLI 依赖注入同形(dir/file/deviceId/saveAccount/claimReward/skipReward/env)
|
|
119
|
+
* @param {Function} a.log
|
|
120
|
+
* @param {string} [a.methodNote] 登录方式说明行(如密码登录标注手机号),纯展示
|
|
121
|
+
* @returns {Promise<boolean>} 恒 true(表示「已处理」,交还 CLI 派发链)
|
|
122
|
+
*/
|
|
123
|
+
export async function finishRaccoonLogin({ credential, fetchImpl, deps = {}, log, methodNote = "" }) {
|
|
124
|
+
// 新登录的会话是「未确认」状态:不先 POST /iam/confirm,此后所有业务端点(含下面的领奖励、
|
|
125
|
+
// 以及 daemon 的 chat)都会撞 `400 code 200901 iam_confirm_required`。登录时当场确认一次,
|
|
126
|
+
// 让落盘的凭据从一开始就是可用的;确认失败不阻断(chat.js 有懒重试兜底),只在末尾如实提示。
|
|
127
|
+
const confirmed = await confirmRaccoonSession(credential, { fetchImpl });
|
|
128
|
+
const info = await fetchRaccoonUserInfo(credential, { fetchImpl });
|
|
129
|
+
const uid = resolveRaccoonUid({
|
|
130
|
+
userId: info.userId,
|
|
131
|
+
officeIdentity: info.officeIdentity || credential.office_identity,
|
|
132
|
+
token: credential.access_token,
|
|
133
|
+
});
|
|
134
|
+
const deviceId = deps.deviceId || ensureRaccoonDeviceId({ uid, dir: deps.dir });
|
|
135
|
+
const save = deps.saveAccount || saveRaccoonAccount;
|
|
136
|
+
const saved = await save({
|
|
137
|
+
uid,
|
|
138
|
+
accessToken: credential.access_token,
|
|
139
|
+
refreshToken: credential.refresh_token || "",
|
|
140
|
+
expiresAt: credential.expires_at || "",
|
|
141
|
+
officeIdentity: info.officeIdentity || credential.office_identity || "",
|
|
142
|
+
deviceId,
|
|
143
|
+
name: info.name || "",
|
|
144
|
+
phone: info.phone || "",
|
|
145
|
+
dir: deps.dir,
|
|
146
|
+
file: deps.file,
|
|
147
|
+
});
|
|
148
|
+
const allow = seedRaccoonAllowlist({ file: deps.file });
|
|
149
|
+
|
|
150
|
+
// 一次性新手礼包只能在这儿领:此刻 access_token 刚签发(寿命约 3 小时),是全程唯一稳的窗口。
|
|
151
|
+
// 刻意不交给 daemon 定时去做 —— 那时 token 多半已过期,要续期就得和网关请求抢同一条一次性 refresh_token。
|
|
152
|
+
let reward;
|
|
153
|
+
if (!deps.skipReward) {
|
|
154
|
+
const claim = deps.claimReward || claimRaccoonLoginReward;
|
|
155
|
+
try {
|
|
156
|
+
reward = await claim({ credential, fetchImpl, env: deps.env });
|
|
157
|
+
} catch (e) {
|
|
158
|
+
reward = { status: "error", msg: String(e?.message || e).slice(0, 120) };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
log("");
|
|
163
|
+
log(`✅ 登录成功!uid=${uid} 昵称=${info.name || "(空)"} token=${raccoonTokenFingerprint(credential.access_token)}…`);
|
|
164
|
+
if (methodNote) log(methodNote);
|
|
165
|
+
log(` 凭据: ${saved.file}`);
|
|
166
|
+
if (!confirmed) log(" ⚠ 会话确认(iam/confirm)暂未成功 —— 不影响使用(调用时会自动重试确认),若持续报错请重跑 login");
|
|
167
|
+
if (allow.added.length) log(` allowlist 已补齐 ${allow.added.length} 个模型:${allow.added.join(", ")}`);
|
|
168
|
+
if (reward) {
|
|
169
|
+
const line = {
|
|
170
|
+
claimed: `🎁 已领取登录奖励 +${reward.points} 分(每号仅此一次,账单已确认入账)`,
|
|
171
|
+
already: ` 登录奖励此前已领过 —— 这家每号只有一次,不是每日签到`,
|
|
172
|
+
phantom: ` ⚠ 上游回执「成功」但账单里没有这笔 —— 实为未到账,可稍后用 -provider raccoon checkin 复核`,
|
|
173
|
+
unverified: ` 已提交领取,但账单暂时查不到,稍后用 -provider raccoon quota 核对`,
|
|
174
|
+
error: ` 领取登录奖励失败:${reward.msg || "未知原因"}`,
|
|
175
|
+
}[reward.status || raccoonClaimStatus(reward)];
|
|
176
|
+
if (line) log(line);
|
|
177
|
+
}
|
|
178
|
+
log(" 下一步: mslxdff -provider raccoon quota 查看积分 · mslxdff -provider raccoon models 查看模型(含积分倍率)");
|
|
179
|
+
return true;
|
|
180
|
+
}
|