workbuddy/qoder/trae[不用开机,云端签到]

143 阅读20分钟

接上篇 《把 WorkBuddy 每日签到搬上云端》 (2026-08-28)

快进到 操作步骤

仓库地址

源码(建议直接 Fork):cnb.cool/holzwege.co…

Fork 之后就得到了你自己的「签到仓库」,下面所有操作都在你自己的仓库里进行。


一、为什么需要这个

1. 为什么不能只在本机跑

签到要挂在「每天某个时刻」上,本机方案有个绕不过去的坑:机器得开着。 笔记本合盖、下班关机、客户端没启动,签到就断了。断了还得手动补, 补的时候又常常已经错过活动重置时间。

搬到云端定时跑,这些都不再是问题。代价是凭据得从本机挪到云端。

2. 为什么必须有「密钥仓库」这个独立仓库

因为凭据不能写在代码仓库里。

.cnb.yml 里没有任何 env 硬编码的 token —— 凭据只能经由 CNB 的 imports 从另一个私有仓库注入。这样带来三个好处:

  • 签到仓库可以放心开源、随手分享,里面一个密钥都没有
  • 换 token 只动密钥仓库一个文件,不用改代码、不用重跑流水线
  • 泄漏面收敛:即使代码仓库被看到,也拿不到任何可用凭据

3. 为什么凭据只能在本机取一次

几个平台的登录态都没法在云端解密:

平台本机登录态形态为什么云端解不了
WorkBuddyAES-256-GCM 信封解密密钥只驻留客户端进程内存,不落盘
TRAE WorkAES-256-CBC 加密块密钥由客户端代码派生,且设备ID只出现在客户端日志里
Qoder CNDPAPI 保护的 AES 密钥DPAPI 与本机 Windows 账户强绑定,不可导出
MiniMax Code浏览器 localStorage 的 _token就是个 JWT,浏览器里复制一下就行 —— 唯一不需要客户端的

所以架构只能是三段式:

本机(Windows,一次性)        CNB 密钥仓库(私有)        CNB 云端(每天定时)
  auth/<平台>_auth.py    →     <平台>_secrets.yml    →    runner.py + providers/
  解出明文凭据                  只读、只被 imports 引用      不碰加密,直接用明文

4. 为什么只要一个仓库

最初每个工具各占一个仓库,结果是同一套「取凭据 → 存密钥 → 定时签到」 被抄了三遍,三份代码慢慢长得不一样,命名也各不相同。合并成一个仓库之后:

  • 一套调度:几点跑、跑哪些、跑挂了怎么办,只有一个地方要改
  • 一套命名:见下面「命名约定」,脚本、产物、变量名全部同构
  • 加工具只加文件:不碰 .cnb.yml
  • 工具自己的「附加动作」也收在 provider 里:比如 WorkBuddy 签完要派遣 Buddy(喵喵)旅行,就放在 providers/travel.py,由 workbuddy provider 调用, 不往 runner 里塞平台判断(见「四、WorkBuddy 的附加动作」)

二、命名约定

这套约定是强制的 —— tests/selftest.py 会检查,写歪了自测直接红。

项约定示例
取凭据脚本auth/<平台>_auth.pyauth/qoder_auth.py
一键导出(汇总器)export_auth.pytools/ai-checkin/export_auth.py
取凭据产物<平台>_secrets.ymlqoder_secrets.yml
密钥仓库内文件.cnb/<平台>-secrets.yml.cnb/qoder-secrets.yml
云端实现providers/<平台>.pyproviders/qoder.py
环境变量前缀<平台大写>_QODER_TOKEN、QODER_MACHINE_OS
工具名全小写平台名workbuddy / trae / qoder / minimax / baidu

一句话:平台名是唯一的命名基准,脚本、文件、变量都从它派生。 取凭据脚本一律叫 *_auth.py,作用一律是「拿本机登录态,生成密钥文件」; 产物一律以平台名打头。

