Files
AgentsMeeting/gateway/scripts/specs/usage_monitor.json
T
hmo fefdc29d06 feat: OpenCode Go usage monitor — hybrid collector + 246-centric dashboard
- usage_collector.py v3.0: HTTP+stored cookies (primary) + CDP (bonus)
  - Cookie keepalive: captures Set-Cookie from responses, saves back to JSON
  - Per-metric regex for SSR parsing (handles nested \[N]={...} structure)
  - Cache merge: preserves other accounts' data on partial collection
- extract_cookies.py: one-time CDP /cookies endpoint → per-account JSON
- dashboard.py: /api/usage reads local file (no Windows bot dependency)
  - /api/usage/refresh: local subprocess collector
  - Auto-timer: 5min background thread (cookie keepalive + fresh data)
- xmpp_agent_core.py: /usage endpoint + auto-timer (for local testing)
- dashboard.html: usage-monitor section (4 cards × 3 metrics + alerts)
- accounts.json: 4 accounts configured (key1-4, gitignored)
- .gitignore: cookies/, accounts.json, usage_stats.json
2026-07-15 22:30:20 +08:00

121 lines
12 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"module": "usage_monitor",
"version": "2.0",
"purpose": "监控 4 个 OpenCode Go 订阅账号的用量配额 (5h rolling / weekly / monthly),避免跑超限额被限流至免费模型。通过 CDP proxy 在 Chrome tab 内执行 fetch(location.href, {credentials:'include'}) 获取 SSR HTML,浏览器自动带 httpOnly cookies,无需提取/存储 cookies。",
"ui_location": "I Tab (Infrastructure) → OpenCode Go Usage section",
"human_help": {
"title": "OpenCode Go Usage Monitor",
"description": [
"4 个 OpenCode Go 订阅账号 (每个对应独立的 OAuth 登录: 2 Google + 2 GitHub) 的实时用量监控。",
"每个账号采集 3 个指标: 5h rolling (5小时滚动用量) + weekly (周用量) + monthly (月用量),含百分比+到期倒计时。",
"数据源: opencode.ai 的 SSR hydration script — 用量数据由服务端注入到页面 HTML 的 <script> 中,无公开 API。",
"采集方式: CDP-driven — 通过 CDP proxy (/eval) 在 Chrome tab 内执行 fetch(location.href, {credentials:'include'}),浏览器自动带 httpOnly cookies 获取 SSR HTML,正则解析 rollingUsage/weeklyUsage/monthlyUsage 的 usagePercent + resetInSec。",
"数据更新: 采集脚本每 5 分钟跑一次 (--daemon 模式) 或仪表板手动点击 Refresh 触发按需采集 (collect_now)。",
"告警: 任一账号任一指标 usagePercent > 80% 时卡片标橙;> 95% 时标红并提示考虑切号。",
"架构关键: Chrome 单 profile 同时只能登录 1 个 opencode 账号 → 每次采集只更新当前登录账号的数据,其他账号保留上次采集值。用户切换账号后触发 Refresh 即可更新新账号数据。"
],
"participants": [
{"name": "老莫", "device": "Windows 192.168.1.16", "role": "4 个 opencode 账号的持有者 + Chrome 登录态提供方 + dashboard 使用者", "note": "运行 usage_collector.py + xmpp_bot /usage endpoint (5807) + CDP proxy (3456)"},
{"name": "莫荷", "device": "Linux 192.168.1.246", "role": "dashboard 服务 (5803) 代理 /api/usage 到 Windows xmpp_bot", "note": "通过 _bridge_post(/usage) 代理到 Windows:5807"},
{"name": "Aliyun 47.115.32.206", "device": "Aliyun 公网服务器", "role": "Dashboard 前端访问入口 (ags.yoin.fun via nginx → 5803)", "note": "公网入口"}
],
"usage": [
"1. 首次配置: 逐个登录 4 个 opencode 账号到 Windows Chrome → 从 URL 获取 workspace ID (wrk_XXX) 填入 accounts.json → 触发 Refresh 采集",
"2. 采集脚本自动每 5min 运行 (--daemon),或仪表板点击 [Refresh Now] 按钮触发按需采集 (collect_now)",
"3. 仪表板显示 4 张卡片,每张卡片含 5h / weekly / monthly 三个进度条 + 倒计时",
"4. 当用量超 80% 卡片变橙,超 95% 变红 + 告警文字",
"5. 当某账号 session 过期 (Chrome tab 跳转到 auth.opencode.ai/authorize),卡片显示 'session expired — 请重新登录' 提示",
"6. 切换账号采集: 在 Chrome 中登录另一个 opencode 账号 → 点击 Refresh → 新账号数据更新,旧账号保留上次值"
],
"troubleshooting": [
"如果卡片显示 'session expired': 在 Windows Chrome 中重新登录该 opencode 账号,然后点击 Refresh",
"如果所有账号都显示过期: Chrome 可能已关闭或 CDP proxy (3456) 未运行 — 检查 Chrome 进程和 cdp-proxy.mjs",
"如果 Refresh 按钮无响应: 检查 xmpp_bot (Windows:5807) 是否 LISTENING + dashboard (246:5803) 的 _bridge_post 是否能联通 Windows",
"如果某账号 usagePercent 为 null 且无过期提示: 可能该账号没有 Go 订阅,或 SSR hydration 脚本结构变更 — 需手动在 Chrome 中打开页面验证",
"采集数据过旧 (更新时间 > 10min): 检查 usage_collector.py --daemon 是否在运行,或 CDP proxy 是否正常",
"CDP proxy 不可达: 确认 Chrome 以 --remote-debugging-port=9222 启动 + cdp-proxy.mjs (localhost:3456) 在运行"
],
"related": "依赖 xmpp_bot HTTP bridge (5807) — 与 EasyTier/RDP 共享同一 bridge 端口; 依赖 CDP proxy (3456) — 与 web-access skill 共享"
},
"ai_spec": {
"apis": [
{"method": "POST", "path": "/usage", "body": "{\"action\":\"status\"}", "returns": "{ok, accounts: [{key_id, workspace_id, label, rolling, weekly, monthly, last_update, session_expired, error}], last_refresh, collected_key_id}", "note": "读取缓存,即时返回"},
{"method": "POST", "path": "/usage", "body": "{\"action\":\"collect_now\"}", "returns": "{ok, message, triggered_at}", "note": "触发后台采集,~10-15s后 poll status 看结果"},
{"method": "GET", "path": "/api/usage", "returns": "同 /usage action=status", "proxied_to": "xmpp_bot /usage action=status via _bridge_post"},
{"method": "POST", "path": "/api/usage/refresh", "body": "{}", "returns": "同 /usage action=collect_now", "proxied_to": "xmpp_bot /usage action=collect_now via _bridge_post"}
],
"dependencies": [
"xmpp_bot on Windows 192.168.1.16:5807 — /usage HTTP endpoint (POST, 触发采集+读取缓存)",
"_bridge_post() + _BRIDGE_KEY in dashboard.py — proxy 机制同 EasyTier/RDP",
"gateway/scripts/usage_collector.py (Windows) — Python CDP-driven 采集脚本",
"gateway/scripts/usage_monitor/accounts.json — workspace ID × key_id × label 映射 (配置文件, .gitignore)",
"CDP proxy (cdp-proxy.mjs) localhost:3456 — Chrome 远程调试代理 (/eval, /targets, /navigate)",
"Chrome (Windows) with --remote-debugging-port=9222 — 单 profile, 当前登录 1 个 opencode 账号",
"Python 3.10+ (stdlib only: urllib, json, re, logging) — 无第三方依赖"
],
"architecture": {
"flow": "Dashboard(246:5803) → _bridge_post() → xmpp_bot(Windows:5807) → 触发/读取 usage_collector.py 采集结果",
"collection_mechanism": "usage_collector.py: 1) CDP /targets 查找 opencode.ai workspace tab → 2) CDP /eval 执行 JS: fetch(location.href, {credentials:'include'}) → 浏览器自动带 httpOnly cookies → 3) 从 SSR HTML 正则匹配 rollingUsage:$R[N]={status,usagePercent,resetInSec} (per-metric regex, 处理嵌套 $R[N]={...}) → 4) 合并已有缓存(保留其他账号上次值) → 5) 写入 temp/usage_stats.json",
"pipeline": "CDP /eval + fetch → SSR HTML → 正则解析 → 合并缓存 → temp/usage_stats.json → xmpp_bot 读取缓存 → _bridge_post 代理回 dashboard → HTML 卡片渲染",
"participants": {
"windows_192_168_1_16": {"role": "采集执行 + CDP proxy + xmpp_bot /usage 端点 (5807)", "agent": "xxm"},
"linux_246": {"role": "Dashboard 服务 (5803) 代理 /api/usage 到 Windows xmpp_bot", "agent": "mohe"},
"opencode_ai_servers": {"role": "数据源; SSR hydration 注入了 lite.subscription.get 资源到页面 HTML <script>; tab 跳转到 auth.opencode.ai/authorize 表示 session 失效"}
},
"cdp_driven_mechanism": {
"key_insight": "httpOnly cookies 无法通过 document.cookie 提取,但 fetch(location.href, {credentials:'include'}) 从浏览器内部发起请求时会自动带上所有 cookies (含 httpOnly) → 无需提取/存储 cookies",
"collection_flow": "CDP /targets → 找 opencode.ai tab → CDP /eval → JS fetch(location.href, {credentials:'include'}) → 获取 SSR HTML → 正则解析 usagePercent + resetInSec",
"single_account_limitation": "Chrome 单 profile 同时只能登录 1 个 opencode 账号 → 每次采集只更新当前登录账号; 其他账号保留上次值; 用户切换账号后 Refresh 即可采集新账号"
}
},
"constraints": [
"采集必须通过 CDP /eval + fetch 在 Chrome tab 内执行 — 不能用 Python requests + cookies (httpOnly cookies 无法提取)",
"采集脚本必须在 Windows 执行 (CDP proxy 连接本机 Chrome :9222)",
"Chrome 必须以 --remote-debugging-port=9222 启动 + cdp-proxy.mjs (3456) 必须运行",
"Workspace ID 必须从已登录的 opencode.ai 页面 URL 提取 — 不要手动猜测",
"解析 SSR 时用 per-metric regex (rollingUsage:.*?\\{[^}]*\\}) 而非 blob regex — SSR 格式是嵌套 $R[N]={...}, blob regex 在遇到内层 } 时会断",
"采集频率: 默认 5min — 过密会触发 opencode.ai 风控",
"session_expired 判定: Chrome tab URL 包含 'auth.opencode.ai' 或 'auth/authorize' → session 失效",
"accounts.json 不可提交 git — 已加入 .gitignore (含 workspace IDs)"
],
"must_not": [
"不要把 accounts.json 提交到 git — 已加入 .gitignore",
"不要在采集脚本中硬编码 workspace ID — 必须从 accounts.json 配置读取",
"不要尝试用 Python requests + 提取的 cookies 采集 — httpOnly cookies 无法通过 document.cookie 或 CDP /eval 提取",
"不要用 blob regex 解析 SSR — 嵌套 $R[N]={...} 会导致 [^}]* 在内层 } 处断; 用 per-metric regex 替代",
"不要重写整个 fI() 函数 在 dashboard.html — 用 create-once/update-state pattern 像 EasyTier/RDP 那样添加 usage section",
"不要在 CDP /eval 中返回整个 HTML (~16KB) — 尽量在 JS 中提取关键数据后返回小 JSON (当前实现返回完整 HTML, 可优化)"
],
"related_modules": [
{"module": "easytier", "relation": "共享 xmpp_bot HTTP bridge (5807); EasyTier VPN 不影响 usage 监控,但二者均通过 _bridge_post 代理触发"},
{"module": "rdp", "relation": "共享 xmpp_bot HTTP bridge (5807); 采集脚本运行在 Windows 同一机器上,但功能相互独立"}
],
"tests": [
{"id": "UM01", "name": "POST /usage action=status returns accounts array", "endpoint": "POST /usage {action:status} — 期望 response.accounts 是数组, 含 key_id/rolling/weekly/monthly 字段"},
{"id": "UM02", "name": "POST /usage action=collect_now triggers CDP collection", "endpoint": "POST /usage {action:collect_now} — 期望 response.ok=true, 几秒后 cache 文件更新"},
{"id": "UM03", "name": "Session expired account shows auth redirect message", "endpoint": "POST /usage — 当 tab 在 auth.opencode.ai 时, account.error 应含 'session expired'"},
{"id": "UM04", "name": "Usage cards render without flicker on consecutive polls", "endpoint": "连续 3 次 GET /api/usage — dashboard 卡片不应闪烁/recreate DOM"},
{"id": "UM05", "name": "High-usage warning displays when usagePercent > 80", "endpoint": "GET /api/usage — usagePercent > 80 卡片应标橙;> 95 标红"}
],
"known_issues": [
"opencode.ai Go 用量目前无公开 API — PR #16513 未上线,数据唯一来源是 SSR hydration script",
"Chrome 单 profile 限制: 一次只能登录一个 opencode 账号 → 每次采集只更新当前登录账号; 需用户手动切换账号",
"Session 有效期: opencode.ai session cookies 可能随时过期 → 采集脚本检测 tab URL 是否在 auth.opencode.ai 来判定 session_expired",
"CDP proxy 依赖: 若 Chrome 关闭或 cdp-proxy.mjs 未运行,采集将失败 — 需确保两个进程都在运行",
"HTML 返回大小: 当前 CDP /eval 返回完整 SSR HTML (~16KB), 未来可优化为 JS 端提取关键数据后返回小 JSON"
],
"related_files": [
"gateway/scripts/usage_collector.py — Python CDP-driven 采集脚本 (v2.0, 重写)",
"gateway/scripts/usage_monitor/accounts.json — workspace ID 配置 (.gitignore)",
"gateway/scripts/dashboard.py — /api/usage + /api/usage/refresh 端点 + _bridge_post 代理",
"gateway/scripts/templates/dashboard.html — fI() function 中添加 usage section (create-once pattern)",
"gateway/scripts/specs/usage_monitor.json — 本 spec 文件",
"xmpp_agent_core.py — /usage HTTP endpoint in _BridgeHandler.do_POST()",
"gateway/temp/usage_stats.json — 采集结果缓存文件 (.gitignore)",
".opencode/skills/web-access/scripts/cdp-proxy.mjs — CDP proxy (localhost:3456, 提供 /eval /targets /navigate)"
]
}
}