creditdaddy 1.2.0 → 1.3.2

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 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
  [![Release](https://img.shields.io/github/v/release/techysy/CreditDaddy?label=%E7%89%88%E6%9C%AC&color=2563eb)](https://github.com/techysy/CreditDaddy/releases/latest)
10
10
  [![CI](https://img.shields.io/github/actions/workflow/status/techysy/CreditDaddy/ci.yml?branch=main&label=CI)](https://github.com/techysy/CreditDaddy/actions/workflows/ci.yml)
@@ -16,7 +16,10 @@
16
16
 
17
17
  [下载](#下载) · [架构](#%EF%B8%8F-架构) · [功能](#功能) · [10Router 集成](#10router-集成) · [产品线说明](#产品线说明) · [快速开始](#快速开始) · [HTTP API](#本地-http-api) · [相关项目](#-相关项目) · [贡献者致谢](#-贡献者致谢) · [许可证](#-许可证)
18
18
 
19
- <img src="docs/screenshot-dashboard.png" width="860" alt="CreditDaddy 仪表盘">
19
+ <picture>
20
+ <source media="(prefers-color-scheme: dark)" srcset="docs/screenshot-dashboard-dark.png">
21
+ <img src="docs/screenshot-dashboard-light.png" width="860" alt="CreditDaddy 仪表盘">
22
+ </picture>
20
23
 
21
24
  </div>
22
25
 
@@ -59,11 +62,11 @@
59
62
  ## 功能
60
63
 
61
64
  **多账号管理与切换**
62
- - **仪表盘 + 分产品标签页**:首页仪表盘汇总账号总数、今日领取进度、剩余总积分、下次自动执行时间、各产品概况、待处理异常账号(token 失效 / 即将过期、领取失败)与最近领取审计;Qoder、WorkBuddy、ZCode、mirasim、妙手、Trae 各自独立标签页,卡片式展示,支持国内 / 国际版筛选,直观查看连续天数与额度包到期,自适应亮色 / 暗色主题。右上角「组件开关」可整块收起不使用的产品 / 10Router 标签页与仪表盘卡片(偏好保存在浏览器本地)。
63
- - **本机导入**:一键安全读取本机已登录的客户端凭据 —— 本地解密 Qoder / Qoder CN 的 `auth.v1.dat`,读取 WorkBuddy 客户端(当前与历史会话),解密 ZCode / mirasim / 妙手 / Trae 客户端凭据。敏感 Token 绝不出机器;同一用户续期时自动覆盖。
65
+ - **仪表盘 + 分产品标签页**:首页仪表盘汇总账号总数、今日领取进度、剩余总积分、下次自动执行时间、各产品概况、待处理异常账号(token 失效 / 即将过期、领取失败)与最近领取审计;Qoder、WorkBuddy、ZCode、mirasim、妙手、Trae、MiniMax 各自独立标签页,卡片式展示,支持国内 / 国际版筛选,直观查看连续天数与额度包到期,自适应亮色 / 暗色主题。右上角「组件开关」可整块收起不使用的产品 / 10Router 标签页与仪表盘卡片(偏好保存在浏览器本地)。
66
+ - **本机导入**:一键安全读取本机已登录的客户端凭据 —— 本地解密 Qoder / Qoder CN 的 `auth.v1.dat`,读取 WorkBuddy 客户端(当前与历史会话),解密 ZCode / mirasim / 妙手 / Trae 客户端凭据,读取 MiniMax Code 的 `~/.minimax` 登录记录。敏感 Token 绝不出机器;同一用户续期时自动覆盖。
64
67
  - **客户端一键换号**:一键将桌面客户端切换至指定账号(WorkBuddy 自动热切换;ZCode / mirasim / 妙手 / Trae 退出客户端后写回并自动重新拉起),切号前自动保存当前在线 Token,各账号拥有独立设备指纹,杜绝风控串号。
65
68
  - **内置隐私(无痕)授权**:桌面版提供隔离的一次性会话窗口进行网页登录授权,不污染系统浏览器 Cookie,支持同平台无缝扩增多账号。
66
- - **全流程浏览器登录**:支持三家产品线在面板内直接完成网页 / 设备码授权并入库 —— Qoder 设备码授权(PKCE + nonce)、WorkBuddy 状态轮询、ZCode CLI 轮询(支持智谱 BigModel 与 Z.ai)。
69
+ - **全流程浏览器登录**:支持四家产品线在面板内直接完成网页 / 设备码授权并入库 —— Qoder 设备码授权(PKCE + nonce)、WorkBuddy 状态轮询、ZCode CLI 轮询(支持智谱 BigModel 与 Z.ai)、MiniMax 官方 OAuth 设备码授权(S256 PKCE)。
67
70
 
68
71
  **自动领取与资产保障**
69
72
  - **每日智能轮询**:本地守护进程每 ~2 小时自动扫描执行;当日已领成功的账号自动记忆跳过,未开启活动的账号不落记忆,以每日 10:00 (UTC+8) 刷新周期为准自动重试。
@@ -117,6 +120,12 @@
117
120
  - **有意不做自动续期**:Trae 的 refresh 会轮转 refreshToken 并作废 IDE 自己那份,等于把用户正在用的客户端踢下线,因此不提供自动续期。
118
121
  - **客户端切号**:按账号存 15 项登录态快照到 `~/.creditdaddy/trae-slots/<uid>/`,换走前先自动快照当前登录避免丢号;客户端在跑时拒绝切换(Trae 会把内存里的旧登录写回文件),强制切换后自动重新拉起。`x-device-id` 必须为纯数字设备号(传 GUID 触发 `code 9074`),优先取 `storage.json` 里 `iCubeAuthInfo://icube-dc:<数字>` 的键名后缀以与 IDE 自身指纹一致,缺失时按 uid 确定性派生。
119
122
 
123
+ ### MiniMax(MiniMax Code)
124
+ - **账号来源**:两种方式——① 本机导入,读取 MiniMax Code 客户端的 `~/.minimax/auth/prod/cn/mcode-public/auth.json` 当前登录记录(明文文件,token 不出机);② **浏览器登录**,走官方 OAuth 设备码授权(RFC 8628 + S256 PKCE),桌面版在内置隐私窗口打开 `account.minimax.cn/oauth-authorize` 完成授权,无需本机客户端。
125
+ - **额度与签到**:查询积分余额(按「免费/活动」与「购买」拆分,带套餐名与到期);每日签到领积分并累计连续天数。
126
+ - **刷新轮换互斥的处理**:MiniMax 的 refreshToken 是一次性轮换的,本机客户端会独立刷新。为避免 CreditDaddy 用陈旧快照刷新触发 `invalid_grant`:本机导入账号在刷新前会从 `auth.json` 重读最新 refreshToken 对齐,刷新成功后再安全回写文件(仅客户端未运行时,运行中回写会作废客户端内存里的 token 故跳过)。浏览器登录拿到的是独立 `loginEpoch` 凭据链,不与本机客户端共用,从根上消除互斥。
127
+ - **本地网关**:内置 Anthropic 兼容端点 `POST /gateway/minimax/v1/messages`,10Router 建 anthropic-compatible 节点指向即可把 MiniMax 当普通供应商调度;多账号轮换、401 自动刷新重试、429 冷却、SSE / JSON 流式透传、局域网 IP 白名单(与 ZCode 体验包接口同机制)。
128
+
120
129
  ## 快速开始
121
130
 
122
131
  ### 方式一:桌面安装版 / 便携版(推荐)
@@ -140,14 +149,33 @@
140
149
  # 全局安装(npm)
141
150
  npm i -g creditdaddy
142
151
 
143
- # 启动后台守护进程(默认访问 http://127.0.0.1:47860)
152
+ # 后台启动(关掉终端也继续跑,默认 http://127.0.0.1:47860)
153
+ creditdaddy start
154
+
155
+ # 或者前台跑(Ctrl+C 停止,调试用)
144
156
  creditdaddy daemon
145
157
  ```
146
158
 
159
+ **后台运行**(CLI 用户用这个,不需要一直开着终端):
160
+
161
+ ```bash
162
+ creditdaddy start # 后台启动;已在运行则直接报出地址,不会起第二个
163
+ creditdaddy status # 查看运行状态(未运行时退出码 1,便于脚本判断)
164
+ creditdaddy stop # 停止(没在跑也算成功)
165
+ creditdaddy restart # 重启
166
+ ```
167
+
168
+ `start` 会把实例拉到独立进程组,终端窗口关掉、SSH 断开都不影响每日自动签到。运行信息记在
169
+ `<数据目录>/daemon.json`,日志在 `<数据目录>/logs/background.log`(按日归档的运行日志另在
170
+ `daemon-<日期>.log`)。`start` / `stop` 都是幂等的,脚本里可以放心串着写。
171
+
172
+ > 想要**托盘图标**和**开机自启**请用方式一的桌面版;CLI 这条路只做后台守护,不装任何原生依赖。
173
+ > 也可以把 `creditdaddy start` 挂到系统服务 / 任务计划程序上实现开机自启。
174
+
147
175
  **CLI 常用命令**:
148
176
 
149
177
  ```bash
150
- creditdaddy scan # 扫描导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 客户端登录的账号
178
+ creditdaddy scan # 扫描导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax 客户端登录的账号
151
179
  creditdaddy add <token> # 添加 Qoder 国际版账号
152
180
  creditdaddy add <token> --cn # 添加 Qoder 国内版账号
153
181
  creditdaddy add <token> --workbuddy # 添加 WorkBuddy 国内版账号(--intl 为国际版)
@@ -194,7 +222,7 @@ creditdaddy help # 查看完整命令行帮助
194
222
  | `POST` | `/api/tenrouter/sync` | 立即触发本机模型用量向 10Router 同步 |
195
223
  | `POST` | `/api/tenrouter/sync-accounts` | 把本机账号推送到 10Router 连接 `{adminPassword?}`(OAuth / apikey 双通道) |
196
224
  | `GET` / `POST` | `/api/qoder/umid` | 获取 / 一键安装 Linux 平台 Qoder 设备身份组件 |
197
- | `GET` | `/api/logs` | 查看环形内存日志 |
225
+ | `GET` | `/api/logs` | 查看环形内存日志(`?tag=MINIMAX-GW` / `?tag=ZCODE-GW` 可只看网关调用日志) |
198
226
  | `POST` | `/api/export` / `/api/import` | 账号数据安全加密导出与导入 |
199
227
 
200
228
  ## 状态与记录说明
@@ -235,6 +263,10 @@ CreditDaddy/
235
263
  │ ├── logger.js # 内存环形日志缓冲
236
264
  │ ├── mirasimClient.js # mirasim 套餐 / 额度请求
237
265
  │ ├── mirasimLocal.js # 本机 mirasim 凭据解密与切号
266
+ │ ├── minimaxAuth.js # MiniMax 浏览器登录(OAuth 设备码 + S256 PKCE)
267
+ │ ├── minimaxClient.js # MiniMax 用户信息 / 额度 / 签到 / Token 续期请求
268
+ │ ├── minimaxGateway.js # MiniMax 本地 Anthropic 兼容网关(多账号轮换)
269
+ │ ├── minimaxLocal.js # 本机 MiniMax 凭据读取、刷新对齐与安全回写
238
270
  │ ├── panel.html # Web 管理面板前端
239
271
  │ ├── providers.js # 多产品线适配器注册表
240
272
  │ ├── qoderApp.js # 本机 Qoder 客户端探测与 safeStorage 解密
@@ -279,9 +311,7 @@ npm test
279
311
 
280
312
  欢迎加入 **CreditDaddy 交流群** 交流使用体验与反馈 issue:
281
313
 
282
- <div align="center">
283
314
  <img src="assets/feishu-group-qr.jpg" width="280" alt="CreditDaddy 交流群二维码">
284
- </div>
285
315
 
286
316
  ### 贡献者致谢
287
317
 
@@ -8,6 +8,8 @@
8
8
  * creditdaddy remove <id> 删除账号
9
9
  * creditdaddy export/import 导出/导入账号
10
10
  * creditdaddy logs 查看领取状态(state.json)
11
+ * creditdaddy start/stop 后台运行(关掉终端不影响每日签到)
12
+ * creditdaddy status 查看后台运行状态
11
13
  * creditdaddy help 显示帮助
12
14
  */
13
15
 
@@ -16,6 +18,7 @@ import { logger } from '../src/logger.js';
16
18
 
17
19
  const args = process.argv.slice(2);
18
20
  const cmd = args[0] || 'daemon';
21
+ const DEFAULT_BG_PORT = 47860; // 与 src/daemon.js 的 DEFAULT_PORT 一致
19
22
 
20
23
  const HELP = `CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 多账号管理 + 每日积分自动领取
21
24
 
@@ -31,6 +34,10 @@ const HELP = `CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Tra
31
34
  creditdaddy import <file.json> [--password 口令] 导入 CreditDaddy / 10router 导出文件
32
35
  creditdaddy scan 导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 客户端已登录的账号
33
36
  creditdaddy umid [install|remove] Qoder 设备身份组件(Linux / fnOS 国际版领取用)
37
+ creditdaddy start [--port 47860] [--host 127.0.0.1] 后台启动(关掉终端也继续跑)
38
+ creditdaddy stop 停止后台实例
39
+ creditdaddy status 查看后台运行状态
40
+ creditdaddy restart 重启后台实例
34
41
  creditdaddy logs 查看领取状态
35
42
  creditdaddy help 显示本帮助`;
36
43
 
@@ -39,15 +46,137 @@ function flag(name) {
39
46
  return i >= 0 ? args[i + 1] : undefined;
40
47
  }
41
48
 
49
+ /** 面板访问地址(只监听回环时用 127.0.0.1,0.0.0.0 不能当访问地址用) */
50
+ function panelUrl(host, port) {
51
+ const h = host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host;
52
+ return `http://${h}:${port}`;
53
+ }
54
+
55
+ /** 把运行时长说成人话(status 用) */
56
+ function humanUptime(ms) {
57
+ if (!Number.isFinite(ms) || ms < 0) return '未知';
58
+ const min = Math.floor(ms / 60000);
59
+ if (min < 1) return '不到 1 分钟';
60
+ if (min < 60) return `${min} 分钟`;
61
+ const h = Math.floor(min / 60);
62
+ if (h < 24) return `${h} 小时 ${min % 60} 分`;
63
+ return `${Math.floor(h / 24)} 天 ${h % 24} 小时`;
64
+ }
65
+
66
+ /**
67
+ * 优雅退出。之前 src/ 里一个 process.on 都没有,Ctrl+C 是 Node 默认的硬杀:
68
+ * 状态文件留在原地(下次 start 会当成「已在运行」)、当天的日志既不落盘也不归档。
69
+ */
70
+ function installShutdown({ server, background }) {
71
+ let closing = false;
72
+ const shutdown = async (signal) => {
73
+ if (closing) return;
74
+ closing = true;
75
+ try { logger.info('CLI', `收到 ${signal},正在退出…`); } catch {}
76
+ try { (await import('../src/checkin.js')).stopScheduler(); } catch {}
77
+ try { await server?.close(); } catch {}
78
+ if (background) {
79
+ // 只删自己写的状态文件:并发跑着第二个实例时别把它的状态抹掉
80
+ try { const { clearRuntime } = await import('../src/bgdaemon.js'); await clearRuntime(process.pid); } catch {}
81
+ }
82
+ // 等归档流真的 close(gzip 完成)再退,否则今天的日志丢了
83
+ try { await (await import('../src/logger.js')).closeArchiveStream(); } catch {}
84
+ process.exit(0);
85
+ };
86
+ process.on('SIGINT', () => shutdown('SIGINT'));
87
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
88
+ // 关掉终端会发 SIGHUP。后台实例(stdout 不是 TTY)本来就该在终端消失后活着,
89
+ // 照着这个忽略;前台则当作用户要收摊。
90
+ process.on('SIGHUP', () => { if (process.stdout.isTTY) shutdown('SIGHUP'); });
91
+ }
92
+
42
93
  async function main() {
43
94
  switch (cmd) {
44
95
  case 'daemon': {
45
96
  const port = Number(flag('--port')) || Number(process.env.PORT) || undefined;
46
97
  const host = flag('--host') || process.env.HOST || undefined;
47
- await startDaemon(port, host);
98
+ const { server, port: boundPort } = await startDaemon(port, host);
99
+ // 端口被占时 daemon 会自动 +1 重试,真正在听的是 boundPort;
100
+ // 后台模式要把它记进状态文件,start/status 打印的必须是这个值
101
+ const background = process.env.CREDITDADDY_BG === '1';
102
+ if (background) {
103
+ const { APP_VERSION } = await import('../src/constants.js');
104
+ const { writeRuntime } = await import('../src/bgdaemon.js');
105
+ await writeRuntime({
106
+ pid: process.pid,
107
+ port: boundPort,
108
+ host: host || '127.0.0.1',
109
+ startedAt: new Date().toISOString(),
110
+ version: APP_VERSION,
111
+ });
112
+ logger.info('CLI', '已在后台运行');
113
+ }
48
114
  const { startScheduler } = await import('../src/checkin.js');
49
115
  startScheduler();
50
- logger.info('CLI', '按 Ctrl+C 停止');
116
+ installShutdown({ server, background });
117
+ logger.info('CLI', background ? '按 creditdaddy stop 停止' : '按 Ctrl+C 停止');
118
+ break;
119
+ }
120
+ case 'start': {
121
+ const { startBackground, backgroundLogFile } = await import('../src/bgdaemon.js');
122
+ const { APP_VERSION } = await import('../src/constants.js');
123
+ const port = Number(flag('--port')) || Number(process.env.PORT) || DEFAULT_BG_PORT;
124
+ const host = flag('--host') || '127.0.0.1';
125
+ console.log('正在后台启动 CreditDaddy…');
126
+ const r = await startBackground({ port, host });
127
+ if (r.error) { console.error('✗', r.error); process.exit(1); }
128
+ const rec = r.rec;
129
+ console.log(r.already ? '✓ 已在运行' : '✓ 已启动');
130
+ console.log(' 面板:', panelUrl(rec.host, rec.port));
131
+ console.log(' 进程:pid', rec.pid, '· 端口', rec.port, '· 版本', rec.version || APP_VERSION);
132
+ console.log(' 日志:', backgroundLogFile());
133
+ console.log(' 停止:creditdaddy stop');
134
+ break;
135
+ }
136
+ case 'stop': {
137
+ const { stopDaemon } = await import('../src/bgdaemon.js');
138
+ const r = await stopDaemon();
139
+ // 没在跑也算成功:脚本里 stop 之后紧接 start 不该因为「本来就没跑」而中断
140
+ if (!r.stopped) {
141
+ console.log(r.stale ? '· 清理了残留状态(进程已不在)' : '· 未在运行');
142
+ break;
143
+ }
144
+ console.log('✓ 已停止(pid ' + r.rec.pid + ')');
145
+ break;
146
+ }
147
+ case 'status': {
148
+ const { statusRuntime } = await import('../src/bgdaemon.js');
149
+ const s = await statusRuntime();
150
+ if (!s.running) {
151
+ console.log('· 未在运行');
152
+ if (s.reason === 'no-state') {
153
+ console.log(' 启动:creditdaddy start');
154
+ } else if (s.pidAlive) {
155
+ // 进程在但端口不应答:多半卡在 listen 之前,或状态文件指向了旧实例
156
+ console.log(' 进程 pid ' + s.pid + ' 还在,但面板没有应答;建议 creditdaddy stop 后重启');
157
+ }
158
+ process.exit(1);
159
+ }
160
+ const startedMs = s.startedAt ? Date.now() - Date.parse(s.startedAt) : NaN;
161
+ console.log('✓ 运行中');
162
+ console.log(' 面板:', panelUrl(s.host, s.port));
163
+ console.log(' 进程:pid', s.pid, '· 端口', s.port, '· 版本', s.probe?.version || s.version || '未知');
164
+ console.log(' 已运行:', humanUptime(startedMs));
165
+ if (s.probe?.needsKey) console.log(' 账号数:需面板密码(creditdaddy 设过访问密码)');
166
+ else if (s.probe?.accountsCount != null) console.log(' 账号数:', s.probe.accountsCount);
167
+ if (s.probe?.nextTickAt) console.log(' 下次签到:', new Date(s.probe.nextTickAt).toLocaleString());
168
+ break;
169
+ }
170
+ case 'restart': {
171
+ const { stopDaemon, startBackground, backgroundLogFile } = await import('../src/bgdaemon.js');
172
+ await stopDaemon();
173
+ const port = Number(flag('--port')) || Number(process.env.PORT) || DEFAULT_BG_PORT;
174
+ const host = flag('--host') || '127.0.0.1';
175
+ const r = await startBackground({ port, host });
176
+ if (r.error) { console.error('✗', r.error); process.exit(1); }
177
+ console.log('✓ 已重启');
178
+ console.log(' 面板:', panelUrl(r.rec.host, r.rec.port));
179
+ console.log(' 日志:', backgroundLogFile());
51
180
  break;
52
181
  }
53
182
  case 'add': {
package/package.json CHANGED
@@ -1,29 +1,29 @@
1
- {
2
- "name": "creditdaddy",
3
- "version": "1.2.0",
4
- "description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 多账号本地管理与每日积分自动签到助手",
5
- "homepage": "https://github.com/techysy/CreditDaddy",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/techysy/CreditDaddy.git"
9
- },
10
- "type": "module",
11
- "bin": {
12
- "creditdaddy": "bin/creditdaddy.js"
13
- },
14
- "files": [
15
- "bin",
16
- "src",
17
- "README.md",
18
- "LICENSE"
19
- ],
20
- "engines": {
21
- "node": ">=20"
22
- },
23
- "scripts": {
24
- "start": "node bin/creditdaddy.js daemon",
25
- "test": "node --test",
26
- "release-check": "bash scripts/release-check.sh"
27
- },
28
- "license": "MIT"
29
- }
1
+ {
2
+ "name": "creditdaddy",
3
+ "version": "1.3.2",
4
+ "description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax 多账号本地管理与每日积分自动签到助手",
5
+ "homepage": "https://github.com/techysy/CreditDaddy",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/techysy/CreditDaddy.git"
9
+ },
10
+ "type": "module",
11
+ "bin": {
12
+ "creditdaddy": "bin/creditdaddy.js"
13
+ },
14
+ "files": [
15
+ "bin",
16
+ "src",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "scripts": {
24
+ "start": "node bin/creditdaddy.js daemon",
25
+ "test": "node --test",
26
+ "release-check": "bash scripts/release-check.sh"
27
+ },
28
+ "license": "MIT"
29
+ }
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, loadSettings, saveSettings } from './store.js';
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
- const pending = (await loadSettings()).pendingQoderWebSessions || {};
22
- const item = pending[target.uid];
23
- if (!item || item.kind !== target.provider || !item.cookie) return false;
24
- const { [target.uid]: _used, ...rest } = pending;
25
- await saveSettings({ pendingQoderWebSessions: rest });
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
- set('refreshToken', incoming.refreshToken);
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)' };