汇总器只有一个:export_auth.py 调各平台的 collect(),把它们合成一份成品。 它是「汇总」不是「取凭据」,所以放在 tools/ai-checkin/ 而不是 auth/ —— selftest.py 会盯着这条边界,别把平台无关的脚本混进 auth/。


三、操作步骤

① 准备密钥仓库

有一个私有仓库专门放凭据(本仓库默认指向 holzwege.coz/check-in/secrets)。 没有就新建一个,把 .cnb.yml 里 imports 的地址换成你的。

② 在本机一键生成密钥文件(推荐:一条命令)

在仓库根目录执行:

python tools/ai-checkin/export_auth.py

脚本在 tools/ai-checkin/ 下,必须带上这个相对路径; 在仓库根目录直接敲 python export_auth.py 会报 can't open file '...\export_auth.py': [Errno 2] No such file or directory。 也可以先 cd tools/ai-checkin 再 python export_auth.py,脚本自己会处理路径。

它会逐个平台取本机登录态,然后合成一份 ai-checkin-secrets.yml —— 文件与密钥仓库里那份同名同构,全选复制、整份覆盖粘贴即可,不用再手工拼。

# ── AI 工具签到 · CNB 密钥仓库文件(一键生成,整份复制即可)────────
# 已包含工具:workbuddy、trae
# 未包含工具(补上凭据后重新跑一次 export_auth.py 即可):
#   - qoder:未找到 Qoder CN 登录态文件,请先在客户端登录。

allow_slugs:
  - "<你的组织>/<签到仓库>"
allow_events:
  - "crontab: 2 0 * * *"
  - "crontab: 2 10 * * *"
  - web_trigger

# ── workbuddy 凭据 ─────────────────────────────────────
WORKBUDDY_TOKEN: "<明文凭据>"
WORKBUDDY_DOMAIN: "www.codebuddy.cn"

# ── trae 凭据 ──────────────────────────────────────────
TRAE_CLOUDIDE_TOKEN: "<明文凭据>"
TRAE_DEVICE_ID: "<纯数字设备ID>"

# ── minimax 凭据 ───────────────────────────────────────
MINIMAX_TOKEN: "<浏览器 localStorage 的 _token>"

# ── baidu 凭据 ─────────────────────────────────────────
BAIDU_COOKIE: "<客户端登录态 Cookie,含 BDUSS>"

授权范围(allow_slugs / allow_events)只出现一次,从 .cnb.yml 逐字推导 —— 这正是手工合并最容易写坏的地方(三份产物各带一份,同名 key 互相覆盖)。

前提:对应客户端已启动并登录。各平台的数据来源不同,所以一台机器上通常 只能导出装了客户端的那几个;取不到的平台自动跳过(文件头会注明原因), 不当失败 —— runner 对缺凭据的工具本来就是 skip。

常用选项:

python tools/ai-checkin/export_auth.py --stdout          # 打到终端,不落盘
python tools/ai-checkin/export_auth.py --copy            # 生成后直接进剪贴板
python tools/ai-checkin/export_auth.py --only qoder      # 只导出指定平台
python tools/ai-checkin/export_auth.py --slug <组织>/<仓库>  # 覆盖 allow_slugs

三个安全约定:授权范围占位符会在粘贴前提醒;覆盖已有产物时若平台变少会 拒绝(加 --force 才放行),免得一次误跑把凭据越写越少;一个都没取到 就不落盘。

只想单独取某一个平台也可以,生成的是单平台文件:

python tools/ai-checkin/auth/workbuddy_auth.py      # → workbuddy_secrets.yml
python tools/ai-checkin/auth/trae_auth.py           # → trae_secrets.yml
python tools/ai-checkin/auth/qoder_auth.py --full   # → qoder_secrets.yml
MINIMAX_TOKEN="eyJ..." python tools/ai-checkin/auth/minimax_auth.py  # → minimax_secrets.yml
python tools/ai-checkin/auth/baidu_auth.py                          # → baidu_secrets.yml

