(id)
| 1419 | * Errors are returned in-band so the dashboard can show them without throwing. |
| 1420 | */ |
| 1421 | export async function refreshCredits(id) { |
| 1422 | const account = accounts.find(a => a.id === id); |
| 1423 | if (!account) return { ok: false, error: 'Account not found' }; |
| 1424 | try { |
| 1425 | const { getUserStatus } = await import('./windsurf-api.js'); |
| 1426 | const proxy = getEffectiveProxy(account.id) || null; |
| 1427 | const status = await getUserStatus(account.apiKey, proxy); |
| 1428 | // Drop the huge raw payload before persisting — keep it only in memory for |
| 1429 | // downstream callers (e.g. model catalog cache) to inspect once. |
| 1430 | const { raw, ...persist } = status; |
| 1431 | account.credits = persist; |
| 1432 | // Tier hint: if the plan info is explicit, prefer it over capability probing. |
| 1433 | // Trial / individual accounts also count as pro — Windsurf returns |
| 1434 | // "INDIVIDUAL" / "TRIAL" / similar for paid-tier trials (issue #8 follow-up: |
| 1435 | // motto1's 14-day Pro trial was misclassified as free because planName |
| 1436 | // wasn't "Pro"). |
| 1437 | const pn = status.planName || ''; |
| 1438 | if (/pro|teams|enterprise|trial|individual|premium|paid/i.test(pn)) { |
| 1439 | if (account.tier !== 'pro') account.tier = 'pro'; |
| 1440 | } else if (/free/i.test(pn)) { |
| 1441 | if (account.tier === 'unknown') account.tier = 'free'; |
| 1442 | } |
| 1443 | saveAccounts(); |
| 1444 | // Surface the raw response once so the caller can decide whether to mine |
| 1445 | // the bundled model catalog from it. |
| 1446 | return { ok: true, credits: persist, raw }; |
| 1447 | } catch (e) { |
| 1448 | const msg = e.message || String(e); |
| 1449 | log.warn(`refreshCredits ${id} failed: ${msg}`); |
| 1450 | // Stash the error on the account so the dashboard can show "last refresh |
| 1451 | // failed" without losing the previously successful snapshot. |
| 1452 | if (account.credits) account.credits.lastError = msg; |
| 1453 | else account.credits = { lastError: msg, fetchedAt: Date.now() }; |
| 1454 | return { ok: false, error: msg }; |
| 1455 | } |
| 1456 | } |
| 1457 | |
| 1458 | export async function refreshAllCredits({ skipBusy = false } = {}) { |
| 1459 | const results = []; |
no test coverage detected