mslxdff 0.1.138 → 0.1.140
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/package.json +1 -1
- package/src/chooser.js +31 -12
- package/src/cli/commands/model/list-live.js +22 -0
- package/src/cli/commands/model/list.js +2 -0
- package/src/cli/commands/model/live-models.js +24 -0
- package/src/cli/commands/provider/codearts-login.js +45 -0
- package/src/cli/commands/provider/index.js +12 -0
- package/src/cli/commands/provider/keys.js +13 -2
- package/src/cli/commands/provider/models.js +9 -0
- package/src/cli/commands/provider/qoder-checkin.js +81 -0
- package/src/cli/commands/provider/qoder-login.js +103 -0
- package/src/cli/commands/provider/traework-login.js +202 -0
- package/src/cli/interactive.js +41 -4
- package/src/cli/provider-row.js +1 -1
- package/src/providers/classify.js +1 -1
- package/src/providers/codearts/auth-pool.js +148 -0
- package/src/providers/codearts/chat.js +176 -0
- package/src/providers/codearts/const.js +60 -0
- package/src/providers/codearts/dpop.js +95 -0
- package/src/providers/codearts/index.js +160 -0
- package/src/providers/codearts/login.js +214 -0
- package/src/providers/codearts/models.js +182 -0
- package/src/providers/codearts/sign.js +96 -0
- package/src/providers/codearts/sse.js +204 -0
- package/src/providers/codearts/stream.js +209 -0
- package/src/providers/codearts/sts.js +124 -0
- package/src/providers/codearts.js +1 -0
- package/src/providers/qoder/account-store.js +107 -0
- package/src/providers/qoder/aggregate.js +51 -0
- package/src/providers/qoder/baseprompt.json +592 -0
- package/src/providers/qoder/chat.js +72 -0
- package/src/providers/qoder/checkin.js +160 -0
- package/src/providers/qoder/constants.js +45 -0
- package/src/providers/qoder/encode.js +42 -0
- package/src/providers/qoder/fingerprint.js +39 -0
- package/src/providers/qoder/index.js +106 -0
- package/src/providers/qoder/models.js +62 -0
- package/src/providers/qoder/oauth.js +85 -0
- package/src/providers/qoder/payload.js +156 -0
- package/src/providers/qoder/request.js +26 -0
- package/src/providers/qoder/session.js +135 -0
- package/src/providers/qoder/sse.js +46 -0
- package/src/providers/qoder/stream.js +81 -0
- package/src/providers/qoder.js +2 -0
- package/src/providers/registry.js +15 -0
- package/src/providers/share-keys.js +2 -2
- package/src/providers/traework/account-store.js +149 -0
- package/src/providers/traework/chat.js +211 -0
- package/src/providers/traework/checkin.js +35 -0
- package/src/providers/traework/constants.js +21 -0
- package/src/providers/traework/errors.js +52 -0
- package/src/providers/traework/headers.js +47 -0
- package/src/providers/traework/index.js +122 -0
- package/src/providers/traework/models.js +97 -0
- package/src/providers/traework/payload.js +86 -0
- package/src/providers/traework/sse.js +184 -0
- package/src/providers/traework/token.js +52 -0
- package/src/providers/traework.js +2 -0
- package/src/routes/models-route.js +19 -1
- package/src/runtime/bootstrap.js +10 -0
- package/src/runtime/provider-gate.js +30 -0
- package/src/runtime/providers-setup.js +9 -3
- package/src/runtime/qoder-checkin.js +93 -0
- package/src/runtime/traework-checkin.js +104 -0
package/package.json
CHANGED
package/src/chooser.js
CHANGED
|
@@ -1,33 +1,52 @@
|
|
|
1
|
-
// 交互式模型选择器的纯渲染逻辑(便于测试);键盘循环在
|
|
1
|
+
// 交互式模型选择器的纯渲染逻辑(便于测试);键盘循环在 interactive.js
|
|
2
2
|
|
|
3
|
-
// items: [{ id, status?, ms?, fail?, current?, picked? }];cursor
|
|
3
|
+
// items: [{ id, status?, ms?, fail?, current?, picked? }];cursor 当前高亮行(全局下标)
|
|
4
4
|
// multi=true 时多选勾选(picked 显示 [x]/[ ]),否则单选默认模型(当前显示 ✓)
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
// index/size 可选——窗口分页渲染(只画当页 + 页头),不传 size 则全量渲染(行为不变)
|
|
6
|
+
// Note: 分页是本模块职责(键盘循环在 interactive.js),不传 size 必全量渲染 — 见 .agents/notes/implemented/feature/2026-09-21-models-paginated-chooser.md
|
|
7
|
+
export function renderChooser(items, cursor = 0, { multi = false, index = 0, size } = {}) {
|
|
8
|
+
if (size == null) return renderRows(items, cursor, 0, multi);
|
|
9
|
+
const total = items.length;
|
|
10
|
+
const sz = Math.max(1, Number(size) || 1);
|
|
11
|
+
const pages = Math.max(1, Math.ceil(total / sz));
|
|
12
|
+
const idx = Math.min(Math.max(Number(index) || 0, 0), pages - 1);
|
|
13
|
+
const start = idx * sz;
|
|
14
|
+
const rows = [`── 第 ${idx + 1}/${pages} 页 · 共 ${total} 项(←/→ 翻页) ──`];
|
|
15
|
+
rows.push(...renderRows(items.slice(start, start + sz), cursor, start, multi));
|
|
16
|
+
return rows;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderRows(slice, cursor, offset, multi) {
|
|
20
|
+
return slice.map((it, li) => {
|
|
21
|
+
const i = offset + li;
|
|
8
22
|
const arrow = i === cursor ? "❯" : " ";
|
|
9
|
-
const check = multi
|
|
10
|
-
? (it.picked ? " [✓]" : " [ ]")
|
|
11
|
-
: (it.current ? " ✓ (current)" : "");
|
|
12
23
|
let state = "";
|
|
13
24
|
if (it.fail) state = ` [fail: ${it.fail}]`;
|
|
14
25
|
else if (it.ms != null) state = ` [${it.ms}ms]`;
|
|
15
26
|
else if (it.status && it.status !== "normal") state = ` [${it.status}]`;
|
|
27
|
+
if (multi) {
|
|
28
|
+
const box = it.picked ? "[✓]" : "[ ]";
|
|
29
|
+
return `${arrow} ${box} ${it.id}${state}`;
|
|
30
|
+
}
|
|
31
|
+
const check = it.current ? " ✓ (current)" : "";
|
|
16
32
|
return `${arrow} ${it.id}${check}${state}`;
|
|
17
33
|
});
|
|
18
34
|
}
|
|
19
35
|
|
|
20
|
-
export function renderChooserHelp(multi = false) {
|
|
36
|
+
export function renderChooserHelp(multi = false, hasPages = false) {
|
|
37
|
+
const paging = hasPages ? " · ←/→ page" : "";
|
|
21
38
|
return multi
|
|
22
|
-
? ["",
|
|
23
|
-
: ["",
|
|
39
|
+
? ["", `↑/↓ move · Space toggle pick${paging} · Enter save picks · q/Esc cancel`]
|
|
40
|
+
: ["", `↑/↓ move${paging} · Enter select as default · q/Esc cancel`];
|
|
24
41
|
}
|
|
25
42
|
|
|
26
|
-
// 解析按键:返回 "up" | "down" | "enter" | "cancel" | "space" | null(忽略)
|
|
43
|
+
// 解析按键:返回 "up" | "down" | "pageup" | "pagedown" | "enter" | "cancel" | "space" | null(忽略)
|
|
27
44
|
export function parseKey(str) {
|
|
28
45
|
if (!str) return null;
|
|
29
46
|
if (str === "\x1b[A" || str === "k") return "up";
|
|
30
47
|
if (str === "\x1b[B" || str === "j") return "down";
|
|
48
|
+
if (str === "\x1b[D" || str === "h" || str === "\x1b[5~") return "pageup";
|
|
49
|
+
if (str === "\x1b[C" || str === "l" || str === "\x1b[6~") return "pagedown";
|
|
31
50
|
if (str === "\r" || str === "\n") return "enter";
|
|
32
51
|
if (str === " " || str === "\x1b[32") return "space"; // 空格(32=0x20 的 ASC 表示)或某些终端 terminator
|
|
33
52
|
if (str === "\x1b" || str === "q" || str === "\x03") return "cancel";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// 交互式 `-models` 候选合并(从 list.js 拆出以守住 ≤10KB 体积门)。
|
|
2
|
+
// allowAny 且空白名单的供应商(qoder/codearts 类)不写 allowlist,其模型只存在于
|
|
3
|
+
// daemon 聚合的 /v1/models —— 不并入这些 id,该供应商在交互列表里「一个都挑不到」。
|
|
4
|
+
import { loadProviderAllowedModels } from "../../../state.js";
|
|
5
|
+
|
|
6
|
+
/** 把「空白名单且已启用」供应商的网关 live 模型并入候选(按前缀归属,去重)。 */
|
|
7
|
+
export async function mergeLiveIds({ combinedIds, seen, knownProviders, liveIds }) {
|
|
8
|
+
const needsLive = knownProviders.filter(
|
|
9
|
+
(k) => String(k).toLowerCase() !== "opencode" && loadProviderAllowedModels(k).length === 0,
|
|
10
|
+
);
|
|
11
|
+
if (!needsLive.length) return;
|
|
12
|
+
const { fetchDaemonModelIds } = await import("./live-models.js");
|
|
13
|
+
const live = await fetchDaemonModelIds();
|
|
14
|
+
for (const id of live) {
|
|
15
|
+
if (liveIds.includes(id)) continue; // 单供应商缓存已含 → 不重复
|
|
16
|
+
const pid = String(id).includes("/") ? String(id).split("/")[0].toLowerCase() : "opencode";
|
|
17
|
+
if (!needsLive.some((k) => String(k).toLowerCase() === pid)) continue;
|
|
18
|
+
if (seen.has(id)) continue;
|
|
19
|
+
seen.add(id);
|
|
20
|
+
combinedIds.push(id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -10,6 +10,7 @@ import { pickInteractiveMulti } from "../../interactive.js";
|
|
|
10
10
|
import { buildAliasMap, readFullAliases, renderProviderList, renderFreeList, groupByProvider } from "./list-render.js";
|
|
11
11
|
import { renderOtherProviders } from "./list-providers.js";
|
|
12
12
|
import { filterStalePicks } from "../../../providers/model-id.js";
|
|
13
|
+
import { mergeLiveIds } from "./list-live.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* `-model list` 全流程:参数解析 → 刷新/回退 → provider 分支 → TTY 交互 → 分组渲染。
|
|
@@ -159,6 +160,7 @@ export async function handleModelList(args, idx, sub) {
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
}
|
|
163
|
+
await mergeLiveIds({ combinedIds, seen, knownProviders, liveIds: ids });
|
|
162
164
|
staleFilter = { knownProviders, liveIds: ids, allowedIds };
|
|
163
165
|
} catch {}
|
|
164
166
|
// provider 已不存在的 picks 孤儿不进交互列表(数据不动,status --all 仍可审计)。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// 网关 live 模型列表:allowAny 且空白名单的供应商(qoder/codearts 类)不写 allowlist,
|
|
2
|
+
// 其模型只存在于 daemon 聚合的 /v1/models 里;交互式 `-models` 必须并入这些 id,
|
|
3
|
+
// 否则该供应商「一个模型都挑不到」(用户视角:-models 什么也没有)。
|
|
4
|
+
import { compatFetch, timeoutSignal } from "../../../compat.js";
|
|
5
|
+
import { effectivePort, effectiveHost } from "../../policy.js";
|
|
6
|
+
import { loadToken } from "../../../state.js";
|
|
7
|
+
|
|
8
|
+
export async function fetchDaemonModelIds({ fetchImpl = compatFetch, port, host, token, timeoutMs = 6000 } = {}) {
|
|
9
|
+
try {
|
|
10
|
+
const p = port ?? effectivePort([]);
|
|
11
|
+
const h = host ?? effectiveHost() ?? "127.0.0.1";
|
|
12
|
+
const tk = (token ?? (await loadToken())?.token) || "";
|
|
13
|
+
const res = await fetchImpl(`http://${h}:${p}/v1/models?all=1`, {
|
|
14
|
+
headers: tk ? { Authorization: `Bearer ${tk}` } : {},
|
|
15
|
+
signal: timeoutSignal(timeoutMs),
|
|
16
|
+
});
|
|
17
|
+
if (!res?.ok) return [];
|
|
18
|
+
const json = await res.json().catch(() => ({}));
|
|
19
|
+
return (json?.data || []).map((m) => m?.id).filter(Boolean);
|
|
20
|
+
} catch {
|
|
21
|
+
// 网关没跑 / 超时 / 鉴权失败:静默降级(候选列表退回 allowlist + picks)
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// `-provider codearts login`:浏览器 PKCE 登录华为云 → 凭证 blob 落盘 providerConfigs.codearts。
|
|
2
|
+
// 约定(对齐 cline-login/workbuddy-login):不匹配返 false 交给下一个 handler;匹配即处理并退出。
|
|
3
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
4
|
+
import { defaultStateFile, loadProviderKeys, saveProviderConfig, saveProviderAllowAnyModels, loadProviderBaseUrl } from "../../../state.js";
|
|
5
|
+
import { SNAP_BASE } from "../../../providers/codearts/const.js";
|
|
6
|
+
import { runCodeartsLogin, accountToBlob } from "../../../providers/codearts/login.js";
|
|
7
|
+
import { accountFromBlob } from "../../../providers/codearts/auth-pool.js";
|
|
8
|
+
|
|
9
|
+
export async function handleCodeartsLogin(id, sub, rest = [], deps = {}) {
|
|
10
|
+
if (normalizeProviderId(id) !== "codearts") return false;
|
|
11
|
+
if (!(sub === "login" || sub === "auth" || sub === "oauth")) return false;
|
|
12
|
+
const noAllowAny = (rest || []).includes("--no-allow-any");
|
|
13
|
+
const file = deps.file || defaultStateFile();
|
|
14
|
+
const fetchImpl = deps.fetchImpl;
|
|
15
|
+
const log = deps.log || ((m) => console.log(m));
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const { account, state } = await runCodeartsLogin({ fetchImpl, log });
|
|
19
|
+
const blob = accountToBlob(account, { codeVerifier: state.codeVerifier, dpopJwk: state.dpopJwk, clientId: state.clientId });
|
|
20
|
+
// 同一账号重复登录:替换旧 blob;新账号:追加(多账号 keyring 轮转)。
|
|
21
|
+
// 去重键:userId 非空才可信;双方任一为空时退化为 refreshToken 比较
|
|
22
|
+
// (空 userId==空 userId 的误判会把第二个不同账号当同一账号替换,真机实测恒剩 1 key)
|
|
23
|
+
const cur = loadProviderKeys("codearts", { file }).filter((k) => {
|
|
24
|
+
const parsed = accountFromBlob(k);
|
|
25
|
+
if (!parsed) return false;
|
|
26
|
+
if (parsed.userId && account.userId) return parsed.userId !== account.userId;
|
|
27
|
+
return parsed.refreshToken !== account.refreshToken;
|
|
28
|
+
});
|
|
29
|
+
const baseUrl = loadProviderBaseUrl("codearts", { file }) || SNAP_BASE;
|
|
30
|
+
saveProviderConfig("codearts", { baseUrl, keys: [...cur, blob] }, { file });
|
|
31
|
+
if (!noAllowAny) saveProviderAllowAnyModels("codearts", true, { file });
|
|
32
|
+
|
|
33
|
+
log("");
|
|
34
|
+
log(`✅ codearts 登录成功:${account.userName || account.userId || "(user)"}(剩余账号数:${cur.length + 1})`);
|
|
35
|
+
log(` baseUrl: ${baseUrl}`);
|
|
36
|
+
log(` 凭证已写入 providerConfigs.codearts.keys(state.json,0600 权限目录,绝不入库)`);
|
|
37
|
+
if (!noAllowAny) log(" allowAnyModels=on(免费福利供应商默认放行全部模型;收紧:mslxdff -provider codearts allowAny off)");
|
|
38
|
+
log(" 生效:mslxdff -restart(用户终端跑),然后 mslxdff -provider codearts models 验证");
|
|
39
|
+
process.exit(0);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`❌ codearts 登录失败:${String(err?.message || err)}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
@@ -41,6 +41,9 @@ export async function handleProvider(args) {
|
|
|
41
41
|
console.error(" mslxdff -provider add myapi https://api.example.com/v1 sk-xxx gpt-4 add with allowlist");
|
|
42
42
|
console.error(" mslxdff -provider add myapi https://api.example.com/v1 sk-xxx --models-path /v1/models --chat-path /v1/chat/completions");
|
|
43
43
|
console.error(" mslxdff -provider openrouter interactive hidden input (append)");
|
|
44
|
+
console.error(" mslxdff -provider codearts login 华为云 CodeArts PKCE 授权(浏览器登录 → 凭证 blob 落盘,ADR-0027)");
|
|
45
|
+
console.error(" mslxdff -provider qoder login [--region cn|global] Qoder 设备授权(国际站默认,国内站 --region cn)");
|
|
46
|
+
console.error(" mslxdff -provider qoder checkin [--json] [--region cn|global] [--any] [--dry] 每日签到领积分(cn=daily-check-in,global=campaigns)");
|
|
44
47
|
console.error(" mslxdff -provider openrouter clear remove all keys");
|
|
45
48
|
process.exit(1);
|
|
46
49
|
}
|
|
@@ -67,6 +70,15 @@ export async function handleProvider(args) {
|
|
|
67
70
|
if (await handleClineLogin(id, sub)) return true;
|
|
68
71
|
const { handleWorkbuddyLogin } = await import("./workbuddy-login.js");
|
|
69
72
|
if (await handleWorkbuddyLogin(id, sub, rest)) return true;
|
|
73
|
+
const { handleCodeartsLogin } = await import("./codearts-login.js");
|
|
74
|
+
if (await handleCodeartsLogin(id, sub, rest)) return true;
|
|
75
|
+
const { handleTraeworkLogin, handleTraeworkCheckin } = await import("./traework-login.js");
|
|
76
|
+
if (await handleTraeworkLogin(id, sub, rest)) return true;
|
|
77
|
+
if (await handleTraeworkCheckin(id, sub, rest)) return true;
|
|
78
|
+
const { handleQoderLogin } = await import("./qoder-login.js");
|
|
79
|
+
if (await handleQoderLogin(id, sub, rest)) return true;
|
|
80
|
+
const { handleQoderCheckin } = await import("./qoder-checkin.js");
|
|
81
|
+
if (await handleQoderCheckin(id, sub, rest)) return true;
|
|
70
82
|
const { handleProviderConfig } = await import("./config.js");
|
|
71
83
|
if (await handleProviderConfig(id, sub, rest)) return true;
|
|
72
84
|
const { handleProviderAllowlist } = await import("./allowlist.js");
|
|
@@ -10,12 +10,23 @@ export async function handleProviderKeys(id, sub, rest, args) {
|
|
|
10
10
|
else console.log(`provider: ${id}${id === "openrouter" ? " (built-in baseUrl: https://openrouter.ai/api/v1)" : ""}`);
|
|
11
11
|
if (keys.length) {
|
|
12
12
|
console.log(` keys: ${keys.length} key${keys.length > 1 ? "s" : ""}`);
|
|
13
|
-
|
|
13
|
+
if (id === "codearts") {
|
|
14
|
+
const { accountFromBlob } = await import("../../../providers/codearts/auth-pool.js");
|
|
15
|
+
for (const [i, k] of keys.entries()) {
|
|
16
|
+
const a = accountFromBlob(k);
|
|
17
|
+
if (a) console.log(` [${i + 1}] user=${a.userName || "(未识别)"} uid=${a.userId ? `${a.userId.slice(0, 8)}…` : "(空,请删了重登回填)"} domain=${a.domainId ? a.domainId.slice(0, 8) + "…" : "(空)"} (${k.length} chars)`);
|
|
18
|
+
else console.log(` [${i + 1}] ${k.slice(0, 4)}…${k.slice(-4)} (${k.length} chars)`);
|
|
19
|
+
}
|
|
20
|
+
} else keys.forEach((k, i) => console.log(` [${i + 1}] ${k.slice(0, 4)}…${k.slice(-4)} (${k.length} chars)`));
|
|
14
21
|
console.log(` remove by: mslxdff -provider ${id} remove <seq> [seq...] | <key-value>`);
|
|
15
22
|
} else {
|
|
16
23
|
console.log(` keys: (no keys configured)`);
|
|
17
24
|
}
|
|
18
|
-
|
|
25
|
+
try {
|
|
26
|
+
const { shareableProviderIds } = await import("../../../providers/share-keys.js");
|
|
27
|
+
const shared = shareableProviderIds().includes(id);
|
|
28
|
+
console.log(` share keys to peers: ${shared ? "借出(随转发自动附带,ADR-0019)" : "不借出(local-only / 硬排除,codearts/cline 恒不借,ADR-0019/0015)"}`);
|
|
29
|
+
} catch { console.log(` share keys to peers: 借出(默认,key 随转发自动附带,ADR-0019)`); }
|
|
19
30
|
console.log(` allowAnyModels: ${allowAny ? "ON (empty allowlist = allow all)" : "OFF (empty allowlist = BLOCK ALL)"} (mslxdff -provider ${id} allowAny on|off)`);
|
|
20
31
|
if (allowed.length) console.log(` allowedModels: ${allowed.length} (${allowed.join(", ")}) — only these can be used`);
|
|
21
32
|
else console.log(` allowedModels: (none — ${allowAny ? "allow all" : "BLOCK ALL"}) (mslxdff -provider ${id} allowlist set <model...> or allowAny on)`);
|
|
@@ -41,6 +41,15 @@ export async function handleProviderModels(id, sub, args, rest) {
|
|
|
41
41
|
// 与聚合目录同源:recommended-models 的 free(generic 的 /api/v1/models 是全量目录,含大量不可用条目)
|
|
42
42
|
const { createClineProvider } = await import("../../../providers/cline.js");
|
|
43
43
|
provider = createClineProvider({ id, baseUrl: baseUrl || undefined, apiKeys: keys, file: defaultStateFile() });
|
|
44
|
+
} else if (id === "codearts") {
|
|
45
|
+
const { createCodeartsProvider } = await import("../../../providers/codearts.js");
|
|
46
|
+
provider = createCodeartsProvider({ baseUrl: baseUrl || undefined, apiKeys: keys, file: defaultStateFile() });
|
|
47
|
+
} else if (id === "traework") {
|
|
48
|
+
const { createTraeworkProvider } = await import("../../../providers/traework.js");
|
|
49
|
+
provider = createTraeworkProvider({ baseUrl: baseUrl || undefined, apiKeys: keys, auths, file: defaultStateFile() });
|
|
50
|
+
} else if (id === "qoder") {
|
|
51
|
+
const { createQoderProvider } = await import("../../../providers/qoder/index.js");
|
|
52
|
+
provider = createQoderProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
44
53
|
} else {
|
|
45
54
|
if (!baseUrl) {
|
|
46
55
|
console.error(`provider ${id}: missing baseUrl — set via: mslxdff -provider ${id} set-url <baseUrl>`);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// mslxdff -provider qoder checkin [--json] [--region cn|global] [--any] [--dry]
|
|
2
|
+
// 四态反馈:空(无账号→引导 login)→ 执行中(逐号打印)→ 成功 ✅ / 失败 ❌ + 下一步。
|
|
3
|
+
// 账号单一源 = auths/qoder-<uid>.json(含每号 region);device token 直接取该文件。
|
|
4
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
5
|
+
import { normalizeRegion } from "../../../providers/qoder/constants.js";
|
|
6
|
+
import { compatFetch } from "../../../compat.js";
|
|
7
|
+
|
|
8
|
+
function parseFlags(rest) {
|
|
9
|
+
const has = (...names) => names.some((n) => rest.includes(n));
|
|
10
|
+
const regionArg = (() => {
|
|
11
|
+
const i = rest.findIndex((x) => x === "--region");
|
|
12
|
+
return i >= 0 ? String(rest[i + 1] || "") : "";
|
|
13
|
+
})();
|
|
14
|
+
return {
|
|
15
|
+
json: has("--json"),
|
|
16
|
+
dry: has("--dry", "--dry-run"),
|
|
17
|
+
any: has("--any", "--promo"),
|
|
18
|
+
region: regionArg ? normalizeRegion(regionArg) : "",
|
|
19
|
+
badRegion: Boolean(regionArg) && !["cn", "global"].includes(String(regionArg).trim().toLowerCase()),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function handleQoderCheckin(id, sub, rest = [], deps = {}) {
|
|
24
|
+
if (normalizeProviderId(id) !== "qoder") return false;
|
|
25
|
+
if (sub !== "checkin" && sub !== "check-in" && sub !== "signin") return false;
|
|
26
|
+
const log = deps.log || ((m) => console.log(m));
|
|
27
|
+
const err = deps.err || ((m) => console.error(m));
|
|
28
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
29
|
+
const flags = parseFlags(rest.slice(1));
|
|
30
|
+
if (flags.badRegion) { err("usage: mslxdff -provider qoder checkin [--json] [--region cn|global] [--any] [--dry]"); if (!deps.noExit) process.exit(1); return true; }
|
|
31
|
+
|
|
32
|
+
const accounts = deps.accounts
|
|
33
|
+
|| (await import("../../../providers/qoder/account-store.js")).listAccountDocs()
|
|
34
|
+
.map((d) => ({ uid: d.uid, deviceToken: d.doc?.auth?.deviceToken || "", region: d.doc?.auth?.region || "global", name: d.doc?.account?.name || "" }));
|
|
35
|
+
const picked = flags.region ? accounts.filter((a) => normalizeRegion(a.region) === flags.region) : accounts;
|
|
36
|
+
|
|
37
|
+
// 空状态:不留白
|
|
38
|
+
if (!picked.length) {
|
|
39
|
+
log(accounts.length
|
|
40
|
+
? `qoder 签到:没有 ${flags.region} 区账号(现有 ${accounts.length} 个:${accounts.map((a) => normalizeRegion(a.region)).join("/")})`
|
|
41
|
+
: "qoder 签到:还没有账号");
|
|
42
|
+
log(" 下一步:mslxdff -provider qoder login [--region cn|global]");
|
|
43
|
+
if (!deps.noExit) process.exit(accounts.length ? 0 : 1);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { runCheckin, fetchQuota } = await import("../../../providers/qoder/checkin.js");
|
|
48
|
+
log(`Qoder 签到 · ${picked.length} 个账号(cn ${picked.filter((a) => normalizeRegion(a.region) === "cn").length} / global ${picked.filter((a) => normalizeRegion(a.region) === "global").length})${flags.dry ? " · 干跑(只查不领)" : ""}`);
|
|
49
|
+
const results = [];
|
|
50
|
+
for (let i = 0; i < picked.length; i++) {
|
|
51
|
+
const a = picked[i];
|
|
52
|
+
const tag = `${String(a.uid).slice(0, 8)}…(${normalizeRegion(a.region)})`;
|
|
53
|
+
if (!a.deviceToken) {
|
|
54
|
+
results.push({ uid: a.uid, region: normalizeRegion(a.region), ok: false, status: "no_token", message: "无 device token" });
|
|
55
|
+
log(` ❌ [${i + 1}/${picked.length}] ${tag} 无 device token → 重新 login`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
log(` ⏳ [${i + 1}/${picked.length}] ${tag} 签到中…`);
|
|
59
|
+
const r = flags.dry
|
|
60
|
+
? { ...(await runCheckin({ deviceToken: a.deviceToken, region: a.region, fetchImpl: deps.fetchImpl, allowPromo: flags.any, probeOnly: true })), dry: true }
|
|
61
|
+
: await runCheckin({ deviceToken: a.deviceToken, region: a.region, fetchImpl: deps.fetchImpl, allowPromo: flags.any });
|
|
62
|
+
const quota = await fetchQuota({ deviceToken: a.deviceToken, region: a.region, fetchImpl: deps.fetchImpl }).catch(() => ({ ok: false }));
|
|
63
|
+
r.quota = quota.ok ? `${quota.remaining}/${quota.total} ${quota.unit}${quota.exhausted ? "(已耗尽)" : ""}` : "余额不可用";
|
|
64
|
+
r.name = a.name;
|
|
65
|
+
results.push(r);
|
|
66
|
+
const icon = r.ok ? (r.status === "claimed" ? "✅" : "⚠️") : "❌";
|
|
67
|
+
const stats = r.streak || r.totalDays ? ` · 连续 ${r.streak} 天 · 累计 ${r.totalDays} 天 · 共 ${r.totalCredits} 积分` : "";
|
|
68
|
+
log(` ${icon} [${i + 1}/${picked.length}] ${tag} ${r.message}${stats} · 余额 ${r.quota}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const okCount = results.filter((r) => r.ok).length;
|
|
72
|
+
const claimed = results.filter((r) => r.status === "claimed").length;
|
|
73
|
+
if (flags.json) {
|
|
74
|
+
log(JSON.stringify({ ok: okCount, total: results.length, claimed, results }, null, 2));
|
|
75
|
+
} else {
|
|
76
|
+
log(`\n结果:${okCount}/${results.length} 正常${claimed ? ` · 新领取 ${claimed} 个` : ""}`);
|
|
77
|
+
if (claimed) log(" 积分到账可能有延迟(上游记账异步),可用 -provider qoder checkin 复查余额");
|
|
78
|
+
}
|
|
79
|
+
if (!deps.noExit) process.exit(results.some((r) => r.status === "error" || r.status === "no_token") ? 1 : 0);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
@@ -0,0 +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;
|
|
103
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// mslxdff -provider traework login — 复刻 traework2api login.sh。
|
|
2
|
+
// 随机 hex16 machine/device id → 构造 trae.cn 授权链接 → 打印 → stdin 读回调链接 →
|
|
3
|
+
// 解析 refreshToken/userInfo/userJwt → ExchangeToken → GetUserInfo → 落盘 → 自动签到+查积分。
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { compatFetch, timeoutSignal } from "../../../compat.js";
|
|
6
|
+
import { CLIENT_ID, IDE_VERSION, OAUTH_HOST } from "../../../providers/traework/constants.js";
|
|
7
|
+
import { normalizeExpiresAt } from "../../../providers/traework/token.js";
|
|
8
|
+
|
|
9
|
+
const API_HOST = OAUTH_HOST;
|
|
10
|
+
const APP_VERSION = IDE_VERSION;
|
|
11
|
+
|
|
12
|
+
function hex(n) { return randomBytes(n).toString("hex"); }
|
|
13
|
+
|
|
14
|
+
function readStdinLine(prompt) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
try { process.stdout.write(`${prompt}`); } catch {}
|
|
17
|
+
let data = "";
|
|
18
|
+
const onData = (chunk) => { data += String(chunk || ""); if (data.includes("\n")) done(); };
|
|
19
|
+
const done = () => {
|
|
20
|
+
try { process.stdin.removeListener("data", onData); } catch {}
|
|
21
|
+
try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch {}
|
|
22
|
+
try { process.stdin.pause(); } catch {}
|
|
23
|
+
resolve(data.trim());
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
process.stdin.resume();
|
|
27
|
+
process.stdin.setEncoding("utf8");
|
|
28
|
+
process.stdin.on("data", onData);
|
|
29
|
+
process.stdin.on("end", done);
|
|
30
|
+
} catch { resolve(""); }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function buildLoginUrl({ machineId, deviceId, traceId = hex(8) } = {}) {
|
|
34
|
+
const params = new URLSearchParams({
|
|
35
|
+
login_version: "1",
|
|
36
|
+
auth_from: "solo",
|
|
37
|
+
login_channel: "native_ide",
|
|
38
|
+
plugin_version: "2.3.62834",
|
|
39
|
+
auth_type: "local",
|
|
40
|
+
client_id: CLIENT_ID,
|
|
41
|
+
redirect: "0",
|
|
42
|
+
login_trace_id: traceId,
|
|
43
|
+
auth_callback_url: "http://127.0.0.1:18080/authorize",
|
|
44
|
+
machine_id: machineId,
|
|
45
|
+
device_id: deviceId,
|
|
46
|
+
x_device_id: deviceId,
|
|
47
|
+
x_machine_id: machineId,
|
|
48
|
+
x_device_brand: "PC",
|
|
49
|
+
x_device_type: "PC",
|
|
50
|
+
x_os_version: "1.0",
|
|
51
|
+
x_app_version: APP_VERSION,
|
|
52
|
+
x_app_type: "stable",
|
|
53
|
+
});
|
|
54
|
+
return `https://www.trae.cn/authorization?${params.toString()}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 解析回调链接:parse_qs + 双层 unquote 容错 → { refreshToken, userInfo, userJwt }。
|
|
58
|
+
export function parseCallbackUrl(url) {
|
|
59
|
+
let u;
|
|
60
|
+
try { u = new URL(String(url || "").trim()); } catch { throw new Error("回调链接不是合法 URL,请复制浏览器地址栏完整链接"); }
|
|
61
|
+
const qs = u.searchParams;
|
|
62
|
+
const rawRefresh = qs.get("refreshToken") || "";
|
|
63
|
+
const parseJsonParam = (raw) => {
|
|
64
|
+
if (!raw) return {};
|
|
65
|
+
for (const v of [raw, decodeURIComponent(raw)]) {
|
|
66
|
+
try { const o = JSON.parse(v); if (o && typeof o === "object") return o; } catch {}
|
|
67
|
+
}
|
|
68
|
+
return {};
|
|
69
|
+
};
|
|
70
|
+
const userInfo = parseJsonParam(qs.get("userInfo") || "");
|
|
71
|
+
const userJwt = parseJsonParam(qs.get("userJwt") || "");
|
|
72
|
+
let refreshToken = rawRefresh || String(userJwt.RefreshToken || "");
|
|
73
|
+
return {
|
|
74
|
+
refreshToken,
|
|
75
|
+
userInfo: { uid: String(userInfo.UserID || ""), nickname: String(userInfo.ScreenName || ""), enterpriseId: String(userInfo.TenantID || "") },
|
|
76
|
+
userJwt: { token: String(userJwt.Token || ""), refreshToken: String(userJwt.RefreshToken || ""), expiresAt: Number(userJwt.TokenExpireAt) || 0 },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function postJson(fetchImpl, url, obj, headers = {}) {
|
|
81
|
+
const res = await fetchImpl(url, { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": `Trae/${APP_VERSION}`, ...headers }, body: JSON.stringify(obj), signal: timeoutSignal(30000) });
|
|
82
|
+
const txt = await res.text();
|
|
83
|
+
let j;
|
|
84
|
+
try { j = JSON.parse(txt); } catch { throw new Error(`上游返回非 JSON: ${txt.slice(0, 200)}`); }
|
|
85
|
+
if (res.status >= 400) throw new Error(`上游 HTTP ${res.status}: ${txt.slice(0, 200)}`);
|
|
86
|
+
return j;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function handleTraeworkLogin(id, sub, rest = [], deps = {}) {
|
|
90
|
+
if (String(id || "").toLowerCase() !== "traework") return false;
|
|
91
|
+
if (sub !== "login" && sub !== "auth" && sub !== "oauth") return false;
|
|
92
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
93
|
+
const log = deps.log || ((m) => console.log(m));
|
|
94
|
+
const machineId = hex(16);
|
|
95
|
+
const deviceId = hex(16);
|
|
96
|
+
const loginUrl = buildLoginUrl({ machineId, deviceId });
|
|
97
|
+
log("=".repeat(60));
|
|
98
|
+
log(" TRAE SOLO 登录");
|
|
99
|
+
log("=".repeat(60));
|
|
100
|
+
log("");
|
|
101
|
+
log(" 1. 在浏览器打开下面链接,用手机号/验证码登录");
|
|
102
|
+
log(" 2. 登录成功后浏览器会跳到打不开的 127.0.0.1 地址");
|
|
103
|
+
log(" 3. 复制浏览器地址栏的完整链接,粘贴到下面");
|
|
104
|
+
log("");
|
|
105
|
+
log(` ${loginUrl}`);
|
|
106
|
+
log("");
|
|
107
|
+
let callback;
|
|
108
|
+
if (deps.callbackUrl) callback = deps.callbackUrl;
|
|
109
|
+
else callback = await readStdinLine("登录完成后,请粘贴浏览器地址栏的完整回调链接: ");
|
|
110
|
+
if (!callback || !String(callback).trim()) { console.error("未输入回调链接,已取消"); process.exit(1); }
|
|
111
|
+
const parsed = parseCallbackUrl(callback);
|
|
112
|
+
let token = "";
|
|
113
|
+
let refreshToken = parsed.refreshToken;
|
|
114
|
+
let expiresAt = 0;
|
|
115
|
+
if (refreshToken) {
|
|
116
|
+
const resp = await postJson(fetchImpl, `${API_HOST}/cloudide/api/v3/trae/oauth/ExchangeToken`, { ClientID: CLIENT_ID, RefreshToken: refreshToken, ClientSecret: "-", UserID: "" });
|
|
117
|
+
const result = resp?.Result || {};
|
|
118
|
+
token = result.Token || "";
|
|
119
|
+
if (!token) { console.error(`ExchangeToken 失败: ${JSON.stringify(resp).slice(0, 300)}`); process.exit(1); }
|
|
120
|
+
refreshToken = result.RefreshToken || refreshToken;
|
|
121
|
+
expiresAt = normalizeExpiresAt(result.TokenExpireAt || 0);
|
|
122
|
+
if (!(expiresAt > Math.floor(Date.now() / 1000))) expiresAt = Math.floor(Date.now() / 1000) + (Number(result.TokenExpireDuration) || 1209600);
|
|
123
|
+
log("ExchangeToken 成功");
|
|
124
|
+
} else {
|
|
125
|
+
token = parsed.userJwt.token;
|
|
126
|
+
expiresAt = normalizeExpiresAt(parsed.userJwt.expiresAt);
|
|
127
|
+
if (!token) { console.error("回调链接缺少 refreshToken,且 userJwt 也没有 Token"); process.exit(1); }
|
|
128
|
+
log("无 refreshToken,使用 userJwt 的 Token 兜底");
|
|
129
|
+
}
|
|
130
|
+
let uid = parsed.userInfo.uid;
|
|
131
|
+
let nickname = parsed.userInfo.nickname;
|
|
132
|
+
let enterpriseId = parsed.userInfo.enterpriseId;
|
|
133
|
+
try {
|
|
134
|
+
const ui = await postJson(fetchImpl, `${API_HOST}/cloudide/api/v3/trae/GetUserInfo`, { ReqSource: "IDE", IDEVersion: APP_VERSION }, { "x-cloudide-token": token });
|
|
135
|
+
const u = ui?.Result || ui || {};
|
|
136
|
+
if (u.UserID) { uid = String(u.UserID); nickname = String(u.ScreenName || nickname); enterpriseId = String(u.EnterpriseID || enterpriseId); }
|
|
137
|
+
} catch (e) { log(`GetUserInfo 失败(使用回调 userInfo): ${e.message}`); }
|
|
138
|
+
if (!uid) { console.error("未能获取 uid,请检查 token 是否有效"); process.exit(1); }
|
|
139
|
+
const { saveTraeworkAccount } = await import("../../../providers/traework/account-store.js");
|
|
140
|
+
const save = deps.saveAccount || saveTraeworkAccount;
|
|
141
|
+
const saved = await save({ uid, enterpriseId, nickname, accessToken: token, refreshToken, expiresAt, domain: "trae.cn", apiHost: API_HOST, machineId, deviceId });
|
|
142
|
+
// 自动签到 + 查积分
|
|
143
|
+
try {
|
|
144
|
+
const { checkinStatus, checkinClaim, entUsage } = await import("../../../providers/traework/checkin.js");
|
|
145
|
+
const cred = { accessToken: token, deviceId };
|
|
146
|
+
const st = await checkinStatus({ cred, fetchImpl });
|
|
147
|
+
log(`签到状态: checked_in=${st.checkedIn} credits=${st.credits} enable=${st.enable}`);
|
|
148
|
+
if (!st.checkedIn && st.enable) {
|
|
149
|
+
await checkinClaim({ cred, fetchImpl });
|
|
150
|
+
log("签到: success");
|
|
151
|
+
}
|
|
152
|
+
const ent = await entUsage({ cred, fetchImpl });
|
|
153
|
+
log(`当前积分: ${ent.remain}`);
|
|
154
|
+
} catch (e) { log(`签到/查积分: ${String(e?.message || e).slice(0, 200)}`); }
|
|
155
|
+
log("");
|
|
156
|
+
log("=".repeat(60));
|
|
157
|
+
log(" 登录完成!");
|
|
158
|
+
log(` UID: ${uid}`);
|
|
159
|
+
log(` Nickname: ${nickname || "(未获取到)"}`);
|
|
160
|
+
log(` 账号数: ${saved.accounts}`);
|
|
161
|
+
log("=".repeat(60));
|
|
162
|
+
log("下一步:mslxdff -restart(用户终端跑),然后 mslxdff -provider traework models 验证");
|
|
163
|
+
if (!deps.noExit) process.exit(0);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 手动签到:-provider traework checkin
|
|
168
|
+
export async function handleTraeworkCheckin(id, sub, rest = [], deps = {}) {
|
|
169
|
+
if (String(id || "").toLowerCase() !== "traework") return false;
|
|
170
|
+
if (sub !== "checkin") return false;
|
|
171
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
172
|
+
const log = deps.log || ((m) => console.log(m));
|
|
173
|
+
const { loadProviderConfig, loadProviderKeys, defaultStateFile } = await import("../../../state.js");
|
|
174
|
+
const cfg = loadProviderConfig("traework");
|
|
175
|
+
const keys = deps.keys || loadProviderKeys("traework");
|
|
176
|
+
const authList = deps.auths || cfg?.auths || [];
|
|
177
|
+
if (!keys.length || !authList.length) { console.error("没有 traework 账号,请先 mslxdff -provider traework login"); process.exit(1); }
|
|
178
|
+
const { checkinStatus, checkinClaim, entUsage } = await import("../../../providers/traework/checkin.js");
|
|
179
|
+
for (let i = 0; i < Math.min(keys.length, authList.length); i++) {
|
|
180
|
+
const auth = authList[i];
|
|
181
|
+
const at = keys[i];
|
|
182
|
+
if (!auth?.uid || !at) continue;
|
|
183
|
+
const cred = { accessToken: at, deviceId: auth?.deviceId || "", uid: auth.uid };
|
|
184
|
+
log(`账号 ${auth.uid.slice(0, 8)}...`);
|
|
185
|
+
try {
|
|
186
|
+
const st = await checkinStatus({ cred, fetchImpl });
|
|
187
|
+
log(` 签到状态: checked_in=${st.checkedIn} credits=${st.credits} enable=${st.enable}`);
|
|
188
|
+
if (!st.checkedIn && st.enable) {
|
|
189
|
+
await checkinClaim({ cred, fetchImpl });
|
|
190
|
+
log(` 签到: success`);
|
|
191
|
+
const st2 = await checkinStatus({ cred, fetchImpl });
|
|
192
|
+
log(` 签到后积分: ${st2.credits}`);
|
|
193
|
+
}
|
|
194
|
+
const ent = await entUsage({ cred, fetchImpl });
|
|
195
|
+
log(` 总积分: ${ent.remain}`);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
log(` 失败: ${String(e?.message || e).slice(0, 120)}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!deps.noExit) process.exit(0);
|
|
201
|
+
return true;
|
|
202
|
+
}
|