不想落盘就加 --verify(只校验能不能解出来)或 --stdout。 这条路要自己把几份产物拼成一份,注意 allow_slugs / allow_events 只能留一份。

③ 粘贴进密钥仓库

把 ai-checkin-secrets.yml 整份复制,覆盖密钥仓库里的 .cnb/ai-checkin-secrets.yml,提交。

.cnb.yml 里只有一行 imports,指向的正是这个路径:

imports:
  - https://cnb.cool/<密钥仓库>/-/blob/main/.cnb/ai-checkin-secrets.yml

所以文件只有一份,变量按工具分几组 —— 加工具只是多一组变量,不用动 imports。

手写也行:tools/ai-checkin/secrets.example.yml 是完整的变量清单模板。 但要小心 allow_events 必须与 .cnb.yml 的事件键逐字一致 (含 cron 表达式与空格),只写 crontab 会在 Prepare 阶段报 does not conform to allow_events。用脚本生成就没这个问题。

④ 跑一次验证

在「代码构建」页点「执行」(或推一次代码触发 push),看日志:

>>> [workbuddy] WorkBuddy(签到 + 派遣 Buddy 旅行)
    结果: OK · 签到成功(12.3s)
    (detail 里多一段 travel:派猫猫旅行:空闲(可派遣)……)
>>> [trae] TRAE Work(每日签到领积分)
    结果: SKIP · 今日已签到(3.1s)
>>> [qoder] Qoder CN(每日签到领 Credits)
    结果: SKIP · 当前无可领活动(2.4s)
>>> [minimax] MiniMax Code(每日签到领积分)
    结果: OK · 签到成功(0.6s)
>>> [baidu] 百度搭子(客户端签到领积分)
    结果: OK · 签到成功(1.1s)
汇总:成功 3 / 跳过 2 / 失败 0

日志末尾会打整段 JSON 摘要(含每个工具的 detail),要看全就往下翻。 WorkBuddy 的 detail.travel 就是派遣结果:available: false 说明旅行接口 没通(签到本身没坏),auto_log 里是「已派出 Buddy 前往【XX】」之类的原话。

没配的工具显示 SKIP 是正常的,不算失败 —— 想启用就把它的凭据补进密钥仓库。

用「代码构建」页手动跑时,密钥仓库的 allow_events 要加上 web_trigger, 否则 Prepare 阶段失败。脚本生成的产物会自动带上(如果 .cnb.yml 里有这个入口)。

⑤ 之后就自动跑了

时间(北京)跑什么为什么是这个点
每天 00:02workbuddy + trae + minimax + baidu按自然日重置,零点后 2 分钟领最稳
每天 10:02qoderQoder 活动每日 10:00 重置,早跑必是空的

四、WorkBuddy 的附加动作:派遣 Buddy(喵喵)

「签到」只是 WorkBuddy 这条线的一半。旧仓库 holzwege.coz/workbuddy-checkin 里还有两件本仓库合并时漏掉的事,这次一并接回来了:

动作在哪说明
派遣 Buddy 旅行providers/travel.py签到之后「先领后派」闭环
多渠道消息通知notifier.pyrunner 跑完每个工具发一条,配置从密钥仓库注入

旅行为什么单独一个文件

因为旅行接口和签到接口不是同一个域名,路径规则也不一样:

签到   https://www.codebuddy.cn/v2/billing/meter/...         ← 带 /v2
旅行   https://www.workbuddy.cn/activity/growth/buddy/...    ← 不带 /v2

写错任一项直接 404。把两者塞进一个 provider 里,改签到路由时很容易连累旅行, 所以独立成 travel.py,默认域名写死,只有服务端路由变了才用 WORKBUDDY_TRAVEL_DOMAIN 覆盖。

闭环逻辑(与旧仓库一致):已到达就先领积分,空闲且未达每日上限才派出。 三条安全约束:

  • 达每日上限绝不发派遣请求 —— 上限外的请求服务端会白记一次
  • 只读接口失败(500 / 返回结构不对)一律降级,一个写请求都不发
  • 派遣任何异常都就地吞掉,不改变签到结论、不影响退出码

