creditdaddy 1.2.0 → 1.3.0
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/LICENSE +21 -21
- package/README.md +16 -8
- package/package.json +2 -2
- package/src/accounts.js +18 -7
- package/src/authDevice.js +3 -0
- package/src/catpawClient.js +103 -103
- package/src/catpawLocal.js +229 -229
- package/src/checkin.js +7 -2
- package/src/constants.js +4 -2
- package/src/daemon.js +73 -11
- package/src/localDetect.js +147 -147
- package/src/logger.js +60 -7
- package/src/minimaxAuth.js +127 -0
- package/src/minimaxClient.js +330 -0
- package/src/minimaxGateway.js +404 -0
- package/src/minimaxLocal.js +259 -0
- package/src/mirasimClient.js +189 -189
- package/src/panel.html +214 -29
- package/src/providers.js +26 -0
- package/src/qoderApp.js +3 -9
- package/src/qoderClient.js +237 -237
- package/src/qoderUmid.js +198 -198
- package/src/store.js +49 -6
- package/src/tenrouter.js +8 -0
- package/src/tenrouterAccounts.js +7 -0
- package/src/transfer.js +24 -1
- package/src/usageSync.js +505 -505
- package/src/winProc.js +36 -0
- package/src/workbuddyLocal.js +133 -133
- package/src/zcodeAuth.js +131 -131
- package/src/zcodeAutoClaim.js +97 -97
- package/src/zcodeClient.js +30 -13
- package/src/zcodeGateway.js +79 -33
- package/src/zcrypto.js +160 -160
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 CreditDaddy contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CreditDaddy contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# CreditDaddy
|
|
6
6
|
|
|
7
|
-
**AI 编程工具多账号本地管理 + 每日积分自动领取(领鸡蛋)助手:Qoder · WorkBuddy · ZCode · mirasim · 妙手 · Trae,集成 10Router 额度总览与用量同步**
|
|
7
|
+
**AI 编程工具多账号本地管理 + 每日积分自动领取(领鸡蛋)助手:Qoder · WorkBuddy · ZCode · mirasim · 妙手 · Trae · MiniMax,集成 10Router 额度总览与用量同步**
|
|
8
8
|
|
|
9
9
|
[](https://github.com/techysy/CreditDaddy/releases/latest)
|
|
10
10
|
[](https://github.com/techysy/CreditDaddy/actions/workflows/ci.yml)
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
## 功能
|
|
60
60
|
|
|
61
61
|
**多账号管理与切换**
|
|
62
|
-
- **仪表盘 + 分产品标签页**:首页仪表盘汇总账号总数、今日领取进度、剩余总积分、下次自动执行时间、各产品概况、待处理异常账号(token 失效 / 即将过期、领取失败)与最近领取审计;Qoder、WorkBuddy、ZCode、mirasim、妙手、Trae 各自独立标签页,卡片式展示,支持国内 / 国际版筛选,直观查看连续天数与额度包到期,自适应亮色 / 暗色主题。右上角「组件开关」可整块收起不使用的产品 / 10Router 标签页与仪表盘卡片(偏好保存在浏览器本地)。
|
|
63
|
-
- **本机导入**:一键安全读取本机已登录的客户端凭据 —— 本地解密 Qoder / Qoder CN 的 `auth.v1.dat`,读取 WorkBuddy 客户端(当前与历史会话),解密 ZCode / mirasim / 妙手 / Trae
|
|
62
|
+
- **仪表盘 + 分产品标签页**:首页仪表盘汇总账号总数、今日领取进度、剩余总积分、下次自动执行时间、各产品概况、待处理异常账号(token 失效 / 即将过期、领取失败)与最近领取审计;Qoder、WorkBuddy、ZCode、mirasim、妙手、Trae、MiniMax 各自独立标签页,卡片式展示,支持国内 / 国际版筛选,直观查看连续天数与额度包到期,自适应亮色 / 暗色主题。右上角「组件开关」可整块收起不使用的产品 / 10Router 标签页与仪表盘卡片(偏好保存在浏览器本地)。
|
|
63
|
+
- **本机导入**:一键安全读取本机已登录的客户端凭据 —— 本地解密 Qoder / Qoder CN 的 `auth.v1.dat`,读取 WorkBuddy 客户端(当前与历史会话),解密 ZCode / mirasim / 妙手 / Trae 客户端凭据,读取 MiniMax Code 的 `~/.minimax` 登录记录。敏感 Token 绝不出机器;同一用户续期时自动覆盖。
|
|
64
64
|
- **客户端一键换号**:一键将桌面客户端切换至指定账号(WorkBuddy 自动热切换;ZCode / mirasim / 妙手 / Trae 退出客户端后写回并自动重新拉起),切号前自动保存当前在线 Token,各账号拥有独立设备指纹,杜绝风控串号。
|
|
65
65
|
- **内置隐私(无痕)授权**:桌面版提供隔离的一次性会话窗口进行网页登录授权,不污染系统浏览器 Cookie,支持同平台无缝扩增多账号。
|
|
66
|
-
-
|
|
66
|
+
- **全流程浏览器登录**:支持四家产品线在面板内直接完成网页 / 设备码授权并入库 —— Qoder 设备码授权(PKCE + nonce)、WorkBuddy 状态轮询、ZCode CLI 轮询(支持智谱 BigModel 与 Z.ai)、MiniMax 官方 OAuth 设备码授权(S256 PKCE)。
|
|
67
67
|
|
|
68
68
|
**自动领取与资产保障**
|
|
69
69
|
- **每日智能轮询**:本地守护进程每 ~2 小时自动扫描执行;当日已领成功的账号自动记忆跳过,未开启活动的账号不落记忆,以每日 10:00 (UTC+8) 刷新周期为准自动重试。
|
|
@@ -117,6 +117,12 @@
|
|
|
117
117
|
- **有意不做自动续期**:Trae 的 refresh 会轮转 refreshToken 并作废 IDE 自己那份,等于把用户正在用的客户端踢下线,因此不提供自动续期。
|
|
118
118
|
- **客户端切号**:按账号存 15 项登录态快照到 `~/.creditdaddy/trae-slots/<uid>/`,换走前先自动快照当前登录避免丢号;客户端在跑时拒绝切换(Trae 会把内存里的旧登录写回文件),强制切换后自动重新拉起。`x-device-id` 必须为纯数字设备号(传 GUID 触发 `code 9074`),优先取 `storage.json` 里 `iCubeAuthInfo://icube-dc:<数字>` 的键名后缀以与 IDE 自身指纹一致,缺失时按 uid 确定性派生。
|
|
119
119
|
|
|
120
|
+
### MiniMax(MiniMax Code)
|
|
121
|
+
- **账号来源**:两种方式——① 本机导入,读取 MiniMax Code 客户端的 `~/.minimax/auth/prod/cn/mcode-public/auth.json` 当前登录记录(明文文件,token 不出机);② **浏览器登录**,走官方 OAuth 设备码授权(RFC 8628 + S256 PKCE),桌面版在内置隐私窗口打开 `account.minimax.cn/oauth-authorize` 完成授权,无需本机客户端。
|
|
122
|
+
- **额度与签到**:查询积分余额(按「免费/活动」与「购买」拆分,带套餐名与到期);每日签到领积分并累计连续天数。
|
|
123
|
+
- **刷新轮换互斥的处理**:MiniMax 的 refreshToken 是一次性轮换的,本机客户端会独立刷新。为避免 CreditDaddy 用陈旧快照刷新触发 `invalid_grant`:本机导入账号在刷新前会从 `auth.json` 重读最新 refreshToken 对齐,刷新成功后再安全回写文件(仅客户端未运行时,运行中回写会作废客户端内存里的 token 故跳过)。浏览器登录拿到的是独立 `loginEpoch` 凭据链,不与本机客户端共用,从根上消除互斥。
|
|
124
|
+
- **本地网关**:内置 Anthropic 兼容端点 `POST /gateway/minimax/v1/messages`,10Router 建 anthropic-compatible 节点指向即可把 MiniMax 当普通供应商调度;多账号轮换、401 自动刷新重试、429 冷却、SSE / JSON 流式透传、局域网 IP 白名单(与 ZCode 体验包接口同机制)。
|
|
125
|
+
|
|
120
126
|
## 快速开始
|
|
121
127
|
|
|
122
128
|
### 方式一:桌面安装版 / 便携版(推荐)
|
|
@@ -147,7 +153,7 @@ creditdaddy daemon
|
|
|
147
153
|
**CLI 常用命令**:
|
|
148
154
|
|
|
149
155
|
```bash
|
|
150
|
-
creditdaddy scan # 扫描导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 客户端登录的账号
|
|
156
|
+
creditdaddy scan # 扫描导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax 客户端登录的账号
|
|
151
157
|
creditdaddy add <token> # 添加 Qoder 国际版账号
|
|
152
158
|
creditdaddy add <token> --cn # 添加 Qoder 国内版账号
|
|
153
159
|
creditdaddy add <token> --workbuddy # 添加 WorkBuddy 国内版账号(--intl 为国际版)
|
|
@@ -194,7 +200,7 @@ creditdaddy help # 查看完整命令行帮助
|
|
|
194
200
|
| `POST` | `/api/tenrouter/sync` | 立即触发本机模型用量向 10Router 同步 |
|
|
195
201
|
| `POST` | `/api/tenrouter/sync-accounts` | 把本机账号推送到 10Router 连接 `{adminPassword?}`(OAuth / apikey 双通道) |
|
|
196
202
|
| `GET` / `POST` | `/api/qoder/umid` | 获取 / 一键安装 Linux 平台 Qoder 设备身份组件 |
|
|
197
|
-
| `GET` | `/api/logs` |
|
|
203
|
+
| `GET` | `/api/logs` | 查看环形内存日志(`?tag=MINIMAX-GW` / `?tag=ZCODE-GW` 可只看网关调用日志) |
|
|
198
204
|
| `POST` | `/api/export` / `/api/import` | 账号数据安全加密导出与导入 |
|
|
199
205
|
|
|
200
206
|
## 状态与记录说明
|
|
@@ -235,6 +241,10 @@ CreditDaddy/
|
|
|
235
241
|
│ ├── logger.js # 内存环形日志缓冲
|
|
236
242
|
│ ├── mirasimClient.js # mirasim 套餐 / 额度请求
|
|
237
243
|
│ ├── mirasimLocal.js # 本机 mirasim 凭据解密与切号
|
|
244
|
+
│ ├── minimaxAuth.js # MiniMax 浏览器登录(OAuth 设备码 + S256 PKCE)
|
|
245
|
+
│ ├── minimaxClient.js # MiniMax 用户信息 / 额度 / 签到 / Token 续期请求
|
|
246
|
+
│ ├── minimaxGateway.js # MiniMax 本地 Anthropic 兼容网关(多账号轮换)
|
|
247
|
+
│ ├── minimaxLocal.js # 本机 MiniMax 凭据读取、刷新对齐与安全回写
|
|
238
248
|
│ ├── panel.html # Web 管理面板前端
|
|
239
249
|
│ ├── providers.js # 多产品线适配器注册表
|
|
240
250
|
│ ├── qoderApp.js # 本机 Qoder 客户端探测与 safeStorage 解密
|
|
@@ -279,9 +289,7 @@ npm test
|
|
|
279
289
|
|
|
280
290
|
欢迎加入 **CreditDaddy 交流群** 交流使用体验与反馈 issue:
|
|
281
291
|
|
|
282
|
-
<div align="center">
|
|
283
292
|
<img src="assets/feishu-group-qr.jpg" width="280" alt="CreditDaddy 交流群二维码">
|
|
284
|
-
</div>
|
|
285
293
|
|
|
286
294
|
### 贡献者致谢
|
|
287
295
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "creditdaddy",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 多账号本地管理与每日积分自动签到助手",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax 多账号本地管理与每日积分自动签到助手",
|
|
5
5
|
"homepage": "https://github.com/techysy/CreditDaddy",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/accounts.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 同一 provider 下 uid 相同但 token 不同 → 视为 token 续期,原地更新而不是新增。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { normalizeAccountInput, findDuplicate, loadAccounts, withAccounts,
|
|
9
|
+
import { normalizeAccountInput, findDuplicate, loadAccounts, withAccounts, withSettings } from './store.js';
|
|
10
10
|
import { productImpl, displayNameFrom } from './providers.js';
|
|
11
11
|
|
|
12
12
|
export { displayNameFrom };
|
|
@@ -18,11 +18,16 @@ export { displayNameFrom };
|
|
|
18
18
|
async function attachPendingWebSession(target) {
|
|
19
19
|
if (!String(target.provider || '').startsWith('qoder') || !target.uid) return false;
|
|
20
20
|
if (target.meta?.qoderWebSession) return false;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// 与 daemon 写入暂存会话(/api/auth/qoder-web-session)共用 withSettings 队列,
|
|
22
|
+
// 否则两边同时「读 pending → 改 → 写回」会把对方的暂存项冲掉
|
|
23
|
+
const item = await withSettings((settings) => {
|
|
24
|
+
const pending = settings.pendingQoderWebSessions || {};
|
|
25
|
+
const found = pending[target.uid];
|
|
26
|
+
if (!found || found.kind !== target.provider || !found.cookie) return null;
|
|
27
|
+
delete pending[target.uid];
|
|
28
|
+
return found;
|
|
29
|
+
});
|
|
30
|
+
if (!item) return false;
|
|
26
31
|
target.meta = { ...(target.meta || {}), qoderWebSession: { cookie: item.cookie, capturedAt: item.capturedAt || new Date().toISOString() } };
|
|
27
32
|
return true;
|
|
28
33
|
}
|
|
@@ -37,7 +42,13 @@ function refreshExisting(existing, incoming) {
|
|
|
37
42
|
const set = (k, v) => { if (v && existing[k] !== v) { existing[k] = v; changed = true; } };
|
|
38
43
|
if (existing.token !== incoming.token) {
|
|
39
44
|
set('token', incoming.token);
|
|
40
|
-
|
|
45
|
+
// 换了 token 就必须连 refreshToken 一起换:新会话没带 refreshToken(WorkBuddy 会轮换它)时,
|
|
46
|
+
// 用 set 的真值判断会留下服务端已作废的旧 refreshToken,之后每次刷新都失败,而「重新导入」
|
|
47
|
+
// 同样清不掉它——死循环。token 变了就以新记录为准,没有就是没有。
|
|
48
|
+
if (existing.refreshToken !== (incoming.refreshToken || null)) {
|
|
49
|
+
existing.refreshToken = incoming.refreshToken || null;
|
|
50
|
+
changed = true;
|
|
51
|
+
}
|
|
41
52
|
set('expiresAt', incoming.expiresAt);
|
|
42
53
|
if (incoming.verified !== undefined) existing.verified = incoming.verified;
|
|
43
54
|
// 换上了新凭据:旧 token 留下的失败结果(如「登录已失效,请重新登录」)不再成立,
|
package/src/authDevice.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* qoder / qoder-cn Qoder 设备码登录(PKCE + nonce,与官方 qodercli 同流程,移植自 10router)
|
|
5
5
|
* workbuddy / workbuddy-intl WorkBuddy / CodeBuddy 登录(state 轮询,见 workbuddyAuth.js)
|
|
6
6
|
* zcode-bigmodel / zcode-zai ZCode 登录(客户端 CLI 轮询流程,见 zcodeAuth.js)
|
|
7
|
+
* minimax MiniMax Code 登录(OAuth 设备码 + S256 PKCE,见 minimaxAuth.js)
|
|
7
8
|
*
|
|
8
9
|
* Qoder 流程:
|
|
9
10
|
* 1. 本地生成 PKCE 对 (verifier/challenge-S256) + nonce + machine_id
|
|
@@ -26,6 +27,7 @@ import { addAccount } from './accounts.js';
|
|
|
26
27
|
import { logger } from './logger.js';
|
|
27
28
|
import { startWorkbuddyLogin, pollWorkbuddyLogin } from './workbuddyAuth.js';
|
|
28
29
|
import { startZcodeLogin, pollZcodeLogin } from './zcodeAuth.js';
|
|
30
|
+
import { startMiniMaxLogin, pollMiniMaxLogin } from './minimaxAuth.js';
|
|
29
31
|
|
|
30
32
|
const FETCH_TIMEOUT_MS = 15000;
|
|
31
33
|
const QODER_TTL_MS = 5 * 60 * 1000; // Qoder 授权链接 5 分钟有效
|
|
@@ -125,6 +127,7 @@ const FLOWS = {
|
|
|
125
127
|
'workbuddy-intl': { start: startWorkbuddyLogin, poll: pollWorkbuddyLogin },
|
|
126
128
|
'zcode-bigmodel': { start: startZcodeLogin, poll: pollZcodeLogin },
|
|
127
129
|
'zcode-zai': { start: startZcodeLogin, poll: pollZcodeLogin },
|
|
130
|
+
minimax: { start: startMiniMaxLogin, poll: pollMiniMaxLogin },
|
|
128
131
|
};
|
|
129
132
|
export const LOGIN_KINDS = Object.keys(FLOWS);
|
|
130
133
|
const KIND_LABEL = { 'zcode-bigmodel': 'ZCode(BigModel)', 'zcode-zai': 'ZCode(Z.ai)' };
|
package/src/catpawClient.js
CHANGED
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 妙手(美团 CatPaw)桌面网关 API 客户端 — 用户信息与额度/版本查询。
|
|
3
|
-
*
|
|
4
|
-
* GET https://catx.nocode.cn/api/gateway/auth/current-user 用户信息(userId, name, mobile, avatarUrl)
|
|
5
|
-
* GET https://catx.nocode.cn/api/gateway/credit/balance 可用额度 + 当前套餐(体验版/专业版…)
|
|
6
|
-
*
|
|
7
|
-
* 鉴权:本机妙手客户端解密出的 access_token,放在 X-Auth-Token 头
|
|
8
|
-
* (客户端内部 API 封装同款;网页端 credit.catpaw.meituan.com 的接口只认浏览器 Cookie,不适用)。
|
|
9
|
-
* 响应统一信封 { code: 0, message, data },code !== 0 视为业务错误。
|
|
10
|
-
*
|
|
11
|
-
* 凭据由妙手客户端登录管理(网页 OAuth 轮转),没有对外的 refresh 接口:
|
|
12
|
-
* token 失效(401)时提示在妙手客户端重新登录后重新本机导入。
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { fetchJsonRace } from './zcodeClient.js';
|
|
16
|
-
import { FETCH_TIMEOUT_MS } from './constants.js';
|
|
17
|
-
|
|
18
|
-
export const CATPAW_GATEWAY = 'https://catx.nocode.cn';
|
|
19
|
-
|
|
20
|
-
const gw = (p) => CATPAW_GATEWAY + '/api/gateway' + p;
|
|
21
|
-
const authHeaders = (token) => ({ 'X-Auth-Token': token, Accept: 'application/json' });
|
|
22
|
-
|
|
23
|
-
/** GET 网关接口并拆信封;401 → e.auth = true(凭据失效) */
|
|
24
|
-
async function gwGet(path, token) {
|
|
25
|
-
const res = await fetchJsonRace(gw(path), { headers: authHeaders(token), timeoutMs: FETCH_TIMEOUT_MS });
|
|
26
|
-
if (res.status === 401) {
|
|
27
|
-
const e = new Error('妙手登录凭据已过期,请在妙手客户端重新登录后到「添加账号 → 本机导入」同步');
|
|
28
|
-
e.auth = true;
|
|
29
|
-
throw e;
|
|
30
|
-
}
|
|
31
|
-
if (!res.ok) throw new Error(`妙手接口 ${path} 失败: HTTP ${res.status}`);
|
|
32
|
-
const body = await res.json().catch(() => null);
|
|
33
|
-
if (!body || typeof body !== 'object') throw new Error(`妙手接口 ${path} 返回无法解析`);
|
|
34
|
-
if (body.code !== 0) {
|
|
35
|
-
const msg = String(body.message || body.errorCode || '未知错误');
|
|
36
|
-
if (body.code === 401 || /未登录|登录失效/.test(msg)) {
|
|
37
|
-
const e = new Error('妙手登录凭据已过期,请在妙手客户端重新登录后重新本机导入');
|
|
38
|
-
e.auth = true;
|
|
39
|
-
throw e;
|
|
40
|
-
}
|
|
41
|
-
throw new Error(`妙手接口 ${path} 返回错误:${msg}`);
|
|
42
|
-
}
|
|
43
|
-
return body.data;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** 用户信息 → { userId, name, mobile, avatarUrl } */
|
|
47
|
-
export async function fetchCatpawProfile(token) {
|
|
48
|
-
if (!token) throw new Error('账号缺少 token');
|
|
49
|
-
const d = await gwGet('/auth/current-user', token);
|
|
50
|
-
if (!d || !d.userId) throw new Error('妙手用户信息响应缺少 userId(上游结构可能变化)');
|
|
51
|
-
return { userId: String(d.userId), name: d.name || null, mobile: d.mobile || null, avatarUrl: d.avatarUrl || null };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const iso = (ms) => (Number.isFinite(ms) && ms > 0 ? new Date(ms).toISOString() : null);
|
|
55
|
-
|
|
56
|
-
/** credit/balance → 统一配额结构(可用妙手 Credits + 当前版本;无总量口径,不计入面板「剩余积分」合计) */
|
|
57
|
-
export function normalizeCatpawBalance(d) {
|
|
58
|
-
// 注意 Number(null) === 0:字段缺失 / 为 null 都要判为空额度,而不是 0
|
|
59
|
-
const raw = d?.availableCredits;
|
|
60
|
-
const credits = raw === null || raw === undefined || raw === '' ? NaN : Number(raw);
|
|
61
|
-
const plan = d?.userPlan || {};
|
|
62
|
-
const planName = plan.planName || (plan.pro ? '专业版' : '体验版');
|
|
63
|
-
const remaining = Number.isFinite(credits) ? credits : null;
|
|
64
|
-
return {
|
|
65
|
-
total: remaining ?? 0,
|
|
66
|
-
used: 0,
|
|
67
|
-
remaining: remaining ?? 0,
|
|
68
|
-
unit: 'Credits',
|
|
69
|
-
plan: planName,
|
|
70
|
-
planExpiresAt: iso(Number(plan.expireTime)),
|
|
71
|
-
nextRefreshAt: iso(Number(plan.nextRefreshTime)),
|
|
72
|
-
parts: [{
|
|
73
|
-
name: '可用额度',
|
|
74
|
-
total: remaining ?? 0,
|
|
75
|
-
used: 0,
|
|
76
|
-
remaining: remaining ?? 0,
|
|
77
|
-
unit: 'Credits',
|
|
78
|
-
expiresAt: iso(Number(plan.nextRefreshTime)),
|
|
79
|
-
recurring: true,
|
|
80
|
-
}],
|
|
81
|
-
empty: remaining === null,
|
|
82
|
-
exceeded: plan.status ? plan.status !== 'active' : false,
|
|
83
|
-
pro: plan.pro === true,
|
|
84
|
-
autoRenew: plan.autoRenew === true,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** 查询额度与版本(一次 balance 请求即含 userPlan;401 抛 auth 错误) */
|
|
89
|
-
export async function fetchCatpawQuota(account) {
|
|
90
|
-
if (!account.token) throw new Error('账号缺少 token');
|
|
91
|
-
return normalizeCatpawBalance(await gwGet('/credit/balance', account.token));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 云端用量(按天 token 总量,无输入/输出/缓存拆分、无模型维度):
|
|
96
|
-
* GET /api/gateway/v1/usage/token/daily?startTime=&endTime= → { daily: [{ date: 'YYYY-MM-DD', totalTokens }] }
|
|
97
|
-
* 当天的桶随用随涨,调用方应只取已结束的日期,避免同一天反复快照产生重复行。
|
|
98
|
-
*/
|
|
99
|
-
export async function fetchCatpawTokenDaily(token, { startTime, endTime } = {}) {
|
|
100
|
-
if (!token) throw new Error('账号缺少 token');
|
|
101
|
-
const d = await gwGet(`/v1/usage/token/daily?startTime=${startTime}&endTime=${endTime}`, token);
|
|
102
|
-
return Array.isArray(d && d.daily) ? d.daily : [];
|
|
103
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 妙手(美团 CatPaw)桌面网关 API 客户端 — 用户信息与额度/版本查询。
|
|
3
|
+
*
|
|
4
|
+
* GET https://catx.nocode.cn/api/gateway/auth/current-user 用户信息(userId, name, mobile, avatarUrl)
|
|
5
|
+
* GET https://catx.nocode.cn/api/gateway/credit/balance 可用额度 + 当前套餐(体验版/专业版…)
|
|
6
|
+
*
|
|
7
|
+
* 鉴权:本机妙手客户端解密出的 access_token,放在 X-Auth-Token 头
|
|
8
|
+
* (客户端内部 API 封装同款;网页端 credit.catpaw.meituan.com 的接口只认浏览器 Cookie,不适用)。
|
|
9
|
+
* 响应统一信封 { code: 0, message, data },code !== 0 视为业务错误。
|
|
10
|
+
*
|
|
11
|
+
* 凭据由妙手客户端登录管理(网页 OAuth 轮转),没有对外的 refresh 接口:
|
|
12
|
+
* token 失效(401)时提示在妙手客户端重新登录后重新本机导入。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { fetchJsonRace } from './zcodeClient.js';
|
|
16
|
+
import { FETCH_TIMEOUT_MS } from './constants.js';
|
|
17
|
+
|
|
18
|
+
export const CATPAW_GATEWAY = 'https://catx.nocode.cn';
|
|
19
|
+
|
|
20
|
+
const gw = (p) => CATPAW_GATEWAY + '/api/gateway' + p;
|
|
21
|
+
const authHeaders = (token) => ({ 'X-Auth-Token': token, Accept: 'application/json' });
|
|
22
|
+
|
|
23
|
+
/** GET 网关接口并拆信封;401 → e.auth = true(凭据失效) */
|
|
24
|
+
async function gwGet(path, token) {
|
|
25
|
+
const res = await fetchJsonRace(gw(path), { headers: authHeaders(token), timeoutMs: FETCH_TIMEOUT_MS });
|
|
26
|
+
if (res.status === 401) {
|
|
27
|
+
const e = new Error('妙手登录凭据已过期,请在妙手客户端重新登录后到「添加账号 → 本机导入」同步');
|
|
28
|
+
e.auth = true;
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
31
|
+
if (!res.ok) throw new Error(`妙手接口 ${path} 失败: HTTP ${res.status}`);
|
|
32
|
+
const body = await res.json().catch(() => null);
|
|
33
|
+
if (!body || typeof body !== 'object') throw new Error(`妙手接口 ${path} 返回无法解析`);
|
|
34
|
+
if (body.code !== 0) {
|
|
35
|
+
const msg = String(body.message || body.errorCode || '未知错误');
|
|
36
|
+
if (body.code === 401 || /未登录|登录失效/.test(msg)) {
|
|
37
|
+
const e = new Error('妙手登录凭据已过期,请在妙手客户端重新登录后重新本机导入');
|
|
38
|
+
e.auth = true;
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`妙手接口 ${path} 返回错误:${msg}`);
|
|
42
|
+
}
|
|
43
|
+
return body.data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 用户信息 → { userId, name, mobile, avatarUrl } */
|
|
47
|
+
export async function fetchCatpawProfile(token) {
|
|
48
|
+
if (!token) throw new Error('账号缺少 token');
|
|
49
|
+
const d = await gwGet('/auth/current-user', token);
|
|
50
|
+
if (!d || !d.userId) throw new Error('妙手用户信息响应缺少 userId(上游结构可能变化)');
|
|
51
|
+
return { userId: String(d.userId), name: d.name || null, mobile: d.mobile || null, avatarUrl: d.avatarUrl || null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const iso = (ms) => (Number.isFinite(ms) && ms > 0 ? new Date(ms).toISOString() : null);
|
|
55
|
+
|
|
56
|
+
/** credit/balance → 统一配额结构(可用妙手 Credits + 当前版本;无总量口径,不计入面板「剩余积分」合计) */
|
|
57
|
+
export function normalizeCatpawBalance(d) {
|
|
58
|
+
// 注意 Number(null) === 0:字段缺失 / 为 null 都要判为空额度,而不是 0
|
|
59
|
+
const raw = d?.availableCredits;
|
|
60
|
+
const credits = raw === null || raw === undefined || raw === '' ? NaN : Number(raw);
|
|
61
|
+
const plan = d?.userPlan || {};
|
|
62
|
+
const planName = plan.planName || (plan.pro ? '专业版' : '体验版');
|
|
63
|
+
const remaining = Number.isFinite(credits) ? credits : null;
|
|
64
|
+
return {
|
|
65
|
+
total: remaining ?? 0,
|
|
66
|
+
used: 0,
|
|
67
|
+
remaining: remaining ?? 0,
|
|
68
|
+
unit: 'Credits',
|
|
69
|
+
plan: planName,
|
|
70
|
+
planExpiresAt: iso(Number(plan.expireTime)),
|
|
71
|
+
nextRefreshAt: iso(Number(plan.nextRefreshTime)),
|
|
72
|
+
parts: [{
|
|
73
|
+
name: '可用额度',
|
|
74
|
+
total: remaining ?? 0,
|
|
75
|
+
used: 0,
|
|
76
|
+
remaining: remaining ?? 0,
|
|
77
|
+
unit: 'Credits',
|
|
78
|
+
expiresAt: iso(Number(plan.nextRefreshTime)),
|
|
79
|
+
recurring: true,
|
|
80
|
+
}],
|
|
81
|
+
empty: remaining === null,
|
|
82
|
+
exceeded: plan.status ? plan.status !== 'active' : false,
|
|
83
|
+
pro: plan.pro === true,
|
|
84
|
+
autoRenew: plan.autoRenew === true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 查询额度与版本(一次 balance 请求即含 userPlan;401 抛 auth 错误) */
|
|
89
|
+
export async function fetchCatpawQuota(account) {
|
|
90
|
+
if (!account.token) throw new Error('账号缺少 token');
|
|
91
|
+
return normalizeCatpawBalance(await gwGet('/credit/balance', account.token));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 云端用量(按天 token 总量,无输入/输出/缓存拆分、无模型维度):
|
|
96
|
+
* GET /api/gateway/v1/usage/token/daily?startTime=&endTime= → { daily: [{ date: 'YYYY-MM-DD', totalTokens }] }
|
|
97
|
+
* 当天的桶随用随涨,调用方应只取已结束的日期,避免同一天反复快照产生重复行。
|
|
98
|
+
*/
|
|
99
|
+
export async function fetchCatpawTokenDaily(token, { startTime, endTime } = {}) {
|
|
100
|
+
if (!token) throw new Error('账号缺少 token');
|
|
101
|
+
const d = await gwGet(`/v1/usage/token/daily?startTime=${startTime}&endTime=${endTime}`, token);
|
|
102
|
+
return Array.isArray(d && d.daily) ? d.daily : [];
|
|
103
|
+
}
|