creditdaddy 0.9.5 → 1.2.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 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,集成 10Router 额度总览与用量同步**
7
+ **AI 编程工具多账号本地管理 + 每日积分自动领取(领鸡蛋)助手:Qoder · WorkBuddy · ZCode · mirasim · 妙手 · Trae,集成 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)
@@ -14,7 +14,7 @@
14
14
  [![Node](https://img.shields.io/badge/Node.js-%E2%89%A5%2020-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
15
15
  [![License](https://img.shields.io/github/license/techysy/CreditDaddy?label=%E8%AE%B8%E5%8F%AF&color=f59e0b)](LICENSE)
16
16
 
17
- [下载](#下载) · [架构](#%EF%B8%8F-架构) · [功能](#功能) · [10Router 集成](#10router-集成) · [产品线说明](#产品线说明) · [快速开始](#快速开始) · [HTTP API](#本地-http-api) · [相关项目](#-相关项目) · [许可证](#-许可证)
17
+ [下载](#下载) · [架构](#%EF%B8%8F-架构) · [功能](#功能) · [10Router 集成](#10router-集成) · [产品线说明](#产品线说明) · [快速开始](#快速开始) · [HTTP API](#本地-http-api) · [相关项目](#-相关项目) · [贡献者致谢](#-贡献者致谢) · [许可证](#-许可证)
18
18
 
19
19
  <img src="docs/screenshot-dashboard.png" width="860" alt="CreditDaddy 仪表盘">
20
20
 
@@ -59,9 +59,9 @@
59
59
  ## 功能
60
60
 
61
61
  **多账号管理与切换**
62
- - **仪表盘 + 分产品标签页**:首页仪表盘汇总账号总数、今日领取进度、剩余总积分、下次自动执行时间、各产品概况、待处理异常账号(token 失效 / 即将过期、领取失败)与最近领取审计;Qoder、WorkBuddy、ZCode 各自独立标签页,卡片式展示,支持国内 / 国际版筛选,直观查看连续天数与额度包到期,自适应亮色 / 暗色主题。右上角「组件开关」可整块收起不使用的产品 / 10Router 标签页与仪表盘卡片(偏好保存在浏览器本地)。
63
- - **本机导入**:一键安全读取本机已登录的客户端凭据 —— 本地解密 Qoder / Qoder CN 的 `auth.v1.dat`,读取 WorkBuddy 客户端(当前与历史会话),解密 ZCode 凭据。敏感 Token 绝不出机器;同一用户续期时自动覆盖。
64
- - **客户端一键换号**:一键将桌面客户端切换至指定账号(WorkBuddy 自动热切换;ZCode 退出客户端后写回),切号前自动保存当前在线 Token,各账号拥有独立设备指纹,杜绝风控串号。
62
+ - **仪表盘 + 分产品标签页**:首页仪表盘汇总账号总数、今日领取进度、剩余总积分、下次自动执行时间、各产品概况、待处理异常账号(token 失效 / 即将过期、领取失败)与最近领取审计;Qoder、WorkBuddy、ZCode、mirasim、妙手、Trae 各自独立标签页,卡片式展示,支持国内 / 国际版筛选,直观查看连续天数与额度包到期,自适应亮色 / 暗色主题。右上角「组件开关」可整块收起不使用的产品 / 10Router 标签页与仪表盘卡片(偏好保存在浏览器本地)。
63
+ - **本机导入**:一键安全读取本机已登录的客户端凭据 —— 本地解密 Qoder / Qoder CN 的 `auth.v1.dat`,读取 WorkBuddy 客户端(当前与历史会话),解密 ZCode / mirasim / 妙手 / Trae 客户端凭据。敏感 Token 绝不出机器;同一用户续期时自动覆盖。
64
+ - **客户端一键换号**:一键将桌面客户端切换至指定账号(WorkBuddy 自动热切换;ZCode / mirasim / 妙手 / Trae 退出客户端后写回并自动重新拉起),切号前自动保存当前在线 Token,各账号拥有独立设备指纹,杜绝风控串号。
65
65
  - **内置隐私(无痕)授权**:桌面版提供隔离的一次性会话窗口进行网页登录授权,不污染系统浏览器 Cookie,支持同平台无缝扩增多账号。
66
66
  - **全流程浏览器登录**:支持三家产品线在面板内直接完成网页 / 设备码授权并入库 —— Qoder 设备码授权(PKCE + nonce)、WorkBuddy 状态轮询、ZCode CLI 轮询(支持智谱 BigModel 与 Z.ai)。
67
67
 
@@ -77,7 +77,8 @@
77
77
  在面板「10Router」标签页配置 10Router 服务地址与仪表盘创建的 **虚拟 key**(sk-…)。Key 仅存储于本地 `tenrouter.json`(0600 权限),面板只显示脱敏值。
78
78
 
79
79
  - **供应商额度卡片(10Router 1.2.1+)**:通过 `GET /api/usage/quotas` 一键读取 10Router 中其他供应商(CodeBuddy / Qoder / Claude / GLM 等)的额度卡片。CodeBuddy / Qoder / GLM 会自动标明**国内版 / 国际版**,当额度不足 10% 时自动标红并推送到仪表盘「需要处理」。支持 5 分钟连接级缓存与手动强制刷新。
80
- - **用量自动同步(10Router 1.0.7+)**:内置与 10router-sync 插件一致的同步逻辑,支持一键或每小时自动同步本机 ZCode(`db.sqlite` 官方渠道)、OpenCode(`opencode.db`)、mirasim(`usage-*.ndjson`)、小米 MiMo(`mimocode.db`)的真实用量至 10Router 统计。支持断点续传与 2 天重叠补偿,服务端校验签名去重。
80
+ - **账号同步(一键推送为 10Router 连接)**:把本机 Qoder / WorkBuddy 账号直接推为 10Router 上游连接。配置 10Router 面板密码后走 **OAuth 通道**(accessToken + refreshToken,10R 端按身份去重并原位更新、自动续期,反复同步幂等);仅配虚拟 key 时走 **apikey 通道**(同名连接不覆盖,token 过期后需重新同步)。zcode → GLM 映射待反代就绪后补充(映射表已留接口)。
81
+ - **用量自动同步(10Router 1.0.7+)**:内置与 10router-sync 插件一致的同步逻辑,支持一键或每小时自动同步本机 ZCode(`db.sqlite` 官方渠道)、OpenCode(`opencode.db`)、mirasim(`usage-*.ndjson`)、小米 MiMo(`mimocode.db`)的真实用量,以及**妙手(CatPaw)云端用量**(`v1/usage/token/daily` 按天 token 总量;当天桶隔天入账,云端无输入/输出拆分、整包计入输入,模型列默认 `unknown`,可在 `tenrouter.json` 的 `sync.catpawModel` 标注)至 10Router 统计。支持断点续传与 2 天重叠补偿。10Router 按**行内容签名**(时间戳 / provider / model / token 数等)去重,行内容还在变(会话进行中、按天桶增长)会被当成新行——因此客户端发出去的行保证已定型:1 小时结算窗内暂不发送、妙手按天桶只发差量、缺时间戳的行直接跳过;日志显示「新增 N 行,跳过 M 行重复」让去重结果可见。
81
82
  - *注:读取 SQLite 依赖 Node 22.5+ 内置的 `node:sqlite`(桌面版与 fnOS nodejs_v24 环境原生支持)。*
82
83
 
83
84
  ## 产品线说明
@@ -104,6 +105,18 @@
104
105
  - **额度与套餐**:通过 `auth.mirasim.ai/auth/me` 查询当前套餐(Pro/Plus)及到期时间;通过 `relay.mirasim.ai/v1/limits` 查询「平台额度」(5小时滚动窗口、7天全局上限与分模型 Claude 上限)。支持直连与代理优先切换,防止网络区域限制。
105
106
  - **客户端切号**:一键切换当前登录账号并写回 `setting.json`(保留工作区与模型配置不变),切号前自动同步当前在线 Token;桌面版切换成功后自动重新拉起 Mirasim 客户端。
106
107
 
108
+ ### 妙手(美团 CatPaw)
109
+ - **账号来源**:读取 `%APPDATA%\catpaw-moon\catx-credential.json`,其 `ssoTokenEnc` 为 AES-256-GCM 密文(`iv‖tag‖ciphertext`),密钥由本机 `MachineGuid` 派生(`sha256(machineId + ":catpaw-desk-token-v2")`),敏感 Token 绝不出机。
110
+ - **额度与版本**:走桌面网关 `https://catx.nocode.cn/api/gateway/*`(纯 token 鉴权,`X-Auth-Token` 头)——`auth/current-user` 用户信息、`credit/balance` 可用 Credits 与当前套餐版本(体验版 / 专业版,含到期与下次刷新时间)。网页端 `credit.catpaw.meituan.com` 接口只认浏览器 Cookie,daemon 不使用。
111
+ - **无签到、无对外续期**:按套餐发放 Credits,无每日签到活动;凭据由妙手客户端登录轮转,失效(401)时在客户端重新登录后再本机导入即可。
112
+ - **客户端切号**:妙手启动时读凭据恢复登录(无热加载),切换 = 重写加密凭据 + 重启客户端;客户端运行中默认拒绝切换,强制切换自动退出并重新拉起妙手。
113
+
114
+ ### Trae(字节 TRAE SOLO / Trae CN)
115
+ - **账号来源**:解本机客户端 `<userData>/User/globalStorage/storage.json` 的 `iCubeAuthInfo://icube.cloudide`(Trae 自研「tc」信封:pepper 为随安装包分发的公开常量表,SHA512 两轮派生 + AES-128-CBC + SHA512 完整性前缀),纯 `node:crypto` 实现,不引入依赖、不需要 SQLite。敏感 Token 绝不出机。
116
+ - **签到与积分**:每日签到领积分,查询当前额度;面板显示凭据到期日,临期变红。
117
+ - **有意不做自动续期**:Trae 的 refresh 会轮转 refreshToken 并作废 IDE 自己那份,等于把用户正在用的客户端踢下线,因此不提供自动续期。
118
+ - **客户端切号**:按账号存 15 项登录态快照到 `~/.creditdaddy/trae-slots/<uid>/`,换走前先自动快照当前登录避免丢号;客户端在跑时拒绝切换(Trae 会把内存里的旧登录写回文件),强制切换后自动重新拉起。`x-device-id` 必须为纯数字设备号(传 GUID 触发 `code 9074`),优先取 `storage.json` 里 `iCubeAuthInfo://icube-dc:<数字>` 的键名后缀以与 IDE 自身指纹一致,缺失时按 uid 确定性派生。
119
+
107
120
  ## 快速开始
108
121
 
109
122
  ### 方式一:桌面安装版 / 便携版(推荐)
@@ -134,7 +147,7 @@ creditdaddy daemon
134
147
  **CLI 常用命令**:
135
148
 
136
149
  ```bash
137
- creditdaddy scan # 扫描导入本机 Qoder / WorkBuddy / ZCode 客户端登录的账号
150
+ creditdaddy scan # 扫描导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 客户端登录的账号
138
151
  creditdaddy add <token> # 添加 Qoder 国际版账号
139
152
  creditdaddy add <token> --cn # 添加 Qoder 国内版账号
140
153
  creditdaddy add <token> --workbuddy # 添加 WorkBuddy 国内版账号(--intl 为国际版)
@@ -179,6 +192,7 @@ creditdaddy help # 查看完整命令行帮助
179
192
  | `GET` | `/api/tenrouter/quotas` | 获取 10Router 聚合的外部供应商配额总览 |
180
193
  | `GET` | `/api/tenrouter/health` | 检查 10Router 服务端自身健康状态 |
181
194
  | `POST` | `/api/tenrouter/sync` | 立即触发本机模型用量向 10Router 同步 |
195
+ | `POST` | `/api/tenrouter/sync-accounts` | 把本机账号推送到 10Router 连接 `{adminPassword?}`(OAuth / apikey 双通道) |
182
196
  | `GET` / `POST` | `/api/qoder/umid` | 获取 / 一键安装 Linux 平台 Qoder 设备身份组件 |
183
197
  | `GET` | `/api/logs` | 查看环形内存日志 |
184
198
  | `POST` | `/api/export` / `/api/import` | 账号数据安全加密导出与导入 |
@@ -212,11 +226,15 @@ CreditDaddy/
212
226
  ├── src/ # 服务核心实现(纯标准库 ESM)
213
227
  │ ├── accounts.js # 账号存储与生命周期管理
214
228
  │ ├── authDevice.js # 网页 / 设备码授权分发中心
229
+ │ ├── catpawClient.js # 妙手桌面网关(用户信息 / 额度 / 套餐)请求
230
+ │ ├── catpawLocal.js # 本机妙手凭据 AES-GCM 解密与切号
215
231
  │ ├── checkin.js # 自动轮询调度器
216
232
  │ ├── constants.js # 服务接口与固定头常量
217
233
  │ ├── daemon.js # 本地 HTTP API 与前端面板静态服务
218
234
  │ ├── localDetect.js # 客户端已安装环境侦测
219
235
  │ ├── logger.js # 内存环形日志缓冲
236
+ │ ├── mirasimClient.js # mirasim 套餐 / 额度请求
237
+ │ ├── mirasimLocal.js # 本机 mirasim 凭据解密与切号
220
238
  │ ├── panel.html # Web 管理面板前端
221
239
  │ ├── providers.js # 多产品线适配器注册表
222
240
  │ ├── qoderApp.js # 本机 Qoder 客户端探测与 safeStorage 解密
@@ -224,7 +242,10 @@ CreditDaddy/
224
242
  │ ├── qoderUmid.js # UMID 设备组件提取与运行
225
243
  │ ├── store.js # 0600 本机原子化配置存储引擎
226
244
  │ ├── tenrouter.js # 10Router 配额查询与调度
245
+ │ ├── tenrouterAccounts.js # 账号一键推送为 10Router 连接(OAuth / apikey 双通道)
227
246
  │ ├── transfer.js # 加密备份导入导出
247
+ │ ├── traeClient.js # Trae 签到 / 额度请求
248
+ │ ├── traeLocal.js # 本机 Trae「tc」信封凭据解密与切号
228
249
  │ ├── usageSync.js # 本地多模型 SQLite 用量抽取
229
250
  │ ├── workbuddyAuth.js # WorkBuddy 授权流程
230
251
  │ ├── workbuddyClient.js # WorkBuddy 业务请求
@@ -232,7 +253,8 @@ CreditDaddy/
232
253
  │ ├── zcodeAuth.js # ZCode 登录授权流程
233
254
  │ ├── zcodeAutoClaim.js # ZCode 自动领活动与验证码处理
234
255
  │ ├── zcodeClient.js # ZCode 权益与配额请求
235
- │ ├── zcodeLocal.js # 本机 ZCode 会话切号
256
+ │ ├── zcodeGateway.js # ZCode 本机网关(额度 / 活动代理)
257
+ │ ├── zcodeLocal.js # 本机 ZCode 会话切号(支持数据目录挪盘)
236
258
  │ └── zcrypto.js # ZCode 本地凭据 AES-GCM 加解密
237
259
  └── test/ # 自动化单元测试
238
260
  ```
@@ -253,6 +275,32 @@ npm test
253
275
 
254
276
  ---
255
277
 
278
+ ## 👥 交流群
279
+
280
+ 欢迎加入 **CreditDaddy 交流群** 交流使用体验与反馈 issue:
281
+
282
+ <div align="center">
283
+ <img src="assets/feishu-group-qr.jpg" width="280" alt="CreditDaddy 交流群二维码">
284
+ </div>
285
+
286
+ ### 贡献者致谢
287
+
288
+ <p>
289
+ <a href="https://github.com/techysy" title="techysy — 主要维护者"><img src="https://github.com/techysy.png?size=80" width="48" height="48" alt="techysy" /></a>
290
+ <a href="https://github.com/fioew" title="fioew — issue 反馈"><img src="https://github.com/fioew.png?size=80" width="48" height="48" alt="fioew" /></a>
291
+ <a href="https://github.com/iMissNan" title="iMissNan — issue 反馈"><img src="https://github.com/iMissNan.png?size=80" width="48" height="48" alt="iMissNan" /></a>
292
+ <a href="https://github.com/JinsFoni" title="JinsFoni — issue 反馈"><img src="https://github.com/JinsFoni.png?size=80" width="48" height="48" alt="JinsFoni" /></a>
293
+ <a href="https://github.com/zeonseoi" title="zeonseoi — issue 反馈"><img src="https://github.com/zeonseoi.png?size=80" width="48" height="48" alt="zeonseoi" /></a>
294
+ <a href="https://github.com/rua-aaa" title="rua-aaa — issue 反馈"><img src="https://github.com/rua-aaa.png?size=80" width="48" height="48" alt="rua-aaa" /></a>
295
+ <a href="https://github.com/Osinglehh" title="Osinglehh — issue 反馈"><img src="https://github.com/Osinglehh.png?size=80" width="48" height="48" alt="Osinglehh" /></a>
296
+ <a href="https://github.com/JacksonJiangxh" title="JacksonJiangxh — issue 反馈"><img src="https://github.com/JacksonJiangxh.png?size=80" width="48" height="48" alt="JacksonJiangxh" /></a>
297
+ <a href="https://github.com/juyou1" title="juyou1 — issue 反馈"><img src="https://github.com/juyou1.png?size=80" width="48" height="48" alt="juyou1" /></a>
298
+ </p>
299
+
300
+ <sub>名单由 issue 与 PR 的反馈者及协同项目汇总,头像取自 GitHub 公开个人信息;若有遗漏请随时提 issue 补充。</sub>
301
+
302
+ ---
303
+
256
304
  ## 📄 许可证
257
305
 
258
306
  [MIT](LICENSE)
@@ -1,191 +1,209 @@
1
- #!/usr/bin/env node
2
- /**
3
- * CreditDaddy CLI
4
- * creditdaddy daemon 启动守护进程(默认)
5
- * creditdaddy add <token> 添加账号(--cn 国内版 --name 名字)
6
- * creditdaddy list 查看账号
7
- * creditdaddy checkin 立即领取全部账号
8
- * creditdaddy remove <id> 删除账号
9
- * creditdaddy export/import 导出/导入账号
10
- * creditdaddy logs 查看领取状态(state.json)
11
- * creditdaddy help 显示帮助
12
- */
13
-
14
- import { startDaemon } from '../src/daemon.js';
15
- import { logger } from '../src/logger.js';
16
-
17
- const args = process.argv.slice(2);
18
- const cmd = args[0] || 'daemon';
19
-
20
- const HELP = `CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim 多账号管理 + 每日积分自动领取
21
-
22
- 用法:
23
- creditdaddy daemon [--port 47860] [--host 127.0.0.1] 启动守护进程 + Web 面板
24
- creditdaddy add <token> [--cn] [--name 名字] 添加 Qoder 账号(--cn 为国内版)
25
- creditdaddy add <token> --workbuddy [--intl] 添加 WorkBuddy 账号(登录 token)
26
- creditdaddy list 查看账号
27
- creditdaddy checkin [--cn | --intl] 立即领取
28
- creditdaddy remove <id前缀> 删除账号
29
- creditdaddy export [file.json] [--password 口令] [--cn|--intl]
30
- 导出账号;带口令则加密(与 10router 迁移文件互通)
31
- creditdaddy import <file.json> [--password 口令] 导入 CreditDaddy / 10router 导出文件
32
- creditdaddy scan 导入本机 Qoder / WorkBuddy / ZCode / mirasim 客户端已登录的账号
33
- creditdaddy umid [install|remove] Qoder 设备身份组件(Linux / fnOS 国际版领取用)
34
- creditdaddy logs 查看领取状态
35
- creditdaddy help 显示本帮助`;
36
-
37
- function flag(name) {
38
- const i = args.indexOf(name);
39
- return i >= 0 ? args[i + 1] : undefined;
40
- }
41
-
42
- async function main() {
43
- switch (cmd) {
44
- case 'daemon': {
45
- const port = Number(flag('--port')) || Number(process.env.PORT) || undefined;
46
- const host = flag('--host') || process.env.HOST || undefined;
47
- await startDaemon(port, host);
48
- const { startScheduler } = await import('../src/checkin.js');
49
- startScheduler();
50
- logger.info('CLI', '按 Ctrl+C 停止');
51
- break;
52
- }
53
- case 'add': {
54
- const token = args[1];
55
- if (!token) { console.error('用法: creditdaddy add <token> [--cn] [--name 名字]'); process.exit(1); }
56
- const { publicAccount } = await import('../src/store.js');
57
- const { addAccount } = await import('../src/accounts.js');
58
- const wbFlag = args.includes('--workbuddy');
59
- const provider = wbFlag
60
- ? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
61
- : (args.includes('--cn') ? 'qoder-cn' : 'qoder');
62
- const { account, duplicate } = await addAccount({ token, provider, name: flag('--name') });
63
- if (duplicate) { console.error('该账号已存在'); process.exit(1); }
64
- console.log(account.verified ? '✓ token 校验通过' : '⚠ token 校验失败(仍会保存):' + account.verifyError);
65
- console.log('✓ 已添加:', JSON.stringify(publicAccount(account), null, 2));
66
- break;
67
- }
68
- case 'list': {
69
- const { loadAccounts, publicAccount } = await import('../src/store.js');
70
- const accounts = await loadAccounts();
71
- if (accounts.length === 0) return console.log('(还没有账号,用 creditdaddy add <token> 添加)');
72
- for (const a of accounts) {
73
- const p = publicAccount(a);
74
- console.log(`[${p.id.slice(0, 8)}] ${(p.name || '(未命名)').padEnd(16)} ${p.provider.padEnd(14)} ${p.tokenMasked} ${p.lastCheckin ? '上次领取 ' + p.lastCheckin.slice(0, 16) : '未领取'}`);
75
- }
76
- break;
77
- }
78
- case 'checkin': {
79
- const { runCheckinTick } = await import('../src/checkin.js');
80
- const provider = args.includes('--workbuddy')
81
- ? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
82
- : args.includes('--cn') ? 'qoder-cn' : args.includes('--intl') ? 'qoder' : undefined;
83
- const { summary, results } = await runCheckinTick({ provider, skipIfCheckedToday: false });
84
- console.log(summary);
85
- for (const r of results) {
86
- console.log(` ${r.status === 'checked-in' ? '✓' : r.status === 'failed' ? '✗' : '·'} ${r.account}(${r.provider})${r.status === 'checked-in' ? ` +${r.claimedAmount} Credits` : r.error ? ' ' + r.error : r.message ? ' ' + r.message : ''}`);
87
- }
88
- break;
89
- }
90
- case 'remove': {
91
- const id = args[1];
92
- if (!id) { console.error('用法: creditdaddy remove <id前缀>(id 见 creditdaddy list)'); process.exit(1); }
93
- const { withAccounts } = await import('../src/store.js');
94
- const outcome = await withAccounts((accounts) => {
95
- const matches = accounts.filter((a) => a.id.startsWith(id));
96
- if (matches.length !== 1) return { count: matches.length };
97
- accounts.splice(accounts.indexOf(matches[0]), 1);
98
- return { count: 1, removed: matches[0] };
99
- });
100
- if (outcome.count === 0) { console.error('账号不存在'); process.exit(1); }
101
- if (outcome.count > 1) { console.error(`id 前缀 "${id}" 匹配到 ${outcome.count} 个账号,请输入更长的前缀`); process.exit(1); }
102
- console.log('✓ 已删除', outcome.removed.name || outcome.removed.id);
103
- break;
104
- }
105
- case 'export': {
106
- const { loadAccounts } = await import('../src/store.js');
107
- const { exportAccounts } = await import('../src/transfer.js');
108
- const out = args[1] && !args[1].startsWith('--') ? args[1] : 'creditdaddy-backup.json';
109
- const password = flag('--password');
110
- const provider = args.includes('--cn') ? 'qoder-cn' : args.includes('--intl') ? 'qoder' : undefined;
111
- const { writeFileSync } = await import('node:fs');
112
- writeFileSync(out, JSON.stringify(exportAccounts(await loadAccounts(), { password, provider }), null, 2), { mode: 0o600 });
113
- console.log('✓ 已导出到', out, password ? '(已加密,10router 可直接导入)' : '(含明文 token,注意保管;加 --password 可加密)');
114
- break;
115
- }
116
- case 'import': {
117
- const file = args[1];
118
- if (!file) { console.error('用法: creditdaddy import <file.json>'); process.exit(1); }
119
- const { readFileSync } = await import('node:fs');
120
- const { importAccounts } = await import('../src/accounts.js');
121
- const { parseImport } = await import('../src/transfer.js');
122
- const parsed = parseImport(JSON.parse(readFileSync(file, 'utf8')), { password: flag('--password') });
123
- const r = await importAccounts(parsed.accounts);
124
- console.log(`✓ 导入完成(来源 ${parsed.source}):新增 ${r.added},续期 ${r.updated},跳过 ${r.skipped + parsed.skipped}`);
125
- break;
126
- }
127
- case 'scan': {
128
- const { readQoderAppAccounts } = await import('../src/qoderApp.js');
129
- const { readWorkbuddySessions } = await import('../src/workbuddyLocal.js');
130
- const { liveToAccount: zcodeLive } = await import('../src/zcodeLocal.js');
131
- const { liveToAccount: mirasimLive } = await import('../src/mirasimLocal.js');
132
- const { addAccount } = await import('../src/accounts.js');
133
- const qa = await readQoderAppAccounts();
134
- const wb = readWorkbuddySessions();
135
- const zAccount = zcodeLive();
136
- let miraAccount = null;
137
- try { miraAccount = await mirasimLive(); } catch {}
138
- const records = [
139
- ...qa.accounts.map((c) => ({
140
- label: c.source,
141
- rec: { provider: c.provider, token: c.token, name: c.user.name || c.user.email, uid: c.user.id, email: c.user.email, refreshToken: c.refreshToken, expiresAt: c.expiresAt, source: 'local-app' },
142
- })),
143
- ...wb.accounts.map(({ file: _f, fileTime: _t, current, source, ...rec }) => ({
144
- label: source, rec: { ...rec, source: current ? 'workbuddy-current' : 'workbuddy-history' },
145
- })),
146
- ...(zAccount ? [{ label: 'ZCode 当前登录', rec: zAccount }] : []),
147
- ...(miraAccount ? [{ label: 'mirasim 当前登录', rec: miraAccount }] : []),
148
- ];
149
- for (const e of [...qa.errors, ...wb.errors]) console.log('⚠', e.file, e.error);
150
- if (!records.length) { console.log('(本机 Qoder / WorkBuddy / ZCode / mirasim 客户端未登录或未安装)'); break; }
151
- for (const { label, rec } of records) {
152
- const r = await addAccount(rec, { trusted: true });
153
- console.log(r.duplicate ? (r.updated ? '↻ 已更新' : '· 已存在') : '✓ 已导入', r.account.name || r.account.id, `(${label})`);
154
- }
155
- break;
156
- }
157
- case 'umid': {
158
- const umid = await import('../src/qoderUmid.js');
159
- const sub = args[1] || 'status';
160
- if (sub === 'install') {
161
- console.log('正在从 npm 下载官方 @qoder-ai/qodercli 并提取设备身份组件(约 30MB)…');
162
- const m = await umid.installUmid();
163
- console.log(`✓ 已安装:qodercli ${m.version} / ${m.arch},Qoder 国际版领取可用`);
164
- } else if (sub === 'remove') {
165
- umid.removeUmid();
166
- console.log('✓ 已移除设备身份组件');
167
- } else {
168
- const info = umid.umidInfo();
169
- if (!info.supported) console.log('当前平台不需要该组件(Windows / macOS 使用 Qoder 客户端自带的 runtime-info)');
170
- else if (info.installed) console.log(`已安装:qodercli ${info.installed.version} / ${info.installed.arch}(${info.installed.installedAt.slice(0, 10)})`);
171
- else console.log('未安装。运行 creditdaddy umid install 安装后即可领取 Qoder 国际版');
172
- }
173
- break;
174
- }
175
- case 'logs': {
176
- const { loadState } = await import('../src/store.js');
177
- console.log('数据目录状态:', JSON.stringify(await loadState(), null, 2));
178
- break;
179
- }
180
- case 'help':
181
- case '--help':
182
- case '-h':
183
- console.log(HELP);
184
- break;
185
- default:
186
- console.error(`未知命令: ${cmd}\n\n${HELP}`);
187
- process.exit(1);
188
- }
189
- }
190
-
191
- main().catch((e) => { console.error('✗', e.message); process.exit(1); });
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CreditDaddy CLI
4
+ * creditdaddy daemon 启动守护进程(默认)
5
+ * creditdaddy add <token> 添加账号(--cn 国内版 --name 名字)
6
+ * creditdaddy list 查看账号
7
+ * creditdaddy checkin 立即领取全部账号
8
+ * creditdaddy remove <id> 删除账号
9
+ * creditdaddy export/import 导出/导入账号
10
+ * creditdaddy logs 查看领取状态(state.json)
11
+ * creditdaddy help 显示帮助
12
+ */
13
+
14
+ import { startDaemon } from '../src/daemon.js';
15
+ import { logger } from '../src/logger.js';
16
+
17
+ const args = process.argv.slice(2);
18
+ const cmd = args[0] || 'daemon';
19
+
20
+ const HELP = `CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 多账号管理 + 每日积分自动领取
21
+
22
+ 用法:
23
+ creditdaddy daemon [--port 47860] [--host 127.0.0.1] 启动守护进程 + Web 面板
24
+ creditdaddy add <token> [--cn] [--name 名字] 添加 Qoder 账号(--cn 为国内版)
25
+ creditdaddy add <token> --workbuddy [--intl] 添加 WorkBuddy 账号(登录 token)
26
+ creditdaddy list 查看账号
27
+ creditdaddy checkin [--cn | --intl] 立即领取
28
+ creditdaddy remove <id前缀> 删除账号
29
+ creditdaddy export [file.json] --password 口令 [--cn|--intl]
30
+ 导出账号(口令必填,至少 4 位;与 10router 迁移文件互通)
31
+ creditdaddy import <file.json> [--password 口令] 导入 CreditDaddy / 10router 导出文件
32
+ creditdaddy scan 导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 客户端已登录的账号
33
+ creditdaddy umid [install|remove] Qoder 设备身份组件(Linux / fnOS 国际版领取用)
34
+ creditdaddy logs 查看领取状态
35
+ creditdaddy help 显示本帮助`;
36
+
37
+ function flag(name) {
38
+ const i = args.indexOf(name);
39
+ return i >= 0 ? args[i + 1] : undefined;
40
+ }
41
+
42
+ async function main() {
43
+ switch (cmd) {
44
+ case 'daemon': {
45
+ const port = Number(flag('--port')) || Number(process.env.PORT) || undefined;
46
+ const host = flag('--host') || process.env.HOST || undefined;
47
+ await startDaemon(port, host);
48
+ const { startScheduler } = await import('../src/checkin.js');
49
+ startScheduler();
50
+ logger.info('CLI', '按 Ctrl+C 停止');
51
+ break;
52
+ }
53
+ case 'add': {
54
+ const token = args[1];
55
+ if (!token) { console.error('用法: creditdaddy add <token> [--cn] [--name 名字]'); process.exit(1); }
56
+ const { publicAccount } = await import('../src/store.js');
57
+ const { addAccount } = await import('../src/accounts.js');
58
+ const wbFlag = args.includes('--workbuddy');
59
+ const provider = wbFlag
60
+ ? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
61
+ : (args.includes('--cn') ? 'qoder-cn' : 'qoder');
62
+ const { account, duplicate } = await addAccount({ token, provider, name: flag('--name') });
63
+ if (duplicate) { console.error('该账号已存在'); process.exit(1); }
64
+ console.log(account.verified ? '✓ token 校验通过' : '⚠ token 校验失败(仍会保存):' + account.verifyError);
65
+ console.log('✓ 已添加:', JSON.stringify(publicAccount(account), null, 2));
66
+ break;
67
+ }
68
+ case 'list': {
69
+ const { loadAccounts, publicAccount } = await import('../src/store.js');
70
+ const accounts = await loadAccounts();
71
+ if (accounts.length === 0) return console.log('(还没有账号,用 creditdaddy add <token> 添加)');
72
+ for (const a of accounts) {
73
+ const p = publicAccount(a);
74
+ console.log(`[${p.id.slice(0, 8)}] ${(p.name || '(未命名)').padEnd(16)} ${p.provider.padEnd(14)} ${p.tokenMasked} ${p.lastCheckin ? '上次领取 ' + p.lastCheckin.slice(0, 16) : '未领取'}`);
75
+ }
76
+ break;
77
+ }
78
+ case 'checkin': {
79
+ const { runCheckinTick } = await import('../src/checkin.js');
80
+ const provider = args.includes('--workbuddy')
81
+ ? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
82
+ : args.includes('--cn') ? 'qoder-cn' : args.includes('--intl') ? 'qoder' : undefined;
83
+ const { summary, results } = await runCheckinTick({ provider, skipIfCheckedToday: false });
84
+ console.log(summary);
85
+ for (const r of results) {
86
+ console.log(` ${r.status === 'checked-in' ? '✓' : r.status === 'failed' ? '✗' : '·'} ${r.account}(${r.provider})${r.status === 'checked-in' ? ` +${r.claimedAmount} Credits` : r.error ? ' ' + r.error : r.message ? ' ' + r.message : ''}`);
87
+ }
88
+ break;
89
+ }
90
+ case 'remove': {
91
+ const id = args[1];
92
+ if (!id) { console.error('用法: creditdaddy remove <id前缀>(id 见 creditdaddy list)'); process.exit(1); }
93
+ const { withAccounts } = await import('../src/store.js');
94
+ const outcome = await withAccounts((accounts) => {
95
+ const matches = accounts.filter((a) => a.id.startsWith(id));
96
+ if (matches.length !== 1) return { count: matches.length };
97
+ accounts.splice(accounts.indexOf(matches[0]), 1);
98
+ return { count: 1, removed: matches[0] };
99
+ });
100
+ if (outcome.count === 0) { console.error('账号不存在'); process.exit(1); }
101
+ if (outcome.count > 1) { console.error(`id 前缀 "${id}" 匹配到 ${outcome.count} 个账号,请输入更长的前缀`); process.exit(1); }
102
+ console.log('✓ 已删除', outcome.removed.name || outcome.removed.id);
103
+ break;
104
+ }
105
+ case 'export': {
106
+ const { loadAccounts } = await import('../src/store.js');
107
+ const { exportAccounts, TransferError } = await import('../src/transfer.js');
108
+ const out = args[1] && !args[1].startsWith('--') ? args[1] : 'creditdaddy-secure-backup.json';
109
+ const password = flag('--password');
110
+ if (!password) { console.error('导出账号必须设置加密口令:creditdaddy export [file.json] --password 口令(至少 4 位)'); process.exit(1); }
111
+ const provider = args.includes('--cn') ? 'qoder-cn' : args.includes('--intl') ? 'qoder' : undefined;
112
+ const { writeFileSync } = await import('node:fs');
113
+ let blob;
114
+ try {
115
+ blob = exportAccounts(await loadAccounts(), { password, provider });
116
+ } catch (e) {
117
+ console.error(e instanceof TransferError ? `导出失败:${e.message}` : e.message);
118
+ process.exit(1);
119
+ }
120
+ writeFileSync(out, JSON.stringify(blob, null, 2), { mode: 0o600 });
121
+ console.log('✓ 已导出加密文件(10router 可直接导入):', out);
122
+ break;
123
+ }
124
+ case 'import': {
125
+ const file = args[1];
126
+ if (!file) { console.error('用法: creditdaddy import <file.json>'); process.exit(1); }
127
+ const { readFileSync } = await import('node:fs');
128
+ const { importAccounts } = await import('../src/accounts.js');
129
+ const { parseImport } = await import('../src/transfer.js');
130
+ const parsed = parseImport(JSON.parse(readFileSync(file, 'utf8')), { password: flag('--password') });
131
+ const r = await importAccounts(parsed.accounts);
132
+ console.log(`✓ 导入完成(来源 ${parsed.source}):新增 ${r.added},续期 ${r.updated},跳过 ${r.skipped + parsed.skipped}`);
133
+ break;
134
+ }
135
+ case 'scan': {
136
+ const { readQoderAppAccounts } = await import('../src/qoderApp.js');
137
+ const { readWorkbuddySessions } = await import('../src/workbuddyLocal.js');
138
+ const { liveToAccount: zcodeLive } = await import('../src/zcodeLocal.js');
139
+ const { liveToAccount: mirasimLive } = await import('../src/mirasimLocal.js');
140
+ const { liveToAccount: catpawLive } = await import('../src/catpawLocal.js');
141
+ const { liveToAccount: traeLive } = await import('../src/traeLocal.js');
142
+ const { addAccount } = await import('../src/accounts.js');
143
+ const qa = await readQoderAppAccounts();
144
+ const wb = readWorkbuddySessions();
145
+ const skipped = [];
146
+ let zAccount = null;
147
+ try { zAccount = zcodeLive(); } catch (e) { skipped.push({ file: '~/.zcode/v2/credentials.json', error: e.message }); }
148
+ let miraAccount = null;
149
+ try { miraAccount = await mirasimLive(); } catch (e) { skipped.push({ file: '~/.mirasim/setting.json', error: e.message }); }
150
+ let cpAccount = null;
151
+ try { cpAccount = await catpawLive(); } catch (e) { skipped.push({ file: 'catpaw-moon/catx-credential.json', error: e.message }); }
152
+ let traeAccount = null;
153
+ try { traeAccount = await traeLive(); } catch (e) { skipped.push({ file: 'TRAE SOLO CN/User/globalStorage/storage.json', error: e.message }); }
154
+ const records = [
155
+ ...qa.accounts.map((c) => ({
156
+ label: c.source,
157
+ rec: { provider: c.provider, token: c.token, name: c.user.name || c.user.email, uid: c.user.id, email: c.user.email, refreshToken: c.refreshToken, expiresAt: c.expiresAt, source: 'local-app', meta: { qoderAuth: c.authJson, qoderAuthFile: c.file } },
158
+ })),
159
+ ...wb.accounts.map(({ file: _f, fileTime: _t, current, source, ...rec }) => ({
160
+ label: source, rec: { ...rec, source: current ? 'workbuddy-current' : 'workbuddy-history' },
161
+ })),
162
+ ...(zAccount ? [{ label: 'ZCode 当前登录', rec: zAccount }] : []),
163
+ ...(miraAccount ? [{ label: 'mirasim 当前登录', rec: miraAccount }] : []),
164
+ ...(cpAccount ? [{ label: '妙手当前登录', rec: cpAccount }] : []),
165
+ ...(traeAccount ? [{ label: 'Trae 当前登录', rec: traeAccount }] : []),
166
+ ];
167
+ for (const e of [...qa.errors, ...wb.errors, ...skipped]) console.log('⚠', e.file, e.error);
168
+ if (!records.length) { console.log('(本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae 客户端未登录或未安装)'); break; }
169
+ for (const { label, rec } of records) {
170
+ const r = await addAccount(rec, { trusted: true });
171
+ console.log(r.duplicate ? (r.updated ? '↻ 已更新' : '· 已存在') : '✓ 已导入', r.account.name || r.account.id, `(${label})`);
172
+ }
173
+ break;
174
+ }
175
+ case 'umid': {
176
+ const umid = await import('../src/qoderUmid.js');
177
+ const sub = args[1] || 'status';
178
+ if (sub === 'install') {
179
+ console.log('正在从 npm 下载官方 @qoder-ai/qodercli 并提取设备身份组件(约 30MB)…');
180
+ const m = await umid.installUmid();
181
+ console.log(`✓ 已安装:qodercli ${m.version} / ${m.arch},Qoder 国际版领取可用`);
182
+ } else if (sub === 'remove') {
183
+ umid.removeUmid();
184
+ console.log('✓ 已移除设备身份组件');
185
+ } else {
186
+ const info = umid.umidInfo();
187
+ if (!info.supported) console.log('当前平台不需要该组件(Windows / macOS 使用 Qoder 客户端自带的 runtime-info)');
188
+ else if (info.installed) console.log(`已安装:qodercli ${info.installed.version} / ${info.installed.arch}(${info.installed.installedAt.slice(0, 10)})`);
189
+ else console.log('未安装。运行 creditdaddy umid install 安装后即可领取 Qoder 国际版');
190
+ }
191
+ break;
192
+ }
193
+ case 'logs': {
194
+ const { loadState } = await import('../src/store.js');
195
+ console.log('数据目录状态:', JSON.stringify(await loadState(), null, 2));
196
+ break;
197
+ }
198
+ case 'help':
199
+ case '--help':
200
+ case '-h':
201
+ console.log(HELP);
202
+ break;
203
+ default:
204
+ console.error(`未知命令: ${cmd}\n\n${HELP}`);
205
+ process.exit(1);
206
+ }
207
+ }
208
+
209
+ main().catch((e) => { console.error('✗', e.message); process.exit(1); });