想只签到不派遣:密钥仓库里加 WORKBUDDY_TRAVEL: "off"。

通知怎么配

通知配置和凭据一样只能从密钥仓库来(容器里没有本机配置文件):

# 值本身是一份 JSON(与旧仓库本机的 notify_config.json 同构),不是文件路径
AI_CHECKIN_PUSH_CONFIG: '{"channels": {"wecom": {"webhook": "https://..."}}}'
AI_CHECKIN_PUSH_CHANNELS: "wecom"   # 选填,缺省发配置里全部已就绪渠道
AI_CHECKIN_PUSH_ON: "error"         # 选填:error(默认,只报失败)/ ok / always

支持钉钉、飞书、企业微信、PushPlus、邮件、Bark、Slack、Telegram、通用 webhook… (渠道实现从旧仓库 push_message.py 逐字平移,字段见 notifier.py 的 _SAMPLE_CONFIG)。

默认只在签到失败时发(AI_CHECKIN_PUSH_ON=error):每天四条「签到成功」迟早被练成 免打扰,真该看的失败反而淹在里面 —— 失败必须响,成功静默即可。想成/败都发就写 always。

不注入就完全不发通知,这属预期,不算失败。通知本身坏了也只吞掉自己 —— 它和签到是同一条流水线里的两件事,通知挂掉不该让签到结果变红。

本机那份 ~/.workbuddy/scripts/notify_config.json 仍然会被读(方便本机调试), 但云端容器里没有它,所以线上只有注入这一条路。

启动时会打印通知配置(脱敏)

每次 runner 起来,先打一遍通知配置,省得「到底配没配、发去哪」只能靠猜:

通知:已配置(来源 AI_CHECKIN_PUSH_CONFIG,模式 error,enabled=True)
  · wecom:webhook=https://qyapi.weixin.qq.com/***/***/***?key=***
  · 未配置:dingtalk, feishu, wechat, bark, ...

没注入时是 通知:未配置 —— 未注入 AI_CHECKIN_PUSH_CONFIG,本次不发通知(属预期,不算失败)。 配置写坏了也会打出原因而不是把签到带崩。凭据一律脱敏:URL 的 path 整段遮掉、query 只留键名, token 类字段只留长度 —— host 足够判断「配到哪个平台」,凭据一个字符都不落日志。

单独看这份摘要(不跑签到):

cd tools/ai-checkin
python3 notifier.py --report     # 与流水线启动时打印的是同一份
python3 notifier.py --ready      # 机器可读版(JSON)

五、MiniMax Code:不用客户端的网页签到

四个平台里唯一「不需要装客户端」的一个 —— 因为它有个网页版的每日签到, 凭据就是浏览器 localStorage 里的 _token(一个 JWT),抠出来即可。

在哪拿 token

  1. 浏览器打开 agent.minimax.cn/ 并登录
  2. F12 → Application → Local Storage → https://agent.minimax.cn → 复制 _token
  3. 本机执行:
cd tools/ai-checkin
MINIMAX_TOKEN="eyJ..." python auth/minimax_auth.py            # 生成 minimax_secrets.yml
MINIMAX_TOKEN="eyJ..." python auth/minimax_auth.py --verify   # 先在线校验一次再导出(推荐)

user_id(= user_detail_agent.realUserID)是必填项,不是可选优化: 签到 query 里缺了它、或写成 0,网关一律回 401,看起来和「token 过期」一模一样。 好消息是脚本会自动去 user/info 把它补上,所以通常你只要给 _token 就够; 想显式指定就用 --user-id,或直接把 DevTools 里复制的整段 JSON 丢给 --token。 想省事可以 --save,token 存到 ~/.minimax/token.json,之后重跑不用再粘 (明文,只在本机;已在 .gitignore 之外,别提交)。

签到口径

