- add shared owner-only credential storage for OpenCode Go, Ollama Cloud, and Cursor - validate credentials before atomic writes using 0700 directories and 0600 files - replace provider-specific credential routes with an allowlisted lifecycle API - stop automatically reading Ollama's legacy cookie file - stop reading or modifying Cursor's database during regular quota requests - add explicit one-time Cursor credential import without mutating Cursor storage - persist refreshed Cursor credentials only in OpenChamber-managed storage - add Ollama Cloud and Cursor credential controls to provider settings - preserve OpenCode Go tracking through the shared credential flow - add VS Code credential management and Cursor quota parity - reject authentication redirects, enforce request timeouts, and fail on unparseable usage pages - mask stored secrets in API responses and extend quota security coverage - update quota provider documentation
29 lines
1.9 KiB
TypeScript
29 lines
1.9 KiB
TypeScript
type OpenCodeGoCredential = { workspaceId: string; authCookie: string };
|
|
|
|
const toWindow = (usedPercent: number, resetInSec: number) => ({
|
|
usedPercent: Math.min(100, Math.max(0, usedPercent)),
|
|
remainingPercent: 100 - Math.min(100, Math.max(0, usedPercent)),
|
|
windowSeconds: null,
|
|
resetAfterSeconds: Math.max(0, resetInSec),
|
|
resetAt: Date.now() + Math.max(0, resetInSec) * 1000,
|
|
resetAtFormatted: null,
|
|
resetAfterFormatted: null,
|
|
});
|
|
|
|
export const fetchOpenCodeGoUsage = async (credential: OpenCodeGoCredential) => {
|
|
const response = await fetch(`https://opencode.ai/workspace/${encodeURIComponent(credential.workspaceId)}/go`, { headers: { Accept: 'text/html', Cookie: `auth=${credential.authCookie}` }, redirect: 'manual', signal: AbortSignal.timeout(15_000) });
|
|
if (response.status === 401 || response.status === 403 || (response.status >= 300 && response.status < 400)) throw new Error('OpenCode Go authentication failed');
|
|
if (!response.ok) throw new Error(`OpenCode Go dashboard returned HTTP ${response.status}`);
|
|
const html = (await response.text()).replaceAll('"', '"').replaceAll('"', '"').replaceAll('\\u0022', '"').replaceAll('\\"', '"');
|
|
const windows: Record<string, ReturnType<typeof toWindow>> = {};
|
|
for (const [key, field] of Object.entries({ '5h': 'rollingUsage', weekly: 'weeklyUsage', monthly: 'monthlyUsage' })) {
|
|
const body = html.match(new RegExp(`["']?${field}["']?\\s*:\\s*(?:\\$R\\[\\d+\\]\\s*=\\s*)?\\{([^{}]*)\\}`, 's'))?.[1];
|
|
if (!body) continue;
|
|
const used = Number(body.match(/usagePercent\s*:\s*["']?(-?\d+(?:\.\d+)?)/)?.[1]);
|
|
const reset = Number(body.match(/resetInSec\s*:\s*["']?(-?\d+(?:\.\d+)?)/)?.[1]);
|
|
if (Number.isFinite(used) && Number.isFinite(reset)) windows[key] = toWindow(used, reset);
|
|
}
|
|
if (!Object.keys(windows).length) throw new Error('OpenCode Go usage data could not be parsed');
|
|
return windows;
|
|
};
|