// 方案2:把 coderelay 的 auth 文件转换为 wb-switch 的 accounts.json 记录格式 // 依据 wb-switch-rust.exe 中提取的结构字符串: // Account 字段: id uid nickname email access_token refresh_token token_type // expiresAt refreshExpiresAt refreshedAt createdAt domain // enterpriseId enterpriseName variant needs_relogin needs_relogin_reason // auth_raw profile_raw // credit_usage_snapshots: { accountId, accountName, variant, remaining, total, ts } import fs from 'node:fs'; import path from 'node:path'; const AUTH_DIR = 'C:/Users/admin/AppData/Roaming/com.coderelay.desktop/sidecar-runtime/auths'; const WB_DIR = 'C:/Users/admin/.wb-switch'; const ACCOUNTS = path.join(WB_DIR, 'accounts.json'); const SNAPSHOTS = path.join(WB_DIR, 'credit_usage_snapshots.json'); const DRY_RUN = !process.argv.includes('--apply'); // ---- 1. 读取 coderelay 的 auth 文件 ---- const authFiles = fs.readdirSync(AUTH_DIR).filter((f) => f.endsWith('.json')); const converted = []; const now = Date.now(); for (const file of authFiles) { const raw = fs.readFileSync(path.join(AUTH_DIR, file), 'utf8'); const a = JSON.parse(raw); // uid 是唯一稳定的账号标识;id 与 uid 取同一个值(wb-switch 用它关联积分快照) const id = a.uid; if (!id) { console.warn(`[skip] ${file}: 无 uid`); continue; } // JWT exp 解出过期时间,用于 expiresAt(毫秒) let expMs; try { const payload = JSON.parse( Buffer.from(a.access_token.split('.')[1], 'base64url').toString('utf8'), ); if (payload.exp) expMs = payload.exp * 1000; } catch { /* 保守处理:解不出就不写 expiresAt */ } const nickname = a.email || id; // profile_raw 承载 plan/quota 等展示字段,让界面能显示套餐与积分 const profileRaw = { planType: a.payment_type ?? null, planRank: a.plan_rank ?? null, region: a.region ?? null, enterpriseId: a.enterprise_id ?? '', baseUrl: a.base_url ?? null, type: a.type ?? null, }; converted.push({ id, uid: id, nickname, email: a.email ?? '', access_token: a.access_token, refresh_token: a.refresh_token, token_type: 'Bearer', domain: a.domain ?? 'www.codebuddy.cn', enterpriseId: a.enterprise_id ?? '', enterpriseName: '', variant: a.region === 'cn' ? 'cn' : 'ai', expiresAt: expMs ?? null, createdAt: now, refreshedAt: now, // auth_raw 保存原始认证响应,是 wb-switch 认账的关键字段 auth_raw: raw, profile_raw: JSON.stringify(profileRaw), remaining: typeof a.quota_remain === 'number' ? a.quota_remain : undefined, }); } console.log(`coderelay auth 文件: ${authFiles.length} 个,成功转换 ${converted.length} 条`); // ---- 2. 读取现有 accounts.json,保留非本次导入的账号 ---- const existing = JSON.parse(fs.readFileSync(ACCOUNTS, 'utf8')); const importedUids = new Set(converted.map((c) => c.uid)); // 残缺记录:无 id 且 uid 属于本次导入范围。 // 注意:这类记录是 wb-switch 刷新时 append 出来的孤儿(每条 token 都不同, // 并非同一 token 的复制),清除它们不会丢失任何独立账号。 const isJunk = (r) => r.id === undefined && importedUids.has(r.uid); const kept = existing.filter((r) => !isJunk(r)); const removed = existing.length - kept.length; console.log(`现有记录 ${existing.length} 条 → 清除残缺重复 ${removed} 条,保留 ${kept.length} 条`); // ---- 3. 组装新 accounts.json ---- const cleaned = converted.map(({ remaining, ...rest }) => rest); const merged = [...kept, ...cleaned]; console.log(`\n新 accounts.json 将包含 ${merged.length} 条:`); for (const r of merged) { console.log(` - ${r.nickname} | id=${r.id} | variant=${r.variant ?? '-'}`); } // ---- 4. 积分快照 ---- const snapshots = JSON.parse(fs.readFileSync(SNAPSHOTS, 'utf8')); const newSnaps = converted .filter((c) => c.remaining !== undefined) .map((c) => ({ accountId: c.id, accountName: c.nickname, remaining: c.remaining, total: 0, ts: now, variant: c.variant, })); const snapMerged = [...snapshots, ...newSnaps]; console.log(`\n积分快照 ${snapshots.length} → ${snapMerged.length} 条(新增 ${newSnaps.length})`); for (const s of newSnaps) { console.log(` - ${s.accountName} | remaining=${s.remaining} | accountId=${s.accountId}`); } // ---- 5. 写入 ---- const accountsJson = JSON.stringify(merged, null, 2); const snapJson = JSON.stringify(snapMerged, null, 2); if (DRY_RUN) { fs.writeFileSync('D:/workspace/code/fms/.tmp-probe/accounts.new.json', accountsJson); fs.writeFileSync('D:/workspace/code/fms/.tmp-probe/snapshots.new.json', snapJson); console.log('\n[DRY-RUN] 未改动 wb-switch。预览已写入 .tmp-probe/'); console.log('确认无误后加 --apply 执行'); } else { fs.writeFileSync(ACCOUNTS, accountsJson); fs.writeFileSync(SNAPSHOTS, snapJson); console.log('\n[APPLIED] 已写入 accounts.json 和 credit_usage_snapshots.json'); }