接口就两个:GET /signin/status(7 天进度面板)、POST /signin/claim(领取)。

  • 只有 status 明确说「今天可领」才发 claim —— 不可领时一个写请求都不打
  • 「今日已签到」是 skip 不是失败:面板今天那格已是 Claimed,或 claim 返回 AlreadyClaimed,都按 skip 收口,重跑不会刷红流水线
  • 401 = token 失效,按 skip · Token 失效 收口(见下节);400 = 签名被拒, 才是真 error(网页端公共参数变了,看 providers/minimax.py)

六、「skip」与「error」的边界

四个平台的凭据都会过期,过期是常态不是事故。所以口径定死:

结果什么时候流水线
skip今日已签到、当前无可领、凭据失效、未配置绿
error网络/5xx、响应结构变了、签到被拒但原因不明红

凭据失效的判定收在 providers/authfail.py 一处(HTTP 401/403,或业务码 1001), 各 provider 只管调它。这样做的理由:从前的实现里每家自己 return error, 任何一家 token 过期就把整条流水线刷红 —— 其它三家明明签得好好的,日报却只有 一片红,分不清是「签到坏了」还是「该换 token 了」。

现在过期只留一行 skip · Token 失效,请重新…,流水线照常绿,看到 reason 就知道 该去密钥仓库换凭据了。注意 skip 不等于签到成功,该换的凭据还是得换。

真出错仍是 error。这个口径只放宽「凭据失效」这一种,不兜底 5xx 和响应异常。


七、加一个新工具

四步,不用改 .cnb.yml:

  1. providers/ 下加文件,导出 PROVIDER = YourProvider()
  2. providers/__init__.py 的 _MODULES 加一行
  3. auth/ 下加 your_tool_auth.py,从 _common 拿现成骨架, 并导出 collect(full=False) -> (values, summary) —— 一键导出就是靠它自动带上新工具
  4. 密钥仓库加一组 YOUR_TOOL_* 变量

第 4 步的变量名要登记到 tests/selftest.py 的 test_real_required_env_names —— 写错变量名线上会永远 skip 且没人发现,这条断言就是防这个的。

工具自己的附加动作(如 WorkBuddy 的派遣)放在它自己的文件或同级模块里, 不要往 runner.py 里加平台判断 —— runner 的职责只有「逐个跑、逐个兜错、统一收口」。


八、本地自测

不联网,验证装配是否正确(含命名约定、变量对齐、AES 后端对拍):

cd tools/ai-checkin && python3 tests/selftest.py

手动跑一次真实签到(会消耗当天签到机会,注意幂等):

cd tools/ai-checkin
python3 runner.py --list            # 看哪些工具已配置
python3 runner.py workbuddy         # 只跑一个
python3 runner.py                   # 跑全部

九、目录结构

.
├── .cnb.yml                       # 流水线:00:02 主跑 / 10:02 Qoder / 手动 / 知识库
├── README.md                      # 本文档
└── tools/ai-checkin/
    ├── runner.py                  # 汇总调度器:逐个跑、逐个兜错、统一摘要、发通知
    ├── notifier.py                # 多渠道通知(标准库,配置由环境变量注入)
    ├── export_auth.py             # 一键导出:各平台凭据合成一份密钥文件
    ├── secrets.example.yml        # 密钥仓库文件模板(手写时的变量清单)
    ├── auth/                      # 取凭据:本机跑,生成密钥文件
    │   ├── _common.py             # 公共骨架:授权推导 + YAML 生成/合并 + 脱敏输出
    │   ├── _workbuddy_atrest.py   # WorkBuddy 登录态解密器
    │   ├── workbuddy_auth.py
    │   ├── trae_auth.py
    │   ├── qoder_auth.py
    │   ├── minimax_auth.py        # 网页凭据:--from-browser 直读 / --renew 续期
    │   └── baidu_auth.py          # 百度搭子:读客户端 auth.json + .cookie-key 解密(L1/L2/L3 分层)
    ├── providers/                 # 云端签到:零第三方依赖
    │   ├── __init__.py            # 工具注册表
    │   ├── base.py                # Provider 基类与环境变量取值
    │   ├── authfail.py            # 凭据失效判定:401/403、业务码 1001/10001 → skip
    │   ├── workbuddy.py           # 签到,签完调用 travel
    │   ├── travel.py              # 派遣 Buddy 旅行(域名与签到不同,故独立)
    │   ├── trae.py
    │   ├── qoder.py
    │   ├── minimax.py             # MiniMax Code 网页签到(网关签名 + user_id 自愈 + 401 自动续期)
    │   └── baidu.py               # 百度搭子(DuMate)签到领积分(Cookie 鉴权)
    └── tests/selftest.py          # 离线回归自检

十、常见问题

现象原因与处理
skip · 缺少凭据 X该工具未配置,正常。补密钥仓库变量即可
skip · Token 失效凭据过期(401/403 或业务码 1001)。不刷红流水线,但也没签到 —— 重跑 export_auth.py 更新密钥仓库
error + 9074(TRAE)设备ID 不对,必须是客户端 AHA SDK 的纯数字 did,不是 telemetry.devDeviceId
Prepare 阶段 allow_events 报错密钥里的 allow_events 与 .cnb.yml 事件键不一致,用脚本重新生成
Qoder 永远「无可领活动」活动每日 10:00 重置;确认跑在 10:02 那条 cron
构建整体失败但只有一家挂了runner 口径是「有 error 就非 0」,看摘要定位具体工具;若是 skip · Token 失效 则不会失败,属提醒
取凭据报「未找到登录态」对应客户端没登录,或没和脚本在同一用户下运行
export_auth.py 只导出了一部分正常:没装客户端的平台取不到。文件头会写明缺席的是谁
覆盖产物时被拒(平台变少)防「越跑越秃」:确认要删就加 --force
WorkBuddy 有签到但没派遣看 detail.travel.available:false 是旅行接口没通(签到没坏);WORKBUDDY_TRAVEL=off 则是自己关掉的
一次通知都没收到先看启动那几行 通知:...:显示「未配置」= 没注入(属预期);显示已配置但模式是 error 且这次全成功,那本来就不发
通知里出现 notify_error通知模块自己抛了(签名/配置/网络)。签到结论不受影响,看这行定位
派遣多打了一个请求 / 404旅行路径不带 /v2、域名默认 www.workbuddy.cn,两者都别跟签到混
skip · 鉴权失败(MiniMax)token 与 user_id 有一个不对(401 只看「token 过期」会误判)。用 auth/minimax_auth.py --from-browser 重新导出
error + 400 签名无效(MiniMax)网页端公共参数/签名算法变了,看 providers/minimax.py 的签名段
error + 401 缺少凭据(MiniMax)token 压根没注入,不是「过期」。见下面「刚配置完就 401」
MiniMax 签到总在 401 但 token 刚抄多半是 user_id 缺失/为 0,不是 token 过期。换 --from-browser(见「401 的三种成因」)
百度搭子 skip · 登录态失效Cookie 过期(常态):开一次 DuMate 客户端重登,再跑 auth/baidu_auth.py 重导。客户端不重登,云端换不了这份 cookie
百度搭子导出时报「读不出 Cookie」先跑 python auth/baidu_auth.py --check:它会把探测过的候选路径列全(哪个目录不存在 / 有 auth.json 但没 .cookie-key / 有文件但解不开)。多数是跨了 Windows 账户,或客户端没真正登录过。--cookie 可手给兜底
百度搭子「未找到登录态」(新版已修)早期按 Electron safeStorage 解、而客户端其实是 .cookie-key + AES-256-GCM(issue #31),密文永远解不开。现按实测契约解,并会打印 解密密钥: ….cookie-key 供确认

凭据多久换一次

平台到期信号日志里能提前看到吗
WorkBuddyHTTP 401/403 或业务码 1001能:WORKBUDDY_EXPIRES_AT(客户端 expiresAt)→ JWT exp
TRAE返回 code=1001能:TRAE_EXPIRES_AT(客户端 expiredAt)→ JWT exp
Qoder活动列表返回 401能:QODER_EXPIRES_AT(登录态 expiresAt)→ JWT exp
MiniMax Code签到接口 401(user_id 缺失也会 401;脚本先补 user_id、再自动续期)不能:_token 不是 JWT,也没有到期字段,不打
百度搭子HTTP 401/403 或业务码 10001/10006(按 skip 收口)Cookie 里真带 expires 才打(BAIDU_EXPIRES_AT),会话 Cookie 不打

每个平台各自打自己登录态里真实的到期时间,几家的天数本来就不一样 —— 这正常。 providers/expiry.py 只做一件事:把凭据里真实存在的那个时刻打出来。 取值的顺序是「客户端自己写的最先,其次才是解 token」,因为客户端那份更贴真实失效点:

  • WorkBuddy:WORKBUDDY_EXPIRES_AT(客户端 expiresAt)→ JWT exp;签发时间 WORKBUDDY_ISSUED_AT → JWT iat
  • TRAE:TRAE_EXPIRES_AT(客户端 expiredAt)→ JWT exp
  • Qoder:QODER_EXPIRES_AT(登录态 expiresAt)→ JWT exp
  • 百度搭子:BAIDU_EXPIRES_AT(Cookie 自己的 expires,取最早那个)→ 没有就不打
  • MiniMax:_token 不是 JWT、也没到期字段 → 静默(属预期)

auth/*_auth.py 导出凭据时会把这些真实到期时刻一并带走(读不到就不带), 所以云端拿到的就是本机登录态那一刻的真实值,不经过任何推算。

各平台那行由 providers/expiry.py 在每次签到前打印,例如:

WorkBuddy 登录态有效期至 2027-01-16 10:10(签发于 2026-10-13 10:10,已过 0.0 天 / 剩余 95.0 天)
TRAE 登录态有效期至 2026-10-25 10:10(剩余 12.0 天)
Qoder 登录态有效期至 2026-11-12 10:10(剩余 30.0 天)
百度搭子 登录态有效期至 2026-11-02 10:10(剩余 20.0 天)

(上面四行的天数各不相同 —— 这正是「按真实的打印」:有几家就几家,天数互不相干。 数字仅示意形态,实际以当次凭据为准。)

只打真实数据,不推算。有效期与签发时间都来自凭据自身携带的事实; 拿不到任何线索时一行都不打 —— 绝不编数字。

为什么要打「签发于 …,已过 X 天」:只有「剩余 X 天」时,刚换完凭据却看到 剩 27 天会让人怀疑「是不是没换上」;有签发时间就一眼分明 —— 刚换是「签发于 今天」,没换则签发时间不动。

早先版本为了算「已过」,拿一个假设的寿命(已过 = 55 - 剩余)去反推。 那是编的:寿命是猜的、剩余是算的,两个不确定量一相减,得到的数只对 「寿命恰好 55 天」成立。现在改成认 iat;JWT 里没有 iat 就省掉这半句, 只报真实剩余天数,绝不拿假设凑数(LIFETIME_DAYS 这个口子已删除,selftest 盯着)。

不足 7 天时追加一行 ::warning::,日志页会高亮:

::warning::<平台> 登录态剩余不足 7 天(5.2 天),请在电脑上打开客户端刷新后重新导出凭据更新密钥仓库

(哪家不足 7 天就报哪家 —— WorkBuddy / TRAE / Qoder / 百度搭子 只要打得出真实 到期时刻,都会走这条门槛;MiniMax 打不出有效期,自然不会报。)

毫秒戳(13 位)会先归一化成秒,否则会被当成公元 5 万年。这个提示纯属旁路: 字段畸形、缺失都只是不打印,绝不影响签到结果与退出码。

换完凭据想确认「到底换没换」:看日志那行的「签发于」是不是变成了今天。 只重传了密钥文件却忘了客户端重新登录,token 不变,签发时间也不会变。

客户端大版本升级后建议重新取一次 —— 设备指纹和登录态格式都可能变。