diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 025ed4a..317d668 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -21,17 +21,22 @@ jobs: with: node-version: "20" registry-url: "https://registry.npmjs.org" + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Enable Corepack + run: corepack enable - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Build - run: npm run build + run: pnpm run build - name: Run unit tests - run: npm run test:unit -- --runInBand + run: pnpm run test:unit -- --runInBand - name: Publish to npm - run: npm publish --access public + run: npm publish --access public --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index a4886c1..12d0029 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ node auto-login/credentials.json auto-login/debug_*.png auto-login/__pycache__/ +/.idea/ +/dist/ +/web/node_modules/ +opencode.json +/.playwright-cli/ +/.agents/ diff --git a/README.md b/README.md index e961712..233ba61 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # opencode-multi-auth-codex -Multi-account OAuth rotation plugin for OpenCode with a local dashboard, force mode, weighted settings, limits probing, and reliability hardening. -image +Multi-account OAuth rotation plugin for OpenCode with a localhost dashboard, force mode, weighted rotation, sticky-session admin, account re-auth, and reliability hardening. +image ## Documentation map @@ -15,25 +15,28 @@ Multi-account OAuth rotation plugin for OpenCode with a local dashboard, force m - Rotates requests across multiple ChatGPT/Codex OAuth accounts. - Keeps a local account store with migration, validation, and atomic writes. -- Provides a localhost dashboard to manage accounts and limits. +- Provides a localhost React/Vite dashboard to manage accounts, limits, rotation presets, force mode, re-auth, and sticky sessions. - Supports force mode (pin one alias), account enable/disable, and re-auth. -- Supports settings-driven rotation strategy (`round-robin`, `least-used`, `random`, `weighted-round-robin`). +- Supports settings-driven rotation strategy (`round-robin`, `least-used`, `random`, `weighted-round-robin`) and account weights. - Probes limits safely and keeps authoritative data quality rules. -- Gates non-core Antigravity features behind a feature flag. +- Gates non-core Antigravity and sticky-session features behind feature flags. ## Current implementation status - Core phases A-G are implemented in this workspace. -- Validation scripts are available for: unit, integration, web-headless, failure, stress, sandbox, soak. +- The dashboard is a React/Vite app with dedicated Overview, Accounts, Configuration, Operations, and Sticky Session views. +- Validation scripts are available for: unit, integration, web-headless, frontend, failure, stress, sandbox, soak. - Web hardening fixes are in place: - localhost-only bind enforcement - malformed JSON returns deterministic `400` without process crash - dashboard client script parse issue fixed +- Runtime UI/API coverage includes account re-auth, enabled/disabled gating, rotation presets, and sticky-session admin endpoints. ## Behavior guarantees (latest) - Rate-limit handling sleeps an alias until reset when reset timing is known (`Retry-After`, rate-limit window reset, or parsed provider reset text), instead of retrying that alias immediately. - Force mode is strict: when enabled, requests stay pinned to the forced alias and do not silently fall back to other aliases. +- Force mode is time-bound: it pins one alias for up to 24 hours and restores the previous strategy when cleared. - Rotation strategy control is shown next to Force Mode in the dashboard. - Strategy changes from dashboard settings are applied to runtime selection logic (not just persisted state/UI display). - Force Mode and strategy interaction is explicit: @@ -41,6 +44,7 @@ Multi-account OAuth rotation plugin for OpenCode with a local dashboard, force m - saved strategy becomes active when Force Mode is turned OFF - Dashboard controls include mouseover help text for Force Mode and rotation strategy definitions. - Account enable/disable toggle is authoritative for eligibility in rotation. +- Sticky-session routing is opt-in and persisted separately from the main rotation settings. ## Rotation strategy reference @@ -53,6 +57,7 @@ Multi-account OAuth rotation plugin for OpenCode with a local dashboard, force m ## Repository structure - `src/` -> TypeScript source +- `web/` -> React/Vite dashboard workspace - `dist/` -> compiled output (`tsc` generated) - `tests/unit/` -> unit tests - `tests/integration/` -> integration tests @@ -62,6 +67,7 @@ Multi-account OAuth rotation plugin for OpenCode with a local dashboard, force m - `tests/sandbox/` -> sandbox isolation tests - `tests/soak/` -> soak scaffolding - `docs/` -> QA and phase documentation (see `docs/README.md` for canonical/historical split) +- `openspec/` -> archived and current design/spec artifacts - `IMPLEMENTATION_PLAN.md` -> full plan and contracts - `TEST_EXECUTION_PLAN.md` -> required test order and gates - `codextesting.md` -> live testing TODO for Codex CLI sessions @@ -70,9 +76,10 @@ Multi-account OAuth rotation plugin for OpenCode with a local dashboard, force m ## Requirements - Node.js 20+ -- npm +- pnpm 11.x (use Corepack or install pnpm directly) - OpenCode CLI - ChatGPT/Codex OAuth accounts +- Python 3.9+ and Playwright if you use `auto-login/` ## Install and use @@ -138,8 +145,9 @@ Update existing installs: ```bash git clone https://github.com/guard22/opencode-multi-auth-codex.git cd opencode-multi-auth-codex -npm ci -npm run build +corepack enable +pnpm install --frozen-lockfile +pnpm run build ``` ### Quick start @@ -299,6 +307,10 @@ Outlook login often shows interstitial pages after password entry: - `PUT /api/settings/feature-flags` - `POST /api/settings/reset` - `POST /api/settings/preset` +- `GET /api/sticky-sessions/config` (feature-flag gated) +- `PUT /api/sticky-sessions/config` (feature-flag gated) +- `GET /api/sticky-sessions/status` (feature-flag gated) +- `POST /api/sticky-sessions/cleanup` (feature-flag gated) - `POST /api/antigravity/refresh` (feature-flag gated) - `POST /api/antigravity/refresh-all` (feature-flag gated) @@ -320,6 +332,7 @@ Outlook login often shows interstitial pages after password entry: - `OPENCODE_MULTI_AUTH_TOKEN_FAILURE_COOLDOWN_MS` - `OPENCODE_MULTI_AUTH_PROBE_EFFORT` - `OPENCODE_MULTI_AUTH_LIMITS_PROBE_MODELS` +- `OPENCODE_MULTI_AUTH_USAGE_BASE_URL` ### Model mapping and runtime behavior @@ -391,6 +404,7 @@ See [docs/gpt-5.4-fast-benchmark.md](./docs/gpt-5.4-fast-benchmark.md) for a con ### Feature flags - `OPENCODE_MULTI_AUTH_ANTIGRAVITY_ENABLED` +- `OPENCODE_MULTI_AUTH_STICKY_SESSIONS_ENABLED` ### Notifications @@ -412,18 +426,19 @@ See [docs/gpt-5.4-fast-benchmark.md](./docs/gpt-5.4-fast-benchmark.md) for a con ## Build and test ```bash -npm ci -npm run lint -npm run build -npx tsc --noEmit - -npm run test:unit -npm run test:integration -npm run test:web:headless -npm run test:failure -npm run test:stress -npm run test:sandbox -npm run test:soak:48h +corepack enable +pnpm install --frozen-lockfile +pnpm run verify:static +pnpm run build + +pnpm run test:unit +pnpm run test:integration +pnpm run test:web:headless +pnpm run test:frontend +pnpm run test:failure +pnpm run test:stress +pnpm run test:sandbox +pnpm run test:soak:48h ``` Current test script surfaces are scaffolded and active. For true long soak, set a long duration and keep the run alive. @@ -442,27 +457,28 @@ Use `codextesting.md` for the Codex CLI live-testing checklist and copy-paste co ## Development notes -- Edit `src/*`, never hand-edit `dist/*`. -- Run `npm run build` after source changes. +- Edit `src/*` and `web/src/*`, never hand-edit `dist/*`. +- Use pnpm scripts for local verification (`pnpm run verify:static`, `pnpm run test:unit`, `pnpm run test:frontend`). - Keep manual/live tests sandboxed (temp HOME/store/auth paths). ## Release flow -- This plugin is now intended to be installed from npm, so every shipped update should bump `package.json` version and publish a new package version. Reusing the same version on a new commit will leave users stuck on cached installs. -- Prepare the next release by bumping the package version, rebuilding, and publishing: +- Releases are published from `v*` tags through `.github/workflows/publish-npm.yml`. +- The workflow uses pnpm for install/build/test, then publishes to npm with provenance. +- Reusing the same version on a new commit will leave users stuck on cached installs, so bump `package.json` before tagging. +- Prepare the next release by bumping the package version, rebuilding, and tagging: ```bash -npm version 1.2.1 --no-git-tag-version -npm install -npm run build -npm publish --access public +pnpm version X.Y.Z --no-git-tag-version +pnpm install --frozen-lockfile +pnpm run build ``` - After that, cut the git release from `main`: ```bash -git commit -m "chore: release v1.2.1" -git tag v1.2.1 +git commit -m "chore: release vX.Y.Z" +git tag vX.Y.Z git push origin main --follow-tags ``` @@ -470,7 +486,7 @@ git push origin main --follow-tags ```json { - "plugin": ["npm:@guard22/opencode-multi-auth-codex@1.2.1"] + "plugin": ["npm:@guard22/opencode-multi-auth-codex@X.Y.Z"] } ``` diff --git a/auto-login/auto_login.py b/auto-login/auto_login.py index 8bb3e76..ad6da77 100755 --- a/auto-login/auto_login.py +++ b/auto-login/auto_login.py @@ -49,6 +49,21 @@ # Timing BETWEEN_ACCOUNTS_DELAY = 5 # seconds between accounts +ACCOUNT_METRIC_KEYS = { + "lastRefresh", + "lastSeenAt", + "lastActiveUntil", + "lastUsed", + "usageCount", + "rateLimits", + "rateLimitHistory", + "limitStatus", + "limitError", + "lastLimitProbeAt", + "lastLimitErrorAt", + "limitsConfidence", +} + def find_system_browser(): override = os.environ.get("OPENCODE_MULTI_AUTH_BROWSER") @@ -197,6 +212,10 @@ def fetch_userinfo_email(access_token): return None +def strip_metric_fields(account): + return {key: value for key, value in account.items() if key not in ACCOUNT_METRIC_KEYS} + + # ── Account store (v2 format compatible with plugin) ─────────────────────── def load_store(): if not STORE_FILE.exists(): @@ -218,7 +237,7 @@ def save_store(store): tmp = STORE_FILE.with_suffix(f".tmp-{os.getpid()}-{int(time.time() * 1000)}") with open(tmp, "w") as f: json.dump(store, f, indent=2) - tmp.rename(STORE_FILE) + tmp.replace(STORE_FILE) os.chmod(STORE_FILE, 0o600) @@ -250,12 +269,9 @@ def add_account_to_store(tokens): "accountId": account_id, "expiresAt": expires_at, "email": email, - "lastRefresh": datetime.now(timezone.utc).isoformat(), - "lastSeenAt": now, "addedAt": now, "source": "opencode", "authInvalid": False, - "usageCount": 0, "enabled": True, } @@ -264,11 +280,9 @@ def add_account_to_store(tokens): for i, acc in enumerate(store["accounts"]): if acc.get("email") == email: store["accounts"][i] = { - **acc, + **strip_metric_fields(acc), **new_account, - "usageCount": acc.get("usageCount", 0), "addedAt": acc.get("addedAt", now), - "rateLimitHistory": acc.get("rateLimitHistory", []), } save_store(store) return email, i, False diff --git a/dist/auth-sync.d.ts b/dist/auth-sync.d.ts deleted file mode 100644 index dd6ed90..0000000 --- a/dist/auth-sync.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Auth } from '@opencode-ai/sdk'; -export declare function syncAuthFromOpenCode(getAuth: () => Promise): Promise; -//# sourceMappingURL=auth-sync.d.ts.map \ No newline at end of file diff --git a/dist/auth-sync.d.ts.map b/dist/auth-sync.d.ts.map deleted file mode 100644 index bb86b0c..0000000 --- a/dist/auth-sync.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"auth-sync.d.ts","sourceRoot":"","sources":["../src/auth-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAkD5C,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDtF"} \ No newline at end of file diff --git a/dist/auth-sync.js b/dist/auth-sync.js deleted file mode 100644 index 72b0ac1..0000000 --- a/dist/auth-sync.js +++ /dev/null @@ -1,105 +0,0 @@ -import { addAccount, loadStore, updateAccount } from './store.js'; -import { decodeJwtPayload, getAccountIdFromClaims, getEmailFromClaims } from './codex-auth.js'; -const OPENAI_ISSUER = 'https://auth.openai.com'; -const AUTH_SYNC_COOLDOWN_MS = 10_000; -let lastSyncedAccess = null; -let lastSyncAt = 0; -async function fetchEmail(accessToken) { - try { - const res = await fetch(`${OPENAI_ISSUER}/userinfo`, { - headers: { Authorization: `Bearer ${accessToken}` } - }); - if (!res.ok) - return undefined; - const user = (await res.json()); - return user.email; - } - catch { - return undefined; - } -} -function findAccountAliasByToken(access, refresh) { - const store = loadStore(); - for (const account of Object.values(store.accounts)) { - if (account.accessToken === access) - return account.alias; - if (refresh && account.refreshToken === refresh) - return account.alias; - } - return null; -} -function findAccountAliasByEmail(email, store) { - for (const account of Object.values(store.accounts)) { - if (account.email && account.email === email) - return account.alias; - } - return null; -} -function buildAlias(email, existingAliases) { - const base = email ? email.split('@')[0] : 'account'; - let candidate = base || 'account'; - let suffix = 1; - while (existingAliases.has(candidate)) { - candidate = `${base}-${suffix}`; - suffix += 1; - } - return candidate; -} -export async function syncAuthFromOpenCode(getAuth) { - const now = Date.now(); - if (now - lastSyncAt < AUTH_SYNC_COOLDOWN_MS) - return; - lastSyncAt = now; - let auth = null; - try { - auth = await getAuth(); - } - catch { - return; - } - if (!auth || auth.type !== 'oauth') - return; - if (!auth.access) - return; - if (auth.access === lastSyncedAccess) - return; - lastSyncedAccess = auth.access; - const existingAlias = findAccountAliasByToken(auth.access, auth.refresh); - const accessClaims = decodeJwtPayload(auth.access); - const derivedEmail = getEmailFromClaims(accessClaims); - const derivedAccountId = getAccountIdFromClaims(accessClaims); - if (existingAlias) { - updateAccount(existingAlias, { - accessToken: auth.access, - refreshToken: auth.refresh, - expiresAt: auth.expires, - email: derivedEmail, - accountId: derivedAccountId - }); - return; - } - const store = loadStore(); - const email = (await fetchEmail(auth.access)) || derivedEmail; - if (email) { - const existingByEmail = findAccountAliasByEmail(email, store); - if (existingByEmail) { - updateAccount(existingByEmail, { - accessToken: auth.access, - refreshToken: auth.refresh, - expiresAt: auth.expires, - email - }); - return; - } - } - const alias = buildAlias(email, new Set(Object.keys(store.accounts))); - addAccount(alias, { - accessToken: auth.access, - refreshToken: auth.refresh, - expiresAt: auth.expires, - email, - accountId: derivedAccountId, - source: 'opencode' - }); -} -//# sourceMappingURL=auth-sync.js.map \ No newline at end of file diff --git a/dist/auth-sync.js.map b/dist/auth-sync.js.map deleted file mode 100644 index 33d5579..0000000 --- a/dist/auth-sync.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"auth-sync.js","sourceRoot":"","sources":["../src/auth-sync.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/auth.d.ts b/dist/auth.d.ts deleted file mode 100644 index d2d51a6..0000000 --- a/dist/auth.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { AccountCredentials } from './types.js'; -interface AuthorizationFlow { - pkce: { - verifier: string; - challenge: string; - }; - state: string; - url: string; - redirectUri: string; - port: number; -} -export interface LoginAccountOptions { - timeoutMs?: number; -} -export declare function createAuthorizationFlow(port?: number): Promise; -export declare function loginAccount(alias: string, flow?: AuthorizationFlow, options?: LoginAccountOptions): Promise; -export declare function refreshToken(alias: string): Promise; -export declare function ensureValidToken(alias: string): Promise; -export {}; -//# sourceMappingURL=auth.d.ts.map \ No newline at end of file diff --git a/dist/auth.d.ts.map b/dist/auth.d.ts.map deleted file mode 100644 index 4ce50b3..0000000 --- a/dist/auth.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAqBpD,UAAU,iBAAiB;IACzB,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAQD,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoBvF;AAkCD,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAiK7B;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAiEpF;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAc5E"} \ No newline at end of file diff --git a/dist/auth.js b/dist/auth.js deleted file mode 100644 index dbd2ff3..0000000 --- a/dist/auth.js +++ /dev/null @@ -1,283 +0,0 @@ -import { createHash, randomBytes } from 'node:crypto'; -import * as http from 'http'; -import * as url from 'url'; -import { addAccount, updateAccount, loadStore } from './store.js'; -import { clearAuthInvalid } from './rotation.js'; -import { decodeJwtPayload, getAccountIdFromClaims, getEmailFromClaims, getExpiryFromClaims, getPlanTypeFromClaims } from './codex-auth.js'; -const OPENAI_ISSUER = 'https://auth.openai.com'; -const AUTHORIZE_URL = `${OPENAI_ISSUER}/oauth/authorize`; -const TOKEN_URL = `${OPENAI_ISSUER}/oauth/token`; -const CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann'; -const DEFAULT_REDIRECT_PORTS = [1455, 1456, 1457, 1458, 1459]; -const SCOPES = ['openid', 'profile', 'email', 'offline_access']; -function getRedirectUri(port) { - return `http://localhost:${port}/auth/callback`; -} -function generatePKCE() { - const verifier = randomBytes(32).toString('base64url'); - const challenge = createHash('sha256').update(verifier).digest('base64url'); - return { verifier, challenge }; -} -export async function createAuthorizationFlow(port) { - const pkce = generatePKCE(); - const state = randomBytes(16).toString('hex'); - const redirectPort = port || DEFAULT_REDIRECT_PORTS[0]; - const redirectUri = getRedirectUri(redirectPort); - const authUrl = new URL(AUTHORIZE_URL); - authUrl.searchParams.set('client_id', CLIENT_ID); - authUrl.searchParams.set('redirect_uri', redirectUri); - authUrl.searchParams.set('response_type', 'code'); - authUrl.searchParams.set('scope', SCOPES.join(' ')); - authUrl.searchParams.set('code_challenge', pkce.challenge); - authUrl.searchParams.set('code_challenge_method', 'S256'); - authUrl.searchParams.set('state', state); - authUrl.searchParams.set('audience', 'https://api.openai.com/v1'); - authUrl.searchParams.set('id_token_add_organizations', 'true'); - authUrl.searchParams.set('codex_cli_simplified_flow', 'true'); - authUrl.searchParams.set('originator', 'codex_cli_rs'); - return { pkce, state, url: authUrl.toString(), redirectUri, port: redirectPort }; -} -function tryListenOnPort(server, port) { - return new Promise((resolve, reject) => { - const onError = (err) => { - server.off('error', onError); - reject(err); - }; - server.on('error', onError); - server.listen(port, () => { - server.off('error', onError); - resolve(); - }); - }); -} -async function findAvailablePort(server, ports) { - for (const port of ports) { - try { - await tryListenOnPort(server, port); - return port; - } - catch (err) { - if (err.code === 'EADDRINUSE') { - continue; - } - throw err; - } - } - throw new Error(`All ports ${ports.join(', ')} are in use. Stop Codex CLI if running.`); -} -export async function loginAccount(alias, flow, options) { - const ports = DEFAULT_REDIRECT_PORTS; - let activeFlow = flow; - let server = null; - const timeoutMs = Math.max(30_000, options?.timeoutMs ?? 5 * 60 * 1000); - return new Promise(async (resolve, reject) => { - let finished = false; - let timeout = null; - const cleanup = () => { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - if (server) { - server.close(); - server = null; - } - }; - const finish = (fn) => { - if (finished) - return; - finished = true; - cleanup(); - fn(); - }; - server = http.createServer(async (req, res) => { - if (!req.url?.startsWith('/auth/callback')) { - res.writeHead(404); - res.end('Not found'); - return; - } - if (!activeFlow) { - res.writeHead(500); - res.end('No active flow'); - finish(() => reject(new Error('No active flow'))); - return; - } - const parsedUrl = url.parse(req.url, true); - const code = parsedUrl.query.code; - const returnedState = parsedUrl.query.state; - if (!code) { - res.writeHead(400); - res.end('No authorization code received'); - finish(() => reject(new Error('No authorization code'))); - return; - } - if (returnedState && returnedState !== activeFlow.state) { - res.writeHead(400); - res.end('Invalid state'); - finish(() => reject(new Error('Invalid state'))); - return; - } - try { - const tokenRes = await fetch(TOKEN_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'authorization_code', - client_id: CLIENT_ID, - code, - code_verifier: activeFlow.pkce.verifier, - redirect_uri: activeFlow.redirectUri - }) - }); - if (!tokenRes.ok) { - throw new Error(`Token exchange failed: ${tokenRes.status}`); - } - const tokens = (await tokenRes.json()); - if (!tokens.refresh_token) { - throw new Error('Token exchange did not return a refresh_token'); - } - const now = Date.now(); - const accessClaims = decodeJwtPayload(tokens.access_token); - const idClaims = tokens.id_token ? decodeJwtPayload(tokens.id_token) : null; - const expiresAt = getExpiryFromClaims(accessClaims) || getExpiryFromClaims(idClaims) || now + tokens.expires_in * 1000; - let email = getEmailFromClaims(idClaims) || getEmailFromClaims(accessClaims); - try { - const userRes = await fetch(`${OPENAI_ISSUER}/userinfo`, { - headers: { Authorization: `Bearer ${tokens.access_token}` } - }); - if (userRes.ok) { - const user = (await userRes.json()); - email = user.email || email; - } - } - catch { - /* user info fetch is non-critical */ - } - const accountId = getAccountIdFromClaims(idClaims) || - getAccountIdFromClaims(accessClaims); - const planType = getPlanTypeFromClaims(idClaims) || - getPlanTypeFromClaims(accessClaims); - const store = addAccount(alias, { - accessToken: tokens.access_token, - refreshToken: tokens.refresh_token, - idToken: tokens.id_token, - accountId, - planType, - expiresAt, - email, - lastRefresh: new Date(now).toISOString(), - lastSeenAt: now, - source: 'opencode', - authInvalid: false, - authInvalidatedAt: undefined - }); - const account = store.accounts[alias]; - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(` - - -

Account "${alias}" authenticated!

-

${email || 'Unknown email'}

-

You can close this window.

- - - `); - finish(() => resolve(account)); - } - catch (err) { - res.writeHead(500); - res.end('Authentication failed'); - finish(() => reject(err)); - } - }); - try { - const actualPort = await findAvailablePort(server, ports); - if (!activeFlow || activeFlow.port !== actualPort) { - activeFlow = await createAuthorizationFlow(actualPort); - } - console.log(`\n[multi-auth] Login for account "${alias}"`); - console.log(`[multi-auth] Open this URL in your browser:\n`); - console.log(` ${activeFlow.url}\n`); - console.log(`[multi-auth] Waiting for callback on port ${actualPort}...`); - } - catch (err) { - finish(() => reject(err)); - return; - } - timeout = setTimeout(() => { - finish(() => reject(new Error(`Login timeout after ${Math.round(timeoutMs / 1000)}s - no callback received`))); - }, timeoutMs); - }); -} -export async function refreshToken(alias) { - const store = loadStore(); - const account = store.accounts[alias]; - if (!account?.refreshToken) { - console.error(`[multi-auth] No refresh token for ${alias}`); - return null; - } - try { - const tokenRes = await fetch(TOKEN_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'refresh_token', - client_id: CLIENT_ID, - refresh_token: account.refreshToken - }) - }); - if (!tokenRes.ok) { - console.error(`[multi-auth] Refresh failed for ${alias}: ${tokenRes.status}`); - if (tokenRes.status === 401 || tokenRes.status === 403) { - try { - updateAccount(alias, { - authInvalid: true, - authInvalidatedAt: Date.now() - }); - } - catch { - // ignore - } - } - return null; - } - const tokens = (await tokenRes.json()); - const accessClaims = decodeJwtPayload(tokens.access_token); - const idClaims = tokens.id_token ? decodeJwtPayload(tokens.id_token) : null; - const expiresAt = getExpiryFromClaims(accessClaims) || getExpiryFromClaims(idClaims) || Date.now() + tokens.expires_in * 1000; - const updates = { - accessToken: tokens.access_token, - refreshToken: tokens.refresh_token || account.refreshToken, - expiresAt, - lastRefresh: new Date().toISOString(), - idToken: tokens.id_token || account.idToken, - accountId: getAccountIdFromClaims(idClaims) || - getAccountIdFromClaims(accessClaims) || - account.accountId, - planType: getPlanTypeFromClaims(idClaims) || - getPlanTypeFromClaims(accessClaims) || - account.planType - }; - const updatedStore = updateAccount(alias, updates); - clearAuthInvalid(alias); - return updatedStore.accounts[alias]; - } - catch (err) { - console.error(`[multi-auth] Refresh error for ${alias}:`, err); - return null; - } -} -export async function ensureValidToken(alias) { - const store = loadStore(); - const account = store.accounts[alias]; - if (!account) - return null; - const bufferMs = 5 * 60 * 1000; - if (account.expiresAt < Date.now() + bufferMs) { - console.log(`[multi-auth] Refreshing token for ${alias}`); - const refreshed = await refreshToken(alias); - return refreshed?.accessToken || null; - } - return account.accessToken; -} -//# sourceMappingURL=auth.js.map \ No newline at end of file diff --git a/dist/auth.js.map b/dist/auth.js.map deleted file mode 100644 index 9b43592..0000000 --- a/dist/auth.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cli.d.ts b/dist/cli.d.ts deleted file mode 100644 index faaadd5..0000000 --- a/dist/cli.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node -export {}; -//# sourceMappingURL=cli.d.ts.map \ No newline at end of file diff --git a/dist/cli.d.ts.map b/dist/cli.d.ts.map deleted file mode 100644 index f022439..0000000 --- a/dist/cli.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/cli.js b/dist/cli.js deleted file mode 100644 index 2b96a4f..0000000 --- a/dist/cli.js +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env node -import { fileURLToPath } from 'node:url'; -import { loginAccount } from './auth.js'; -import { removeAccount, listAccounts, getStorePath, loadStore } from './store.js'; -import { startWebConsole } from './web.js'; -import { disableService, installService, serviceStatus } from './systemd.js'; -const args = process.argv.slice(2); -const command = args[0]; -const alias = args[1]; -function getFlagValue(flag) { - const idx = args.indexOf(flag); - if (idx === -1) - return undefined; - return args[idx + 1]; -} -async function main() { - switch (command) { - case 'add': - case 'login': { - if (!alias) { - console.error('Usage: opencode-multi-auth add '); - console.error('Example: opencode-multi-auth add work'); - process.exit(1); - } - try { - const account = await loginAccount(alias); - console.log(`\nAccount "${alias}" added successfully!`); - console.log(`Email: ${account.email || 'unknown'}`); - } - catch (err) { - console.error(`Failed to add account: ${err}`); - process.exit(1); - } - break; - } - case 'remove': - case 'rm': { - if (!alias) { - console.error('Usage: opencode-multi-auth remove '); - process.exit(1); - } - removeAccount(alias); - console.log(`Account "${alias}" removed.`); - break; - } - case 'list': - case 'ls': { - const accounts = listAccounts(); - if (accounts.length === 0) { - console.log('No accounts configured.'); - console.log('Add one with: opencode-multi-auth add '); - } - else { - console.log('\nConfigured accounts:\n'); - for (const acc of accounts) { - console.log(` ${acc.alias}: ${acc.email || 'unknown email'} (uses: ${acc.usageCount})`); - } - console.log(); - } - break; - } - case 'status': { - const store = loadStore(); - const accounts = Object.values(store.accounts); - console.log('\n[multi-auth] Account Status\n'); - console.log('Strategy: round-robin'); - console.log(`Accounts: ${accounts.length}`); - console.log(`Active: ${store.activeAlias || 'none'}\n`); - if (accounts.length === 0) { - console.log('No accounts configured. Run: opencode-multi-auth add \n'); - return; - } - for (const acc of accounts) { - const isActive = acc.alias === store.activeAlias ? ' (active)' : ''; - const isRateLimited = acc.rateLimitedUntil && acc.rateLimitedUntil > Date.now() - ? ` [RATE LIMITED until ${new Date(acc.rateLimitedUntil).toLocaleTimeString()}]` - : ''; - const expiry = new Date(acc.expiresAt).toLocaleString(); - console.log(` ${acc.alias}${isActive}${isRateLimited}`); - console.log(` Email: ${acc.email || 'unknown'}`); - console.log(` Uses: ${acc.usageCount}`); - console.log(` Token expires: ${expiry}`); - console.log(); - } - break; - } - case 'path': { - console.log(getStorePath()); - break; - } - case 'web': { - const portArg = getFlagValue('--port'); - const hostArg = getFlagValue('--host'); - const port = portArg ? Number(portArg) : undefined; - if (portArg && Number.isNaN(port)) { - console.error('Invalid --port value'); - process.exit(1); - } - startWebConsole({ port, host: hostArg }); - break; - } - case 'service': { - const action = args[1] || 'status'; - const portArg = getFlagValue('--port'); - const hostArg = getFlagValue('--host'); - const port = portArg ? Number(portArg) : undefined; - if (portArg && Number.isNaN(port)) { - console.error('Invalid --port value'); - process.exit(1); - } - const cliPath = fileURLToPath(import.meta.url); - if (action === 'install') { - const file = installService({ cliPath, host: hostArg, port }); - console.log(`Installed systemd user service at ${file}`); - break; - } - if (action === 'disable') { - disableService(); - console.log('Disabled codex-soft systemd user service.'); - break; - } - serviceStatus(); - break; - } - case 'help': - case '--help': - case '-h': - default: { - console.log(` -opencode-multi-auth - Multi-account OAuth rotation for OpenAI Codex - -Commands: - add Add a new account (opens browser for OAuth) - remove Remove an account - list List all configured accounts - status Show detailed account status - path Show config file location - web Launch local Codex auth.json dashboard (use --port/--host) - service Install/disable systemd user service (install|disable|status) - help Show this help message - -Examples: - opencode-multi-auth add personal - opencode-multi-auth add work - opencode-multi-auth add backup - opencode-multi-auth status - opencode-multi-auth web --port 3434 --host 127.0.0.1 - opencode-multi-auth service install --port 3434 --host 127.0.0.1 - -After adding accounts, the plugin auto-rotates between them. -`); - break; - } - } -} -main().catch(err => { - console.error('Fatal error:', err); - process.exit(1); -}); -//# sourceMappingURL=cli.js.map \ No newline at end of file diff --git a/dist/cli.js.map b/dist/cli.js.map deleted file mode 100644 index af79dd4..0000000 --- a/dist/cli.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;AACvB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;AAErB,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IAChC,OAAO,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;AACtB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,KAAK,CAAC;QACX,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAA;gBACvD,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAA;gBACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,CAAA;gBACzC,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,uBAAuB,CAAC,CAAA;gBACvD,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC,CAAA;YACrD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAA;gBAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,MAAK;QACP,CAAC;QAED,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAA;gBAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,aAAa,CAAC,KAAK,CAAC,CAAA;YACpB,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,YAAY,CAAC,CAAA;YAC1C,MAAK;QACP,CAAC;QAED,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAA;YAC/B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAA;gBACtC,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAA;YAC9D,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;gBACvC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;oBAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK,IAAI,eAAe,WAAW,GAAG,CAAC,UAAU,GAAG,CAAC,CAAA;gBAC1F,CAAC;gBACD,OAAO,CAAC,GAAG,EAAE,CAAA;YACf,CAAC;YACD,MAAK;QACP,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;YACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;YAE9C,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAA;YAC9C,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAA;YACpC,OAAO,CAAC,GAAG,CAAC,aAAa,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;YAC3C,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,CAAC,WAAW,IAAI,MAAM,IAAI,CAAC,CAAA;YAEvD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAA;gBAC7E,OAAM;YACR,CAAC;YAED,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,KAAK,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAA;gBACnE,MAAM,aAAa,GAAG,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE;oBAC7E,CAAC,CAAC,wBAAwB,IAAI,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,kBAAkB,EAAE,GAAG;oBAChF,CAAC,CAAC,EAAE,CAAA;gBACN,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,CAAA;gBAEvD,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,KAAK,GAAG,QAAQ,GAAG,aAAa,EAAE,CAAC,CAAA;gBACxD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC,CAAA;gBACnD,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,UAAU,EAAE,CAAC,CAAA;gBAC1C,OAAO,CAAC,GAAG,CAAC,sBAAsB,MAAM,EAAE,CAAC,CAAA;gBAC3C,OAAO,CAAC,GAAG,EAAE,CAAA;YACf,CAAC;YACD,MAAK;QACP,CAAC;QAED,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAA;YAC3B,MAAK;QACP,CAAC;QAED,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;YACtC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;YACtC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YAClD,IAAI,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAA;gBACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;YACxC,MAAK;QACP,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAA;YAClC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;YACtC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;YACtC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YAClD,IAAI,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAA;gBACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,IAAI,GAAG,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;gBAC7D,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,EAAE,CAAC,CAAA;gBACxD,MAAK;YACP,CAAC;YACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,cAAc,EAAE,CAAA;gBAChB,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAA;gBACxD,MAAK;YACP,CAAC;YACD,aAAa,EAAE,CAAA;YACf,MAAK;QACP,CAAC;QAED,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI,CAAC;QACV,OAAO,CAAC,CAAC,CAAC;YACR,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;CAsBjB,CAAC,CAAA;YACI,MAAK;QACP,CAAC;IACH,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;IAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/dist/codex-auth.d.ts b/dist/codex-auth.d.ts deleted file mode 100644 index 7af46ae..0000000 --- a/dist/codex-auth.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { loadStore } from './store.js'; -export interface CodexAuthTokens { - id_token?: string; - access_token?: string; - refresh_token?: string; - account_id?: string; -} -export interface CodexAuthFile { - OPENAI_API_KEY?: string | null; - tokens?: CodexAuthTokens; - last_refresh?: string; -} -export declare function getCodexAuthPath(): string; -export interface CodexAuthSummary { - email?: string; - accountId?: string; - accountUserId?: string; - userId?: string; - planType?: string; - expiresAt?: number; - lastRefresh?: string; - hasAccessToken: boolean; - hasRefreshToken: boolean; - hasIdToken: boolean; -} -export declare function loadCodexAuthFile(): CodexAuthFile | null; -export declare function writeCodexAuthFile(auth: CodexAuthFile): void; -export declare function decodeJwtPayload(token: string): Record | null; -export declare function getEmailFromClaims(claims: Record | null): string | undefined; -export declare function getAccountIdFromClaims(claims: Record | null): string | undefined; -export declare function getPlanTypeFromClaims(claims: Record | null): string | undefined; -export declare function getExpiryFromClaims(claims: Record | null): number | undefined; -export declare function getCodexAuthSummary(): CodexAuthSummary; -export declare function resolveAliasForCurrentAuth(store?: ReturnType): string | null; -export declare function syncCodexAuthFile(): { - alias: string | null; - added: boolean; - updated: boolean; - authEmail?: string; - authAccountId?: string; -}; -export declare function getCodexAuthStatus(): { - error: string | null; -}; -export declare function writeCodexAuthForAlias(alias: string): void; -//# sourceMappingURL=codex-auth.d.ts.map \ No newline at end of file diff --git a/dist/codex-auth.d.ts.map b/dist/codex-auth.d.ts.map deleted file mode 100644 index 0979e2a..0000000 --- a/dist/codex-auth.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"codex-auth.d.ts","sourceRoot":"","sources":["../src/codex-auth.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,SAAS,EAAiB,MAAM,YAAY,CAAA;AAGjE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAiBD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,OAAO,CAAA;IACvB,eAAe,EAAE,OAAO,CAAA;IACxB,UAAU,EAAE,OAAO,CAAA;CACpB;AAQD,wBAAgB,iBAAiB,IAAI,aAAa,GAAG,IAAI,CAWxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAK5D;AAqDD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAW1E;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMzF;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAI7F;AAiBD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAI5F;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAK1F;AAmDD,wBAAgB,mBAAmB,IAAI,gBAAgB,CA0BtD;AAED,wBAAgB,0BAA0B,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,CAuB9F;AAED,wBAAgB,iBAAiB,IAAI;IACnC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAmEA;AAED,wBAAgB,kBAAkB,IAAI;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAkC1D"} \ No newline at end of file diff --git a/dist/codex-auth.js b/dist/codex-auth.js deleted file mode 100644 index 485ce24..0000000 --- a/dist/codex-auth.js +++ /dev/null @@ -1,331 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { addAccount, loadStore, updateAccount } from './store.js'; -const CODEX_AUTH_FILE_ENV = 'OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE'; -function getCodexAuthFilePath() { - const override = process.env[CODEX_AUTH_FILE_ENV]; - if (override && override.trim()) - return path.resolve(override.trim()); - const CODEX_DIR = path.join(os.homedir(), '.codex'); - return path.join(CODEX_DIR, 'auth.json'); -} -const CODEX_DIR = path.join(os.homedir(), '.codex'); -const CODEX_AUTH_FILE = getCodexAuthFilePath(); -let lastFingerprint = null; -let lastAuthError = null; -export function getCodexAuthPath() { - return CODEX_AUTH_FILE; -} -function ensureDir() { - if (!fs.existsSync(CODEX_DIR)) { - fs.mkdirSync(CODEX_DIR, { recursive: true, mode: 0o700 }); - } -} -export function loadCodexAuthFile() { - lastAuthError = null; - if (!fs.existsSync(CODEX_AUTH_FILE)) - return null; - try { - const raw = fs.readFileSync(CODEX_AUTH_FILE, 'utf-8'); - return JSON.parse(raw); - } - catch (err) { - lastAuthError = 'Failed to parse codex auth.json'; - console.error('[multi-auth] Failed to parse codex auth.json:', err); - return null; - } -} -export function writeCodexAuthFile(auth) { - ensureDir(); - fs.writeFileSync(CODEX_AUTH_FILE, JSON.stringify(auth, null, 2), { - mode: 0o600 - }); -} -function normalizeTokens(auth) { - if (!auth || typeof auth !== 'object') - return null; - const tokens = (auth.tokens && typeof auth.tokens === 'object') ? auth.tokens : auth; - const accessToken = tokens.access_token ?? - tokens.accessToken ?? - tokens.access ?? - auth.access_token ?? - auth.accessToken ?? - auth.access; - const refreshToken = tokens.refresh_token ?? - tokens.refreshToken ?? - tokens.refresh ?? - auth.refresh_token ?? - auth.refreshToken ?? - auth.refresh; - const idToken = tokens.id_token ?? - tokens.idToken ?? - tokens.id ?? - auth.id_token ?? - auth.idToken ?? - auth.id; - const accountId = tokens.account_id ?? - tokens.accountId ?? - auth.account_id ?? - auth.accountId; - const lastRefresh = auth.last_refresh ?? auth.lastRefresh; - const result = { - accessToken: typeof accessToken === 'string' ? accessToken : undefined, - refreshToken: typeof refreshToken === 'string' ? refreshToken : undefined, - idToken: typeof idToken === 'string' ? idToken : undefined, - accountId: typeof accountId === 'string' ? accountId : undefined, - lastRefresh: typeof lastRefresh === 'string' ? lastRefresh : undefined - }; - if (!result.accessToken && !result.refreshToken && !result.idToken && !result.accountId) { - return null; - } - return result; -} -export function decodeJwtPayload(token) { - try { - const parts = token.split('.'); - if (parts.length !== 3) - return null; - const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/'); - const padded = payload.padEnd(payload.length + ((4 - (payload.length % 4)) % 4), '='); - const decoded = Buffer.from(padded, 'base64').toString('utf-8'); - return JSON.parse(decoded); - } - catch { - return null; - } -} -export function getEmailFromClaims(claims) { - if (!claims) - return undefined; - if (typeof claims.email === 'string') - return claims.email; - const profile = claims['https://api.openai.com/profile']; - if (profile?.email) - return profile.email; - return undefined; -} -export function getAccountIdFromClaims(claims) { - if (!claims) - return undefined; - const auth = claims['https://api.openai.com/auth']; - return auth?.chatgpt_account_id; -} -function getAccountUserIdFromClaims(claims) { - if (!claims) - return undefined; - const auth = claims['https://api.openai.com/auth']; - if (typeof auth?.chatgpt_account_user_id === 'string') - return auth.chatgpt_account_user_id; - return undefined; -} -function getUserIdFromClaims(claims) { - if (!claims) - return undefined; - const auth = claims['https://api.openai.com/auth']; - if (typeof auth?.user_id === 'string') - return auth.user_id; - if (typeof auth?.chatgpt_user_id === 'string') - return auth.chatgpt_user_id; - return undefined; -} -export function getPlanTypeFromClaims(claims) { - if (!claims) - return undefined; - const auth = claims['https://api.openai.com/auth']; - return typeof auth?.chatgpt_plan_type === 'string' ? auth.chatgpt_plan_type : undefined; -} -export function getExpiryFromClaims(claims) { - if (!claims) - return undefined; - const exp = claims.exp; - if (typeof exp === 'number') - return exp * 1000; - return undefined; -} -function fingerprintTokens(tokens) { - return `${tokens.access_token || ''}:${tokens.refresh_token || ''}:${tokens.id_token || ''}`; -} -function buildAlias(email, accountId, store) { - const base = email?.split('@')[0] || accountId?.slice(0, 8) || `account-${Date.now()}`; - const existing = new Set(Object.keys(store.accounts)); - let candidate = base || `account-${Date.now()}`; - let suffix = 1; - while (existing.has(candidate)) { - candidate = `${base}-${suffix}`; - suffix += 1; - } - return candidate; -} -function findMatchingAlias(tokens, accountId, accountUserId, userId, email, store) { - for (const account of Object.values(store.accounts)) { - const existingAccountUserId = account.accountUserId || - getAccountUserIdFromClaims(decodeJwtPayload(account.accessToken)) || - (account.idToken ? getAccountUserIdFromClaims(decodeJwtPayload(account.idToken)) : undefined); - if (accountUserId) { - if (existingAccountUserId === accountUserId) - return account.alias; - continue; - } - const existingUserId = account.userId || - getUserIdFromClaims(decodeJwtPayload(account.accessToken)) || - (account.idToken ? getUserIdFromClaims(decodeJwtPayload(account.idToken)) : undefined); - if (userId && existingUserId === userId) - return account.alias; - if (tokens.access_token && account.accessToken === tokens.access_token) - return account.alias; - if (tokens.refresh_token && account.refreshToken === tokens.refresh_token) - return account.alias; - if (tokens.id_token && account.idToken === tokens.id_token) - return account.alias; - if (email && account.email === email) - return account.alias; - if (!userId && accountId && account.accountId === accountId) - return account.alias; - } - return null; -} -export function getCodexAuthSummary() { - const auth = loadCodexAuthFile(); - const normalized = normalizeTokens(auth); - const access = normalized?.accessToken; - const refresh = normalized?.refreshToken; - const idToken = normalized?.idToken; - const accessClaims = access ? decodeJwtPayload(access) : null; - const idClaims = idToken ? decodeJwtPayload(idToken) : null; - const email = getEmailFromClaims(idClaims) || getEmailFromClaims(accessClaims); - const accountId = normalized?.accountId || getAccountIdFromClaims(idClaims) || getAccountIdFromClaims(accessClaims); - const accountUserId = getAccountUserIdFromClaims(accessClaims) || getAccountUserIdFromClaims(idClaims); - const userId = getUserIdFromClaims(accessClaims) || getUserIdFromClaims(idClaims); - const planType = getPlanTypeFromClaims(accessClaims) || getPlanTypeFromClaims(idClaims); - const expiresAt = getExpiryFromClaims(accessClaims) || getExpiryFromClaims(idClaims); - return { - email, - accountId, - accountUserId, - userId, - planType, - expiresAt, - lastRefresh: normalized?.lastRefresh, - hasAccessToken: Boolean(access), - hasRefreshToken: Boolean(refresh), - hasIdToken: Boolean(idToken) - }; -} -export function resolveAliasForCurrentAuth(store) { - const auth = loadCodexAuthFile(); - const normalized = normalizeTokens(auth); - if (!normalized) - return null; - const accessClaims = normalized.accessToken ? decodeJwtPayload(normalized.accessToken) : null; - const idClaims = normalized.idToken ? decodeJwtPayload(normalized.idToken) : null; - const email = getEmailFromClaims(idClaims) || getEmailFromClaims(accessClaims); - const accountId = normalized.accountId || getAccountIdFromClaims(idClaims) || getAccountIdFromClaims(accessClaims); - const accountUserId = getAccountUserIdFromClaims(accessClaims) || getAccountUserIdFromClaims(idClaims); - const userId = getUserIdFromClaims(accessClaims) || getUserIdFromClaims(idClaims); - const targetStore = store ?? loadStore(); - return findMatchingAlias({ - access_token: normalized.accessToken, - refresh_token: normalized.refreshToken, - id_token: normalized.idToken - }, accountId, accountUserId, userId, email, targetStore); -} -export function syncCodexAuthFile() { - const auth = loadCodexAuthFile(); - const normalized = normalizeTokens(auth); - if (!normalized?.accessToken || !normalized.refreshToken) { - lastAuthError = 'Missing access_token/refresh_token in auth.json'; - const accessClaims = normalized?.accessToken ? decodeJwtPayload(normalized.accessToken) : null; - const idClaims = normalized?.idToken ? decodeJwtPayload(normalized.idToken) : null; - const email = getEmailFromClaims(idClaims) || getEmailFromClaims(accessClaims); - const accountId = normalized?.accountId || getAccountIdFromClaims(idClaims) || getAccountIdFromClaims(accessClaims); - return { - alias: null, - added: false, - updated: false, - authEmail: email, - authAccountId: accountId - }; - } - const tokens = { - access_token: normalized.accessToken, - refresh_token: normalized.refreshToken, - id_token: normalized.idToken, - account_id: normalized.accountId - }; - const fingerprint = fingerprintTokens(tokens); - const accessClaims = decodeJwtPayload(normalized.accessToken); - const idClaims = normalized.idToken ? decodeJwtPayload(normalized.idToken) : null; - const email = getEmailFromClaims(idClaims) || getEmailFromClaims(accessClaims); - const accountId = normalized.accountId || getAccountIdFromClaims(idClaims) || getAccountIdFromClaims(accessClaims); - const accountUserId = getAccountUserIdFromClaims(accessClaims) || getAccountUserIdFromClaims(idClaims); - const userId = getUserIdFromClaims(accessClaims) || getUserIdFromClaims(idClaims); - const planType = getPlanTypeFromClaims(idClaims) || getPlanTypeFromClaims(accessClaims); - const expiresAt = getExpiryFromClaims(accessClaims) || getExpiryFromClaims(idClaims) || Date.now(); - const store = loadStore(); - const now = Date.now(); - const alias = findMatchingAlias(tokens, accountId, accountUserId, userId, email, store); - if (lastFingerprint === fingerprint && alias) { - return { alias, added: false, updated: false, authEmail: email, authAccountId: accountId }; - } - lastFingerprint = fingerprint; - const update = { - accessToken: normalized.accessToken, - refreshToken: normalized.refreshToken, - accountId, - accountUserId, - userId, - planType, - expiresAt, - email, - lastRefresh: normalized.lastRefresh, - lastSeenAt: now, - source: 'codex' - }; - if (normalized.idToken) { - update.idToken = normalized.idToken; - } - if (alias) { - updateAccount(alias, update); - return { alias, added: false, updated: true, authEmail: email, authAccountId: accountId }; - } - const newAlias = buildAlias(email, accountId, store); - addAccount(newAlias, update); - return { alias: newAlias, added: true, updated: true, authEmail: email, authAccountId: accountId }; -} -export function getCodexAuthStatus() { - return { error: lastAuthError }; -} -export function writeCodexAuthForAlias(alias) { - const store = loadStore(); - const account = store.accounts[alias]; - if (!account) { - throw new Error(`Unknown alias: ${alias}`); - } - if (!account.accessToken || !account.refreshToken) { - throw new Error('Missing token data for alias'); - } - const current = loadCodexAuthFile(); - const baseTokens = { - access_token: account.accessToken, - refresh_token: account.refreshToken - }; - if (account.idToken) { - baseTokens.id_token = account.idToken; - } - if (account.accountId) { - baseTokens.account_id = account.accountId; - } - const auth = { - OPENAI_API_KEY: current?.OPENAI_API_KEY ?? null, - tokens: baseTokens, - last_refresh: new Date().toISOString() - }; - writeCodexAuthFile(auth); - updateAccount(alias, { - lastRefresh: auth.last_refresh, - lastSeenAt: Date.now(), - source: 'codex' - }); -} -//# sourceMappingURL=codex-auth.js.map \ No newline at end of file diff --git a/dist/codex-auth.js.map b/dist/codex-auth.js.map deleted file mode 100644 index 9a9967f..0000000 --- a/dist/codex-auth.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"codex-auth.js","sourceRoot":"","sources":["../src/codex-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAgBjE,MAAM,mBAAmB,GAAG,qCAAqC,CAAA;AAEjE,SAAS,oBAAoB;IAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACjD,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IACrE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAA;IACnD,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAA;AACnD,MAAM,eAAe,GAAG,oBAAoB,EAAE,CAAA;AAE9C,IAAI,eAAe,GAAkB,IAAI,CAAA;AACzC,IAAI,aAAa,GAAkB,IAAI,CAAA;AAEvC,MAAM,UAAU,gBAAgB;IAC9B,OAAO,eAAe,CAAA;AACxB,CAAC;AAeD,SAAS,SAAS;IAChB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC3D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,aAAa,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAA;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;QACrD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAA;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,aAAa,GAAG,iCAAiC,CAAA;QACjD,OAAO,CAAC,KAAK,CAAC,+CAA+C,EAAE,GAAG,CAAC,CAAA;QACnE,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAmB;IACpD,SAAS,EAAE,CAAA;IACX,EAAE,CAAC,aAAa,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAC/D,IAAI,EAAE,KAAK;KACZ,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAS;IAChC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAElD,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;IAEpF,MAAM,WAAW,GACf,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,WAAW;QAClB,MAAM,CAAC,MAAM;QACb,IAAI,CAAC,YAAY;QACjB,IAAI,CAAC,WAAW;QAChB,IAAI,CAAC,MAAM,CAAA;IAEb,MAAM,YAAY,GAChB,MAAM,CAAC,aAAa;QACpB,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,OAAO;QACd,IAAI,CAAC,aAAa;QAClB,IAAI,CAAC,YAAY;QACjB,IAAI,CAAC,OAAO,CAAA;IAEd,MAAM,OAAO,GACX,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,EAAE;QACT,IAAI,CAAC,QAAQ;QACb,IAAI,CAAC,OAAO;QACZ,IAAI,CAAC,EAAE,CAAA;IAET,MAAM,SAAS,GACb,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,SAAS;QAChB,IAAI,CAAC,UAAU;QACf,IAAI,CAAC,SAAS,CAAA;IAEhB,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,WAAW,CAAA;IAEzD,MAAM,MAAM,GAAgH;QAC1H,WAAW,EAAE,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QACtE,YAAY,EAAE,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;QACzE,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC1D,SAAS,EAAE,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QAChE,WAAW,EAAE,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;KACvE,CAAA;IAED,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACxF,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACnC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC9D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QACrF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAC/D,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAwB,CAAA;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAkC;IACnE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAA;IACzD,MAAM,OAAO,GAAG,MAAM,CAAC,gCAAgC,CAAmC,CAAA;IAC1F,IAAI,OAAO,EAAE,KAAK;QAAE,OAAO,OAAO,CAAC,KAAK,CAAA;IACxC,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAkC;IACvE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,6BAA6B,CAAgD,CAAA;IACjG,OAAO,IAAI,EAAE,kBAAkB,CAAA;AACjC,CAAC;AAED,SAAS,0BAA0B,CAAC,MAAkC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,6BAA6B,CAAqD,CAAA;IACtG,IAAI,OAAO,IAAI,EAAE,uBAAuB,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,uBAAuB,CAAA;IAC1F,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAkC;IAC7D,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,6BAA6B,CAA+D,CAAA;IAChH,IAAI,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,OAAO,CAAA;IAC1D,IAAI,OAAO,IAAI,EAAE,eAAe,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,eAAe,CAAA;IAC1E,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAkC;IACtE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,6BAA6B,CAA+C,CAAA;IAChG,OAAO,OAAO,IAAI,EAAE,iBAAiB,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAA;AACzF,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAkC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAA;IACtB,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,GAAG,IAAI,CAAA;IAC9C,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAuB;IAChD,OAAO,GAAG,MAAM,CAAC,YAAY,IAAI,EAAE,IAAI,MAAM,CAAC,aAAa,IAAI,EAAE,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAA;AAC9F,CAAC;AAED,SAAS,UAAU,CAAC,KAAyB,EAAE,SAA6B,EAAE,KAAmC;IAC/G,MAAM,IAAI,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,WAAW,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;IACtF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAA;IACrD,IAAI,SAAS,GAAG,IAAI,IAAI,WAAW,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;IAC/C,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,OAAO,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/B,SAAS,GAAG,GAAG,IAAI,IAAI,MAAM,EAAE,CAAA;QAC/B,MAAM,IAAI,CAAC,CAAA;IACb,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,iBAAiB,CACxB,MAA8F,EAC9F,SAA6B,EAC7B,aAAiC,EACjC,MAA0B,EAC1B,KAAyB,EACzB,KAAmC;IAEnC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,MAAM,qBAAqB,GACzB,OAAO,CAAC,aAAa;YACrB,0BAA0B,CAAC,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YACjE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QAC/F,IAAI,aAAa,EAAE,CAAC;YAClB,IAAI,qBAAqB,KAAK,aAAa;gBAAE,OAAO,OAAO,CAAC,KAAK,CAAA;YACjE,SAAQ;QACV,CAAC;QAED,MAAM,cAAc,GAClB,OAAO,CAAC,MAAM;YACd,mBAAmB,CAAC,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC1D,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QACxF,IAAI,MAAM,IAAI,cAAc,KAAK,MAAM;YAAE,OAAO,OAAO,CAAC,KAAK,CAAA;QAE7D,IAAI,MAAM,CAAC,YAAY,IAAI,OAAO,CAAC,WAAW,KAAK,MAAM,CAAC,YAAY;YAAE,OAAO,OAAO,CAAC,KAAK,CAAA;QAC5F,IAAI,MAAM,CAAC,aAAa,IAAI,OAAO,CAAC,YAAY,KAAK,MAAM,CAAC,aAAa;YAAE,OAAO,OAAO,CAAC,KAAK,CAAA;QAC/F,IAAI,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC,OAAO,KAAK,MAAM,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC,KAAK,CAAA;QAChF,IAAI,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;YAAE,OAAO,OAAO,CAAC,KAAK,CAAA;QAC1D,IAAI,CAAC,MAAM,IAAI,SAAS,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,KAAK,CAAA;IACnF,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAA;IAChC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,UAAU,EAAE,WAAW,CAAA;IACtC,MAAM,OAAO,GAAG,UAAU,EAAE,YAAY,CAAA;IACxC,MAAM,OAAO,GAAG,UAAU,EAAE,OAAO,CAAA;IACnC,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC7D,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3D,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAA;IAC9E,MAAM,SAAS,GAAG,UAAU,EAAE,SAAS,IAAI,sBAAsB,CAAC,QAAQ,CAAC,IAAI,sBAAsB,CAAC,YAAY,CAAC,CAAA;IACnH,MAAM,aAAa,GAAG,0BAA0B,CAAC,YAAY,CAAC,IAAI,0BAA0B,CAAC,QAAQ,CAAC,CAAA;IACtG,MAAM,MAAM,GAAG,mBAAmB,CAAC,YAAY,CAAC,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACjF,MAAM,QAAQ,GAAG,qBAAqB,CAAC,YAAY,CAAC,IAAI,qBAAqB,CAAC,QAAQ,CAAC,CAAA;IACvF,MAAM,SAAS,GAAG,mBAAmB,CAAC,YAAY,CAAC,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACpF,OAAO;QACL,KAAK;QACL,SAAS;QACT,aAAa;QACb,MAAM;QACN,QAAQ;QACR,SAAS;QACT,WAAW,EAAE,UAAU,EAAE,WAAW;QACpC,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC;QAC/B,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC;QACjC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC;KAC7B,CAAA;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,KAAoC;IAC7E,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAA;IAChC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACxC,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,YAAY,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC7F,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACjF,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAA;IAC9E,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,IAAI,sBAAsB,CAAC,QAAQ,CAAC,IAAI,sBAAsB,CAAC,YAAY,CAAC,CAAA;IAClH,MAAM,aAAa,GAAG,0BAA0B,CAAC,YAAY,CAAC,IAAI,0BAA0B,CAAC,QAAQ,CAAC,CAAA;IACtG,MAAM,MAAM,GAAG,mBAAmB,CAAC,YAAY,CAAC,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACjF,MAAM,WAAW,GAAG,KAAK,IAAI,SAAS,EAAE,CAAA;IACxC,OAAO,iBAAiB,CACtB;QACE,YAAY,EAAE,UAAU,CAAC,WAAW;QACpC,aAAa,EAAE,UAAU,CAAC,YAAY;QACtC,QAAQ,EAAE,UAAU,CAAC,OAAO;KAC7B,EACD,SAAS,EACT,aAAa,EACb,MAAM,EACN,KAAK,EACL,WAAW,CACZ,CAAA;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAO/B,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAA;IAChC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACxC,IAAI,CAAC,UAAU,EAAE,WAAW,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;QACzD,aAAa,GAAG,iDAAiD,CAAA;QACjE,MAAM,YAAY,GAAG,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC9F,MAAM,QAAQ,GAAG,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAClF,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAA;QAC9E,MAAM,SAAS,GAAG,UAAU,EAAE,SAAS,IAAI,sBAAsB,CAAC,QAAQ,CAAC,IAAI,sBAAsB,CAAC,YAAY,CAAC,CAAA;QACnH,OAAO;YACL,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,KAAK;YAChB,aAAa,EAAE,SAAS;SACzB,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAoB;QAC9B,YAAY,EAAE,UAAU,CAAC,WAAW;QACpC,aAAa,EAAE,UAAU,CAAC,YAAY;QACtC,QAAQ,EAAE,UAAU,CAAC,OAAO;QAC5B,UAAU,EAAE,UAAU,CAAC,SAAS;KACjC,CAAA;IACD,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAE7C,MAAM,YAAY,GAAG,gBAAgB,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;IAC7D,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACjF,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAA;IAC9E,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,IAAI,sBAAsB,CAAC,QAAQ,CAAC,IAAI,sBAAsB,CAAC,YAAY,CAAC,CAAA;IAClH,MAAM,aAAa,GAAG,0BAA0B,CAAC,YAAY,CAAC,IAAI,0BAA0B,CAAC,QAAQ,CAAC,CAAA;IACtG,MAAM,MAAM,GAAG,mBAAmB,CAAC,YAAY,CAAC,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACjF,MAAM,QAAQ,GAAG,qBAAqB,CAAC,QAAQ,CAAC,IAAI,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACvF,MAAM,SAAS,GAAG,mBAAmB,CAAC,YAAY,CAAC,IAAI,mBAAmB,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;IAElG,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;IACvF,IAAI,eAAe,KAAK,WAAW,IAAI,KAAK,EAAE,CAAC;QAC7C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,CAAA;IAC5F,CAAC;IACD,eAAe,GAAG,WAAW,CAAA;IAC7B,MAAM,MAAM,GAAgC;QAC1C,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,YAAY,EAAE,UAAU,CAAC,YAAY;QACrC,SAAS;QACT,aAAa;QACb,MAAM;QACN,QAAQ;QACR,SAAS;QACT,KAAK;QACL,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,OAAO;KAChB,CAAA;IACD,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;QACvB,MAAM,CAAC,OAAO,GAAG,UAAU,CAAC,OAAO,CAAA;IACrC,CAAC;IAED,IAAI,KAAK,EAAE,CAAC;QACV,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QAC5B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,CAAA;IAC3F,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;IACpD,UAAU,CAAC,QAAQ,EAAE,MAA0D,CAAC,CAAA;IAChF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,CAAA;AACpG,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAErC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAA;IAC5C,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IACjD,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAA;IACnC,MAAM,UAAU,GAAwB;QACtC,YAAY,EAAE,OAAO,CAAC,WAAW;QACjC,aAAa,EAAE,OAAO,CAAC,YAAY;KACpC,CAAA;IACD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,UAAU,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAA;IACvC,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,UAAU,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAA;IAC3C,CAAC;IACD,MAAM,IAAI,GAAkB;QAC1B,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI;QAC/C,MAAM,EAAE,UAA6B;QACrC,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACvC,CAAA;IAED,kBAAkB,CAAC,IAAI,CAAC,CAAA;IACxB,aAAa,CAAC,KAAK,EAAE;QACnB,WAAW,EAAE,IAAI,CAAC,YAAY;QAC9B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,OAAO;KAChB,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file diff --git a/dist/errors.d.ts b/dist/errors.d.ts deleted file mode 100644 index 223cee1..0000000 --- a/dist/errors.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type ErrorCode = 'NO_ELIGIBLE_ACCOUNTS' | 'ACCOUNT_NOT_FOUND' | 'ACCOUNT_DISABLED' | 'TOKEN_REFRESH_FAILED' | 'STORE_LOCKED' | 'STORE_CORRUPTED' | 'STORE_MIGRATION_FAILED' | 'VALIDATION_ERROR' | 'RATE_LIMITED' | 'AUTH_INVALID' | 'MODEL_UNSUPPORTED' | 'WORKSPACE_DEACTIVATED' | 'MAX_RETRIES_EXCEEDED' | 'INVALID_REQUEST' | 'LOCALHOST_ONLY'; -export interface DeterministicError { - code: ErrorCode; - message: string; - details?: Record; -} -export declare function createError(code: ErrorCode, message: string, details?: Record): DeterministicError; -export declare function errorResponse(error: DeterministicError, status?: number): Response; -export declare const Errors: { - noEligibleAccounts: (reason?: string) => DeterministicError; - accountNotFound: (alias: string) => DeterministicError; - accountDisabled: (alias: string) => DeterministicError; - maxRetriesExceeded: (attempts: number, aliasesTried: string[]) => DeterministicError; - storeLocked: (reason?: string) => DeterministicError; - localhostOnly: (host: string) => DeterministicError; -}; -//# sourceMappingURL=errors.d.ts.map \ No newline at end of file diff --git a/dist/errors.d.ts.map b/dist/errors.d.ts.map deleted file mode 100644 index 6f13e42..0000000 --- a/dist/errors.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACjB,sBAAsB,GACtB,mBAAmB,GACnB,kBAAkB,GAClB,sBAAsB,GACtB,cAAc,GACd,iBAAiB,GACjB,wBAAwB,GACxB,kBAAkB,GAClB,cAAc,GACd,cAAc,GACd,mBAAmB,GACnB,uBAAuB,GACvB,sBAAsB,GACtB,iBAAiB,GACjB,gBAAgB,CAAA;AAEpB,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,CAEnH;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,GAAE,MAAY,GAAG,QAAQ,CAKvF;AAED,eAAO,MAAM,MAAM;kCACa,MAAM,KAAG,kBAAkB;6BAIhC,MAAM,KAAG,kBAAkB;6BAK3B,MAAM,KAAG,kBAAkB;mCAKrB,MAAM,gBAAgB,MAAM,EAAE,KAAG,kBAAkB;2BAK3D,MAAM,KAAG,kBAAkB;0BAI5B,MAAM,KAAG,kBAAkB;CAKlD,CAAA"} \ No newline at end of file diff --git a/dist/errors.js b/dist/errors.js deleted file mode 100644 index cc2b886..0000000 --- a/dist/errors.js +++ /dev/null @@ -1,37 +0,0 @@ -export function createError(code, message, details) { - return { code, message, details }; -} -export function errorResponse(error, status = 500) { - return new Response(JSON.stringify({ error }), { status, headers: { 'Content-Type': 'application/json' } }); -} -export const Errors = { - noEligibleAccounts: (reason) => ({ - code: 'NO_ELIGIBLE_ACCOUNTS', - message: reason || 'No eligible accounts available for rotation', - }), - accountNotFound: (alias) => ({ - code: 'ACCOUNT_NOT_FOUND', - message: `Account not found: ${alias}`, - details: { alias }, - }), - accountDisabled: (alias) => ({ - code: 'ACCOUNT_DISABLED', - message: `Account is disabled: ${alias}`, - details: { alias }, - }), - maxRetriesExceeded: (attempts, aliasesTried) => ({ - code: 'MAX_RETRIES_EXCEEDED', - message: `Exhausted all ${attempts} retry attempts`, - details: { attempts, aliasesTried }, - }), - storeLocked: (reason) => ({ - code: 'STORE_LOCKED', - message: reason || 'Store is locked and cannot be modified', - }), - localhostOnly: (host) => ({ - code: 'LOCALHOST_ONLY', - message: 'Dashboard can only bind to localhost (127.0.0.1 or ::1)', - details: { attemptedHost: host }, - }), -}; -//# sourceMappingURL=errors.js.map \ No newline at end of file diff --git a/dist/errors.js.map b/dist/errors.js.map deleted file mode 100644 index e8a61cb..0000000 --- a/dist/errors.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,WAAW,CAAC,IAAe,EAAE,OAAe,EAAE,OAAiC;IAC7F,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAyB,EAAE,SAAiB,GAAG;IAC3E,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EACzB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAC5D,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,kBAAkB,EAAE,CAAC,MAAe,EAAsB,EAAE,CAAC,CAAC;QAC5D,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,MAAM,IAAI,6CAA6C;KACjE,CAAC;IACF,eAAe,EAAE,CAAC,KAAa,EAAsB,EAAE,CAAC,CAAC;QACvD,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,sBAAsB,KAAK,EAAE;QACtC,OAAO,EAAE,EAAE,KAAK,EAAE;KACnB,CAAC;IACF,eAAe,EAAE,CAAC,KAAa,EAAsB,EAAE,CAAC,CAAC;QACvD,IAAI,EAAE,kBAAkB;QACxB,OAAO,EAAE,wBAAwB,KAAK,EAAE;QACxC,OAAO,EAAE,EAAE,KAAK,EAAE;KACnB,CAAC;IACF,kBAAkB,EAAE,CAAC,QAAgB,EAAE,YAAsB,EAAsB,EAAE,CAAC,CAAC;QACrF,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,iBAAiB,QAAQ,iBAAiB;QACnD,OAAO,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE;KACpC,CAAC;IACF,WAAW,EAAE,CAAC,MAAe,EAAsB,EAAE,CAAC,CAAC;QACrD,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,MAAM,IAAI,wCAAwC;KAC5D,CAAC;IACF,aAAa,EAAE,CAAC,IAAY,EAAsB,EAAE,CAAC,CAAC;QACpD,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,yDAAyD;QAClE,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;KACjC,CAAC;CACH,CAAA"} \ No newline at end of file diff --git a/dist/force-mode.d.ts b/dist/force-mode.d.ts deleted file mode 100644 index 94b70d2..0000000 --- a/dist/force-mode.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface ForceState { - forcedAlias: string | null; - forcedUntil: number | null; - previousRotationStrategy: string | null; - forcedBy: string | null; -} -export declare function getForceState(): ForceState; -export declare function isForceActive(): boolean; -export declare function activateForce(alias: string, actor?: string): { - success: boolean; - error?: string; - state?: ForceState; -}; -export declare function clearForce(): { - success: boolean; - restoredStrategy?: string | null; -}; -export declare function checkAndAutoClearForce(): { - wasCleared: boolean; - reason?: string; -}; -export declare function getRemainingForceTimeMs(): number; -export declare function formatForceDuration(ms: number): string; -//# sourceMappingURL=force-mode.d.ts.map \ No newline at end of file diff --git a/dist/force-mode.d.ts.map b/dist/force-mode.d.ts.map deleted file mode 100644 index fd74deb..0000000 --- a/dist/force-mode.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"force-mode.d.ts","sourceRoot":"","sources":["../src/force-mode.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAcD,wBAAgB,aAAa,IAAI,UAAU,CAQ1C;AAED,wBAAgB,aAAa,IAAI,OAAO,CAwBvC;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAiB,GACvB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,UAAU,CAAA;CAAE,CA+C1D;AAED,wBAAgB,UAAU,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAiCnF;AAED,wBAAgB,sBAAsB,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA6BjF;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAOhD;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAQtD"} \ No newline at end of file diff --git a/dist/force-mode.js b/dist/force-mode.js deleted file mode 100644 index 9d09b4c..0000000 --- a/dist/force-mode.js +++ /dev/null @@ -1,149 +0,0 @@ -import { loadStore, saveStore } from './store.js'; -const FORCE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours -const ROTATION_STRATEGIES = new Set([ - 'round-robin', - 'least-used', - 'random', - 'weighted-round-robin' -]); -function isRotationStrategy(value) { - return typeof value === 'string' && ROTATION_STRATEGIES.has(value); -} -export function getForceState() { - const store = loadStore(); - return { - forcedAlias: store.forcedAlias ?? null, - forcedUntil: store.forcedUntil ?? null, - previousRotationStrategy: store.previousRotationStrategy ?? null, - forcedBy: store.forcedBy ?? null - }; -} -export function isForceActive() { - const state = getForceState(); - if (!state.forcedAlias || !state.forcedUntil) { - return false; - } - const now = Date.now(); - if (now > state.forcedUntil) { - return false; - } - // Check if forced alias still exists and is eligible - const store = loadStore(); - const forcedAccount = store.accounts[state.forcedAlias]; - if (!forcedAccount) { - return false; - } - // Check if account is disabled - if (forcedAccount.enabled === false) { - return false; - } - return true; -} -export function activateForce(alias, actor = 'system') { - const store = loadStore(); - // Validate alias exists - if (!store.accounts[alias]) { - return { success: false, error: `Account '${alias}' not found` }; - } - // Validate alias is enabled - if (store.accounts[alias].enabled === false) { - return { success: false, error: `Account '${alias}' is disabled` }; - } - const now = Date.now(); - const keepExistingTtl = store.forcedAlias === alias && - typeof store.forcedUntil === 'number' && - store.forcedUntil > now; - const forcedUntil = keepExistingTtl ? store.forcedUntil : now + FORCE_TTL_MS; - const currentStrategy = store.settings?.rotationStrategy || - store.rotationStrategy || - 'round-robin'; - // Store previous rotation strategy if not already forcing - const previousStrategy = (store.forcedAlias ? store.previousRotationStrategy : currentStrategy) ?? null; - const newStore = { - ...store, - forcedAlias: alias, - forcedUntil, - previousRotationStrategy: previousStrategy, - forcedBy: actor - }; - saveStore(newStore); - return { - success: true, - state: { - forcedAlias: alias, - forcedUntil, - previousRotationStrategy: previousStrategy, - forcedBy: actor - } - }; -} -export function clearForce() { - const store = loadStore(); - const restoredStrategy = store.previousRotationStrategy; - const currentStrategy = store.settings?.rotationStrategy || - store.rotationStrategy || - 'round-robin'; - const nextStrategy = isRotationStrategy(restoredStrategy) - ? restoredStrategy - : currentStrategy; - const newStore = { - ...store, - forcedAlias: null, - forcedUntil: null, - rotationStrategy: nextStrategy, - previousRotationStrategy: null, - forcedBy: null - }; - if (store.settings) { - newStore.settings = { - ...store.settings, - rotationStrategy: nextStrategy - }; - } - saveStore(newStore); - return { - success: true, - restoredStrategy - }; -} -export function checkAndAutoClearForce() { - const state = getForceState(); - if (!state.forcedAlias) { - return { wasCleared: false }; - } - const store = loadStore(); - const now = Date.now(); - // Check expiry - if (state.forcedUntil && now > state.forcedUntil) { - clearForce(); - return { wasCleared: true, reason: 'expired' }; - } - // Check if alias still exists - if (!store.accounts[state.forcedAlias]) { - clearForce(); - return { wasCleared: true, reason: 'account_removed' }; - } - // Check if alias is disabled - if (store.accounts[state.forcedAlias].enabled === false) { - clearForce(); - return { wasCleared: true, reason: 'account_disabled' }; - } - return { wasCleared: false }; -} -export function getRemainingForceTimeMs() { - const state = getForceState(); - if (!state.forcedUntil) { - return 0; - } - const remaining = state.forcedUntil - Date.now(); - return Math.max(0, remaining); -} -export function formatForceDuration(ms) { - const hours = Math.floor(ms / (60 * 60 * 1000)); - const minutes = Math.floor((ms % (60 * 60 * 1000)) / (60 * 1000)); - if (hours > 0) { - return `${hours}h ${minutes}m`; - } - return `${minutes}m`; -} -//# sourceMappingURL=force-mode.js.map \ No newline at end of file diff --git a/dist/force-mode.js.map b/dist/force-mode.js.map deleted file mode 100644 index 323af91..0000000 --- a/dist/force-mode.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"force-mode.js","sourceRoot":"","sources":["../src/force-mode.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 04e846a..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Plugin } from '@opencode-ai/plugin'; -export declare function isCyberPolicyError(payload: any, fallbackText?: string): boolean; -/** - * Multi-account OAuth plugin for OpenCode - * - * Rotates between multiple ChatGPT Plus/Pro accounts for rate limit resilience. - */ -declare const MultiAuthPlugin: Plugin; -export default MultiAuthPlugin; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map deleted file mode 100644 index 7e1aaaa..0000000 --- a/dist/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AA2M9D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,GAAE,MAAW,GAAG,OAAO,CAKnF;AAqED;;;;GAIG;AACH,QAAA,MAAM,eAAe,EAAE,MAyuBtB,CAAA;AAED,eAAe,eAAe,CAAA"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index b6efe84..0000000 --- a/dist/index.js +++ /dev/null @@ -1,848 +0,0 @@ -import fs from 'node:fs'; -import { syncAuthFromOpenCode } from './auth-sync.js'; -import { createAuthorizationFlow, loginAccount } from './auth.js'; -import { extractRateLimitUpdate, getBlockingRateLimitResetAt, mergeRateLimits, parseRateLimitResetFromError, parseRetryAfterHeader } from './rate-limits.js'; -import { getNextAccount, markAuthInvalid, markModelUnsupported, markRateLimited, markWorkspaceDeactivated } from './rotation.js'; -import { getDefaultModels } from './models.js'; -import { getForceState, isForceActive } from './force-mode.js'; -import { getRuntimeSettings } from './settings.js'; -import { listAccounts, updateAccount, loadStore } from './store.js'; -import { DEFAULT_CONFIG } from './types.js'; -import { Errors } from './errors.js'; -const PROVIDER_ID = 'openai'; -const CODEX_BASE_URL = 'https://chatgpt.com/backend-api'; -const REDIRECT_PORT = 1455; -const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/auth/callback`; -const URL_PATHS = { - RESPONSES: '/responses', - CODEX_RESPONSES: '/codex/responses' -}; -const OPENAI_HEADERS = { - BETA: 'OpenAI-Beta', - ACCOUNT_ID: 'chatgpt-account-id', - ORIGINATOR: 'originator', - SESSION_ID: 'session_id', - CONVERSATION_ID: 'conversation_id' -}; -const OPENAI_HEADER_VALUES = { - BETA_RESPONSES: 'responses=experimental', - ORIGINATOR_CODEX: 'codex_cli_rs' -}; -const JWT_CLAIM_PATH = 'https://api.openai.com/auth'; -const DEFAULT_LATEST_CODEX_MODEL = 'gpt-5.5'; -let pluginConfig = { ...DEFAULT_CONFIG }; -function configure(config) { - pluginConfig = { ...pluginConfig, ...config }; -} -function decodeJWT(token) { - try { - const parts = token.split('.'); - if (parts.length !== 3) - return null; - const payload = parts[1]; - const decoded = Buffer.from(payload, 'base64').toString('utf-8'); - return JSON.parse(decoded); - } - catch { - return null; - } -} -function extractRequestUrl(input) { - if (typeof input === 'string') - return input; - if (input instanceof URL) - return input.toString(); - return input.url; -} -function rewriteUrlForCodex(url) { - return url.replace(URL_PATHS.RESPONSES, URL_PATHS.CODEX_RESPONSES); -} -function extractPathAndSearch(url) { - // OpenCode sometimes passes relative paths (e.g. "/chat/completions") or even - // malformed strings when provider base_url is missing (e.g. "undefined/..."). - // We only need the path+query and then we force the ChatGPT backend base URL. - try { - const u = new URL(url); - return `${u.pathname}${u.search}`; - } - catch { - // best-effort fallback - } - const trimmed = String(url || '').trim(); - if (trimmed.startsWith('/')) - return trimmed; - const firstSlash = trimmed.indexOf('/'); - if (firstSlash >= 0) - return trimmed.slice(firstSlash); - return trimmed; -} -function toCodexBackendUrl(originalUrl) { - const pathAndSearch = extractPathAndSearch(originalUrl); - // Map OpenAI v1 endpoints to ChatGPT Codex endpoints. - let mapped = pathAndSearch; - if (mapped.includes(URL_PATHS.RESPONSES)) { - mapped = mapped.replace(URL_PATHS.RESPONSES, URL_PATHS.CODEX_RESPONSES); - } - else if (mapped.includes('/chat/completions')) { - mapped = mapped.replace('/chat/completions', '/codex/chat/completions'); - } - return new URL(mapped, CODEX_BASE_URL).toString(); -} -function filterInput(input) { - if (!Array.isArray(input)) - return input; - return input - .filter((item) => item?.type !== 'item_reference') - .map((item) => { - if (item && typeof item === 'object' && 'id' in item) { - const { id, ...rest } = item; - return rest; - } - return item; - }); -} -function normalizeModel(model) { - if (!model) - return 'gpt-5.1'; - const modelId = model.includes('/') ? model.split('/').pop() : model; - const baseModel = modelId.replace(/-(?:fast|none|minimal|low|medium|high|xhigh)$/, ''); - // OpenCode may lag behind the ChatGPT Codex model allowlist. Route known older - // Codex selections to the latest backend model when users opt in. - const preferLatestRaw = process.env.OPENCODE_MULTI_AUTH_PREFER_CODEX_LATEST; - const preferLatest = preferLatestRaw === '1' || preferLatestRaw === 'true'; - if (preferLatest && - (baseModel === 'gpt-5.4' || - baseModel === 'gpt-5.3-codex' || - baseModel === 'gpt-5.2-codex' || - baseModel === 'gpt-5-codex')) { - const latestModel = (process.env.OPENCODE_MULTI_AUTH_CODEX_LATEST_MODEL || DEFAULT_LATEST_CODEX_MODEL).trim(); - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.log(`[multi-auth] model map: ${baseModel} -> ${latestModel}`); - } - return latestModel; - } - return baseModel; -} -function isSparkModel(model) { - return typeof model === 'string' && model.startsWith('gpt-5.3-codex-spark'); -} -function supportsFastMode(model) { - return model === 'gpt-5.5' || model === 'gpt-5.4'; -} -function ensureContentType(headers) { - const responseHeaders = new Headers(headers); - if (!responseHeaders.has('content-type')) { - responseHeaders.set('content-type', 'text/event-stream; charset=utf-8'); - } - return responseHeaders; -} -function extractErrorMessage(payload, fallbackText = '') { - if (!payload || typeof payload !== 'object') { - return fallbackText; - } - const detailMessage = typeof payload?.detail?.message === 'string' - ? payload.detail.message - : typeof payload?.detail === 'string' - ? payload.detail - : ''; - const errorMessage = typeof payload?.error?.message === 'string' - ? payload.error.message - : ''; - const topLevelMessage = typeof payload?.message === 'string' - ? payload.message - : ''; - return detailMessage || errorMessage || topLevelMessage || fallbackText; -} -function extractErrorCode(payload) { - if (!payload || typeof payload !== 'object') - return ''; - return ((typeof payload?.detail?.code === 'string' && payload.detail.code) || - (typeof payload?.error?.code === 'string' && payload.error.code) || - (typeof payload?.code === 'string' && payload.code) || - ''); -} -export function isCyberPolicyError(payload, fallbackText = '') { - const code = extractErrorCode(payload).toLowerCase(); - const text = `${extractErrorMessage(payload, fallbackText)} ${fallbackText}`.toLowerCase(); - return code === 'cyber_policy' || text.includes('cyber_policy'); -} -function resolveRateLimitedUntil(rateLimits, headers, errorText, fallbackCooldownMs, now = Date.now()) { - const retryAfterUntil = parseRetryAfterHeader(headers.get('retry-after'), now) || 0; - const windowResetUntil = getBlockingRateLimitResetAt(rateLimits, now, { - conservativeWhenRemainingUnknown: true - }) || 0; - const messageResetUntil = parseRateLimitResetFromError(errorText, now) || 0; - const fallbackUntil = now + fallbackCooldownMs; - return Math.max(fallbackUntil, retryAfterUntil, windowResetUntil, messageResetUntil); -} -function parseSseStream(sseText) { - const lines = sseText.split('\n'); - for (const line of lines) { - if (!line.startsWith('data: ')) - continue; - try { - const data = JSON.parse(line.substring(6)); - if (data?.type === 'response.done' || data?.type === 'response.completed') { - return data.response; - } - } - catch { - // ignore malformed chunks - } - } - return null; -} -async function convertSseToJson(response, headers) { - if (!response.body) { - throw new Error('[multi-auth] Response has no body'); - } - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let fullText = ''; - while (true) { - const { done, value } = await reader.read(); - if (done) - break; - fullText += decoder.decode(value, { stream: true }); - } - const finalResponse = parseSseStream(fullText); - if (!finalResponse) { - return new Response(fullText, { - status: response.status, - statusText: response.statusText, - headers - }); - } - const jsonHeaders = new Headers(headers); - jsonHeaders.set('content-type', 'application/json; charset=utf-8'); - return new Response(JSON.stringify(finalResponse), { - status: response.status, - statusText: response.statusText, - headers: jsonHeaders - }); -} -/** - * Multi-account OAuth plugin for OpenCode - * - * Rotates between multiple ChatGPT Plus/Pro accounts for rate limit resilience. - */ -const MultiAuthPlugin = async ({ client, $, serverUrl, project, directory }) => { - const terminalNotifierPath = (() => { - const candidates = [ - '/opt/homebrew/bin/terminal-notifier', - '/usr/local/bin/terminal-notifier' - ]; - for (const c of candidates) { - try { - if (fs.existsSync(c)) - return c; - } - catch { - // ignore - } - } - return null; - })(); - const notifyEnabledRaw = process.env.OPENCODE_MULTI_AUTH_NOTIFY; - const notifyEnabled = notifyEnabledRaw === '1' || notifyEnabledRaw === 'true'; - const notifySound = (process.env.OPENCODE_MULTI_AUTH_NOTIFY_SOUND || '/System/Library/Sounds/Glass.aiff').trim(); - const lastStatusBySession = new Map(); - const lastNotifiedAtByKey = new Map(); - const lastRetryAttemptBySession = new Map(); - const escapeAppleScriptString = (value) => { - return String(value) - .replaceAll('\\', '\\\\') - .replaceAll('"', '\"') - .replaceAll(String.fromCharCode(10), '\n'); - }; - let didWarnTerminalNotifier = false; - const notifyMac = (title, message, clickUrl) => { - if (!notifyEnabled) - return; - if (process.platform !== 'darwin') - return; - const macOpenRaw = process.env.OPENCODE_MULTI_AUTH_NOTIFY_MAC_OPEN; - const macOpenEnabled = macOpenRaw !== '0' && macOpenRaw !== 'false'; - // Best effort: clickable notifications require terminal-notifier. - if (macOpenEnabled && clickUrl && terminalNotifierPath) { - try { - $ `${terminalNotifierPath} -title ${title} -message ${message} -open ${clickUrl}` - .nothrow() - .catch(() => { }); - } - catch { - // ignore - } - } - else { - if (macOpenEnabled && clickUrl && !terminalNotifierPath && !didWarnTerminalNotifier) { - didWarnTerminalNotifier = true; - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.log('[multi-auth] mac click-to-open requires terminal-notifier (brew install terminal-notifier)'); - } - } - try { - const osascript = '/usr/bin/osascript'; - const safeTitle = escapeAppleScriptString(title); - const safeMessage = escapeAppleScriptString(message); - const script = `display notification "${safeMessage}" with title "${safeTitle}"`; - // Fire-and-forget: never block OpenCode event processing. - $ `${osascript} -e ${script}`.nothrow().catch(() => { }); - } - catch { - // ignore - } - } - if (!notifySound) - return; - try { - const afplay = '/usr/bin/afplay'; - $ `${afplay} ${notifySound}`.nothrow().catch(() => { }); - } - catch { - // ignore - } - }; - const ntfyUrl = (process.env.OPENCODE_MULTI_AUTH_NOTIFY_NTFY_URL || '').trim(); - const ntfyToken = (process.env.OPENCODE_MULTI_AUTH_NOTIFY_NTFY_TOKEN || '').trim(); - const notifyUiBaseUrl = (process.env.OPENCODE_MULTI_AUTH_NOTIFY_UI_BASE_URL || '').trim(); - const getSessionUrl = (sessionID) => { - const base = (notifyUiBaseUrl || serverUrl?.origin || '').replace(/\/$/, ''); - if (!base) - return ''; - return `${base}/session/${sessionID}`; - }; - const projectLabel = (project?.name || project?.id || '').trim() || 'OpenCode'; - const sessionMetaCache = new Map(); - const formatTitle = (kind) => { - if (kind === 'error') - return `OpenCode - ${projectLabel} - Error`; - if (kind === 'retry') - return `OpenCode - ${projectLabel} - Retrying`; - return `OpenCode - ${projectLabel}`; - }; - const formatBody = (kind, sessionID, detail) => { - const meta = sessionMetaCache.get(sessionID) || {}; - const titleLine = meta.title ? `Task: ${meta.title}` : ''; - const url = getSessionUrl(sessionID); - if (kind === 'idle') { - return [titleLine, `Session finished: ${sessionID}`, detail || '', url].filter(Boolean).join('\n'); - } - if (kind === 'retry') { - return [titleLine, `Retrying: ${sessionID}`, detail || '', url].filter(Boolean).join('\n'); - } - return [titleLine, `Error: ${sessionID}`, detail || '', url].filter(Boolean).join('\n'); - }; - const notifyMacRich = (kind, sessionID, detail) => { - const body = formatBody(kind, sessionID, detail); - notifyMac(formatTitle(kind), body, getSessionUrl(sessionID) || undefined); - }; - const notifyNtfyRich = async (kind, sessionID, detail) => { - if (!notifyEnabled) - return; - if (!ntfyUrl) - return; - const sessionUrl = getSessionUrl(sessionID); - const title = formatTitle(kind); - const body = formatBody(kind, sessionID, detail); - // ntfy priority: 1=min, 3=default, 5=max - const priority = kind === 'error' ? '5' : kind === 'retry' ? '4' : '3'; - const headers = { - 'Content-Type': 'text/plain; charset=utf-8', - 'Title': title, - 'Priority': priority - }; - if (sessionUrl) - headers['Click'] = sessionUrl; - if (ntfyToken) - headers['Authorization'] = `Bearer ${ntfyToken}`; - try { - await fetch(ntfyUrl, { method: 'POST', headers, body }); - } - catch { - // ignore - } - }; - const shouldThrottle = (key, minMs) => { - const last = lastNotifiedAtByKey.get(key) || 0; - const now = Date.now(); - if (now - last < minMs) - return true; - lastNotifiedAtByKey.set(key, now); - return false; - }; - const formatRetryDetail = (status) => { - const attempt = typeof status?.attempt === 'number' ? status.attempt : undefined; - const message = typeof status?.message === 'string' ? status.message : ''; - const next = typeof status?.next === 'number' ? status.next : undefined; - const parts = []; - if (typeof attempt === 'number') - parts.push(`Attempt: ${attempt}`); - // OpenCode has emitted both "seconds-until-next" and "epoch ms" variants over time. - if (typeof next === 'number') { - const seconds = next > 1e12 ? Math.max(0, Math.round((next - Date.now()) / 1000)) : Math.max(0, Math.round(next)); - parts.push(`Next in: ${seconds}s`); - } - if (message) - parts.push(message); - return parts.join(' | '); - }; - const formatErrorDetail = (err) => { - if (!err || typeof err !== 'object') - return ''; - const name = typeof err.name === 'string' ? err.name : ''; - const code = typeof err.code === 'string' ? err.code : ''; - const message = (typeof err.message === 'string' && err.message) || - (typeof err.error?.message === 'string' && err.error.message) || - ''; - return [name, code, message].filter(Boolean).join(': '); - }; - const notifyRich = async (kind, sessionID, detail) => { - try { - notifyMacRich(kind, sessionID, detail); - } - catch { - // ignore - } - try { - await notifyNtfyRich(kind, sessionID, detail); - } - catch { - // ignore - } - }; - return { - event: async ({ event }) => { - if (!notifyEnabled) - return; - if (!event || !('type' in event)) - return; - if (event.type === 'session.created' || event.type === 'session.updated') { - const info = event.properties?.info; - const id = info?.id; - if (id) { - sessionMetaCache.set(id, { title: info?.title }); - } - return; - } - if (event.type === 'session.status') { - const sessionID = event.properties?.sessionID; - const status = event.properties?.status; - const statusType = status?.type; - if (!sessionID || !statusType) - return; - lastStatusBySession.set(sessionID, statusType); - if (statusType === 'retry') { - const attempt = typeof status?.attempt === 'number' ? status.attempt : undefined; - const prevAttempt = lastRetryAttemptBySession.get(sessionID); - if (typeof attempt === 'number') { - if (prevAttempt === attempt && shouldThrottle(`retry:${sessionID}:${attempt}`, 5000)) { - return; - } - lastRetryAttemptBySession.set(sessionID, attempt); - } - const key = `retry:${sessionID}:${typeof attempt === 'number' ? attempt : 'na'}`; - if (shouldThrottle(key, 2000)) - return; - void notifyRich('retry', sessionID, formatRetryDetail(status)); - } - return; - } - if (event.type === 'session.error') { - const sessionID = event.properties?.sessionID; - const id = sessionID || 'unknown'; - const err = event.properties?.error; - const detail = formatErrorDetail(err); - const key = `error:${id}:${detail}`; - if (shouldThrottle(key, 2000)) - return; - void notifyRich('error', id, detail); - return; - } - if (event.type === 'session.idle') { - const sessionID = event.properties?.sessionID; - if (!sessionID) - return; - const prev = lastStatusBySession.get(sessionID); - if (prev === 'busy' || prev === 'retry') { - if (shouldThrottle(`idle:${sessionID}`, 2000)) - return; - void notifyRich('idle', sessionID); - } - lastStatusBySession.set(sessionID, 'idle'); - } - }, - config: async (config) => { - const injectModelsRaw = process.env.OPENCODE_MULTI_AUTH_INJECT_MODELS; - const injectModels = injectModelsRaw !== '0' && injectModelsRaw !== 'false'; - if (!injectModels) - return; - const latestModel = (process.env.OPENCODE_MULTI_AUTH_CODEX_LATEST_MODEL || DEFAULT_LATEST_CODEX_MODEL).trim(); - try { - const openai = config.provider?.[PROVIDER_ID] || null; - if (!openai || typeof openai !== 'object') - return; - openai.models ||= {}; - openai.whitelist ||= []; - const defaultModels = getDefaultModels(); - const injectedModelIds = [latestModel]; - if (supportsFastMode(latestModel) && defaultModels[`${latestModel}-fast`]) { - injectedModelIds.push(`${latestModel}-fast`); - } - for (const sparkVariant of [ - 'gpt-5.3-codex-spark-low', - 'gpt-5.3-codex-spark-medium', - 'gpt-5.3-codex-spark-high', - 'gpt-5.3-codex-spark-xhigh' - ]) { - if (defaultModels[sparkVariant]) { - injectedModelIds.push(sparkVariant); - } - } - for (const modelID of injectedModelIds) { - const model = defaultModels[modelID]; - if (!model || openai.models[modelID]) - continue; - openai.models[modelID] = model; - } - for (const modelID of injectedModelIds) { - if (!openai.whitelist.includes(modelID)) { - openai.whitelist.unshift(modelID); - } - } - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.log(`[multi-auth] injected runtime models: ${injectedModelIds.join(', ')}`); - } - } - catch (err) { - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.log('[multi-auth] config injection failed:', err); - } - } - }, - auth: { - provider: PROVIDER_ID, - /** - * Loader configures the SDK with multi-account rotation - */ - async loader(getAuth, provider) { - await syncAuthFromOpenCode(getAuth); - const accounts = listAccounts(); - if (accounts.length === 0) { - console.log('[multi-auth] No accounts configured. Run: opencode-multi-auth add '); - return {}; - } - const customFetch = async (input, init) => { - await syncAuthFromOpenCode(getAuth); - let body = {}; - try { - body = init?.body ? JSON.parse(init.body) : {}; - } - catch { - body = {}; - } - const normalizedModel = normalizeModel(body.model); - const store = loadStore(); - const forceState = getForceState(); - const forcePinned = isForceActive() && !!forceState.forcedAlias; - const eligibleCount = Object.values(store.accounts).filter(acc => { - const now = Date.now(); - return (!acc.rateLimitedUntil || acc.rateLimitedUntil < now) && - (!acc.modelUnsupportedUntil || acc.modelUnsupportedUntil < now) && - (!acc.workspaceDeactivatedUntil || acc.workspaceDeactivatedUntil < now) && - !acc.authInvalid && - acc.enabled !== false; - }).length; - const maxAttempts = forcePinned ? 1 : Math.max(1, eligibleCount); - const triedAliases = new Set(); - let attempt = 0; - while (attempt < maxAttempts) { - attempt++; - const settings = getRuntimeSettings(); - const effectiveConfig = { - ...pluginConfig, - rotationStrategy: settings.settings.rotationStrategy - }; - const rotation = await getNextAccount(effectiveConfig, { - model: normalizedModel - }); - if (!rotation) { - if (forcePinned && forceState.forcedAlias) { - const forced = loadStore().accounts[forceState.forcedAlias]; - const now = Date.now(); - if (forced?.rateLimitedUntil && forced.rateLimitedUntil > now) { - return new Response(JSON.stringify({ - error: { - code: 'RATE_LIMITED', - message: `Forced account '${forced.alias}' is rate-limited until ${new Date(forced.rateLimitedUntil).toISOString()}`, - details: { alias: forced.alias, rateLimitedUntil: forced.rateLimitedUntil } - } - }), { status: 429, headers: { 'Content-Type': 'application/json' } }); - } - } - return new Response(JSON.stringify({ - error: Errors.noEligibleAccounts('No available accounts after filtering') - }), { status: 503, headers: { 'Content-Type': 'application/json' } }); - } - const { account, token } = rotation; - if (triedAliases.has(account.alias)) { - continue; - } - triedAliases.add(account.alias); - const decoded = decodeJWT(token); - const accountId = decoded?.[JWT_CLAIM_PATH]?.chatgpt_account_id; - if (!accountId) { - return new Response(JSON.stringify({ - error: { - code: 'TOKEN_PARSE_ERROR', - message: '[multi-auth] Failed to extract accountId from token' - } - }), { status: 401, headers: { 'Content-Type': 'application/json' } }); - } - const originalUrl = extractRequestUrl(input); - const url = toCodexBackendUrl(originalUrl); - const isStreaming = body?.stream === true; - const fastMode = /-fast$/.test(body.model || ''); - const supportedFastMode = fastMode && supportsFastMode(normalizedModel); - const reasoningMatch = body.model?.match(/-(none|low|medium|high|xhigh)$/); - const payload = { - ...body, - model: normalizedModel, - store: false - }; - if (payload.truncation === undefined) { - const truncationRaw = (process.env.OPENCODE_MULTI_AUTH_TRUNCATION || '').trim(); - if (truncationRaw && truncationRaw !== 'disabled' && truncationRaw !== 'false' && truncationRaw !== '0') { - payload.truncation = truncationRaw; - } - } - if (payload.input) { - payload.input = filterInput(payload.input); - } - if (reasoningMatch?.[1]) { - payload.reasoning = { - ...(payload.reasoning || {}), - effort: reasoningMatch[1] - }; - if (!isSparkModel(normalizedModel)) { - payload.reasoning.summary = payload.reasoning?.summary || 'auto'; - } - } - if (isSparkModel(normalizedModel) && payload.reasoning?.summary !== undefined) { - delete payload.reasoning.summary; - } - if (supportedFastMode) { - payload.service_tier = payload.service_tier || 'priority'; - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.log(`[multi-auth] fast mode enabled: ${normalizedModel} + service_tier=priority`); - } - } - else if (fastMode && process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.log(`[multi-auth] fast mode ignored for unsupported model: ${normalizedModel}`); - } - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1' && payload.service_tier === 'priority') { - console.log(`[multi-auth] priority service tier requested for ${normalizedModel}`); - } - delete payload.reasoning_effort; - try { - const headers = new Headers(init?.headers || {}); - headers.delete('x-api-key'); - headers.set('Content-Type', 'application/json'); - headers.set('Authorization', `Bearer ${token}`); - headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId); - headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES); - headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX); - const cacheKey = payload?.prompt_cache_key; - if (cacheKey) { - headers.set(OPENAI_HEADERS.CONVERSATION_ID, cacheKey); - headers.set(OPENAI_HEADERS.SESSION_ID, cacheKey); - } - else { - headers.delete(OPENAI_HEADERS.CONVERSATION_ID); - headers.delete(OPENAI_HEADERS.SESSION_ID); - } - headers.set('accept', 'text/event-stream'); - const sendPayload = async (requestPayload) => { - return fetch(url, { - method: init?.method || 'POST', - headers, - body: JSON.stringify(requestPayload) - }); - }; - const applyLimitUpdate = (response) => { - const limitUpdate = extractRateLimitUpdate(response.headers); - const mergedRateLimits = limitUpdate - ? mergeRateLimits(account.rateLimits, limitUpdate) - : account.rateLimits; - if (limitUpdate) { - const blockingResetAt = getBlockingRateLimitResetAt(mergedRateLimits); - updateAccount(account.alias, { - rateLimits: mergedRateLimits, - rateLimitedUntil: blockingResetAt - }); - } - return mergedRateLimits; - }; - let res = await sendPayload(payload); - let mergedRateLimits = applyLimitUpdate(res); - if (res.status === 400 && payload.service_tier === 'priority') { - const errorData = await res.clone().json().catch(() => ({})); - const errorText = await res.clone().text().catch(() => ''); - if (isCyberPolicyError(errorData, errorText)) { - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.log('[multi-auth] cyber_policy on priority tier; retrying once without service_tier'); - } - const standardTierPayload = { ...payload }; - delete standardTierPayload.service_tier; - res = await sendPayload(standardTierPayload); - mergedRateLimits = applyLimitUpdate(res); - } - } - if (res.status === 401 || res.status === 403) { - const errorData = await res.clone().json().catch(() => ({})); - const message = errorData?.error?.message || ''; - if (message.toLowerCase().includes('invalidated') || res.status === 401) { - markAuthInvalid(account.alias); - } - if (attempt < maxAttempts) { - continue; - } - return new Response(JSON.stringify({ - error: Errors.maxRetriesExceeded(attempt, Array.from(triedAliases)) - }), { status: res.status, headers: { 'Content-Type': 'application/json' } }); - } - if (res.status === 429) { - const errorData = await res.clone().json().catch(() => ({})); - const errorText = extractErrorMessage(errorData); - const rateLimitedUntil = resolveRateLimitedUntil(mergedRateLimits, res.headers, errorText, pluginConfig.rateLimitCooldownMs); - markRateLimited(account.alias, rateLimitedUntil); - if (attempt < maxAttempts) { - continue; - } - return new Response(JSON.stringify({ - error: Errors.maxRetriesExceeded(attempt, Array.from(triedAliases)) - }), { status: 429, headers: { 'Content-Type': 'application/json' } }); - } - if (res.status === 402) { - const errorData = await res.clone().json().catch(() => null); - const errorText = await res.clone().text().catch(() => ''); - const code = (typeof errorData?.detail?.code === 'string' && errorData.detail.code) || - (typeof errorData?.error?.code === 'string' && errorData.error.code) || - ''; - const message = (typeof errorData?.detail?.message === 'string' && errorData.detail.message) || - (typeof errorData?.detail === 'string' && errorData.detail) || - (typeof errorData?.error?.message === 'string' && errorData.error.message) || - (typeof errorData?.message === 'string' && errorData.message) || - errorText || - ''; - const isDeactivatedWorkspace = code === 'deactivated_workspace' || - message.toLowerCase().includes('deactivated_workspace') || - message.toLowerCase().includes('deactivated workspace'); - if (isDeactivatedWorkspace) { - markWorkspaceDeactivated(account.alias, pluginConfig.workspaceDeactivatedCooldownMs, { - error: message || code - }); - if (attempt < maxAttempts) { - continue; - } - return new Response(JSON.stringify({ - error: Errors.maxRetriesExceeded(attempt, Array.from(triedAliases)) - }), { status: 402, headers: { 'Content-Type': 'application/json' } }); - } - } - if (res.status === 400) { - const errorData = await res.clone().json().catch(() => ({})); - const message = (typeof errorData?.detail === 'string' && errorData.detail) || - (typeof errorData?.error?.message === 'string' && errorData.error.message) || - (typeof errorData?.message === 'string' && errorData.message) || - ''; - const isModelUnsupported = typeof message === 'string' && - message.toLowerCase().includes('model is not supported') && - message.toLowerCase().includes('chatgpt account'); - if (isModelUnsupported) { - markModelUnsupported(account.alias, pluginConfig.modelUnsupportedCooldownMs, { - model: normalizedModel, - error: message - }); - if (attempt < maxAttempts) { - continue; - } - return new Response(JSON.stringify({ - error: Errors.maxRetriesExceeded(attempt, Array.from(triedAliases)) - }), { status: 400, headers: { 'Content-Type': 'application/json' } }); - } - } - if (!res.ok) { - return res; - } - const responseHeaders = ensureContentType(res.headers); - if (!isStreaming && responseHeaders.get('content-type')?.includes('text/event-stream')) { - return await convertSseToJson(res, responseHeaders); - } - return res; - } - catch (err) { - return new Response(JSON.stringify({ error: { code: 'REQUEST_FAILED', message: `[multi-auth] Request failed: ${err}` } }), { status: 500, headers: { 'Content-Type': 'application/json' } }); - } - } - return new Response(JSON.stringify({ - error: Errors.maxRetriesExceeded(attempt, Array.from(triedAliases)) - }), { status: 503, headers: { 'Content-Type': 'application/json' } }); - }; - // Return SDK configuration with custom fetch for rotation - return { - apiKey: 'chatgpt-oauth', - baseURL: CODEX_BASE_URL, - fetch: customFetch - }; - }, - methods: [ - { - label: 'ChatGPT OAuth (Multi-Account)', - type: 'oauth', - prompts: [ - { - type: 'text', - key: 'alias', - message: 'Account alias (e.g., personal, work)', - placeholder: 'personal' - } - ], - /** - * OAuth flow - opens browser for ChatGPT login - */ - authorize: async (inputs) => { - const alias = inputs?.alias || `account-${Date.now()}`; - const flow = await createAuthorizationFlow(); - return { - url: flow.url, - method: 'auto', - instructions: `Login with your ChatGPT Plus/Pro account for "${alias}"`, - callback: async () => { - try { - const account = await loginAccount(alias, flow); - return { - type: 'success', - provider: PROVIDER_ID, - refresh: account.refreshToken, - access: account.accessToken, - expires: account.expiresAt - }; - } - catch { - return { type: 'failed' }; - } - } - }; - } - }, - { - label: 'Skip (use existing accounts)', - type: 'api' - } - ] - } - }; -}; -export default MultiAuthPlugin; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index 444856b..0000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/limits-refresh.d.ts b/dist/limits-refresh.d.ts deleted file mode 100644 index 1335eb2..0000000 --- a/dist/limits-refresh.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { AccountCredentials } from './types.js'; -export interface LimitRefreshResult { - alias: string; - updated: boolean; - error?: string; -} -export declare function refreshRateLimitsForAccount(account: AccountCredentials): Promise; -export declare function refreshRateLimits(accounts: AccountCredentials[], alias?: string): Promise; -//# sourceMappingURL=limits-refresh.d.ts.map \ No newline at end of file diff --git a/dist/limits-refresh.d.ts.map b/dist/limits-refresh.d.ts.map deleted file mode 100644 index ab18bf6..0000000 --- a/dist/limits-refresh.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"limits-refresh.d.ts","sourceRoot":"","sources":["../src/limits-refresh.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkH1G;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAkB/B"} \ No newline at end of file diff --git a/dist/limits-refresh.js b/dist/limits-refresh.js deleted file mode 100644 index 874eca6..0000000 --- a/dist/limits-refresh.js +++ /dev/null @@ -1,120 +0,0 @@ -import { getBlockingRateLimitResetAt, isRateLimitErrorText, mergeRateLimits, parseRateLimitResetFromError } from './rate-limits.js'; -import { markAuthInvalid, markWorkspaceDeactivated } from './rotation.js'; -import { loadStore, updateAccount } from './store.js'; -import { probeRateLimitsForAccount } from './probe-limits.js'; -import { logError, logInfo } from './logger.js'; -import { DEFAULT_CONFIG, calculateLimitsConfidence } from './types.js'; -import { fetchUsageRateLimitsForAccount } from './usage-limits.js'; -export async function refreshRateLimitsForAccount(account) { - updateAccount(account.alias, { limitStatus: 'running', limitError: undefined }); - logInfo(`Refreshing limits for ${account.alias}`); - const usage = await fetchUsageRateLimitsForAccount(account); - if (usage.rateLimits) { - const now = Date.now(); - const updates = { - rateLimits: mergeRateLimits(account.rateLimits, usage.rateLimits), - limitStatus: 'success', - limitError: undefined, - lastLimitProbeAt: now, - limitsConfidence: calculateLimitsConfidence(now, account.lastLimitErrorAt, 'success'), - authInvalid: false, - authInvalidatedAt: undefined - }; - if (usage.planType) { - updates.planType = usage.planType; - } - if (typeof usage.rateLimitedUntil === 'number' && usage.rateLimitedUntil > now) { - updates.rateLimitedUntil = usage.rateLimitedUntil; - } - updateAccount(account.alias, updates); - logInfo(`Limits refreshed for ${account.alias} via usage API`); - return { alias: account.alias, updated: true }; - } - if (usage.error) { - if (usage.shouldProbeFallback === false) { - const now = Date.now(); - if (usage.authInvalid) { - markAuthInvalid(account.alias); - } - if (usage.workspaceDeactivated) { - markWorkspaceDeactivated(account.alias, DEFAULT_CONFIG.workspaceDeactivatedCooldownMs, { error: usage.workspaceDeactivatedReason || usage.error }); - } - updateAccount(account.alias, { - limitStatus: 'error', - limitError: usage.error, - lastLimitErrorAt: now, - limitsConfidence: calculateLimitsConfidence(account.lastLimitProbeAt, now, 'error') - }); - logInfo(`Skipping limits probe for ${account.alias}: ${usage.error}`); - return { - alias: account.alias, - updated: false, - error: usage.error - }; - } - logInfo(`Usage API limits lookup failed for ${account.alias}, falling back to probe: ${usage.error}`); - } - const probe = await probeRateLimitsForAccount(account); - if (!probe.isAuthoritative || !probe.rateLimits) { - const now = Date.now(); - const errorText = usage.error || probe.error || 'Probe failed'; - logError(`Limit refresh failed for ${account.alias}: ${errorText}`); - const likelyRateLimit = isRateLimitErrorText(errorText); - const parsedResetAt = parseRateLimitResetFromError(errorText, now); - const fallbackResetAt = likelyRateLimit - ? getBlockingRateLimitResetAt(account.rateLimits, now, { - conservativeWhenRemainingUnknown: true - }) - : undefined; - const rateLimitedUntil = parsedResetAt ?? fallbackResetAt; - const updates = { - limitStatus: 'error', - limitError: errorText, - lastLimitErrorAt: now, - limitsConfidence: calculateLimitsConfidence(account.lastLimitProbeAt, now, 'error') - }; - if (typeof rateLimitedUntil === 'number' && rateLimitedUntil > now) { - updates.rateLimitedUntil = rateLimitedUntil; - } - updateAccount(account.alias, updates); - return { - alias: account.alias, - updated: false, - error: errorText - }; - } - const now = Date.now(); - const mergedRateLimits = mergeRateLimits(account.rateLimits, probe.rateLimits); - const blockingResetAt = getBlockingRateLimitResetAt(mergedRateLimits, now); - updateAccount(account.alias, { - rateLimits: mergedRateLimits, - limitStatus: 'success', - limitError: undefined, - lastLimitProbeAt: now, - limitsConfidence: calculateLimitsConfidence(now, account.lastLimitErrorAt, 'success'), - rateLimitedUntil: blockingResetAt, - authInvalid: false, - authInvalidatedAt: undefined - }); - logInfo(`Limits refreshed for ${account.alias} using model ${probe.probeModel || 'unknown'}, effort ${probe.probeEffort || 'default'}`); - return { alias: account.alias, updated: true }; -} -export async function refreshRateLimits(accounts, alias) { - if (alias) { - const account = accounts.find((acc) => acc.alias === alias); - if (!account) { - return [{ alias, updated: false, error: 'Unknown alias' }]; - } - return [await refreshRateLimitsForAccount(account)]; - } - const store = loadStore(); - const results = []; - for (const account of accounts) { - results.push(await refreshRateLimitsForAccount(account)); - } - if (results.length === 0 && !store.activeAlias) { - return [{ alias: 'active', updated: false, error: 'No accounts configured' }]; - } - return results; -} -//# sourceMappingURL=limits-refresh.js.map \ No newline at end of file diff --git a/dist/limits-refresh.js.map b/dist/limits-refresh.js.map deleted file mode 100644 index 2364626..0000000 --- a/dist/limits-refresh.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"limits-refresh.js","sourceRoot":"","sources":["../src/limits-refresh.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/logger.d.ts b/dist/logger.d.ts deleted file mode 100644 index bc852fd..0000000 --- a/dist/logger.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export declare function logInfo(message: string): void; -export declare function logWarn(message: string): void; -export declare function logError(message: string): void; -export declare function getLogPath(): string; -export declare function readLogTail(maxLines?: number): string[]; -//# sourceMappingURL=logger.d.ts.map \ No newline at end of file diff --git a/dist/logger.d.ts.map b/dist/logger.d.ts.map deleted file mode 100644 index 1ce3ba6..0000000 --- a/dist/logger.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AA+BA,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,WAAW,CAAC,QAAQ,SAAgB,GAAG,MAAM,EAAE,CAS9D"} \ No newline at end of file diff --git a/dist/logger.js b/dist/logger.js deleted file mode 100644 index c2119da..0000000 --- a/dist/logger.js +++ /dev/null @@ -1,52 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -const DEFAULT_LOG_DIR = path.join(os.homedir(), '.config', 'opencode-multi-auth', 'logs'); -const LOG_FILE = process.env.CODEX_SOFT_LOG_PATH || path.join(DEFAULT_LOG_DIR, 'codex-soft.log'); -const MAX_LOG_LINES = 400; -function ensureDir() { - const dir = path.dirname(LOG_FILE); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); - } -} -function sanitize(message) { - return message - .replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[jwt]') - .replace(/\bsk-[A-Za-z0-9]{10,}\b/g, '[token]'); -} -function append(level, message) { - try { - ensureDir(); - const line = `${new Date().toISOString()} [${level}] ${sanitize(message)}\n`; - fs.appendFileSync(LOG_FILE, line, { encoding: 'utf-8', mode: 0o600 }); - } - catch { - // Ignore log write failures - } -} -export function logInfo(message) { - append('info', message); -} -export function logWarn(message) { - append('warn', message); -} -export function logError(message) { - append('error', message); -} -export function getLogPath() { - return LOG_FILE; -} -export function readLogTail(maxLines = MAX_LOG_LINES) { - try { - if (!fs.existsSync(LOG_FILE)) - return []; - const data = fs.readFileSync(LOG_FILE, 'utf-8'); - const lines = data.split('\n').filter(Boolean); - return lines.slice(Math.max(0, lines.length - maxLines)); - } - catch { - return []; - } -} -//# sourceMappingURL=logger.js.map \ No newline at end of file diff --git a/dist/logger.js.map b/dist/logger.js.map deleted file mode 100644 index 03428e7..0000000 --- a/dist/logger.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,CAAC,CAAA;AACzF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAA;AAChG,MAAM,aAAa,GAAG,GAAG,CAAA;AAEzB,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACrD,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,OAAe;IAC/B,OAAO,OAAO;SACX,OAAO,CAAC,oEAAoE,EAAE,OAAO,CAAC;SACtF,OAAO,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAA;AACnD,CAAC;AAED,SAAS,MAAM,CAAC,KAAa,EAAE,OAAe;IAC5C,IAAI,CAAC;QACH,SAAS,EAAE,CAAA;QACX,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,KAAK,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAA;QAC5E,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,4BAA4B;IAC9B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAQ,GAAG,aAAa;IAClD,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAA;QACvC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC9C,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAA;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/dist/models.d.ts b/dist/models.d.ts deleted file mode 100644 index 91f824e..0000000 --- a/dist/models.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { OpenAIModel, ProviderModel } from './types.js'; -export declare function fetchAvailableModels(token: string): Promise; -export declare function filterGPT5Models(models: OpenAIModel[]): OpenAIModel[]; -export declare function generateModelVariants(baseModels: OpenAIModel[]): Record; -export declare function getDefaultModels(): Record; -export declare function getModels(token?: string): Promise>; -//# sourceMappingURL=models.d.ts.map \ No newline at end of file diff --git a/dist/models.d.ts.map b/dist/models.d.ts.map deleted file mode 100644 index 995668f..0000000 --- a/dist/models.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAiF5D,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAiBhF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAErE;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAwB9F;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAwChE;AAKD,wBAAsB,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAsBtF"} \ No newline at end of file diff --git a/dist/models.js b/dist/models.js deleted file mode 100644 index 32a9456..0000000 --- a/dist/models.js +++ /dev/null @@ -1,167 +0,0 @@ -const MODELS_ENDPOINT = 'https://api.openai.com/v1/models'; -const REASONING_LEVELS = ['none', 'low', 'medium', 'high', 'xhigh']; -const MODEL_LIMITS = { - 'gpt-5.5': { context: 530000, input: 400000, output: 130000 }, - 'gpt-5.4': { context: 1050000, input: 922000, output: 128000 }, - 'gpt-5.3': { context: 272000, output: 128000 }, - 'gpt-5.3-codex-spark': { context: 272000, output: 128000 }, - 'gpt-5.2': { context: 272000, output: 128000 }, - 'gpt-5.3-codex': { context: 272000, output: 128000 }, - 'gpt-5.2-codex': { context: 272000, output: 128000 }, - 'gpt-5.1': { context: 272000, output: 128000 }, - 'gpt-5.1-codex': { context: 272000, output: 128000 }, - 'gpt-5.1-codex-max': { context: 272000, output: 128000 }, - 'gpt-5.1-codex-mini': { context: 272000, output: 128000 }, -}; -function getModelLimits(modelId) { - for (const [prefix, limits] of Object.entries(MODEL_LIMITS)) { - if (modelId.startsWith(prefix)) - return limits; - } - return { context: 128000, output: 32000 }; -} -function buildProviderModel(baseId, reasoning) { - const limits = getModelLimits(baseId); - const displayName = `${baseId} ${reasoning.charAt(0).toUpperCase() + reasoning.slice(1)} (OAuth)`; - return { - name: displayName, - limit: limits, - modalities: { - input: ['text', 'image'], - output: ['text'] - }, - options: { - reasoningEffort: reasoning, - reasoningSummary: reasoning === 'high' || reasoning === 'xhigh' ? 'detailed' : 'auto', - textVerbosity: 'medium', - include: ['reasoning.encrypted_content'], - store: false - } - }; -} -function buildDefaultProviderModel(baseId) { - return { - ...buildProviderModel(baseId, 'medium'), - name: `${baseId} (OAuth)` - }; -} -function supportsFastMode(baseId) { - return baseId === 'gpt-5.5' || baseId === 'gpt-5.4'; -} -function buildFastProviderModel(baseId) { - const limits = getModelLimits(baseId); - return { - name: `${baseId} Fast (OAuth)`, - limit: limits, - modalities: { - input: ['text', 'image'], - output: ['text'] - }, - options: { - reasoningEffort: 'medium', - reasoningSummary: 'auto', - textVerbosity: 'medium', - include: ['reasoning.encrypted_content'], - store: false, - service_tier: 'priority' - } - }; -} -export async function fetchAvailableModels(token) { - try { - const res = await fetch(MODELS_ENDPOINT, { - headers: { Authorization: `Bearer ${token}` } - }); - if (!res.ok) { - console.error(`[multi-auth] Failed to fetch models: ${res.status}`); - return []; - } - const data = (await res.json()); - return data.data || []; - } - catch (err) { - console.error('[multi-auth] Error fetching models:', err); - return []; - } -} -export function filterGPT5Models(models) { - return models.filter(m => m.id.match(/^gpt-5/)); -} -export function generateModelVariants(baseModels) { - const result = {}; - for (const model of baseModels) { - const baseId = model.id; - const isCodex = baseId.includes('codex'); - result[baseId] = buildDefaultProviderModel(baseId); - const levels = isCodex - ? ['low', 'medium', 'high', 'xhigh'] - : ['none', 'low', 'medium', 'high', 'xhigh']; - for (const level of levels) { - const variantId = `${baseId}-${level}`; - result[variantId] = buildProviderModel(baseId, level); - } - if (supportsFastMode(baseId)) { - result[`${baseId}-fast`] = buildFastProviderModel(baseId); - } - } - return result; -} -export function getDefaultModels() { - const defaults = [ - 'gpt-5.5', - 'gpt-5.4', - 'gpt-5.3', - 'gpt-5.3-codex-spark', - 'gpt-5.3-codex', - 'gpt-5.2', - 'gpt-5.2-codex', - 'gpt-5.1', - 'gpt-5.1-codex', - 'gpt-5.1-codex-max', - 'gpt-5.1-codex-mini' - ]; - const result = {}; - for (const baseId of defaults) { - const isCodex = baseId.includes('codex'); - const levels = isCodex - ? ['low', 'medium', 'high', 'xhigh'] - : ['none', 'low', 'medium', 'high', 'xhigh']; - result[baseId] = buildDefaultProviderModel(baseId); - for (const level of levels) { - if (baseId === 'gpt-5.1-codex-mini' && !['medium', 'high'].includes(level)) - continue; - if (baseId === 'gpt-5.1-codex' && level === 'xhigh') - continue; - if (baseId === 'gpt-5.1' && level === 'xhigh') - continue; - const variantId = `${baseId}-${level}`; - result[variantId] = buildProviderModel(baseId, level); - } - if (supportsFastMode(baseId)) { - result[`${baseId}-fast`] = buildFastProviderModel(baseId); - } - } - return result; -} -let cachedModels = null; -let cacheExpiry = 0; -export async function getModels(token) { - const now = Date.now(); - const CACHE_TTL = 60 * 60 * 1000; - if (cachedModels && now < cacheExpiry) { - return cachedModels; - } - if (token) { - const fetched = await fetchAvailableModels(token); - const gpt5 = filterGPT5Models(fetched); - if (gpt5.length > 0) { - cachedModels = generateModelVariants(gpt5); - cacheExpiry = now + CACHE_TTL; - return cachedModels; - } - } - cachedModels = getDefaultModels(); - cacheExpiry = now + CACHE_TTL; - return cachedModels; -} -//# sourceMappingURL=models.js.map \ No newline at end of file diff --git a/dist/models.js.map b/dist/models.js.map deleted file mode 100644 index d14befa..0000000 --- a/dist/models.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/probe-limits.d.ts b/dist/probe-limits.d.ts deleted file mode 100644 index 3ff2c96..0000000 --- a/dist/probe-limits.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { AccountCredentials, AccountRateLimits } from './types.js'; -export interface ProbeResult { - rateLimits?: AccountRateLimits; - eventTs?: number; - sourceFile?: string; - probeModel?: string; - probeEffort?: string; - probeDurationMs?: number; - error?: string; - isAuthoritative?: boolean; -} -export declare function shouldRetryWithFallback(error?: string): boolean; -export declare function getProbeEffort(): string; -export declare function getProbeModels(): string[]; -export declare function probeRateLimitsForAccount(account: AccountCredentials): Promise; -export declare function getProbeHomeRoot(): string; -//# sourceMappingURL=probe-limits.d.ts.map \ No newline at end of file diff --git a/dist/probe-limits.d.ts.map b/dist/probe-limits.d.ts.map deleted file mode 100644 index af793f8..0000000 --- a/dist/probe-limits.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"probe-limits.d.ts","sourceRoot":"","sources":["../src/probe-limits.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAUvE,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAyLD,wBAAgB,uBAAuB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAa/D;AAGD,wBAAgB,cAAc,IAAI,MAAM,CAMvC;AAED,wBAAgB,cAAc,IAAI,MAAM,EAAE,CASzC;AAqED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CA0FjG;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"} \ No newline at end of file diff --git a/dist/probe-limits.js b/dist/probe-limits.js deleted file mode 100644 index c13b348..0000000 --- a/dist/probe-limits.js +++ /dev/null @@ -1,348 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { spawn } from 'node:child_process'; -import { findLatestSessionRateLimits } from './sessions-limits.js'; -import { loadStore, updateAccount } from './store.js'; -const CODEX_HOME_ROOT = path.join(os.homedir(), '.codex-multi'); -const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml'); -const DEFAULT_PROMPT = 'Reply ONLY with OK. Do not run any commands.'; -const EXEC_TIMEOUT_MS = 120_000; -const DEFAULT_PROBE_MODELS = ['gpt-5.5', 'gpt-5.4', 'gpt-5.3-codex', 'gpt-5.2-codex', 'gpt-5-codex']; -const DEFAULT_PROBE_EFFORT = 'low'; -function asString(value) { - return typeof value === 'string' ? value : undefined; -} -function decodeJwtPayload(token) { - if (!token) - return null; - try { - const parts = token.split('.'); - if (parts.length !== 3) - return null; - const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/'); - const padded = payload.padEnd(payload.length + ((4 - (payload.length % 4)) % 4), '='); - const decoded = Buffer.from(padded, 'base64').toString('utf-8'); - return JSON.parse(decoded); - } - catch { - return null; - } -} -function getEmailFromClaims(claims) { - if (!claims) - return undefined; - if (typeof claims.email === 'string') - return claims.email; - const profile = claims['https://api.openai.com/profile']; - if (typeof profile?.email === 'string') - return profile.email; - return undefined; -} -function getAuthClaim(claims) { - if (!claims) - return undefined; - const auth = claims['https://api.openai.com/auth']; - if (!auth || typeof auth !== 'object') - return undefined; - return auth; -} -function readProbeAuthTokens(codexHome) { - const authPath = path.join(codexHome, 'auth.json'); - if (!fs.existsSync(authPath)) - return null; - let parsed; - try { - parsed = JSON.parse(fs.readFileSync(authPath, 'utf-8')); - } - catch { - return null; - } - const tokens = parsed?.tokens && typeof parsed.tokens === 'object' ? parsed.tokens : parsed; - const accessToken = asString(tokens?.access_token ?? tokens?.accessToken ?? parsed?.access_token ?? parsed?.accessToken); - const refreshToken = asString(tokens?.refresh_token ?? tokens?.refreshToken ?? parsed?.refresh_token ?? parsed?.refreshToken); - const idToken = asString(tokens?.id_token ?? tokens?.idToken ?? parsed?.id_token ?? parsed?.idToken); - const accountIdFromToken = asString(tokens?.account_id ?? tokens?.accountId ?? parsed?.account_id ?? parsed?.accountId); - if (!accessToken && !refreshToken) - return null; - const accessClaims = decodeJwtPayload(accessToken); - const idClaims = decodeJwtPayload(idToken); - const authAccess = getAuthClaim(accessClaims); - const authId = getAuthClaim(idClaims); - const accountId = accountIdFromToken || - asString(authAccess?.chatgpt_account_id) || - asString(authId?.chatgpt_account_id); - const accountUserId = asString(authAccess?.chatgpt_account_user_id) || - asString(authId?.chatgpt_account_user_id); - const userId = asString(authAccess?.user_id) || - asString(authAccess?.chatgpt_user_id) || - asString(authId?.user_id) || - asString(authId?.chatgpt_user_id); - const planType = asString(authAccess?.chatgpt_plan_type) || - asString(authId?.chatgpt_plan_type); - const email = getEmailFromClaims(idClaims) || getEmailFromClaims(accessClaims); - const exp = accessClaims?.exp ?? idClaims?.exp; - const expiresAt = typeof exp === 'number' ? exp * 1000 : undefined; - const lastRefresh = asString(parsed?.last_refresh ?? parsed?.lastRefresh); - return { - accessToken, - refreshToken, - idToken, - accountId, - accountUserId, - userId, - planType, - email, - expiresAt, - lastRefresh - }; -} -function syncAccountTokensFromProbeHome(alias, codexHome) { - const parsed = readProbeAuthTokens(codexHome); - if (!parsed?.accessToken || !parsed.refreshToken) - return; - const current = loadStore().accounts[alias]; - const tokenChanged = Boolean(current && - (current.accessToken !== parsed.accessToken || - current.refreshToken !== parsed.refreshToken || - (parsed.idToken && current.idToken !== parsed.idToken))); - const updates = { - accessToken: parsed.accessToken, - refreshToken: parsed.refreshToken, - lastSeenAt: Date.now(), - source: 'codex' - }; - if (parsed.idToken) - updates.idToken = parsed.idToken; - if (parsed.accountId) - updates.accountId = parsed.accountId; - if (parsed.accountUserId) - updates.accountUserId = parsed.accountUserId; - if (parsed.userId) - updates.userId = parsed.userId; - if (parsed.planType) - updates.planType = parsed.planType; - if (parsed.email) - updates.email = parsed.email; - if (typeof parsed.expiresAt === 'number' && Number.isFinite(parsed.expiresAt)) { - updates.expiresAt = parsed.expiresAt; - } - if (tokenChanged && parsed.lastRefresh) - updates.lastRefresh = parsed.lastRefresh; - updateAccount(alias, updates); -} -function ensureDir(dir) { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); - } -} -function sanitizeAlias(alias) { - return alias.replace(/[^a-zA-Z0-9._-]/g, '_'); -} -function getAliasHome(alias) { - return path.join(CODEX_HOME_ROOT, sanitizeAlias(alias)); -} -function writeAuthJson(dir, account) { - if (!account.accessToken || !account.refreshToken) { - throw new Error('Missing tokens for alias'); - } - const tokens = { - access_token: account.accessToken, - refresh_token: account.refreshToken, - account_id: account.accountId - }; - if (account.idToken) { - tokens.id_token = account.idToken; - } - const auth = { - OPENAI_API_KEY: null, - tokens, - last_refresh: new Date().toISOString() - }; - const authPath = path.join(dir, 'auth.json'); - fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), { mode: 0o600 }); -} -function copyConfigToml(dir) { - if (!fs.existsSync(CODEX_CONFIG_PATH)) - return; - const target = path.join(dir, 'config.toml'); - try { - fs.copyFileSync(CODEX_CONFIG_PATH, target); - } - catch { - // ignore config copy errors - } -} -export function shouldRetryWithFallback(error) { - if (!error) - return false; - const text = error.toLowerCase(); - return (text.includes('model_not_found') || - text.includes('model is not supported') || - text.includes('requested model') || - text.includes('does not exist') || - // Phase C: Handle reasoning.effort and unsupported_value errors - text.includes('unsupported_value') || - text.includes('reasoning.effort') || - text.includes('reasoning effort')); -} -// Phase C: Get probe effort from environment or default to 'low' -export function getProbeEffort() { - const envEffort = process.env.OPENCODE_MULTI_AUTH_PROBE_EFFORT; - if (envEffort && ['low', 'medium', 'high'].includes(envEffort.toLowerCase())) { - return envEffort.toLowerCase(); - } - return DEFAULT_PROBE_EFFORT; -} -export function getProbeModels() { - const raw = (process.env.OPENCODE_MULTI_AUTH_LIMITS_PROBE_MODELS || '').trim(); - const fromEnv = raw - .split(',') - .map((item) => item.trim()) - .filter(Boolean); - const candidates = fromEnv.length > 0 ? fromEnv : DEFAULT_PROBE_MODELS; - return Array.from(new Set(candidates)); -} -async function runCodexExec(codexHome, model, effort) { - return new Promise((resolve) => { - const args = [ - 'exec', - '--skip-git-repo-check', - '--cd', - codexHome, - '--sandbox', - 'read-only' - ]; - if (model) { - args.push('-m', model); - } - // Phase C: Add reasoning effort configuration - if (effort) { - args.push('-c', `model_reasoning_effort="${effort}"`); - } - args.push(DEFAULT_PROMPT); - let stderr = ''; - let stdout = ''; - const startTime = Date.now(); - const codexBin = process.env.CODEX_CLI_BIN?.trim() || 'codex'; - const child = spawn(codexBin, args, { - env: { ...process.env, CODEX_HOME: codexHome }, - stdio: ['ignore', 'pipe', 'pipe'] - }); - const timer = setTimeout(() => { - child.kill('SIGTERM'); - const durationMs = Date.now() - startTime; - resolve({ ok: false, error: 'codex exec timed out', durationMs }); - }, EXEC_TIMEOUT_MS); - child.stdout.on('data', (data) => { - stdout += data.toString(); - if (stdout.length > 4000) - stdout = stdout.slice(-4000); - }); - child.stderr.on('data', (data) => { - stderr += data.toString(); - if (stderr.length > 4000) - stderr = stderr.slice(-4000); - }); - child.on('error', (err) => { - clearTimeout(timer); - const durationMs = Date.now() - startTime; - resolve({ ok: false, error: String(err), durationMs }); - }); - child.on('close', (code) => { - clearTimeout(timer); - const durationMs = Date.now() - startTime; - if (code === 0) { - resolve({ ok: true, durationMs }); - } - else { - const message = stderr.trim() || stdout.trim() || `codex exec failed (code ${code})`; - resolve({ ok: false, error: message, durationMs }); - } - }); - }); -} -export async function probeRateLimitsForAccount(account) { - const codexHome = getAliasHome(account.alias); - ensureDir(codexHome); - writeAuthJson(codexHome, account); - copyConfigToml(codexHome); - const sessionsDir = path.join(codexHome, 'sessions'); - const probeModels = getProbeModels(); - const probeEffort = getProbeEffort(); - let lastError = 'No usable rate_limits data found in current Codex session output'; - const attemptErrors = []; - for (let idx = 0; idx < probeModels.length; idx++) { - const probeModel = probeModels[idx]; - const startedAt = Date.now(); - // Phase C: Pass effort config and track duration - const execResult = await runCodexExec(codexHome, probeModel, probeEffort); - syncAccountTokensFromProbeHome(account.alias, codexHome); - const latest = findLatestSessionRateLimits({ - sessionsDir, - sinceMs: startedAt - 5_000 - }); - // Phase C: Only accept authoritative data from successful completions - if (execResult.ok && latest?.rateLimits) { - return { - rateLimits: latest.rateLimits, - eventTs: latest.eventTs, - sourceFile: latest.sourceFile, - probeModel, - probeEffort, - probeDurationMs: execResult.durationMs, - isAuthoritative: true - }; - } - if (execResult.error) { - lastError = execResult.error; - attemptErrors.push(`[model=${probeModel}, effort=${probeEffort}] ${execResult.error}`); - } - const hasNext = idx < probeModels.length - 1; - if (!hasNext) - break; - // Phase C: Retry with fallback on unsupported_value / reasoning.effort errors - if (shouldRetryWithFallback(execResult.error)) { - // Try with 'low' effort explicitly if current effort failed - if (probeEffort !== 'low' && execResult.error?.toLowerCase().includes('reasoning')) { - const lowEffortResult = await runCodexExec(codexHome, probeModel, 'low'); - syncAccountTokensFromProbeHome(account.alias, codexHome); - const lowEffortLatest = findLatestSessionRateLimits({ - sessionsDir, - sinceMs: Date.now() - 5_000 - }); - if (lowEffortResult.ok && lowEffortLatest?.rateLimits) { - return { - rateLimits: lowEffortLatest.rateLimits, - eventTs: lowEffortLatest.eventTs, - sourceFile: lowEffortLatest.sourceFile, - probeModel, - probeEffort: 'low', - probeDurationMs: lowEffortResult.durationMs, - isAuthoritative: true - }; - } - if (lowEffortResult.error) { - attemptErrors.push(`[model=${probeModel}, effort=low] ${lowEffortResult.error}`); - } - } - continue; - } - // Don't retry if it's not a fallback-eligible error - break; - } - if (attemptErrors.length > 0) { - return { - error: attemptErrors[attemptErrors.length - 1], - isAuthoritative: false - }; - } - return { - error: lastError, - isAuthoritative: false - }; -} -export function getProbeHomeRoot() { - return CODEX_HOME_ROOT; -} -//# sourceMappingURL=probe-limits.js.map \ No newline at end of file diff --git a/dist/probe-limits.js.map b/dist/probe-limits.js.map deleted file mode 100644 index 8a4adf7..0000000 --- a/dist/probe-limits.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"probe-limits.js","sourceRoot":"","sources":["../src/probe-limits.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/rate-limits.d.ts b/dist/rate-limits.d.ts deleted file mode 100644 index effc3b8..0000000 --- a/dist/rate-limits.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { AccountRateLimits, RateLimitWindow } from './types.js'; -type RateLimitUpdate = AccountRateLimits; -export declare function hasMeaningfulRateLimitWindow(window: RateLimitWindow | undefined): boolean; -export declare function hasMeaningfulRateLimits(rateLimits: AccountRateLimits | undefined | null): boolean; -export declare function extractRateLimitUpdate(headers: Headers): RateLimitUpdate | null; -export declare function mergeRateLimits(existing: AccountRateLimits | undefined, update: RateLimitUpdate): AccountRateLimits; -export declare function parseRetryAfterHeader(retryAfter: string | null, now?: number): number | undefined; -export declare function parseRateLimitResetFromError(text: string, now?: number): number | undefined; -export declare function isRateLimitErrorText(text: string): boolean; -export declare function getBlockingRateLimitResetAt(rateLimits: AccountRateLimits | undefined, now?: number, opts?: { - conservativeWhenRemainingUnknown?: boolean; -}): number | undefined; -export {}; -//# sourceMappingURL=rate-limits.d.ts.map \ No newline at end of file diff --git a/dist/rate-limits.d.ts.map b/dist/rate-limits.d.ts.map deleted file mode 100644 index b044a77..0000000 --- a/dist/rate-limits.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"rate-limits.d.ts","sourceRoot":"","sources":["../src/rate-limits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEpE,KAAK,eAAe,GAAG,iBAAiB,CAAA;AA6DxC,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,GAAG,OAAO,CAMzF;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAMjG;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CA0D/E;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,iBAAiB,GAAG,SAAS,EACvC,MAAM,EAAE,eAAe,GACtB,iBAAiB,CAQnB;AAED,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,GAAG,GAAE,MAAmB,GACvB,MAAM,GAAG,SAAS,CAapB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAmB,GACvB,MAAM,GAAG,SAAS,CAqBpB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAW1D;AAED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,iBAAiB,GAAG,SAAS,EACzC,GAAG,GAAE,MAAmB,EACxB,IAAI,CAAC,EAAE;IAAE,gCAAgC,CAAC,EAAE,OAAO,CAAA;CAAE,GACpD,MAAM,GAAG,SAAS,CAkCpB"} \ No newline at end of file diff --git a/dist/rate-limits.js b/dist/rate-limits.js deleted file mode 100644 index bf37fe5..0000000 --- a/dist/rate-limits.js +++ /dev/null @@ -1,222 +0,0 @@ -const WINDOW_PATTERNS = [ - { key: 'fiveHour', patterns: ['5h', '5hr', '5hour', '5hours', '5-hour'] }, - { key: 'weekly', patterns: ['week', 'weekly', '1w', '7d', '7day', '7days', '7-day', '1-week'] } -]; -function parseNumber(value) { - const match = value.match(/-?\d+(?:\.\d+)?/); - if (!match) - return undefined; - const num = Number(match[0]); - if (Number.isNaN(num)) - return undefined; - return num; -} -function parseReset(value, now) { - const num = parseNumber(value); - if (num === undefined) - return undefined; - if (num > 1e12) - return num; - if (num > 1e9) - return num * 1000; - return now + num * 1000; -} -function parseTimestamp(value) { - const num = parseNumber(value); - if (num !== undefined) { - if (num > 1e12) - return num; - if (num > 1e9) - return num * 1000; - } - const parsed = Date.parse(value); - if (!Number.isNaN(parsed)) - return parsed; - return undefined; -} -function parseHumanDate(value) { - const normalized = value - .replace(/\b(\d{1,2})(st|nd|rd|th)\b/gi, '$1') - .trim(); - const parsed = Date.parse(normalized); - if (!Number.isNaN(parsed)) - return parsed; - return undefined; -} -function matchWindowKey(headerName) { - for (const entry of WINDOW_PATTERNS) { - if (entry.patterns.some((pattern) => headerName.includes(pattern))) { - return entry.key; - } - } - return null; -} -function ensureWindow(update, key, now) { - if (!update[key]) { - update[key] = { updatedAt: now }; - } - else if (!update[key]?.updatedAt) { - update[key] = { ...update[key], updatedAt: now }; - } - return update[key]; -} -export function hasMeaningfulRateLimitWindow(window) { - if (!window) - return false; - return (typeof window.remaining === 'number' || - typeof window.resetAt === 'number'); -} -export function hasMeaningfulRateLimits(rateLimits) { - if (!rateLimits) - return false; - return (hasMeaningfulRateLimitWindow(rateLimits.fiveHour) || - hasMeaningfulRateLimitWindow(rateLimits.weekly)); -} -export function extractRateLimitUpdate(headers) { - const update = {}; - const now = Date.now(); - for (const [rawName, value] of headers.entries()) { - const name = rawName.toLowerCase(); - if (name.startsWith('x-codex-')) { - if (name.startsWith('x-codex-primary-') || name.startsWith('x-codex-secondary-')) { - const windowKey = name.startsWith('x-codex-primary-') ? 'fiveHour' : 'weekly'; - const window = ensureWindow(update, windowKey, now); - if (name.endsWith('used-percent')) { - const usedPercent = parseNumber(value); - if (usedPercent !== undefined) { - const remaining = Math.max(0, 100 - usedPercent); - window.limit = 100; - window.remaining = remaining; - } - continue; - } - if (name.endsWith('reset-at')) { - const resetAt = parseTimestamp(value); - if (resetAt !== undefined) - window.resetAt = resetAt; - continue; - } - if (name.endsWith('window-minutes')) { - // Window length is informational; not shown in UI yet. - continue; - } - } - continue; - } - if (name.startsWith('x-ratelimit-')) { - const windowKey = matchWindowKey(name); - if (!windowKey) - continue; - const window = ensureWindow(update, windowKey, now); - if (name.includes('limit')) { - const limit = parseNumber(value); - if (limit !== undefined) - window.limit = limit; - continue; - } - if (name.includes('remaining')) { - const remaining = parseNumber(value); - if (remaining !== undefined) - window.remaining = remaining; - continue; - } - if (name.includes('reset')) { - const resetAt = parseReset(value, now); - if (resetAt !== undefined) - window.resetAt = resetAt; - } - } - } - return hasMeaningfulRateLimits(update) ? update : null; -} -export function mergeRateLimits(existing, update) { - if (!hasMeaningfulRateLimits(update)) { - return existing || {}; - } - return { - fiveHour: { ...(existing?.fiveHour || {}), ...(update.fiveHour || {}) }, - weekly: { ...(existing?.weekly || {}), ...(update.weekly || {}) } - }; -} -export function parseRetryAfterHeader(retryAfter, now = Date.now()) { - if (!retryAfter) - return undefined; - const trimmed = retryAfter.trim(); - if (!trimmed) - return undefined; - const asNumber = Number(trimmed); - if (Number.isFinite(asNumber) && asNumber >= 0) { - return now + asNumber * 1000; - } - const asDate = Date.parse(trimmed); - if (!Number.isNaN(asDate)) - return asDate; - return undefined; -} -export function parseRateLimitResetFromError(text, now = Date.now()) { - if (!text) - return undefined; - const retryAfterMatch = text.match(/(?:retry[\s-]*after|try again in)\s*(\d+)\s*(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\b/i); - if (retryAfterMatch) { - const amount = Number(retryAfterMatch[1]); - const unit = retryAfterMatch[2].toLowerCase(); - if (Number.isFinite(amount) && amount >= 0) { - if (unit.startsWith('h')) - return now + amount * 60 * 60 * 1000; - if (unit.startsWith('m')) - return now + amount * 60 * 1000; - return now + amount * 1000; - } - } - const tryAgainAtMatch = text.match(/try again at\s+([^\n.]+)/i); - if (tryAgainAtMatch?.[1]) { - const resetAt = parseHumanDate(tryAgainAtMatch[1]); - if (resetAt !== undefined) - return resetAt; - } - return undefined; -} -export function isRateLimitErrorText(text) { - if (!text) - return false; - const normalized = text.toLowerCase(); - return (normalized.includes('rate limit') || - normalized.includes('usage limit') || - normalized.includes("you've hit your usage limit") || - normalized.includes('too many requests') || - normalized.includes('try again at') || - normalized.includes('retry after')); -} -export function getBlockingRateLimitResetAt(rateLimits, now = Date.now(), opts) { - if (!rateLimits) - return undefined; - const windows = [ - rateLimits.fiveHour, - rateLimits.weekly - ]; - const exhaustedResets = []; - const futureResets = []; - let sawKnownRemaining = false; - for (const window of windows) { - if (!window || typeof window.resetAt !== 'number' || window.resetAt <= now) { - continue; - } - futureResets.push(window.resetAt); - if (typeof window.remaining === 'number') { - sawKnownRemaining = true; - if (window.remaining <= 0) { - exhaustedResets.push(window.resetAt); - } - } - } - if (exhaustedResets.length > 0) { - // If multiple windows are exhausted, wait for the last one to reset. - return Math.max(...exhaustedResets); - } - if (opts?.conservativeWhenRemainingUnknown && futureResets.length > 0 && !sawKnownRemaining) { - // Conservative fallback when backend says limited but remaining counters are absent. - return Math.max(...futureResets); - } - return undefined; -} -//# sourceMappingURL=rate-limits.js.map \ No newline at end of file diff --git a/dist/rate-limits.js.map b/dist/rate-limits.js.map deleted file mode 100644 index db050c3..0000000 --- a/dist/rate-limits.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"rate-limits.js","sourceRoot":"","sources":["../src/rate-limits.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/refresh-queue.d.ts b/dist/refresh-queue.d.ts deleted file mode 100644 index d912934..0000000 --- a/dist/refresh-queue.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type LimitRefreshResult } from './limits-refresh.js'; -import type { AccountCredentials } from './types.js'; -export interface RefreshQueueState { - running: boolean; - startedAt: number; - finishedAt?: number; - total: number; - completed: number; - errors: number; - currentAlias?: string; - currentAliases: string[]; - active: number; - concurrency: number; - stopRequested: boolean; - stopped: boolean; - results: LimitRefreshResult[]; -} -export declare function getRefreshQueueState(): RefreshQueueState | null; -export declare function stopRefreshQueue(): void; -export declare function startRefreshQueue(accounts: AccountCredentials[], alias?: string): RefreshQueueState; -//# sourceMappingURL=refresh-queue.d.ts.map \ No newline at end of file diff --git a/dist/refresh-queue.d.ts.map b/dist/refresh-queue.d.ts.map deleted file mode 100644 index cb9b200..0000000 --- a/dist/refresh-queue.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"refresh-queue.d.ts","sourceRoot":"","sources":["../src/refresh-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAG1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAQpD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,OAAO,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,kBAAkB,EAAE,CAAA;CAC9B;AAKD,wBAAgB,oBAAoB,IAAI,iBAAiB,GAAG,IAAI,CAE/D;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AA4FD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAuCnG"} \ No newline at end of file diff --git a/dist/refresh-queue.js b/dist/refresh-queue.js deleted file mode 100644 index a7c813a..0000000 --- a/dist/refresh-queue.js +++ /dev/null @@ -1,129 +0,0 @@ -import { refreshRateLimitsForAccount } from './limits-refresh.js'; -import { updateAccount } from './store.js'; -import { logInfo, logWarn } from './logger.js'; -// Keep the default below Cloudflare's per-IP burst threshold for -// /backend-api/codex/usage. The env override still allows higher concurrency. -const DEFAULT_REFRESH_QUEUE_CONCURRENCY = 5; -const MAX_REFRESH_QUEUE_CONCURRENCY = 20; -const REFRESH_QUEUE_CONCURRENCY_ENV = 'OPENCODE_MULTI_AUTH_REFRESH_QUEUE_CONCURRENCY'; -let queueState = null; -let stopRequested = false; -export function getRefreshQueueState() { - return queueState; -} -export function stopRefreshQueue() { - stopRequested = true; - if (queueState) { - queueState.stopRequested = true; - } -} -function resolveRefreshQueueConcurrency(targetCount) { - if (targetCount <= 1) { - return targetCount; - } - const raw = process.env[REFRESH_QUEUE_CONCURRENCY_ENV]; - const parsed = raw ? Number(raw) : DEFAULT_REFRESH_QUEUE_CONCURRENCY; - const normalized = Number.isFinite(parsed) ? Math.floor(parsed) : DEFAULT_REFRESH_QUEUE_CONCURRENCY; - return Math.max(1, Math.min(targetCount, Math.min(normalized, MAX_REFRESH_QUEUE_CONCURRENCY))); -} -function syncActiveAliases(activeAliases) { - if (!queueState) - return; - const currentAliases = Array.from(activeAliases.values()); - queueState.currentAliases = currentAliases; - queueState.active = currentAliases.length; - queueState.currentAlias = currentAliases[0]; -} -async function runWorker(targets, nextIndexRef, activeAliases) { - for (;;) { - if (!queueState || stopRequested) { - return; - } - const targetIndex = nextIndexRef.value; - nextIndexRef.value += 1; - if (targetIndex >= targets.length) { - return; - } - const account = targets[targetIndex]; - activeAliases.add(account.alias); - syncActiveAliases(activeAliases); - try { - const result = await refreshRateLimitsForAccount(account); - if (!queueState) { - return; - } - queueState.results.push(result); - queueState.completed += 1; - if (result.error) { - queueState.errors += 1; - } - } - finally { - activeAliases.delete(account.alias); - syncActiveAliases(activeAliases); - } - } -} -async function runQueue(targets) { - if (!queueState) - return; - const nextIndexRef = { value: 0 }; - const activeAliases = new Set(); - const workers = Array.from({ length: queueState.concurrency }, () => runWorker(targets, nextIndexRef, activeAliases)); - await Promise.all(workers); - if (queueState && stopRequested && nextIndexRef.value < targets.length) { - for (let idx = nextIndexRef.value; idx < targets.length; idx += 1) { - const account = targets[idx]; - updateAccount(account.alias, { limitStatus: 'stopped', limitError: 'Stopped by user' }); - queueState.results.push({ alias: account.alias, updated: false, error: 'Stopped' }); - queueState.completed += 1; - } - } - syncActiveAliases(activeAliases); - queueState.running = false; - queueState.finishedAt = Date.now(); - queueState.stopped = stopRequested; - queueState.stopRequested = stopRequested; - if (stopRequested) { - logWarn('Limit refresh queue stopped by user'); - } - else { - logInfo('Limit refresh queue completed'); - } - stopRequested = false; -} -export function startRefreshQueue(accounts, alias) { - if (queueState?.running) { - return queueState; - } - const targets = alias ? accounts.filter((acc) => acc.alias === alias) : accounts; - const startedAt = Date.now(); - const concurrency = resolveRefreshQueueConcurrency(targets.length); - queueState = { - running: true, - startedAt, - total: targets.length, - completed: 0, - errors: 0, - currentAliases: [], - active: 0, - concurrency, - stopRequested: false, - stopped: false, - results: [] - }; - stopRequested = false; - if (targets.length === 0) { - queueState.running = false; - queueState.finishedAt = Date.now(); - logWarn('Limit refresh queue requested with no targets'); - return queueState; - } - for (const account of targets) { - updateAccount(account.alias, { limitStatus: 'queued', limitError: undefined }); - } - logInfo(`Limit refresh queue started (${targets.length} accounts, concurrency ${concurrency})`); - void runQueue(targets); - return queueState; -} -//# sourceMappingURL=refresh-queue.js.map \ No newline at end of file diff --git a/dist/refresh-queue.js.map b/dist/refresh-queue.js.map deleted file mode 100644 index 93073de..0000000 --- a/dist/refresh-queue.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"refresh-queue.js","sourceRoot":"","sources":["../src/refresh-queue.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/rotation.d.ts b/dist/rotation.d.ts deleted file mode 100644 index e86baf4..0000000 --- a/dist/rotation.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { AccountCredentials, DEFAULT_CONFIG } from './types.js'; -export interface RotationResult { - account: AccountCredentials; - token: string; - forceState?: { - active: boolean; - alias: string | null; - remainingMs: number; - }; -} -export interface AccountSelectionContext { - model?: string; -} -export declare function getNextAccount(config: typeof DEFAULT_CONFIG, selection?: AccountSelectionContext): Promise; -export declare function markRateLimited(alias: string, rateLimitedUntil: number): void; -export declare function clearRateLimit(alias: string): void; -export declare function markModelUnsupported(alias: string, cooldownMs: number, info?: { - model?: string; - error?: string; -}): void; -export declare function clearModelUnsupported(alias: string): void; -export declare function markWorkspaceDeactivated(alias: string, cooldownMs: number, info?: { - error?: string; -}): void; -export declare function clearWorkspaceDeactivated(alias: string): void; -export declare function markAuthInvalid(alias: string): void; -export declare function clearAuthInvalid(alias: string): void; -//# sourceMappingURL=rotation.d.ts.map \ No newline at end of file diff --git a/dist/rotation.d.ts.map b/dist/rotation.d.ts.map deleted file mode 100644 index 040e230..0000000 --- a/dist/rotation.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../src/rotation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEpE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,kBAAkB,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE;QACX,MAAM,EAAE,OAAO,CAAA;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAkHD,wBAAsB,cAAc,CAClC,MAAM,EAAE,OAAO,cAAc,EAC7B,SAAS,CAAC,EAAE,uBAAuB,GAClC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA8OhC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAQ7E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,IAAI,CAWN;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAOzD;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxB,IAAI,CASN;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM7D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMnD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAKpD"} \ No newline at end of file diff --git a/dist/rotation.js b/dist/rotation.js deleted file mode 100644 index 2f26dd0..0000000 --- a/dist/rotation.js +++ /dev/null @@ -1,366 +0,0 @@ -import { getStoreDiagnostics, loadStore, saveStore, updateAccount } from './store.js'; -import { ensureValidToken } from './auth.js'; -import { decodeJwtPayload, getPlanTypeFromClaims } from './codex-auth.js'; -import { isForceActive, checkAndAutoClearForce, getForceState, clearForce } from './force-mode.js'; -import { getRuntimeSettings, calculateWeightedSelection } from './settings.js'; -const HEALTH_HYSTERESIS_MS = 10_000; -const RECENT_FAILURE_WINDOW_MS = 60_000; -function shuffled(input) { - const a = [...input]; - for (let i = a.length - 1; i > 0; i -= 1) { - const j = Math.floor(Math.random() * (i + 1)); - [a[i], a[j]] = [a[j], a[i]]; - } - return a; -} -function normalizePlanType(value) { - const normalized = value?.trim().toLowerCase(); - return normalized ? normalized : undefined; -} -function getAccountPlanType(acc) { - const persisted = normalizePlanType(acc.planType); - if (persisted) - return persisted; - const idClaims = acc.idToken ? decodeJwtPayload(acc.idToken) : null; - const accessClaims = acc.accessToken ? decodeJwtPayload(acc.accessToken) : null; - return normalizePlanType(getPlanTypeFromClaims(idClaims) || getPlanTypeFromClaims(accessClaims)); -} -function isProAccount(acc) { - return getAccountPlanType(acc) === 'pro'; -} -function isSparkModel(model) { - return typeof model === 'string' && model.startsWith('gpt-5.3-codex-spark'); -} -function getPreferredPools(store, availableAliases, selection) { - const proAliases = availableAliases.filter((alias) => isProAccount(store.accounts[alias])); - const nonProAliases = availableAliases.filter((alias) => !isProAccount(store.accounts[alias])); - if (isSparkModel(selection?.model)) { - return { - primaryAliases: proAliases, - fallbackAliases: [] - }; - } - if (proAliases.length > 0) { - return { - primaryAliases: proAliases, - fallbackAliases: nonProAliases - }; - } - return { - primaryAliases: availableAliases, - fallbackAliases: [] - }; -} -function evaluateAccountHealth(acc, now) { - const wasRateLimited = !!(acc.rateLimitedUntil && acc.rateLimitedUntil > now - HEALTH_HYSTERESIS_MS); - const wasModelUnsupported = !!(acc.modelUnsupportedUntil && acc.modelUnsupportedUntil > now - HEALTH_HYSTERESIS_MS); - const wasWorkspaceDeactivated = !!(acc.workspaceDeactivatedUntil && acc.workspaceDeactivatedUntil > now - HEALTH_HYSTERESIS_MS); - // Phase D: Check if account is disabled - const isDisabled = acc.enabled === false; - const currentlyBlocked = !!(acc.rateLimitedUntil && acc.rateLimitedUntil > now) || - !!(acc.modelUnsupportedUntil && acc.modelUnsupportedUntil > now) || - !!(acc.workspaceDeactivatedUntil && acc.workspaceDeactivatedUntil > now) || - !!acc.authInvalid || - isDisabled; // Phase D: Exclude disabled accounts - const isInProbation = !currentlyBlocked && (wasRateLimited || wasModelUnsupported || wasWorkspaceDeactivated); - let recentFailures = 0; - if (acc.lastLimitErrorAt && acc.lastLimitErrorAt > now - RECENT_FAILURE_WINDOW_MS) { - recentFailures++; - } - if (acc.authInvalidatedAt && acc.authInvalidatedAt > now - RECENT_FAILURE_WINDOW_MS) { - recentFailures++; - } - let priority = 100; - if (isInProbation) - priority -= 30; - if (recentFailures > 0) - priority -= recentFailures * 10; - if (acc.usageCount === 0) - priority -= 5; - if (currentlyBlocked) - priority = 0; - // Phase D: Disabled accounts get lowest priority - if (isDisabled) - priority = -1; - return { - alias: acc.alias, - isHealthy: !currentlyBlocked && !acc.authInvalid && !isDisabled, - isInProbation, - recentFailures, - priority - }; -} -export async function getNextAccount(config, selection) { - // Phase E: Check and auto-clear expired/invalid force state - const autoClear = checkAndAutoClearForce(); - if (autoClear.wasCleared) { - console.log(`[multi-auth] Force mode auto-cleared: ${autoClear.reason}`); - } - // Phase E: Check if force mode is active - const forceActive = isForceActive(); - const forceState = getForceState(); - let store = loadStore(); - const aliases = Object.keys(store.accounts); - if (aliases.length === 0) { - const diag = getStoreDiagnostics(); - const extra = diag.error ? ` (${diag.error})` : ''; - console.error(`[multi-auth] No accounts configured. Run: opencode-multi-auth add ${extra}`); - if (process.env.OPENCODE_MULTI_AUTH_DEBUG === '1') { - console.error(`[multi-auth] store file: ${diag.storeFile}`); - } - return null; - } - const now = Date.now(); - // Phase E: If force mode is active, never fall back to another alias. - if (forceActive && forceState.forcedAlias) { - const forcedAlias = forceState.forcedAlias; - const forcedAccount = store.accounts[forcedAlias]; - if (forcedAccount) { - const health = evaluateAccountHealth(forcedAccount, now); - if (health.isHealthy) { - const token = await ensureValidToken(forcedAlias); - if (token) { - store = updateAccount(forcedAlias, { - usageCount: (forcedAccount.usageCount || 0) + 1, - lastUsed: now, - limitError: undefined - }); - store.activeAlias = forcedAlias; - store.lastRotation = now; - saveStore(store); - console.log(`[multi-auth] Force mode: using ${forcedAlias}`); - return { - account: store.accounts[forcedAlias], - token, - forceState: { - active: true, - alias: forcedAlias, - remainingMs: forceState.forcedUntil ? forceState.forcedUntil - now : 0 - } - }; - } - else { - console.warn(`[multi-auth] Force mode: ${forcedAlias} token unavailable; refusing fallback`); - return null; - } - } - else { - console.warn(`[multi-auth] Force mode: ${forcedAlias} currently blocked; refusing fallback`); - return null; - } - } - else { - // Forced account no longer exists - clear force and proceed normally. - console.warn(`[multi-auth] Force mode: ${forcedAlias} not found, clearing force`); - clearForce(); - } - } - const healthMap = new Map(); - for (const alias of aliases) { - const acc = store.accounts[alias]; - healthMap.set(alias, evaluateAccountHealth(acc, now)); - } - const availableAliases = aliases.filter(alias => { - const health = healthMap.get(alias); - return health?.isHealthy === true; - }); - if (availableAliases.length === 0) { - console.warn('[multi-auth] No available accounts (rate-limited or invalidated).'); - return null; - } - const tokenFailureCooldownMs = (() => { - const raw = process.env.OPENCODE_MULTI_AUTH_TOKEN_FAILURE_COOLDOWN_MS; - const parsed = raw ? Number(raw) : NaN; - if (Number.isFinite(parsed) && parsed > 0) - return parsed; - return 60_000; - })(); - const runtimeSettings = getRuntimeSettings(); - const rotationStrategy = runtimeSettings.settings.rotationStrategy || config.rotationStrategy; - const buildCandidates = (candidateAliases) => { - switch (rotationStrategy) { - case 'least-used': { - const sorted = [...candidateAliases].sort((a, b) => { - const aa = store.accounts[a]; - const bb = store.accounts[b]; - const healthA = healthMap.get(a); - const healthB = healthMap.get(b); - const priorityDiff = (healthB?.priority || 0) - (healthA?.priority || 0); - if (priorityDiff !== 0) - return priorityDiff; - const usageDiff = (aa?.usageCount || 0) - (bb?.usageCount || 0); - if (usageDiff !== 0) - return usageDiff; - const lastDiff = (aa?.lastUsed || 0) - (bb?.lastUsed || 0); - if (lastDiff !== 0) - return lastDiff; - return a.localeCompare(b); - }); - return { aliases: sorted }; - } - case 'random': { - const sorted = [...candidateAliases].sort((a, b) => { - const healthA = healthMap.get(a); - const healthB = healthMap.get(b); - return (healthB?.priority || 0) - (healthA?.priority || 0); - }); - const topPriority = sorted.slice(0, Math.ceil(sorted.length / 2)); - return { aliases: shuffled(topPriority.length > 0 ? topPriority : sorted) }; - } - // Phase F: Weighted round-robin - case 'weighted-round-robin': { - const weights = runtimeSettings.settings.accountWeights; - // Filter to healthy accounts with weights - const weightedAliases = candidateAliases.filter(alias => (weights[alias] || 0) > 0); - if (weightedAliases.length === 0) { - // Fallback to round-robin if no weights defined - const sorted = [...candidateAliases].sort((a, b) => { - const healthA = healthMap.get(a); - const healthB = healthMap.get(b); - return (healthB?.priority || 0) - (healthA?.priority || 0); - }); - const start = store.rotationIndex % sorted.length; - const rr = sorted.map((_, i) => sorted[(start + i) % sorted.length]); - const nextIndex = (selected) => { - const idx = sorted.indexOf(selected); - if (idx < 0) - return store.rotationIndex; - return (idx + 1) % sorted.length; - }; - return { aliases: rr, nextIndex }; - } - // Use weighted selection - const selected = calculateWeightedSelection(weightedAliases, weights); - if (!selected) { - // Fallback to round-robin - const sorted = [...candidateAliases].sort((a, b) => { - const healthA = healthMap.get(a); - const healthB = healthMap.get(b); - return (healthB?.priority || 0) - (healthA?.priority || 0); - }); - const start = store.rotationIndex % sorted.length; - const rr = sorted.map((_, i) => sorted[(start + i) % sorted.length]); - return { aliases: rr }; - } - return { aliases: [selected] }; - } - case 'round-robin': - default: { - const sorted = [...candidateAliases].sort((a, b) => { - const healthA = healthMap.get(a); - const healthB = healthMap.get(b); - return (healthB?.priority || 0) - (healthA?.priority || 0); - }); - const start = store.rotationIndex % sorted.length; - const rr = sorted.map((_, i) => sorted[(start + i) % sorted.length]); - const nextIndex = (selected) => { - const idx = sorted.indexOf(selected); - if (idx < 0) - return store.rotationIndex; - return (idx + 1) % sorted.length; - }; - return { aliases: rr, nextIndex }; - } - } - }; - const { primaryAliases, fallbackAliases } = getPreferredPools(store, availableAliases, selection); - const primary = buildCandidates(primaryAliases); - const fallback = fallbackAliases.length > 0 ? buildCandidates(fallbackAliases) : { aliases: [] }; - const candidates = [...primary.aliases, ...fallback.aliases]; - for (const candidate of candidates) { - const token = await ensureValidToken(candidate); - if (!token) { - store = updateAccount(candidate, { - rateLimitedUntil: now + tokenFailureCooldownMs, - limitError: '[multi-auth] Token unavailable (refresh failed?)', - lastLimitErrorAt: now - }); - continue; - } - store = updateAccount(candidate, { - usageCount: (store.accounts[candidate]?.usageCount || 0) + 1, - lastUsed: now, - limitError: undefined - }); - store.activeAlias = candidate; - store.lastRotation = now; - const nextIndex = primary.aliases.includes(candidate) ? primary.nextIndex : fallback.nextIndex; - if (nextIndex) { - store.rotationIndex = nextIndex(candidate); - } - saveStore(store); - const currentForceState = getForceState(); - return { - account: store.accounts[candidate], - token, - forceState: { - active: isForceActive(), - alias: currentForceState.forcedAlias, - remainingMs: currentForceState.forcedUntil ? currentForceState.forcedUntil - now : 0 - } - }; - } - console.error('[multi-auth] No available accounts (token refresh failed on all candidates).'); - return null; -} -export function markRateLimited(alias, rateLimitedUntil) { - const now = Date.now(); - const safeUntil = Math.max(rateLimitedUntil, now + 1000); - const seconds = Math.max(1, Math.ceil((safeUntil - now) / 1000)); - updateAccount(alias, { - rateLimitedUntil: safeUntil - }); - console.warn(`[multi-auth] Account ${alias} marked rate-limited for ${seconds}s`); -} -export function clearRateLimit(alias) { - updateAccount(alias, { - rateLimitedUntil: undefined - }); -} -export function markModelUnsupported(alias, cooldownMs, info) { - updateAccount(alias, { - modelUnsupportedUntil: Date.now() + cooldownMs, - modelUnsupportedAt: Date.now(), - modelUnsupportedModel: info?.model, - modelUnsupportedError: info?.error - }); - const extra = info?.model ? ` (model=${info.model})` : ''; - console.warn(`[multi-auth] Account ${alias} marked model-unsupported for ${cooldownMs / 1000}s${extra}`); -} -export function clearModelUnsupported(alias) { - updateAccount(alias, { - modelUnsupportedUntil: undefined, - modelUnsupportedAt: undefined, - modelUnsupportedModel: undefined, - modelUnsupportedError: undefined - }); -} -export function markWorkspaceDeactivated(alias, cooldownMs, info) { - updateAccount(alias, { - workspaceDeactivatedUntil: Date.now() + cooldownMs, - workspaceDeactivatedAt: Date.now(), - workspaceDeactivatedError: info?.error - }); - console.warn(`[multi-auth] Account ${alias} marked workspace-deactivated for ${cooldownMs / 1000}s`); -} -export function clearWorkspaceDeactivated(alias) { - updateAccount(alias, { - workspaceDeactivatedUntil: undefined, - workspaceDeactivatedAt: undefined, - workspaceDeactivatedError: undefined - }); -} -export function markAuthInvalid(alias) { - updateAccount(alias, { - authInvalid: true, - authInvalidatedAt: Date.now() - }); - console.warn(`[multi-auth] Account ${alias} marked invalidated`); -} -export function clearAuthInvalid(alias) { - updateAccount(alias, { - authInvalid: false, - authInvalidatedAt: undefined - }); -} -//# sourceMappingURL=rotation.js.map \ No newline at end of file diff --git a/dist/rotation.js.map b/dist/rotation.js.map deleted file mode 100644 index 95f584e..0000000 --- a/dist/rotation.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"rotation.js","sourceRoot":"","sources":["../src/rotation.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/sessions-limits.d.ts b/dist/sessions-limits.d.ts deleted file mode 100644 index 01aad89..0000000 --- a/dist/sessions-limits.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { AccountRateLimits } from './types.js'; -export declare function findLatestSessionRateLimits(options?: { - sinceMs?: number; - untilMs?: number; - sessionsDir?: string; -}): { - rateLimits: AccountRateLimits; - eventTs?: number; - sourceFile: string; -} | null; -//# sourceMappingURL=sessions-limits.d.ts.map \ No newline at end of file diff --git a/dist/sessions-limits.d.ts.map b/dist/sessions-limits.d.ts.map deleted file mode 100644 index 4a45fdc..0000000 --- a/dist/sessions-limits.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"sessions-limits.d.ts","sourceRoot":"","sources":["../src/sessions-limits.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAA;AAuIpE,wBAAgB,2BAA2B,CAAC,OAAO,CAAC,EAAE;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG;IACF,UAAU,EAAE,iBAAiB,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,IAAI,CAcP"} \ No newline at end of file diff --git a/dist/sessions-limits.js b/dist/sessions-limits.js deleted file mode 100644 index a3f8ae9..0000000 --- a/dist/sessions-limits.js +++ /dev/null @@ -1,128 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import * as os from 'node:os'; -import { hasMeaningfulRateLimits } from './rate-limits.js'; -const DEFAULT_SESSIONS_DIR = path.join(os.homedir(), '.codex', 'sessions'); -function toEpochMs(value) { - if (value === undefined) - return undefined; - if (value > 1e12) - return value; - if (value > 1e9) - return value * 1000; - return value * 1000; -} -function parseTimestamp(value) { - if (!value) - return undefined; - const parsed = Date.parse(value); - if (Number.isNaN(parsed)) - return undefined; - return parsed; -} -function isWeekly(limit) { - const minutes = limit?.window_minutes; - if (!minutes) - return false; - return minutes >= 1000; -} -function buildWindow(limit, eventTs) { - if (!limit) - return undefined; - const used = limit.used_percent; - const remaining = typeof used === 'number' ? Math.max(0, 100 - used) : undefined; - const resetAt = toEpochMs(limit.resets_at ?? limit.reset_at) ?? - (typeof limit.resets_in_seconds === 'number' && eventTs - ? eventTs + limit.resets_in_seconds * 1000 - : undefined); - return { - limit: 100, - remaining, - resetAt, - updatedAt: eventTs - }; -} -function listSessionFiles(dir) { - const entries = []; - if (!fs.existsSync(dir)) - return entries; - const stack = [dir]; - while (stack.length > 0) { - const current = stack.pop(); - if (!current) - continue; - const items = fs.readdirSync(current, { withFileTypes: true }); - for (const item of items) { - const fullPath = path.join(current, item.name); - if (item.isDirectory()) { - stack.push(fullPath); - } - else if (item.isFile() && item.name.endsWith('.jsonl')) { - const stat = fs.statSync(fullPath); - entries.push({ path: fullPath, mtimeMs: stat.mtimeMs }); - } - } - } - return entries.sort((a, b) => b.mtimeMs - a.mtimeMs); -} -function parseLatestTokenCountFromFile(filePath, options) { - let contents = ''; - try { - contents = fs.readFileSync(filePath, 'utf-8'); - } - catch { - return null; - } - const lines = contents.split('\n').filter(Boolean); - for (let i = lines.length - 1; i >= 0; i -= 1) { - let parsed = null; - try { - parsed = JSON.parse(lines[i]); - } - catch { - continue; - } - const payload = parsed?.payload; - if (payload?.type !== 'token_count') - continue; - const eventTs = parseTimestamp(parsed.timestamp); - if (!eventTs) - continue; - if (options?.sinceMs && eventTs < options.sinceMs) - continue; - if (options?.untilMs && eventTs > options.untilMs) - continue; - const primary = payload.rate_limits?.primary; - const secondary = payload.rate_limits?.secondary; - const fiveHour = isWeekly(primary) ? secondary : primary; - const weekly = isWeekly(primary) ? primary : secondary; - const rateLimits = { - fiveHour: buildWindow(fiveHour, eventTs), - weekly: buildWindow(weekly, eventTs) - }; - if (!hasMeaningfulRateLimits(rateLimits)) { - continue; - } - return { - eventTs, - rateLimits - }; - } - return null; -} -export function findLatestSessionRateLimits(options) { - const sessionsDir = options?.sessionsDir || DEFAULT_SESSIONS_DIR; - const files = listSessionFiles(sessionsDir); - for (const file of files) { - const parsed = parseLatestTokenCountFromFile(file.path, options); - if (parsed?.rateLimits) { - return { - rateLimits: parsed.rateLimits, - eventTs: parsed.eventTs, - sourceFile: file.path - }; - } - } - return null; -} -//# sourceMappingURL=sessions-limits.js.map \ No newline at end of file diff --git a/dist/sessions-limits.js.map b/dist/sessions-limits.js.map deleted file mode 100644 index 4f97fc3..0000000 --- a/dist/sessions-limits.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"sessions-limits.js","sourceRoot":"","sources":["../src/sessions-limits.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/settings.d.ts b/dist/settings.d.ts deleted file mode 100644 index 0815ebe..0000000 --- a/dist/settings.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type RotationSettings, type WeightPreset, type SettingsValidationError } from './types.js'; -export interface SettingsResult { - settings: RotationSettings; - source: 'default' | 'persisted' | 'runtime' | 'env'; - errors?: SettingsValidationError[]; -} -export declare function getSettings(): SettingsResult; -export declare function getRuntimeSettings(): SettingsResult; -export declare function updateSettings(updates: Partial, actor?: string): { - success: boolean; - settings?: RotationSettings; - errors?: SettingsValidationError[]; -}; -export declare function resetSettings(actor?: string): RotationSettings; -export declare function applyPreset(preset: WeightPreset, actor?: string): { - success: boolean; - settings?: RotationSettings; - errors?: SettingsValidationError[]; -}; -export declare function calculateWeightedSelection(aliases: string[], weights: Record): string | null; -export declare function getSettingsWithInfo(): { - settings: RotationSettings; - source: string; - preset?: WeightPreset; - canReset: boolean; -}; -export declare function isFeatureEnabled(flag: keyof NonNullable): boolean; -//# sourceMappingURL=settings.d.ts.map \ No newline at end of file diff --git a/dist/settings.d.ts.map b/dist/settings.d.ts.map deleted file mode 100644 index 46ef79d..0000000 --- a/dist/settings.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC7B,MAAM,YAAY,CAAA;AAInB,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,KAAK,CAAA;IACnD,MAAM,CAAC,EAAE,uBAAuB,EAAE,CAAA;CACnC;AAmED,wBAAgB,WAAW,IAAI,cAAc,CAE5C;AAGD,wBAAgB,kBAAkB,IAAI,cAAc,CAEnD;AAGD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAClC,KAAK,GAAE,MAAiB,GACvB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAAC,MAAM,CAAC,EAAE,uBAAuB,EAAE,CAAA;CAAE,CA2BvF;AAGD,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAiB,GAAG,gBAAgB,CAQxE;AAGD,wBAAgB,WAAW,CACzB,MAAM,EAAE,YAAY,EACpB,KAAK,GAAE,MAAiB,GACvB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAAC,MAAM,CAAC,EAAE,uBAAuB,EAAE,CAAA;CAAE,CAyDvF;AAGD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,GAAG,IAAI,CAuBf;AAGD,wBAAgB,mBAAmB,IAAI;IACrC,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,QAAQ,EAAE,OAAO,CAAA;CAClB,CAwBA;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,WAAW,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC,GAAG,OAAO,CAGnG"} \ No newline at end of file diff --git a/dist/settings.js b/dist/settings.js deleted file mode 100644 index 4eea87a..0000000 --- a/dist/settings.js +++ /dev/null @@ -1,208 +0,0 @@ -import { loadStore, saveStore } from './store.js'; -import { logInfo, logError } from './logger.js'; -import { DEFAULT_ROTATION_SETTINGS, WEIGHTED_PRESETS, validateSettings } from './types.js'; -function resolveSettings(includeEnvOverrides) { - const store = loadStore(); - // Start with defaults - let settings = { ...DEFAULT_ROTATION_SETTINGS }; - let source = 'default'; - // Layer 1: Persisted settings from store - if (store.settings) { - settings = { - ...settings, - ...store.settings - }; - source = 'persisted'; - } - // Layer 2: Environment variables override (optional for runtime behavior) - if (includeEnvOverrides) { - const envStrategy = process.env.OPENCODE_MULTI_AUTH_ROTATION_STRATEGY; - if (envStrategy && ['round-robin', 'least-used', 'random', 'weighted-round-robin'].includes(envStrategy)) { - settings.rotationStrategy = envStrategy; - source = 'env'; - } - const envCriticalThreshold = process.env.OPENCODE_MULTI_AUTH_CRITICAL_THRESHOLD; - if (envCriticalThreshold) { - const parsed = parseFloat(envCriticalThreshold); - if (!isNaN(parsed) && parsed >= 0 && parsed <= 100) { - settings.criticalThreshold = parsed; - source = 'env'; - } - } - const envLowThreshold = process.env.OPENCODE_MULTI_AUTH_LOW_THRESHOLD; - if (envLowThreshold) { - const parsed = parseFloat(envLowThreshold); - if (!isNaN(parsed) && parsed >= 0 && parsed <= 100) { - settings.lowThreshold = parsed; - source = 'env'; - } - } - // Phase G: Feature flag environment overrides - const envAntigravity = process.env.OPENCODE_MULTI_AUTH_ANTIGRAVITY_ENABLED; - if (envAntigravity) { - const enabled = envAntigravity.toLowerCase() === 'true' || envAntigravity === '1'; - settings.featureFlags = { - ...(settings.featureFlags || {}), - antigravityEnabled: enabled - }; - source = 'env'; - } - } - // Validate final settings - const errors = validateSettings(settings); - if (errors.length > 0) { - logError(`Settings validation errors: ${errors.map(e => e.message).join(', ')}`); - } - return { settings, source, errors: errors.length > 0 ? errors : undefined }; -} -// Phase F: Get settings with proper precedence (including env overrides) -export function getSettings() { - return resolveSettings(true); -} -// Runtime behavior should use persisted settings so dashboard changes take effect immediately. -export function getRuntimeSettings() { - return resolveSettings(false); -} -// Phase F: Update settings with validation -export function updateSettings(updates, actor = 'system') { - const current = getRuntimeSettings(); - // Merge updates with current settings - const newSettings = { - ...current.settings, - ...updates, - updatedAt: Date.now(), - updatedBy: actor - }; - // Validate new settings - const errors = validateSettings(newSettings); - if (errors.length > 0) { - logError(`Settings update failed validation: ${errors.map(e => e.message).join(', ')}`); - return { success: false, errors }; - } - // Save to store - const store = loadStore(); - store.settings = newSettings; - // Keep legacy field in sync for force-mode compatibility. - store.rotationStrategy = newSettings.rotationStrategy; - saveStore(store); - logInfo(`Settings updated by ${actor}: ${JSON.stringify(updates)}`); - return { success: true, settings: newSettings }; -} -// Phase F: Reset settings to defaults -export function resetSettings(actor = 'system') { - const store = loadStore(); - delete store.settings; - store.rotationStrategy = DEFAULT_ROTATION_SETTINGS.rotationStrategy; - saveStore(store); - logInfo(`Settings reset to defaults by ${actor}`); - return { ...DEFAULT_ROTATION_SETTINGS }; -} -// Phase F: Apply a preset -export function applyPreset(preset, actor = 'system') { - const store = loadStore(); - const accounts = Object.keys(store.accounts); - const presetConfig = WEIGHTED_PRESETS[preset]; - let accountWeights = {}; - if (preset === 'balanced') { - // Equal weights for all accounts - const weight = accounts.length > 0 ? 1 / accounts.length : 0; - accounts.forEach(alias => { - accountWeights[alias] = weight; - }); - } - else if (preset === 'conservative') { - // Weights based on limit health - accounts.forEach(alias => { - const account = store.accounts[alias]; - const fiveHourRemaining = account.rateLimits?.fiveHour?.remaining ?? 50; - const weeklyRemaining = account.rateLimits?.weekly?.remaining ?? 50; - const health = (fiveHourRemaining + weeklyRemaining) / 2; - accountWeights[alias] = health / 100; - }); - // Normalize to sum to 1 - const total = Object.values(accountWeights).reduce((sum, w) => sum + w, 0); - if (total > 0) { - accounts.forEach(alias => { - accountWeights[alias] = accountWeights[alias] / total; - }); - } - } - else if (preset === 'aggressive') { - // Favor accounts with high usage (lower remaining) - accounts.forEach(alias => { - const account = store.accounts[alias]; - const fiveHourRemaining = account.rateLimits?.fiveHour?.remaining ?? 50; - const weeklyRemaining = account.rateLimits?.weekly?.remaining ?? 50; - const health = (fiveHourRemaining + weeklyRemaining) / 2; - // Inverse: lower health = higher weight - accountWeights[alias] = (100 - health) / 100; - }); - // Normalize to sum to 1 - const total = Object.values(accountWeights).reduce((sum, w) => sum + w, 0); - if (total > 0) { - accounts.forEach(alias => { - accountWeights[alias] = accountWeights[alias] / total; - }); - } - } - const updates = { - rotationStrategy: 'weighted-round-robin', - criticalThreshold: presetConfig.thresholds.critical, - lowThreshold: presetConfig.thresholds.low, - accountWeights - }; - return updateSettings(updates, actor); -} -// Phase F: Calculate weighted selection -export function calculateWeightedSelection(aliases, weights) { - if (aliases.length === 0) - return null; - // Filter to only available aliases - const available = aliases.filter(alias => weights[alias] > 0); - if (available.length === 0) - return null; - // Calculate total weight - const totalWeight = available.reduce((sum, alias) => sum + (weights[alias] || 0), 0); - if (totalWeight === 0) - return null; - // Weighted random selection - let random = Math.random() * totalWeight; - for (const alias of available) { - random -= weights[alias] || 0; - if (random <= 0) { - return alias; - } - } - // Fallback to last - return available[available.length - 1]; -} -// Phase F: Get settings with environment info -export function getSettingsWithInfo() { - const result = getSettings(); - const store = loadStore(); - // Detect if using a preset - let preset; - if (result.settings.rotationStrategy === 'weighted-round-robin') { - // Check if weights match a preset pattern - for (const [presetName, config] of Object.entries(WEIGHTED_PRESETS)) { - if (presetName !== 'custom' && - Math.abs(result.settings.criticalThreshold - config.thresholds.critical) < 0.01 && - Math.abs(result.settings.lowThreshold - config.thresholds.low) < 0.01) { - preset = presetName; - break; - } - } - } - return { - settings: result.settings, - source: result.source, - preset, - canReset: !!store.settings - }; -} -// Phase G: Check if a feature flag is enabled -export function isFeatureEnabled(flag) { - const settings = getSettings(); - return settings.settings.featureFlags?.[flag] ?? false; -} -//# sourceMappingURL=settings.js.map \ No newline at end of file diff --git a/dist/settings.js.map b/dist/settings.js.map deleted file mode 100644 index 08fc48d..0000000 --- a/dist/settings.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAiB,MAAM,YAAY,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,gBAAgB,EAIjB,MAAM,YAAY,CAAA;AAUnB,SAAS,eAAe,CAAC,mBAA4B;IACnD,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IAEzB,sBAAsB;IACtB,IAAI,QAAQ,GAAqB,EAAE,GAAG,yBAAyB,EAAE,CAAA;IACjE,IAAI,MAAM,GAA6B,SAAS,CAAA;IAEhD,yCAAyC;IACzC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,QAAQ,GAAG;YACT,GAAG,QAAQ;YACX,GAAG,KAAK,CAAC,QAAQ;SAClB,CAAA;QACD,MAAM,GAAG,WAAW,CAAA;IACtB,CAAC;IAED,0EAA0E;IAC1E,IAAI,mBAAmB,EAAE,CAAC;QACxB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAA;QACrE,IAAI,WAAW,IAAI,CAAC,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,sBAAsB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACzG,QAAQ,CAAC,gBAAgB,GAAG,WAAmD,CAAA;YAC/E,MAAM,GAAG,KAAK,CAAA;QAChB,CAAC;QAED,MAAM,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAA;QAC/E,IAAI,oBAAoB,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,UAAU,CAAC,oBAAoB,CAAC,CAAA;YAC/C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;gBACnD,QAAQ,CAAC,iBAAiB,GAAG,MAAM,CAAA;gBACnC,MAAM,GAAG,KAAK,CAAA;YAChB,CAAC;QACH,CAAC;QAED,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAA;QACrE,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,UAAU,CAAC,eAAe,CAAC,CAAA;YAC1C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;gBACnD,QAAQ,CAAC,YAAY,GAAG,MAAM,CAAA;gBAC9B,MAAM,GAAG,KAAK,CAAA;YAChB,CAAC;QACH,CAAC;QAED,8CAA8C;QAC9C,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAA;QAC1E,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,cAAc,KAAK,GAAG,CAAA;YACjF,QAAQ,CAAC,YAAY,GAAG;gBACtB,GAAG,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC;gBAChC,kBAAkB,EAAE,OAAO;aAC5B,CAAA;YACD,MAAM,GAAG,KAAK,CAAA;QAChB,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAEzC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,QAAQ,CAAC,+BAA+B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClF,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;AAC7E,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,WAAW;IACzB,OAAO,eAAe,CAAC,IAAI,CAAC,CAAA;AAC9B,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;AAC/B,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,cAAc,CAC5B,OAAkC,EAClC,QAAgB,QAAQ;IAExB,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;IAEpC,sCAAsC;IACtC,MAAM,WAAW,GAAqB;QACpC,GAAG,OAAO,CAAC,QAAQ;QACnB,GAAG,OAAO;QACV,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,SAAS,EAAE,KAAK;KACjB,CAAA;IAED,wBAAwB;IACxB,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAA;IAC5C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,QAAQ,CAAC,sCAAsC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACvF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;IACnC,CAAC;IAED,gBAAgB;IAChB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,KAAK,CAAC,QAAQ,GAAG,WAAW,CAAA;IAC5B,0DAA0D;IAC1D,KAAK,CAAC,gBAAgB,GAAG,WAAW,CAAC,gBAAgB,CAAA;IACrD,SAAS,CAAC,KAAK,CAAC,CAAA;IAEhB,OAAO,CAAC,uBAAuB,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAA;AACjD,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,aAAa,CAAC,QAAgB,QAAQ;IACpD,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,OAAQ,KAAa,CAAC,QAAQ,CAAA;IAC9B,KAAK,CAAC,gBAAgB,GAAG,yBAAyB,CAAC,gBAAgB,CAAA;IACnE,SAAS,CAAC,KAAK,CAAC,CAAA;IAEhB,OAAO,CAAC,iCAAiC,KAAK,EAAE,CAAC,CAAA;IACjD,OAAO,EAAE,GAAG,yBAAyB,EAAE,CAAA;AACzC,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,WAAW,CACzB,MAAoB,EACpB,QAAgB,QAAQ;IAExB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAE5C,MAAM,YAAY,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAA;IAE7C,IAAI,cAAc,GAA2B,EAAE,CAAA;IAE/C,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,iCAAiC;QACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5D,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACvB,cAAc,CAAC,KAAK,CAAC,GAAG,MAAM,CAAA;QAChC,CAAC,CAAC,CAAA;IACJ,CAAC;SAAM,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QACrC,gCAAgC;QAChC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACvB,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YACrC,MAAM,iBAAiB,GAAG,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAA;YACvE,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,IAAI,EAAE,CAAA;YACnE,MAAM,MAAM,GAAG,CAAC,iBAAiB,GAAG,eAAe,CAAC,GAAG,CAAC,CAAA;YACxD,cAAc,CAAC,KAAK,CAAC,GAAG,MAAM,GAAG,GAAG,CAAA;QACtC,CAAC,CAAC,CAAA;QACF,wBAAwB;QACxB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QAC1E,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBACvB,cAAc,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;YACvD,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;QACnC,mDAAmD;QACnD,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACvB,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YACrC,MAAM,iBAAiB,GAAG,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAA;YACvE,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,IAAI,EAAE,CAAA;YACnE,MAAM,MAAM,GAAG,CAAC,iBAAiB,GAAG,eAAe,CAAC,GAAG,CAAC,CAAA;YACxD,wCAAwC;YACxC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,GAAG,CAAA;QAC9C,CAAC,CAAC,CAAA;QACF,wBAAwB;QACxB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QAC1E,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBACvB,cAAc,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;YACvD,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAA8B;QACzC,gBAAgB,EAAE,sBAAsB;QACxC,iBAAiB,EAAE,YAAY,CAAC,UAAU,CAAC,QAAQ;QACnD,YAAY,EAAE,YAAY,CAAC,UAAU,CAAC,GAAG;QACzC,cAAc;KACf,CAAA;IAED,OAAO,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;AACvC,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,0BAA0B,CACxC,OAAiB,EACjB,OAA+B;IAE/B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAErC,mCAAmC;IACnC,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAC7D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAEvC,yBAAyB;IACzB,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACpF,IAAI,WAAW,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAElC,4BAA4B;IAC5B,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,WAAW,CAAA;IAExC,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,OAAO,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACxC,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,mBAAmB;IAMjC,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAC5B,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IAEzB,2BAA2B;IAC3B,IAAI,MAAgC,CAAA;IACpC,IAAI,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAK,sBAAsB,EAAE,CAAC;QAChE,0CAA0C;QAC1C,KAAK,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACpE,IAAI,UAAU,KAAK,QAAQ;gBACvB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI;gBAC/E,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC;gBAC1E,MAAM,GAAG,UAA0B,CAAA;gBACnC,MAAK;YACP,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM;QACN,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ;KAC3B,CAAA;AACH,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,gBAAgB,CAAC,IAAyD;IACxF,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,OAAO,QAAQ,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,CAAA;AACxD,CAAC"} \ No newline at end of file diff --git a/dist/store.d.ts b/dist/store.d.ts deleted file mode 100644 index 1fe1ab5..0000000 --- a/dist/store.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { AccountStore, AccountCredentials } from './types.js'; -export declare function loadStore(): AccountStore; -export declare function saveStore(store: AccountStore): void; -export declare function withWriteLock(fn: () => T): Promise; -export declare function getStoreDiagnostics(): { - storeDir: string; - storeFile: string; - locked: boolean; - encrypted: boolean; - error: string | null; -}; -export declare function addAccount(alias: string, creds: Omit): AccountStore; -export declare function removeAccount(alias: string): AccountStore; -export declare function updateAccount(alias: string, updates: Partial): AccountStore; -export declare function setActiveAlias(alias: string | null): AccountStore; -export declare function getActiveAccount(): AccountCredentials | null; -export declare function listAccounts(): AccountCredentials[]; -export declare function getStorePath(): string; -export declare function getStoreStatus(): { - locked: boolean; - encrypted: boolean; - error: string | null; -}; -//# sourceMappingURL=store.d.ts.map \ No newline at end of file diff --git a/dist/store.d.ts.map b/dist/store.d.ts.map deleted file mode 100644 index 0cd9bfc..0000000 --- a/dist/store.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAGnB,MAAM,YAAY,CAAA;AAoVnB,wBAAgB,SAAS,IAAI,YAAY,CAsExC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAiFnD;AAED,wBAAsB,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAO9D;AAED,wBAAgB,mBAAmB,IAAI;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,CAQA;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,YAAY,CAAC,GAAG,YAAY,CAc/G;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CASzD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,YAAY,CAe/F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,CA+BjE;AAED,wBAAgB,gBAAgB,IAAI,kBAAkB,GAAG,IAAI,CAI5D;AAED,wBAAgB,YAAY,IAAI,kBAAkB,EAAE,CAGnD;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,cAAc,IAAI;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAG9F"} \ No newline at end of file diff --git a/dist/store.js b/dist/store.js deleted file mode 100644 index ce9f496..0000000 --- a/dist/store.js +++ /dev/null @@ -1,545 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as os from 'os'; -import * as crypto from 'node:crypto'; -import { hasMeaningfulRateLimits } from './rate-limits.js'; -const STORE_DIR_ENV = 'OPENCODE_MULTI_AUTH_STORE_DIR'; -const STORE_FILE_ENV = 'OPENCODE_MULTI_AUTH_STORE_FILE'; -const DEFAULT_STORE_DIR = path.join(os.homedir(), '.config', 'opencode-multi-auth'); -const DEFAULT_STORE_FILE = 'accounts.json'; -function getStoreDir() { - const override = process.env[STORE_DIR_ENV]; - if (override && override.trim()) - return path.resolve(override.trim()); - return DEFAULT_STORE_DIR; -} -function getStoreFile() { - const override = process.env[STORE_FILE_ENV]; - if (override && override.trim()) - return path.resolve(override.trim()); - return path.join(getStoreDir(), DEFAULT_STORE_FILE); -} -const STORE_ENV_PASSPHRASE = 'CODEX_SOFT_STORE_PASSPHRASE'; -const CURRENT_STORE_VERSION = 2; -let storeLocked = false; -let lastStoreError = null; -let lastStoreEncrypted = false; -let writeLock = false; -let writeLockQueue = []; -function ensureDir() { - const dir = getStoreDir(); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); - } -} -function emptyStore() { - return { - version: CURRENT_STORE_VERSION, - accounts: {}, - activeAlias: null, - rotationIndex: 0, - lastRotation: Date.now() - }; -} -function getPassphrase() { - const value = process.env[STORE_ENV_PASSPHRASE]; - return value && value.trim().length > 0 ? value : null; -} -function isEncryptedFile(payload) { - return Boolean(payload && payload.encrypted === true && typeof payload.data === 'string'); -} -function deriveKey(passphrase, salt) { - return crypto.scryptSync(passphrase, salt, 32); -} -function encryptStore(store, passphrase) { - const salt = crypto.randomBytes(16); - const iv = crypto.randomBytes(12); - const key = deriveKey(passphrase, salt); - const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); - const serialized = JSON.stringify(store); - const encrypted = Buffer.concat([cipher.update(serialized, 'utf8'), cipher.final()]); - const tag = cipher.getAuthTag(); - return { - encrypted: true, - version: CURRENT_STORE_VERSION, - salt: salt.toString('base64'), - iv: iv.toString('base64'), - tag: tag.toString('base64'), - data: encrypted.toString('base64') - }; -} -function decryptStore(file, passphrase) { - const salt = Buffer.from(file.salt, 'base64'); - const iv = Buffer.from(file.iv, 'base64'); - const tag = Buffer.from(file.tag, 'base64'); - const data = Buffer.from(file.data, 'base64'); - const key = deriveKey(passphrase, salt); - const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); - decipher.setAuthTag(tag); - const decrypted = Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8'); - return JSON.parse(decrypted); -} -function validateAccount(acc, alias) { - if (!acc || typeof acc !== 'object') - return null; - if (typeof acc.accessToken !== 'string' || !acc.accessToken) - return null; - if (typeof acc.refreshToken !== 'string' || !acc.refreshToken) - return null; - if (typeof acc.expiresAt !== 'number') - return null; - const rateLimitHistory = Array.isArray(acc.rateLimitHistory) - ? acc.rateLimitHistory.filter((entry) => hasMeaningfulRateLimits({ fiveHour: entry?.fiveHour, weekly: entry?.weekly })) - : undefined; - const rateLimits = hasMeaningfulRateLimits(acc.rateLimits) ? acc.rateLimits : undefined; - return { - alias, - accessToken: acc.accessToken, - refreshToken: acc.refreshToken, - idToken: typeof acc.idToken === 'string' ? acc.idToken : undefined, - accountId: typeof acc.accountId === 'string' ? acc.accountId : undefined, - accountUserId: typeof acc.accountUserId === 'string' ? acc.accountUserId : undefined, - userId: typeof acc.userId === 'string' ? acc.userId : undefined, - planType: typeof acc.planType === 'string' ? acc.planType : undefined, - expiresAt: acc.expiresAt, - email: typeof acc.email === 'string' ? acc.email : undefined, - lastRefresh: typeof acc.lastRefresh === 'string' ? acc.lastRefresh : undefined, - lastSeenAt: typeof acc.lastSeenAt === 'number' ? acc.lastSeenAt : undefined, - lastActiveUntil: typeof acc.lastActiveUntil === 'number' ? acc.lastActiveUntil : undefined, - lastUsed: typeof acc.lastUsed === 'number' ? acc.lastUsed : undefined, - usageCount: typeof acc.usageCount === 'number' ? acc.usageCount : 0, - rateLimitedUntil: typeof acc.rateLimitedUntil === 'number' ? acc.rateLimitedUntil : undefined, - modelUnsupportedUntil: typeof acc.modelUnsupportedUntil === 'number' ? acc.modelUnsupportedUntil : undefined, - modelUnsupportedAt: typeof acc.modelUnsupportedAt === 'number' ? acc.modelUnsupportedAt : undefined, - modelUnsupportedModel: typeof acc.modelUnsupportedModel === 'string' ? acc.modelUnsupportedModel : undefined, - modelUnsupportedError: typeof acc.modelUnsupportedError === 'string' ? acc.modelUnsupportedError : undefined, - workspaceDeactivatedUntil: typeof acc.workspaceDeactivatedUntil === 'number' ? acc.workspaceDeactivatedUntil : undefined, - workspaceDeactivatedAt: typeof acc.workspaceDeactivatedAt === 'number' ? acc.workspaceDeactivatedAt : undefined, - workspaceDeactivatedError: typeof acc.workspaceDeactivatedError === 'string' ? acc.workspaceDeactivatedError : undefined, - authInvalid: typeof acc.authInvalid === 'boolean' ? acc.authInvalid : undefined, - authInvalidatedAt: typeof acc.authInvalidatedAt === 'number' ? acc.authInvalidatedAt : undefined, - // Phase D: Account availability fields - enabled: typeof acc.enabled === 'boolean' ? acc.enabled : undefined, - disabledAt: typeof acc.disabledAt === 'number' ? acc.disabledAt : undefined, - disabledBy: typeof acc.disabledBy === 'string' ? acc.disabledBy : undefined, - disableReason: typeof acc.disableReason === 'string' ? acc.disableReason : undefined, - rateLimits, - rateLimitHistory: rateLimitHistory && rateLimitHistory.length > 0 ? rateLimitHistory : undefined, - limitStatus: typeof acc.limitStatus === 'string' ? acc.limitStatus : undefined, - limitError: typeof acc.limitError === 'string' ? acc.limitError : undefined, - lastLimitProbeAt: typeof acc.lastLimitProbeAt === 'number' ? acc.lastLimitProbeAt : undefined, - lastLimitErrorAt: typeof acc.lastLimitErrorAt === 'number' ? acc.lastLimitErrorAt : undefined, - limitsConfidence: acc.limitsConfidence === 'fresh' || - acc.limitsConfidence === 'stale' || - acc.limitsConfidence === 'error' || - acc.limitsConfidence === 'unknown' - ? acc.limitsConfidence - : undefined, - tags: Array.isArray(acc.tags) ? acc.tags : undefined, - notes: typeof acc.notes === 'string' ? acc.notes : undefined, - source: acc.source === 'opencode' || acc.source === 'codex' ? acc.source : undefined - }; -} -function validateStore(data) { - if (!data || typeof data !== 'object') - return null; - const accounts = {}; - const rawAccounts = data.accounts; - if (rawAccounts && typeof rawAccounts === 'object') { - for (const [alias, acc] of Object.entries(rawAccounts)) { - const validated = validateAccount(acc, alias); - if (validated) { - accounts[alias] = validated; - } - } - } - return { - version: typeof data.version === 'number' ? data.version : undefined, - accounts, - activeAlias: typeof data.activeAlias === 'string' ? data.activeAlias : null, - rotationIndex: typeof data.rotationIndex === 'number' ? data.rotationIndex : 0, - lastRotation: typeof data.lastRotation === 'number' ? data.lastRotation : Date.now(), - // Phase E: Preserve force mode fields - forcedAlias: data.forcedAlias ?? null, - forcedUntil: data.forcedUntil ?? null, - previousRotationStrategy: data.previousRotationStrategy ?? null, - forcedBy: data.forcedBy ?? null, - // Phase F: Preserve rotation strategy and settings - rotationStrategy: data.rotationStrategy ?? 'round-robin', - settings: data.settings ?? undefined - }; -} -function migrateV1toV2(data) { - return { - ...data, - version: 2, - settings: { - rotationStrategy: 'round-robin' - }, - force: { - forcedAlias: null, - forcedUntil: null, - previousRotationStrategy: null, - forcedBy: null - } - }; -} -function migrateStore(data) { - if (!data || typeof data !== 'object') - return null; - const version = typeof data.version === 'number' ? data.version : 1; - if (version > CURRENT_STORE_VERSION) { - console.warn(`[multi-auth] Store version ${version} is newer than supported ${CURRENT_STORE_VERSION}. Proceeding with caution.`); - return validateStore(data); - } - let migrated = data; - if (version === 1) { - migrated = migrateV1toV2(data); - console.log('[multi-auth] Migrated store from v1 to v2'); - } - return validateStore(migrated); -} -function getLastKnownGoodPath() { - return `${getStoreFile()}.lkg`; -} -function saveLastKnownGood(store) { - // Avoid writing plaintext snapshots when store encryption is enabled. - if (getPassphrase()) { - return; - } - const lkgPath = getLastKnownGoodPath(); - try { - fs.writeFileSync(lkgPath, JSON.stringify(store, null, 2), { mode: 0o600 }); - } - catch { - // ignore - } -} -function loadLastKnownGood() { - const lkgPath = getLastKnownGoodPath(); - if (!fs.existsSync(lkgPath)) - return null; - try { - const data = fs.readFileSync(lkgPath, 'utf-8'); - const parsed = JSON.parse(data); - return validateStore(parsed); - } - catch { - return null; - } -} -async function acquireWriteLock() { - if (!writeLock) { - writeLock = true; - return; - } - return new Promise((resolve) => { - writeLockQueue.push(resolve); - }); -} -function releaseWriteLock() { - const next = writeLockQueue.shift(); - if (next) { - next(); - } - else { - writeLock = false; - } -} -function buildSnapshot(window) { - if (!window) - return undefined; - return { - remaining: window.remaining, - limit: window.limit, - resetAt: window.resetAt - }; -} -function buildHistoryEntry(rateLimits) { - if (!hasMeaningfulRateLimits(rateLimits)) - return null; - const updatedAtValues = [rateLimits?.fiveHour?.updatedAt, rateLimits?.weekly?.updatedAt].filter((value) => typeof value === 'number'); - const at = updatedAtValues.length > 0 ? Math.max(...updatedAtValues) : Date.now(); - return { - at, - fiveHour: buildSnapshot(rateLimits?.fiveHour), - weekly: buildSnapshot(rateLimits?.weekly) - }; -} -function appendHistory(history, entry) { - const next = history ? [...history] : []; - const last = next[next.length - 1]; - const same = last && - last.fiveHour?.remaining === entry.fiveHour?.remaining && - last.weekly?.remaining === entry.weekly?.remaining && - last.fiveHour?.resetAt === entry.fiveHour?.resetAt && - last.weekly?.resetAt === entry.weekly?.resetAt; - if (!same) { - next.push(entry); - } - if (next.length > 160) { - return next.slice(next.length - 160); - } - return next; -} -export function loadStore() { - storeLocked = false; - lastStoreError = null; - lastStoreEncrypted = false; - ensureDir(); - const file = getStoreFile(); - if (fs.existsSync(file)) { - try { - const data = fs.readFileSync(file, 'utf-8'); - const parsed = JSON.parse(data); - if (isEncryptedFile(parsed)) { - lastStoreEncrypted = true; - const passphrase = getPassphrase(); - if (!passphrase) { - storeLocked = true; - lastStoreError = `Store is encrypted. Set ${STORE_ENV_PASSPHRASE} to unlock.`; - return emptyStore(); - } - try { - const decrypted = decryptStore(parsed, passphrase); - const validated = validateStore(decrypted); - if (validated) { - saveLastKnownGood(validated); - return validated; - } - storeLocked = true; - lastStoreError = 'Store validation failed after decryption.'; - const lkg = loadLastKnownGood(); - if (lkg) { - console.warn('[multi-auth] Restored from last-known-good snapshot'); - return lkg; - } - return emptyStore(); - } - catch (err) { - storeLocked = true; - lastStoreError = 'Failed to decrypt store. Check passphrase.'; - console.error('[multi-auth] Failed to decrypt store:', err); - return emptyStore(); - } - } - const migrated = migrateStore(parsed); - if (migrated) { - saveLastKnownGood(migrated); - return migrated; - } - storeLocked = true; - lastStoreError = 'Store validation failed.'; - console.error('[multi-auth] Store validation failed'); - const lkg = loadLastKnownGood(); - if (lkg) { - console.warn('[multi-auth] Restored from last-known-good snapshot'); - return lkg; - } - return emptyStore(); - } - catch (err) { - storeLocked = true; - lastStoreError = 'Failed to parse store. Store locked until fixed.'; - console.error('[multi-auth] Failed to parse store:', err); - const lkg = loadLastKnownGood(); - if (lkg) { - console.warn('[multi-auth] Restored from last-known-good snapshot'); - return lkg; - } - } - } - return emptyStore(); -} -export function saveStore(store) { - ensureDir(); - if (storeLocked) { - console.error('[multi-auth] Store locked; refusing to overwrite encrypted file.'); - return; - } - const file = getStoreFile(); - const passphrase = getPassphrase(); - const payload = passphrase ? encryptStore(store, passphrase) : store; - const json = JSON.stringify(payload, null, 2); - try { - if (fs.existsSync(file)) { - fs.copyFileSync(file, `${file}.bak`); - fs.chmodSync(`${file}.bak`, 0o600); - } - } - catch { - // ignore backup failures - } - const tmp = `${file}.tmp-${process.pid}-${Date.now()}`; - let fd = null; - try { - fd = fs.openSync(tmp, 'w', 0o600); - fs.writeFileSync(fd, json, { encoding: 'utf-8' }); - try { - fs.fsyncSync(fd); - } - catch { - // fsync not supported everywhere; best-effort - } - } - finally { - if (fd !== null) { - try { - fs.closeSync(fd); - } - catch { - // ignore - } - } - } - try { - fs.renameSync(tmp, file); - } - catch (err) { - if (err?.code === 'EPERM' || err?.code === 'EEXIST') { - try { - fs.unlinkSync(file); - } - catch { - // ignore - } - fs.renameSync(tmp, file); - } - else { - try { - fs.unlinkSync(tmp); - } - catch { - // ignore - } - throw err; - } - } - try { - const dirFd = fs.openSync(getStoreDir(), 'r'); - try { - fs.fsyncSync(dirFd); - } - catch { - // ignore - } - fs.closeSync(dirFd); - } - catch { - // ignore - } - try { - fs.chmodSync(file, 0o600); - } - catch { - // ignore - } - saveLastKnownGood(store); -} -export async function withWriteLock(fn) { - await acquireWriteLock(); - try { - return fn(); - } - finally { - releaseWriteLock(); - } -} -export function getStoreDiagnostics() { - return { - storeDir: getStoreDir(), - storeFile: getStoreFile(), - locked: storeLocked, - encrypted: lastStoreEncrypted, - error: lastStoreError - }; -} -export function addAccount(alias, creds) { - const store = loadStore(); - const entry = buildHistoryEntry(creds.rateLimits); - store.accounts[alias] = { - ...creds, - alias, - usageCount: 0, - rateLimitHistory: entry ? [entry] : creds.rateLimitHistory - }; - if (!store.activeAlias) { - store.activeAlias = alias; - } - saveStore(store); - return store; -} -export function removeAccount(alias) { - const store = loadStore(); - delete store.accounts[alias]; - if (store.activeAlias === alias) { - const remaining = Object.keys(store.accounts); - store.activeAlias = remaining[0] || null; - } - saveStore(store); - return store; -} -export function updateAccount(alias, updates) { - const store = loadStore(); - if (store.accounts[alias]) { - const current = store.accounts[alias]; - const next = { ...current, ...updates }; - if (updates.rateLimits || next.rateLimits) { - const entry = buildHistoryEntry(next.rateLimits); - if (entry) { - next.rateLimitHistory = appendHistory(current.rateLimitHistory, entry); - } - } - store.accounts[alias] = next; - saveStore(store); - } - return store; -} -export function setActiveAlias(alias) { - const store = loadStore(); - const now = Date.now(); - const previousAlias = store.activeAlias; - if (alias === null) { - store.activeAlias = null; - } - else if (store.accounts[alias]) { - if (previousAlias && previousAlias !== alias && store.accounts[previousAlias]) { - store.accounts[previousAlias] = { - ...store.accounts[previousAlias], - lastActiveUntil: now - }; - } - store.activeAlias = alias; - store.accounts[alias] = { - ...store.accounts[alias], - lastSeenAt: now, - lastActiveUntil: undefined - }; - const aliases = Object.keys(store.accounts); - const idx = aliases.indexOf(alias); - if (idx >= 0) { - store.rotationIndex = idx; - } - store.lastRotation = now; - } - saveStore(store); - return store; -} -export function getActiveAccount() { - const store = loadStore(); - if (!store.activeAlias) - return null; - return store.accounts[store.activeAlias] || null; -} -export function listAccounts() { - const store = loadStore(); - return Object.values(store.accounts); -} -export function getStorePath() { - return getStoreFile(); -} -export function getStoreStatus() { - const diag = getStoreDiagnostics(); - return { locked: diag.locked, encrypted: diag.encrypted, error: diag.error }; -} -//# sourceMappingURL=store.js.map \ No newline at end of file diff --git a/dist/store.js.map b/dist/store.js.map deleted file mode 100644 index 0686678..0000000 --- a/dist/store.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/systemd.d.ts b/dist/systemd.d.ts deleted file mode 100644 index eefe911..0000000 --- a/dist/systemd.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface ServiceOptions { - cliPath: string; - host?: string; - port?: number; -} -export declare function getServiceFilePath(): string; -export declare function installService(options: ServiceOptions): string; -export declare function disableService(): void; -export declare function serviceStatus(): void; -//# sourceMappingURL=systemd.d.ts.map \ No newline at end of file diff --git a/dist/systemd.d.ts.map b/dist/systemd.d.ts.map deleted file mode 100644 index 63ac4a4..0000000 --- a/dist/systemd.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"systemd.d.ts","sourceRoot":"","sources":["../src/systemd.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAMD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAaD,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CA2B9D;AAED,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC"} \ No newline at end of file diff --git a/dist/systemd.js b/dist/systemd.js deleted file mode 100644 index 426af76..0000000 --- a/dist/systemd.js +++ /dev/null @@ -1,53 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { execFileSync } from 'node:child_process'; -const SERVICE_NAME = 'codex-soft'; -function getServiceDir() { - return path.join(os.homedir(), '.config', 'systemd', 'user'); -} -export function getServiceFilePath() { - return path.join(getServiceDir(), `${SERVICE_NAME}.service`); -} -function ensureDir() { - const dir = getServiceDir(); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); - } -} -function runSystemctl(args) { - execFileSync('systemctl', ['--user', ...args], { stdio: 'inherit' }); -} -export function installService(options) { - const host = options.host || '127.0.0.1'; - const port = options.port || 3434; - const serviceFile = getServiceFilePath(); - const workingDir = path.dirname(options.cliPath); - const execStart = `${process.execPath} ${options.cliPath} web --host ${host} --port ${port}`; - ensureDir(); - const unit = `[Unit] -Description=Codex Soft Dashboard -After=network.target - -[Service] -Type=simple -WorkingDirectory=${workingDir} -ExecStart=${execStart} -Restart=on-failure -RestartSec=3 - -[Install] -WantedBy=default.target -`; - fs.writeFileSync(serviceFile, unit, { mode: 0o600 }); - runSystemctl(['daemon-reload']); - runSystemctl(['enable', '--now', `${SERVICE_NAME}.service`]); - return serviceFile; -} -export function disableService() { - runSystemctl(['disable', '--now', `${SERVICE_NAME}.service`]); -} -export function serviceStatus() { - runSystemctl(['status', `${SERVICE_NAME}.service`, '--no-pager']); -} -//# sourceMappingURL=systemd.js.map \ No newline at end of file diff --git a/dist/systemd.js.map b/dist/systemd.js.map deleted file mode 100644 index 292843c..0000000 --- a/dist/systemd.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"systemd.js","sourceRoot":"","sources":["../src/systemd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,MAAM,YAAY,GAAG,YAAY,CAAA;AAQjC,SAAS,aAAa;IACpB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,GAAG,YAAY,UAAU,CAAC,CAAA;AAC9D,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,aAAa,EAAE,CAAA;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACrD,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAAc;IAClC,YAAY,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;AACtE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAuB;IACpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAA;IACxC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAA;IACjC,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAA;IACxC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAChD,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,OAAO,eAAe,IAAI,WAAW,IAAI,EAAE,CAAA;IAE5F,SAAS,EAAE,CAAA;IACX,MAAM,IAAI,GAAG;;;;;;mBAMI,UAAU;YACjB,SAAS;;;;;;CAMpB,CAAA;IAEC,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACpD,YAAY,CAAC,CAAC,eAAe,CAAC,CAAC,CAAA;IAC/B,YAAY,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,UAAU,CAAC,CAAC,CAAA;IAC5D,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,YAAY,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,YAAY,UAAU,CAAC,CAAC,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,YAAY,CAAC,CAAC,QAAQ,EAAE,GAAG,YAAY,UAAU,EAAE,YAAY,CAAC,CAAC,CAAA;AACnE,CAAC"} \ No newline at end of file diff --git a/dist/types.d.ts b/dist/types.d.ts deleted file mode 100644 index 1cb55f6..0000000 --- a/dist/types.d.ts +++ /dev/null @@ -1,144 +0,0 @@ -export interface AccountCredentials { - alias: string; - accessToken: string; - refreshToken: string; - idToken?: string; - accountId?: string; - accountUserId?: string; - userId?: string; - planType?: string; - expiresAt: number; - email?: string; - lastRefresh?: string; - lastSeenAt?: number; - lastActiveUntil?: number; - lastUsed?: number; - usageCount: number; - rateLimitedUntil?: number; - modelUnsupportedUntil?: number; - modelUnsupportedAt?: number; - modelUnsupportedModel?: string; - modelUnsupportedError?: string; - workspaceDeactivatedUntil?: number; - workspaceDeactivatedAt?: number; - workspaceDeactivatedError?: string; - authInvalid?: boolean; - authInvalidatedAt?: number; - enabled?: boolean; - disabledAt?: number; - disabledBy?: string; - disableReason?: string; - rateLimits?: AccountRateLimits; - rateLimitHistory?: RateLimitHistoryEntry[]; - limitStatus?: LimitStatus; - limitError?: string; - lastLimitProbeAt?: number; - lastLimitErrorAt?: number; - limitsConfidence?: LimitsConfidence; - tags?: string[]; - notes?: string; - source?: 'opencode' | 'codex'; -} -export interface RateLimitWindow { - limit?: number; - remaining?: number; - resetAt?: number; - updatedAt?: number; -} -export interface AccountRateLimits { - fiveHour?: RateLimitWindow; - weekly?: RateLimitWindow; -} -export interface RateLimitSnapshot { - remaining?: number; - limit?: number; - resetAt?: number; -} -export interface RateLimitHistoryEntry { - at: number; - fiveHour?: RateLimitSnapshot; - weekly?: RateLimitSnapshot; -} -export type LimitStatus = 'idle' | 'queued' | 'running' | 'success' | 'error' | 'stopped'; -export type LimitsConfidence = 'fresh' | 'stale' | 'error' | 'unknown'; -export declare function calculateLimitsConfidence(lastProbeAt: number | undefined, lastErrorAt: number | undefined, limitStatus: LimitStatus | undefined): LimitsConfidence; -export interface AccountStore { - version?: number; - accounts: Record; - activeAlias: string | null; - rotationIndex: number; - lastRotation: number; - forcedAlias?: string | null; - forcedUntil?: number | null; - previousRotationStrategy?: string | null; - forcedBy?: string | null; - rotationStrategy?: 'round-robin' | 'least-used' | 'random' | 'weighted-round-robin'; - settings?: RotationSettings; -} -export interface OpenAIModel { - id: string; - object: string; - created: number; - owned_by: string; -} -export interface PluginConfig { - rotationStrategy: 'round-robin' | 'least-used' | 'random' | 'weighted-round-robin'; - autoRefreshTokens: boolean; - rateLimitCooldownMs: number; - modelUnsupportedCooldownMs: number; - workspaceDeactivatedCooldownMs: number; - modelFilter: RegExp; -} -export interface ProviderModel { - name: string; - limit: { - context: number; - input?: number; - output: number; - }; - modalities: { - input: string[]; - output: string[]; - }; - options: { - reasoningEffort: string; - reasoningSummary: string; - textVerbosity: string; - include: string[]; - store: boolean; - service_tier?: string; - }; -} -export declare const DEFAULT_CONFIG: PluginConfig; -export interface RotationSettings { - rotationStrategy: 'round-robin' | 'least-used' | 'random' | 'weighted-round-robin'; - criticalThreshold: number; - lowThreshold: number; - accountWeights: Record; - featureFlags?: FeatureFlags; - updatedAt?: number; - updatedBy?: string; -} -export interface FeatureFlags { - antigravityEnabled: boolean; -} -export declare const DEFAULT_FEATURE_FLAGS: FeatureFlags; -export type WeightPreset = 'balanced' | 'conservative' | 'aggressive' | 'custom'; -export interface WeightedPresetConfig { - name: WeightPreset; - description: string; - defaultWeights: Record; - thresholds: { - critical: number; - low: number; - }; -} -export declare const DEFAULT_ROTATION_SETTINGS: RotationSettings; -export declare const WEIGHTED_PRESETS: Record; -export interface SettingsValidationError { - field: string; - message: string; - constraint: string; -} -export declare function validateSettings(settings: Partial): SettingsValidationError[]; -//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/dist/types.d.ts.map b/dist/types.d.ts.map deleted file mode 100644 index 9e46893..0000000 --- a/dist/types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/types.js b/dist/types.js deleted file mode 100644 index 1dc86f2..0000000 --- a/dist/types.js +++ /dev/null @@ -1,130 +0,0 @@ -// Phase C: Calculate limits confidence based on probe timestamps -export function calculateLimitsConfidence(lastProbeAt, lastErrorAt, limitStatus) { - const now = Date.now(); - const FRESH_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes - const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 60 minutes - // If we have an error more recent than last success, show error - if (lastErrorAt && (!lastProbeAt || lastErrorAt > lastProbeAt)) { - // If we have some successful data, show stale with error - if (lastProbeAt && now - lastProbeAt < STALE_THRESHOLD_MS) { - return 'error'; - } - } - // No successful probe ever - if (!lastProbeAt) { - return 'unknown'; - } - const ageMs = now - lastProbeAt; - if (ageMs < FRESH_THRESHOLD_MS) { - return 'fresh'; - } - else if (ageMs < STALE_THRESHOLD_MS) { - return 'stale'; - } - else { - // Data is too old, treat as unknown - return 'unknown'; - } -} -export const DEFAULT_CONFIG = { - rotationStrategy: 'round-robin', - autoRefreshTokens: true, - rateLimitCooldownMs: 5 * 60 * 1000, // 5 minutes - modelUnsupportedCooldownMs: 30 * 60 * 1000, // 30 minutes - workspaceDeactivatedCooldownMs: 30 * 60 * 1000, // 30 minutes - modelFilter: /^gpt-5/ -}; -// Phase G: Default feature flags -export const DEFAULT_FEATURE_FLAGS = { - antigravityEnabled: false -}; -// Phase F: Default settings -export const DEFAULT_ROTATION_SETTINGS = { - rotationStrategy: 'round-robin', - criticalThreshold: 10, - lowThreshold: 30, - accountWeights: {}, - featureFlags: { ...DEFAULT_FEATURE_FLAGS } -}; -// Phase F: Preset configurations -export const WEIGHTED_PRESETS = { - balanced: { - name: 'balanced', - description: 'Equal distribution across all accounts', - defaultWeights: {}, // Calculated dynamically as 1/n - thresholds: { critical: 10, low: 30 } - }, - conservative: { - name: 'conservative', - description: 'Prefer accounts with higher remaining limits', - defaultWeights: {}, // Calculated based on limit health - thresholds: { critical: 20, low: 40 } - }, - aggressive: { - name: 'aggressive', - description: 'Maximize throughput, accept higher risk', - defaultWeights: {}, // Favor accounts with high usage - thresholds: { critical: 5, low: 20 } - }, - custom: { - name: 'custom', - description: 'User-defined weights and thresholds', - defaultWeights: {}, - thresholds: { critical: 10, low: 30 } - } -}; -export function validateSettings(settings) { - const errors = []; - // Validate thresholds are in 0-100 range - if (settings.criticalThreshold !== undefined) { - if (settings.criticalThreshold < 0 || settings.criticalThreshold > 100) { - errors.push({ - field: 'criticalThreshold', - message: 'Critical threshold must be between 0 and 100', - constraint: '0 <= criticalThreshold <= 100' - }); - } - } - if (settings.lowThreshold !== undefined) { - if (settings.lowThreshold < 0 || settings.lowThreshold > 100) { - errors.push({ - field: 'lowThreshold', - message: 'Low threshold must be between 0 and 100', - constraint: '0 <= lowThreshold <= 100' - }); - } - } - // Validate critical < low - if (settings.criticalThreshold !== undefined && settings.lowThreshold !== undefined) { - if (settings.criticalThreshold >= settings.lowThreshold) { - errors.push({ - field: 'thresholds', - message: 'Critical threshold must be less than low threshold', - constraint: 'criticalThreshold < lowThreshold' - }); - } - } - // Validate weights are in (0, 1] range - if (settings.accountWeights) { - for (const [alias, weight] of Object.entries(settings.accountWeights)) { - if (weight <= 0 || weight > 1) { - errors.push({ - field: `accountWeights.${alias}`, - message: `Weight for ${alias} must be between 0 and 1`, - constraint: '0 < weight <= 1' - }); - } - } - // Validate weights sum to approximately 1 - const totalWeight = Object.values(settings.accountWeights).reduce((sum, w) => sum + w, 0); - if (totalWeight > 0 && Math.abs(totalWeight - 1) > 0.01) { - errors.push({ - field: 'accountWeights', - message: 'Total weights must sum to 1.0', - constraint: 'sum(weights) ≈ 1.0' - }); - } - } - return errors; -} -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/types.js.map b/dist/types.js.map deleted file mode 100644 index 6ab5731..0000000 --- a/dist/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/usage-limits.d.ts b/dist/usage-limits.d.ts deleted file mode 100644 index 08540da..0000000 --- a/dist/usage-limits.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { AccountCredentials, AccountRateLimits } from './types.js'; -export interface UsageRateLimitFetchResult { - rateLimits?: AccountRateLimits; - planType?: string; - rateLimitedUntil?: number; - error?: string; - shouldProbeFallback?: boolean; - authInvalid?: boolean; - workspaceDeactivated?: boolean; - workspaceDeactivatedReason?: string; - source: 'usage-api'; -} -interface UsageApiFailureClassification { - shouldProbeFallback: boolean; - authInvalid?: boolean; - workspaceDeactivated?: boolean; - workspaceDeactivatedReason?: string; -} -export declare function classifyUsageApiFailure(status: number, rawText: string): UsageApiFailureClassification; -export declare function fetchUsageRateLimitsForAccount(account: AccountCredentials): Promise; -export {}; -//# sourceMappingURL=usage-limits.d.ts.map \ No newline at end of file diff --git a/dist/usage-limits.d.ts.map b/dist/usage-limits.d.ts.map deleted file mode 100644 index c845721..0000000 --- a/dist/usage-limits.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"usage-limits.d.ts","sourceRoot":"","sources":["../src/usage-limits.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAA;AAoCxF,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,MAAM,EAAE,WAAW,CAAA;CACpB;AAED,UAAU,6BAA6B;IACrC,mBAAmB,EAAE,OAAO,CAAA;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,0BAA0B,CAAC,EAAE,MAAM,CAAA;CACpC;AAoED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,6BAA6B,CAuC/B;AAED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,yBAAyB,CAAC,CAwGpC"} \ No newline at end of file diff --git a/dist/usage-limits.js b/dist/usage-limits.js deleted file mode 100644 index bc0bcc0..0000000 --- a/dist/usage-limits.js +++ /dev/null @@ -1,190 +0,0 @@ -import { getBlockingRateLimitResetAt, hasMeaningfulRateLimits } from './rate-limits.js'; -const DEFAULT_USAGE_BASE_URL = 'https://chatgpt.com/backend-api'; -const USAGE_BASE_URL_ENV = 'OPENCODE_MULTI_AUTH_USAGE_BASE_URL'; -function getUsageBaseUrl() { - const override = process.env[USAGE_BASE_URL_ENV]?.trim(); - const baseUrl = override || DEFAULT_USAGE_BASE_URL; - return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; -} -function mapWindow(window, now) { - if (!window) - return undefined; - const usedPercent = typeof window.used_percent === 'number' ? window.used_percent : undefined; - const resetAt = typeof window.reset_at === 'number' - ? window.reset_at * 1000 - : typeof window.reset_after_seconds === 'number' - ? now + window.reset_after_seconds * 1000 - : undefined; - if (usedPercent === undefined && resetAt === undefined) { - return undefined; - } - return { - limit: 100, - remaining: typeof usedPercent === 'number' ? Math.max(0, 100 - usedPercent) : undefined, - resetAt, - updatedAt: now - }; -} -function pickRateLimitDetails(payload) { - if (payload.rate_limit) - return payload.rate_limit; - const additional = Array.isArray(payload.additional_rate_limits) ? payload.additional_rate_limits : []; - const preferred = additional.find((entry) => { - const feature = entry.metered_feature?.trim().toLowerCase(); - const limitName = entry.limit_name?.trim().toLowerCase(); - return feature === 'codex' || limitName === 'codex'; - }); - if (preferred?.rate_limit) - return preferred.rate_limit; - return additional.find((entry) => entry.rate_limit)?.rate_limit || null; -} -function parseUsageFailure(rawText) { - const trimmed = rawText.trim(); - if (!trimmed) { - return {}; - } - try { - const payload = JSON.parse(trimmed); - const code = (typeof payload?.detail?.code === 'string' && payload.detail.code) || - (typeof payload?.error?.code === 'string' && payload.error.code) || - undefined; - const message = (typeof payload?.detail?.message === 'string' && payload.detail.message) || - (typeof payload?.detail === 'string' && payload.detail) || - (typeof payload?.error?.message === 'string' && payload.error.message) || - (typeof payload?.message === 'string' && payload.message) || - undefined; - return { code, message }; - } - catch { - return { message: trimmed }; - } -} -export function classifyUsageApiFailure(status, rawText) { - const { code, message } = parseUsageFailure(rawText); - const normalized = [code, message, rawText.trim()] - .filter(Boolean) - .join(' ') - .toLowerCase(); - if (status === 401) { - return { - shouldProbeFallback: false, - authInvalid: true - }; - } - // 403 from the Codex usage endpoint is usually a Cloudflare gate, not proof - // that the OAuth token is invalid. Keep the account eligible and fall back to - // the probe path. - if (status === 403) { - return { - shouldProbeFallback: true, - authInvalid: false - }; - } - if (status === 402 && - (normalized.includes('deactivated_workspace') || - normalized.includes('deactivated workspace'))) { - return { - shouldProbeFallback: false, - workspaceDeactivated: true, - workspaceDeactivatedReason: message || code || rawText.trim() || undefined - }; - } - return { shouldProbeFallback: true }; -} -export async function fetchUsageRateLimitsForAccount(account) { - const token = account.accessToken?.trim(); - if (!token) { - return { - source: 'usage-api', - error: 'Missing access token' - }; - } - const url = `${getUsageBaseUrl()}/codex/usage`; - const headers = { - Authorization: `Bearer ${token}`, - 'User-Agent': 'codex_cli_rs/0.122.0 (linux)', - originator: 'codex_cli_rs' - }; - if (account.accountId) { - headers['ChatGPT-Account-Id'] = account.accountId; - } - const maxAttempts = 3; - let res; - let rawText = ''; - let lastErr; - for (let attempt = 0; attempt < maxAttempts; attempt++) { - try { - res = await fetch(url, { method: 'GET', headers }); - } - catch (err) { - lastErr = err; - if (attempt === maxAttempts - 1) { - return { - source: 'usage-api', - error: `Usage API request failed: ${err}` - }; - } - await new Promise((resolve) => setTimeout(resolve, 500 + attempt * 1500)); - continue; - } - try { - rawText = await res.text(); - } - catch { - rawText = ''; - } - const isCloudflareChallenge = res.status === 403 && - rawText.trimStart().slice(0, 16).toLowerCase().includes(' setTimeout(resolve, 1000 + attempt * 2000)); - } - if (!res) { - return { - source: 'usage-api', - error: `Usage API request failed: ${lastErr}` - }; - } - if (!res.ok) { - const trimmed = rawText.trim(); - const classification = classifyUsageApiFailure(res.status, rawText); - return { - source: 'usage-api', - error: `Usage API returned ${res.status}${trimmed ? `: ${trimmed.slice(0, 280)}` : ''}`, - ...classification - }; - } - let payload; - try { - payload = JSON.parse(rawText); - } - catch (err) { - return { - source: 'usage-api', - error: `Usage API returned invalid JSON: ${err}` - }; - } - const now = Date.now(); - const details = pickRateLimitDetails(payload); - const rateLimits = { - fiveHour: mapWindow(details?.primary_window, now), - weekly: mapWindow(details?.secondary_window, now) - }; - if (!hasMeaningfulRateLimits(rateLimits)) { - return { - source: 'usage-api', - planType: payload.plan_type, - error: 'Usage API response contained no usable rate limit windows' - }; - } - const rateLimitedUntil = details?.limit_reached || details?.allowed === false - ? getBlockingRateLimitResetAt(rateLimits, now) - : undefined; - return { - source: 'usage-api', - planType: payload.plan_type, - rateLimits, - rateLimitedUntil - }; -} -//# sourceMappingURL=usage-limits.js.map \ No newline at end of file diff --git a/dist/usage-limits.js.map b/dist/usage-limits.js.map deleted file mode 100644 index 05ef5bd..0000000 --- a/dist/usage-limits.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"usage-limits.js","sourceRoot":"","sources":["../src/usage-limits.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/web.d.ts b/dist/web.d.ts deleted file mode 100644 index b7205bf..0000000 --- a/dist/web.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as http from 'node:http'; -export declare function isLocalhostHost(host: string): boolean; -export declare function startWebConsole(options?: { - port?: number; - host?: string; -}): http.Server; -//# sourceMappingURL=web.d.ts.map \ No newline at end of file diff --git a/dist/web.d.ts.map b/dist/web.d.ts.map deleted file mode 100644 index ff885b0..0000000 --- a/dist/web.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAoFjC,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AA0nGD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,MAAM,CAyrBvF"} \ No newline at end of file diff --git a/dist/web.js b/dist/web.js deleted file mode 100644 index f41a7c4..0000000 --- a/dist/web.js +++ /dev/null @@ -1,3721 +0,0 @@ -import * as fs from 'node:fs'; -import * as http from 'node:http'; -import * as https from 'node:https'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { fileURLToPath, URL } from 'node:url'; -import { exec, spawn } from 'node:child_process'; -import { promisify } from 'node:util'; -import { createAuthorizationFlow, loginAccount, refreshToken } from './auth.js'; -import { getCodexAuthPath, getCodexAuthStatus, getCodexAuthSummary, resolveAliasForCurrentAuth, syncCodexAuthFile, writeCodexAuthForAlias } from './codex-auth.js'; -import { getStoreStatus, listAccounts, loadStore, removeAccount, updateAccount } from './store.js'; -import { getRefreshQueueState, startRefreshQueue, stopRefreshQueue } from './refresh-queue.js'; -import { getLogPath, logError, logInfo, readLogTail } from './logger.js'; -import { getForceState, activateForce, clearForce, isForceActive, getRemainingForceTimeMs, formatForceDuration } from './force-mode.js'; -import { getSettings, getRuntimeSettings, isFeatureEnabled } from './settings.js'; -import { Errors } from './errors.js'; -const DEFAULT_HOST = '127.0.0.1'; -const DEFAULT_PORT = 3434; -const LOCALHOST_HOST_PATTERN = /^(127\.0\.0\.1|::1|localhost)$/i; -const SYNC_INTERVAL_MS = 3000; -const SYNC_DEBOUNCE_MS = 600; -const ANTIGRAVITY_ACCOUNTS_FILE = path.join(os.homedir(), '.config', 'opencode', 'antigravity-accounts.json'); -const LOGIN_TIMEOUT_MS = 5 * 60 * 1000; -const AUTO_LOGIN_TIMEOUT_MS = 6 * 60 * 1000; -const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url)); -const AUTO_LOGIN_SCRIPT_ENV = 'OPENCODE_MULTI_AUTH_AUTO_LOGIN_SCRIPT'; -const AUTO_LOGIN_CREDENTIALS_ENV = 'OPENCODE_MULTI_AUTH_AUTO_LOGIN_CREDENTIALS_FILE'; -const AUTO_LOGIN_PYTHON_ENV = 'OPENCODE_MULTI_AUTH_AUTO_LOGIN_PYTHON'; -const DEFAULT_AUTO_LOGIN_CREDENTIALS_FILE = path.join(os.homedir(), '.config', 'opencode-multi-auth', 'credentials.json'); -const DEFAULT_AUTO_LOGIN_VENV_PYTHON = path.join(os.homedir(), '.config', 'opencode-multi-auth', '.venv', 'bin', 'python'); -export function isLocalhostHost(host) { - return LOCALHOST_HOST_PATTERN.test(host.trim()); -} -const execAsync = promisify(exec); -let lastSyncAt = 0; -let lastSyncError = null; -let lastSyncAlias = null; -let syncTimer = null; -let pendingLogin = null; -let lastLoginError = null; -let antigravityQuotaState = { status: 'idle', scope: 'active' }; -let antigravityQuotaInFlight = null; -let autoLoginChild = null; -const HTML = ` - - - - - Codex Token Dashboard - - - -
-
-
-
Codex Special Functions
-

Codex Token Dashboard

-
Add a new account from login + password, keep it in the local auto-login config, and watch the whole flow live.
-
- -
-
-
-
-
-
-
-
- - - - -
-
- - -
-
- - -
-
-
-
-
-
-
- - - - -
-
-
- - -
-
-
-
Force Mode
-
Pin rotation to a specific account for 24 hours
-
-
-
- - ? - -
-
- - - ? -
-
-
-
-
-
- - - -
-
-
-
Logs
-
-
- -
-

-      
-
- -
- - -`; -function sendJson(res, status, payload) { - const data = JSON.stringify(payload); - res.writeHead(status, { - 'Content-Type': 'application/json; charset=utf-8', - 'Content-Length': Buffer.byteLength(data) - }); - res.end(data); -} -function scrubAccount(account) { - const { accessToken, refreshToken, idToken, ...rest } = account; - return rest; -} -async function readJsonBody(req) { - return new Promise((resolve, reject) => { - let data = ''; - req.on('data', (chunk) => { - data += chunk; - if (data.length > 1_000_000) { - req.destroy(); - const payloadError = new Error('Payload too large'); - payloadError.code = 'PAYLOAD_TOO_LARGE'; - reject(payloadError); - } - }); - req.on('end', () => { - if (!data) { - resolve({}); - return; - } - try { - resolve(JSON.parse(data)); - } - catch { - const parseError = new Error('Invalid JSON payload'); - parseError.code = 'INVALID_JSON'; - reject(parseError); - } - }); - }); -} -function remainingPercent(window) { - if (!window || typeof window.remaining !== 'number' || typeof window.limit !== 'number') - return null; - if (window.limit === 0) - return null; - return Math.round((window.remaining / window.limit) * 100); -} -function recommendAlias(accounts) { - let best = null; - for (const account of accounts) { - if (account.enabled === false) { - continue; - } - const weeklyPercent = remainingPercent(account.rateLimits?.weekly) ?? -1; - const weeklyRemaining = typeof account.rateLimits?.weekly?.remaining === 'number' - ? account.rateLimits.weekly.remaining - : -1; - const fivePercent = remainingPercent(account.rateLimits?.fiveHour) ?? -1; - if (weeklyPercent < 0 && weeklyRemaining < 0 && fivePercent < 0) { - continue; - } - if (!best || - weeklyPercent > best.weeklyPercent || - (weeklyPercent === best.weeklyPercent && weeklyRemaining > best.weeklyRemaining) || - (weeklyPercent === best.weeklyPercent && - weeklyRemaining === best.weeklyRemaining && - fivePercent > best.fivePercent)) { - best = { alias: account.alias, weeklyPercent, weeklyRemaining, fivePercent }; - } - } - return best?.alias ?? null; -} -function getAutoLoginScriptPath() { - const override = process.env[AUTO_LOGIN_SCRIPT_ENV]; - if (override && override.trim()) - return path.resolve(override.trim()); - return path.resolve(MODULE_DIR, '..', 'auto-login', 'auto_login.py'); -} -function getAutoLoginCredentialsPath() { - const override = process.env[AUTO_LOGIN_CREDENTIALS_ENV]; - if (override && override.trim()) - return path.resolve(override.trim()); - return DEFAULT_AUTO_LOGIN_CREDENTIALS_FILE; -} -function getAutoLoginPythonPath() { - const override = process.env[AUTO_LOGIN_PYTHON_ENV]; - if (override && override.trim()) - return path.resolve(override.trim()); - if (fs.existsSync(DEFAULT_AUTO_LOGIN_VENV_PYTHON)) { - return DEFAULT_AUTO_LOGIN_VENV_PYTHON; - } - return 'python3'; -} -function sanitizeAliasSeed(value) { - const cleaned = value - .trim() - .toLowerCase() - .replace(/[^a-z0-9._-]+/g, '-') - .replace(/^-+|-+$/g, ''); - return cleaned || 'account'; -} -function ensureAutoLoginCredentialsDir(credentialsPath) { - const dir = path.dirname(credentialsPath); - fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); - try { - fs.chmodSync(dir, 0o700); - } - catch { - // Best effort on non-POSIX environments. - } -} -function readAutoLoginCredentialsFile(credentialsPath = getAutoLoginCredentialsPath()) { - if (!fs.existsSync(credentialsPath)) { - return { accounts: [] }; - } - const parsed = JSON.parse(fs.readFileSync(credentialsPath, 'utf-8')); - return { - defaults: parsed && typeof parsed.defaults === 'object' ? parsed.defaults : undefined, - accounts: Array.isArray(parsed?.accounts) ? parsed.accounts : [] - }; -} -function writeAutoLoginCredentialsFile(credentialsPath, data) { - ensureAutoLoginCredentialsDir(credentialsPath); - fs.writeFileSync(credentialsPath, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 }); - try { - fs.chmodSync(credentialsPath, 0o600); - } - catch { - // Best effort on non-POSIX environments. - } -} -function upsertAutoLoginCredentials(input) { - const email = input.email.trim(); - if (!email) { - throw new Error('Missing login/email'); - } - const password = input.password.trim(); - if (!password) { - throw new Error('Missing password'); - } - const scriptPath = getAutoLoginScriptPath(); - if (!fs.existsSync(scriptPath)) { - throw new Error('auto_login.py not found'); - } - const credentialsPath = getAutoLoginCredentialsPath(); - const file = readAutoLoginCredentialsFile(credentialsPath); - const accounts = Array.isArray(file.accounts) ? [...file.accounts] : []; - const existingIndex = accounts.findIndex((entry) => typeof entry.email === 'string' && entry.email.toLowerCase() === email.toLowerCase()); - const existing = existingIndex >= 0 ? accounts[existingIndex] : undefined; - const aliasSource = input.alias?.trim() || - existing?.alias?.trim() || - existing?.id?.trim() || - email.split('@')[0] || - email; - const alias = sanitizeAliasSeed(aliasSource); - const chatgptPassword = input.chatgptPassword?.trim() || - existing?.chatgpt_password?.trim() || - file.defaults?.chatgpt_password?.trim() || - password; - const nextAccount = { - ...existing, - id: existing?.id || alias, - alias, - email, - outlook_password: password, - chatgpt_password: chatgptPassword, - enabled: true - }; - if (existingIndex >= 0) { - accounts[existingIndex] = nextAccount; - } - else { - accounts.push(nextAccount); - } - writeAutoLoginCredentialsFile(credentialsPath, { - ...file, - accounts - }); - return { - alias, - email, - enabled: true - }; -} -function loadAutoLoginConfig() { - const pathValue = getAutoLoginCredentialsPath(); - const scriptPath = getAutoLoginScriptPath(); - const result = { - path: pathValue, - scriptPath, - pythonPath: getAutoLoginPythonPath(), - configured: false, - accounts: [] - }; - if (!fs.existsSync(scriptPath)) { - return { ...result, error: 'auto_login.py not found' }; - } - if (!fs.existsSync(pathValue)) { - return { ...result, error: 'credentials.json not found' }; - } - try { - const parsed = JSON.parse(fs.readFileSync(pathValue, 'utf-8')); - const accounts = Array.isArray(parsed?.accounts) ? parsed.accounts : []; - const view = accounts - .map((entry) => { - const email = typeof entry.email === 'string' ? entry.email.trim() : ''; - if (!email) - return null; - const aliasSource = (typeof entry.alias === 'string' && entry.alias.trim()) || - (typeof entry.id === 'string' && entry.id.trim()) || - email.split('@')[0]; - return { - alias: sanitizeAliasSeed(aliasSource), - email, - enabled: entry.enabled !== false - }; - }) - .filter((entry) => Boolean(entry)) - .sort((a, b) => a.email.localeCompare(b.email)); - if (view.length === 0) { - return { ...result, error: 'No accounts in credentials.json' }; - } - return { - ...result, - configured: true, - accounts: view - }; - } - catch (err) { - return { ...result, error: `Failed to parse credentials.json: ${err}` }; - } -} -function findAutoLoginAccount(config, selector) { - const normalized = selector.trim().toLowerCase(); - if (!normalized) - return null; - return config.accounts.find((account) => account.email.toLowerCase() === normalized || account.alias.toLowerCase() === normalized) || null; -} -function resolveAutoLoginAlias(store, account) { - const existing = Object.values(store.accounts).find((entry) => typeof entry.email === 'string' && entry.email.toLowerCase() === account.email.toLowerCase()); - if (existing) { - return existing.alias; - } - const base = sanitizeAliasSeed(account.alias || account.email.split('@')[0]); - let candidate = base; - let index = 1; - while (store.accounts[candidate]) { - candidate = `${base}-${index}`; - index += 1; - } - return candidate; -} -function setPendingLogin(state) { - pendingLogin = state; -} -function updatePendingLogin(patch) { - if (!pendingLogin) - return; - pendingLogin = { - ...pendingLogin, - ...patch, - output: patch.output ?? pendingLogin.output - }; -} -function appendPendingLoginOutput(line) { - const normalized = line - .replace(/\x1b\[[0-9;]*m/g, '') - .trim(); - if (!normalized || !pendingLogin) - return; - const output = [...pendingLogin.output, normalized].slice(-6); - updatePendingLogin({ - output, - step: normalized, - status: normalized.toLowerCase().includes('callback') ? 'waiting-callback' : 'running' - }); -} -function stopAutoLoginChild() { - if (autoLoginChild && !autoLoginChild.killed) { - autoLoginChild.kill('SIGTERM'); - } - autoLoginChild = null; -} -function consumeProcessLines(stream, onLine) { - if (!stream) - return; - let buffered = ''; - stream.setEncoding('utf8'); - stream.on('data', (chunk) => { - buffered += chunk; - let newlineIndex = buffered.indexOf('\n'); - while (newlineIndex >= 0) { - const line = buffered.slice(0, newlineIndex).trim(); - buffered = buffered.slice(newlineIndex + 1); - if (line) - onLine(line); - newlineIndex = buffered.indexOf('\n'); - } - }); - stream.on('end', () => { - const line = buffered.trim(); - if (line) - onLine(line); - }); -} -function startManualLogin(alias) { - if (pendingLogin) { - throw new Error(`Login already in progress for ${pendingLogin.alias}`); - } - return createAuthorizationFlow().then((flow) => { - setPendingLogin({ - alias, - startedAt: Date.now(), - url: flow.url, - mode: 'manual', - status: 'running', - step: 'Waiting for browser login...', - output: [] - }); - lastLoginError = null; - loginAccount(alias, flow, { timeoutMs: LOGIN_TIMEOUT_MS }) - .then(() => { - logInfo(`Login completed for ${alias}`); - setPendingLogin(null); - }) - .catch((err) => { - lastLoginError = String(err); - logError(`Login failed for ${alias}: ${err}`); - setPendingLogin(null); - }); - return { ok: true, url: flow.url }; - }); -} -async function startAutoLogin(selector, visible = false) { - if (pendingLogin) { - throw new Error(`Login already in progress for ${pendingLogin.alias}`); - } - const config = loadAutoLoginConfig(); - if (!config.configured) { - throw new Error(config.error || 'Auto-login is not configured'); - } - const selected = findAutoLoginAccount(config, selector); - if (!selected) { - throw new Error('Unknown auto-login account'); - } - if (!selected.enabled) { - throw new Error('Selected auto-login account is disabled'); - } - const store = loadStore(); - const alias = resolveAutoLoginAlias(store, selected); - const flow = await createAuthorizationFlow(); - setPendingLogin({ - alias, - email: selected.email, - startedAt: Date.now(), - url: flow.url, - mode: 'auto', - status: 'starting', - step: 'Launching browser automation (a Chrome window may open)...', - output: [] - }); - lastLoginError = null; - let loginSettled = false; - const loginPromise = loginAccount(alias, flow, { timeoutMs: AUTO_LOGIN_TIMEOUT_MS }) - .then(() => { - loginSettled = true; - stopAutoLoginChild(); - logInfo(`Auto-login completed for ${alias} (${selected.email})`); - setPendingLogin(null); - }) - .catch((err) => { - loginSettled = true; - stopAutoLoginChild(); - lastLoginError = String(err); - logError(`Auto-login failed for ${alias} (${selected.email}): ${err}`); - setPendingLogin(null); - }); - autoLoginChild = spawn(config.pythonPath, [ - '-u', - config.scriptPath, - '--email', - selected.email, - '--auth-url', - flow.url, - '--credentials-file', - config.path, - ...(visible ? ['--visible'] : []) - ], { - stdio: ['ignore', 'pipe', 'pipe'] - }); - updatePendingLogin({ - pid: autoLoginChild.pid ?? undefined, - status: 'running' - }); - consumeProcessLines(autoLoginChild.stdout, (line) => { - appendPendingLoginOutput(line); - }); - consumeProcessLines(autoLoginChild.stderr, (line) => { - appendPendingLoginOutput(`[stderr] ${line}`); - }); - autoLoginChild.on('error', (err) => { - appendPendingLoginOutput(`Process error: ${err.message}`); - }); - autoLoginChild.on('exit', (code, signal) => { - if (!pendingLogin || pendingLogin.mode !== 'auto' || pendingLogin.alias !== alias) { - autoLoginChild = null; - return; - } - autoLoginChild = null; - if (loginSettled) { - return; - } - if (code === 0) { - updatePendingLogin({ - status: 'waiting-callback', - step: 'Browser automation finished. Waiting for local callback...' - }); - return; - } - const reason = signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`; - appendPendingLoginOutput(`Browser automation exited with ${reason}.`); - lastLoginError = `Auto-login browser automation exited with ${reason}`; - updatePendingLogin({ - status: 'waiting-callback', - step: 'Browser automation stopped before callback. Complete login manually or wait for timeout.' - }); - }); - void loginPromise; - return { - ok: true, - alias, - email: selected.email, - url: flow.url - }; -} -async function saveAutoLoginAccountAndStart(input) { - const account = upsertAutoLoginCredentials(input); - return startAutoLogin(account.email); -} -function runSync() { - try { - const result = syncCodexAuthFile(); - const authStatus = getCodexAuthStatus(); - lastSyncAt = Date.now(); - lastSyncError = authStatus.error; - lastSyncAlias = result.alias ?? null; - if (result.updated || result.added) { - logInfo(`Synced auth.json (${result.alias ?? 'none'})`); - } - if (authStatus.error) { - logError(authStatus.error); - } - } - catch (err) { - lastSyncError = String(err); - logError(`Sync failed: ${lastSyncError}`); - } -} -function loadAntigravityAccounts() { - const result = { path: ANTIGRAVITY_ACCOUNTS_FILE, accounts: [], readAt: Date.now() }; - if (!fs.existsSync(ANTIGRAVITY_ACCOUNTS_FILE)) { - return { ...result, error: 'antigravity-accounts.json not found' }; - } - try { - const raw = JSON.parse(fs.readFileSync(ANTIGRAVITY_ACCOUNTS_FILE, 'utf-8')); - const activeIndex = typeof raw?.activeIndex === 'number' ? raw.activeIndex : undefined; - const accounts = Array.isArray(raw?.accounts) ? raw.accounts : []; - const view = accounts.map((acc, index) => ({ - index, - alias: acc?.projectId || acc?.managedProjectId, - projectId: acc?.projectId, - managedProjectId: acc?.managedProjectId, - addedAt: acc?.addedAt, - lastUsed: acc?.lastUsed, - hasRefreshToken: Boolean(acc?.refreshToken), - rateLimitResetTimes: acc?.rateLimitResetTimes && typeof acc.rateLimitResetTimes === 'object' - ? acc.rateLimitResetTimes - : undefined - })); - return { ...result, activeIndex, accounts: view }; - } - catch (err) { - return { ...result, error: `Failed to parse antigravity accounts: ${err}` }; - } -} -function isAntigravityProcessLine(line) { - const lower = line.toLowerCase(); - if (lower.includes('antigravity')) - return true; - return /--app_data_dir\s+antigravity\b/i.test(line); -} -function getAntigravityProcessName() { - if (process.platform === 'darwin') { - return `language_server_macos${process.arch === 'arm64' ? '_arm' : ''}`; - } - if (process.platform === 'linux') { - return `language_server_linux${process.arch === 'arm64' ? '_arm' : '_x64'}`; - } - if (process.platform === 'win32') { - return 'language_server_windows_x64.exe'; - } - return null; -} -async function detectAntigravityProcessInfo() { - const processName = getAntigravityProcessName(); - if (!processName) { - throw new Error('Unsupported platform for Antigravity quotas'); - } - if (process.platform === 'win32') { - throw new Error('Antigravity quota detection is not implemented for Windows yet'); - } - const cmd = process.platform === 'darwin' ? `pgrep -fl ${processName}` : `pgrep -af ${processName}`; - const { stdout } = await execAsync(cmd); - const lines = stdout - .split('\n') - .map((line) => line.trim()) - .filter(Boolean); - const tokenLines = lines.filter((line) => line.includes('--csrf_token')); - if (tokenLines.length === 0) { - return null; - } - const ordered = [ - ...tokenLines.filter(isAntigravityProcessLine), - ...tokenLines.filter((line) => !isAntigravityProcessLine(line)) - ]; - for (const line of ordered) { - const parts = line.split(/\s+/); - const pid = Number(parts[0]); - if (!Number.isFinite(pid)) { - continue; - } - const portMatch = line.match(/--extension_server_port[=\s]+(\d+)/); - const tokenMatch = line.match(/--csrf_token[=\s]+([a-zA-Z0-9\-]+)/); - if (!tokenMatch) { - continue; - } - const extensionPort = portMatch ? Number(portMatch[1]) : 0; - const csrfToken = tokenMatch[1]; - const ports = await listListeningPorts(pid); - const workingPort = await findWorkingPort(ports, csrfToken); - const connectPort = workingPort || (extensionPort > 0 ? extensionPort : 0); - if (!connectPort) { - return null; - } - return { pid, extensionPort, csrfToken, connectPort }; - } - return null; -} -async function listListeningPorts(pid) { - try { - const cmd = `lsof -nP -a -iTCP -sTCP:LISTEN -p ${pid}`; - const { stdout } = await execAsync(cmd); - const ports = new Set(); - const regex = /:(\d+)\s+\(LISTEN\)/g; - let match; - while ((match = regex.exec(stdout)) !== null) { - const port = Number(match[1]); - if (Number.isFinite(port)) { - ports.add(port); - } - } - return Array.from(ports.values()).sort((a, b) => a - b); - } - catch { - return []; - } -} -function antigravityRequest(port, csrfToken, pathName, body) { - return new Promise((resolve, reject) => { - const payload = JSON.stringify(body); - const req = https.request({ - hostname: '127.0.0.1', - port, - path: pathName, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(payload), - 'Connect-Protocol-Version': '1', - 'X-Codeium-Csrf-Token': csrfToken - }, - rejectUnauthorized: false, - timeout: 5000 - }, (res) => { - let data = ''; - res.on('data', (chunk) => { data += chunk; }); - res.on('end', () => { - try { - resolve(JSON.parse(data)); - } - catch { - reject(new Error('Invalid JSON response')); - } - }); - }); - req.on('error', reject); - req.on('timeout', () => { - req.destroy(); - reject(new Error('Request timeout')); - }); - req.write(payload); - req.end(); - }); -} -async function testAntigravityPort(port, csrfToken) { - try { - await antigravityRequest(port, csrfToken, '/exa.language_server_pb.LanguageServerService/GetUnleashData', { wrapper_data: {} }); - return true; - } - catch { - return false; - } -} -async function findWorkingPort(ports, csrfToken) { - for (const port of ports) { - const ok = await testAntigravityPort(port, csrfToken); - if (ok) - return port; - } - return null; -} -function formatAntigravityDuration(ms, resetTime) { - if (!Number.isFinite(ms) || ms <= 0) - return 'Ready'; - const mins = Math.ceil(ms / 60000); - let duration = ''; - if (mins < 60) { - duration = `${mins}m`; - } - else { - const hours = Math.floor(mins / 60); - duration = `${hours}h ${mins % 60}m`; - } - if (!resetTime) - return duration; - const resetDate = new Date(resetTime); - const dateStr = resetDate.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric' }); - const timeStr = resetDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false }); - return `${duration} (${dateStr} ${timeStr})`; -} -function updateAntigravityActiveIndex(raw, index) { - const next = { ...raw }; - next.activeIndex = index; - if (next.activeIndexByFamily && typeof next.activeIndexByFamily === 'object') { - next.activeIndexByFamily = { ...next.activeIndexByFamily }; - for (const key of Object.keys(next.activeIndexByFamily)) { - next.activeIndexByFamily[key] = index; - } - } - return next; -} -async function refreshAntigravityQuotaAll() { - if (antigravityQuotaInFlight) { - return antigravityQuotaInFlight; - } - antigravityQuotaInFlight = (async () => { - let originalRaw = ''; - let parsed; - try { - if (!fs.existsSync(ANTIGRAVITY_ACCOUNTS_FILE)) { - throw new Error('antigravity-accounts.json not found'); - } - originalRaw = fs.readFileSync(ANTIGRAVITY_ACCOUNTS_FILE, 'utf-8'); - parsed = JSON.parse(originalRaw); - const accounts = Array.isArray(parsed?.accounts) ? parsed.accounts : []; - if (accounts.length === 0) { - throw new Error('No Antigravity accounts available'); - } - const perAccount = {}; - for (let index = 0; index < accounts.length; index += 1) { - const next = updateAntigravityActiveIndex(parsed, index); - fs.writeFileSync(ANTIGRAVITY_ACCOUNTS_FILE, JSON.stringify(next, null, 2)); - await new Promise((resolve) => setTimeout(resolve, 500)); - const snapshot = await fetchAntigravityQuota(); - perAccount[index] = snapshot; - } - antigravityQuotaState = { - status: 'ok', - scope: 'all', - fetchedAt: Date.now(), - snapshot: perAccount[parsed.activeIndex ?? 0] || perAccount[0], - perAccount - }; - } - catch (err) { - antigravityQuotaState = { - status: 'error', - scope: 'all', - fetchedAt: Date.now(), - error: String(err), - perAccount: antigravityQuotaState.perAccount - }; - } - finally { - if (originalRaw) { - try { - fs.writeFileSync(ANTIGRAVITY_ACCOUNTS_FILE, originalRaw); - } - catch (err) { - logError(`Failed to restore antigravity accounts file: ${err}`); - } - } - antigravityQuotaInFlight = null; - } - return antigravityQuotaState; - })(); - return antigravityQuotaInFlight; -} -function parseAntigravityQuota(data) { - const userStatus = data?.userStatus; - const planInfo = userStatus?.planStatus?.planInfo; - const availableCredits = userStatus?.planStatus?.availablePromptCredits; - let promptCredits; - if (planInfo && availableCredits !== undefined) { - const monthly = Number(planInfo.monthlyPromptCredits); - const available = Number(availableCredits); - if (Number.isFinite(monthly) && monthly > 0) { - promptCredits = { - available, - monthly, - usedPercentage: ((monthly - available) / monthly) * 100, - remainingPercentage: (available / monthly) * 100 - }; - } - } - const rawModels = userStatus?.cascadeModelConfigData?.clientModelConfigs || []; - const models = rawModels - .filter((model) => model?.quotaInfo) - .map((model) => { - const reset = model.quotaInfo?.resetTime ? new Date(model.quotaInfo.resetTime) : null; - const resetTime = reset ? reset.getTime() : undefined; - const remainingFraction = typeof model.quotaInfo?.remainingFraction === 'number' - ? model.quotaInfo.remainingFraction - : undefined; - const remainingPercentage = typeof remainingFraction === 'number' - ? remainingFraction * 100 - : undefined; - const diff = resetTime ? resetTime - Date.now() : undefined; - const label = model.label || model.modelOrAlias?.model || 'model'; - const modelId = model.modelOrAlias?.model || model.modelOrAlias?.alias || 'unknown'; - return { - label, - modelId, - remainingFraction, - remainingPercentage, - isExhausted: remainingFraction === 0, - resetTime, - timeUntilResetMs: diff, - timeUntilResetFormatted: typeof diff === 'number' ? formatAntigravityDuration(diff, resetTime) : undefined - }; - }); - return { - timestamp: Date.now(), - name: userStatus?.name, - email: userStatus?.email, - promptCredits, - models - }; -} -async function fetchAntigravityQuota() { - const info = await detectAntigravityProcessInfo(); - if (!info) { - throw new Error('Antigravity process not found'); - } - const data = await antigravityRequest(info.connectPort, info.csrfToken, '/exa.language_server_pb.LanguageServerService/GetUserStatus', { - metadata: { - ideName: 'antigravity', - extensionName: 'antigravity', - locale: 'en' - } - }); - return parseAntigravityQuota(data); -} -async function refreshAntigravityQuota() { - if (antigravityQuotaInFlight) { - return antigravityQuotaInFlight; - } - antigravityQuotaInFlight = (async () => { - try { - const snapshot = await fetchAntigravityQuota(); - antigravityQuotaState = { - status: 'ok', - scope: 'active', - fetchedAt: Date.now(), - snapshot - }; - } - catch (err) { - antigravityQuotaState = { - status: 'error', - scope: 'active', - fetchedAt: Date.now(), - error: String(err) - }; - } - finally { - antigravityQuotaInFlight = null; - } - return antigravityQuotaState; - })(); - return antigravityQuotaInFlight; -} -function scheduleSync() { - if (syncTimer) { - clearTimeout(syncTimer); - } - syncTimer = setTimeout(() => { - runSync(); - }, SYNC_DEBOUNCE_MS); -} -function startAuthWatcher() { - const authPath = getCodexAuthPath(); - fs.watchFile(authPath, { interval: SYNC_INTERVAL_MS }, () => { - scheduleSync(); - }); -} -export function startWebConsole(options) { - const host = options?.host || DEFAULT_HOST; - const port = options?.port || DEFAULT_PORT; - if (!isLocalhostHost(host)) { - const err = Errors.localhostOnly(host); - throw new Error(`${err.code}: ${err.message}`); - } - runSync(); - startAuthWatcher(); - const server = http.createServer(async (req, res) => { - const requestUrl = new URL(req.url || '/', `http://${host}:${port}`); - const path = requestUrl.pathname; - try { - if (req.method === 'GET' && path === '/') { - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - res.end(HTML); - return; - } - if (req.method === 'GET' && path === '/api/state') { - runSync(); - const store = loadStore(); - const rawAccounts = Object.values(store.accounts); - const accounts = rawAccounts.map(scrubAccount); - const deviceAlias = resolveAliasForCurrentAuth(store); - const authSummary = getCodexAuthSummary(); - const storeStatus = getStoreStatus(); - // Phase G: Only load antigravity if feature is enabled - const settings = getSettings(); - const runtimeSettings = getRuntimeSettings(); - const antigravityEnabled = settings.settings.featureFlags?.antigravityEnabled ?? false; - const antigravity = antigravityEnabled ? loadAntigravityAccounts() : { accounts: [], path: ANTIGRAVITY_ACCOUNTS_FILE }; - const forceState = getForceState(); - const forceActive = isForceActive(); - const autoLogin = loadAutoLoginConfig(); - sendJson(res, 200, { - authPath: getCodexAuthPath(), - deviceAlias, - rotationAlias: store.activeAlias, - accounts, - lastSyncAt, - lastSyncError, - lastSyncAlias, - authSummary, - storeStatus, - login: pendingLogin, - lastLoginError, - // Phase G: Only include antigravity data if feature is enabled - antigravity: antigravityEnabled ? { ...antigravity, quota: antigravityQuotaState } : { accounts: [], path: ANTIGRAVITY_ACCOUNTS_FILE, quota: { status: 'disabled', scope: 'active' } }, - queue: getRefreshQueueState(), - recommendedAlias: recommendAlias(rawAccounts), - logPath: getLogPath(), - autoLogin, - rotationStrategy: runtimeSettings.settings.rotationStrategy, - force: { - active: forceActive, - alias: forceState.forcedAlias, - forcedUntil: forceState.forcedUntil, - forcedBy: forceState.forcedBy, - remainingMs: getRemainingForceTimeMs(), - remainingTime: formatForceDuration(getRemainingForceTimeMs()) - }, - // Phase G: Include feature flags in state - featureFlags: settings.settings.featureFlags || { antigravityEnabled: false } - }); - return; - } - if (req.method === 'GET' && path === '/api/logs') { - const limitParam = requestUrl.searchParams.get('limit'); - const limit = limitParam ? Number(limitParam) : undefined; - const lines = readLogTail(Number.isFinite(limit) ? limit : undefined); - sendJson(res, 200, { path: getLogPath(), lines }); - return; - } - if (req.method === 'POST' && path === '/api/sync') { - try { - runSync(); - sendJson(res, 200, { ok: true }); - } - catch (err) { - sendJson(res, 500, { error: String(err) }); - } - return; - } - if (req.method === 'POST' && path === '/api/auth/start') { - const body = await readJsonBody(req); - const alias = typeof body.alias === 'string' ? body.alias.trim() : ''; - if (!alias) { - sendJson(res, 400, { error: 'Missing alias' }); - return; - } - if (pendingLogin) { - sendJson(res, 409, { error: `Login already in progress for ${pendingLogin.alias}` }); - return; - } - try { - const result = await startManualLogin(alias); - sendJson(res, 200, result); - } - catch (err) { - lastLoginError = String(err); - sendJson(res, 500, { error: String(err) }); - } - return; - } - if (req.method === 'POST' && path === '/api/auto-login/start') { - const body = await readJsonBody(req); - const selector = typeof body.selector === 'string' ? body.selector.trim() : ''; - if (!selector) { - sendJson(res, 400, { error: 'Missing selector' }); - return; - } - try { - const result = await startAutoLogin(selector, body.visible === true); - sendJson(res, 200, result); - } - catch (err) { - lastLoginError = String(err); - sendJson(res, 500, { error: String(err) }); - } - return; - } - if (req.method === 'POST' && path === '/api/auto-login/add') { - const body = await readJsonBody(req); - const email = typeof body.email === 'string' ? body.email.trim() : ''; - const password = typeof body.password === 'string' ? body.password : ''; - const alias = typeof body.alias === 'string' ? body.alias.trim() : ''; - const chatgptPassword = typeof body.chatgptPassword === 'string' ? body.chatgptPassword : ''; - if (!email) { - sendJson(res, 400, { error: 'Missing login/email' }); - return; - } - if (!password.trim()) { - sendJson(res, 400, { error: 'Missing password' }); - return; - } - try { - const result = await saveAutoLoginAccountAndStart({ - email, - password, - alias, - chatgptPassword - }); - sendJson(res, 200, result); - } - catch (err) { - lastLoginError = String(err); - sendJson(res, 500, { error: String(err) }); - } - return; - } - if (req.method === 'POST' && path === '/api/switch') { - const body = await readJsonBody(req); - if (!body.alias) { - sendJson(res, 400, { error: 'Missing alias' }); - return; - } - try { - writeCodexAuthForAlias(body.alias); - sendJson(res, 200, { ok: true }); - } - catch (err) { - sendJson(res, 400, { error: String(err) }); - } - return; - } - if (req.method === 'POST' && path === '/api/remove') { - const body = await readJsonBody(req); - if (!body.alias) { - sendJson(res, 400, { error: 'Missing alias' }); - return; - } - removeAccount(body.alias); - sendJson(res, 200, { ok: true }); - return; - } - if (req.method === 'POST' && path === '/api/account/meta') { - const body = await readJsonBody(req); - if (!body.alias) { - sendJson(res, 400, { error: 'Missing alias' }); - return; - } - const tags = typeof body.tags === 'string' - ? body.tags - .split(',') - .map((tag) => tag.trim().toLowerCase()) - .filter(Boolean) - : []; - const uniqueTags = Array.from(new Set(tags)); - const notes = typeof body.notes === 'string' ? body.notes.trim() : ''; - updateAccount(body.alias, { - tags: uniqueTags.length > 0 ? uniqueTags : undefined, - notes: notes || undefined - }); - sendJson(res, 200, { ok: true }); - return; - } - if (req.method === 'POST' && path === '/api/token/refresh') { - const body = await readJsonBody(req); - const store = loadStore(); - const candidates = Object.values(store.accounts); - const alias = typeof body.alias === 'string' ? body.alias : undefined; - const targets = alias ? candidates.filter((acc) => acc.alias === alias) : candidates; - if (alias && targets.length === 0) { - sendJson(res, 400, { error: 'Unknown alias' }); - return; - } - const deviceAlias = resolveAliasForCurrentAuth(store); - const results = []; - for (const account of targets) { - if (!account.refreshToken) { - results.push({ alias: account.alias, updated: false, error: 'No refresh token' }); - continue; - } - const refreshed = await refreshToken(account.alias); - if (!refreshed) { - results.push({ alias: account.alias, updated: false, error: 'Token refresh failed' }); - continue; - } - if (deviceAlias === account.alias) { - try { - writeCodexAuthForAlias(account.alias); - } - catch (err) { - results.push({ alias: account.alias, updated: true, error: `Refreshed, but failed to update auth.json: ${err}` }); - continue; - } - } - results.push({ alias: account.alias, updated: true }); - } - sendJson(res, 200, { ok: true, results }); - return; - } - if (req.method === 'POST' && path === '/api/limits/refresh') { - const body = await readJsonBody(req); - const accounts = listAccounts().filter((acc) => acc.refreshToken && acc.accessToken); - if (body.alias && !accounts.find((acc) => acc.alias === body.alias)) { - sendJson(res, 400, { error: 'Unknown alias' }); - return; - } - const queue = startRefreshQueue(accounts, body.alias); - sendJson(res, 200, { ok: true, queue }); - return; - } - if (req.method === 'POST' && path === '/api/limits/stop') { - stopRefreshQueue(); - sendJson(res, 200, { ok: true }); - return; - } - // Phase G: Antigravity endpoints - check feature flag - if (req.method === 'POST' && path === '/api/antigravity/refresh') { - // Check if antigravity feature is enabled - if (!isFeatureEnabled('antigravityEnabled')) { - sendJson(res, 403, { - error: 'Antigravity feature is disabled', - code: 'FEATURE_DISABLED', - feature: 'antigravity' - }); - return; - } - await refreshAntigravityQuota(); - sendJson(res, 200, { ok: true, quota: antigravityQuotaState }); - return; - } - if (req.method === 'POST' && path === '/api/antigravity/refresh-all') { - // Check if antigravity feature is enabled - if (!isFeatureEnabled('antigravityEnabled')) { - sendJson(res, 403, { - error: 'Antigravity feature is disabled', - code: 'FEATURE_DISABLED', - feature: 'antigravity' - }); - return; - } - await refreshAntigravityQuotaAll(); - sendJson(res, 200, { ok: true, quota: antigravityQuotaState }); - return; - } - // Phase D: Account Lifecycle API Endpoints - // GET /api/accounts - List all accounts with metadata - if (req.method === 'GET' && path === '/api/accounts') { - const store = loadStore(); - const accounts = Object.values(store.accounts).map(acc => ({ - alias: acc.alias, - email: acc.email, - enabled: acc.enabled !== false, // Defaults to true - disabledAt: acc.disabledAt, - disabledBy: acc.disabledBy, - disableReason: acc.disableReason, - usageCount: acc.usageCount, - rateLimits: acc.rateLimits, - limitsConfidence: acc.limitsConfidence, - limitStatus: acc.limitStatus, - limitError: acc.limitError, - lastLimitProbeAt: acc.lastLimitProbeAt, - lastLimitErrorAt: acc.lastLimitErrorAt, - tags: acc.tags, - notes: acc.notes - })); - sendJson(res, 200, { accounts }); - return; - } - // PUT /api/accounts/:alias/enabled - Enable/disable an account - if (req.method === 'PUT' && path.startsWith('/api/accounts/') && path.endsWith('/enabled')) { - const aliasMatch = path.match(/^\/api\/accounts\/([^\/]+)\/enabled$/); - if (!aliasMatch) { - sendJson(res, 400, { error: 'Invalid path format' }); - return; - } - const alias = aliasMatch[1]; - const store = loadStore(); - if (!store.accounts[alias]) { - sendJson(res, 404, { error: 'Unknown alias', code: 'ACCOUNT_NOT_FOUND' }); - return; - } - const body = await readJsonBody(req); - const enabled = body.enabled === true; - // Phase D: Prevent disabling the last enabled account - if (!enabled) { - const enabledCount = Object.values(store.accounts).filter(acc => acc.alias !== alias && acc.enabled !== false).length; - if (enabledCount === 0) { - sendJson(res, 409, { - error: 'Cannot disable the last enabled account', - code: 'LAST_ACCOUNT' - }); - return; - } - } - // Phase D: Double-submit protection - check if already in desired state - const currentEnabled = store.accounts[alias].enabled !== false; - if (currentEnabled === enabled) { - sendJson(res, 409, { - error: enabled ? 'Account is already enabled' : 'Account is already disabled', - code: 'ALREADY_IN_STATE' - }); - return; - } - const updates = { enabled }; - if (!enabled) { - updates.disabledAt = Date.now(); - updates.disabledBy = 'dashboard'; // Could be expanded to track actor - } - else { - // Clear disable metadata when enabling - updates.disabledAt = undefined; - updates.disabledBy = undefined; - updates.disableReason = undefined; - } - updateAccount(alias, updates); - logInfo(`Account ${alias} ${enabled ? 'enabled' : 'disabled'} via dashboard`); - sendJson(res, 200, { - ok: true, - alias, - enabled, - disabledAt: updates.disabledAt, - disabledBy: updates.disabledBy - }); - return; - } - // POST /api/accounts/:alias/reauth - Re-authenticate an account - if (req.method === 'POST' && path.startsWith('/api/accounts/') && path.endsWith('/reauth')) { - const aliasMatch = path.match(/^\/api\/accounts\/([^\/]+)\/reauth$/); - if (!aliasMatch) { - sendJson(res, 400, { error: 'Invalid path format' }); - return; - } - const alias = aliasMatch[1]; - const store = loadStore(); - if (!store.accounts[alias]) { - sendJson(res, 404, { error: 'Unknown alias', code: 'ACCOUNT_NOT_FOUND' }); - return; - } - // Phase D: Cannot re-auth a disabled account - if (store.accounts[alias].enabled === false) { - sendJson(res, 409, { - error: 'Cannot re-authenticate a disabled account', - code: 'ACCOUNT_DISABLED' - }); - return; - } - // Phase D: Only targeted alias credentials mutate - // Start OAuth flow for the specific alias - try { - if (pendingLogin) { - sendJson(res, 409, { error: `Login already in progress for ${pendingLogin.alias}` }); - return; - } - const flow = await createAuthorizationFlow(); - const body = await readJsonBody(req); - const actor = body.actor || 'dashboard'; - loginAccount(alias, flow, { timeoutMs: LOGIN_TIMEOUT_MS }) - .then(() => { - logInfo(`Re-auth completed for ${alias} by ${actor}`); - // Update account metadata - updateAccount(alias, { - lastRefresh: new Date().toISOString() - }); - }) - .catch((err) => { - logError(`Re-auth failed for ${alias}: ${err}`); - }); - sendJson(res, 200, { - ok: true, - alias, - url: flow.url, - message: 'OAuth flow started. Complete authentication in the browser.' - }); - } - catch (err) { - sendJson(res, 500, { error: String(err), code: 'AUTH_FLOW_ERROR' }); - } - return; - } - // Phase E: Force Mode API endpoints - // GET /api/force - Get current force state - if (req.method === 'GET' && path === '/api/force') { - const forceState = getForceState(); - const active = isForceActive(); - const remainingMs = getRemainingForceTimeMs(); - sendJson(res, 200, { - active, - alias: forceState.forcedAlias, - forcedAt: forceState.forcedAlias && forceState.forcedUntil - ? forceState.forcedUntil - (24 * 60 * 60 * 1000) - : null, - forcedUntil: forceState.forcedUntil, - forcedBy: forceState.forcedBy, - remainingMs, - remainingTime: formatForceDuration(remainingMs), - previousRotationStrategy: forceState.previousRotationStrategy - }); - return; - } - // POST /api/force - Activate force mode for an alias - if (req.method === 'POST' && path === '/api/force') { - const body = await readJsonBody(req); - const alias = typeof body.alias === 'string' ? body.alias.trim() : ''; - const actor = typeof body.actor === 'string' ? body.actor.trim() : 'api'; - if (!alias) { - sendJson(res, 400, { error: 'Missing alias', code: 'MISSING_ALIAS' }); - return; - } - const result = activateForce(alias, actor); - if (!result.success) { - const statusCode = result.error?.includes('not found') ? 404 - : result.error?.includes('disabled') ? 409 - : 400; - sendJson(res, statusCode, { error: result.error, code: 'FORCE_FAILED' }); - return; - } - logInfo(`Force mode activated for ${alias} by ${actor}`); - sendJson(res, 200, { - ok: true, - alias, - forcedUntil: result.state?.forcedUntil, - remainingMs: result.state?.forcedUntil ? result.state.forcedUntil - Date.now() : 0, - remainingTime: result.state?.forcedUntil - ? formatForceDuration(result.state.forcedUntil - Date.now()) - : '0m', - previousRotationStrategy: result.state?.previousRotationStrategy - }); - return; - } - // POST /api/force/clear - Deactivate force mode - if (req.method === 'POST' && path === '/api/force/clear') { - const result = clearForce(); - if (result.success) { - logInfo('Force mode cleared'); - sendJson(res, 200, { - ok: true, - restoredStrategy: result.restoredStrategy - }); - } - else { - sendJson(res, 500, { error: 'Failed to clear force mode', code: 'CLEAR_FAILED' }); - } - return; - } - // Phase F: Settings API Endpoints - // GET /api/settings - Get current settings - if (req.method === 'GET' && path === '/api/settings') { - const { getSettingsWithInfo } = await import('./settings.js'); - const info = getSettingsWithInfo(); - sendJson(res, 200, { - settings: info.settings, - source: info.source, - preset: info.preset, - canReset: info.canReset - }); - return; - } - // PUT /api/settings - Update settings - if (req.method === 'PUT' && path === '/api/settings') { - const body = await readJsonBody(req); - const { updateSettings } = await import('./settings.js'); - const actor = body.actor || 'dashboard'; - const updates = {}; - if (body.rotationStrategy) { - updates.rotationStrategy = body.rotationStrategy; - } - if (typeof body.criticalThreshold === 'number') { - updates.criticalThreshold = body.criticalThreshold; - } - if (typeof body.lowThreshold === 'number') { - updates.lowThreshold = body.lowThreshold; - } - if (body.accountWeights) { - updates.accountWeights = body.accountWeights; - } - // Phase G: Handle feature flags - if (body.featureFlags && typeof body.featureFlags === 'object') { - updates.featureFlags = body.featureFlags; - } - const result = updateSettings(updates, actor); - if (result.success) { - sendJson(res, 200, { - ok: true, - settings: result.settings - }); - } - else { - sendJson(res, 400, { - error: 'Validation failed', - code: 'VALIDATION_ERROR', - details: result.errors - }); - } - return; - } - // Phase G: GET /api/settings/feature-flags - Get feature flags - if (req.method === 'GET' && path === '/api/settings/feature-flags') { - const settings = getSettings(); - sendJson(res, 200, { - featureFlags: settings.settings.featureFlags || { antigravityEnabled: false } - }); - return; - } - // Phase G: PUT /api/settings/feature-flags - Update feature flags - if (req.method === 'PUT' && path === '/api/settings/feature-flags') { - const body = await readJsonBody(req); - const { updateSettings } = await import('./settings.js'); - const actor = body.actor || 'dashboard'; - const updates = {}; - if (body.featureFlags && typeof body.featureFlags === 'object') { - updates.featureFlags = body.featureFlags; - const result = updateSettings(updates, actor); - if (result.success && result.settings) { - logInfo(`Feature flags updated by ${actor}: ${JSON.stringify(body.featureFlags)}`); - sendJson(res, 200, { - ok: true, - featureFlags: result.settings.featureFlags || { antigravityEnabled: false } - }); - } - else { - sendJson(res, 400, { - error: 'Validation failed', - code: 'VALIDATION_ERROR', - details: result.errors - }); - } - } - else { - sendJson(res, 400, { - error: 'Invalid feature flags', - code: 'INVALID_FEATURE_FLAGS' - }); - } - return; - } - // POST /api/settings/reset - Reset to defaults - if (req.method === 'POST' && path === '/api/settings/reset') { - const { resetSettings } = await import('./settings.js'); - const body = await readJsonBody(req); - const actor = body.actor || 'dashboard'; - const settings = resetSettings(actor); - sendJson(res, 200, { - ok: true, - settings - }); - return; - } - // POST /api/settings/preset - Apply a preset - if (req.method === 'POST' && path === '/api/settings/preset') { - const body = await readJsonBody(req); - const { applyPreset } = await import('./settings.js'); - const preset = body.preset; - if (!preset || !['balanced', 'conservative', 'aggressive', 'custom'].includes(preset)) { - sendJson(res, 400, { - error: 'Invalid preset', - code: 'INVALID_PRESET', - validPresets: ['balanced', 'conservative', 'aggressive', 'custom'] - }); - return; - } - const actor = body.actor || 'dashboard'; - const result = applyPreset(preset, actor); - if (result.success) { - sendJson(res, 200, { - ok: true, - preset, - settings: result.settings - }); - } - else { - sendJson(res, 400, { - error: 'Failed to apply preset', - code: 'PRESET_ERROR', - details: result.errors - }); - } - return; - } - res.writeHead(404, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Not found' })); - } - catch (err) { - if (res.writableEnded) { - return; - } - const errorCode = err?.code; - if (errorCode === 'INVALID_JSON') { - sendJson(res, 400, { error: 'Invalid JSON payload', code: 'INVALID_JSON' }); - return; - } - if (errorCode === 'PAYLOAD_TOO_LARGE') { - sendJson(res, 413, { error: 'Payload too large', code: 'PAYLOAD_TOO_LARGE' }); - return; - } - const errorMessage = err instanceof Error ? err.message : String(err); - logError(`Web request failed (${req.method} ${path}): ${errorMessage}`); - sendJson(res, 500, { error: 'Internal server error', code: 'INTERNAL_ERROR' }); - } - }); - server.listen(port, host, () => { - console.log(`[multi-auth] Codex dashboard running at http://${host}:${port}`); - logInfo(`Codex dashboard running at http://${host}:${port}`); - }); - return server; -} -//# sourceMappingURL=web.js.map \ No newline at end of file diff --git a/dist/web.js.map b/dist/web.js.map deleted file mode 100644 index a4022b5..0000000 --- a/dist/web.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"} \ No newline at end of file diff --git a/img/img.png b/img/img.png new file mode 100644 index 0000000..a87e0fc Binary files /dev/null and b/img/img.png differ diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index a55f7b4..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3889 +0,0 @@ -{ - "name": "@guard22/opencode-multi-auth-codex", - "version": "1.4.3", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@guard22/opencode-multi-auth-codex", - "version": "1.4.3", - "license": "MIT", - "bin": { - "opencode-multi-auth": "dist/cli.js" - }, - "devDependencies": { - "@opencode-ai/plugin": "^1.0.193", - "@opencode-ai/sdk": "^1.0.193", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.2", - "jest": "^29.7.0", - "ts-jest": "^29.4.6", - "typescript": "^5.7.2" - }, - "peerDependencies": { - "@opencode-ai/plugin": "^1.0.150" - }, - "peerDependenciesMeta": { - "@opencode-ai/plugin": { - "optional": true - } - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@opencode-ai/plugin": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.1.21.tgz", - "integrity": "sha512-oAWVlKG7LACGFYawfdHGMN6e+6lyN6F+zPVncFUB99BrTl/TjELE5gTZwU7MalGpjwfU77yslBOZm4BXVAYGvw==", - "dev": true, - "dependencies": { - "@opencode-ai/sdk": "1.1.21", - "zod": "4.1.8" - } - }, - "node_modules/@opencode-ai/sdk": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.21.tgz", - "integrity": "sha512-4M6lBjRPlPz99Rb5rS5ZqKrb0UDDxOT9VTG06JpNxvA7ynTd8C50ckc2NGzWtvjarmxfaAk1VeuBYN/cq2pIKQ==", - "dev": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001770", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", - "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.3", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index 31ae2b2..cc6eb3a 100644 --- a/package.json +++ b/package.json @@ -9,20 +9,28 @@ "opencode-multi-auth": "dist/cli.js" }, "scripts": { - "build": "tsc", + "build": "pnpm run build:backend && pnpm run build:web", + "build:backend": "tsc", + "build:web": "pnpm --dir web run build", "clean": "rm -rf dist", + "typecheck": "tsc --noEmit", + "verify:static": "pnpm run typecheck && pnpm --dir web lint && pnpm --dir web typecheck", + "verify:plan": "pnpm run verify:static && pnpm run test", + "package:manual-approval": "echo Production build/package validation is deferred until explicit approval", "web": "node ./dist/cli.js web --port 3434 --host 127.0.0.1", - "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test": "pnpm run test:backend && pnpm run test:frontend", + "test:backend": "pnpm run test:unit && pnpm run test:integration && pnpm run test:web:headless", "test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/unit", "test:integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/integration", + "test:integration:dashboard-contract": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/integration/dashboard-api-contract.test.ts", + "test:frontend": "pnpm --dir web test", "test:web:headless": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/web-headless", "test:failure": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/failure", "test:stress": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/stress", "test:sandbox": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/sandbox", "test:soak:48h": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/soak", - "lint": "tsc --noEmit" + "lint": "pnpm run typecheck" }, - "dependencies": {}, "peerDependencies": { "@opencode-ai/plugin": "^1.0.150" }, @@ -59,6 +67,7 @@ "oauth", "multi-account" ], + "packageManager": "pnpm@11.5.0", "author": "a3fckx", "license": "MIT" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..5e5d6be --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5093 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@opencode-ai/plugin': + specifier: ^1.0.193 + version: 1.15.0 + '@opencode-ai/sdk': + specifier: ^1.0.193 + version: 1.15.0 + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/node': + specifier: ^22.10.2 + version: 22.19.19 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@22.19.19) + ts-jest: + specifier: ^29.4.6 + version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19))(typescript@5.9.3) + typescript: + specifier: ^5.7.2 + version: 5.9.3 + + web: + dependencies: + '@tanstack/react-query': + specifier: ^5.64.0 + version: 5.100.10(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^6.28.0 + version: 6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts: + specifier: ^3.8.1 + version: 3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(redux@5.0.1) + devDependencies: + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.1.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react': + specifier: ^18.3.18 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.5 + version: 18.3.7(@types/react@18.3.28) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.2(@types/node@22.19.19)(yaml@2.9.0)) + jsdom: + specifier: ^25.0.1 + version: 25.0.1 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + vite: + specifier: ^6.0.7 + version: 6.4.2(@types/node@22.19.19)(yaml@2.9.0) + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@22.19.19)(jsdom@25.0.1) + +packages: + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + + '@opencode-ai/plugin@1.15.0': + resolution: {integrity: sha512-97N985FDqNMpbBbsNotu0j3lkHwDyHX+2BnPFfnuNmBrTQNqLrn2UyA6wBfP6J5l4xUslwKNrH2IVrGIZeiSxQ==} + peerDependencies: + '@opentui/core': '>=0.2.10' + '@opentui/keymap': '>=0.2.10' + '@opentui/solid': '>=0.2.10' + peerDependenciesMeta: + '@opentui/core': + optional: true + '@opentui/keymap': + optional: true + '@opentui/solid': + optional: true + + '@opencode-ai/sdk@1.15.0': + resolution: {integrity: sha512-npmURKwd1HoV/i3CsGl2tdHpGhWSGVqTN1cp0s2gDH5gE6XduAtFmmVXBZ/ovJLh3Evu6sTVZ9n0IkJzFNeveQ==} + + '@reduxjs/toolkit@2.12.0': + resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@remix-run/router@1.23.2': + resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} + engines: {node: '>=14.0.0'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + cpu: [x64] + os: [win32] + + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@tanstack/query-core@5.100.10': + resolution: {integrity: sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==} + + '@tanstack/react-query@5.100.10': + resolution: {integrity: sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==} + peerDependencies: + react: ^18 || ^19 + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/node@22.19.19': + resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.10.29: + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + effect@4.0.0-beta.65: + resolution: {integrity: sha512-QYKvQPAj3CmtsvWkHQww15wX4KG2gNsszDWEcOO5sZCMknp66u6Si/Opmt3wwWCwsyvRmDAdIg+JIz5qzbbFIw==} + + electron-to-chromium@1.5.356: + resolution: {integrity: sha512-9NgFd7m5t5MCJ5rUSjJITUXAH9mEGlrlofnMf4YEr+pz6JlP7cWmTAH+JFmbPnaSW8koVTkuW7pacORWAnA5Yw==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-toolkit@1.46.1: + resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + fast-check@4.8.0: + resolution: {integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==} + engines: {node: '>=12.17.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-my-way-ts@0.1.6: + resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.8: + resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kubernetes-types@1.30.0: + resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.12: + resolution: {integrity: sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==} + + multipasta@0.2.7: + resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + pure-rand@8.4.0: + resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-redux@9.3.0: + resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-router-dom@6.30.3: + resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.3: + resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + recharts@3.8.1: + resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toml@4.1.1: + resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} + engines: {node: '>=20'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + ts-jest@29.4.9: + resolution: {integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <7' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + uuid@13.0.2: + resolution: {integrity: sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==} + hasBin: true + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@4.1.8: + resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==} + +snapshots: + + '@adobe/css-tools@4.4.4': {} + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.3': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.3 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.19.19) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 22.19.19 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 22.19.19 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.2.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.19.19 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + + '@opencode-ai/plugin@1.15.0': + dependencies: + '@opencode-ai/sdk': 1.15.0 + effect: 4.0.0-beta.65 + zod: 4.1.8 + + '@opencode-ai/sdk@1.15.0': + dependencies: + cross-spawn: 7.0.6 + + '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.8 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 18.3.1 + react-redux: 9.3.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) + + '@remix-run/router@1.23.2': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + + '@rollup/rollup-android-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + + '@sinclair/typebox@0.27.10': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@tanstack/query-core@5.100.10': {} + + '@tanstack/react-query@5.100.10(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.100.10 + react: 18.3.1 + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/estree@1.0.9': {} + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 22.19.19 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/node@22.19.19': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/stack-utils@2.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@22.19.19)(yaml@2.9.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.2(@types/node@22.19.19)(yaml@2.9.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.19))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.19) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + agent-base@7.1.4: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + + babel-jest@29.7.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@29.6.3(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.10.29: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.29 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.356 + node-releases: 2.0.44 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001792: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + check-error@2.1.3: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + create-jest@29.7.0(@types/node@22.19.19): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.19.19) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + + dedent@1.7.2: {} + + deep-eql@5.0.2: {} + + deepmerge@4.3.1: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: + optional: true + + detect-newline@3.1.0: {} + + diff-sequences@29.6.3: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + effect@4.0.0-beta.65: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 4.8.0 + find-my-way-ts: 0.1.6 + ini: 6.0.0 + kubernetes-types: 1.30.0 + msgpackr: 1.11.12 + multipasta: 0.2.7 + toml: 4.1.1 + uuid: 13.0.2 + yaml: 2.9.0 + + electron-to-chromium@1.5.356: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + entities@6.0.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + es-toolkit@1.46.1: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + esprima@4.0.1: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + eventemitter3@5.0.4: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect-type@1.3.0: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + fast-check@4.8.0: + dependencies: + pure-rand: 8.4.0 + + fast-json-stable-stringify@2.1.0: {} + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-my-way-ts@0.1.6: {} + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + immer@10.2.0: {} + + immer@11.1.8: {} + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@6.0.0: {} + + internmap@2.0.3: {} + + is-arrayish@0.2.1: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.3 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-stream@2.0.1: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.3 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.3 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.8.0 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.2 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@22.19.19): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.19.19) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.19.19) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@22.19.19): + dependencies: + '@babel/core': 7.29.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.19.19 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 22.19.19 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.12 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.8.0 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.2 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.19 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 22.19.19 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@22.19.19): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.19.19) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + jsdom@25.0.1: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + form-data: 4.0.5 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.20.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json5@2.2.3: {} + + kleur@3.0.3: {} + + kubernetes-types@1.30.0: {} + + leven@3.1.0: {} + + lines-and-columns@1.2.4: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash.memoize@4.1.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@4.0.0: + dependencies: + semver: 7.8.0 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.12: + optionalDependencies: + msgpackr-extract: 3.0.3 + + multipasta@0.2.7: {} + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + neo-async@2.6.2: {} + + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 + optional: true + + node-int64@0.4.0: {} + + node-releases@2.0.44: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nwsapi@2.2.23: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + pathe@1.1.2: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + postcss@8.5.14: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + pure-rand@8.4.0: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-redux@9.3.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + redux: 5.0.1 + + react-refresh@0.17.0: {} + + react-router-dom@6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.3(react@18.3.1) + + react-router@6.30.3(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + recharts@3.8.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.46.1 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-redux: 9.3.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@18.3.1) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + require-directory@2.1.1: {} + + reselect@5.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + + rrweb-cssom@0.7.1: {} + + rrweb-cssom@0.8.0: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.8.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 7.2.3 + minimatch: 3.1.5 + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toml@4.1.1: {} + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.9 + jest: 29.7.0(@types/node@22.19.19) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.8.0 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + jest-util: 29.7.0 + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + + typescript@5.9.3: {} + + uglify-js@3.19.3: + optional: true + + undici-types@6.21.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + uuid@13.0.2: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite-node@2.1.9(@types/node@22.19.19): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@22.19.19) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@22.19.19): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.14 + rollup: 4.60.4 + optionalDependencies: + '@types/node': 22.19.19 + fsevents: 2.3.3 + + vite@6.4.2(@types/node@22.19.19)(yaml@2.9.0): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.14 + rollup: 4.60.4 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 22.19.19 + fsevents: 2.3.3 + yaml: 2.9.0 + + vitest@2.1.9(@types/node@22.19.19)(jsdom@25.0.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.19)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@22.19.19) + vite-node: 2.1.9(@types/node@22.19.19) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.19 + jsdom: 25.0.1 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@8.20.1: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@2.9.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zod@4.1.8: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..59ad747 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +packages: + - web + +allowBuilds: + esbuild: true + msgpackr-extract: true diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..548cc76 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "skills": { + "accessibility": { + "source": "addyosmani/web-quality-skills", + "sourceType": "autoskills-registry", + "computedHash": "bffe3d08cfe92ebad63699f74ce29e35c19850ebfbf474c1463183cfe34d6a09" + }, + "bash-defensive-patterns": { + "source": "wshobson/agents", + "sourceType": "autoskills-registry", + "computedHash": "1f566c2d9c3b50a784b05d08f6b08ebfeb31f038e4d4173f2d488484edb058a6" + }, + "bun": { + "source": "midudev/autoskills", + "sourceType": "autoskills-registry", + "computedHash": "1386ed5490ed278e38312a416c9aa074dbe3849f32ae7f841e4142fef5d4df94" + }, + "composition-patterns": { + "source": "vercel-labs/agent-skills", + "sourceType": "autoskills-registry", + "computedHash": "c124390cc3441844903ada94cb5820b23d922f2d0ad34e01e029e410197c8a32" + }, + "frontend-design": { + "source": "anthropics/skills", + "sourceType": "autoskills-registry", + "computedHash": "82fb11a63fb1e35ee2469516ed02d54695f783115b1540c0e783197af4240a3a" + }, + "nodejs-backend-patterns": { + "source": "wshobson/agents", + "sourceType": "autoskills-registry", + "computedHash": "710a5e6f83c46e8f6c43356df55c143a7375c4414559a581654ce51709138c55" + }, + "nodejs-best-practices": { + "source": "sickn33/antigravity-awesome-skills", + "sourceType": "autoskills-registry", + "computedHash": "7361ab02fb6b09913e3bdd9cf61c629ed6c17de9485e6a781054e5d437ccfc29" + }, + "react-best-practices": { + "source": "vercel-labs/agent-skills", + "sourceType": "autoskills-registry", + "computedHash": "502eb06700e2c3cb1bd7f710860528f72281320bd5171a2087b05a090b573dc1" + }, + "typescript-advanced-types": { + "source": "wshobson/agents", + "sourceType": "autoskills-registry", + "computedHash": "5ca0e177c6aaaba1889255691224daafdb7d71f317cc70bede1590d3907ded42" + }, + "vite": { + "source": "antfu/skills", + "sourceType": "autoskills-registry", + "computedHash": "e0b52fe3ffea533e1e543ac8a8a20fccca3beed9f8bdc31a0934fe9f554439e8" + }, + "vitest": { + "source": "antfu/skills", + "sourceType": "autoskills-registry", + "computedHash": "be7329a28a492b802061206c2d75603ab294f25f541bc5421d5de8d41abe68b1" + } + } +} diff --git a/src/auth.ts b/src/auth.ts index fc766a3..bda91e0 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -2,6 +2,7 @@ import { createHash, randomBytes } from 'node:crypto' import * as http from 'http' import * as url from 'url' import { addAccount, updateAccount, loadStore } from './store.js' +import { setMetrics } from './metrics-store.js' import { clearAuthInvalid } from './rotation.js' import { decodeJwtPayload, @@ -10,6 +11,7 @@ import { getExpiryFromClaims, getPlanTypeFromClaims } from './codex-auth.js' +import { logError, logInfo } from './logger.js' import type { AccountCredentials } from './types.js' const OPENAI_ISSUER = 'https://auth.openai.com' @@ -31,6 +33,8 @@ interface TokenResponse { token_type: string } +const inFlightRefreshes = new Map>() + interface AuthorizationFlow { pkce: { verifier: string; challenge: string } state: string @@ -220,12 +224,14 @@ export async function loginAccount( planType, expiresAt, email, - lastRefresh: new Date(now).toISOString(), - lastSeenAt: now, source: 'opencode', authInvalid: false, authInvalidatedAt: undefined }) + setMetrics(alias, { + lastRefresh: new Date(now).toISOString(), + lastSeenAt: now + }) const account = store.accounts[alias] @@ -270,12 +276,12 @@ export async function loginAccount( }) } -export async function refreshToken(alias: string): Promise { +async function performRefreshToken(alias: string): Promise { const store = loadStore() const account = store.accounts[alias] if (!account?.refreshToken) { - console.error(`[multi-auth] No refresh token for ${alias}`) + logError(`[multi-auth] No refresh token for ${alias}`) return null } @@ -291,7 +297,7 @@ export async function refreshToken(alias: string): Promise { + const inFlight = inFlightRefreshes.get(alias) + if (inFlight) { + return inFlight + } + + const refresh = performRefreshToken(alias) + inFlightRefreshes.set(alias, refresh) + try { + return await refresh + } finally { + if (inFlightRefreshes.get(alias) === refresh) { + inFlightRefreshes.delete(alias) + } + } +} + export async function ensureValidToken(alias: string): Promise { const store = loadStore() const account = store.accounts[alias] diff --git a/src/codex-auth.ts b/src/codex-auth.ts index 6ea1012..0e7830f 100644 --- a/src/codex-auth.ts +++ b/src/codex-auth.ts @@ -2,7 +2,9 @@ import * as fs from 'fs' import * as os from 'os' import * as path from 'path' import { addAccount, loadStore, updateAccount } from './store.js' -import type { AccountCredentials } from './types.js' +import { setMetrics } from './metrics-store.js' +import { logInfo } from './logger.js' +import type { AccountCredentials, AccountStore, CodexActiveState } from './types.js' export interface CodexAuthTokens { id_token?: string @@ -60,6 +62,7 @@ export function loadCodexAuthFile(): CodexAuthFile | null { if (!fs.existsSync(CODEX_AUTH_FILE)) return null try { const raw = fs.readFileSync(CODEX_AUTH_FILE, 'utf-8') + if (!raw.trim()) return null return JSON.parse(raw) as CodexAuthFile } catch (err) { lastAuthError = 'Failed to parse codex auth.json' @@ -181,6 +184,26 @@ export function getExpiryFromClaims(claims: Record | null): number return undefined } +function getIssuedAtFromClaims(claims: Record | null): number | undefined { + if (!claims) return undefined + return typeof claims.iat === 'number' ? claims.iat : undefined +} + +function hasNewerStoredToken( + existing: AccountCredentials, + authAccessClaims: Record | null, + authExpiresAt: number +): boolean { + const storedAccessClaims = decodeJwtPayload(existing.accessToken) + const storedIssuedAt = getIssuedAtFromClaims(storedAccessClaims) + const authIssuedAt = getIssuedAtFromClaims(authAccessClaims) + if (typeof storedIssuedAt === 'number' && typeof authIssuedAt === 'number') { + return storedIssuedAt > authIssuedAt + } + + return existing.expiresAt > authExpiresAt +} + function fingerprintTokens(tokens: CodexAuthTokens): string { return `${tokens.access_token || ''}:${tokens.refresh_token || ''}:${tokens.id_token || ''}` } @@ -230,8 +253,7 @@ function findMatchingAlias( return null } -export function getCodexAuthSummary(): CodexAuthSummary { - const auth = loadCodexAuthFile() +function buildCodexAuthSummary(auth: CodexAuthFile | null): CodexAuthSummary { const normalized = normalizeTokens(auth) const access = normalized?.accessToken const refresh = normalized?.refreshToken @@ -258,6 +280,84 @@ export function getCodexAuthSummary(): CodexAuthSummary { } } +function readCodexAuthFileReadOnly(): { auth: CodexAuthFile | null; error?: string } { + if (!fs.existsSync(CODEX_AUTH_FILE)) return { auth: null } + + let raw: string + try { + raw = fs.readFileSync(CODEX_AUTH_FILE, 'utf-8') + } catch { + return { auth: null, error: 'Failed to read codex auth.json' } + } + + if (!raw.trim()) return { auth: null } + + try { + return { auth: JSON.parse(raw) as CodexAuthFile } + } catch { + return { auth: null, error: 'Failed to parse codex auth.json' } + } +} + +function activeStateFromSummary( + status: CodexActiveState['status'], + alias: string | null, + summary: CodexAuthSummary, + error?: string +): CodexActiveState { + return { + status, + alias, + email: summary.email, + accountId: summary.accountId, + accountUserId: summary.accountUserId, + userId: summary.userId, + planType: summary.planType, + expiresAt: summary.expiresAt, + lastRefresh: summary.lastRefresh, + hasAccessToken: summary.hasAccessToken, + hasRefreshToken: summary.hasRefreshToken, + hasIdToken: summary.hasIdToken, + error + } +} + +export function getCodexAuthSummary(): CodexAuthSummary { + const { auth } = readCodexAuthFileReadOnly() + return buildCodexAuthSummary(auth) +} + +export function getCodexActiveState(store?: AccountStore): CodexActiveState { + const { auth, error } = readCodexAuthFileReadOnly() + if (error) { + return activeStateFromSummary('error', null, buildCodexAuthSummary(null), error) + } + + const normalized = normalizeTokens(auth) + const summary = buildCodexAuthSummary(auth) + const hasToken = summary.hasAccessToken || summary.hasRefreshToken || summary.hasIdToken + const hasIdentity = Boolean(summary.email || summary.accountId || summary.accountUserId || summary.userId) + if (!normalized || (!hasToken && !hasIdentity)) { + return activeStateFromSummary('missing', null, summary) + } + + const targetStore = store ?? loadStore() + const alias = findMatchingAlias( + { + access_token: normalized.accessToken, + refresh_token: normalized.refreshToken, + id_token: normalized.idToken + }, + summary.accountId, + summary.accountUserId, + summary.userId, + summary.email, + targetStore + ) + + return activeStateFromSummary(alias ? 'matched' : 'unknown', alias, summary) +} + export function resolveAliasForCurrentAuth(store?: ReturnType): string | null { const auth = loadCodexAuthFile() const normalized = normalizeTokens(auth) @@ -340,8 +440,6 @@ export function syncCodexAuthFile(): { planType, expiresAt, email, - lastRefresh: normalized.lastRefresh, - lastSeenAt: now, source: 'codex' } if (normalized.idToken) { @@ -349,12 +447,25 @@ export function syncCodexAuthFile(): { } if (alias) { - updateAccount(alias, update) + const existing = store.accounts[alias] + if (existing && hasNewerStoredToken(existing, accessClaims, expiresAt)) { + const metadataUpdate = { ...update } + delete metadataUpdate.accessToken + delete metadataUpdate.refreshToken + delete metadataUpdate.expiresAt + delete metadataUpdate.idToken + logInfo(`Skipped stale auth.json token overwrite for ${alias}`) + updateAccount(alias, metadataUpdate) + } else { + updateAccount(alias, update) + } + setMetrics(alias, { lastRefresh: normalized.lastRefresh, lastSeenAt: now }) return { alias, added: false, updated: true, authEmail: email, authAccountId: accountId } } const newAlias = buildAlias(email, accountId, store) addAccount(newAlias, update as Omit) + setMetrics(newAlias, { lastRefresh: normalized.lastRefresh, lastSeenAt: now }) return { alias: newAlias, added: true, updated: true, authEmail: email, authAccountId: accountId } } @@ -392,8 +503,10 @@ export function writeCodexAuthForAlias(alias: string): void { writeCodexAuthFile(auth) updateAccount(alias, { - lastRefresh: auth.last_refresh, - lastSeenAt: Date.now(), source: 'codex' }) + setMetrics(alias, { + lastRefresh: auth.last_refresh, + lastSeenAt: Date.now() + }) } diff --git a/src/cyber-policy.ts b/src/cyber-policy.ts new file mode 100644 index 0000000..364e877 --- /dev/null +++ b/src/cyber-policy.ts @@ -0,0 +1,39 @@ +export function extractErrorMessage(payload: any, fallbackText: string = ''): string { + if (!payload || typeof payload !== 'object') { + return fallbackText + } + + const detailMessage = typeof payload?.detail?.message === 'string' + ? payload.detail.message + : typeof payload?.detail === 'string' + ? payload.detail + : '' + + const errorMessage = typeof payload?.error?.message === 'string' + ? payload.error.message + : '' + + const topLevelMessage = typeof payload?.message === 'string' + ? payload.message + : '' + + return detailMessage || errorMessage || topLevelMessage || fallbackText +} + +function extractErrorCode(payload: any): string { + if (!payload || typeof payload !== 'object') return '' + + return ( + (typeof payload?.detail?.code === 'string' && payload.detail.code) || + (typeof payload?.error?.code === 'string' && payload.error.code) || + (typeof payload?.code === 'string' && payload.code) || + '' + ) +} + +export function isCyberPolicyError(payload: any, fallbackText: string = ''): boolean { + const code = extractErrorCode(payload).toLowerCase() + const text = `${extractErrorMessage(payload, fallbackText)} ${fallbackText}`.toLowerCase() + + return code === 'cyber_policy' || text.includes('cyber_policy') +} diff --git a/src/index.ts b/src/index.ts index be8c610..e1787ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,9 +18,19 @@ import { } from './rotation.js' import { getDefaultModels } from './models.js' import { getForceState, isForceActive } from './force-mode.js' -import { getRuntimeSettings } from './settings.js' +import { getRuntimeSettings, getStickySessionRuntimeSettings } from './settings.js' import { listAccounts, updateAccount, loadStore } from './store.js' -import { DEFAULT_CONFIG, type AccountRateLimits, type PluginConfig } from './types.js' +import { registerMetricsFlushHooks, updateRateLimits } from './metrics-store.js' +import { resolveStickyIdentity } from './sticky-identity.js' +import { hashStickyIdentity } from './sticky-sessions.js' +import { logInfo } from './logger.js' +import { extractErrorMessage, isCyberPolicyError } from './cyber-policy.js' +import { + DEFAULT_CONFIG, + type AccountRateLimits, + type PluginConfig +} from './types.js' +import type { ResolvedStickyIdentity, StickyIdentitySource } from './types.js' import { Errors, type DeterministicError } from './errors.js' const PROVIDER_ID = 'openai' @@ -168,46 +178,6 @@ function ensureContentType(headers: Headers): Headers { return responseHeaders } -function extractErrorMessage(payload: any, fallbackText: string = ''): string { - if (!payload || typeof payload !== 'object') { - return fallbackText - } - - const detailMessage = typeof payload?.detail?.message === 'string' - ? payload.detail.message - : typeof payload?.detail === 'string' - ? payload.detail - : '' - - const errorMessage = typeof payload?.error?.message === 'string' - ? payload.error.message - : '' - - const topLevelMessage = typeof payload?.message === 'string' - ? payload.message - : '' - - return detailMessage || errorMessage || topLevelMessage || fallbackText -} - -function extractErrorCode(payload: any): string { - if (!payload || typeof payload !== 'object') return '' - - return ( - (typeof payload?.detail?.code === 'string' && payload.detail.code) || - (typeof payload?.error?.code === 'string' && payload.error.code) || - (typeof payload?.code === 'string' && payload.code) || - '' - ) -} - -export function isCyberPolicyError(payload: any, fallbackText: string = ''): boolean { - const code = extractErrorCode(payload).toLowerCase() - const text = `${extractErrorMessage(payload, fallbackText)} ${fallbackText}`.toLowerCase() - - return code === 'cyber_policy' || text.includes('cyber_policy') -} - function resolveRateLimitedUntil( rateLimits: AccountRateLimits | undefined, headers: Headers, @@ -281,6 +251,8 @@ async function convertSseToJson(response: Response, headers: Headers): Promise { + registerMetricsFlushHooks() + const terminalNotifierPath = (() => { const candidates = [ '/opt/homebrew/bin/terminal-notifier', @@ -658,8 +630,80 @@ const MultiAuthPlugin: Plugin = async ({ client, $, serverUrl, project, director rotationStrategy: settings.settings.rotationStrategy } + const sticky = settings.settings.featureFlags?.stickySessionsEnabled + ? (() => { + const stickySettings = getStickySessionRuntimeSettings() + const stickyHeaders = new Headers(init?.headers || {}) + const resolved = resolveStickyIdentity({ + headers: stickyHeaders, + body, + allowPromptCacheKey: stickySettings.allowPromptCacheKey, + identitySources: stickySettings.identitySources + }) + if (attempt === 1) { + const present = { + 'header:session_id': Boolean(stickyHeaders.get('session_id')), + 'header:conversation_id': Boolean(stickyHeaders.get('conversation_id')), + 'body:metadata.session_id': Boolean(body?.metadata?.session_id), + 'body:metadata.conversation_id': Boolean(body?.metadata?.conversation_id), + 'body:prompt_cache_key': Boolean(body?.prompt_cache_key) + } + const presentList = Object.entries(present) + .filter(([, v]) => v) + .map(([k]) => k) + const outcome = resolved + ? `HIT source=${resolved.source} hash=${hashStickyIdentity(resolved.canonical).slice(0, 12)}` + : 'MISS (no configured identity source present in request)' + logInfo( + `[sticky] identity resolution: ${outcome}; configured=[${stickySettings.identitySources.join(',')}] allowPromptCacheKey=${stickySettings.allowPromptCacheKey} present=[${presentList.join(',') || 'none'}]` + ) + + // Full diagnostic inventory: list every header + body identity candidate + // (redacted to type + short hash, never raw values) to choose the + // canonical identity source from evidence instead of guessing. + const describeValue = (value: unknown): string => { + if (value === undefined) return 'absent' + if (value === null) return 'null' + if (typeof value === 'string') { + const trimmed = value.trim() + if (trimmed.length === 0) return 'string(empty)' + return `string(len=${trimmed.length},hash=${hashStickyIdentity(trimmed).slice(0, 12)})` + } + if (typeof value === 'object') return `object(keys=[${Object.keys(value as object).join(',') || 'none'}])` + return `${typeof value}` + } + const headerKeys: string[] = [] + stickyHeaders.forEach((_, key) => headerKeys.push(key)) + const bodyTopKeys = body && typeof body === 'object' ? Object.keys(body) : [] + const metadata = body?.metadata + const metadataKeys = + metadata && typeof metadata === 'object' ? Object.keys(metadata as object) : [] + const idCandidates = [ + `header:session-id=${describeValue(stickyHeaders.get('session-id'))}`, + `header:x-session-affinity=${describeValue(stickyHeaders.get('x-session-affinity'))}`, + `header:originator=${describeValue(stickyHeaders.get('originator'))}`, + `header:session_id=${describeValue(stickyHeaders.get('session_id'))}`, + `header:conversation_id=${describeValue(stickyHeaders.get('conversation_id'))}`, + `body.session_id=${describeValue((body as Record)?.session_id)}`, + `body.conversation_id=${describeValue((body as Record)?.conversation_id)}`, + `body.user=${describeValue((body as Record)?.user)}`, + `body.prompt_cache_key=${describeValue((body as Record)?.prompt_cache_key)}`, + `body.metadata.session_id=${describeValue((metadata as Record)?.session_id)}`, + `body.metadata.conversation_id=${describeValue((metadata as Record)?.conversation_id)}`, + `body.metadata.user_id=${describeValue((metadata as Record)?.user_id)}` + ] + logInfo( + `[sticky] request inventory: headerKeys=[${headerKeys.join(',') || 'none'}] bodyKeys=[${bodyTopKeys.join(',') || 'none'}] metadataKeys=[${metadataKeys.join(',') || 'none'}]` + ) + logInfo(`[sticky] identity candidates: ${idCandidates.join(' | ')}`) + } + return resolved + })() + : null + const rotation = await getNextAccount(effectiveConfig, { - model: normalizedModel + model: normalizedModel, + ...(sticky ? { sticky } : {}) }) if (!rotation) { @@ -799,10 +843,10 @@ const MultiAuthPlugin: Plugin = async ({ client, $, serverUrl, project, director : account.rateLimits if (limitUpdate) { const blockingResetAt = getBlockingRateLimitResetAt(mergedRateLimits) - updateAccount(account.alias, { - rateLimits: mergedRateLimits, - rateLimitedUntil: blockingResetAt - }) + if (mergedRateLimits) { + updateRateLimits(account.alias, mergedRateLimits) + } + updateAccount(account.alias, { rateLimitedUntil: blockingResetAt }) } return mergedRateLimits diff --git a/src/limits-refresh.ts b/src/limits-refresh.ts index 12af6c5..da0f628 100644 --- a/src/limits-refresh.ts +++ b/src/limits-refresh.ts @@ -6,6 +6,7 @@ import { } from './rate-limits.js' import { markAuthInvalid, markWorkspaceDeactivated } from './rotation.js' import { loadStore, updateAccount } from './store.js' +import { setMetrics, updateRateLimits } from './metrics-store.js' import { probeRateLimitsForAccount } from './probe-limits.js' import { logError, logInfo } from './logger.js' import { DEFAULT_CONFIG, calculateLimitsConfidence } from './types.js' @@ -19,18 +20,21 @@ export interface LimitRefreshResult { } export async function refreshRateLimitsForAccount(account: AccountCredentials): Promise { - updateAccount(account.alias, { limitStatus: 'running', limitError: undefined }) + setMetrics(account.alias, { limitStatus: 'running', limitError: undefined }) logInfo(`Refreshing limits for ${account.alias}`) const usage = await fetchUsageRateLimitsForAccount(account) if (usage.rateLimits) { const now = Date.now() - const updates: Partial = { - rateLimits: mergeRateLimits(account.rateLimits, usage.rateLimits), + const mergedRateLimits = mergeRateLimits(account.rateLimits, usage.rateLimits) + updateRateLimits(account.alias, mergedRateLimits) + setMetrics(account.alias, { limitStatus: 'success', limitError: undefined, lastLimitProbeAt: now, - limitsConfidence: calculateLimitsConfidence(now, account.lastLimitErrorAt, 'success'), + limitsConfidence: calculateLimitsConfidence(now, account.lastLimitErrorAt, 'success') + }) + const updates: Partial = { authInvalid: false, authInvalidatedAt: undefined } @@ -39,6 +43,8 @@ export async function refreshRateLimitsForAccount(account: AccountCredentials): } if (typeof usage.rateLimitedUntil === 'number' && usage.rateLimitedUntil > now) { updates.rateLimitedUntil = usage.rateLimitedUntil + } else { + updates.rateLimitedUntil = undefined } updateAccount(account.alias, updates) logInfo(`Limits refreshed for ${account.alias} via usage API`) @@ -59,7 +65,7 @@ export async function refreshRateLimitsForAccount(account: AccountCredentials): ) } - updateAccount(account.alias, { + setMetrics(account.alias, { limitStatus: 'error', limitError: usage.error, lastLimitErrorAt: now, @@ -95,7 +101,7 @@ export async function refreshRateLimitsForAccount(account: AccountCredentials): : undefined const rateLimitedUntil = parsedResetAt ?? fallbackResetAt - const updates: Partial = { + setMetrics(account.alias, { limitStatus: 'error', limitError: errorText, lastLimitErrorAt: now, @@ -104,11 +110,14 @@ export async function refreshRateLimitsForAccount(account: AccountCredentials): now, 'error' ) - } + }) + const updates: Partial = {} if (typeof rateLimitedUntil === 'number' && rateLimitedUntil > now) { updates.rateLimitedUntil = rateLimitedUntil } - updateAccount(account.alias, updates) + if (Object.keys(updates).length > 0) { + updateAccount(account.alias, updates) + } return { alias: account.alias, updated: false, @@ -119,12 +128,14 @@ export async function refreshRateLimitsForAccount(account: AccountCredentials): const now = Date.now() const mergedRateLimits = mergeRateLimits(account.rateLimits, probe.rateLimits) const blockingResetAt = getBlockingRateLimitResetAt(mergedRateLimits, now) - updateAccount(account.alias, { - rateLimits: mergedRateLimits, + updateRateLimits(account.alias, mergedRateLimits) + setMetrics(account.alias, { limitStatus: 'success', limitError: undefined, lastLimitProbeAt: now, - limitsConfidence: calculateLimitsConfidence(now, account.lastLimitErrorAt, 'success'), + limitsConfidence: calculateLimitsConfidence(now, account.lastLimitErrorAt, 'success') + }) + updateAccount(account.alias, { rateLimitedUntil: blockingResetAt, authInvalid: false, authInvalidatedAt: undefined diff --git a/src/logger.ts b/src/logger.ts index be68b13..1f63209 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -45,12 +45,30 @@ export function getLogPath(): string { return LOG_FILE } -export function readLogTail(maxLines = MAX_LOG_LINES): string[] { +export interface LogLine { + time: string + level: string + message: string +} + +const LOG_LINE_RE = /^(\S+)\s+\[(\w+)\]\s+(.*)$/ + +function parseLogLine(raw: string): LogLine { + const match = raw.match(LOG_LINE_RE) + if (match) { + return { time: match[1], level: match[2].toLowerCase(), message: match[3] } + } + // Fallback: treat entire line as message with unknown level + return { time: '', level: 'unknown', message: raw } +} + +export function readLogTail(maxLines = MAX_LOG_LINES): LogLine[] { try { if (!fs.existsSync(LOG_FILE)) return [] const data = fs.readFileSync(LOG_FILE, 'utf-8') - const lines = data.split('\n').filter(Boolean) - return lines.slice(Math.max(0, lines.length - maxLines)) + const rawLines = data.split('\n').filter(Boolean) + const tail = rawLines.slice(Math.max(0, rawLines.length - maxLines)) + return tail.map(parseLogLine) } catch { return [] } diff --git a/src/metrics-store.ts b/src/metrics-store.ts new file mode 100644 index 0000000..cac6156 --- /dev/null +++ b/src/metrics-store.ts @@ -0,0 +1,520 @@ +import fs from 'node:fs' +import * as path from 'node:path' +import { hasMeaningfulRateLimits } from './rate-limits.js' +import { getStorePath } from './store.js' +import type { + AccountRateLimits, + LimitStatus, + LimitsConfidence, + RateLimitHistoryEntry, + RateLimitSnapshot, + RateLimitWindow +} from './types.js' + +const METRICS_SIDE_CAR_FILE = 'account-metrics.json' +const METRICS_SIDE_CAR_VERSION = 1 + +// Coalesce high-frequency request telemetry; hot paths must not write per call. +export const METRICS_FLUSH_DEBOUNCE_MS = 2_000 +// Bound telemetry loss during long-running processes even when updates never go idle. +export const METRICS_PERIODIC_FLUSH_MS = 30_000 +const METRICS_SIGNAL_FLUSH_TIMEOUT_MS = 1_500 + +export interface MetricsData { + lastRefresh?: string + lastSeenAt?: number + lastActiveUntil?: number + lastUsed?: number + usageCount?: number + rateLimits?: AccountRateLimits + rateLimitHistory?: RateLimitHistoryEntry[] + limitStatus?: LimitStatus + limitError?: string + lastLimitProbeAt?: number + lastLimitErrorAt?: number + limitsConfidence?: LimitsConfidence +} + +interface MetricsSideCarFile { + version: 1 + updatedAt: number + metrics: Record +} + +const metricsCache = new Map() +let loaded = false +let loadedPath: string | null = null +let dirty = false +let debounceTimer: NodeJS.Timeout | null = null +let periodicTimer: NodeJS.Timeout | null = null +let metricsFlushHooksRegistered = false + +type MetricsFlushHookDeps = { + flush?: () => Promise + flushSync?: () => void + process?: NodeJS.Process +} + +function getMetricsPath(): string { + return path.join(path.dirname(getStorePath()), METRICS_SIDE_CAR_FILE) +} + +export function getMetricsStorePath(): string { + return getMetricsPath() +} + +function cloneRateLimitWindow(window: RateLimitWindow | undefined): RateLimitWindow | undefined { + if (!window) return undefined + const next: RateLimitWindow = {} + if (typeof window.limit === 'number') next.limit = window.limit + if (typeof window.remaining === 'number') next.remaining = window.remaining + if (typeof window.resetAt === 'number') next.resetAt = window.resetAt + if (typeof window.updatedAt === 'number') next.updatedAt = window.updatedAt + return Object.keys(next).length > 0 ? next : undefined +} + +function cloneRateLimits(rateLimits: AccountRateLimits | undefined): AccountRateLimits | undefined { + if (!hasMeaningfulRateLimits(rateLimits)) return undefined + return { + fiveHour: cloneRateLimitWindow(rateLimits?.fiveHour), + weekly: cloneRateLimitWindow(rateLimits?.weekly) + } +} + +function cloneSnapshot(window: RateLimitSnapshot | undefined): RateLimitSnapshot | undefined { + if (!window) return undefined + const next: RateLimitSnapshot = {} + if (typeof window.limit === 'number') next.limit = window.limit + if (typeof window.remaining === 'number') next.remaining = window.remaining + if (typeof window.resetAt === 'number') next.resetAt = window.resetAt + return Object.keys(next).length > 0 ? next : undefined +} + +function cloneHistoryEntry(entry: RateLimitHistoryEntry): RateLimitHistoryEntry { + return { + at: entry.at, + fiveHour: cloneSnapshot(entry.fiveHour), + weekly: cloneSnapshot(entry.weekly) + } +} + +function cloneMetricsData(data: MetricsData): MetricsData { + return { + ...data, + rateLimits: cloneRateLimits(data.rateLimits), + rateLimitHistory: data.rateLimitHistory?.map(cloneHistoryEntry) + } +} + +function isLimitStatus(value: unknown): value is LimitStatus { + return ( + value === 'idle' || + value === 'queued' || + value === 'running' || + value === 'success' || + value === 'error' || + value === 'stopped' + ) +} + +function isLimitsConfidence(value: unknown): value is LimitsConfidence { + return value === 'fresh' || value === 'stale' || value === 'error' || value === 'unknown' +} + +function sanitizeHistory(value: unknown): RateLimitHistoryEntry[] | undefined { + if (!Array.isArray(value)) return undefined + const entries = value + .map((raw): RateLimitHistoryEntry | null => { + if (!raw || typeof raw !== 'object') return null + const entry = raw as Record + const candidate: RateLimitHistoryEntry = { + at: typeof entry.at === 'number' ? entry.at : Date.now(), + fiveHour: cloneSnapshot(entry.fiveHour as RateLimitSnapshot | undefined), + weekly: cloneSnapshot(entry.weekly as RateLimitSnapshot | undefined) + } + return hasMeaningfulRateLimits({ fiveHour: candidate.fiveHour, weekly: candidate.weekly }) ? candidate : null + }) + .filter((entry): entry is RateLimitHistoryEntry => entry !== null) + return entries.length > 0 ? entries.slice(Math.max(0, entries.length - 160)) : undefined +} + +function sanitizeMetricsData(value: unknown): MetricsData | null { + if (!value || typeof value !== 'object') return null + const raw = value as Record + const next: MetricsData = {} + + if (typeof raw.lastRefresh === 'string') next.lastRefresh = raw.lastRefresh + if (typeof raw.lastSeenAt === 'number') next.lastSeenAt = raw.lastSeenAt + if (typeof raw.lastActiveUntil === 'number') next.lastActiveUntil = raw.lastActiveUntil + if (typeof raw.lastUsed === 'number') next.lastUsed = raw.lastUsed + if (typeof raw.usageCount === 'number') next.usageCount = raw.usageCount + const rateLimits = cloneRateLimits(raw.rateLimits as AccountRateLimits | undefined) + if (rateLimits) next.rateLimits = rateLimits + const history = sanitizeHistory(raw.rateLimitHistory) + if (history) next.rateLimitHistory = history + if (isLimitStatus(raw.limitStatus)) next.limitStatus = raw.limitStatus + if (typeof raw.limitError === 'string') next.limitError = raw.limitError + if (typeof raw.lastLimitProbeAt === 'number') next.lastLimitProbeAt = raw.lastLimitProbeAt + if (typeof raw.lastLimitErrorAt === 'number') next.lastLimitErrorAt = raw.lastLimitErrorAt + if (isLimitsConfidence(raw.limitsConfidence)) next.limitsConfidence = raw.limitsConfidence + + return Object.keys(next).length > 0 ? next : null +} + +function sanitizeMetricsFile(value: unknown): Record { + if (!value || typeof value !== 'object') return {} + const file = value as Record + const rawMetrics = file.metrics && typeof file.metrics === 'object' + ? file.metrics as Record + : file + const metrics: Record = {} + for (const [alias, raw] of Object.entries(rawMetrics)) { + if (!alias.trim()) continue + const sanitized = sanitizeMetricsData(raw) + if (sanitized) metrics[alias] = sanitized + } + return metrics +} + +export function ensureLoaded(): void { + const file = getMetricsPath() + if (loaded && loadedPath === file) return + loaded = true + loadedPath = file + metricsCache.clear() + + if (!fs.existsSync(file)) return + + try { + const parsed = JSON.parse(fs.readFileSync(file, 'utf8')) + for (const [alias, metrics] of Object.entries(sanitizeMetricsFile(parsed))) { + metricsCache.set(alias, metrics) + } + } catch (err) { + console.warn('[multi-auth] Failed to load account metrics sidecar; starting with empty metrics:', err) + } +} + +export function loadAccountMetricsOnStartup(): Map { + ensureLoaded() + return new Map(Array.from(metricsCache.entries(), ([alias, data]) => [alias, cloneMetricsData(data)])) +} + +function buildSnapshot(window?: { remaining?: number; limit?: number; resetAt?: number }): RateLimitSnapshot | undefined { + if (!window) return undefined + return { + remaining: window.remaining, + limit: window.limit, + resetAt: window.resetAt + } +} + +export function buildHistoryEntry(rateLimits?: AccountRateLimits): RateLimitHistoryEntry | null { + if (!hasMeaningfulRateLimits(rateLimits)) return null + const updatedAtValues = [rateLimits?.fiveHour?.updatedAt, rateLimits?.weekly?.updatedAt].filter( + (value): value is number => typeof value === 'number' + ) + const at = updatedAtValues.length > 0 ? Math.max(...updatedAtValues) : Date.now() + return { + at, + fiveHour: buildSnapshot(rateLimits?.fiveHour), + weekly: buildSnapshot(rateLimits?.weekly) + } +} + +export function appendHistory( + history: RateLimitHistoryEntry[] | undefined, + entry: RateLimitHistoryEntry +): RateLimitHistoryEntry[] { + const next = history ? history.map(cloneHistoryEntry) : [] + const last = next[next.length - 1] + const same = + last && + last.fiveHour?.remaining === entry.fiveHour?.remaining && + last.weekly?.remaining === entry.weekly?.remaining && + last.fiveHour?.resetAt === entry.fiveHour?.resetAt && + last.weekly?.resetAt === entry.weekly?.resetAt + if (!same) { + next.push(cloneHistoryEntry(entry)) + } + if (next.length > 160) { + return next.slice(next.length - 160) + } + return next +} + +function clearDebounceTimer(): void { + if (debounceTimer) { + clearTimeout(debounceTimer) + debounceTimer = null + } +} + +function clearPeriodicTimer(): void { + if (periodicTimer) { + clearTimeout(periodicTimer) + periodicTimer = null + } +} + +function unrefTimer(timer: NodeJS.Timeout): void { + if (typeof timer.unref === 'function') timer.unref() +} + +function scheduleFlush(): void { + clearDebounceTimer() + debounceTimer = setTimeout(() => { + void flush().catch((err) => { + console.warn('[multi-auth] Failed to flush account metrics:', err) + }) + }, METRICS_FLUSH_DEBOUNCE_MS) + unrefTimer(debounceTimer) + + if (!periodicTimer) { + periodicTimer = setTimeout(() => { + void flush().catch((err) => { + console.warn('[multi-auth] Failed to periodic-flush account metrics:', err) + }) + }, METRICS_PERIODIC_FLUSH_MS) + unrefTimer(periodicTimer) + } +} + +function markDirty(): void { + dirty = true + scheduleFlush() +} + +function getWriteMetricsPath(): string { + return loadedPath ?? getMetricsPath() +} + +function ensureStoreDir(file: string): void { + fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }) +} + +function writeMetricsFile(): void { + const file = getWriteMetricsPath() + ensureStoreDir(file) + const payload: MetricsSideCarFile = { + version: METRICS_SIDE_CAR_VERSION, + updatedAt: Date.now(), + metrics: Object.fromEntries( + Array.from(metricsCache.entries()).map(([alias, data]) => [alias, cloneMetricsData(data)]) + ) + } + const tmp = `${file}.tmp-${process.pid}-${Date.now()}` + fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), { encoding: 'utf8', mode: 0o600 }) + try { + fs.renameSync(tmp, file) + } catch (err: any) { + if (err?.code === 'EPERM' || err?.code === 'EEXIST') { + try { + fs.unlinkSync(file) + } catch { + // ignore + } + fs.renameSync(tmp, file) + } else { + try { + fs.unlinkSync(tmp) + } catch { + // ignore + } + throw err + } + } + try { + fs.chmodSync(file, 0o600) + } catch { + // ignore chmod failures on platforms that do not support it + } +} + +export function getMetrics(alias: string): MetricsData | undefined { + ensureLoaded() + const data = metricsCache.get(alias) + return data ? cloneMetricsData(data) : undefined +} + +export function getAllMetrics(): Record { + ensureLoaded() + return Object.fromEntries( + Array.from(metricsCache.entries()).map(([alias, data]) => [alias, cloneMetricsData(data)]) + ) +} + +export function setMetrics(alias: string, partial: MetricsData): MetricsData { + ensureLoaded() + const current = metricsCache.get(alias) ?? {} + const next: MetricsData = { + ...current, + ...partial, + rateLimits: partial.rateLimits !== undefined ? cloneRateLimits(partial.rateLimits) : cloneRateLimits(current.rateLimits), + rateLimitHistory: partial.rateLimitHistory !== undefined + ? partial.rateLimitHistory.map(cloneHistoryEntry).slice(Math.max(0, partial.rateLimitHistory.length - 160)) + : current.rateLimitHistory?.map(cloneHistoryEntry) + } + + if (partial.rateLimits !== undefined) { + const entry = buildHistoryEntry(next.rateLimits) + if (entry) { + next.rateLimitHistory = appendHistory(current.rateLimitHistory, entry) + } + } + + metricsCache.set(alias, next) + markDirty() + return cloneMetricsData(next) +} + +function preferExistingNumber(existing: number | undefined, incoming: number | undefined): number | undefined { + return typeof existing === 'number' ? existing : incoming +} + +function maxNumber(existing: number | undefined, incoming: number | undefined): number | undefined { + if (typeof existing === 'number' && typeof incoming === 'number') return Math.max(existing, incoming) + return preferExistingNumber(existing, incoming) +} + +function preferExistingString(existing: string | undefined, incoming: string | undefined): string | undefined { + return typeof existing === 'string' ? existing : incoming +} + +function mergeHistoryForMigration( + existing: RateLimitHistoryEntry[] | undefined, + incoming: RateLimitHistoryEntry[] | undefined, + rateLimits: AccountRateLimits | undefined +): RateLimitHistoryEntry[] | undefined { + const entries = [...(existing ?? []), ...(incoming ?? [])] + .map(cloneHistoryEntry) + .sort((a, b) => a.at - b.at) + let merged: RateLimitHistoryEntry[] | undefined + for (const entry of entries) { + merged = appendHistory(merged, entry) + } + + const derived = buildHistoryEntry(rateLimits) + if (derived) { + merged = appendHistory(merged, derived) + } + return merged && merged.length > 0 ? merged : undefined +} + +function setIfDefined(target: MetricsData, key: K, value: MetricsData[K]): void { + if (value !== undefined) target[key] = value +} + +export function mergeMetricsForMigration(alias: string, incoming: MetricsData): MetricsData { + ensureLoaded() + const current = metricsCache.get(alias) ?? {} + const rateLimits = current.rateLimits ?? incoming.rateLimits + const next: MetricsData = {} + + setIfDefined(next, 'lastRefresh', preferExistingString(current.lastRefresh, incoming.lastRefresh)) + setIfDefined(next, 'lastSeenAt', maxNumber(current.lastSeenAt, incoming.lastSeenAt)) + setIfDefined(next, 'lastActiveUntil', maxNumber(current.lastActiveUntil, incoming.lastActiveUntil)) + setIfDefined(next, 'lastUsed', maxNumber(current.lastUsed, incoming.lastUsed)) + setIfDefined(next, 'usageCount', preferExistingNumber(current.usageCount, incoming.usageCount)) + setIfDefined(next, 'rateLimits', rateLimits ? cloneRateLimits(rateLimits) : undefined) + setIfDefined( + next, + 'rateLimitHistory', + mergeHistoryForMigration(current.rateLimitHistory, incoming.rateLimitHistory, rateLimits) + ) + setIfDefined(next, 'limitStatus', current.limitStatus ?? incoming.limitStatus) + setIfDefined(next, 'limitError', preferExistingString(current.limitError, incoming.limitError)) + setIfDefined(next, 'lastLimitProbeAt', maxNumber(current.lastLimitProbeAt, incoming.lastLimitProbeAt)) + setIfDefined(next, 'lastLimitErrorAt', maxNumber(current.lastLimitErrorAt, incoming.lastLimitErrorAt)) + setIfDefined(next, 'limitsConfidence', current.limitsConfidence ?? incoming.limitsConfidence) + + if (Object.keys(next).length > 0) { + metricsCache.set(alias, next) + markDirty() + } + return cloneMetricsData(next) +} + +export function updateRateLimits(alias: string, rateLimits: AccountRateLimits): MetricsData { + return setMetrics(alias, { rateLimits }) +} + +export function removeMetrics(alias: string): void { + ensureLoaded() + if (metricsCache.delete(alias)) { + markDirty() + } +} + +export async function flush(): Promise { + flushSync() +} + +export function flushSync(force = false): void { + if (!loaded) { + if (force) ensureLoaded() + else return + } else if (force && !dirty && loadedPath !== getMetricsPath()) { + ensureLoaded() + } + if (!loaded || (!dirty && !force)) return + clearDebounceTimer() + clearPeriodicTimer() + writeMetricsFile() + dirty = false +} + +export function registerMetricsFlushHooks(deps: MetricsFlushHookDeps = {}): void { + if (metricsFlushHooksRegistered) return + metricsFlushHooksRegistered = true + + const targetProcess = deps.process ?? process + const asyncFlush = deps.flush ?? flush + const syncFlush = deps.flushSync ?? (() => flushSync()) + + const runBestEffortFlush = async (): Promise => { + try { + await asyncFlush() + } catch (err) { + void err + console.warn('[multi-auth] Failed to flush account metrics during shutdown') + } + } + + const handleSignal = (signal: NodeJS.Signals, listener: () => void): void => { + let finished = false + const finish = (): void => { + if (finished) return + finished = true + try { + targetProcess.removeListener(signal, listener) + targetProcess.kill(targetProcess.pid, signal) + } catch (err) { + void err + console.warn(`[multi-auth] Failed to re-raise ${signal} after metrics flush`) + } + } + const timeout = setTimeout(finish, METRICS_SIGNAL_FLUSH_TIMEOUT_MS) + unrefTimer(timeout) + void runBestEffortFlush().finally(() => { + clearTimeout(timeout) + finish() + }) + } + const handleSigint = (): void => handleSignal('SIGINT', handleSigint) + const handleSigterm = (): void => handleSignal('SIGTERM', handleSigterm) + + targetProcess.on('beforeExit', () => { + void runBestEffortFlush() + }) + targetProcess.on('SIGINT', handleSigint) + targetProcess.on('SIGTERM', handleSigterm) + targetProcess.on('exit', () => { + try { + syncFlush() + } catch (err) { + void err + console.warn('[multi-auth] Failed to synchronously flush account metrics during exit') + } + }) +} diff --git a/src/refresh-queue.ts b/src/refresh-queue.ts index 1945654..49ce61d 100644 --- a/src/refresh-queue.ts +++ b/src/refresh-queue.ts @@ -1,10 +1,10 @@ import { refreshRateLimitsForAccount, type LimitRefreshResult } from './limits-refresh.js' -import { updateAccount } from './store.js' +import { setMetrics } from './metrics-store.js' import { logInfo, logWarn } from './logger.js' import type { AccountCredentials } from './types.js' // Keep the default below Cloudflare's per-IP burst threshold for -// /backend-api/codex/usage. The env override still allows higher concurrency. +// /backend-api/wham/usage. The env override still allows higher concurrency. const DEFAULT_REFRESH_QUEUE_CONCURRENCY = 5 const MAX_REFRESH_QUEUE_CONCURRENCY = 20 const REFRESH_QUEUE_CONCURRENCY_ENV = 'OPENCODE_MULTI_AUTH_REFRESH_QUEUE_CONCURRENCY' @@ -110,7 +110,7 @@ async function runQueue(targets: AccountCredentials[]): Promise { if (queueState && stopRequested && nextIndexRef.value < targets.length) { for (let idx = nextIndexRef.value; idx < targets.length; idx += 1) { const account = targets[idx] - updateAccount(account.alias, { limitStatus: 'stopped', limitError: 'Stopped by user' }) + setMetrics(account.alias, { limitStatus: 'stopped', limitError: 'Stopped by user' }) queueState.results.push({ alias: account.alias, updated: false, error: 'Stopped' }) queueState.completed += 1 } @@ -161,7 +161,7 @@ export function startRefreshQueue(accounts: AccountCredentials[], alias?: string } for (const account of targets) { - updateAccount(account.alias, { limitStatus: 'queued', limitError: undefined }) + setMetrics(account.alias, { limitStatus: 'queued', limitError: undefined }) } logInfo(`Limit refresh queue started (${targets.length} accounts, concurrency ${concurrency})`) diff --git a/src/rotation.ts b/src/rotation.ts index f877864..36eb07e 100644 --- a/src/rotation.ts +++ b/src/rotation.ts @@ -2,8 +2,15 @@ import { getStoreDiagnostics, loadStore, saveStore, updateAccount } from './stor import { ensureValidToken } from './auth.js' import { decodeJwtPayload, getPlanTypeFromClaims } from './codex-auth.js' import { isForceActive, checkAndAutoClearForce, getForceState, clearForce } from './force-mode.js' -import { getRuntimeSettings, calculateWeightedSelection } from './settings.js' -import type { AccountCredentials, DEFAULT_CONFIG } from './types.js' +import { getRuntimeSettings, getStickySessionRuntimeSettings, calculateWeightedSelection } from './settings.js' +import { getStickyAssignment, removeStickyAssignment, upsertStickyAssignment } from './sticky-sessions.js' +import { getMetrics, setMetrics, type MetricsData } from './metrics-store.js' +import { + type AccountCredentials, + DEFAULT_CONFIG, + type StickySessionSettings +} from './types.js' +import type { ResolvedStickyIdentity } from './types.js' export interface RotationResult { account: AccountCredentials @@ -17,11 +24,27 @@ export interface RotationResult { export interface AccountSelectionContext { model?: string + sticky?: ResolvedStickyIdentity | null } const HEALTH_HYSTERESIS_MS = 10_000 const RECENT_FAILURE_WINDOW_MS = 60_000 +const ALLOCATOR_ACCOUNT_METRIC_FIELDS = [ + 'lastRefresh', + 'lastSeenAt', + 'lastActiveUntil', + 'lastUsed', + 'usageCount', + 'rateLimits', + 'rateLimitHistory', + 'limitStatus', + 'limitError', + 'lastLimitProbeAt', + 'lastLimitErrorAt', + 'limitsConfidence' +] as const + function shuffled(input: T[]): T[] { const a = [...input] for (let i = a.length - 1; i > 0; i -= 1) { @@ -53,6 +76,58 @@ function isSparkModel(model: string | undefined): boolean { return typeof model === 'string' && model.startsWith('gpt-5.3-codex-spark') } +function getAccountMetrics(alias: string): MetricsData { + return getMetrics(alias) ?? {} +} + +function getMetricUsageCount(alias: string, acc: AccountCredentials | undefined): number { + const metrics = getAccountMetrics(alias) + return typeof metrics.usageCount === 'number' ? metrics.usageCount : acc?.usageCount ?? 0 +} + +function getMetricLastUsed(alias: string, acc: AccountCredentials | undefined): number { + const metrics = getAccountMetrics(alias) + return typeof metrics.lastUsed === 'number' ? metrics.lastUsed : acc?.lastUsed ?? 0 +} + +function withAllocatorMetrics( + alias: string, + acc: AccountCredentials, + metrics: MetricsData = getAccountMetrics(alias) +): AccountCredentials { + return { + ...acc, + ...metrics, + usageCount: typeof metrics.usageCount === 'number' ? metrics.usageCount : acc.usageCount ?? 0, + lastUsed: typeof metrics.lastUsed === 'number' ? metrics.lastUsed : acc.lastUsed, + lastLimitErrorAt: typeof metrics.lastLimitErrorAt === 'number' ? metrics.lastLimitErrorAt : acc.lastLimitErrorAt + } +} + +function incrementAllocatorUsage(alias: string, now: number): MetricsData { + const current = getAccountMetrics(alias) + return setMetrics(alias, { + usageCount: (current.usageCount ?? 0) + 1, + lastUsed: now, + limitError: undefined + }) +} + +function stripMetricsFromAccount(acc: AccountCredentials): AccountCredentials { + const next = { ...acc } as Record + for (const field of ALLOCATOR_ACCOUNT_METRIC_FIELDS) { + delete next[field] + } + return next as unknown as AccountCredentials +} + +function saveAllocatorState(store: ReturnType): void { + store.accounts = Object.fromEntries( + Object.entries(store.accounts).map(([alias, account]) => [alias, stripMetricsFromAccount(account)]) + ) as Record + saveStore(store) +} + function getPreferredPools( store: ReturnType, availableAliases: string[], @@ -89,7 +164,10 @@ interface AccountHealth { priority: number } -function evaluateAccountHealth(acc: AccountCredentials, now: number): AccountHealth { +type StickyFailureDisposition = 'temporary' | 'permanent' + +function evaluateAccountHealth(alias: string, acc: AccountCredentials, now: number): AccountHealth { + const metrics = getAccountMetrics(alias) const wasRateLimited: boolean = !!(acc.rateLimitedUntil && acc.rateLimitedUntil > now - HEALTH_HYSTERESIS_MS) const wasModelUnsupported: boolean = !!(acc.modelUnsupportedUntil && acc.modelUnsupportedUntil > now - HEALTH_HYSTERESIS_MS) const wasWorkspaceDeactivated: boolean = !!(acc.workspaceDeactivatedUntil && acc.workspaceDeactivatedUntil > now - HEALTH_HYSTERESIS_MS) @@ -107,7 +185,7 @@ function evaluateAccountHealth(acc: AccountCredentials, now: number): AccountHea const isInProbation: boolean = !currentlyBlocked && (wasRateLimited || wasModelUnsupported || wasWorkspaceDeactivated) let recentFailures = 0 - if (acc.lastLimitErrorAt && acc.lastLimitErrorAt > now - RECENT_FAILURE_WINDOW_MS) { + if (metrics.lastLimitErrorAt && metrics.lastLimitErrorAt > now - RECENT_FAILURE_WINDOW_MS) { recentFailures++ } if (acc.authInvalidatedAt && acc.authInvalidatedAt > now - RECENT_FAILURE_WINDOW_MS) { @@ -117,7 +195,7 @@ function evaluateAccountHealth(acc: AccountCredentials, now: number): AccountHea let priority = 100 if (isInProbation) priority -= 30 if (recentFailures > 0) priority -= recentFailures * 10 - if (acc.usageCount === 0) priority -= 5 + if ((metrics.usageCount ?? acc.usageCount) === 0) priority -= 5 if (currentlyBlocked) priority = 0 // Phase D: Disabled accounts get lowest priority if (isDisabled) priority = -1 @@ -131,6 +209,41 @@ function evaluateAccountHealth(acc: AccountCredentials, now: number): AccountHea } } +function classifyStickyFailure( + account: AccountCredentials | undefined, + now: number +): StickyFailureDisposition { + if (!account || account.enabled === false || account.authInvalid) { + return 'permanent' + } + + if ( + (account.rateLimitedUntil && account.rateLimitedUntil > now) || + (account.modelUnsupportedUntil && account.modelUnsupportedUntil > now) || + (account.workspaceDeactivatedUntil && account.workspaceDeactivatedUntil > now) + ) { + return 'temporary' + } + + return 'temporary' +} + +async function persistStickySelection( + sticky: ResolvedStickyIdentity | null | undefined, + alias: string, + now: number, + stickySettings: StickySessionSettings +): Promise { + if (!sticky) return + + await upsertStickyAssignment({ + canonicalIdentity: sticky.canonical, + alias, + now, + settings: stickySettings + }) +} + export async function getNextAccount( config: typeof DEFAULT_CONFIG, selection?: AccountSelectionContext @@ -168,24 +281,21 @@ export async function getNextAccount( const forcedAccount = store.accounts[forcedAlias] if (forcedAccount) { - const health = evaluateAccountHealth(forcedAccount, now) + const health = evaluateAccountHealth(forcedAlias, forcedAccount, now) if (health.isHealthy) { const token = await ensureValidToken(forcedAlias) if (token) { - store = updateAccount(forcedAlias, { - usageCount: (forcedAccount.usageCount || 0) + 1, - lastUsed: now, - limitError: undefined - }) + const updatedMetrics = incrementAllocatorUsage(forcedAlias, now) + store = loadStore() store.activeAlias = forcedAlias store.lastRotation = now - saveStore(store) + saveAllocatorState(store) console.log(`[multi-auth] Force mode: using ${forcedAlias}`) return { - account: store.accounts[forcedAlias], + account: withAllocatorMetrics(forcedAlias, store.accounts[forcedAlias], updatedMetrics), token, forceState: { active: true, @@ -211,7 +321,7 @@ export async function getNextAccount( const healthMap = new Map() for (const alias of aliases) { const acc = store.accounts[alias] - healthMap.set(alias, evaluateAccountHealth(acc, now)) + healthMap.set(alias, evaluateAccountHealth(alias, acc, now)) } const availableAliases = aliases.filter(alias => { @@ -219,11 +329,6 @@ export async function getNextAccount( return health?.isHealthy === true }) - if (availableAliases.length === 0) { - console.warn('[multi-auth] No available accounts (rate-limited or invalidated).') - return null - } - const tokenFailureCooldownMs = (() => { const raw = process.env.OPENCODE_MULTI_AUTH_TOKEN_FAILURE_COOLDOWN_MS const parsed = raw ? Number(raw) : NaN @@ -232,7 +337,77 @@ export async function getNextAccount( })() const runtimeSettings = getRuntimeSettings() + const stickySettings = getStickySessionRuntimeSettings() const rotationStrategy = runtimeSettings.settings.rotationStrategy || config.rotationStrategy + const sticky = + runtimeSettings.settings.featureFlags?.stickySessionsEnabled === true ? selection?.sticky ?? null : null + let stickyAliasToExclude: string | null = null + + if (sticky) { + const mappedAlias = ( + await getStickyAssignment({ + stickyHash: sticky.hash, + now, + settings: stickySettings + }) + )?.alias + + if (mappedAlias) { + const mappedAccount = store.accounts[mappedAlias] + const mappedHealth = mappedAccount ? healthMap.get(mappedAlias) : undefined + + if (mappedAccount && mappedHealth?.isHealthy) { + const token = await ensureValidToken(mappedAlias) + if (token) { + const updatedMetrics = incrementAllocatorUsage(mappedAlias, now) + store = loadStore() + if (store.accounts[mappedAlias]) { + store.activeAlias = mappedAlias + store.lastRotation = now + saveAllocatorState(store) + await persistStickySelection(sticky, mappedAlias, now, stickySettings) + + const currentForceState = getForceState() + return { + account: withAllocatorMetrics(mappedAlias, store.accounts[mappedAlias], updatedMetrics), + token, + forceState: { + active: isForceActive(), + alias: currentForceState.forcedAlias, + remainingMs: currentForceState.forcedUntil ? currentForceState.forcedUntil - now : 0 + } + } + } + } + + store = loadStore() + } + + stickyAliasToExclude = mappedAlias + const replacementAliases = availableAliases.filter((alias) => alias !== mappedAlias) + + if (replacementAliases.length === 0) { + if (classifyStickyFailure(store.accounts[mappedAlias], now) === 'permanent') { + await removeStickyAssignment({ + stickyHash: sticky.hash, + now, + settings: stickySettings + }) + } + console.warn('[multi-auth] No available accounts (rate-limited or invalidated).') + return null + } + } + } + + const selectionPool = stickyAliasToExclude + ? availableAliases.filter((alias) => alias !== stickyAliasToExclude) + : availableAliases + + if (selectionPool.length === 0) { + console.warn('[multi-auth] No available accounts (rate-limited or invalidated).') + return null + } const buildCandidates = (candidateAliases: string[]): { aliases: string[]; nextIndex?: (selected: string) => number } => { switch (rotationStrategy) { @@ -246,9 +421,9 @@ export async function getNextAccount( const priorityDiff = (healthB?.priority || 0) - (healthA?.priority || 0) if (priorityDiff !== 0) return priorityDiff - const usageDiff = (aa?.usageCount || 0) - (bb?.usageCount || 0) + const usageDiff = getMetricUsageCount(a, aa) - getMetricUsageCount(b, bb) if (usageDiff !== 0) return usageDiff - const lastDiff = (aa?.lastUsed || 0) - (bb?.lastUsed || 0) + const lastDiff = getMetricLastUsed(a, aa) - getMetricLastUsed(b, bb) if (lastDiff !== 0) return lastDiff return a.localeCompare(b) }) @@ -328,7 +503,7 @@ export async function getNextAccount( } } - const { primaryAliases, fallbackAliases } = getPreferredPools(store, availableAliases, selection) + const { primaryAliases, fallbackAliases } = getPreferredPools(store, selectionPool, selection) const primary = buildCandidates(primaryAliases) const fallback = fallbackAliases.length > 0 ? buildCandidates(fallbackAliases) : { aliases: [] as string[] } const candidates = [...primary.aliases, ...fallback.aliases] @@ -336,19 +511,23 @@ export async function getNextAccount( for (const candidate of candidates) { const token = await ensureValidToken(candidate) if (!token) { - store = updateAccount(candidate, { - rateLimitedUntil: now + tokenFailureCooldownMs, + setMetrics(candidate, { limitError: '[multi-auth] Token unavailable (refresh failed?)', lastLimitErrorAt: now }) + store = loadStore() + if (store.accounts[candidate]) { + store.accounts[candidate] = { + ...store.accounts[candidate], + rateLimitedUntil: now + tokenFailureCooldownMs + } + saveAllocatorState(store) + } continue } - store = updateAccount(candidate, { - usageCount: (store.accounts[candidate]?.usageCount || 0) + 1, - lastUsed: now, - limitError: undefined - }) + const updatedMetrics = incrementAllocatorUsage(candidate, now) + store = loadStore() store.activeAlias = candidate store.lastRotation = now @@ -356,11 +535,12 @@ export async function getNextAccount( if (nextIndex) { store.rotationIndex = nextIndex(candidate) } - saveStore(store) + saveAllocatorState(store) + await persistStickySelection(sticky, candidate, now, stickySettings) const currentForceState = getForceState() return { - account: store.accounts[candidate], + account: withAllocatorMetrics(candidate, store.accounts[candidate], updatedMetrics), token, forceState: { active: isForceActive(), @@ -370,6 +550,18 @@ export async function getNextAccount( } } + if ( + sticky && + stickyAliasToExclude && + classifyStickyFailure(loadStore().accounts[stickyAliasToExclude], now) === 'permanent' + ) { + await removeStickyAssignment({ + stickyHash: sticky.hash, + now, + settings: stickySettings + }) + } + console.error('[multi-auth] No available accounts (token refresh failed on all candidates).') return null } diff --git a/src/settings.ts b/src/settings.ts index b9987f5..0438e4a 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,10 +1,18 @@ import { loadStore, saveStore, updateAccount } from './store.js' +import { getMetrics } from './metrics-store.js' import { logInfo, logError } from './logger.js' import { + ALL_STICKY_IDENTITY_SOURCES, + DEFAULT_FEATURE_FLAGS, DEFAULT_ROTATION_SETTINGS, + DEFAULT_STICKY_SESSION_SETTINGS, WEIGHTED_PRESETS, validateSettings, type RotationSettings, + type StickyIdentitySource, + type StickySessionAdminConfig, + type StickySessionSettings, + type PersistedStickySessionSettings, type WeightPreset, type SettingsValidationError } from './types.js' @@ -17,6 +25,105 @@ export interface SettingsResult { errors?: SettingsValidationError[] } +export interface StickySessionConfigResult { + config: StickySessionAdminConfig + source: 'default' | 'persisted' + errors?: SettingsValidationError[] +} + +function getStoredStickySessionSettings(): PersistedStickySessionSettings | undefined { + const store = loadStore() + const sticky = store.stickySessions + if (!sticky) return undefined + + return { + ...DEFAULT_STICKY_SESSION_SETTINGS, + ...sticky, + identitySources: Array.isArray(sticky.identitySources) + ? sticky.identitySources + : DEFAULT_STICKY_SESSION_SETTINGS.identitySources + } +} + +function validateStickyIdentitySources(value: unknown): value is StickyIdentitySource[] { + return ( + Array.isArray(value) && + value.every((source) => typeof source === 'string' && ALL_STICKY_IDENTITY_SOURCES.includes(source as StickyIdentitySource)) + ) +} + +export function validateStickySessionConfig(config: StickySessionAdminConfig): SettingsValidationError[] { + const errors: SettingsValidationError[] = [] + + if (typeof config.enabled !== 'boolean') { + errors.push({ + field: 'enabled', + message: 'Enabled must be a boolean', + constraint: 'typeof enabled === boolean' + }) + } + + if (!validateStickyIdentitySources(config.identitySources)) { + errors.push({ + field: 'identitySources', + message: 'Identity sources must be a non-empty array of supported sticky identity sources', + constraint: `identitySources ∈ ${ALL_STICKY_IDENTITY_SOURCES.join(', ')}` + }) + } else { + if (config.identitySources.length === 0) { + errors.push({ + field: 'identitySources', + message: 'At least one identity source is required', + constraint: 'identitySources.length > 0' + }) + } + + if (new Set(config.identitySources).size !== config.identitySources.length) { + errors.push({ + field: 'identitySources', + message: 'Identity sources must not contain duplicates', + constraint: 'identitySources are unique' + }) + } + } + + if (typeof config.allowPromptCacheKey !== 'boolean') { + errors.push({ + field: 'allowPromptCacheKey', + message: 'allowPromptCacheKey must be a boolean', + constraint: 'typeof allowPromptCacheKey === boolean' + }) + } + + if ( + Array.isArray(config.identitySources) && + config.identitySources.includes('body:prompt_cache_key') && + config.allowPromptCacheKey !== true + ) { + errors.push({ + field: 'identitySources', + message: 'body:prompt_cache_key requires allowPromptCacheKey to be true', + constraint: 'allowPromptCacheKey === true when body:prompt_cache_key is configured' + }) + } + + for (const [field, value] of [ + ['ttlMs', config.ttlMs], + ['maxEntries', config.maxEntries], + ['maxFileBytes', config.maxFileBytes] + ] as const) { + if (!Number.isInteger(value) || value <= 0) { + errors.push({ + field, + message: `${field} must be a positive integer`, + constraint: `${field} ∈ positive integers` + }) + } + } + + return errors +} + function resolveSettings(includeEnvOverrides: boolean): SettingsResult { const store = loadStore() @@ -26,9 +133,17 @@ function resolveSettings(includeEnvOverrides: boolean): SettingsResult { // Layer 1: Persisted settings from store if (store.settings) { + const persistedFeatureFlags = store.settings.featureFlags settings = { ...settings, - ...store.settings + ...store.settings, + featureFlags: persistedFeatureFlags + ? { + ...DEFAULT_FEATURE_FLAGS, + ...settings.featureFlags, + ...persistedFeatureFlags + } + : settings.featureFlags } source = 'persisted' } @@ -64,11 +179,23 @@ function resolveSettings(includeEnvOverrides: boolean): SettingsResult { if (envAntigravity) { const enabled = envAntigravity.toLowerCase() === 'true' || envAntigravity === '1' settings.featureFlags = { + ...DEFAULT_FEATURE_FLAGS, ...(settings.featureFlags || {}), antigravityEnabled: enabled } source = 'env' } + + const envStickySessions = process.env.OPENCODE_MULTI_AUTH_STICKY_SESSIONS_ENABLED + if (envStickySessions) { + const enabled = envStickySessions.toLowerCase() === 'true' || envStickySessions === '1' + settings.featureFlags = { + ...DEFAULT_FEATURE_FLAGS, + ...(settings.featureFlags || {}), + stickySessionsEnabled: enabled + } + source = 'env' + } } // Validate final settings @@ -91,9 +218,87 @@ export function getRuntimeSettings(): SettingsResult { return resolveSettings(false) } +export function getStickySessionRuntimeSettings(): StickySessionSettings { + const stored = getStoredStickySessionSettings() + return { + ...DEFAULT_STICKY_SESSION_SETTINGS, + ...stored, + identitySources: stored?.identitySources || DEFAULT_STICKY_SESSION_SETTINGS.identitySources + } +} + +export function getStickySessionConfig(): StickySessionConfigResult { + const sticky = getStoredStickySessionSettings() + const enabled = getRuntimeSettings().settings.featureFlags?.stickySessionsEnabled === true + const config: StickySessionAdminConfig = { + enabled, + ...DEFAULT_STICKY_SESSION_SETTINGS, + ...sticky, + identitySources: sticky?.identitySources || DEFAULT_STICKY_SESSION_SETTINGS.identitySources + } + const errors = validateStickySessionConfig(config) + + return { + config, + source: sticky ? 'persisted' : 'default', + errors: errors.length > 0 ? errors : undefined + } +} + +export function updateStickySessionConfig( + updates: StickySessionAdminConfig, + actor: string = 'system' +): { success: boolean; config?: StickySessionAdminConfig; errors?: SettingsValidationError[] } { + const now = Date.now() + const config: StickySessionAdminConfig = { + enabled: updates.enabled, + identitySources: updates.identitySources, + allowPromptCacheKey: updates.allowPromptCacheKey, + ttlMs: updates.ttlMs, + maxEntries: updates.maxEntries, + maxFileBytes: updates.maxFileBytes, + updatedAt: now, + updatedBy: actor + } + + const errors = validateStickySessionConfig(config) + if (errors.length > 0) { + logError(`Sticky-session config update failed validation: ${errors.map(e => e.message).join(', ')}`) + return { success: false, errors } + } + + const store = loadStore() + const current = getRuntimeSettings().settings + store.settings = { + ...DEFAULT_ROTATION_SETTINGS, + ...current, + featureFlags: { + ...DEFAULT_FEATURE_FLAGS, + ...(current.featureFlags || {}), + stickySessionsEnabled: config.enabled + }, + updatedAt: now, + updatedBy: actor + } + store.rotationStrategy = store.settings.rotationStrategy + store.stickySessions = { + identitySources: [...config.identitySources], + allowPromptCacheKey: config.allowPromptCacheKey, + ttlMs: config.ttlMs, + maxEntries: config.maxEntries, + maxFileBytes: config.maxFileBytes, + updatedAt: now, + updatedBy: actor + } + saveStore(store) + + logInfo(`Sticky-session config updated by ${actor}: ${JSON.stringify({ ...config, updatedAt: now, updatedBy: actor })}`) + return { success: true, config } +} + // Phase F: Update settings with validation export function updateSettings( - updates: Partial, + updates: Partial & { featureFlags?: Partial> }, actor: string = 'system' ): { success: boolean; settings?: RotationSettings; errors?: SettingsValidationError[] } { const current = getRuntimeSettings() @@ -102,6 +307,11 @@ export function updateSettings( const newSettings: RotationSettings = { ...current.settings, ...updates, + featureFlags: { + ...DEFAULT_FEATURE_FLAGS, + ...(current.settings.featureFlags || {}), + ...(updates.featureFlags || {}) + }, updatedAt: Date.now(), updatedBy: actor } @@ -154,12 +364,17 @@ export function applyPreset( accountWeights[alias] = weight }) } else if (preset === 'conservative') { - // Weights based on limit health + // Weights based on limit health (normalized to percentages) accounts.forEach(alias => { const account = store.accounts[alias] - const fiveHourRemaining = account.rateLimits?.fiveHour?.remaining ?? 50 - const weeklyRemaining = account.rateLimits?.weekly?.remaining ?? 50 - const health = (fiveHourRemaining + weeklyRemaining) / 2 + const rateLimits = getMetrics(alias)?.rateLimits ?? account.rateLimits + const fiveHourLimit = rateLimits?.fiveHour?.limit ?? 100 + const fiveHourRemaining = rateLimits?.fiveHour?.remaining ?? (fiveHourLimit / 2) + const weeklyLimit = rateLimits?.weekly?.limit ?? 1000 + const weeklyRemaining = rateLimits?.weekly?.remaining ?? (weeklyLimit / 2) + const fiveHourPct = fiveHourLimit > 0 ? (fiveHourRemaining / fiveHourLimit) * 100 : 50 + const weeklyPct = weeklyLimit > 0 ? (weeklyRemaining / weeklyLimit) * 100 : 50 + const health = (fiveHourPct + weeklyPct) / 2 accountWeights[alias] = health / 100 }) // Normalize to sum to 1 @@ -173,9 +388,14 @@ export function applyPreset( // Favor accounts with high usage (lower remaining) accounts.forEach(alias => { const account = store.accounts[alias] - const fiveHourRemaining = account.rateLimits?.fiveHour?.remaining ?? 50 - const weeklyRemaining = account.rateLimits?.weekly?.remaining ?? 50 - const health = (fiveHourRemaining + weeklyRemaining) / 2 + const rateLimits = getMetrics(alias)?.rateLimits ?? account.rateLimits + const fiveHourLimit = rateLimits?.fiveHour?.limit ?? 100 + const fiveHourRemaining = rateLimits?.fiveHour?.remaining ?? (fiveHourLimit / 2) + const weeklyLimit = rateLimits?.weekly?.limit ?? 1000 + const weeklyRemaining = rateLimits?.weekly?.remaining ?? (weeklyLimit / 2) + const fiveHourPct = fiveHourLimit > 0 ? (fiveHourRemaining / fiveHourLimit) * 100 : 50 + const weeklyPct = weeklyLimit > 0 ? (weeklyRemaining / weeklyLimit) * 100 : 50 + const health = (fiveHourPct + weeklyPct) / 2 // Inverse: lower health = higher weight accountWeights[alias] = (100 - health) / 100 }) diff --git a/src/sticky-identity.ts b/src/sticky-identity.ts new file mode 100644 index 0000000..b931575 --- /dev/null +++ b/src/sticky-identity.ts @@ -0,0 +1,70 @@ +import { hashStickyIdentity } from './sticky-sessions.js' +import type { ResolvedStickyIdentity, StickyIdentitySource } from './types.js' + +export type ResolveStickyIdentityOptions = { + headers: Headers + body?: { + metadata?: { + session_id?: unknown + conversation_id?: unknown + } + session_id?: unknown + conversation_id?: unknown + prompt_cache_key?: unknown + } + allowPromptCacheKey: boolean + identitySources: StickyIdentitySource[] +} + +function normalizeStickyIdentityValue(value: unknown): string | null { + if (typeof value !== 'string') return null + const normalized = value.trim().toLowerCase() + return normalized.length > 0 ? normalized : null +} + +function resolveStickyIdentitySource( + source: StickyIdentitySource, + value: unknown, + allowPromptCacheKey: boolean +): ResolvedStickyIdentity | null { + if (source === 'body:prompt_cache_key' && !allowPromptCacheKey) return null + + const canonical = normalizeStickyIdentityValue(value) + if (!canonical) return null + + return { + source, + canonical, + hash: hashStickyIdentity(canonical) + } +} + +export function resolveStickyIdentity(options: ResolveStickyIdentityOptions): ResolvedStickyIdentity | null { + const body = options.body + const bodyMetadata = body?.metadata + + for (const source of options.identitySources) { + const value = (() => { + switch (source) { + case 'header:x-session-affinity': + return options.headers.get('x-session-affinity') + case 'header:session-id': + return options.headers.get('session-id') + case 'header:session_id': + return options.headers.get('session_id') + case 'header:conversation_id': + return options.headers.get('conversation_id') + case 'body:metadata.session_id': + return bodyMetadata?.session_id + case 'body:metadata.conversation_id': + return bodyMetadata?.conversation_id + case 'body:prompt_cache_key': + return body?.prompt_cache_key + } + })() + const resolved = resolveStickyIdentitySource(source, value, options.allowPromptCacheKey) + if (resolved) return resolved + } + + return null +} diff --git a/src/sticky-sessions.ts b/src/sticky-sessions.ts new file mode 100644 index 0000000..6b55900 --- /dev/null +++ b/src/sticky-sessions.ts @@ -0,0 +1,402 @@ +import * as crypto from 'node:crypto' +import * as fs from 'node:fs' +import * as path from 'node:path' +import { getStorePath, withWriteLock } from './store.js' +import type { StickySessionSettings as StickySessionPolicy } from './types.js' + +export type StickySessionSettings = StickySessionPolicy + +export type StickySessionEntry = { + alias: string + createdAt: number + lastUsedAt: number +} + +export type StickySessionsFile = { + version: 1 + updatedAt: number + entries: Record +} + +export type StickySessionCleanupResult = { + before: number + after: number + removed: number + prunedAt: number +} + +export type StickySessionStatus = StickySessionCleanupResult & { + path: string + exists: boolean + entries: number + sizeBytes: number + updatedAt: number | null + ttlMs: number + maxEntries: number + maxFileBytes: number +} + +const STICKY_SIDE_CAR_FILE = 'sticky-sessions.json' +const STICKY_SIDE_CAR_VERSION = 1 + +type LoadStickySessionsOptions = { + now: number + settings: StickySessionSettings +} + +type UpsertStickyAssignmentOptions = LoadStickySessionsOptions & { + canonicalIdentity: string + alias: string +} + +type GetStickyAssignmentOptions = LoadStickySessionsOptions & { + stickyHash: string +} + +function createEmptyStickySessions(now: number): StickySessionsFile { + return { + version: STICKY_SIDE_CAR_VERSION, + updatedAt: now, + entries: {} + } +} + +function getStickySessionsPath(): string { + return path.join(path.dirname(getStorePath()), STICKY_SIDE_CAR_FILE) +} + +export function getStickySessionsFilePath(): string { + return getStickySessionsPath() +} + +function ensureStoreDir(): void { + fs.mkdirSync(path.dirname(getStickySessionsPath()), { recursive: true, mode: 0o700 }) +} + +function normalizeStickyIdentity(value: string): string { + return value.trim().toLowerCase() +} + +export function hashStickyIdentity(canonicalIdentity: string): string { + const normalized = normalizeStickyIdentity(canonicalIdentity) + return crypto.createHash('sha256').update(normalized, 'utf8').digest('hex') +} + +function isFiniteTimestamp(value: unknown): value is number { + return typeof value === 'number' && Number.isFinite(value) && value >= 0 +} + +function sanitizeStickyEntry(value: unknown): StickySessionEntry | null { + if (!value || typeof value !== 'object') return null + + const entry = value as Record + if (typeof entry.alias !== 'string' || entry.alias.trim().length === 0) return null + if (!isFiniteTimestamp(entry.createdAt) || !isFiniteTimestamp(entry.lastUsedAt)) return null + if (entry.lastUsedAt < entry.createdAt) return null + + return { + alias: entry.alias, + createdAt: entry.createdAt, + lastUsedAt: entry.lastUsedAt + } +} + +function sanitizeStickySessions(value: unknown, now: number, settings: StickySessionSettings): StickySessionsFile { + const fallback = createEmptyStickySessions(now) + if (!value || typeof value !== 'object') return fallback + + const file = value as Record + if (file.version !== STICKY_SIDE_CAR_VERSION) return fallback + + const entries: Record = {} + const rawEntries = file.entries + if (rawEntries && typeof rawEntries === 'object') { + for (const [key, rawEntry] of Object.entries(rawEntries)) { + const sanitized = sanitizeStickyEntry(rawEntry) + if (sanitized) { + entries[key] = sanitized + } + } + } + + return pruneStickySessions( + { + version: STICKY_SIDE_CAR_VERSION, + updatedAt: isFiniteTimestamp(file.updatedAt) ? file.updatedAt : now, + entries + }, + now, + settings + ) +} + +export function pruneStickySessions( + file: StickySessionsFile, + now: number, + settings: StickySessionSettings +): StickySessionsFile { + const ttlMs = Math.max(0, settings.ttlMs) + const maxEntries = Math.max(0, settings.maxEntries) + const maxFileBytes = Math.max(0, settings.maxFileBytes) + + let entries = Object.entries(file.entries) + .filter(([, entry]) => now - entry.lastUsedAt <= ttlMs) + .sort((a, b) => a[1].lastUsedAt - b[1].lastUsedAt) + + while (entries.length > maxEntries) { + entries.shift() + } + + let next = materializeStickySessions(entries, now) + while (entries.length > 0 && Buffer.byteLength(JSON.stringify(next), 'utf8') > maxFileBytes) { + entries.shift() + next = materializeStickySessions(entries, now) + } + + if (entries.length === 0 && Buffer.byteLength(JSON.stringify(next), 'utf8') > maxFileBytes) { + return createEmptyStickySessions(now) + } + + return next +} + +function materializeStickySessions( + sortedEntries: Array<[string, StickySessionEntry]>, + now: number +): StickySessionsFile { + return { + version: STICKY_SIDE_CAR_VERSION, + updatedAt: now, + entries: Object.fromEntries(sortedEntries) + } +} + +function readStickySessionsFromDisk(now: number, settings: StickySessionSettings): StickySessionsFile { + ensureStoreDir() + const sidecarPath = getStickySessionsPath() + if (!fs.existsSync(sidecarPath)) { + return createEmptyStickySessions(now) + } + + try { + const raw = fs.readFileSync(sidecarPath, 'utf8') + const parsed = JSON.parse(raw) as unknown + return sanitizeStickySessions(parsed, now, settings) + } catch { + return createEmptyStickySessions(now) + } +} + +function readStickySessionsSnapshot(now: number): StickySessionsFile { + ensureStoreDir() + const sidecarPath = getStickySessionsPath() + if (!fs.existsSync(sidecarPath)) { + return createEmptyStickySessions(now) + } + + try { + const raw = fs.readFileSync(sidecarPath, 'utf8') + const parsed = JSON.parse(raw) as Record + if (parsed.version !== STICKY_SIDE_CAR_VERSION) { + return createEmptyStickySessions(now) + } + + const entries: Record = {} + if (parsed.entries && typeof parsed.entries === 'object') { + for (const [key, value] of Object.entries(parsed.entries)) { + const sanitized = sanitizeStickyEntry(value) + if (sanitized) { + entries[key] = sanitized + } + } + } + + return { + version: STICKY_SIDE_CAR_VERSION, + updatedAt: isFiniteTimestamp(parsed.updatedAt) ? parsed.updatedAt : now, + entries + } + } catch { + return createEmptyStickySessions(now) + } +} + +export async function getStickyAssignment( + options: GetStickyAssignmentOptions +): Promise { + return withWriteLock(() => { + const sidecarPath = getStickySessionsPath() + if (!fs.existsSync(sidecarPath)) { + return null + } + + const loaded = readStickySessionsFromDisk(options.now, options.settings) + writeStickySessionsToDisk(loaded) + return loaded.entries[options.stickyHash] ?? null + }) +} + +function writeStickySessionsToDisk(file: StickySessionsFile): void { + ensureStoreDir() + const sidecarPath = getStickySessionsPath() + const json = JSON.stringify(file, null, 2) + const tmpPath = `${sidecarPath}.tmp-${process.pid}-${Date.now()}` + let fd: number | null = null + + try { + fd = fs.openSync(tmpPath, 'w', 0o600) + fs.writeFileSync(fd, json, { encoding: 'utf8' }) + try { + fs.fsyncSync(fd) + } catch { + // best effort + } + } finally { + if (fd !== null) { + try { + fs.closeSync(fd) + } catch { + // ignore + } + } + } + + try { + fs.renameSync(tmpPath, sidecarPath) + } catch (error: any) { + if (error?.code === 'EPERM' || error?.code === 'EEXIST') { + try { + fs.unlinkSync(sidecarPath) + } catch { + // ignore + } + fs.renameSync(tmpPath, sidecarPath) + } else { + try { + fs.unlinkSync(tmpPath) + } catch { + // ignore + } + throw error + } + } + + try { + const dirFd = fs.openSync(path.dirname(sidecarPath), 'r') + try { + fs.fsyncSync(dirFd) + } catch { + // best effort + } + fs.closeSync(dirFd) + } catch { + // ignore + } + + try { + fs.chmodSync(sidecarPath, 0o600) + } catch { + // ignore + } +} + +export async function loadStickySessions(options: LoadStickySessionsOptions): Promise { + return withWriteLock(() => { + const loaded = readStickySessionsFromDisk(options.now, options.settings) + const sidecarPath = getStickySessionsPath() + if (fs.existsSync(sidecarPath)) { + writeStickySessionsToDisk(loaded) + } + return loaded + }) +} + +export async function upsertStickyAssignment( + options: UpsertStickyAssignmentOptions +): Promise { + return withWriteLock(() => { + const normalizedIdentity = normalizeStickyIdentity(options.canonicalIdentity) + if (!normalizedIdentity) { + throw new Error('Sticky canonical identity must not be empty') + } + + const loaded = readStickySessionsFromDisk(options.now, options.settings) + const stickyKey = hashStickyIdentity(normalizedIdentity) + const existing = loaded.entries[stickyKey] + + loaded.entries[stickyKey] = { + alias: options.alias, + createdAt: existing?.createdAt ?? options.now, + lastUsedAt: options.now + } + + const pruned = pruneStickySessions(loaded, options.now, options.settings) + writeStickySessionsToDisk(pruned) + return pruned + }) +} + +export async function removeStickyAssignment( + options: LoadStickySessionsOptions & { stickyHash: string } +): Promise { + return withWriteLock(() => { + const loaded = readStickySessionsSnapshot(options.now) + + if (!(options.stickyHash in loaded.entries)) { + return loaded + } + + delete loaded.entries[options.stickyHash] + const pruned = pruneStickySessions(loaded, options.now, options.settings) + writeStickySessionsToDisk(pruned) + return pruned + }) +} + +export async function cleanupStickySessions(options: LoadStickySessionsOptions): Promise { + return withWriteLock(() => { + const loaded = readStickySessionsSnapshot(options.now) + const before = Object.keys(loaded.entries).length + const pruned = pruneStickySessions(loaded, options.now, options.settings) + writeStickySessionsToDisk(pruned) + + const after = Object.keys(pruned.entries).length + return { + before, + after, + removed: before - after, + prunedAt: options.now + } + }) +} + +export async function getStickySessionsStatus(options: LoadStickySessionsOptions): Promise { + return withWriteLock(() => { + const sidecarPath = getStickySessionsPath() + const beforeSnapshot = readStickySessionsSnapshot(options.now) + const before = Object.keys(beforeSnapshot.entries).length + const loaded = readStickySessionsFromDisk(options.now, options.settings) + const after = Object.keys(loaded.entries).length + + if (fs.existsSync(sidecarPath)) { + writeStickySessionsToDisk(loaded) + } + + const exists = fs.existsSync(sidecarPath) + const sizeBytes = exists ? fs.statSync(sidecarPath).size : 0 + return { + path: sidecarPath, + exists, + entries: after, + sizeBytes, + updatedAt: exists ? loaded.updatedAt : null, + ttlMs: options.settings.ttlMs, + maxEntries: options.settings.maxEntries, + maxFileBytes: options.settings.maxFileBytes, + before, + after, + removed: before - after, + prunedAt: options.now + } + }) +} diff --git a/src/store.ts b/src/store.ts index 3c3a623..cf18a83 100644 --- a/src/store.ts +++ b/src/store.ts @@ -3,6 +3,15 @@ import * as path from 'path' import * as os from 'os' import * as crypto from 'node:crypto' import { hasMeaningfulRateLimits } from './rate-limits.js' +import { + flushSync as flushMetricsSync, + getMetrics, + getMetricsStorePath, + mergeMetricsForMigration, + removeMetrics, + setMetrics +} from './metrics-store.js' +import type { MetricsData } from './metrics-store.js' import type { AccountStore, AccountCredentials, @@ -28,7 +37,24 @@ function getStoreFile(): string { } const STORE_ENV_PASSPHRASE = 'CODEX_SOFT_STORE_PASSPHRASE' -const CURRENT_STORE_VERSION = 2 +const CURRENT_STORE_VERSION = 3 + +const ACCOUNT_METRIC_FIELDS = [ + 'lastRefresh', + 'lastSeenAt', + 'lastActiveUntil', + 'lastUsed', + 'usageCount', + 'rateLimits', + 'rateLimitHistory', + 'limitStatus', + 'limitError', + 'lastLimitProbeAt', + 'lastLimitErrorAt', + 'limitsConfidence' +] as const + +const ACCOUNT_METRIC_FIELD_SET = new Set(ACCOUNT_METRIC_FIELDS) type EncryptedStoreFile = { encrypted: true @@ -59,13 +85,18 @@ type StoreFileV2 = StoreFileV1 & { } } -type AnyStoreFile = StoreFileV1 | StoreFileV2 +type StoreFileV3 = Omit & { + version: 3 +} + +type AnyStoreFile = StoreFileV1 | StoreFileV2 | StoreFileV3 let storeLocked = false let lastStoreError: string | null = null let lastStoreEncrypted = false let writeLock = false let writeLockQueue: Array<() => void> = [] +let storeSavedDuringMigration = false function ensureDir(): void { const dir = getStoreDir() @@ -217,7 +248,8 @@ function validateStore(data: any): AccountStore | null { forcedBy: data.forcedBy ?? null, // Phase F: Preserve rotation strategy and settings rotationStrategy: data.rotationStrategy ?? 'round-robin', - settings: data.settings ?? undefined + settings: data.settings ?? undefined, + stickySessions: data.stickySessions ?? undefined } } @@ -237,6 +269,149 @@ function migrateV1toV2(data: StoreFileV1): StoreFileV2 { } } +function isLimitStatus(value: unknown): value is MetricsData['limitStatus'] { + return ( + value === 'idle' || + value === 'queued' || + value === 'running' || + value === 'success' || + value === 'error' || + value === 'stopped' + ) +} + +function isLimitsConfidence(value: unknown): value is MetricsData['limitsConfidence'] { + return value === 'fresh' || value === 'stale' || value === 'error' || value === 'unknown' +} + +function extractAccountMetrics(acc: any): MetricsData { + const metrics: MetricsData = {} + if (!acc || typeof acc !== 'object') return metrics + + if (typeof acc.lastRefresh === 'string') metrics.lastRefresh = acc.lastRefresh + if (typeof acc.lastSeenAt === 'number') metrics.lastSeenAt = acc.lastSeenAt + if (typeof acc.lastActiveUntil === 'number') metrics.lastActiveUntil = acc.lastActiveUntil + if (typeof acc.lastUsed === 'number') metrics.lastUsed = acc.lastUsed + if (typeof acc.usageCount === 'number') metrics.usageCount = acc.usageCount + if (hasMeaningfulRateLimits(acc.rateLimits)) metrics.rateLimits = acc.rateLimits + if (Array.isArray(acc.rateLimitHistory)) { + const history = acc.rateLimitHistory.filter((entry: any) => + hasMeaningfulRateLimits({ fiveHour: entry?.fiveHour, weekly: entry?.weekly }) + ) + if (history.length > 0) metrics.rateLimitHistory = history + } + if (isLimitStatus(acc.limitStatus)) metrics.limitStatus = acc.limitStatus + if (typeof acc.limitError === 'string') metrics.limitError = acc.limitError + if (typeof acc.lastLimitProbeAt === 'number') metrics.lastLimitProbeAt = acc.lastLimitProbeAt + if (typeof acc.lastLimitErrorAt === 'number') metrics.lastLimitErrorAt = acc.lastLimitErrorAt + if (isLimitsConfidence(acc.limitsConfidence)) metrics.limitsConfidence = acc.limitsConfidence + + return metrics +} + +function hasMetrics(metrics: MetricsData): boolean { + return Object.keys(metrics).some((key) => metrics[key as keyof MetricsData] !== undefined) +} + +function stripAccountMetrics(acc: any): any { + if (!acc || typeof acc !== 'object') return acc + const next = { ...acc } + for (const field of ACCOUNT_METRIC_FIELDS) { + delete next[field] + } + return next +} + +function pickAccountMetricUpdates(acc: Partial): MetricsData { + const metrics: MetricsData = {} + for (const field of ACCOUNT_METRIC_FIELDS) { + if (Object.prototype.hasOwnProperty.call(acc, field)) { + ;(metrics as any)[field] = acc[field as keyof AccountCredentials] + } + } + return metrics +} + +function hasAccountMetricUpdates(acc: Partial): boolean { + return ACCOUNT_METRIC_FIELDS.some((field) => Object.prototype.hasOwnProperty.call(acc, field)) +} + +function hasAccountStateUpdates(acc: Partial): boolean { + return Object.keys(acc).some((field) => !ACCOUNT_METRIC_FIELD_SET.has(field)) +} + +function stripStoreMetrics(store: AccountStore): AccountStore { + const accounts = Object.fromEntries( + Object.entries(store.accounts).map(([alias, account]) => [alias, stripAccountMetrics(account)]) + ) as Record + return { ...store, version: 3, accounts } +} + +function writeMetricsSidecarFirst(data: any): boolean { + const rawAccounts = data.accounts && typeof data.accounts === 'object' + ? data.accounts as Record + : {} + for (const [alias, account] of Object.entries(rawAccounts)) { + const metrics = extractAccountMetrics(account) + if (hasMetrics(metrics)) { + mergeMetricsForMigration(alias, metrics) + } + } + + try { + flushMetricsSync(true) + return true + } catch (err) { + console.warn('[multi-auth] Deferred account store v3 migration because account metrics sidecar could not be written:', err) + return false + } +} + +function persistStateOnlyV3(data: any): AccountStore | null { + const rawAccounts = data.accounts && typeof data.accounts === 'object' + ? data.accounts as Record + : {} + const strippedRaw = Object.fromEntries( + Object.entries(rawAccounts).map(([alias, account]) => [alias, stripAccountMetrics(account)]) + ) + const validated = validateStore({ ...data, version: 3, accounts: strippedRaw }) + if (!validated) return null + const stateOnly = stripStoreMetrics(validated) + // saveStore keeps the credentials file crash-safe via atomic rename plus .bak/.lkg recovery cues. + saveStore(stateOnly) + storeSavedDuringMigration = true + return stateOnly +} + +function migrateV2toV3(data: StoreFileV2): AccountStore | null { + if (!writeMetricsSidecarFirst(data)) { + return validateStore(data) + } + const migrated = persistStateOnlyV3(data) + if (migrated) { + console.log('[multi-auth] Migrated store from v2 to v3') + } + // Keep the runtime view compatible until later phases route all readers through the metrics cache. + return validateStore({ ...data, version: 3 }) ?? migrated +} + +function ensureV3MetricsSidecar(data: any): AccountStore | null { + const rawAccounts = data.accounts && typeof data.accounts === 'object' + ? data.accounts as Record + : {} + const sidecarMissing = !fs.existsSync(getMetricsStorePath()) + + if (!sidecarMissing) { + return validateStore(data) + } + + if (!writeMetricsSidecarFirst(data)) { + return validateStore(data) + } + + return validateStore(data) +} + function migrateStore(data: any): AccountStore | null { if (!data || typeof data !== 'object') return null @@ -252,6 +427,14 @@ function migrateStore(data: any): AccountStore | null { migrated = migrateV1toV2(data as StoreFileV1) console.log('[multi-auth] Migrated store from v1 to v2') } + + if (version < 3) { + return migrateV2toV3(migrated as StoreFileV2) + } + + if (version === 3) { + return ensureV3MetricsSidecar(migrated) + } return validateStore(migrated) } @@ -368,9 +551,11 @@ export function loadStore(): AccountStore { } try { const decrypted = decryptStore(parsed, passphrase) - const validated = validateStore(decrypted) + const validated = migrateStore(decrypted) if (validated) { - saveLastKnownGood(validated) + const savedDuringMigration = storeSavedDuringMigration + storeSavedDuringMigration = false + if (!savedDuringMigration) saveLastKnownGood(validated) return validated } storeLocked = true @@ -391,7 +576,9 @@ export function loadStore(): AccountStore { const migrated = migrateStore(parsed) if (migrated) { - saveLastKnownGood(migrated) + const savedDuringMigration = storeSavedDuringMigration + storeSavedDuringMigration = false + if (!savedDuringMigration) saveLastKnownGood(migrated) return migrated } @@ -429,7 +616,19 @@ export function saveStore(store: AccountStore): void { const file = getStoreFile() const passphrase = getPassphrase() - const payload = passphrase ? encryptStore(store, passphrase) : store + if (!fs.existsSync(getMetricsStorePath())) { + for (const [alias, account] of Object.entries(store.accounts)) { + const metrics = extractAccountMetrics(account) + // validateAccount supplies usageCount: 0 as a compatibility default for state-only + // records; do not let unrelated state saves overwrite richer cached telemetry with it. + if (metrics.usageCount === 0) delete metrics.usageCount + if (hasMetrics(metrics)) { + setMetrics(alias, metrics) + } + } + } + const stateOnlyStore = stripStoreMetrics(store) + const payload = passphrase ? encryptStore(stateOnlyStore, passphrase) : stateOnlyStore const json = JSON.stringify(payload, null, 2) try { @@ -500,7 +699,27 @@ export function saveStore(store: AccountStore): void { // ignore } - saveLastKnownGood(store) + saveLastKnownGood(stateOnlyStore) +} + +function mergeAccountMetrics(account: AccountCredentials): AccountCredentials { + const metrics = getMetrics(account.alias) + if (!metrics) return account + return { + ...account, + ...metrics, + usageCount: metrics.usageCount ?? account.usageCount ?? 0 + } +} + +export function loadStoreWithMetrics(): AccountStore { + const store = loadStore() + return { + ...store, + accounts: Object.fromEntries( + Object.entries(store.accounts).map(([alias, account]) => [alias, mergeAccountMetrics(account)]) + ) as Record + } } export async function withWriteLock(fn: () => T): Promise { @@ -531,13 +750,19 @@ export function getStoreDiagnostics(): { export function addAccount(alias: string, creds: Omit): AccountStore { const store = loadStore() - const entry = buildHistoryEntry(creds.rateLimits) - store.accounts[alias] = { - ...creds, + const existing = store.accounts[alias] + if (hasAccountMetricUpdates(creds)) { + setMetrics(alias, pickAccountMetricUpdates(creds)) + } + const stateCreds = stripAccountMetrics(creds) as Omit + const next: AccountCredentials = { + ...(existing ?? {}), + ...stateCreds, alias, - usageCount: 0, - rateLimitHistory: entry ? [entry] : creds.rateLimitHistory + usageCount: existing?.usageCount ?? 0 } + + store.accounts[alias] = next if (!store.activeAlias) { store.activeAlias = alias } @@ -548,6 +773,7 @@ export function addAccount(alias: string, creds: Omit): AccountStore { const store = loadStore() if (store.accounts[alias]) { - const current = store.accounts[alias] - const next = { ...current, ...updates } - if (updates.rateLimits || next.rateLimits) { - const entry = buildHistoryEntry(next.rateLimits) - if (entry) { - next.rateLimitHistory = appendHistory(current.rateLimitHistory, entry) - } + if (hasAccountMetricUpdates(updates)) { + setMetrics(alias, pickAccountMetricUpdates(updates)) + } + + const stateUpdates = stripAccountMetrics(updates) as Partial + if (hasAccountStateUpdates(updates)) { + const current = store.accounts[alias] + const next = { ...current, ...stateUpdates } + store.accounts[alias] = next + saveStore(store) } - store.accounts[alias] = next - saveStore(store) } return store } @@ -582,18 +809,11 @@ export function setActiveAlias(alias: string | null): AccountStore { store.activeAlias = null } else if (store.accounts[alias]) { if (previousAlias && previousAlias !== alias && store.accounts[previousAlias]) { - store.accounts[previousAlias] = { - ...store.accounts[previousAlias], - lastActiveUntil: now - } + setMetrics(previousAlias, { lastActiveUntil: now }) } store.activeAlias = alias - store.accounts[alias] = { - ...store.accounts[alias], - lastSeenAt: now, - lastActiveUntil: undefined - } + setMetrics(alias, { lastSeenAt: now, lastActiveUntil: undefined }) const aliases = Object.keys(store.accounts) const idx = aliases.indexOf(alias) diff --git a/src/types.ts b/src/types.ts index 7d1d83f..2701d03 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,6 +47,24 @@ export interface AccountCredentials { source?: 'opencode' | 'codex' } +export type CodexActiveStatus = 'matched' | 'unknown' | 'missing' | 'error' + +export interface CodexActiveState { + status: CodexActiveStatus + alias: string | null + email?: string + accountId?: string + accountUserId?: string + userId?: string + planType?: string + expiresAt?: number + lastRefresh?: string + hasAccessToken: boolean + hasRefreshToken: boolean + hasIdToken: boolean + error?: string +} + export interface RateLimitWindow { limit?: number remaining?: number @@ -126,6 +144,8 @@ export interface AccountStore { rotationStrategy?: 'round-robin' | 'least-used' | 'random' | 'weighted-round-robin' // Phase F: Settings settings?: RotationSettings + // Phase G: Sticky-session admin config persisted separately from /api/settings + stickySessions?: PersistedStickySessionSettings } // OpenAI model info @@ -201,11 +221,75 @@ export interface RotationSettings { export interface FeatureFlags { // Antigravity integration (default: false) antigravityEnabled: boolean + // Sticky-session routing/persistence gate (default: false) + stickySessionsEnabled?: boolean } // Phase G: Default feature flags export const DEFAULT_FEATURE_FLAGS: FeatureFlags = { - antigravityEnabled: false + antigravityEnabled: false, + stickySessionsEnabled: false +} + +// Phase G: Sticky identity sources used for canonical request matching +export type StickyIdentitySource = + | 'header:x-session-affinity' + | 'header:session-id' + | 'header:session_id' + | 'header:conversation_id' + | 'body:metadata.session_id' + | 'body:metadata.conversation_id' + | 'body:prompt_cache_key' + +export const ALL_STICKY_IDENTITY_SOURCES: StickyIdentitySource[] = [ + 'header:x-session-affinity', + 'header:session-id', + 'header:session_id', + 'header:conversation_id', + 'body:metadata.session_id', + 'body:metadata.conversation_id', + 'body:prompt_cache_key' +] + +// Phase G: Sticky-session policy shape (kept separate from the enablement flag) +export interface StickySessionSettings { + identitySources: StickyIdentitySource[] + allowPromptCacheKey: boolean + ttlMs: number + maxEntries: number + maxFileBytes: number +} + +export interface PersistedStickySessionSettings extends StickySessionSettings { + updatedAt?: number + updatedBy?: string +} + +export interface StickySessionAdminConfig extends PersistedStickySessionSettings { + enabled: boolean +} + +// Phase G: Canonical sticky identity resolved from a request +export interface ResolvedStickyIdentity { + source: StickyIdentitySource + canonical: string + hash: string +} + +// Phase G: Default sticky-session policy constants +export const DEFAULT_STICKY_SESSION_SETTINGS: StickySessionSettings = { + identitySources: [ + 'header:x-session-affinity', + 'header:session-id', + 'header:session_id', + 'header:conversation_id', + 'body:metadata.session_id', + 'body:metadata.conversation_id' + ], + allowPromptCacheKey: false, + ttlMs: 24 * 60 * 60 * 1000, + maxEntries: 1000, + maxFileBytes: 1024 * 1024 } // Phase F: Weighted rotation presets diff --git a/src/usage-limits.ts b/src/usage-limits.ts index 7297fef..9afc13c 100644 --- a/src/usage-limits.ts +++ b/src/usage-limits.ts @@ -83,16 +83,19 @@ function mapWindow(window: UsageWindowSnapshot | null | undefined, now: number): } function pickRateLimitDetails(payload: UsagePayload): UsageRateLimitDetails | null { - if (payload.rate_limit) return payload.rate_limit - const additional = Array.isArray(payload.additional_rate_limits) ? payload.additional_rate_limits : [] const preferred = additional.find((entry) => { const feature = entry.metered_feature?.trim().toLowerCase() const limitName = entry.limit_name?.trim().toLowerCase() - return feature === 'codex' || limitName === 'codex' + return feature === 'codex' || + feature?.startsWith('codex_') || + limitName === 'codex' || + limitName?.includes('codex') }) if (preferred?.rate_limit) return preferred.rate_limit + if (payload.rate_limit) return payload.rate_limit + return additional.find((entry) => entry.rate_limit)?.rate_limit || null } @@ -175,14 +178,9 @@ export async function fetchUsageRateLimitsForAccount( } } - const url = `${getUsageBaseUrl()}/codex/usage` + const url = `${getUsageBaseUrl()}/wham/usage` const headers: Record = { - Authorization: `Bearer ${token}`, - 'User-Agent': 'codex_cli_rs/0.122.0 (linux)', - originator: 'codex_cli_rs' - } - if (account.accountId) { - headers['ChatGPT-Account-Id'] = account.accountId + Authorization: `Bearer ${token}` } const maxAttempts = 3 diff --git a/src/web.ts b/src/web.ts index 3346ff4..b148c62 100644 --- a/src/web.ts +++ b/src/web.ts @@ -7,20 +7,28 @@ import { fileURLToPath, URL } from 'node:url' import { exec, spawn } from 'node:child_process' import { promisify } from 'node:util' import { createAuthorizationFlow, loginAccount, refreshToken } from './auth.js' -import { getCodexAuthPath, getCodexAuthStatus, getCodexAuthSummary, resolveAliasForCurrentAuth, syncCodexAuthFile, writeCodexAuthForAlias } from './codex-auth.js' -import { getStoreStatus, listAccounts, loadStore, removeAccount, updateAccount } from './store.js' +import { getCodexActiveState, getCodexAuthPath, getCodexAuthStatus, getCodexAuthSummary, resolveAliasForCurrentAuth, syncCodexAuthFile, writeCodexAuthForAlias } from './codex-auth.js' +import { getStoreStatus, listAccounts, loadStore, loadStoreWithMetrics, removeAccount, updateAccount } from './store.js' import { getRefreshQueueState, startRefreshQueue, stopRefreshQueue } from './refresh-queue.js' -import { getLogPath, logError, logInfo, readLogTail } from './logger.js' +import { getLogPath, logError, logInfo, logWarn, readLogTail } from './logger.js' import { getForceState, activateForce, clearForce, isForceActive, getRemainingForceTimeMs, formatForceDuration } from './force-mode.js' -import { getSettings, getRuntimeSettings, updateSettings, isFeatureEnabled } from './settings.js' +import { + getSettings, + getRuntimeSettings, + updateSettings, + isFeatureEnabled, + getStickySessionConfig, + getStickySessionRuntimeSettings, + updateStickySessionConfig +} from './settings.js' import { Errors } from './errors.js' -import type { AccountCredentials, RateLimitWindow, LimitsConfidence, RotationSettings, WeightPreset } from './types.js' +import type { AccountCredentials, CodexActiveState, RateLimitWindow, LimitsConfidence, RotationSettings, WeightPreset } from './types.js' +import { cleanupStickySessions, getStickySessionsStatus } from './sticky-sessions.js' +import { registerMetricsFlushHooks } from './metrics-store.js' const DEFAULT_HOST = '127.0.0.1' const DEFAULT_PORT = 3434 const LOCALHOST_HOST_PATTERN = /^(127\.0\.0\.1|::1|localhost)$/i -const SYNC_INTERVAL_MS = 3000 -const SYNC_DEBOUNCE_MS = 600 const ANTIGRAVITY_ACCOUNTS_FILE = path.join(os.homedir(), '.config', 'opencode', 'antigravity-accounts.json') const LOGIN_TIMEOUT_MS = 5 * 60 * 1000 const AUTO_LOGIN_TIMEOUT_MS = 6 * 60 * 1000 @@ -30,6 +38,23 @@ const AUTO_LOGIN_CREDENTIALS_ENV = 'OPENCODE_MULTI_AUTH_AUTO_LOGIN_CREDENTIALS_F const AUTO_LOGIN_PYTHON_ENV = 'OPENCODE_MULTI_AUTH_AUTO_LOGIN_PYTHON' const DEFAULT_AUTO_LOGIN_CREDENTIALS_FILE = path.join(os.homedir(), '.config', 'opencode-multi-auth', 'credentials.json') const DEFAULT_AUTO_LOGIN_VENV_PYTHON = path.join(os.homedir(), '.config', 'opencode-multi-auth', '.venv', 'bin', 'python') +const WEB_DIST_DIR_ENV = 'OPENCODE_MULTI_AUTH_WEB_DIST_DIR' + +const STATIC_CONTENT_TYPES: Record = { + '.css': 'text/css; charset=utf-8', + '.gif': 'image/gif', + '.html': 'text/html; charset=utf-8', + '.ico': 'image/x-icon', + '.jpeg': 'image/jpeg', + '.jpg': 'image/jpeg', + '.js': 'application/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.map': 'application/json; charset=utf-8', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.txt': 'text/plain; charset=utf-8', + '.webp': 'image/webp' +} type AutoLoginMode = 'manual' | 'auto' @@ -89,2239 +114,92 @@ export function isLocalhostHost(host: string): boolean { const execAsync = promisify(exec) -let lastSyncAt = 0 -let lastSyncError: string | null = null -let lastSyncAlias: string | null = null -let syncTimer: NodeJS.Timeout | null = null -let pendingLogin: PendingLoginState | null = null -let lastLoginError: string | null = null -let antigravityQuotaState: AntigravityQuotaState = { status: 'idle', scope: 'active' } -let antigravityQuotaInFlight: Promise | null = null -let autoLoginChild: ReturnType | null = null - -const HTML = ` - - - - - Codex Token Dashboard - - - -
-
-
-
Codex Special Functions
-

Codex Token Dashboard

-
Add a new account from login + password, keep it in the local auto-login config, and watch the whole flow live.
-
- -
-
-
-
-
-
-
-
- - - - -
-
- - -
-
- - -
-
-
-
-
-
-
- - - - -
-
-
- - -
-
-
-
Force Mode
-
Pin rotation to a specific account for 24 hours
-
-
-
- - ? - -
-
- - - ? -
-
-
-
-
-
- - - -
-
-
-
Logs
-
-
- -
-

-      
-
- -
- - -` +let lastSyncAt = 0 +let lastSyncError: string | null = null +let lastSyncAlias: string | null = null +let pendingLogin: PendingLoginState | null = null +let lastLoginError: string | null = null +let antigravityQuotaState: AntigravityQuotaState = { status: 'idle', scope: 'active' } +let antigravityQuotaInFlight: Promise | null = null +let autoLoginChild: ReturnType | null = null function sendJson(res: http.ServerResponse, status: number, payload: unknown): void { const data = JSON.stringify(payload) @@ -2332,6 +210,14 @@ function sendJson(res: http.ServerResponse, status: number, payload: unknown): v res.end(data) } +function getSanitizedErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message.replace(/\s+/g, ' ').trim() + } + + return String(error).replace(/\s+/g, ' ').trim() +} + function scrubAccount(account: AccountCredentials): Omit { const { accessToken, refreshToken, idToken, ...rest } = account return rest @@ -2823,7 +709,19 @@ async function saveAutoLoginAccountAndStart(input: AutoLoginCreateInput): Promis return startAutoLogin(account.email) } -function runSync(): void { +type ManualCodexImportResult = { + ok: true + imported: boolean + alias: string | null + added: boolean + updated: boolean + authEmail?: string + authAccountId?: string + warning?: string + codexActive: CodexActiveState +} + +function runSync(): ManualCodexImportResult { try { const result = syncCodexAuthFile() const authStatus = getCodexAuthStatus() @@ -2836,12 +734,35 @@ function runSync(): void { if (authStatus.error) { logError(authStatus.error) } + const codexActive = getCodexActiveState(loadStore()) + return { + ok: true, + imported: Boolean(result.alias), + alias: result.alias ?? null, + added: result.added, + updated: result.updated, + authEmail: result.authEmail, + authAccountId: result.authAccountId, + warning: result.alias ? undefined : 'No importable Codex auth.json credentials found', + codexActive + } } catch (err) { - lastSyncError = String(err) + const errorMessage = getSanitizedErrorMessage(err) + lastSyncAt = Date.now() + lastSyncError = errorMessage + lastSyncAlias = null logError(`Sync failed: ${lastSyncError}`) + throw err } } +function recordCodexImportError(message: string): void { + lastSyncAt = Date.now() + lastSyncError = message + lastSyncAlias = null + logError(message) +} + type AntigravityAccountView = { index: number alias?: string @@ -3263,22 +1184,6 @@ async function refreshAntigravityQuota(): Promise { return antigravityQuotaInFlight } -function scheduleSync(): void { - if (syncTimer) { - clearTimeout(syncTimer) - } - syncTimer = setTimeout(() => { - runSync() - }, SYNC_DEBOUNCE_MS) -} - -function startAuthWatcher(): void { - const authPath = getCodexAuthPath() - fs.watchFile(authPath, { interval: SYNC_INTERVAL_MS }, () => { - scheduleSync() - }) -} - export function startWebConsole(options?: { port?: number; host?: string }): http.Server { const host = options?.host || DEFAULT_HOST const port = options?.port || DEFAULT_PORT @@ -3288,8 +1193,8 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt throw new Error(`${err.code}: ${err.message}`) } - runSync() - startAuthWatcher() + registerMetricsFlushHooks() + logInfo('Codex auth.json auto-import is disabled; use POST /api/codex/import for manual import.') const server = http.createServer(async (req, res) => { const requestUrl = new URL(req.url || '/', `http://${host}:${port}`) @@ -3297,18 +1202,12 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt try { - if (req.method === 'GET' && path === '/') { - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) - res.end(HTML) - return - } - if (req.method === 'GET' && path === '/api/state') { - runSync() - const store = loadStore() + const store = loadStoreWithMetrics() const rawAccounts = Object.values(store.accounts) const accounts = rawAccounts.map(scrubAccount) - const deviceAlias = resolveAliasForCurrentAuth(store) + const codexActive = getCodexActiveState(store) + const deviceAlias = codexActive.status === 'matched' ? codexActive.alias : null const authSummary = getCodexAuthSummary() const storeStatus = getStoreStatus() // Phase G: Only load antigravity if feature is enabled @@ -3322,6 +1221,7 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt sendJson(res, 200, { authPath: getCodexAuthPath(), deviceAlias, + codexActive, rotationAlias: store.activeAlias, accounts, lastSyncAt, @@ -3360,6 +1260,23 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt return } + if (req.method === 'POST' && path === '/api/codex/import') { + const codexActive = getCodexActiveState() + if (codexActive.status === 'error') { + const message = codexActive.error || 'Failed to parse codex auth.json' + recordCodexImportError(message) + sendJson(res, 422, { ok: false, error: message, code: 'CODEX_AUTH_INVALID' }) + return + } + + try { + sendJson(res, 200, runSync()) + } catch (err) { + sendJson(res, 500, { ok: false, error: getSanitizedErrorMessage(err), code: 'CODEX_AUTH_IMPORT_FAILED' }) + } + return + } + if (req.method === 'POST' && path === '/api/sync') { try { runSync() @@ -3437,14 +1354,44 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt return } + if (req.method === 'POST' && path === '/api/codex/use') { + const body = await readJsonBody(req) + const alias = typeof body.alias === 'string' ? body.alias.trim() : '' + if (!alias) { + sendJson(res, 400, { error: 'Missing alias', code: 'MISSING_ALIAS' }) + return + } + + const store = loadStore() + const account = store.accounts[alias] + if (!account) { + sendJson(res, 404, { error: 'Unknown alias', code: 'ACCOUNT_NOT_FOUND' }) + return + } + if (!account.accessToken || !account.refreshToken) { + sendJson(res, 409, { error: 'Missing token data for alias', code: 'ACCOUNT_NOT_USABLE_IN_CODEX' }) + return + } + + try { + writeCodexAuthForAlias(alias) + sendJson(res, 200, { ok: true, alias, codexActive: getCodexActiveState(loadStore()) }) + } catch (err) { + logError(`Failed to write Codex auth.json for ${alias}: ${getSanitizedErrorMessage(err)}`) + sendJson(res, 500, { error: 'Failed to write Codex auth.json', code: 'CODEX_AUTH_WRITE_FAILED' }) + } + return + } + if (req.method === 'POST' && path === '/api/switch') { const body = await readJsonBody(req) - if (!body.alias) { + const alias = typeof body.alias === 'string' ? body.alias.trim() : '' + if (!alias) { sendJson(res, 400, { error: 'Missing alias' }) return } try { - writeCodexAuthForAlias(body.alias) + writeCodexAuthForAlias(alias) sendJson(res, 200, { ok: true }) } catch (err) { sendJson(res, 400, { error: String(err) }) @@ -3492,6 +1439,7 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt const alias = typeof body.alias === 'string' ? body.alias : undefined const targets = alias ? candidates.filter((acc) => acc.alias === alias) : candidates if (alias && targets.length === 0) { + logWarn(`[multi-auth] Refresh requested for unknown alias: ${alias}`) sendJson(res, 400, { error: 'Unknown alias' }) return } @@ -3500,6 +1448,7 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt const results: Array<{ alias: string; updated: boolean; error?: string }> = [] for (const account of targets) { if (!account.refreshToken) { + logWarn(`[multi-auth] Refresh skipped (no refresh token) for ${account.alias}`) results.push({ alias: account.alias, updated: false, error: 'No refresh token' }) continue } @@ -3513,6 +1462,8 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt try { writeCodexAuthForAlias(account.alias) } catch (err) { + const errorMessage = getSanitizedErrorMessage(err) + logError(`[multi-auth] Refreshed token but failed to update auth.json for ${account.alias}: ${errorMessage}`) results.push({ alias: account.alias, updated: true, error: `Refreshed, but failed to update auth.json: ${err}` }) continue } @@ -3578,7 +1529,7 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt // GET /api/accounts - List all accounts with metadata if (req.method === 'GET' && path === '/api/accounts') { - const store = loadStore() + const store = loadStoreWithMetrics() const accounts = Object.values(store.accounts).map(acc => ({ alias: acc.alias, email: acc.email, @@ -3588,6 +1539,7 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt disableReason: acc.disableReason, usageCount: acc.usageCount, rateLimits: acc.rateLimits, + rateLimitHistory: acc.rateLimitHistory, limitsConfidence: acc.limitsConfidence, limitStatus: acc.limitStatus, limitError: acc.limitError, @@ -3703,6 +1655,11 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt loginAccount(alias, flow, { timeoutMs: LOGIN_TIMEOUT_MS }) .then(() => { logInfo(`Re-auth completed for ${alias} by ${actor}`) + try { + writeCodexAuthForAlias(alias) + } catch (err) { + logError(`Failed to update codex auth for ${alias}: ${getSanitizedErrorMessage(err)}`) + } // Update account metadata updateAccount(alias, { lastRefresh: new Date().toISOString() @@ -3898,7 +1855,82 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt } return } - + + const isStickySessionsEnabled = (): boolean => isFeatureEnabled('stickySessionsEnabled') + const sendStickyFeatureDisabled = (): void => { + sendJson(res, 403, { + error: 'Sticky sessions feature is disabled', + code: 'FEATURE_DISABLED', + feature: 'sticky-sessions' + }) + } + + if (req.method === 'GET' && path === '/api/sticky-sessions/config') { + if (!isStickySessionsEnabled()) { + sendStickyFeatureDisabled() + return + } + + const stickyConfig = getStickySessionConfig() + sendJson(res, 200, stickyConfig.config) + return + } + + if (req.method === 'PUT' && path === '/api/sticky-sessions/config') { + const body = await readJsonBody(req) + const actor = body.actor || 'dashboard' + const currentlyEnabled = isStickySessionsEnabled() + + if (!currentlyEnabled && body.enabled !== false) { + sendStickyFeatureDisabled() + return + } + + const result = updateStickySessionConfig({ + enabled: body.enabled, + identitySources: body.identitySources, + allowPromptCacheKey: body.allowPromptCacheKey, + ttlMs: body.ttlMs, + maxEntries: body.maxEntries, + maxFileBytes: body.maxFileBytes + }, actor) + + if (result.success) { + sendJson(res, 200, result.config) + } else { + sendJson(res, 400, { + error: 'Validation failed', + code: 'VALIDATION_ERROR', + details: result.errors + }) + } + return + } + + if (req.method === 'GET' && path === '/api/sticky-sessions/status') { + if (!isStickySessionsEnabled()) { + sendStickyFeatureDisabled() + return + } + + const stickySettings = getStickySessionRuntimeSettings() + const status = await getStickySessionsStatus({ now: Date.now(), settings: stickySettings }) + sendJson(res, 200, { ok: true, ...status }) + return + } + + if (req.method === 'POST' && path === '/api/sticky-sessions/cleanup') { + if (!isStickySessionsEnabled()) { + sendStickyFeatureDisabled() + return + } + + const stickySettings = getStickySessionRuntimeSettings() + const cleanup = await cleanupStickySessions({ now: Date.now(), settings: stickySettings }) + sendJson(res, 200, { ok: true, ...cleanup }) + return + } + // POST /api/settings/reset - Reset to defaults if (req.method === 'POST' && path === '/api/settings/reset') { const { resetSettings } = await import('./settings.js') @@ -3947,6 +1979,18 @@ export function startWebConsole(options?: { port?: number; host?: string }): htt return } + if (isApiRequestPath(path)) { + res.writeHead(404, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ error: 'Not found' })) + return + } + + if (req.method === 'GET' || req.method === 'HEAD') { + if (await serveSpaRequest(res, path)) { + return + } + } + res.writeHead(404, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ error: 'Not found' })) } catch (err) { diff --git a/tests/fixtures/web-dist/assets/app.js b/tests/fixtures/web-dist/assets/app.js new file mode 100644 index 0000000..aee1765 --- /dev/null +++ b/tests/fixtures/web-dist/assets/app.js @@ -0,0 +1,2 @@ +window.__fixtureDashboardLoaded = true +console.log('fixture-asset') diff --git a/tests/fixtures/web-dist/index.html b/tests/fixtures/web-dist/index.html new file mode 100644 index 0000000..8872386 --- /dev/null +++ b/tests/fixtures/web-dist/index.html @@ -0,0 +1,12 @@ + + + + + + Fixture SPA + + +
fixture-spa-shell
+ + + diff --git a/tests/helpers/dashboard-seed.d.ts b/tests/helpers/dashboard-seed.d.ts new file mode 100644 index 0000000..3568fd7 --- /dev/null +++ b/tests/helpers/dashboard-seed.d.ts @@ -0,0 +1,9 @@ +export const dashboardAlphaMetrics: any +export const dashboardBetaMetrics: any +export function writeDashboardSandbox(options: { + root: string + storeFile: string + authFile: string + stickyEnabled?: boolean + accountSet?: 'alpha' | 'alpha-beta' | 'alpha-beta-gamma' +}): void diff --git a/tests/helpers/dashboard-seed.js b/tests/helpers/dashboard-seed.js new file mode 100644 index 0000000..21ea756 --- /dev/null +++ b/tests/helpers/dashboard-seed.js @@ -0,0 +1,137 @@ +import * as fs from 'node:fs' +import * as path from 'node:path' + +export const DASHBOARD_SEED_TIME = 1_700_000_000_000 + +export const dashboardAlphaMetrics = { + usageCount: 3, + lastRefresh: '2023-11-14T22:13:20.000Z', + lastSeenAt: DASHBOARD_SEED_TIME + 10_000, + lastActiveUntil: DASHBOARD_SEED_TIME + 20_000, + lastUsed: DASHBOARD_SEED_TIME + 30_000, + rateLimits: { + fiveHour: { limit: 100, remaining: 80, resetAt: DASHBOARD_SEED_TIME + 60_000, updatedAt: DASHBOARD_SEED_TIME + 1_000 }, + weekly: { limit: 1000, remaining: 700, resetAt: DASHBOARD_SEED_TIME + 120_000, updatedAt: DASHBOARD_SEED_TIME + 2_000 } + }, + rateLimitHistory: [ + { + at: DASHBOARD_SEED_TIME + 2_000, + fiveHour: { limit: 100, remaining: 80, resetAt: DASHBOARD_SEED_TIME + 60_000 }, + weekly: { limit: 1000, remaining: 700, resetAt: DASHBOARD_SEED_TIME + 120_000 } + } + ], + limitStatus: 'success', + limitError: 'alpha previous soft limit', + lastLimitProbeAt: DASHBOARD_SEED_TIME + 40_000, + lastLimitErrorAt: DASHBOARD_SEED_TIME + 35_000, + limitsConfidence: 'fresh' +} + +export const dashboardBetaMetrics = { + usageCount: 7, + lastRefresh: '2023-11-14T22:15:20.000Z', + lastSeenAt: DASHBOARD_SEED_TIME + 11_000, + lastActiveUntil: DASHBOARD_SEED_TIME + 21_000, + lastUsed: DASHBOARD_SEED_TIME + 31_000, + rateLimits: { + fiveHour: { limit: 100, remaining: 50, resetAt: DASHBOARD_SEED_TIME + 60_000, updatedAt: DASHBOARD_SEED_TIME + 3_000 }, + weekly: { limit: 1000, remaining: 450, resetAt: DASHBOARD_SEED_TIME + 120_000, updatedAt: DASHBOARD_SEED_TIME + 4_000 } + }, + rateLimitHistory: [ + { + at: DASHBOARD_SEED_TIME + 4_000, + fiveHour: { limit: 100, remaining: 50, resetAt: DASHBOARD_SEED_TIME + 60_000 }, + weekly: { limit: 1000, remaining: 450, resetAt: DASHBOARD_SEED_TIME + 120_000 } + } + ], + limitStatus: 'error', + limitError: 'beta probe failed', + lastLimitProbeAt: DASHBOARD_SEED_TIME + 41_000, + lastLimitErrorAt: DASHBOARD_SEED_TIME + 42_000, + limitsConfidence: 'stale' +} + +function stateAccount(alias) { + const expiresAt = Date.now() + (alias === 'beta' ? 120_000 : alias === 'gamma' ? 90_000 : 60_000) + const shared = { + alias, + accessToken: `token-${alias}`, + refreshToken: `refresh-${alias}`, + expiresAt, + email: `${alias}@example.com`, + enabled: true, + source: alias === 'beta' ? 'codex' : 'opencode' + } + + if (alias === 'alpha') { + return { ...shared, tags: ['core'], notes: 'primary account' } + } + if (alias === 'beta') { + return { ...shared, tags: ['backup'], notes: 'secondary account' } + } + return { ...shared, tags: [], notes: '' } +} + +function pickAccounts(accountSet) { + const accounts = { alpha: stateAccount('alpha') } + if (accountSet === 'alpha-beta' || accountSet === 'alpha-beta-gamma') { + accounts.beta = stateAccount('beta') + } + if (accountSet === 'alpha-beta-gamma') { + accounts.gamma = stateAccount('gamma') + } + return accounts +} + +function pickMetrics(accountSet) { + const metrics = { alpha: dashboardAlphaMetrics } + if (accountSet === 'alpha-beta' || accountSet === 'alpha-beta-gamma') { + metrics.beta = dashboardBetaMetrics + } + // gamma intentionally has no metrics entry: it exercises brand-new account defaults. + return metrics +} + +export function writeDashboardSandbox(options) { + const accountSet = options.accountSet ?? 'alpha-beta' + fs.rmSync(options.root, { recursive: true, force: true }) + fs.mkdirSync(options.root, { recursive: true }) + fs.writeFileSync(options.authFile, JSON.stringify({ OPENAI_API_KEY: null, tokens: {} }, null, 2)) + fs.writeFileSync( + options.storeFile, + JSON.stringify( + { + version: 3, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: DASHBOARD_SEED_TIME, + rotationStrategy: 'round-robin', + settings: { + rotationStrategy: 'round-robin', + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: {}, + featureFlags: { + antigravityEnabled: false, + stickySessionsEnabled: options.stickyEnabled ?? false + } + }, + accounts: pickAccounts(accountSet) + }, + null, + 2 + ) + ) + fs.writeFileSync( + path.join(path.dirname(options.storeFile), 'account-metrics.json'), + JSON.stringify( + { + version: 1, + updatedAt: DASHBOARD_SEED_TIME, + metrics: pickMetrics(accountSet) + }, + null, + 2 + ) + ) +} diff --git a/tests/integration/dashboard-api-contract.test.ts b/tests/integration/dashboard-api-contract.test.ts new file mode 100644 index 0000000..690a6eb --- /dev/null +++ b/tests/integration/dashboard-api-contract.test.ts @@ -0,0 +1,784 @@ +import * as fs from 'node:fs' +import * as net from 'node:net' +import * as os from 'node:os' +import * as path from 'node:path' +import type * as http from 'node:http' +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' +import { dashboardAlphaMetrics, dashboardBetaMetrics, writeDashboardSandbox } from '../helpers/dashboard-seed.js' + +const SANDBOX_ROOT = path.join(os.tmpdir(), 'oma-dashboard-contract-sandbox') +const STORE_FILE = path.join(SANDBOX_ROOT, 'accounts.json') +const AUTH_FILE = path.join(SANDBOX_ROOT, 'auth.json') +const originalEnv = process.env + +let startWebConsole: typeof import('../../src/web.js').startWebConsole +let getCodexAuthPath: typeof import('../../src/codex-auth.js').getCodexAuthPath + +type JsonRecord = Record + +async function getFreePort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer() + server.listen(0, '127.0.0.1', () => { + const address = server.address() + if (!address || typeof address === 'string') { + reject(new Error('Failed to resolve free port')) + return + } + const port = address.port + server.close((err) => { + if (err) { + reject(err) + return + } + resolve(port) + }) + }) + server.on('error', reject) + }) +} + +async function closeServer(server: http.Server): Promise { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err) + return + } + resolve() + }) + }) +} + +function seedSandbox(): void { + writeDashboardSandbox({ root: SANDBOX_ROOT, storeFile: STORE_FILE, authFile: AUTH_FILE }) +} + +function readStore(): JsonRecord { + return JSON.parse(fs.readFileSync(STORE_FILE, 'utf8')) as JsonRecord +} + +function jwt(payload: Record): string { + const encode = (value: Record) => Buffer.from(JSON.stringify(value)).toString('base64url') + return `${encode({ alg: 'none', typ: 'JWT' })}.${encode(payload)}.signature` +} + +function authClaims(alias: string, iat = 200, exp = 1_200): Record { + return { + iat, + exp, + email: `${alias}@example.com`, + 'https://api.openai.com/auth': { + chatgpt_account_id: `acct-${alias}`, + chatgpt_account_user_id: `acct-user-${alias}`, + user_id: `user-${alias}`, + chatgpt_plan_type: 'plus' + } + } +} + +function writeCodexAuth(alias: string, overrides: Partial<{ accessToken: string; refreshToken: string; idToken: string; accountId: string; iat: number; exp: number }> = {}): { accessToken: string; refreshToken: string; idToken: string } { + const accessToken = overrides.accessToken ?? jwt(authClaims(alias, overrides.iat, overrides.exp)) + const idToken = overrides.idToken ?? jwt(authClaims(alias, overrides.iat, overrides.exp)) + const refreshToken = overrides.refreshToken ?? `refresh-${alias}` + fs.writeFileSync( + AUTH_FILE, + JSON.stringify( + { + OPENAI_API_KEY: null, + tokens: { + access_token: accessToken, + refresh_token: refreshToken, + id_token: idToken, + account_id: overrides.accountId ?? `acct-${alias}` + }, + last_refresh: '2026-01-01T00:00:00.000Z' + }, + null, + 2 + ) + ) + return { accessToken, refreshToken, idToken } +} + +async function startServer(): Promise<{ server: http.Server; port: number }> { + const port = await getFreePort() + const server = startWebConsole({ host: '127.0.0.1', port }) + await new Promise((resolve) => server.once('listening', () => resolve())) + return { server, port } +} + +async function requestJson(port: number, pathname: string, init?: RequestInit): Promise<{ status: number; body: JsonRecord }> { + const response = await fetch(`http://127.0.0.1:${port}${pathname}`, { + headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) }, + ...init + }) + return { + status: response.status, + body: (await response.json()) as JsonRecord + } +} + +beforeAll(async () => { + seedSandbox() + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: SANDBOX_ROOT, + OPENCODE_MULTI_AUTH_STORE_FILE: STORE_FILE, + OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE: AUTH_FILE + } + + ;({ startWebConsole } = await import('../../src/web.js')) + ;({ getCodexAuthPath } = await import('../../src/codex-auth.js')) +}) + +beforeEach(() => { + seedSandbox() +}) + +afterEach(() => { + fs.unwatchFile(getCodexAuthPath()) +}) + +afterAll(() => { + fs.unwatchFile(getCodexAuthPath()) + process.env = originalEnv + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) +}) + +describe('dashboard API contract parity', () => { + it('preserves state and logs response shapes without leaking secrets', async () => { + const { server, port } = await startServer() + + try { + const state = await requestJson(port, '/api/state') + expect(state.status).toBe(200) + expect(state.body.authPath).toBe(AUTH_FILE) + expect(state.body.deviceAlias).toBeNull() + expect(state.body.rotationAlias).toBe('alpha') + expect(state.body.authSummary).toEqual({ + hasAccessToken: false, + hasIdToken: false, + hasRefreshToken: false + }) + expect(state.body.storeStatus).toEqual({ locked: false, encrypted: false, error: null }) + expect(state.body.login).toBeNull() + expect(state.body.lastSyncAt).toBe(0) + expect(state.body.lastSyncError).toBeNull() + expect(state.body.lastSyncAlias).toBeNull() + expect(state.body.codexActive).toEqual(expect.objectContaining({ + status: 'missing', + alias: null, + hasAccessToken: false, + hasRefreshToken: false, + hasIdToken: false + })) + expect(state.body.antigravity).toEqual({ + accounts: [], + path: expect.any(String), + quota: { + status: 'disabled', + scope: 'active' + } + }) + expect(state.body.queue).toBeNull() + expect(state.body.recommendedAlias).toBe('alpha') + expect(state.body.logPath).toEqual(expect.any(String)) + expect(state.body.autoLogin).toEqual( + expect.objectContaining({ + configured: false, + accounts: [], + error: 'credentials.json not found' + }) + ) + expect(state.body.rotationStrategy).toBe('round-robin') + expect(state.body.force).toEqual({ + active: false, + alias: null, + forcedUntil: null, + forcedBy: null, + remainingMs: 0, + remainingTime: '0m' + }) + expect(state.body.featureFlags).toEqual({ antigravityEnabled: false, stickySessionsEnabled: false }) + expect(state.body.accounts).toHaveLength(2) + expect(state.body.accounts[0]).toEqual( + expect.objectContaining({ + alias: 'alpha', + email: 'alpha@example.com', + enabled: true, + usageCount: 3, + lastRefresh: dashboardAlphaMetrics.lastRefresh, + lastSeenAt: dashboardAlphaMetrics.lastSeenAt, + lastActiveUntil: dashboardAlphaMetrics.lastActiveUntil, + lastUsed: dashboardAlphaMetrics.lastUsed, + rateLimits: dashboardAlphaMetrics.rateLimits, + rateLimitHistory: dashboardAlphaMetrics.rateLimitHistory, + limitStatus: dashboardAlphaMetrics.limitStatus, + limitError: dashboardAlphaMetrics.limitError, + lastLimitProbeAt: dashboardAlphaMetrics.lastLimitProbeAt, + lastLimitErrorAt: dashboardAlphaMetrics.lastLimitErrorAt, + limitsConfidence: dashboardAlphaMetrics.limitsConfidence, + tags: ['core'], + notes: 'primary account' + }) + ) + expect(state.body.accounts[0]).not.toHaveProperty('accessToken') + expect(state.body.accounts[0]).not.toHaveProperty('refreshToken') + expect(state.body.accounts[0]).not.toHaveProperty('idToken') + + const logs = await requestJson(port, '/api/logs?limit=5') + expect(logs.status).toBe(200) + expect(typeof logs.body.path).toBe('string') + expect(Array.isArray(logs.body.lines)).toBe(true) + } finally { + await closeServer(server) + } + }) + + it('preserves account and login action validation and mutation payloads', async () => { + const { server, port } = await startServer() + + try { + const accounts = await requestJson(port, '/api/accounts') + expect(accounts.status).toBe(200) + expect(accounts.body).toEqual({ + accounts: [ + expect.objectContaining({ + alias: 'alpha', + email: 'alpha@example.com', + enabled: true, + usageCount: 3, + rateLimits: dashboardAlphaMetrics.rateLimits, + rateLimitHistory: dashboardAlphaMetrics.rateLimitHistory, + limitsConfidence: dashboardAlphaMetrics.limitsConfidence, + limitStatus: dashboardAlphaMetrics.limitStatus, + limitError: dashboardAlphaMetrics.limitError, + lastLimitProbeAt: dashboardAlphaMetrics.lastLimitProbeAt, + lastLimitErrorAt: dashboardAlphaMetrics.lastLimitErrorAt, + tags: ['core'], + notes: 'primary account' + }), + expect.objectContaining({ + alias: 'beta', + email: 'beta@example.com', + enabled: true, + usageCount: 7, + rateLimits: dashboardBetaMetrics.rateLimits, + rateLimitHistory: dashboardBetaMetrics.rateLimitHistory, + limitsConfidence: dashboardBetaMetrics.limitsConfidence, + limitStatus: dashboardBetaMetrics.limitStatus, + limitError: dashboardBetaMetrics.limitError, + lastLimitProbeAt: dashboardBetaMetrics.lastLimitProbeAt, + lastLimitErrorAt: dashboardBetaMetrics.lastLimitErrorAt, + tags: ['backup'], + notes: 'secondary account' + }) + ] + }) + + const disableAlpha = await requestJson(port, '/api/accounts/alpha/enabled', { + method: 'PUT', + body: JSON.stringify({ enabled: false }) + }) + expect(disableAlpha.status).toBe(200) + expect(disableAlpha.body).toEqual( + expect.objectContaining({ + ok: true, + alias: 'alpha', + enabled: false, + disabledAt: expect.any(Number), + disabledBy: 'dashboard' + }) + ) + + const disableAlphaAgain = await requestJson(port, '/api/accounts/alpha/enabled', { + method: 'PUT', + body: JSON.stringify({ enabled: false }) + }) + expect(disableAlphaAgain.status).toBe(409) + expect(disableAlphaAgain.body).toEqual({ + error: 'Account is already disabled', + code: 'ALREADY_IN_STATE' + }) + + const disableLastEnabled = await requestJson(port, '/api/accounts/beta/enabled', { + method: 'PUT', + body: JSON.stringify({ enabled: false }) + }) + expect(disableLastEnabled.status).toBe(409) + expect(disableLastEnabled.body).toEqual({ + error: 'Cannot disable the last enabled account', + code: 'LAST_ACCOUNT' + }) + + const missingAuth = await requestJson(port, '/api/auth/start', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(missingAuth).toEqual({ status: 400, body: { error: 'Missing alias' } }) + + const missingAutoLoginSelector = await requestJson(port, '/api/auto-login/start', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(missingAutoLoginSelector).toEqual({ status: 400, body: { error: 'Missing selector' } }) + + const missingAutoLoginEmail = await requestJson(port, '/api/auto-login/add', { + method: 'POST', + body: JSON.stringify({ password: 'secret' }) + }) + expect(missingAutoLoginEmail).toEqual({ status: 400, body: { error: 'Missing login/email' } }) + + const missingAutoLoginPassword = await requestJson(port, '/api/auto-login/add', { + method: 'POST', + body: JSON.stringify({ email: 'bot@example.com', password: ' ' }) + }) + expect(missingAutoLoginPassword).toEqual({ status: 400, body: { error: 'Missing password' } }) + + const missingSwitchAlias = await requestJson(port, '/api/switch', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(missingSwitchAlias).toEqual({ status: 400, body: { error: 'Missing alias' } }) + + const missingReauthAlias = await requestJson(port, '/api/accounts/ghost/reauth', { + method: 'POST', + body: JSON.stringify({ actor: 'test-suite' }) + }) + expect(missingReauthAlias.status).toBe(404) + expect(missingReauthAlias.body).toEqual({ error: 'Unknown alias', code: 'ACCOUNT_NOT_FOUND' }) + + const updateMeta = await requestJson(port, '/api/account/meta', { + method: 'POST', + body: JSON.stringify({ alias: 'beta', tags: ' Ops,ops, Team ', notes: ' updated note ' }) + }) + expect(updateMeta).toEqual({ status: 200, body: { ok: true } }) + expect(readStore().accounts.beta.tags).toEqual(['ops', 'team']) + expect(readStore().accounts.beta.notes).toBe('updated note') + + const removeBeta = await requestJson(port, '/api/remove', { + method: 'POST', + body: JSON.stringify({ alias: 'beta' }) + }) + expect(removeBeta).toEqual({ status: 200, body: { ok: true } }) + expect(readStore().accounts.beta).toBeUndefined() + } finally { + await closeServer(server) + } + }) + + it('preserves settings, force-mode, sync, refresh, and antigravity contracts', async () => { + const { server, port } = await startServer() + + try { + const sync = await requestJson(port, '/api/sync', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(sync).toEqual({ status: 200, body: { ok: true } }) + + const tokenRefreshUnknown = await requestJson(port, '/api/token/refresh', { + method: 'POST', + body: JSON.stringify({ alias: 'ghost' }) + }) + expect(tokenRefreshUnknown).toEqual({ status: 400, body: { error: 'Unknown alias' } }) + + const logsAfterUnknownRefresh = await requestJson(port, '/api/logs?limit=50') + expect(logsAfterUnknownRefresh.status).toBe(200) + expect(logsAfterUnknownRefresh.body.lines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + level: 'warn', + message: expect.stringContaining('[multi-auth] Refresh requested for unknown alias: ghost') + }) + ]) + ) + + const limitsRefreshUnknown = await requestJson(port, '/api/limits/refresh', { + method: 'POST', + body: JSON.stringify({ alias: 'ghost' }) + }) + expect(limitsRefreshUnknown).toEqual({ status: 400, body: { error: 'Unknown alias' } }) + + const stopRefresh = await requestJson(port, '/api/limits/stop', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(stopRefresh).toEqual({ status: 200, body: { ok: true } }) + + const forceState = await requestJson(port, '/api/force') + expect(forceState.status).toBe(200) + expect(forceState.body).toEqual( + expect.objectContaining({ + active: false, + alias: null, + forcedAt: null, + forcedUntil: null, + forcedBy: null, + remainingMs: 0, + remainingTime: '0m', + previousRotationStrategy: null + }) + ) + + const missingForceAlias = await requestJson(port, '/api/force', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(missingForceAlias).toEqual({ + status: 400, + body: { error: 'Missing alias', code: 'MISSING_ALIAS' } + }) + + const activateForce = await requestJson(port, '/api/force', { + method: 'POST', + body: JSON.stringify({ alias: 'alpha', actor: 'test-suite' }) + }) + expect(activateForce.status).toBe(200) + expect(activateForce.body).toEqual( + expect.objectContaining({ + ok: true, + alias: 'alpha', + forcedUntil: expect.any(Number), + remainingMs: expect.any(Number), + remainingTime: expect.any(String), + previousRotationStrategy: 'round-robin' + }) + ) + + const forceAfterActivation = await requestJson(port, '/api/force') + expect(forceAfterActivation.status).toBe(200) + expect(forceAfterActivation.body).toEqual( + expect.objectContaining({ + active: true, + alias: 'alpha', + forcedBy: 'test-suite', + previousRotationStrategy: 'round-robin' + }) + ) + + const clearForce = await requestJson(port, '/api/force/clear', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(clearForce).toEqual({ status: 200, body: { ok: true, restoredStrategy: 'round-robin' } }) + + const settings = await requestJson(port, '/api/settings') + expect(settings.status).toBe(200) + expect(settings.body).toEqual( + expect.objectContaining({ + settings: expect.objectContaining({ + rotationStrategy: 'round-robin', + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: {}, + featureFlags: expect.objectContaining({ antigravityEnabled: false, stickySessionsEnabled: false }) + }), + source: 'persisted', + canReset: true + }) + ) + + const updateSettings = await requestJson(port, '/api/settings', { + method: 'PUT', + body: JSON.stringify({ + actor: 'test-suite', + rotationStrategy: 'least-used', + criticalThreshold: 15, + lowThreshold: 45, + featureFlags: { antigravityEnabled: true } + }) + }) + expect(updateSettings.status).toBe(200) + expect(updateSettings.body).toEqual( + expect.objectContaining({ + ok: true, + settings: expect.objectContaining({ + rotationStrategy: 'least-used', + criticalThreshold: 15, + lowThreshold: 45, + featureFlags: expect.objectContaining({ antigravityEnabled: true, stickySessionsEnabled: false }), + updatedBy: 'test-suite' + }) + }) + ) + + const invalidFeatureFlags = await requestJson(port, '/api/settings/feature-flags', { + method: 'PUT', + body: JSON.stringify({}) + }) + expect(invalidFeatureFlags).toEqual({ + status: 400, + body: { error: 'Invalid feature flags', code: 'INVALID_FEATURE_FLAGS' } + }) + + const featureFlags = await requestJson(port, '/api/settings/feature-flags') + expect(featureFlags).toEqual({ + status: 200, + body: { + featureFlags: { + antigravityEnabled: true, + stickySessionsEnabled: false + } + } + }) + + const resetSettings = await requestJson(port, '/api/settings/reset', { + method: 'POST', + body: JSON.stringify({ actor: 'test-suite' }) + }) + expect(resetSettings.status).toBe(200) + expect(resetSettings.body).toEqual( + expect.objectContaining({ + ok: true, + settings: expect.objectContaining({ + rotationStrategy: 'round-robin', + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: {}, + featureFlags: expect.objectContaining({ antigravityEnabled: false, stickySessionsEnabled: false }) + }) + }) + ) + + const applyPreset = await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'balanced', actor: 'test-suite' }) + }) + expect(applyPreset.status).toBe(200) + expect(applyPreset.body).toEqual( + expect.objectContaining({ + ok: true, + preset: 'balanced', + settings: expect.objectContaining({ + rotationStrategy: 'weighted-round-robin', + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: expect.objectContaining({ alpha: 0.5, beta: 0.5 }) + }) + }) + ) + + const invalidPreset = await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'nope' }) + }) + expect(invalidPreset).toEqual({ + status: 400, + body: { + error: 'Invalid preset', + code: 'INVALID_PRESET', + validPresets: ['balanced', 'conservative', 'aggressive', 'custom'] + } + }) + + const antigravityRefresh = await requestJson(port, '/api/antigravity/refresh', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(antigravityRefresh).toEqual({ + status: 403, + body: { + error: 'Antigravity feature is disabled', + code: 'FEATURE_DISABLED', + feature: 'antigravity' + } + }) + + const antigravityRefreshAll = await requestJson(port, '/api/antigravity/refresh-all', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(antigravityRefreshAll).toEqual({ + status: 403, + body: { + error: 'Antigravity feature is disabled', + code: 'FEATURE_DISABLED', + feature: 'antigravity' + } + }) + } finally { + await closeServer(server) + } + }) + + it('does not resurrect a deleted account during repeated state polling', async () => { + writeCodexAuth('beta', { accessToken: 'token-beta', refreshToken: 'refresh-beta' }) + const { server, port } = await startServer() + + try { + const removeBeta = await requestJson(port, '/api/remove', { + method: 'POST', + body: JSON.stringify({ alias: 'beta' }) + }) + expect(removeBeta).toEqual({ status: 200, body: { ok: true } }) + + for (let i = 0; i < 3; i += 1) { + const state = await requestJson(port, '/api/state') + expect(state.status).toBe(200) + expect(state.body.accounts.map((account: JsonRecord) => account.alias)).toEqual(['alpha']) + expect(readStore().accounts.beta).toBeUndefined() + } + } finally { + await closeServer(server) + } + }) + + it('imports Codex auth only from explicit manual endpoints and preserves legacy sync response shape', async () => { + const gammaTokens = writeCodexAuth('gamma') + const { server, port } = await startServer() + + try { + const beforeImport = await requestJson(port, '/api/state') + expect(beforeImport.body.accounts.map((account: JsonRecord) => account.alias)).toEqual(['alpha', 'beta']) + expect(readStore().accounts.gamma).toBeUndefined() + + const manualImport = await requestJson(port, '/api/codex/import', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(manualImport.status).toBe(200) + expect(manualImport.body).toEqual(expect.objectContaining({ + ok: true, + imported: true, + alias: 'gamma', + added: true, + updated: true, + codexActive: expect.objectContaining({ status: 'matched', alias: 'gamma' }) + })) + expect(readStore().accounts.gamma).toEqual(expect.objectContaining({ + accessToken: gammaTokens.accessToken, + refreshToken: gammaTokens.refreshToken, + source: 'codex' + })) + + fs.writeFileSync(AUTH_FILE, '{bad-json') + const malformedImport = await requestJson(port, '/api/codex/import', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(malformedImport).toEqual({ + status: 422, + body: { ok: false, error: 'Failed to parse codex auth.json', code: 'CODEX_AUTH_INVALID' } + }) + + const afterMalformed = await requestJson(port, '/api/state') + expect(afterMalformed.body.lastSyncError).toBe('Failed to parse codex auth.json') + expect(afterMalformed.body.lastSyncAlias).toBeNull() + + const legacySync = await requestJson(port, '/api/sync', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(legacySync).toEqual({ status: 200, body: { ok: true } }) + } finally { + await closeServer(server) + } + }) + + it('writes a stored account to Codex auth without changing rotation state', async () => { + const { server, port } = await startServer() + const beforeStore = readStore() + + try { + const useBeta = await requestJson(port, '/api/codex/use', { + method: 'POST', + body: JSON.stringify({ alias: 'beta' }) + }) + expect(useBeta.status).toBe(200) + expect(useBeta.body).toEqual(expect.objectContaining({ + ok: true, + alias: 'beta', + codexActive: expect.objectContaining({ status: 'matched', alias: 'beta' }) + })) + + const auth = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')) + expect(auth.tokens).toEqual(expect.objectContaining({ + access_token: beforeStore.accounts.beta.accessToken, + refresh_token: beforeStore.accounts.beta.refreshToken + })) + + const afterStore = readStore() + expect(afterStore.activeAlias).toBe(beforeStore.activeAlias) + expect(afterStore.rotationIndex).toBe(beforeStore.rotationIndex) + expect(afterStore.lastRotation).toBe(beforeStore.lastRotation) + } finally { + await closeServer(server) + } + }) + + it('refreshes device-alias tokens, writes Codex auth, and does not read back stale auth on poll', async () => { + const originalFetch = global.fetch + const oldTokens = { accessToken: 'token-alpha', refreshToken: 'refresh-alpha' } + fs.writeFileSync( + AUTH_FILE, + JSON.stringify({ + OPENAI_API_KEY: null, + tokens: { + access_token: oldTokens.accessToken, + refresh_token: oldTokens.refreshToken + }, + last_refresh: '2026-01-01T00:00:00.000Z' + }, null, 2) + ) + const newAccessToken = jwt(authClaims('alpha', 500, 1_500)) + const newIdToken = jwt(authClaims('alpha', 500, 1_500)) + global.fetch = jest.fn(async (input: any, init?: any) => { + const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url + if (url.includes('/oauth/token')) { + return new Response(JSON.stringify({ + access_token: newAccessToken, + refresh_token: 'refresh-alpha-new', + id_token: newIdToken, + expires_in: 3600 + }), { status: 200, headers: { 'Content-Type': 'application/json' } }) + } + return originalFetch(input as any, init) + }) as any + + const { server, port } = await startServer() + + try { + const refresh = await requestJson(port, '/api/token/refresh', { + method: 'POST', + body: JSON.stringify({ alias: 'alpha' }) + }) + expect(refresh).toEqual({ status: 200, body: { ok: true, results: [{ alias: 'alpha', updated: true }] } }) + + expect(readStore().accounts.alpha).toEqual(expect.objectContaining({ + accessToken: newAccessToken, + refreshToken: 'refresh-alpha-new', + idToken: newIdToken + })) + expect(JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')).tokens).toEqual(expect.objectContaining({ + access_token: newAccessToken, + refresh_token: 'refresh-alpha-new', + id_token: newIdToken + })) + + fs.writeFileSync( + AUTH_FILE, + JSON.stringify({ + OPENAI_API_KEY: null, + tokens: { + access_token: oldTokens.accessToken, + refresh_token: oldTokens.refreshToken + }, + last_refresh: '2026-01-01T00:00:00.000Z' + }, null, 2) + ) + const stateAfterStaleAuth = await requestJson(port, '/api/state') + expect(stateAfterStaleAuth.status).toBe(200) + expect(readStore().accounts.alpha).toEqual(expect.objectContaining({ + accessToken: newAccessToken, + refreshToken: 'refresh-alpha-new', + idToken: newIdToken + })) + } finally { + await closeServer(server) + global.fetch = originalFetch + } + }) +}) diff --git a/tests/integration/sticky-session-admin-api.test.ts b/tests/integration/sticky-session-admin-api.test.ts new file mode 100644 index 0000000..88771e6 --- /dev/null +++ b/tests/integration/sticky-session-admin-api.test.ts @@ -0,0 +1,332 @@ +import * as fs from 'node:fs' +import * as net from 'node:net' +import * as os from 'node:os' +import * as path from 'node:path' +import type * as http from 'node:http' + +const SANDBOX_ROOT = path.join(os.tmpdir(), 'oma-sticky-admin-sandbox') +const STORE_FILE = path.join(SANDBOX_ROOT, 'accounts.json') +const AUTH_FILE = path.join(SANDBOX_ROOT, 'auth.json') +const STICKY_FILE = path.join(SANDBOX_ROOT, 'sticky-sessions.json') +const originalEnv = process.env + +let startWebConsole: typeof import('../../src/web.js').startWebConsole +let getCodexAuthPath: typeof import('../../src/codex-auth.js').getCodexAuthPath +let hashStickyIdentity: typeof import('../../src/sticky-sessions.js').hashStickyIdentity + +type JsonRecord = Record + +async function getFreePort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer() + server.listen(0, '127.0.0.1', () => { + const address = server.address() + if (!address || typeof address === 'string') { + reject(new Error('Failed to resolve free port')) + return + } + const port = address.port + server.close((err) => { + if (err) { + reject(err) + return + } + resolve(port) + }) + }) + server.on('error', reject) + }) +} + +async function closeServer(server: http.Server): Promise { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err) + return + } + resolve() + }) + }) +} + +function seedSandbox(options?: { stickyEnabled?: boolean }): void { + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) + fs.mkdirSync(SANDBOX_ROOT, { recursive: true }) + fs.writeFileSync(AUTH_FILE, JSON.stringify({ OPENAI_API_KEY: null, tokens: {} }, null, 2)) + fs.writeFileSync( + STORE_FILE, + JSON.stringify( + { + version: 2, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: 1_700_000_000_000, + rotationStrategy: 'round-robin', + settings: { + rotationStrategy: 'round-robin', + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: {}, + featureFlags: { + antigravityEnabled: false, + stickySessionsEnabled: options?.stickyEnabled ?? true + } + }, + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'token-alpha', + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + email: 'alpha@example.com', + usageCount: 3, + enabled: true + } + } + }, + null, + 2 + ) + ) +} + +function seedStickySidecar(now: number): void { + fs.writeFileSync( + STICKY_FILE, + JSON.stringify( + { + version: 1, + updatedAt: now, + entries: { + [hashStickyIdentity('stale-session')]: { + alias: 'alpha', + createdAt: now - 120_000, + lastUsedAt: now - 120_000 + }, + [hashStickyIdentity('fresh-session')]: { + alias: 'alpha', + createdAt: now - 1000, + lastUsedAt: now - 1000 + } + } + }, + null, + 2 + ) + ) +} + +function readStore(): JsonRecord { + return JSON.parse(fs.readFileSync(STORE_FILE, 'utf8')) as JsonRecord +} + +async function startServer(): Promise<{ server: http.Server; port: number }> { + const port = await getFreePort() + const server = startWebConsole({ host: '127.0.0.1', port }) + await new Promise((resolve) => server.once('listening', () => resolve())) + return { server, port } +} + +async function requestJson(port: number, pathname: string, init?: RequestInit): Promise<{ status: number; body: JsonRecord }> { + const response = await fetch(`http://127.0.0.1:${port}${pathname}`, { + headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) }, + ...init + }) + return { + status: response.status, + body: (await response.json()) as JsonRecord + } +} + +beforeAll(async () => { + seedSandbox() + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: SANDBOX_ROOT, + OPENCODE_MULTI_AUTH_STORE_FILE: STORE_FILE, + OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE: AUTH_FILE + } + + ;({ startWebConsole } = await import('../../src/web.js')) + ;({ getCodexAuthPath } = await import('../../src/codex-auth.js')) + ;({ hashStickyIdentity } = await import('../../src/sticky-sessions.js')) +}) + +beforeEach(() => { + seedSandbox({ stickyEnabled: true }) +}) + +afterEach(() => { + fs.unwatchFile(getCodexAuthPath()) +}) + +afterAll(() => { + fs.unwatchFile(getCodexAuthPath()) + process.env = originalEnv + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) +}) + +describe('sticky-session admin API', () => { + it('exposes additive config, status, and synchronous cleanup endpoints without mutating /api/settings semantics', async () => { + const now = Date.now() + seedStickySidecar(now) + const { server, port } = await startServer() + + try { + const initialSettings = await requestJson(port, '/api/settings') + expect(initialSettings.status).toBe(200) + expect(initialSettings.body.settings.featureFlags).toEqual({ + antigravityEnabled: false, + stickySessionsEnabled: true + }) + expect(initialSettings.body.settings.stickySessions).toBeUndefined() + + const config = await requestJson(port, '/api/sticky-sessions/config') + expect(config.status).toBe(200) + expect(config.body).toEqual( + expect.objectContaining({ + enabled: true, + identitySources: [ + 'header:x-session-affinity', + 'header:session-id', + 'header:session_id', + 'header:conversation_id', + 'body:metadata.session_id', + 'body:metadata.conversation_id' + ], + allowPromptCacheKey: false, + ttlMs: 86_400_000, + maxEntries: 1000, + maxFileBytes: 1_048_576 + }) + ) + + const status = await requestJson(port, '/api/sticky-sessions/status') + expect(status.status).toBe(200) + expect(status.body).toEqual( + expect.objectContaining({ + ok: true, + entries: 2, + path: STICKY_FILE, + exists: true, + ttlMs: 86_400_000, + maxEntries: 1000, + maxFileBytes: 1_048_576, + sizeBytes: expect.any(Number), + updatedAt: expect.any(Number) + }) + ) + + const invalid = await requestJson(port, '/api/sticky-sessions/config', { + method: 'PUT', + body: JSON.stringify({ + enabled: true, + identitySources: ['header:session_id', 'body:prompt_cache_key'], + allowPromptCacheKey: false, + ttlMs: 0, + maxEntries: -1, + maxFileBytes: 0 + }) + }) + expect(invalid.status).toBe(400) + expect(invalid.body.code).toBe('VALIDATION_ERROR') + + const updateConfig = await requestJson(port, '/api/sticky-sessions/config', { + method: 'PUT', + body: JSON.stringify({ + enabled: true, + identitySources: ['header:conversation_id', 'body:prompt_cache_key'], + allowPromptCacheKey: true, + ttlMs: 60_000, + maxEntries: 50, + maxFileBytes: 4096, + actor: 'test-suite' + }) + }) + expect(updateConfig.status).toBe(200) + expect(updateConfig.body).toEqual( + expect.objectContaining({ + enabled: true, + identitySources: ['header:conversation_id', 'body:prompt_cache_key'], + allowPromptCacheKey: true, + ttlMs: 60_000, + maxEntries: 50, + maxFileBytes: 4096, + updatedAt: expect.any(Number), + updatedBy: 'test-suite' + }) + ) + + const cleanup = await requestJson(port, '/api/sticky-sessions/cleanup', { + method: 'POST', + body: JSON.stringify({}) + }) + expect(cleanup.status).toBe(200) + expect(cleanup.body).toEqual({ + ok: true, + before: 2, + after: 1, + removed: 1, + prunedAt: expect.any(Number) + }) + + const disable = await requestJson(port, '/api/sticky-sessions/config', { + method: 'PUT', + body: JSON.stringify({ + enabled: false, + identitySources: ['header:conversation_id'], + allowPromptCacheKey: false, + ttlMs: 60_000, + maxEntries: 50, + maxFileBytes: 4096, + actor: 'test-suite' + }) + }) + expect(disable.status).toBe(200) + expect(disable.body).toEqual( + expect.objectContaining({ + enabled: false, + identitySources: ['header:conversation_id'], + allowPromptCacheKey: false, + ttlMs: 60_000, + maxEntries: 50, + maxFileBytes: 4096, + updatedAt: expect.any(Number), + updatedBy: 'test-suite' + }) + ) + expect(readStore().settings.featureFlags.stickySessionsEnabled).toBe(false) + + const gatedConfig = await requestJson(port, '/api/sticky-sessions/config') + expect(gatedConfig).toEqual({ + status: 403, + body: { + error: 'Sticky sessions feature is disabled', + code: 'FEATURE_DISABLED', + feature: 'sticky-sessions' + } + }) + + const gatedStatus = await requestJson(port, '/api/sticky-sessions/status') + expect(gatedStatus).toEqual({ + status: 403, + body: { + error: 'Sticky sessions feature is disabled', + code: 'FEATURE_DISABLED', + feature: 'sticky-sessions' + } + }) + + const afterSettings = await requestJson(port, '/api/settings') + expect(afterSettings.status).toBe(200) + expect(afterSettings.body.settings.featureFlags).toEqual({ + antigravityEnabled: false, + stickySessionsEnabled: false + }) + expect(afterSettings.body.settings.stickySessions).toBeUndefined() + } finally { + await closeServer(server) + } + }) +}) diff --git a/tests/integration/web-server.test.ts b/tests/integration/web-server.test.ts index 01d5670..b07515f 100644 --- a/tests/integration/web-server.test.ts +++ b/tests/integration/web-server.test.ts @@ -1,3 +1,5 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' import * as fs from 'node:fs' import * as net from 'node:net' import * as os from 'node:os' @@ -8,6 +10,7 @@ import { once } from 'node:events' const SANDBOX_ROOT = path.join(os.tmpdir(), 'oma-web-integration-sandbox') const STORE_FILE = path.join(SANDBOX_ROOT, 'accounts.json') const AUTH_FILE = path.join(SANDBOX_ROOT, 'auth.json') +const WEB_DIST_DIR = path.resolve(process.cwd(), 'tests/fixtures/web-dist') const originalEnv = process.env let startWebConsole: typeof import('../../src/web.js').startWebConsole @@ -47,6 +50,84 @@ async function closeServer(server: http.Server): Promise { }) } +function jwt(payload: Record): string { + const encode = (value: Record) => Buffer.from(JSON.stringify(value)).toString('base64url') + return `${encode({ alg: 'none', typ: 'JWT' })}.${encode(payload)}.signature` +} + +function authClaims(alias: string): Record { + return { + iat: 200, + exp: 1_200, + email: `${alias}@example.com`, + 'https://api.openai.com/auth': { + chatgpt_account_id: `acct-${alias}`, + chatgpt_account_user_id: `acct-user-${alias}`, + user_id: `user-${alias}`, + chatgpt_plan_type: 'plus' + } + } +} + +function writeCodexAuth(alias: string): void { + fs.writeFileSync( + AUTH_FILE, + JSON.stringify( + { + OPENAI_API_KEY: null, + tokens: { + access_token: jwt(authClaims(alias)), + refresh_token: `refresh-${alias}`, + id_token: jwt(authClaims(alias)), + account_id: `acct-${alias}` + }, + last_refresh: '2026-01-01T00:00:00.000Z' + }, + null, + 2 + ) + ) +} + +function writeStoreWithAlphaOnly(): void { + fs.mkdirSync(SANDBOX_ROOT, { recursive: true }) + fs.writeFileSync( + STORE_FILE, + JSON.stringify( + { + version: 3, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: 1_700_000_000_000, + rotationStrategy: 'round-robin', + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'token-alpha', + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + email: 'alpha@example.com', + enabled: true, + source: 'opencode' + } + } + }, + null, + 2 + ) + ) +} + +async function requestJson(port: number, pathname: string): Promise> { + const response = await fetch(`http://127.0.0.1:${port}${pathname}`) + expect(response.status).toBe(200) + return (await response.json()) as Record +} + +async function delay(ms: number): Promise { + await new Promise((resolve) => setTimeout(resolve, ms)) +} + beforeAll(async () => { if (fs.existsSync(SANDBOX_ROOT)) { fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) @@ -58,7 +139,8 @@ beforeAll(async () => { ...originalEnv, OPENCODE_MULTI_AUTH_STORE_DIR: SANDBOX_ROOT, OPENCODE_MULTI_AUTH_STORE_FILE: STORE_FILE, - OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE: AUTH_FILE + OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE: AUTH_FILE, + OPENCODE_MULTI_AUTH_WEB_DIST_DIR: WEB_DIST_DIR } ;({ startWebConsole } = await import('../../src/web.js')) @@ -80,6 +162,32 @@ afterAll(() => { }) describe('web server hardening', () => { + it('registers metrics shutdown flush hooks once when the web console starts', async () => { + const port = await getFreePort() + const secondPort = await getFreePort() + const processOnSpy = jest.spyOn(process, 'on') + jest.resetModules() + const { startWebConsole: isolatedStartWebConsole } = await import('../../src/web.js') + const server = isolatedStartWebConsole({ host: '127.0.0.1', port }) + const serverListening = once(server, 'listening') + const secondServer = isolatedStartWebConsole({ host: '127.0.0.1', port: secondPort }) + const secondServerListening = once(secondServer, 'listening') + + try { + await Promise.all([serverListening, secondServerListening]) + const hookCalls = processOnSpy.mock.calls.filter((call: any[]) => ['beforeExit', 'SIGINT', 'SIGTERM', 'exit'].includes(call[0])) + expect(hookCalls.map((call: any[]) => call[0])).toEqual(['beforeExit', 'SIGINT', 'SIGTERM', 'exit']) + for (const [event, listener] of hookCalls as Array<[NodeJS.Signals | 'beforeExit' | 'exit', (...args: any[]) => void]>) { + process.removeListener(event, listener) + } + } finally { + await closeServer(server) + await closeServer(secondServer) + processOnSpy.mockRestore() + fs.unwatchFile(getCodexAuthPath()) + } + }) + it('rejects non-loopback host binding', () => { expect(() => startWebConsole({ host: '0.0.0.0', port: 4120 })).toThrow(/LOCALHOST_ONLY|localhost/i) }) @@ -113,4 +221,101 @@ describe('web server hardening', () => { fs.unwatchFile(getCodexAuthPath()) } }) + + it('serves static dist assets with SPA fallback and never lets /api routes hit the SPA', async () => { + const port = await getFreePort() + const server = startWebConsole({ host: '127.0.0.1', port }) + + try { + await once(server, 'listening') + + const homeResponse = await fetch(`http://127.0.0.1:${port}/`) + expect(homeResponse.status).toBe(200) + expect(homeResponse.headers.get('content-type')).toContain('text/html') + expect(await homeResponse.text()).toContain('fixture-spa-shell') + + const assetResponse = await fetch(`http://127.0.0.1:${port}/assets/app.js`) + expect(assetResponse.status).toBe(200) + expect(assetResponse.headers.get('content-type')).toContain('javascript') + expect(await assetResponse.text()).toContain('fixture-asset') + + const routeResponse = await fetch(`http://127.0.0.1:${port}/dashboard/accounts`) + expect(routeResponse.status).toBe(200) + expect(routeResponse.headers.get('content-type')).toContain('text/html') + expect(await routeResponse.text()).toContain('fixture-spa-shell') + + const apiResponse = await fetch(`http://127.0.0.1:${port}/api/state`) + expect(apiResponse.status).toBe(200) + expect(apiResponse.headers.get('content-type')).toContain('application/json') + expect((await apiResponse.json()) as { authPath?: string }).toEqual(expect.objectContaining({ authPath: AUTH_FILE })) + + const missingApiResponse = await fetch(`http://127.0.0.1:${port}/api/does-not-exist`) + expect(missingApiResponse.status).toBe(404) + expect(missingApiResponse.headers.get('content-type')).toContain('application/json') + expect((await missingApiResponse.json()) as { error?: string }).toEqual({ error: 'Not found' }) + + const missingAssetResponse = await fetch(`http://127.0.0.1:${port}/assets/missing.js`) + expect(missingAssetResponse.status).toBe(404) + } finally { + await closeServer(server) + fs.unwatchFile(getCodexAuthPath()) + } + }) + + it('returns 404 when SPA dist is missing and never falls back to inline dashboard HTML', async () => { + const port = await getFreePort() + const missingDistDir = path.join(SANDBOX_ROOT, 'missing-dist') + process.env.OPENCODE_MULTI_AUTH_WEB_DIST_DIR = missingDistDir + + const server = startWebConsole({ host: '127.0.0.1', port }) + + try { + await once(server, 'listening') + + const homeResponse = await fetch(`http://127.0.0.1:${port}/`) + expect(homeResponse.status).toBe(404) + expect(homeResponse.headers.get('content-type')).toContain('application/json') + expect((await homeResponse.json()) as { error?: string }).toEqual({ error: 'Not found' }) + + const routeResponse = await fetch(`http://127.0.0.1:${port}/dashboard/accounts`) + expect(routeResponse.status).toBe(404) + expect(routeResponse.headers.get('content-type')).toContain('application/json') + expect((await routeResponse.json()) as { error?: string }).toEqual({ error: 'Not found' }) + + const apiResponse = await fetch(`http://127.0.0.1:${port}/api/state`) + expect(apiResponse.status).toBe(200) + expect(apiResponse.headers.get('content-type')).toContain('application/json') + } finally { + await closeServer(server) + process.env.OPENCODE_MULTI_AUTH_WEB_DIST_DIR = WEB_DIST_DIR + fs.unwatchFile(getCodexAuthPath()) + } + }) + + it('does not auto-import Codex auth on startup or auth-file changes', async () => { + writeStoreWithAlphaOnly() + writeCodexAuth('gamma') + const port = await getFreePort() + const server = startWebConsole({ host: '127.0.0.1', port }) + + try { + await once(server, 'listening') + + const startupState = await requestJson(port, '/api/state') + expect(startupState.accounts.map((account: Record) => account.alias)).toEqual(['alpha']) + expect(JSON.parse(fs.readFileSync(STORE_FILE, 'utf8')).accounts.gamma).toBeUndefined() + + writeCodexAuth('delta') + await delay(3_300) + + const afterAuthChangeState = await requestJson(port, '/api/state') + expect(afterAuthChangeState.accounts.map((account: Record) => account.alias)).toEqual(['alpha']) + const store = JSON.parse(fs.readFileSync(STORE_FILE, 'utf8')) + expect(store.accounts.gamma).toBeUndefined() + expect(store.accounts.delta).toBeUndefined() + } finally { + await closeServer(server) + fs.unwatchFile(getCodexAuthPath()) + } + }) }) diff --git a/tests/integration/weighted-presets-api.test.ts b/tests/integration/weighted-presets-api.test.ts new file mode 100644 index 0000000..8d15c9a --- /dev/null +++ b/tests/integration/weighted-presets-api.test.ts @@ -0,0 +1,493 @@ +import * as fs from 'node:fs' +import * as net from 'node:net' +import * as os from 'node:os' +import * as path from 'node:path' +import type * as http from 'node:http' + +const SANDBOX_ROOT = path.join(os.tmpdir(), 'oma-weighted-presets-sandbox') +const STORE_FILE = path.join(SANDBOX_ROOT, 'accounts.json') +const AUTH_FILE = path.join(SANDBOX_ROOT, 'auth.json') +const originalEnv = process.env + +let startWebConsole: typeof import('../../src/web.js').startWebConsole +let getCodexAuthPath: typeof import('../../src/codex-auth.js').getCodexAuthPath + +type JsonRecord = Record + +async function getFreePort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer() + server.listen(0, '127.0.0.1', () => { + const address = server.address() + if (!address || typeof address === 'string') { + reject(new Error('Failed to resolve free port')) + return + } + const port = address.port + server.close((err) => { + if (err) { + reject(err) + return + } + resolve(port) + }) + }) + server.on('error', reject) + }) +} + +async function closeServer(server: http.Server): Promise { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err) + return + } + resolve() + }) + }) +} + +function seedSandbox(): void { + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) + fs.mkdirSync(SANDBOX_ROOT, { recursive: true }) + fs.writeFileSync(AUTH_FILE, JSON.stringify({ OPENAI_API_KEY: null, tokens: {} }, null, 2)) + fs.writeFileSync( + STORE_FILE, + JSON.stringify( + { + version: 2, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: 1_700_000_000_000, + rotationStrategy: 'round-robin', + settings: { + rotationStrategy: 'round-robin', + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: {}, + featureFlags: { + antigravityEnabled: false, + stickySessionsEnabled: false + } + }, + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'token-alpha', + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + email: 'alpha@example.com', + usageCount: 3, + enabled: true, + source: 'opencode', + rateLimits: { + fiveHour: { limit: 100, remaining: 80, resetAt: Date.now() + 60_000, updatedAt: Date.now() }, + weekly: { limit: 1000, remaining: 700, resetAt: Date.now() + 120_000, updatedAt: Date.now() } + }, + limitsConfidence: 'fresh' + }, + beta: { + alias: 'beta', + accessToken: 'token-beta', + refreshToken: 'refresh-beta', + expiresAt: Date.now() + 120_000, + email: 'beta@example.com', + usageCount: 7, + enabled: true, + source: 'codex', + rateLimits: { + fiveHour: { limit: 100, remaining: 40, resetAt: Date.now() + 60_000, updatedAt: Date.now() }, + weekly: { limit: 1000, remaining: 300, resetAt: Date.now() + 120_000, updatedAt: Date.now() } + }, + limitsConfidence: 'fresh' + }, + gamma: { + alias: 'gamma', + accessToken: 'token-gamma', + refreshToken: 'refresh-gamma', + expiresAt: Date.now() + 180_000, + email: 'gamma@example.com', + usageCount: 1, + enabled: false, + source: 'opencode', + rateLimits: { + fiveHour: { limit: 100, remaining: 100, resetAt: Date.now() + 60_000, updatedAt: Date.now() }, + weekly: { limit: 1000, remaining: 900, resetAt: Date.now() + 120_000, updatedAt: Date.now() } + }, + limitsConfidence: 'fresh' + } + } + }, + null, + 2 + ) + ) +} + +function readStore(): JsonRecord { + return JSON.parse(fs.readFileSync(STORE_FILE, 'utf8')) as JsonRecord +} + +async function startServer(): Promise<{ server: http.Server; port: number }> { + const port = await getFreePort() + const server = startWebConsole({ host: '127.0.0.1', port }) + await new Promise((resolve) => server.once('listening', () => resolve())) + return { server, port } +} + +async function requestJson(port: number, pathname: string, init?: RequestInit): Promise<{ status: number; body: JsonRecord }> { + const response = await fetch(`http://127.0.0.1:${port}${pathname}`, { + headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) }, + ...init + }) + return { + status: response.status, + body: (await response.json()) as JsonRecord + } +} + +describe('Weighted Presets API', () => { + beforeAll(async () => { + seedSandbox() + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: SANDBOX_ROOT, + OPENCODE_MULTI_AUTH_STORE_FILE: STORE_FILE, + OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE: AUTH_FILE + } + + ;({ startWebConsole } = await import('../../src/web.js')) + ;({ getCodexAuthPath } = await import('../../src/codex-auth.js')) + }) + + beforeEach(() => { + seedSandbox() + }) + + afterEach(() => { + fs.unwatchFile(getCodexAuthPath()) + }) + + afterAll(() => { + fs.unwatchFile(getCodexAuthPath()) + process.env = originalEnv + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) + }) + + describe('POST /api/settings/preset', () => { + test('applies balanced preset with equal weights and weighted-round-robin strategy', async () => { + const { server, port } = await startServer() + + try { + const { status, body } = await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'balanced', actor: 'test-suite' }) + }) + + expect(status).toBe(200) + expect(body.ok).toBe(true) + expect(body.preset).toBe('balanced') + expect(body.settings.rotationStrategy).toBe('weighted-round-robin') + expect(body.settings.criticalThreshold).toBe(10) + expect(body.settings.lowThreshold).toBe(30) + expect(body.settings.accountWeights).toBeDefined() + + // Balanced preset: 3 accounts, each should get ~0.333 weight + const weights = body.settings.accountWeights as Record + expect(Object.keys(weights)).toHaveLength(3) + expect(weights.alpha).toBeCloseTo(1 / 3, 3) + expect(weights.beta).toBeCloseTo(1 / 3, 3) + expect(weights.gamma).toBeCloseTo(1 / 3, 3) + + // Verify weights sum to approximately 1 + const totalWeight = Object.values(weights).reduce((sum: number, w: number) => sum + w, 0) + expect(totalWeight).toBeCloseTo(1, 3) + } finally { + await closeServer(server) + } + }) + + test('applies conservative preset favoring healthy accounts', async () => { + const { server, port } = await startServer() + + try { + const { status, body } = await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'conservative', actor: 'test-suite' }) + }) + + expect(status).toBe(200) + expect(body.ok).toBe(true) + expect(body.preset).toBe('conservative') + expect(body.settings.rotationStrategy).toBe('weighted-round-robin') + expect(body.settings.criticalThreshold).toBe(20) + expect(body.settings.lowThreshold).toBe(40) + expect(body.settings.accountWeights).toBeDefined() + + // Conservative preset: alpha has better limits (80/700) than beta (40/300) + // So alpha should get higher weight than beta + const weights = body.settings.accountWeights as Record + expect(weights.alpha).toBeGreaterThan(weights.beta) + + // Verify weights sum to approximately 1 + const totalWeight = Object.values(weights).reduce((sum: number, w: number) => sum + w, 0) + expect(totalWeight).toBeCloseTo(1, 3) + } finally { + await closeServer(server) + } + }) + + test('applies aggressive preset favoring heavily-used accounts', async () => { + const { server, port } = await startServer() + + try { + const { status, body } = await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'aggressive', actor: 'test-suite' }) + }) + + expect(status).toBe(200) + expect(body.ok).toBe(true) + expect(body.preset).toBe('aggressive') + expect(body.settings.rotationStrategy).toBe('weighted-round-robin') + expect(body.settings.criticalThreshold).toBe(5) + expect(body.settings.lowThreshold).toBe(20) + expect(body.settings.accountWeights).toBeDefined() + + // Aggressive preset: beta has lower remaining limits (40/300) than alpha (80/700) + // So beta should get higher weight than alpha (inverse of health) + const weights = body.settings.accountWeights as Record + expect(weights.beta).toBeGreaterThan(weights.alpha) + + // Verify weights sum to approximately 1 + const totalWeight = Object.values(weights).reduce((sum: number, w: number) => sum + w, 0) + expect(totalWeight).toBeCloseTo(1, 3) + } finally { + await closeServer(server) + } + }) + + test('rejects invalid preset with 400 and validPresets list', async () => { + const { server, port } = await startServer() + + try { + const { status, body } = await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'invalid-preset' }) + }) + + expect(status).toBe(400) + expect(body.error).toBe('Invalid preset') + expect(body.code).toBe('INVALID_PRESET') + expect(body.validPresets).toEqual(['balanced', 'conservative', 'aggressive', 'custom']) + } finally { + await closeServer(server) + } + }) + + test('persists preset settings to store', async () => { + const { server, port } = await startServer() + + try { + await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'balanced', actor: 'test-suite' }) + }) + + const store = readStore() + expect(store.settings.rotationStrategy).toBe('weighted-round-robin') + expect(store.settings.accountWeights).toBeDefined() + expect(Object.keys(store.settings.accountWeights)).toHaveLength(3) + } finally { + await closeServer(server) + } + }) + }) + + describe('PUT /api/settings with accountWeights', () => { + test('updates per-account weights directly', async () => { + const { server, port } = await startServer() + + try { + const { status, body } = await requestJson(port, '/api/settings', { + method: 'PUT', + body: JSON.stringify({ + accountWeights: { + alpha: 0.5, + beta: 0.3, + gamma: 0.2 + }, + actor: 'test-suite' + }) + }) + + expect(status).toBe(200) + expect(body.ok).toBe(true) + expect(body.settings.accountWeights.alpha).toBe(0.5) + expect(body.settings.accountWeights.beta).toBe(0.3) + expect(body.settings.accountWeights.gamma).toBe(0.2) + } finally { + await closeServer(server) + } + }) + + test('rejects weights that do not sum to approximately 1', async () => { + const { server, port } = await startServer() + + try { + const { status, body } = await requestJson(port, '/api/settings', { + method: 'PUT', + body: JSON.stringify({ + accountWeights: { + alpha: 0.5, + beta: 0.2 + // Missing gamma, sum = 0.7 + }, + actor: 'test-suite' + }) + }) + + expect(status).toBe(400) + expect(body.error).toBe('Validation failed') + expect(body.code).toBe('VALIDATION_ERROR') + expect(body.details).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + field: 'accountWeights', + message: 'Total weights must sum to 1.0', + constraint: 'sum(weights) ≈ 1.0' + }) + ]) + ) + } finally { + await closeServer(server) + } + }) + + test('rejects individual weights outside (0, 1] range', async () => { + const { server, port } = await startServer() + + try { + const { status, body } = await requestJson(port, '/api/settings', { + method: 'PUT', + body: JSON.stringify({ + accountWeights: { + alpha: 1.5, + beta: -0.3, + gamma: -0.2 + }, + actor: 'test-suite' + }) + }) + + expect(status).toBe(400) + expect(body.error).toBe('Validation failed') + expect(body.code).toBe('VALIDATION_ERROR') + expect(body.details).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + field: 'accountWeights.alpha', + message: 'Weight for alpha must be between 0 and 1', + constraint: '0 < weight <= 1' + }), + expect.objectContaining({ + field: 'accountWeights.beta', + message: 'Weight for beta must be between 0 and 1', + constraint: '0 < weight <= 1' + }), + expect.objectContaining({ + field: 'accountWeights.gamma', + message: 'Weight for gamma must be between 0 and 1', + constraint: '0 < weight <= 1' + }) + ]) + ) + } finally { + await closeServer(server) + } + }) + + test('persists custom weights to store', async () => { + const { server, port } = await startServer() + + try { + await requestJson(port, '/api/settings', { + method: 'PUT', + body: JSON.stringify({ + accountWeights: { + alpha: 0.6, + beta: 0.3, + gamma: 0.1 + }, + actor: 'test-suite' + }) + }) + + const store = readStore() + expect(store.settings.accountWeights.alpha).toBe(0.6) + expect(store.settings.accountWeights.beta).toBe(0.3) + expect(store.settings.accountWeights.gamma).toBe(0.1) + } finally { + await closeServer(server) + } + }) + }) + + describe('GET /api/settings reflects weighted state', () => { + test('returns preset field when weighted-round-robin matches a preset', async () => { + const { server, port } = await startServer() + + try { + // Apply balanced preset first + await requestJson(port, '/api/settings/preset', { + method: 'POST', + body: JSON.stringify({ preset: 'balanced', actor: 'test-suite' }) + }) + + const { status, body } = await requestJson(port, '/api/settings') + + expect(status).toBe(200) + expect(body.settings.rotationStrategy).toBe('weighted-round-robin') + expect(body.preset).toBe('balanced') + expect(body.settings.accountWeights).toBeDefined() + } finally { + await closeServer(server) + } + }) + + test('returns custom weights without preset field', async () => { + const { server, port } = await startServer() + + try { + // Apply custom weights with non-matching thresholds + await requestJson(port, '/api/settings', { + method: 'PUT', + body: JSON.stringify({ + rotationStrategy: 'weighted-round-robin', + criticalThreshold: 15, + lowThreshold: 35, + accountWeights: { + alpha: 0.7, + beta: 0.2, + gamma: 0.1 + }, + actor: 'test-suite' + }) + }) + + const { status, body } = await requestJson(port, '/api/settings') + + expect(status).toBe(200) + expect(body.settings.rotationStrategy).toBe('weighted-round-robin') + // Custom weights don't match any preset thresholds, so preset should be undefined + expect(body.preset).toBeUndefined() + expect(body.settings.accountWeights.alpha).toBe(0.7) + expect(body.settings.accountWeights.beta).toBe(0.2) + expect(body.settings.accountWeights.gamma).toBe(0.1) + } finally { + await closeServer(server) + } + }) + }) +}) diff --git a/tests/python/test_auto_login_no_metrics.py b/tests/python/test_auto_login_no_metrics.py new file mode 100644 index 0000000..847f355 --- /dev/null +++ b/tests/python/test_auto_login_no_metrics.py @@ -0,0 +1,144 @@ +import importlib.util +import json +import os +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +MODULE_PATH = ROOT / "auto-login" / "auto_login.py" +FORBIDDEN_KEYS = {"lastRefresh", "lastSeenAt", "usageCount", "rateLimitHistory"} +EMAIL = "user@example.com" +ACCOUNT_ID = "acct_test_123" + + +def load_helper_module(): + spec = importlib.util.spec_from_file_location("auto_login_helper", MODULE_PATH) + module = importlib.util.module_from_spec(spec) + assert spec and spec.loader + spec.loader.exec_module(module) + return module + + +def configure_temp_home(monkeypatch, home_dir): + monkeypatch.setenv("HOME", str(home_dir)) + monkeypatch.setenv("USERPROFILE", str(home_dir)) + monkeypatch.setenv("HOMEDRIVE", "") + monkeypatch.setenv("HOMEPATH", str(home_dir)) + + +def fake_decode_jwt_payload(token): + if token in {"access-token", "id-token"}: + return { + "exp": 1893456000, + "email": EMAIL, + "https://api.openai.com/auth": {"chatgpt_account_id": ACCOUNT_ID}, + } + return None + + +def configure_helper(module, tmp_path): + module.STORE_DIR = tmp_path / ".config" / "opencode" + module.STORE_FILE = module.STORE_DIR / "opencode-multi-auth-codex-accounts.json" + module.decode_jwt_payload = fake_decode_jwt_payload + module.fetch_userinfo_email = lambda _access_token: None + return module + + +def read_accounts(module): + with open(module.STORE_FILE, "r", encoding="utf-8") as handle: + return json.load(handle)["accounts"] + + +def assert_no_metrics(accounts): + for account in accounts: + for key in FORBIDDEN_KEYS: + assert key not in account, f"unexpected metrics key {key!r} in {account!r}" + + +def exercise_new_account(tmp_path, monkeypatch): + configure_temp_home(monkeypatch, tmp_path / "home") + module = configure_helper(load_helper_module(), tmp_path) + + module.add_account_to_store( + { + "access_token": "access-token", + "refresh_token": "refresh-token", + "id_token": "id-token", + "expires_in": 3600, + } + ) + + assert module.STORE_FILE.exists() + assert_no_metrics(read_accounts(module)) + + +def exercise_overwrite_account(tmp_path, monkeypatch): + configure_temp_home(monkeypatch, tmp_path / "home") + module = configure_helper(load_helper_module(), tmp_path) + + module.add_account_to_store( + { + "access_token": "access-token", + "refresh_token": "refresh-token", + "id_token": "id-token", + "expires_in": 3600, + } + ) + module.add_account_to_store( + { + "access_token": "access-token", + "refresh_token": "refresh-token-2", + "id_token": "id-token", + "expires_in": 3600, + } + ) + + assert_no_metrics(read_accounts(module)) + + +def test_new_account_does_not_persist_metrics(tmp_path, monkeypatch): + exercise_new_account(tmp_path, monkeypatch) + + +def test_overwrite_account_does_not_persist_metrics(tmp_path, monkeypatch): + exercise_overwrite_account(tmp_path, monkeypatch) + + +class _MiniMonkeyPatch: + def __init__(self): + self._env = [] + self._attrs = [] + + def setenv(self, key, value): + self._env.append((key, os.environ.get(key))) + os.environ[key] = value + + def setattr(self, obj, name, value): + self._attrs.append((obj, name, getattr(obj, name))) + setattr(obj, name, value) + + def undo(self): + for obj, name, value in reversed(self._attrs): + setattr(obj, name, value) + for key, value in reversed(self._env): + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value + + +def _run_without_pytest(): + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + monkeypatch = _MiniMonkeyPatch() + try: + exercise_new_account(tmp_path, monkeypatch) + exercise_overwrite_account(tmp_path / "overwrite", monkeypatch) + finally: + monkeypatch.undo() + + +if __name__ == "__main__": + _run_without_pytest() + print("ok") diff --git a/tests/unit/auth-refresh-logging.test.ts b/tests/unit/auth-refresh-logging.test.ts new file mode 100644 index 0000000..836fd5d --- /dev/null +++ b/tests/unit/auth-refresh-logging.test.ts @@ -0,0 +1,228 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' + +const esmJest = jest as typeof jest & { + unstable_mockModule: (moduleName: string, factory: () => Record) => void +} + +const loadStore = jest.fn() +const updateAccount = jest.fn() +const clearAuthInvalid = jest.fn() +const logError = jest.fn() +const logInfo = jest.fn() +const setMetrics = jest.fn() + +esmJest.unstable_mockModule('../../src/store.js', () => ({ + loadStore, + updateAccount, + addAccount: jest.fn() +})) + +esmJest.unstable_mockModule('../../src/rotation.js', () => ({ + clearAuthInvalid +})) + +esmJest.unstable_mockModule('../../src/logger.js', () => ({ + logError, + logInfo +})) + +esmJest.unstable_mockModule('../../src/metrics-store.js', () => ({ + setMetrics +})) + +let refreshToken: typeof import('../../src/auth.js').refreshToken + +beforeAll(async () => { + ;({ refreshToken } = await import('../../src/auth.js')) +}) + +beforeEach(() => { + jest.clearAllMocks() + loadStore.mockReturnValue({ + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'old-access', + refreshToken: 'refresh-token', + expiresAt: Date.now() + 60_000, + usageCount: 0 + } + }, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: Date.now() + }) +}) + +describe('refreshToken logging', () => { + it('writes dashboard-visible error log when refresh token is missing', async () => { + loadStore.mockReturnValue({ + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'old-access', + expiresAt: Date.now() + 60_000, + usageCount: 0 + } + }, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: Date.now() + }) + + const result = await refreshToken('alpha') + + expect(result).toBeNull() + expect(logError).toHaveBeenCalledWith(expect.stringContaining('No refresh token for alpha')) + }) + + it('writes dashboard-visible error log when token endpoint returns non-OK', async () => { + const fetchMock = jest.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response('unauthorized', { status: 401 })) + + const result = await refreshToken('alpha') + + expect(result).toBeNull() + expect(updateAccount).toHaveBeenCalledWith( + 'alpha', + expect.objectContaining({ + authInvalid: true, + authInvalidatedAt: expect.any(Number) + }) + ) + expect(logError).toHaveBeenCalledWith(expect.stringContaining('Refresh failed for alpha: 401')) + fetchMock.mockRestore() + }) + + it('writes dashboard-visible error log when refresh throws', async () => { + const thrown = new Error('network down') + const fetchMock = jest.spyOn(globalThis, 'fetch').mockRejectedValueOnce(thrown) + + const result = await refreshToken('alpha') + + expect(result).toBeNull() + expect(logError).toHaveBeenCalledWith(expect.stringContaining('Refresh error for alpha: network down')) + fetchMock.mockRestore() + }) + + it('writes dashboard-visible info log when refresh succeeds', async () => { + const expiresInSeconds = 3600 + const payload = { + access_token: 'next-access-token', + refresh_token: 'next-refresh-token', + id_token: 'next-id-token', + expires_in: expiresInSeconds, + token_type: 'Bearer' + } + const fetchMock = jest.spyOn(globalThis, 'fetch').mockResolvedValueOnce( + new Response(JSON.stringify(payload), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) + ) + updateAccount.mockReturnValue({ + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'next-access-token', + refreshToken: 'next-refresh-token', + expiresAt: Date.now() + expiresInSeconds * 1000, + usageCount: 0 + } + } + }) + + const result = await refreshToken('alpha') + + expect(result).toEqual(expect.objectContaining({ alias: 'alpha' })) + expect(setMetrics).toHaveBeenCalledWith('alpha', expect.objectContaining({ lastRefresh: expect.any(String) })) + expect(clearAuthInvalid).toHaveBeenCalledWith('alpha') + expect(logInfo).toHaveBeenCalledWith(expect.stringContaining('Token refreshed for alpha')) + fetchMock.mockRestore() + }) + + it('deduplicates concurrent refreshes for the same alias', async () => { + const refreshedAccount = { + alias: 'alpha', + accessToken: 'shared-next-access-token', + refreshToken: 'shared-next-refresh-token', + expiresAt: Date.now() + 3600_000, + usageCount: 0 + } + const fetchMock = jest.spyOn(globalThis, 'fetch').mockResolvedValue( + new Response(JSON.stringify({ + access_token: 'shared-next-access-token', + refresh_token: 'shared-next-refresh-token', + expires_in: 3600, + token_type: 'Bearer' + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) + ) + updateAccount.mockReturnValue({ accounts: { alpha: refreshedAccount } }) + + const [first, second] = await Promise.all([refreshToken('alpha'), refreshToken('alpha')]) + + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(first).toBe(refreshedAccount) + expect(second).toBe(refreshedAccount) + fetchMock.mockRestore() + }) + + it('keeps concurrent refreshes for distinct aliases independent', async () => { + loadStore.mockReturnValue({ + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'old-access-alpha', + refreshToken: 'refresh-token-alpha', + expiresAt: Date.now() + 60_000, + usageCount: 0 + }, + beta: { + alias: 'beta', + accessToken: 'old-access-beta', + refreshToken: 'refresh-token-beta', + expiresAt: Date.now() + 60_000, + usageCount: 0 + } + }, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: Date.now() + }) + const fetchMock = jest.spyOn(globalThis, 'fetch').mockResolvedValue( + new Response(JSON.stringify({ + access_token: 'next-access-token', + refresh_token: 'next-refresh-token', + expires_in: 3600, + token_type: 'Bearer' + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) + ) + updateAccount.mockImplementation((alias: string) => ({ + accounts: { + [alias as string]: { + alias, + accessToken: `next-access-${alias}`, + refreshToken: `next-refresh-${alias}`, + expiresAt: Date.now() + 3600_000, + usageCount: 0 + } + } + })) + + await Promise.all([refreshToken('alpha'), refreshToken('beta')]) + + const refreshTokens = fetchMock.mock.calls.map((call: Parameters) => { + const body = (call[1] as RequestInit).body as URLSearchParams + return body.get('refresh_token') + }) + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(refreshTokens).toEqual(expect.arrayContaining(['refresh-token-alpha', 'refresh-token-beta'])) + fetchMock.mockRestore() + }) +}) diff --git a/tests/unit/codex-auth-sync.test.ts b/tests/unit/codex-auth-sync.test.ts new file mode 100644 index 0000000..c97caee --- /dev/null +++ b/tests/unit/codex-auth-sync.test.ts @@ -0,0 +1,315 @@ +import * as fs from 'node:fs' +import * as os from 'node:os' +import * as path from 'node:path' +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' + +const originalEnv = process.env + +function jwt(payload: Record): string { + const encode = (value: Record) => Buffer.from(JSON.stringify(value)).toString('base64url') + return `${encode({ alg: 'none', typ: 'JWT' })}.${encode(payload)}.signature` +} + +function authClaims(iat: number, exp: number): Record { + return { + iat, + exp, + email: 'alpha@example.com', + 'https://api.openai.com/auth': { + chatgpt_account_id: 'acct-alpha', + chatgpt_account_user_id: 'acct-user-alpha', + user_id: 'user-alpha', + chatgpt_plan_type: 'plus' + } + } +} + +function authClaimsFor(alias: string, iat: number, exp: number): Record { + return { + iat, + exp, + email: `${alias}@example.com`, + 'https://api.openai.com/auth': { + chatgpt_account_id: `acct-${alias}`, + chatgpt_account_user_id: `acct-user-${alias}`, + user_id: `user-${alias}`, + chatgpt_plan_type: 'plus' + } + } +} + +function writeAuthFile(sandboxRoot: string, alias: string, issuedAt = 200): { accessToken: string; idToken: string } { + const accessToken = jwt(authClaimsFor(alias, issuedAt, issuedAt + 1_000)) + const idToken = jwt(authClaimsFor(alias, issuedAt, issuedAt + 1_000)) + fs.writeFileSync( + path.join(sandboxRoot, 'auth.json'), + JSON.stringify({ + OPENAI_API_KEY: null, + tokens: { + access_token: accessToken, + refresh_token: `refresh-${alias}`, + id_token: idToken, + account_id: `acct-${alias}` + }, + last_refresh: '2026-01-01T00:00:00.000Z' + }) + ) + return { accessToken, idToken } +} + +async function loadSandboxModules(sandboxRoot: string) { + jest.resetModules() + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: sandboxRoot, + OPENCODE_MULTI_AUTH_STORE_FILE: path.join(sandboxRoot, 'accounts.json'), + OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE: path.join(sandboxRoot, 'auth.json'), + CODEX_SOFT_LOG_PATH: path.join(sandboxRoot, 'codex-soft.log') + } + + const store = await import('../../src/store.js') + const metricsStore = await import('../../src/metrics-store.js') + const codexAuth = await import('../../src/codex-auth.js') + return { store, metricsStore, codexAuth } +} + +function readJson(file: string): any { + return JSON.parse(fs.readFileSync(file, 'utf8')) +} + +describe('syncCodexAuthFile token freshness', () => { + let sandboxRoot: string + + beforeEach(() => { + sandboxRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'oma-codex-auth-sync-')) + }) + + afterEach(() => { + process.env = originalEnv + fs.rmSync(sandboxRoot, { recursive: true, force: true }) + jest.resetModules() + }) + + it('does not overwrite newer stored credentials with older auth.json tokens', async () => { + const { store, metricsStore, codexAuth } = await loadSandboxModules(sandboxRoot) + const storedAccessToken = jwt(authClaims(200, 1_200)) + const authAccessToken = jwt(authClaims(100, 1_100)) + + store.addAccount('alpha', { + accessToken: storedAccessToken, + refreshToken: 'stored-refresh-token', + idToken: 'stored-id-token', + expiresAt: 1_200_000, + email: 'alpha@example.com', + source: 'opencode' + }) + fs.writeFileSync( + path.join(sandboxRoot, 'auth.json'), + JSON.stringify({ + OPENAI_API_KEY: null, + tokens: { + access_token: authAccessToken, + refresh_token: 'older-auth-refresh-token', + id_token: 'older-auth-id-token', + account_id: 'acct-alpha' + }, + last_refresh: '2026-01-01T00:00:00.000Z' + }) + ) + + const result = codexAuth.syncCodexAuthFile() + const account = store.loadStore().accounts.alpha + + expect(result).toEqual(expect.objectContaining({ alias: 'alpha', updated: true, added: false })) + expect(account.accessToken).toBe(storedAccessToken) + expect(account.refreshToken).toBe('stored-refresh-token') + expect(account.idToken).toBe('stored-id-token') + expect(account.expiresAt).toBe(1_200_000) + expect(readJson(path.join(sandboxRoot, 'accounts.json')).accounts.alpha).not.toHaveProperty('lastSeenAt') + expect(metricsStore.getMetrics('alpha')).toEqual(expect.objectContaining({ + lastSeenAt: expect.any(Number), + lastRefresh: '2026-01-01T00:00:00.000Z' + })) + }) + + it('updates stored credentials when auth.json has newer tokens', async () => { + const { store, metricsStore, codexAuth } = await loadSandboxModules(sandboxRoot) + const storedAccessToken = jwt(authClaims(100, 1_100)) + const authAccessToken = jwt(authClaims(200, 1_200)) + + store.addAccount('alpha', { + accessToken: storedAccessToken, + refreshToken: 'stored-refresh-token', + idToken: 'stored-id-token', + expiresAt: 1_100_000, + email: 'alpha@example.com', + source: 'opencode' + }) + fs.writeFileSync( + path.join(sandboxRoot, 'auth.json'), + JSON.stringify({ + OPENAI_API_KEY: null, + tokens: { + access_token: authAccessToken, + refresh_token: 'newer-auth-refresh-token', + id_token: 'newer-auth-id-token', + account_id: 'acct-alpha' + }, + last_refresh: '2026-01-01T00:00:00.000Z' + }) + ) + + const result = codexAuth.syncCodexAuthFile() + const account = store.loadStore().accounts.alpha + + expect(result).toEqual(expect.objectContaining({ alias: 'alpha', updated: true, added: false })) + expect(account.accessToken).toBe(authAccessToken) + expect(account.refreshToken).toBe('newer-auth-refresh-token') + expect(account.idToken).toBe('newer-auth-id-token') + expect(account.expiresAt).toBe(1_200_000) + expect(readJson(path.join(sandboxRoot, 'accounts.json')).accounts.alpha).not.toHaveProperty('lastRefresh') + expect(metricsStore.getMetrics('alpha')).toEqual(expect.objectContaining({ + lastSeenAt: expect.any(Number), + lastRefresh: '2026-01-01T00:00:00.000Z' + })) + }) + + it('keeps writeCodexAuthForAlias sync telemetry in metrics only', async () => { + const { store, metricsStore, codexAuth } = await loadSandboxModules(sandboxRoot) + const storedAccessToken = jwt(authClaims(300, 1_300)) + store.addAccount('alpha', { + accessToken: storedAccessToken, + refreshToken: 'stored-refresh-token', + idToken: 'stored-id-token', + accountId: 'acct-alpha', + expiresAt: 1_300_000, + email: 'alpha@example.com', + source: 'opencode' + }) + + codexAuth.writeCodexAuthForAlias('alpha') + + const persistedAccount = readJson(path.join(sandboxRoot, 'accounts.json')).accounts.alpha + expect(persistedAccount.source).toBe('codex') + expect(persistedAccount).not.toHaveProperty('lastRefresh') + expect(persistedAccount).not.toHaveProperty('lastSeenAt') + expect(metricsStore.getMetrics('alpha')).toEqual(expect.objectContaining({ + lastRefresh: expect.any(String), + lastSeenAt: expect.any(Number) + })) + }) + + it('reports matched Codex auth without mutating the store', async () => { + const { store, codexAuth } = await loadSandboxModules(sandboxRoot) + const { accessToken, idToken } = writeAuthFile(sandboxRoot, 'alpha') + store.addAccount('alpha', { + accessToken, + refreshToken: 'refresh-alpha', + idToken, + accountId: 'acct-alpha', + accountUserId: 'acct-user-alpha', + userId: 'user-alpha', + expiresAt: 1_200_000, + email: 'alpha@example.com', + source: 'opencode' + }) + const beforeStore = fs.readFileSync(path.join(sandboxRoot, 'accounts.json'), 'utf8') + + const state = codexAuth.getCodexActiveState() + + expect(state).toEqual(expect.objectContaining({ + status: 'matched', + alias: 'alpha', + email: 'alpha@example.com', + accountId: 'acct-alpha', + accountUserId: 'acct-user-alpha', + userId: 'user-alpha', + hasAccessToken: true, + hasRefreshToken: true, + hasIdToken: true + })) + expect(fs.readFileSync(path.join(sandboxRoot, 'accounts.json'), 'utf8')).toBe(beforeStore) + }) + + it('reports unknown Codex auth without adding a store account', async () => { + const { store, codexAuth } = await loadSandboxModules(sandboxRoot) + store.addAccount('alpha', { + accessToken: jwt(authClaimsFor('alpha', 100, 1_100)), + refreshToken: 'refresh-alpha', + expiresAt: 1_100_000, + email: 'alpha@example.com', + source: 'opencode' + }) + writeAuthFile(sandboxRoot, 'gamma') + const beforeStore = readJson(path.join(sandboxRoot, 'accounts.json')) + + const state = codexAuth.getCodexActiveState() + + expect(state).toEqual(expect.objectContaining({ + status: 'unknown', + alias: null, + email: 'gamma@example.com', + accountId: 'acct-gamma', + hasAccessToken: true, + hasRefreshToken: true, + hasIdToken: true + })) + expect(readJson(path.join(sandboxRoot, 'accounts.json'))).toEqual(beforeStore) + }) + + it('reports missing Codex auth for absent or empty auth.json without mutating the store', async () => { + const { store, codexAuth } = await loadSandboxModules(sandboxRoot) + store.addAccount('alpha', { + accessToken: jwt(authClaimsFor('alpha', 100, 1_100)), + refreshToken: 'refresh-alpha', + expiresAt: 1_100_000, + email: 'alpha@example.com', + source: 'opencode' + }) + const beforeStore = readJson(path.join(sandboxRoot, 'accounts.json')) + + expect(codexAuth.getCodexActiveState()).toEqual(expect.objectContaining({ + status: 'missing', + alias: null, + hasAccessToken: false, + hasRefreshToken: false, + hasIdToken: false + })) + + fs.writeFileSync(path.join(sandboxRoot, 'auth.json'), ' ') + expect(codexAuth.getCodexActiveState()).toEqual(expect.objectContaining({ + status: 'missing', + alias: null, + hasAccessToken: false, + hasRefreshToken: false, + hasIdToken: false + })) + expect(readJson(path.join(sandboxRoot, 'accounts.json'))).toEqual(beforeStore) + }) + + it('reports malformed Codex auth as an error without mutating the store', async () => { + const { store, codexAuth } = await loadSandboxModules(sandboxRoot) + store.addAccount('alpha', { + accessToken: jwt(authClaimsFor('alpha', 100, 1_100)), + refreshToken: 'refresh-alpha', + expiresAt: 1_100_000, + email: 'alpha@example.com', + source: 'opencode' + }) + fs.writeFileSync(path.join(sandboxRoot, 'auth.json'), '{not-json') + const beforeStore = readJson(path.join(sandboxRoot, 'accounts.json')) + + const state = codexAuth.getCodexActiveState() + + expect(state).toEqual(expect.objectContaining({ + status: 'error', + alias: null, + hasAccessToken: false, + hasRefreshToken: false, + hasIdToken: false, + error: 'Failed to parse codex auth.json' + })) + expect(readJson(path.join(sandboxRoot, 'accounts.json'))).toEqual(beforeStore) + }) +}) diff --git a/tests/unit/cyber-policy.test.ts b/tests/unit/cyber-policy.test.ts index 1f61716..800d1cb 100644 --- a/tests/unit/cyber-policy.test.ts +++ b/tests/unit/cyber-policy.test.ts @@ -1,4 +1,4 @@ -import { isCyberPolicyError } from '../../src/index.js' +import { isCyberPolicyError } from '../../src/cyber-policy.js' describe('cyber policy errors', () => { it('detects OpenAI cyber_policy error codes', () => { diff --git a/tests/unit/entrypoint-exports.test.ts b/tests/unit/entrypoint-exports.test.ts new file mode 100644 index 0000000..ea29dda --- /dev/null +++ b/tests/unit/entrypoint-exports.test.ts @@ -0,0 +1,7 @@ +describe('plugin entrypoint exports', () => { + it('only exposes the default plugin function', async () => { + const mod = await import('../../src/index.js') + + expect(Object.keys(mod).sort()).toEqual(['default']) + }) +}) diff --git a/tests/unit/feature-flags.test.ts b/tests/unit/feature-flags.test.ts index 5b0da21..f0d2d01 100644 --- a/tests/unit/feature-flags.test.ts +++ b/tests/unit/feature-flags.test.ts @@ -57,11 +57,23 @@ describe('Phase G: Feature Flags', () => { expect(result.settings.featureFlags?.antigravityEnabled).toBe(false) }) + it('should default stickySessionsEnabled to false', () => { + const result = getSettings() + + expect((result.settings.featureFlags as any)?.stickySessionsEnabled).toBe(false) + }) + it('should return false for isFeatureEnabled when flag not set', () => { const result = isFeatureEnabled('antigravityEnabled') expect(result).toBe(false) }) + + it('should return false for isFeatureEnabled when stickySessionsEnabled is not set', () => { + const result = isFeatureEnabled('stickySessionsEnabled' as any) + + expect(result).toBe(false) + }) }) describe('L1: Feature Flag Environment Override', () => { @@ -97,6 +109,15 @@ describe('Phase G: Feature Flags', () => { expect(result.settings.featureFlags?.antigravityEnabled).toBe(false) }) + + it('should enable sticky sessions via environment variable', () => { + process.env.OPENCODE_MULTI_AUTH_STICKY_SESSIONS_ENABLED = 'true' + + const result = getSettings() + + expect((result.settings.featureFlags as any)?.stickySessionsEnabled).toBe(true) + expect(result.source).toBe('env') + }) }) describe('L1: Feature Flag Persistence', () => { diff --git a/tests/unit/index-config.test.ts b/tests/unit/index-config.test.ts index 8562cbb..64118e6 100644 --- a/tests/unit/index-config.test.ts +++ b/tests/unit/index-config.test.ts @@ -1,26 +1,40 @@ -import MultiAuthPlugin from '../../src/index.js' +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' + +async function importPlugin() { + return (await import('../../src/index.js')).default +} + +function pluginInput() { + return { + client: {}, + $: (() => ({ nothrow: () => ({ catch: () => undefined }) })) as any, + serverUrl: new URL('http://localhost:3000'), + project: { id: 'test' }, + directory: '/tmp' + } as any +} describe('runtime model injection', () => { const originalEnv = process.env beforeEach(() => { + jest.resetModules() + jest.restoreAllMocks() process.env = { ...originalEnv } delete process.env.OPENCODE_MULTI_AUTH_CODEX_LATEST_MODEL delete process.env.OPENCODE_MULTI_AUTH_INJECT_MODELS }) afterEach(() => { + jest.restoreAllMocks() process.env = originalEnv + jest.resetModules() }) it('injects GPT-5.5 and fast mode by default', async () => { - const hooks = await MultiAuthPlugin({ - client: {}, - $: (() => ({ nothrow: () => ({ catch: () => undefined }) })) as any, - serverUrl: new URL('http://localhost:3000'), - project: { id: 'test' }, - directory: '/tmp' - } as any) + const MultiAuthPlugin = await importPlugin() + const hooks = await MultiAuthPlugin(pluginInput()) const config = { provider: { openai: { @@ -41,4 +55,17 @@ describe('runtime model injection', () => { expect(config.provider.openai.whitelist).toContain('gpt-5.5') expect(config.provider.openai.whitelist).toContain('gpt-5.5-fast') }) + + it('registers metrics shutdown flush hooks from the plugin entry path', async () => { + const processOnSpy = jest.spyOn(process, 'on') + const MultiAuthPlugin = await importPlugin() + + await MultiAuthPlugin(pluginInput()) + + const hookCalls = processOnSpy.mock.calls.filter((call: any[]) => ['beforeExit', 'SIGINT', 'SIGTERM', 'exit'].includes(call[0])) + expect(hookCalls.map((call: any[]) => call[0])).toEqual(['beforeExit', 'SIGINT', 'SIGTERM', 'exit']) + for (const [event, listener] of hookCalls as Array<[NodeJS.Signals | 'beforeExit' | 'exit', (...args: any[]) => void]>) { + process.removeListener(event, listener) + } + }) }) diff --git a/tests/unit/index-sticky.test.ts b/tests/unit/index-sticky.test.ts new file mode 100644 index 0000000..75dc6d9 --- /dev/null +++ b/tests/unit/index-sticky.test.ts @@ -0,0 +1,485 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' +import * as fs from 'node:fs' +import * as os from 'node:os' +import * as path from 'node:path' + +const esmJest = jest as typeof jest & { + unstable_mockModule: (moduleName: string, factory: () => Record) => void +} + +const mockGetNextAccount = jest.fn() +const mockClearAuthInvalid = jest.fn() +const mockMarkAuthInvalid = jest.fn() +const mockMarkModelUnsupported = jest.fn() +const mockMarkRateLimited = jest.fn() +const mockMarkWorkspaceDeactivated = jest.fn() + +const defaultStickyConfig = { + identitySources: [ + 'header:x-session-affinity', + 'header:session-id', + 'header:session_id', + 'header:conversation_id', + 'body:metadata.session_id', + 'body:metadata.conversation_id' + ] as Array< + 'header:x-session-affinity' | + 'header:session-id' | + 'header:session_id' | + 'header:conversation_id' | + 'body:metadata.session_id' | + 'body:metadata.conversation_id' | + 'body:prompt_cache_key' + >, + allowPromptCacheKey: false, + ttlMs: 86_400_000, + maxEntries: 1000, + maxFileBytes: 1_048_576 +} + +let stickyEnabledState = false +let stickyConfigState = { ...defaultStickyConfig } +let rotationStrategyState: 'round-robin' | 'least-used' = 'round-robin' +let originalFetch: typeof globalThis.fetch | undefined +let mockFetch: jest.Mock | undefined +const coldStartRedIt = process.argv.some((arg) => arg.includes('index-sticky.test.ts')) ? it : it.skip + +let MultiAuthPlugin: typeof import('../../src/index.js').default + +beforeAll(async () => { + await Promise.all([ + esmJest.unstable_mockModule('../../src/rotation', () => ({ + getNextAccount: mockGetNextAccount, + clearAuthInvalid: mockClearAuthInvalid, + markAuthInvalid: mockMarkAuthInvalid, + markModelUnsupported: mockMarkModelUnsupported, + markRateLimited: mockMarkRateLimited, + markWorkspaceDeactivated: mockMarkWorkspaceDeactivated + })), + esmJest.unstable_mockModule('../../src/settings', () => ({ + getRuntimeSettings: () => ({ + settings: { + rotationStrategy: rotationStrategyState, + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: {}, + featureFlags: { + antigravityEnabled: false, + stickySessionsEnabled: stickyEnabledState + } + }, + source: 'persisted' + }), + getStickySessionRuntimeSettings: () => ({ + ...stickyConfigState + }) + })) + ]) + ;({ default: MultiAuthPlugin } = await import('../../src/index.js')) +}) + +describe('Sticky identity request plumbing', () => { + it('resolves the canonical sticky identity when an allowlisted identity is explicitly allowed', async () => { + const { resolveStickyIdentity } = await import('../../src/sticky-identity.js') + + const sticky = resolveStickyIdentity({ + headers: new Headers({ + session_id: ' Session-123 ', + conversation_id: 'ignored-conversation' + }), + body: { + metadata: { + session_id: 'body-session-should-not-win' + }, + prompt_cache_key: 'cache-123' + }, + allowPromptCacheKey: false, + identitySources: [ + 'header:session_id', + 'header:conversation_id', + 'body:metadata.session_id', + 'body:metadata.conversation_id' + ] + }) + + expect(sticky).toEqual({ + source: 'header:session_id', + canonical: 'session-123', + hash: expect.any(String) + }) + }) + + it('returns null when no canonical sticky identity can be derived', async () => { + const { resolveStickyIdentity } = await import('../../src/sticky-identity.js') + + const sticky = resolveStickyIdentity({ + headers: new Headers(), + body: { + metadata: {}, + prompt_cache_key: undefined + }, + allowPromptCacheKey: false, + identitySources: ['header:session_id'] + }) + + expect(sticky).toBeNull() + }) + + it('does not derive sticky identity from prompt_cache_key without explicit authorization', async () => { + const { resolveStickyIdentity } = await import('../../src/sticky-identity.js') + + const sticky = resolveStickyIdentity({ + headers: new Headers(), + body: { + prompt_cache_key: 'cache-only-123' + }, + allowPromptCacheKey: false, + identitySources: ['body:prompt_cache_key'] + }) + + expect(sticky).toBeNull() + }) +}) + +describe('Sticky account-selection context plumbing', () => { + const originalEnv = process.env + const testDir = path.join(os.tmpdir(), `oma-index-sticky-${Date.now()}`) + const testStoreFile = path.join(testDir, 'accounts.json') + + function createAccessToken(accountId: string): string { + const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url') + const payload = Buffer.from(JSON.stringify({ + 'https://api.openai.com/auth': { + chatgpt_account_id: accountId + } + })).toString('base64url') + + return `${header}.${payload}.signature` + } + + function seedStore(): void { + fs.mkdirSync(testDir, { recursive: true }) + fs.writeFileSync( + testStoreFile, + JSON.stringify({ + version: 2, + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + usageCount: 0, + enabled: true + } + }, + activeAlias: null, + rotationIndex: 0, + lastRotation: 0, + forcedAlias: null, + forcedUntil: null, + previousRotationStrategy: null, + forcedBy: null, + rotationStrategy: 'round-robin' + }, null, 2), + { mode: 0o600 } + ) + } + + beforeEach(() => { + jest.clearAllMocks() + stickyEnabledState = false + stickyConfigState = { ...defaultStickyConfig } + rotationStrategyState = 'round-robin' + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: testDir, + OPENCODE_MULTI_AUTH_STORE_FILE: testStoreFile + } + seedStore() + originalFetch = globalThis.fetch + mockFetch = jest.fn().mockResolvedValue( + new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { 'content-type': 'application/json' } + }) + ) + globalThis.fetch = mockFetch as unknown as typeof globalThis.fetch + }) + + afterEach(() => { + if (originalFetch) { + globalThis.fetch = originalFetch + originalFetch = undefined + } + mockFetch = undefined + process.env = originalEnv + if (fs.existsSync(testDir)) { + fs.rmSync(testDir, { recursive: true, force: true }) + } + }) + + async function invokePluginFetch(options: { + stickyEnabled: boolean + headers?: Record + body?: Record + stickyConfig?: { + identitySources: Array< + 'header:session_id' | + 'header:conversation_id' | + 'body:metadata.session_id' | + 'body:metadata.conversation_id' | + 'body:prompt_cache_key' + > + allowPromptCacheKey: boolean + ttlMs?: number + maxEntries?: number + maxFileBytes?: number + } + }): Promise<{ getNextAccount: jest.Mock; fetchSpy: jest.Mock }> { + mockGetNextAccount.mockClear() + mockFetch?.mockClear() + stickyEnabledState = options.stickyEnabled + stickyConfigState = { + ...defaultStickyConfig, + ...options.stickyConfig, + identitySources: options.stickyConfig?.identitySources || defaultStickyConfig.identitySources + } + + mockGetNextAccount.mockResolvedValue({ + account: { + alias: 'alpha', + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + usageCount: 1, + enabled: true + }, + token: createAccessToken('acct-123') + } as any) + + const hooks = await MultiAuthPlugin({ + client: {}, + $: (() => ({ nothrow: () => ({ catch: () => undefined }) })) as any, + serverUrl: new URL('http://localhost:3000'), + project: { id: 'test' }, + directory: testDir + } as any) + + const auth = await (hooks as any).auth.loader(async () => null as any, {} as any) + if (typeof auth?.fetch === 'function') { + await auth.fetch('https://api.openai.com/v1/responses', { + method: 'POST', + headers: options.headers, + body: JSON.stringify({ + model: 'gpt-5.4', + stream: false, + ...options.body + }) + }) + } + + return { getNextAccount: mockGetNextAccount, fetchSpy: mockFetch as jest.Mock } + } + + function headersToRecord(headers: unknown): Record { + if (!headers) { + return {} + } + + if (headers instanceof Headers) { + return Object.fromEntries(headers.entries()) + } + + if (Array.isArray(headers)) { + return Object.fromEntries(headers as Array<[string, string]>) + } + + if (typeof headers === 'object') { + return Object.fromEntries( + Object.entries(headers as Record).map(([key, value]) => [key.toLowerCase(), String(value)]) + ) + } + + return {} + } + + function callHeaders(call: unknown[]): Record { + const first = call[0] + const second = call[1] + + if (second && typeof second === 'object' && 'headers' in (second as Record)) { + return headersToRecord((second as Record).headers) + } + + if (first instanceof Request) { + return headersToRecord(first.headers) + } + + return {} + } + + it('passes sticky context into getNextAccount only when the sticky flag is enabled and a canonical identity exists', async () => { + const { getNextAccount, fetchSpy } = await invokePluginFetch({ + stickyEnabled: true, + headers: { + session_id: ' Session-123 ' + }, + body: { + prompt_cache_key: 'cache-123' + } + }) + + expect(getNextAccount).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + model: 'gpt-5.4', + sticky: { + source: 'header:session_id', + canonical: 'session-123', + hash: expect.any(String) + } + }) + ) + + const headers = callHeaders(fetchSpy.mock.calls[0] ?? []) + expect(headers.conversation_id).toBe('cache-123') + expect(headers.session_id).toBe('cache-123') + }) + + it('keeps non-sticky selection unchanged when the sticky flag is disabled or no canonical identity exists', async () => { + const disabled = await invokePluginFetch({ + stickyEnabled: false, + headers: { + session_id: 'Session-Disabled' + }, + body: { + prompt_cache_key: 'cache-disabled' + } + }) + + expect(disabled.getNextAccount).toHaveBeenCalledWith(expect.any(Object), { model: 'gpt-5.4' }) + expect(disabled.fetchSpy).toBeDefined() + + const noIdentity = await invokePluginFetch({ + stickyEnabled: true, + headers: {}, + body: {} + }) + + expect(noIdentity.getNextAccount).toHaveBeenCalledWith(expect.any(Object), { model: 'gpt-5.4' }) + expect(noIdentity.fetchSpy).toBeDefined() + }) + + it('uses persisted sticky identity source ordering and prompt-cache authorization at runtime', async () => { + const { getNextAccount, fetchSpy } = await invokePluginFetch({ + stickyEnabled: true, + headers: { + session_id: 'session-should-be-ignored' + }, + body: { + prompt_cache_key: 'cache-123' + }, + stickyConfig: { + identitySources: ['body:prompt_cache_key'], + allowPromptCacheKey: true + } + }) + + expect(getNextAccount).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + model: 'gpt-5.4', + sticky: { + source: 'body:prompt_cache_key', + canonical: 'cache-123', + hash: expect.any(String) + } + }) + ) + + const headers = callHeaders(fetchSpy.mock.calls[0] ?? []) + expect(headers.conversation_id).toBe('cache-123') + expect(headers.session_id).toBe('cache-123') + }) + + coldStartRedIt('RED: plugin cold start routes through metrics-cache least-used telemetry without startWebConsole', async () => { + const accessAlpha = createAccessToken('acct-alpha') + const accessBeta = createAccessToken('acct-beta') + rotationStrategyState = 'least-used' + fs.writeFileSync( + testStoreFile, + JSON.stringify({ + version: 2, + accounts: { + alpha: { + alias: 'alpha', + accessToken: accessAlpha, + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + usageCount: 0, + enabled: true + }, + beta: { + alias: 'beta', + accessToken: accessBeta, + refreshToken: 'refresh-beta', + expiresAt: Date.now() + 60_000, + usageCount: 0, + enabled: true + } + }, + activeAlias: null, + rotationIndex: 0, + lastRotation: 0, + forcedAlias: null, + forcedUntil: null, + previousRotationStrategy: null, + forcedBy: null, + rotationStrategy: 'least-used' + }, null, 2), + { mode: 0o600 } + ) + fs.writeFileSync( + path.join(testDir, 'account-metrics.json'), + JSON.stringify({ + version: 1, + updatedAt: 1_700_000_000_000, + metrics: { + alpha: { usageCount: 42, lastUsed: 1_700_000_200_000 }, + beta: { usageCount: 1, lastUsed: 1_700_000_100_000 } + } + }, null, 2), + { mode: 0o600 } + ) + + mockGetNextAccount.mockResolvedValue({ + account: { + alias: 'alpha', + accessToken: accessAlpha, + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + usageCount: 1, + enabled: true + }, + token: accessAlpha + } as any) + + const hooks = await MultiAuthPlugin({ + client: {}, + $: (() => ({ nothrow: () => ({ catch: () => undefined }) })) as any, + serverUrl: new URL('http://localhost:3000'), + project: { id: 'test' }, + directory: testDir + } as any) + const auth = await (hooks as any).auth.loader(async () => null as any, {} as any) + await auth.fetch('https://api.openai.com/v1/responses', { + method: 'POST', + body: JSON.stringify({ model: 'gpt-5.4', stream: false }) + }) + + const headers = callHeaders(mockFetch?.mock.calls[0] ?? []) + expect(headers['chatgpt-account-id']).toBe('acct-beta') + }) +}) diff --git a/tests/unit/limits-refresh.test.ts b/tests/unit/limits-refresh.test.ts index 825b0a1..20ccb2b 100644 --- a/tests/unit/limits-refresh.test.ts +++ b/tests/unit/limits-refresh.test.ts @@ -1,38 +1,50 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. import { jest } from '@jest/globals' import type { AccountCredentials } from '../../src/types.js' +const esmJest = jest as typeof jest & { + unstable_mockModule: (moduleName: string, factory: () => Record) => void +} + const updateAccount = jest.fn() const loadStore = jest.fn() -const probeRateLimitsForAccount = jest.fn<() => Promise>() -const fetchUsageRateLimitsForAccount = jest.fn<() => Promise>() +const probeRateLimitsForAccount = jest.fn() +const fetchUsageRateLimitsForAccount = jest.fn() const logError = jest.fn() const logInfo = jest.fn() const markAuthInvalid = jest.fn() const markWorkspaceDeactivated = jest.fn() +const setMetrics = jest.fn() +const updateRateLimits = jest.fn() -jest.unstable_mockModule('../../src/store.js', () => ({ +esmJest.unstable_mockModule('../../src/store.js', () => ({ loadStore, updateAccount })) -jest.unstable_mockModule('../../src/probe-limits.js', () => ({ +esmJest.unstable_mockModule('../../src/probe-limits.js', () => ({ probeRateLimitsForAccount })) -jest.unstable_mockModule('../../src/usage-limits.js', () => ({ +esmJest.unstable_mockModule('../../src/usage-limits.js', () => ({ fetchUsageRateLimitsForAccount })) -jest.unstable_mockModule('../../src/logger.js', () => ({ +esmJest.unstable_mockModule('../../src/logger.js', () => ({ logError, logInfo })) -jest.unstable_mockModule('../../src/rotation.js', () => ({ +esmJest.unstable_mockModule('../../src/rotation.js', () => ({ markAuthInvalid, markWorkspaceDeactivated })) +esmJest.unstable_mockModule('../../src/metrics-store.js', () => ({ + setMetrics, + updateRateLimits +})) + let refreshRateLimitsForAccount: typeof import('../../src/limits-refresh.js').refreshRateLimitsForAccount const baseAccount: AccountCredentials = { @@ -71,7 +83,7 @@ describe('refreshRateLimitsForAccount', () => { expect(probeRateLimitsForAccount).not.toHaveBeenCalled() expect(markAuthInvalid).toHaveBeenCalledWith('dead-token') expect(markWorkspaceDeactivated).not.toHaveBeenCalled() - expect(updateAccount).toHaveBeenLastCalledWith( + expect(setMetrics).toHaveBeenCalledWith( 'dead-token', expect.objectContaining({ limitStatus: 'error', @@ -80,6 +92,10 @@ describe('refreshRateLimitsForAccount', () => { limitsConfidence: expect.any(String) }) ) + expect(updateAccount).not.toHaveBeenCalledWith( + 'dead-token', + expect.objectContaining({ limitStatus: expect.anything() }) + ) expect(result).toEqual({ alias: 'dead-token', updated: false, @@ -129,15 +145,56 @@ describe('refreshRateLimitsForAccount', () => { }) expect(probeRateLimitsForAccount).not.toHaveBeenCalled() - expect(updateAccount).toHaveBeenLastCalledWith( + expect(updateRateLimits).toHaveBeenCalledWith( + 'dead-token', + expect.objectContaining({ + fiveHour: expect.objectContaining({ remaining: 50 }), + weekly: expect.objectContaining({ remaining: 80 }) + }) + ) + expect(setMetrics).toHaveBeenCalledWith( 'dead-token', expect.objectContaining({ limitStatus: 'success', + limitError: undefined, + lastLimitProbeAt: expect.any(Number), + limitsConfidence: expect.any(String) + }) + ) + expect(updateAccount).toHaveBeenLastCalledWith( + 'dead-token', + expect.objectContaining({ authInvalid: false, authInvalidatedAt: undefined, planType: 'pro' }) ) + expect(updateAccount.mock.calls.at(-1)?.[1]).not.toHaveProperty('rateLimits') + expect(updateAccount.mock.calls.at(-1)?.[1]).not.toHaveProperty('limitStatus') expect(result).toEqual({ alias: 'dead-token', updated: true }) }) + + it('clears stale rate-limited state after a healthy successful usage refresh', async () => { + fetchUsageRateLimitsForAccount.mockResolvedValue({ + source: 'usage-api', + rateLimits: { + fiveHour: { remaining: 50, resetAt: Date.now() + 60_000 }, + weekly: { remaining: 80, resetAt: Date.now() + 120_000 } + } + }) + + await refreshRateLimitsForAccount({ + ...baseAccount, + rateLimitedUntil: Date.now() + 60_000 + }) + + expect(updateAccount).toHaveBeenLastCalledWith( + 'dead-token', + expect.objectContaining({ + rateLimitedUntil: undefined + }) + ) + expect(updateAccount.mock.calls.at(-1)?.[1]).not.toHaveProperty('rateLimits') + expect(updateAccount.mock.calls.at(-1)?.[1]).not.toHaveProperty('limitStatus') + }) }) diff --git a/tests/unit/metrics-store.test.ts b/tests/unit/metrics-store.test.ts new file mode 100644 index 0000000..81ad1ff --- /dev/null +++ b/tests/unit/metrics-store.test.ts @@ -0,0 +1,203 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' +import { createRequire } from 'node:module' +import * as fs from 'node:fs' +import * as os from 'node:os' +import * as path from 'node:path' + +const require = createRequire(import.meta.url) +const fsForSpies = require('node:fs') as typeof import('node:fs') + +const originalEnv = process.env + +async function importMetricsStore() { + return import('../../src/metrics-store.js') +} + +describe('account metrics sidecar store', () => { + let testDir: string + let testStoreFile: string + + beforeEach(() => { + jest.resetModules() + testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'oma-metrics-store-')) + testStoreFile = path.join(testDir, 'accounts.json') + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: testDir, + OPENCODE_MULTI_AUTH_STORE_FILE: testStoreFile + } + }) + + afterEach(() => { + jest.useRealTimers() + jest.restoreAllMocks() + process.env = originalEnv + fs.rmSync(testDir, { recursive: true, force: true }) + jest.resetModules() + }) + + it('resolves account-metrics.json beside the active accounts.json path', async () => { + const nestedDir = path.join(testDir, 'nested-store') + process.env.OPENCODE_MULTI_AUTH_STORE_FILE = path.join(nestedDir, 'accounts.override.json') + const metricsStore = await importMetricsStore() + + expect(metricsStore.getMetricsStorePath()).toBe(path.join(nestedDir, 'account-metrics.json')) + }) + + it('lazily loads sidecar metrics once and serves later reads from the cache', async () => { + const metricsPath = path.join(testDir, 'account-metrics.json') + fs.writeFileSync( + metricsPath, + JSON.stringify({ + version: 1, + updatedAt: 1_700_000_000_000, + metrics: { + alpha: { usageCount: 12, lastUsed: 1_700_000_001_000 }, + beta: { usageCount: 2, lastLimitErrorAt: 1_700_000_002_000 } + } + }), + 'utf8' + ) + const metricsStore = await importMetricsStore() + const readSpy = jest.spyOn(fsForSpies, 'readFileSync') + + expect(metricsStore.getMetrics('alpha')).toEqual({ usageCount: 12, lastUsed: 1_700_000_001_000 }) + expect(metricsStore.getMetrics('beta')).toEqual({ usageCount: 2, lastLimitErrorAt: 1_700_000_002_000 }) + expect(readSpy).toHaveBeenCalledTimes(1) + }) + + it('stores only the metrics fields for each alias and flushes them to the sidecar', async () => { + const metricsStore = await importMetricsStore() + + metricsStore.setMetrics('alpha', { + lastRefresh: '2026-01-01T00:00:00.000Z', + lastSeenAt: 1, + lastActiveUntil: 2, + lastUsed: 3, + usageCount: 4, + rateLimits: { fiveHour: { remaining: 40, limit: 100, resetAt: 10, updatedAt: 11 } }, + limitStatus: 'success', + limitError: 'soft limit', + lastLimitProbeAt: 12, + lastLimitErrorAt: 13, + limitsConfidence: 'fresh' + }) + + await metricsStore.flush() + const persisted = JSON.parse(fs.readFileSync(path.join(testDir, 'account-metrics.json'), 'utf8')) + + expect(persisted).toEqual({ + version: 1, + updatedAt: expect.any(Number), + metrics: { + alpha: expect.objectContaining({ + lastRefresh: '2026-01-01T00:00:00.000Z', + lastSeenAt: 1, + lastActiveUntil: 2, + lastUsed: 3, + usageCount: 4, + rateLimits: { fiveHour: { remaining: 40, limit: 100, resetAt: 10, updatedAt: 11 } }, + limitStatus: 'success', + limitError: 'soft limit', + lastLimitProbeAt: 12, + lastLimitErrorAt: 13, + limitsConfidence: 'fresh' + }) + } + }) + expect(persisted.metrics.alpha.rateLimitHistory).toHaveLength(1) + }) + + it('coalesces rapid set calls instead of writing on the hot path, while flush persists synchronously', async () => { + jest.useFakeTimers() + const metricsStore = await importMetricsStore() + const writeSpy = jest.spyOn(fsForSpies, 'writeFileSync') + + for (let i = 0; i < 25; i++) { + metricsStore.setMetrics('alpha', { usageCount: i }) + } + + expect(writeSpy).not.toHaveBeenCalled() + expect(fs.existsSync(path.join(testDir, 'account-metrics.json'))).toBe(false) + + await metricsStore.flush() + + expect(writeSpy).toHaveBeenCalledTimes(1) + expect(JSON.parse(fs.readFileSync(path.join(testDir, 'account-metrics.json'), 'utf8')).metrics.alpha.usageCount).toBe(24) + }) + + it('flushes pending debounced writes to the sidecar path captured before env restoration', async () => { + jest.useFakeTimers() + const metricsStore = await importMetricsStore() + const restoredDir = path.join(testDir, 'restored-store') + const restoredMetricsPath = path.join(restoredDir, 'account-metrics.json') + + metricsStore.setMetrics('test-alias', { usageCount: 123, limitStatus: 'success' }) + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: restoredDir, + OPENCODE_MULTI_AUTH_STORE_FILE: path.join(restoredDir, 'accounts.json') + } + + metricsStore.flushSync(true) + await jest.advanceTimersByTimeAsync(metricsStore.METRICS_FLUSH_DEBOUNCE_MS) + + expect(fs.existsSync(restoredMetricsPath)).toBe(false) + expect(JSON.parse(fs.readFileSync(path.join(testDir, 'account-metrics.json'), 'utf8')).metrics['test-alias']).toEqual( + expect.objectContaining({ usageCount: 123, limitStatus: 'success' }) + ) + }) + + it('derives rate-limit history with existing dedup semantics and caps history at 160 entries', async () => { + const metricsStore = await importMetricsStore() + + metricsStore.updateRateLimits('alpha', { + fiveHour: { remaining: 10, limit: 100, resetAt: 1_700_000_000_000, updatedAt: 1_700_000_000_100 } + }) + metricsStore.updateRateLimits('alpha', { + fiveHour: { remaining: 10, limit: 100, resetAt: 1_700_000_000_000, updatedAt: 1_700_000_000_200 } + }) + expect(metricsStore.getMetrics('alpha')?.rateLimitHistory).toHaveLength(1) + + for (let i = 0; i < 170; i++) { + metricsStore.updateRateLimits('alpha', { + fiveHour: { remaining: i, limit: 100, resetAt: 1_700_000_001_000 + i, updatedAt: 1_700_000_002_000 + i } + }) + } + + const history = metricsStore.getMetrics('alpha')?.rateLimitHistory ?? [] + expect(history).toHaveLength(160) + expect(history[0].fiveHour?.remaining).toBe(10) + expect(history[history.length - 1].fiveHour?.remaining).toBe(169) + }) + + it('registers shutdown flush hooks once and routes async/sync flushes to the right events', async () => { + const metricsStore = await importMetricsStore() + const handlers = new Map any>() + const processOnSpy = jest.spyOn(process, 'on').mockImplementation(((event: string | symbol, listener: (...args: any[]) => any) => { + handlers.set(event, listener) + return process + }) as typeof process.on) + const flushSpy = jest.fn(async () => {}) + const flushSyncSpy = jest.fn(() => {}) + const killSpy = jest.spyOn(process, 'kill').mockImplementation((() => true) as typeof process.kill) + + ;(metricsStore as any).registerMetricsFlushHooks({ flush: flushSpy, flushSync: flushSyncSpy }) + ;(metricsStore as any).registerMetricsFlushHooks({ flush: flushSpy, flushSync: flushSyncSpy }) + + expect(processOnSpy.mock.calls.map((call: any[]) => call[0])).toEqual(['beforeExit', 'SIGINT', 'SIGTERM', 'exit']) + + await handlers.get('beforeExit')?.() + await handlers.get('SIGINT')?.() + await handlers.get('SIGTERM')?.() + await Promise.resolve() + await Promise.resolve() + handlers.get('exit')?.() + + expect(flushSpy).toHaveBeenCalledTimes(3) + expect(flushSyncSpy).toHaveBeenCalledTimes(1) + expect(killSpy).toHaveBeenCalledWith(process.pid, 'SIGINT') + expect(killSpy).toHaveBeenCalledWith(process.pid, 'SIGTERM') + }) +}) diff --git a/tests/unit/refresh-queue.test.ts b/tests/unit/refresh-queue.test.ts index b26962e..e51d920 100644 --- a/tests/unit/refresh-queue.test.ts +++ b/tests/unit/refresh-queue.test.ts @@ -1,26 +1,37 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. import { jest } from '@jest/globals' import type { AccountCredentials } from '../../src/types.js' -const refreshRateLimitsForAccount = jest.fn<(account: AccountCredentials) => Promise>() +const esmJest = jest as typeof jest & { + unstable_mockModule: (moduleName: string, factory: () => Record) => void +} + +const refreshRateLimitsForAccount = jest.fn() const updateAccount = jest.fn() +const setMetrics = jest.fn() const logInfo = jest.fn() const logWarn = jest.fn() -jest.unstable_mockModule('../../src/limits-refresh.js', () => ({ +esmJest.unstable_mockModule('../../src/limits-refresh.js', () => ({ refreshRateLimitsForAccount })) -jest.unstable_mockModule('../../src/store.js', () => ({ +esmJest.unstable_mockModule('../../src/store.js', () => ({ updateAccount })) -jest.unstable_mockModule('../../src/logger.js', () => ({ +esmJest.unstable_mockModule('../../src/metrics-store.js', () => ({ + setMetrics +})) + +esmJest.unstable_mockModule('../../src/logger.js', () => ({ logInfo, logWarn })) let startRefreshQueue: typeof import('../../src/refresh-queue.js').startRefreshQueue let getRefreshQueueState: typeof import('../../src/refresh-queue.js').getRefreshQueueState +let stopRefreshQueue: typeof import('../../src/refresh-queue.js').stopRefreshQueue const accounts: AccountCredentials[] = Array.from({ length: 5 }, (_, index) => ({ alias: `acc-${index + 1}`, @@ -41,7 +52,7 @@ async function waitForQueueToFinish(): Promise { } beforeAll(async () => { - ;({ startRefreshQueue, getRefreshQueueState } = await import('../../src/refresh-queue.js')) + ;({ startRefreshQueue, getRefreshQueueState, stopRefreshQueue } = await import('../../src/refresh-queue.js')) }) beforeEach(() => { @@ -84,6 +95,14 @@ describe('refresh queue concurrency', () => { }) ) expect(refreshRateLimitsForAccount).toHaveBeenCalledTimes(5) + expect(updateAccount).not.toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ limitStatus: expect.anything() }) + ) + expect(setMetrics).toHaveBeenCalledWith( + 'acc-1', + expect.objectContaining({ limitStatus: 'queued', limitError: undefined }) + ) }) it('caps requested concurrency at 20 workers', async () => { @@ -102,4 +121,30 @@ describe('refresh queue concurrency', () => { expect(queue.concurrency).toBe(20) }) + + it('routes stopped queue telemetry to metrics instead of accounts.json state', async () => { + process.env.OPENCODE_MULTI_AUTH_REFRESH_QUEUE_CONCURRENCY = '1' + let releaseFirst!: () => void + refreshRateLimitsForAccount.mockImplementationOnce(async (account: AccountCredentials) => { + await new Promise((resolve) => { + releaseFirst = resolve + }) + return { alias: account.alias, updated: true } + }) + + startRefreshQueue(accounts.slice(0, 3)) + await new Promise((resolve) => setTimeout(resolve, 5)) + stopRefreshQueue() + releaseFirst() + await waitForQueueToFinish() + + expect(setMetrics).toHaveBeenCalledWith( + 'acc-2', + expect.objectContaining({ limitStatus: 'stopped', limitError: 'Stopped by user' }) + ) + expect(updateAccount).not.toHaveBeenCalledWith( + 'acc-2', + expect.objectContaining({ limitStatus: 'stopped' }) + ) + }) }) diff --git a/tests/unit/rotation-strategy.test.ts b/tests/unit/rotation-strategy.test.ts index d9ba6f6..f78c174 100644 --- a/tests/unit/rotation-strategy.test.ts +++ b/tests/unit/rotation-strategy.test.ts @@ -1,14 +1,27 @@ import * as fs from 'node:fs' import * as path from 'node:path' import * as os from 'node:os' +import { createHash } from 'node:crypto' import { getNextAccount } from '../../src/rotation.js' import { loadStore, saveStore } from '../../src/store.js' import { updateSettings } from '../../src/settings.js' +import { activateForce } from '../../src/force-mode.js' +import { flushSync as flushMetricsSync } from '../../src/metrics-store.js' import { DEFAULT_CONFIG, type AccountCredentials } from '../../src/types.js' -const TEST_DIR = path.join(os.tmpdir(), `oma-rotation-test-${Date.now()}`) -const TEST_STORE_FILE = path.join(TEST_DIR, 'accounts.json') +let TEST_DIR = path.join(os.tmpdir(), `oma-rotation-test-${Date.now()}`) +let TEST_STORE_FILE = path.join(TEST_DIR, 'accounts.json') +let TEST_STICKY_FILE = path.join(TEST_DIR, 'sticky-sessions.json') +let TEST_METRICS_FILE = path.join(TEST_DIR, 'account-metrics.json') const originalEnv = process.env +const originalFetch = global.fetch +const coldStartRedIt = process.argv.some((arg) => arg.includes('rotation-strategy.test.ts')) ? it : it.skip + +type StickySelection = { + source: 'header:session_id' + canonical: string + hash: string +} function createAccount(alias: string, usageCount: number): AccountCredentials { return { @@ -28,8 +41,116 @@ function createPlanAccount(alias: string, usageCount: number, planType: string): } } +function createStickySelection(rawIdentity: string): StickySelection { + const canonical = rawIdentity.trim().toLowerCase() + return { + source: 'header:session_id', + canonical, + hash: createHash('sha256').update(canonical).digest('hex') + } +} + +function writeStickyMappings(entries: Record): void { + fs.writeFileSync( + TEST_STICKY_FILE, + JSON.stringify( + { + version: 1, + updatedAt: 1_700_000_000_000, + entries + }, + null, + 2 + ), + 'utf8' + ) +} + +function writeMetrics(metrics: Record>): void { + fs.writeFileSync( + TEST_METRICS_FILE, + JSON.stringify( + { + version: 1, + updatedAt: 1_700_000_000_000, + metrics + }, + null, + 2 + ), + 'utf8' + ) +} + +function writeStateStore(accounts: Record>, extra: Record = {}): void { + fs.mkdirSync(TEST_DIR, { recursive: true }) + fs.writeFileSync( + TEST_STORE_FILE, + JSON.stringify( + { + version: 3, + accounts, + activeAlias: null, + rotationIndex: 0, + lastRotation: 0, + rotationStrategy: 'least-used', + ...extra + }, + null, + 2 + ), + 'utf8' + ) +} + +function stateAccount(alias: string, extra: Record = {}): Record { + return { + alias, + accessToken: `token-${alias}`, + refreshToken: `refresh-${alias}`, + expiresAt: Date.now() + 60 * 60 * 1000, + enabled: true, + ...extra + } +} + +function readJson(file: string): any { + return JSON.parse(fs.readFileSync(file, 'utf8')) +} + +function readStickyMappings(): { + version: number + updatedAt: number + entries: Record +} { + return JSON.parse(fs.readFileSync(TEST_STICKY_FILE, 'utf8')) as { + version: number + updatedAt: number + entries: Record + } +} + +function enableStickySessions(rotationStrategy: 'round-robin' | 'least-used' = 'round-robin'): void { + const update = updateSettings( + { + rotationStrategy, + featureFlags: { + antigravityEnabled: false, + stickySessionsEnabled: true + } as any + }, + 'test' + ) + + expect(update.success).toBe(true) +} + describe('Rotation Strategy Runtime Behavior', () => { beforeEach(() => { + TEST_DIR = path.join(os.tmpdir(), `oma-rotation-test-${Date.now()}-${Math.random().toString(16).slice(2)}`) + TEST_STORE_FILE = path.join(TEST_DIR, 'accounts.json') + TEST_STICKY_FILE = path.join(TEST_DIR, 'sticky-sessions.json') + TEST_METRICS_FILE = path.join(TEST_DIR, 'account-metrics.json') process.env = { ...originalEnv, OPENCODE_MULTI_AUTH_STORE_DIR: TEST_DIR, @@ -47,6 +168,7 @@ describe('Rotation Strategy Runtime Behavior', () => { afterEach(() => { process.env = originalEnv + global.fetch = originalFetch if (fs.existsSync(TEST_DIR)) { fs.rmSync(TEST_DIR, { recursive: true, force: true }) } @@ -92,6 +214,139 @@ describe('Rotation Strategy Runtime Behavior', () => { expect(rotation?.account.alias).toBe('beta') }) + describe('allocator metrics cache reads and writes (RED)', () => { + it('orders least-used candidates by usageCount and lastUsed from account-metrics.json', async () => { + writeStateStore({ + alpha: stateAccount('alpha', { usageCount: 0, lastUsed: 1 }), + beta: stateAccount('beta', { usageCount: 99, lastUsed: 9_999 }) + }) + writeMetrics({ + alpha: { usageCount: 25, lastUsed: 1_700_000_200_000 }, + beta: { usageCount: 1, lastUsed: 1_700_000_100_000 } + }) + expect(updateSettings({ rotationStrategy: 'least-used' }, 'test').success).toBe(true) + + const rotation = await getNextAccount({ ...DEFAULT_CONFIG, rotationStrategy: 'least-used' }) + + expect(rotation?.account.alias).toBe('beta') + }) + + it('uses cache usageCount for the zero-usage priority adjustment', async () => { + writeStateStore({ + alpha: stateAccount('alpha', { usageCount: 7 }), + beta: stateAccount('beta', { usageCount: 0 }) + }) + writeMetrics({ + alpha: { usageCount: 0 }, + beta: { usageCount: 3 } + }) + expect(updateSettings({ rotationStrategy: 'round-robin' }, 'test').success).toBe(true) + + const rotation = await getNextAccount({ ...DEFAULT_CONFIG, rotationStrategy: 'round-robin' }) + + expect(rotation?.account.alias).toBe('beta') + }) + + it('scores recent limit errors from the cache lastLimitErrorAt value', async () => { + const now = Date.now() + writeStateStore({ + alpha: stateAccount('alpha'), + beta: stateAccount('beta') + }, { rotationStrategy: 'round-robin' }) + writeMetrics({ + alpha: { lastLimitErrorAt: now - 5_000, limitError: 'recent probe failure' }, + beta: {} + }) + expect(updateSettings({ rotationStrategy: 'round-robin' }, 'test').success).toBe(true) + + const rotation = await getNextAccount({ ...DEFAULT_CONFIG, rotationStrategy: 'round-robin' }) + + expect(rotation?.account.alias).toBe('beta') + }) + + it('writes allocator usage telemetry only to the metrics sidecar, not per-account fields in accounts.json', async () => { + writeStateStore({ + alpha: stateAccount('alpha'), + beta: stateAccount('beta') + }) + writeMetrics({ + alpha: { usageCount: 1, lastUsed: 1_700_000_000_000 }, + beta: { usageCount: 5, lastUsed: 1_700_000_100_000 } + }) + expect(updateSettings({ rotationStrategy: 'least-used' }, 'test').success).toBe(true) + + const rotation = await getNextAccount({ ...DEFAULT_CONFIG, rotationStrategy: 'least-used' }) + flushMetricsSync(true) + + const persistedState = readJson(TEST_STORE_FILE) + const persistedMetrics = readJson(TEST_METRICS_FILE) + expect(rotation?.account.alias).toBe('alpha') + expect(persistedState.accounts.alpha).not.toHaveProperty('usageCount') + expect(persistedState.accounts.alpha).not.toHaveProperty('lastUsed') + expect(persistedState.accounts.alpha).not.toHaveProperty('limitError') + expect(persistedMetrics.metrics.alpha.usageCount).toBe(2) + expect(typeof persistedMetrics.metrics.alpha.lastUsed).toBe('number') + expect(persistedMetrics.metrics.alpha.limitError).toBeUndefined() + }) + + it('returns selected accounts with sidecar rate limits and history for runtime consumers', async () => { + const history = [ + { + at: 1_700_000_010_000, + fiveHour: { remaining: 80, limit: 100, resetAt: 1_700_000_100_000 }, + weekly: { remaining: 700, limit: 1000, resetAt: 1_700_000_200_000 } + }, + { + at: 1_700_000_020_000, + fiveHour: { remaining: 70, limit: 100, resetAt: 1_700_000_100_000 }, + weekly: { remaining: 650, limit: 1000, resetAt: 1_700_000_200_000 } + } + ] + const rateLimits = { + fiveHour: { remaining: 70, limit: 100, resetAt: 1_700_000_100_000, updatedAt: 1_700_000_020_000 }, + weekly: { remaining: 650, limit: 1000, resetAt: 1_700_000_200_000, updatedAt: 1_700_000_020_000 } + } + writeStateStore({ + alpha: stateAccount('alpha'), + beta: stateAccount('beta') + }) + writeMetrics({ + alpha: { usageCount: 1, rateLimits, rateLimitHistory: history, limitsConfidence: 'fresh', limitStatus: 'success' }, + beta: { usageCount: 9 } + }) + expect(updateSettings({ rotationStrategy: 'least-used' }, 'test').success).toBe(true) + + const rotation = await getNextAccount({ ...DEFAULT_CONFIG, rotationStrategy: 'least-used' }) + + expect(rotation?.account.alias).toBe('alpha') + expect(rotation?.account.rateLimits).toEqual(rateLimits) + expect(rotation?.account.rateLimitHistory).toEqual(history) + expect(rotation?.account.limitsConfidence).toBe('fresh') + expect(rotation?.account.limitStatus).toBe('success') + }) + }) + + coldStartRedIt('RED: cold-start least-used selection reads usageCount and lastUsed from account-metrics.json cache', async () => { + const store = loadStore() + store.accounts.alpha = createAccount('alpha', 0) + store.accounts.beta = createAccount('beta', 0) + saveStore(store) + writeMetrics({ + alpha: { usageCount: 25, lastUsed: 1_700_000_200_000 }, + beta: { usageCount: 1, lastUsed: 1_700_000_100_000 } + }) + + const update = updateSettings({ rotationStrategy: 'least-used' }, 'test') + expect(update.success).toBe(true) + + const rotation = await getNextAccount({ + ...DEFAULT_CONFIG, + rotationStrategy: 'least-used' + }) + + expect(rotation?.account.alias).toBe('beta') + }) + it('prefers pro accounts first for non-spark models', async () => { const store = loadStore() store.accounts.plus = createPlanAccount('plus', 0, 'plus') @@ -141,4 +396,255 @@ describe('Rotation Strategy Runtime Behavior', () => { expect(rotation).toBeNull() }) + + describe('sticky session routing (RED)', () => { + it('uses the active strategy for the initial sticky assignment and persists the selected alias mapping', async () => { + const sticky = createStickySelection(' Session-001 ') + const store = loadStore() + store.accounts.alpha = createAccount('alpha', 10) + store.accounts.beta = createAccount('beta', 1) + saveStore(store) + enableStickySessions('least-used') + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation?.account.alias).toBe('beta') + expect(fs.existsSync(TEST_STICKY_FILE)).toBe(true) + expect(readStickyMappings().entries[sticky.hash]).toEqual({ + alias: 'beta', + createdAt: expect.any(Number), + lastUsedAt: expect.any(Number) + }) + }) + + it('reuses a healthy sticky account without advancing rotationIndex', async () => { + const sticky = createStickySelection('session-healthy') + const stickyNow = Date.now() + const store = loadStore() + store.accounts.alpha = createAccount('alpha', 0) + store.accounts.beta = createAccount('beta', 0) + store.rotationIndex = 0 + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'beta', + createdAt: stickyNow, + lastUsedAt: stickyNow + } + }) + enableStickySessions('round-robin') + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + const updatedStore = loadStore() + + expect(rotation?.account.alias).toBe('beta') + expect(updatedStore.rotationIndex).toBe(0) + }) + + it('writes sticky reuse telemetry to metrics without reintroducing per-account metric fields in accounts.json', async () => { + const sticky = createStickySelection('session-metrics-cache') + const stickyNow = Date.now() + writeStateStore({ + alpha: stateAccount('alpha'), + beta: stateAccount('beta') + }, { rotationStrategy: 'round-robin', rotationIndex: 0 }) + writeMetrics({ + alpha: { usageCount: 4, lastUsed: stickyNow - 2_000 }, + beta: { usageCount: 2, lastUsed: stickyNow - 1_000 } + }) + writeStickyMappings({ + [sticky.hash]: { + alias: 'beta', + createdAt: stickyNow, + lastUsedAt: stickyNow + } + }) + expect(updateSettings({ + rotationStrategy: 'round-robin', + featureFlags: { stickySessionsEnabled: true } as any + }, 'test').success).toBe(true) + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + flushMetricsSync(true) + + const persistedState = readJson(TEST_STORE_FILE) + const persistedMetrics = readJson(TEST_METRICS_FILE) + expect(rotation?.account.alias).toBe('beta') + expect(persistedState.accounts.beta).not.toHaveProperty('usageCount') + expect(persistedState.accounts.beta).not.toHaveProperty('lastUsed') + expect(persistedState.accounts.beta).not.toHaveProperty('limitError') + expect(persistedMetrics.metrics.beta.usageCount).toBe(3) + expect(typeof persistedMetrics.metrics.beta.lastUsed).toBe('number') + }) + + it('falls back to another valid account and rewrites the sticky mapping when the mapped alias is exhausted', async () => { + const sticky = createStickySelection('session-fallback') + const now = Date.now() + const store = loadStore() + store.accounts.alpha = { + ...createAccount('alpha', 0), + rateLimitedUntil: now + 60_000 + } + store.accounts.beta = createAccount('beta', 0) + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'alpha', + createdAt: now, + lastUsedAt: now + } + }) + enableStickySessions('round-robin') + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation?.account.alias).toBe('beta') + expect(readStickyMappings().entries[sticky.hash]?.alias).toBe('beta') + }) + + it('ignores an expired sticky mapping at runtime and falls back to normal selection', async () => { + const sticky = createStickySelection('session-expired-runtime') + const now = Date.now() + const store = loadStore() + store.accounts.alpha = createAccount('alpha', 5) + store.accounts.beta = createAccount('beta', 0) + store.rotationIndex = 0 + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'beta', + createdAt: now - (25 * 60 * 60 * 1000), + lastUsedAt: now - (25 * 60 * 60 * 1000) + } + }) + enableStickySessions('round-robin') + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation?.account.alias).toBe('alpha') + expect(readStickyMappings().entries[sticky.hash]?.alias).toBe('alpha') + }) + + it('keeps the sticky mapping when the mapped alias fails temporarily and no replacement is available', async () => { + const sticky = createStickySelection('session-temp-failure') + const now = Date.now() + const store = loadStore() + store.accounts.alpha = { + ...createAccount('alpha', 0), + rateLimitedUntil: now + 60_000 + } + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'alpha', + createdAt: now, + lastUsedAt: now + } + }) + enableStickySessions('round-robin') + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation).toBeNull() + expect(readStickyMappings().entries[sticky.hash]?.alias).toBe('alpha') + }) + + it('removes the sticky mapping when the mapped alias has failed permanently and no replacement is available', async () => { + const sticky = createStickySelection('session-permanent-failure') + const store = loadStore() + store.accounts.alpha = { + ...createAccount('alpha', 0), + enabled: false + } + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'alpha', + createdAt: Date.now(), + lastUsedAt: Date.now() + } + }) + enableStickySessions('round-robin') + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation).toBeNull() + expect(readStickyMappings().entries[sticky.hash]).toBeUndefined() + }) + + it('removes the sticky mapping when the mapped alias has failed permanently and fallback candidates are unusable', async () => { + const sticky = createStickySelection('session-permanent-failure-with-bad-fallback') + const now = Date.now() + const store = loadStore() + store.accounts.alpha = { + ...createAccount('alpha', 0), + enabled: false + } + store.accounts.beta = { + ...createAccount('beta', 0), + expiresAt: now - 1_000 + } + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'alpha', + createdAt: now, + lastUsedAt: now + } + }) + enableStickySessions('round-robin') + global.fetch = (async () => new Response('refresh failed', { status: 500 })) as typeof fetch + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation).toBeNull() + expect(readStickyMappings().entries[sticky.hash]).toBeUndefined() + }) + + it('ignores an existing sticky sidecar when the sticky flag is disabled', async () => { + const sticky = createStickySelection('session-flag-disabled') + const store = loadStore() + store.accounts.alpha = createAccount('alpha', 5) + store.accounts.beta = createAccount('beta', 0) + store.rotationIndex = 0 + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'beta', + createdAt: Date.now(), + lastUsedAt: Date.now() + } + }) + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation?.account.alias).toBe('alpha') + expect(readStickyMappings().entries[sticky.hash]?.alias).toBe('beta') + }) + + it('bypasses sticky routing while force mode is active', async () => { + const sticky = createStickySelection('session-force') + const stickyNow = Date.now() + const store = loadStore() + store.accounts.alpha = createAccount('alpha', 0) + store.accounts.beta = createAccount('beta', 0) + saveStore(store) + writeStickyMappings({ + [sticky.hash]: { + alias: 'beta', + createdAt: stickyNow, + lastUsedAt: stickyNow + } + }) + enableStickySessions('round-robin') + + const force = activateForce('alpha', 'test') + expect(force.success).toBe(true) + + const rotation = await getNextAccount(DEFAULT_CONFIG, { sticky } as any) + + expect(rotation?.account.alias).toBe('alpha') + expect(readStickyMappings().entries[sticky.hash]?.alias).toBe('beta') + }) + }) }) diff --git a/tests/unit/settings.test.ts b/tests/unit/settings.test.ts index ebcfa1b..49b16bd 100644 --- a/tests/unit/settings.test.ts +++ b/tests/unit/settings.test.ts @@ -22,6 +22,14 @@ import * as os from 'os' const tmpDir = path.join(os.tmpdir(), 'oma-test-settings-' + Date.now()) const originalEnv = process.env +function readJson(file: string): any { + return JSON.parse(fs.readFileSync(file, 'utf8')) +} + +function writeJson(file: string, value: unknown): void { + fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8') +} + describe('Phase F: Settings + Weighted Rotation', () => { beforeEach(() => { process.env = { ...originalEnv } @@ -300,6 +308,22 @@ describe('Phase F: Settings + Weighted Rotation', () => { expect(weights.account1).toBeGreaterThan(weights.account2) }) + it('should compute health-based weights from cache-backed rate limits when accounts are state-only', () => { + const accountsPath = path.join(tmpDir, 'accounts.json') + const persisted = readJson(accountsPath) + delete persisted.accounts.account1.rateLimits + delete persisted.accounts.account1.rateLimitHistory + delete persisted.accounts.account2.rateLimits + delete persisted.accounts.account2.rateLimitHistory + writeJson(accountsPath, persisted) + + const result = applyPreset('conservative', 'test') + + expect(result.success).toBe(true) + const weights = result.settings?.accountWeights || {} + expect(weights.account1).toBeGreaterThan(weights.account2) + }) + it('should apply aggressive preset favoring low-remaining accounts', () => { const result = applyPreset('aggressive', 'test') diff --git a/tests/unit/sticky-sessions.test.ts b/tests/unit/sticky-sessions.test.ts new file mode 100644 index 0000000..9e2d831 --- /dev/null +++ b/tests/unit/sticky-sessions.test.ts @@ -0,0 +1,277 @@ +import * as fs from 'node:fs' +import * as os from 'node:os' +import * as path from 'node:path' + +type StickySessionSettings = { + ttlMs: number + maxEntries: number + maxFileBytes: number +} + +type StickySessionEntry = { + alias: string + createdAt: number + lastUsedAt: number +} + +type StickySessionsFile = { + version: 1 + updatedAt: number + entries: Record +} + +const DEFAULT_SETTINGS: StickySessionSettings = { + ttlMs: 60_000, + maxEntries: 2, + maxFileBytes: 512 +} + +async function loadStickyModule(): Promise { + return import('../../src/sticky-sessions.js') +} + +function createTestContext() { + const previousEnv = process.env + const storeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'oma-sticky-sidecar-')) + const storeFile = path.join(storeDir, 'accounts.json') + const sidecarFile = path.join(storeDir, 'sticky-sessions.json') + + process.env = { + ...previousEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: storeDir, + OPENCODE_MULTI_AUTH_STORE_FILE: storeFile + } + + return { + storeDir, + storeFile, + sidecarFile, + restore() { + process.env = previousEnv + fs.rmSync(storeDir, { recursive: true, force: true }) + } + } +} + +function writeJson(filePath: string, value: unknown): void { + fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8') +} + +function readSidecar(filePath: string): StickySessionsFile { + return JSON.parse(fs.readFileSync(filePath, 'utf8')) as StickySessionsFile +} + +describe('Sticky session sidecar persistence contract', () => { + it('persists a versioned sidecar with minimal sticky metadata only', async () => { + const ctx = createTestContext() + + try { + const { upsertStickyAssignment } = await loadStickyModule() + + await upsertStickyAssignment({ + canonicalIdentity: ' Session-123 ', + alias: 'alpha', + now: 1_700_000_000_000, + settings: DEFAULT_SETTINGS + }) + + const persisted = readSidecar(ctx.sidecarFile) + const [hashedKey, entry] = Object.entries(persisted.entries)[0] ?? [] + + expect(persisted.version).toBe(1) + expect(persisted.updatedAt).toBe(1_700_000_000_000) + expect(Object.keys(persisted.entries)).toHaveLength(1) + expect(hashedKey).toMatch(/^[a-f0-9]{64}$/) + expect(entry).toEqual({ + alias: 'alpha', + createdAt: 1_700_000_000_000, + lastUsedAt: 1_700_000_000_000 + }) + + const serialized = JSON.stringify(persisted) + expect(serialized).not.toContain('Session-123') + expect(serialized).not.toContain('session-123') + expect((entry as Record).canonicalIdentity).toBeUndefined() + } finally { + ctx.restore() + } + }) + + it('derives deterministic hashed keys from normalized identities without persisting raw values', async () => { + const ctx = createTestContext() + + try { + const { hashStickyIdentity, upsertStickyAssignment } = await loadStickyModule() + + expect(hashStickyIdentity(' Session-123 ')).toBe(hashStickyIdentity('session-123')) + + await upsertStickyAssignment({ + canonicalIdentity: ' Session-123 ', + alias: 'alpha', + now: 100, + settings: DEFAULT_SETTINGS + }) + await upsertStickyAssignment({ + canonicalIdentity: 'session-123', + alias: 'beta', + now: 200, + settings: DEFAULT_SETTINGS + }) + + const persisted = readSidecar(ctx.sidecarFile) + const keys = Object.keys(persisted.entries) + + expect(keys).toHaveLength(1) + expect(keys[0]).toBe(hashStickyIdentity('session-123')) + expect(persisted.entries[keys[0]]).toEqual({ + alias: 'beta', + createdAt: 100, + lastUsedAt: 200 + }) + expect(JSON.stringify(persisted)).not.toContain('session-123') + } finally { + ctx.restore() + } + }) + + it('prunes expired entries first and then evicts least recently used entries to satisfy bounds', async () => { + const ctx = createTestContext() + + try { + const { loadStickySessions } = await loadStickyModule() + + writeJson(ctx.sidecarFile, { + version: 1, + updatedAt: 50, + entries: { + expired: { alias: 'alpha', createdAt: 1, lastUsedAt: 10 }, + oldest: { alias: 'beta', createdAt: 2, lastUsedAt: 40_000 }, + newest: { alias: 'gamma', createdAt: 3, lastUsedAt: 59_500 } + } + }) + + const loaded = await loadStickySessions({ + now: 60_001, + settings: { ...DEFAULT_SETTINGS, ttlMs: 20_000, maxEntries: 1, maxFileBytes: 10_000 } + }) + + expect(loaded.entries).toEqual({ + newest: { alias: 'gamma', createdAt: 3, lastUsedAt: 59_500 } + }) + expect(readSidecar(ctx.sidecarFile).entries).toEqual({ + newest: { alias: 'gamma', createdAt: 3, lastUsedAt: 59_500 } + }) + } finally { + ctx.restore() + } + }) + + it('does not return expired sticky assignments during runtime lookup and self-heals the sidecar', async () => { + const ctx = createTestContext() + + try { + const { getStickyAssignment, hashStickyIdentity } = await loadStickyModule() + const stickyHash = hashStickyIdentity('session-expired-runtime') + + writeJson(ctx.sidecarFile, { + version: 1, + updatedAt: 50, + entries: { + [stickyHash]: { alias: 'beta', createdAt: 1, lastUsedAt: 10 } + } + }) + + const loaded = await getStickyAssignment({ + stickyHash, + now: 60_001, + settings: { ...DEFAULT_SETTINGS, ttlMs: 20_000, maxEntries: 10, maxFileBytes: 10_000 } + }) + + expect(loaded).toBeNull() + expect(readSidecar(ctx.sidecarFile).entries).toEqual({}) + } finally { + ctx.restore() + } + }) + + it('serializes concurrent updates into a valid sidecar and leaves no temporary files behind', async () => { + const ctx = createTestContext() + + try { + const { upsertStickyAssignment } = await loadStickyModule() + + await Promise.all([ + upsertStickyAssignment({ canonicalIdentity: 'session-a', alias: 'alpha', now: 10, settings: DEFAULT_SETTINGS }), + upsertStickyAssignment({ canonicalIdentity: 'session-b', alias: 'beta', now: 20, settings: DEFAULT_SETTINGS }), + upsertStickyAssignment({ canonicalIdentity: 'session-c', alias: 'gamma', now: 30, settings: DEFAULT_SETTINGS }) + ]) + + const persisted = readSidecar(ctx.sidecarFile) + const leftovers = fs.readdirSync(ctx.storeDir).filter((name) => name.startsWith('sticky-sessions.json.tmp-')) + + expect(persisted.version).toBe(1) + expect(() => JSON.parse(fs.readFileSync(ctx.sidecarFile, 'utf8'))).not.toThrow() + expect(leftovers).toEqual([]) + + if (process.platform !== 'win32') { + expect(fs.statSync(ctx.sidecarFile).mode & 0o777).toBe(0o600) + } + } finally { + ctx.restore() + } + }) + + it('falls back to an empty sticky view when the sidecar is malformed and does not touch accounts.json', async () => { + const ctx = createTestContext() + + try { + const { loadStickySessions } = await loadStickyModule() + + fs.writeFileSync(ctx.sidecarFile, '{not-valid-json', 'utf8') + writeJson(ctx.storeFile, { + version: 2, + accounts: { alpha: { accessToken: 'token', refreshToken: 'refresh', expiresAt: 123, usageCount: 0 } }, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: 123 + }) + const accountsBefore = fs.readFileSync(ctx.storeFile, 'utf8') + + const loaded = await loadStickySessions({ now: 500, settings: DEFAULT_SETTINGS }) + + expect(loaded).toEqual({ version: 1, updatedAt: 500, entries: {} }) + expect(fs.readFileSync(ctx.storeFile, 'utf8')).toBe(accountsBefore) + } finally { + ctx.restore() + } + }) + + it('salvages valid sticky entries and prunes invalid ones during self-healing loads', async () => { + const ctx = createTestContext() + + try { + const { loadStickySessions } = await loadStickyModule() + + writeJson(ctx.sidecarFile, { + version: 1, + updatedAt: 100, + entries: { + validhash: { alias: 'alpha', createdAt: 10, lastUsedAt: 90 }, + missingAlias: { createdAt: 20, lastUsedAt: 80 }, + negativeTime: { alias: 'beta', createdAt: -1, lastUsedAt: 70 } + } + }) + + const loaded = await loadStickySessions({ now: 150, settings: DEFAULT_SETTINGS }) + + expect(loaded.entries).toEqual({ + validhash: { alias: 'alpha', createdAt: 10, lastUsedAt: 90 } + }) + expect(readSidecar(ctx.sidecarFile).entries).toEqual({ + validhash: { alias: 'alpha', createdAt: 10, lastUsedAt: 90 } + }) + } finally { + ctx.restore() + } + }) +}) diff --git a/tests/unit/store.test.ts b/tests/unit/store.test.ts index 14eade7..a3244a0 100644 --- a/tests/unit/store.test.ts +++ b/tests/unit/store.test.ts @@ -1,6 +1,8 @@ import * as fs from 'fs' import * as path from 'path' import * as os from 'os' +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' import { loadStore, saveStore, @@ -10,6 +12,7 @@ import { updateAccount, removeAccount } from '../../src/store.js' +import { getMetrics } from '../../src/metrics-store.js' const tmpDir = path.join(os.tmpdir(), 'oma-test-' + Date.now()) const originalEnv = process.env @@ -62,6 +65,71 @@ describe('Store Operations', () => { expect(updated.accounts['test-alias'].email).toBe('updated@example.com') }) + it('should preserve existing account metadata on addAccount overwrite', () => { + const now = Date.now() + addAccount('test-alias', { + accessToken: 'old-access-token', + refreshToken: 'old-refresh-token', + expiresAt: now + 1000, + email: 'old@example.com' + }) + + updateAccount('test-alias', { + usageCount: 42, + tags: ['vip', 'warm'], + notes: 'keep-me', + enabled: false, + disabledAt: now - 10_000, + disabledBy: 'operator', + disableReason: 'maintenance', + limitStatus: 'error', + limitError: 'temporary', + lastLimitProbeAt: now - 5_000, + lastLimitErrorAt: now - 4_000, + lastUsed: now - 3_000, + lastActiveUntil: now - 2_000, + rateLimitedUntil: now + 15_000, + modelUnsupportedUntil: now + 20_000, + workspaceDeactivatedUntil: now + 25_000, + authInvalid: true, + rateLimitHistory: [{ at: now - 60_000, fiveHour: { remaining: 10, limit: 100, resetAt: now + 1_000 } }] + }) + + addAccount('test-alias', { + accessToken: 'new-access-token', + refreshToken: 'new-refresh-token', + expiresAt: now + 3600_000, + email: 'new@example.com' + }) + + const reloaded = loadStore() + const updated = reloaded.accounts['test-alias'] + expect(updated.accessToken).toBe('new-access-token') + expect(updated.refreshToken).toBe('new-refresh-token') + expect(updated.expiresAt).toBe(now + 3600_000) + expect(updated.email).toBe('new@example.com') + expect(updated.tags).toEqual(['vip', 'warm']) + expect(updated.notes).toBe('keep-me') + expect(updated.enabled).toBe(false) + expect(updated.disabledAt).toBe(now - 10_000) + expect(updated.disabledBy).toBe('operator') + expect(updated.disableReason).toBe('maintenance') + expect(updated.rateLimitedUntil).toBe(now + 15_000) + expect(updated.modelUnsupportedUntil).toBe(now + 20_000) + expect(updated.workspaceDeactivatedUntil).toBe(now + 25_000) + expect(updated.authInvalid).toBe(true) + expect(getMetrics('test-alias')).toEqual(expect.objectContaining({ + usageCount: 42, + limitStatus: 'error', + limitError: 'temporary', + lastLimitProbeAt: now - 5_000, + lastLimitErrorAt: now - 4_000, + lastUsed: now - 3_000, + lastActiveUntil: now - 2_000, + rateLimitHistory: [{ at: now - 60_000, fiveHour: { remaining: 10, limit: 100, resetAt: now + 1_000 } }] + })) + }) + it('should persist accountUserId and userId across reload', () => { addAccount('test-alias', { accessToken: 'test-access-token', @@ -151,3 +219,338 @@ describe('Write Lock', () => { expect(result).toBe('after error') }) }) + +const METRIC_FIELDS = [ + 'lastRefresh', + 'lastSeenAt', + 'lastActiveUntil', + 'lastUsed', + 'usageCount', + 'rateLimits', + 'rateLimitHistory', + 'limitStatus', + 'limitError', + 'lastLimitProbeAt', + 'lastLimitErrorAt', + 'limitsConfidence' +] + +function writeJson(file: string, data: unknown): void { + fs.mkdirSync(path.dirname(file), { recursive: true }) + fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8') +} + +function readJson(file: string): any { + return JSON.parse(fs.readFileSync(file, 'utf8')) +} + +async function importFreshStoreModules() { + jest.resetModules() + return { + store: await import('../../src/store.js'), + metricsStore: await import('../../src/metrics-store.js') + } +} + +describe('Store v2 to v3 metrics migration', () => { + let migrationDir: string + let accountsPath: string + let metricsPath: string + + beforeEach(() => { + migrationDir = fs.mkdtempSync(path.join(os.tmpdir(), 'oma-store-migration-')) + accountsPath = path.join(migrationDir, 'accounts.json') + metricsPath = path.join(migrationDir, 'account-metrics.json') + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: migrationDir, + OPENCODE_MULTI_AUTH_STORE_FILE: accountsPath + } + }) + + afterEach(() => { + process.env = originalEnv + fs.rmSync(migrationDir, { recursive: true, force: true }) + jest.resetModules() + }) + + it('moves inline v2 metrics into the sidecar, strips accounts.json, and bumps to v3', async () => { + const now = 1_800_000_000_000 + writeJson(accountsPath, { + version: 2, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: now, + accounts: { + alpha: { + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: now + 60_000, + email: 'alpha@example.com', + rateLimitedUntil: now + 5_000, + tags: ['keep-state'], + lastRefresh: '2026-01-01T00:00:00.000Z', + lastSeenAt: now - 9_000, + lastActiveUntil: now - 8_000, + lastUsed: now - 7_000, + usageCount: 12, + rateLimits: { + fiveHour: { remaining: 44, limit: 100, resetAt: now + 10_000, updatedAt: now - 6_000 } + }, + rateLimitHistory: [ + { at: now - 6_000, fiveHour: { remaining: 44, limit: 100, resetAt: now + 10_000 } } + ], + limitStatus: 'success', + limitError: 'previous soft limit', + lastLimitProbeAt: now - 5_000, + lastLimitErrorAt: now - 4_000, + limitsConfidence: 'fresh' + } + } + }) + + const { store } = await importFreshStoreModules() + const migrated = store.loadStore() + + expect(migrated.version).toBe(3) + expect(migrated.accounts.alpha.accessToken).toBe('access-alpha') + expect(migrated.accounts.alpha.refreshToken).toBe('refresh-alpha') + expect(migrated.accounts.alpha.expiresAt).toBe(now + 60_000) + expect(migrated.accounts.alpha.rateLimitedUntil).toBe(now + 5_000) + + const persistedAccounts = readJson(accountsPath) + expect(persistedAccounts.version).toBe(3) + for (const field of METRIC_FIELDS) { + expect(persistedAccounts.accounts.alpha).not.toHaveProperty(field) + } + expect(persistedAccounts.accounts.alpha).toMatchObject({ + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: now + 60_000, + rateLimitedUntil: now + 5_000, + tags: ['keep-state'] + }) + + const persistedMetrics = readJson(metricsPath) + expect(persistedMetrics.version).toBe(1) + expect(persistedMetrics.metrics.alpha).toMatchObject({ + lastRefresh: '2026-01-01T00:00:00.000Z', + lastSeenAt: now - 9_000, + lastActiveUntil: now - 8_000, + lastUsed: now - 7_000, + usageCount: 12, + rateLimits: { + fiveHour: { remaining: 44, limit: 100, resetAt: now + 10_000, updatedAt: now - 6_000 } + }, + limitStatus: 'success', + limitError: 'previous soft limit', + lastLimitProbeAt: now - 5_000, + lastLimitErrorAt: now - 4_000, + limitsConfidence: 'fresh' + }) + expect(persistedMetrics.metrics.alpha.rateLimitHistory).toHaveLength(1) + }) + + it('converges when sidecar metrics already exist but accounts.json is still v2', async () => { + const now = 1_800_000_100_000 + const snapshot = { at: now - 4_000, fiveHour: { remaining: 20, limit: 100, resetAt: now + 60_000 } } + writeJson(metricsPath, { + version: 1, + updatedAt: now - 1_000, + metrics: { + alpha: { + usageCount: 9, + lastUsed: now - 1_000, + rateLimits: { fiveHour: { remaining: 20, limit: 100, resetAt: now + 60_000, updatedAt: now - 4_000 } }, + rateLimitHistory: [snapshot] + } + } + }) + writeJson(accountsPath, { + version: 2, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: now, + accounts: { + alpha: { + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: now + 60_000, + notes: 'state survives', + usageCount: 5, + lastUsed: now - 2_000, + rateLimits: { fiveHour: { remaining: 20, limit: 100, resetAt: now + 60_000, updatedAt: now - 4_000 } }, + rateLimitHistory: [snapshot] + } + } + }) + + const { store } = await importFreshStoreModules() + store.loadStore() + + const persistedMetrics = readJson(metricsPath) + expect(persistedMetrics.metrics.alpha.usageCount).toBe(9) + expect(persistedMetrics.metrics.alpha.lastUsed).toBe(now - 1_000) + expect(persistedMetrics.metrics.alpha.rateLimitHistory).toEqual([snapshot]) + expect(readJson(accountsPath).accounts.alpha.notes).toBe('state survives') + for (const field of METRIC_FIELDS) { + expect(readJson(accountsPath).accounts.alpha).not.toHaveProperty(field) + } + + await importFreshStoreModules().then(({ store: rerunStore }) => rerunStore.loadStore()) + expect(readJson(metricsPath).metrics.alpha.rateLimitHistory).toEqual([snapshot]) + expect(readJson(metricsPath).metrics.alpha.usageCount).toBe(9) + }) + + it('recovers state-only v3 accounts when the sidecar is missing', async () => { + const now = 1_800_000_200_000 + writeJson(accountsPath, { + version: 3, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: now, + accounts: { + alpha: { + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: now + 60_000, + email: 'alpha@example.com', + enabled: false, + disabledBy: 'operator' + } + } + }) + + const { store } = await importFreshStoreModules() + const loaded = store.loadStore() + + expect(loaded.version).toBe(3) + expect(loaded.accounts.alpha.accessToken).toBe('access-alpha') + expect(loaded.accounts.alpha.refreshToken).toBe('refresh-alpha') + expect(loaded.accounts.alpha.expiresAt).toBe(now + 60_000) + expect(loaded.accounts.alpha.enabled).toBe(false) + expect(loaded.accounts.alpha.disabledBy).toBe('operator') + + expect(fs.existsSync(metricsPath)).toBe(true) + expect(readJson(metricsPath).metrics).toEqual({}) + const persistedAccounts = readJson(accountsPath) + expect(persistedAccounts.accounts.alpha).toMatchObject({ + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: now + 60_000, + email: 'alpha@example.com', + enabled: false, + disabledBy: 'operator' + }) + }) +}) + +describe('Store metrics write-path split', () => { + let splitDir: string + let accountsPath: string + let metricsPath: string + + beforeEach(() => { + splitDir = fs.mkdtempSync(path.join(os.tmpdir(), 'oma-store-split-')) + accountsPath = path.join(splitDir, 'accounts.json') + metricsPath = path.join(splitDir, 'account-metrics.json') + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: splitDir, + OPENCODE_MULTI_AUTH_STORE_FILE: accountsPath + } + }) + + afterEach(() => { + process.env = originalEnv + fs.rmSync(splitDir, { recursive: true, force: true }) + jest.resetModules() + }) + + function writeStateOnlyStore(now: number): void { + writeJson(accountsPath, { + version: 3, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: now, + accounts: { + alpha: { + accessToken: 'access-alpha', + refreshToken: 'refresh-alpha', + expiresAt: now + 60_000, + email: 'alpha@example.com' + }, + beta: { + accessToken: 'access-beta', + refreshToken: 'refresh-beta', + expiresAt: now + 60_000, + email: 'beta@example.com' + } + } + }) + } + + it('routes telemetry-only updateAccount writes to metrics without rewriting accounts.json', async () => { + const now = 1_800_000_300_000 + writeStateOnlyStore(now) + const before = fs.readFileSync(accountsPath, 'utf8') + + const { store, metricsStore } = await importFreshStoreModules() + store.updateAccount('alpha', { + lastSeenAt: now + 1, + limitStatus: 'success', + rateLimits: { + fiveHour: { remaining: 9, limit: 100, resetAt: now + 30_000, updatedAt: now + 1 } + } + }) + + expect(fs.readFileSync(accountsPath, 'utf8')).toBe(before) + expect(metricsStore.getMetrics('alpha')).toEqual(expect.objectContaining({ + lastSeenAt: now + 1, + limitStatus: 'success', + rateLimits: { + fiveHour: { remaining: 9, limit: 100, resetAt: now + 30_000, updatedAt: now + 1 } + } + })) + expect(metricsStore.getMetrics('alpha')?.rateLimitHistory).toHaveLength(1) + }) + + it('keeps setActiveAlias activity telemetry out of accounts.json', async () => { + const now = 1_800_000_400_000 + writeStateOnlyStore(now) + const { store, metricsStore } = await importFreshStoreModules() + + store.setActiveAlias('beta') + + const persisted = readJson(accountsPath) + expect(persisted.activeAlias).toBe('beta') + expect(persisted.accounts.alpha).not.toHaveProperty('lastActiveUntil') + expect(persisted.accounts.beta).not.toHaveProperty('lastSeenAt') + expect(metricsStore.getMetrics('alpha')?.lastActiveUntil).toEqual(expect.any(Number)) + expect(metricsStore.getMetrics('beta')?.lastSeenAt).toEqual(expect.any(Number)) + }) + + it('removes metrics when removeAccount deletes the account', async () => { + const now = 1_800_000_500_000 + writeStateOnlyStore(now) + writeJson(metricsPath, { + version: 1, + updatedAt: now, + metrics: { + alpha: { usageCount: 4, lastSeenAt: now - 1_000 }, + beta: { usageCount: 2 } + } + }) + + const { store, metricsStore } = await importFreshStoreModules() + expect(metricsStore.getMetrics('alpha')).toBeDefined() + + store.removeAccount('alpha') + metricsStore.flushSync(true) + + expect(store.loadStore().accounts.alpha).toBeUndefined() + expect(metricsStore.getMetrics('alpha')).toBeUndefined() + expect(readJson(metricsPath).metrics.alpha).toBeUndefined() + expect(readJson(metricsPath).metrics.beta).toEqual(expect.objectContaining({ usageCount: 2 })) + }) +}) diff --git a/tests/unit/usage-limits.test.ts b/tests/unit/usage-limits.test.ts index a7fe498..2a2c3cc 100644 --- a/tests/unit/usage-limits.test.ts +++ b/tests/unit/usage-limits.test.ts @@ -1,3 +1,4 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. import { jest } from '@jest/globals' import { classifyUsageApiFailure, @@ -74,7 +75,7 @@ describe('usage API fetch', () => { jest.restoreAllMocks() }) - it('calls the Codex usage endpoint with Codex originator headers', async () => { + it('calls the wham usage endpoint with bearer authorization', async () => { const mockFetch = jest.fn(async () => new Response(JSON.stringify({ plan_type: 'pro', rate_limit: { @@ -87,18 +88,47 @@ describe('usage API fetch', () => { const result = await fetchUsageRateLimitsForAccount(account) expect(mockFetch).toHaveBeenCalledWith( - 'https://example.test/backend-api/codex/usage', + 'https://example.test/backend-api/wham/usage', expect.objectContaining({ method: 'GET', headers: expect.objectContaining({ - Authorization: 'Bearer access-token', - 'ChatGPT-Account-Id': 'account-id', - originator: 'codex_cli_rs' + Authorization: 'Bearer access-token' }) }) ) + expect(mockFetch.mock.calls[0][1]?.headers).not.toEqual(expect.objectContaining({ + originator: expect.any(String), + 'ChatGPT-Account-Id': expect.any(String) + })) expect(result.planType).toBe('pro') expect(result.rateLimits?.fiveHour?.remaining).toBe(75) expect(result.rateLimits?.weekly?.remaining).toBe(90) }) + + it('prefers Codex-specific additional rate limits from wham usage payloads', async () => { + const mockFetch = jest.fn(async () => new Response(JSON.stringify({ + plan_type: 'prolite', + rate_limit: { + primary_window: { used_percent: 25, reset_after_seconds: 60 }, + secondary_window: { used_percent: 10, reset_after_seconds: 120 } + }, + additional_rate_limits: [ + { + limit_name: 'GPT-5.3-Codex-Spark', + metered_feature: 'codex_bengalfox', + rate_limit: { + primary_window: { used_percent: 2, reset_after_seconds: 300 }, + secondary_window: { used_percent: 3, reset_after_seconds: 600 } + } + } + ] + }), { status: 200 })) + global.fetch = mockFetch as typeof fetch + + const result = await fetchUsageRateLimitsForAccount(account) + + expect(result.planType).toBe('prolite') + expect(result.rateLimits?.fiveHour?.remaining).toBe(98) + expect(result.rateLimits?.weekly?.remaining).toBe(97) + }) }) diff --git a/tests/unit/web-reauth.test.ts b/tests/unit/web-reauth.test.ts new file mode 100644 index 0000000..99cacce --- /dev/null +++ b/tests/unit/web-reauth.test.ts @@ -0,0 +1,198 @@ +// @ts-ignore - ESM Jest globals are available at runtime in the test environment. +import { jest } from '@jest/globals' +import * as fs from 'node:fs' +import * as net from 'node:net' +import * as os from 'node:os' +import * as path from 'node:path' +import type * as http from 'node:http' + +const esmJest = jest as typeof jest & { + unstable_mockModule: (moduleName: string, factory: () => Record) => void +} + +const AUTH_PATH = path.join(os.tmpdir(), 'oma-web-reauth-auth.json') +const flow = { + pkce: { verifier: 'verifier', challenge: 'challenge' }, + state: 'state', + url: 'http://localhost/auth', + redirectUri: 'http://localhost:1455/auth/callback', + port: 1455 +} + +const createAuthorizationFlow = jest.fn() +const loginAccount = jest.fn() +const refreshToken = jest.fn() +const syncCodexAuthFile = jest.fn() +const writeCodexAuthForAlias = jest.fn() +const loadStore = jest.fn() +const updateAccount = jest.fn() +const logInfo = jest.fn() +const logError = jest.fn() + +esmJest.unstable_mockModule('../../src/auth.js', () => ({ + createAuthorizationFlow, + loginAccount, + refreshToken +})) + +esmJest.unstable_mockModule('../../src/codex-auth.js', () => ({ + getCodexAuthPath: () => AUTH_PATH, + getCodexAuthStatus: () => ({ error: null }), + getCodexAuthSummary: () => ({ hasAccessToken: false, hasRefreshToken: false, hasIdToken: false }), + getCodexActiveState: () => ({ status: 'missing', alias: null, hasAccessToken: false, hasRefreshToken: false, hasIdToken: false }), + resolveAliasForCurrentAuth: () => null, + syncCodexAuthFile, + writeCodexAuthForAlias +})) + +esmJest.unstable_mockModule('../../src/store.js', () => ({ + getStoreStatus: () => ({ locked: false, encrypted: false, error: null }), + listAccounts: jest.fn(), + loadStore, + loadStoreWithMetrics: loadStore, + removeAccount: jest.fn(), + updateAccount +})) + +esmJest.unstable_mockModule('../../src/metrics-store.js', () => ({ + registerMetricsFlushHooks: jest.fn() +})) + +esmJest.unstable_mockModule('../../src/logger.js', () => ({ + getLogPath: () => path.join(os.tmpdir(), 'oma-web-reauth.log'), + logError, + logInfo, + logWarn: jest.fn(), + readLogTail: () => [] +})) + +esmJest.unstable_mockModule('../../src/refresh-queue.js', () => ({ + getRefreshQueueState: () => null, + startRefreshQueue: jest.fn(), + stopRefreshQueue: jest.fn() +})) + +esmJest.unstable_mockModule('../../src/force-mode.js', () => ({ + getForceState: () => ({ forcedAlias: null, forcedUntil: null, forcedBy: null, previousRotationStrategy: null }), + activateForce: jest.fn(), + clearForce: jest.fn(), + isForceActive: () => false, + getRemainingForceTimeMs: () => 0, + formatForceDuration: () => '0m' +})) + +esmJest.unstable_mockModule('../../src/settings.js', () => ({ + getSettings: () => ({ settings: { featureFlags: { antigravityEnabled: false, stickySessionsEnabled: false } } }), + getRuntimeSettings: () => ({ settings: { rotationStrategy: 'round-robin' } }), + updateSettings: jest.fn(), + isFeatureEnabled: () => false, + getStickySessionConfig: () => ({}), + getStickySessionRuntimeSettings: () => ({}), + updateStickySessionConfig: jest.fn() +})) + +esmJest.unstable_mockModule('../../src/sticky-sessions.js', () => ({ + cleanupStickySessions: jest.fn(), + getStickySessionsStatus: () => ({}) +})) + +let startWebConsole: typeof import('../../src/web.js').startWebConsole + +beforeAll(async () => { + ;({ startWebConsole } = await import('../../src/web.js')) +}) + +beforeEach(() => { + jest.clearAllMocks() + fs.writeFileSync(AUTH_PATH, '{}') + createAuthorizationFlow.mockResolvedValue(flow) + syncCodexAuthFile.mockReturnValue({ alias: null, added: false, updated: false }) + loadStore.mockReturnValue({ + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'access-token', + refreshToken: 'refresh-token', + expiresAt: Date.now() + 60_000, + enabled: true, + usageCount: 0 + } + }, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: Date.now() + }) +}) + +afterEach(() => { + fs.unwatchFile(AUTH_PATH) + fs.rmSync(AUTH_PATH, { force: true }) +}) + +async function getFreePort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer() + server.listen(0, '127.0.0.1', () => { + const address = server.address() + if (!address || typeof address === 'string') { + reject(new Error('Failed to resolve free port')) + return + } + server.close(() => resolve(address.port)) + }) + server.on('error', reject) + }) +} + +async function closeServer(server: http.Server): Promise { + await new Promise((resolve, reject) => { + server.close((err) => err ? reject(err) : resolve()) + }) +} + +async function requestJson(port: number, pathname: string, init?: RequestInit): Promise<{ status: number; body: Record }> { + const response = await fetch(`http://127.0.0.1:${port}${pathname}`, { + headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) }, + ...init + }) + return { status: response.status, body: await response.json() as Record } +} + +async function flushMicrotasks(): Promise { + await Promise.resolve() + await Promise.resolve() +} + +describe('dashboard reauth', () => { + it('writes refreshed reauth credentials to codex auth and still updates lastRefresh', async () => { + let resolveLogin!: (account: unknown) => void + loginAccount.mockReturnValue(new Promise((resolve) => { + resolveLogin = resolve + })) + const port = await getFreePort() + const server = startWebConsole({ host: '127.0.0.1', port }) + await new Promise((resolve) => server.once('listening', () => resolve())) + + try { + const response = await requestJson(port, '/api/accounts/alpha/reauth', { + method: 'POST', + body: JSON.stringify({ actor: 'test-suite' }) + }) + expect(response).toEqual({ + status: 200, + body: expect.objectContaining({ ok: true, alias: 'alpha', url: flow.url }) + }) + + resolveLogin({ alias: 'alpha' }) + await flushMicrotasks() + + expect(writeCodexAuthForAlias).toHaveBeenCalledWith('alpha') + expect(updateAccount).toHaveBeenCalledWith('alpha', { + lastRefresh: expect.any(String) + }) + expect(logInfo).toHaveBeenCalledWith('Re-auth completed for alpha by test-suite') + } finally { + await closeServer(server) + } + }) +}) diff --git a/tests/web-headless/dashboard-account-quota-cards.test.ts b/tests/web-headless/dashboard-account-quota-cards.test.ts new file mode 100644 index 0000000..4e28c45 --- /dev/null +++ b/tests/web-headless/dashboard-account-quota-cards.test.ts @@ -0,0 +1,322 @@ +import * as fs from 'node:fs' +import * as net from 'node:net' +import * as os from 'node:os' +import * as path from 'node:path' +import type * as http from 'node:http' +import { once } from 'node:events' + +const SANDBOX_ROOT = path.join(os.tmpdir(), 'oma-dashboard-quota-cards-sandbox') +const STORE_FILE = path.join(SANDBOX_ROOT, 'accounts.json') +const AUTH_FILE = path.join(SANDBOX_ROOT, 'auth.json') +const WEB_DIST_DIR = path.resolve(process.cwd(), 'tests/fixtures/web-dist') +const originalEnv = process.env + +let startWebConsole: typeof import('../../src/web.js').startWebConsole +let getCodexAuthPath: typeof import('../../src/codex-auth.js').getCodexAuthPath + +async function getFreePort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer() + server.listen(0, '127.0.0.1', () => { + const address = server.address() + if (!address || typeof address === 'string') { + reject(new Error('Failed to resolve free port')) + return + } + const port = address.port + server.close((err) => { + if (err) { + reject(err) + return + } + resolve(port) + }) + }) + server.on('error', reject) + }) +} + +async function closeServer(server: http.Server): Promise { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err) + return + } + resolve() + }) + }) +} + +function seedSandbox(): void { + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) + fs.mkdirSync(SANDBOX_ROOT, { recursive: true }) + fs.writeFileSync(AUTH_FILE, JSON.stringify({ OPENAI_API_KEY: null, tokens: {} }, null, 2)) + + fs.writeFileSync( + STORE_FILE, + JSON.stringify( + { + version: 2, + activeAlias: 'alpha', + rotationIndex: 0, + lastRotation: 1_700_000_000_000, + rotationStrategy: 'round-robin', + settings: { + rotationStrategy: 'round-robin', + criticalThreshold: 10, + lowThreshold: 30, + accountWeights: {}, + featureFlags: { + antigravityEnabled: false, + stickySessionsEnabled: false + } + }, + accounts: { + alpha: { + alias: 'alpha', + accessToken: 'token-alpha', + refreshToken: 'refresh-alpha', + expiresAt: Date.now() + 60_000, + email: 'alpha@example.com', + usageCount: 3, + enabled: true, + tags: ['core'], + notes: 'primary account', + source: 'opencode', + rateLimits: { + fiveHour: { limit: 100, remaining: 80, resetAt: Date.now() + 60_000, updatedAt: Date.now() }, + weekly: { limit: 1000, remaining: 700, resetAt: Date.now() + 120_000, updatedAt: Date.now() } + }, + limitsConfidence: 'fresh' + }, + beta: { + alias: 'beta', + accessToken: 'token-beta', + refreshToken: 'refresh-beta', + expiresAt: Date.now() + 120_000, + email: 'beta@example.com', + usageCount: 7, + enabled: false, + tags: ['backup'], + notes: 'secondary account', + source: 'codex', + rateLimits: { + fiveHour: { limit: 100, remaining: 15, resetAt: Date.now() + 60_000, updatedAt: Date.now() }, + weekly: { limit: 1000, remaining: 50, resetAt: Date.now() + 120_000, updatedAt: Date.now() } + }, + limitsConfidence: 'stale', + disabledAt: Date.now(), + disabledBy: 'operator' + }, + gamma: { + alias: 'gamma', + accessToken: 'token-gamma', + refreshToken: 'refresh-gamma', + expiresAt: Date.now() + 90_000, + email: 'gamma@example.com', + usageCount: 1, + enabled: true, + tags: [], + notes: '', + source: 'opencode', + limitsConfidence: 'unknown' + } + }, + login: null, + queue: null + }, + null, + 2 + ) + ) +} + +beforeAll(async () => { + if (fs.existsSync(SANDBOX_ROOT)) { + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) + } + fs.mkdirSync(SANDBOX_ROOT, { recursive: true }) + fs.writeFileSync(AUTH_FILE, JSON.stringify({ OPENAI_API_KEY: null, tokens: {} }, null, 2)) + + process.env = { + ...originalEnv, + OPENCODE_MULTI_AUTH_STORE_DIR: SANDBOX_ROOT, + OPENCODE_MULTI_AUTH_STORE_FILE: STORE_FILE, + OPENCODE_MULTI_AUTH_CODEX_AUTH_FILE: AUTH_FILE, + OPENCODE_MULTI_AUTH_WEB_DIST_DIR: WEB_DIST_DIR + } + + ;({ startWebConsole } = await import('../../src/web.js')) + ;({ getCodexAuthPath } = await import('../../src/codex-auth.js')) +}) + +afterAll(() => { + try { + if (getCodexAuthPath) { + fs.unwatchFile(getCodexAuthPath()) + } + } catch { + // ignore + } + process.env = originalEnv + if (fs.existsSync(SANDBOX_ROOT)) { + fs.rmSync(SANDBOX_ROOT, { recursive: true, force: true }) + } +}) + +describe('dashboard account quota cards', () => { + it('serves the SPA at / with dashboard data surface', async () => { + seedSandbox() + const port = await getFreePort() + const server = startWebConsole({ host: '127.0.0.1', port }) + + try { + await once(server, 'listening') + const response = await fetch(`http://127.0.0.1:${port}/`) + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toContain('text/html') + + const body = await response.text() + expect(body).toContain('fixture-spa-shell') + expect(body).toContain('') + expect(response.body).not.toContain('data-dashboard-surface="overview"') + expect(response.body).not.toContain('id="openAccountModalBtn"') + } finally { + await closeServer(server) + } + }) + + it('serves the fixture asset bundle and no longer exposes inline dashboard scripting', async () => { + const { server, port } = await startServer() + + try { + const response = await requestText(port, '/') + expect(response.status).toBe(200) + + expect(response.body).not.toMatch(/ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..a779127 --- /dev/null +++ b/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "@guard22/opencode-multi-auth-codex-web", + "private": true, + "version": "1.4.3", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "typecheck": "tsc --noEmit", + "verify:static": "pnpm run lint && pnpm run typecheck", + "test": "pnpm run test:unit && pnpm run test:component", + "test:unit": "vitest run src/hooks/__tests__/useDashboardState.test.tsx src/hooks/__tests__/useNotification.test.tsx", + "test:component": "vitest run src/components/__tests__", + "test:watch": "vitest", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@tanstack/react-query": "^5.64.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "recharts": "^3.8.1" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.1.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "jsdom": "^25.0.1", + "typescript": "^5.7.2", + "vite": "^6.0.7", + "vitest": "^2.1.8" + } +} diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 0000000..3fade60 --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,42 @@ +import { Routes, Route, Navigate } from 'react-router-dom' +import { AppShell } from './components/AppShell' +import { NotificationProvider } from './hooks/useNotification' +import { NotificationCenter } from './components/NotificationCenter' +import { DashboardPage } from './components/DashboardPage' +import { ConfigurationPage } from './components/ConfigurationPage' +import { AntigravityPage } from './components/AntigravityPage' +import { useDashboardState } from './hooks/useDashboardState' + +export function App(): JSX.Element { + const { data: state } = useDashboardState() + const featureFlags = state?.featureFlags ?? { antigravityEnabled: false } + + return ( + + + + } /> + } /> + {featureFlags.antigravityEnabled && ( + } /> + )} + + {/* Legacy route redirects: collapse 4-area model into 2-section IA */} + } /> + } /> + } /> + + } + /> + + + + + ) +} diff --git a/web/src/api/client.ts b/web/src/api/client.ts new file mode 100644 index 0000000..ff0d116 --- /dev/null +++ b/web/src/api/client.ts @@ -0,0 +1,258 @@ +/** + * Typed fetch wrapper for the dashboard API. + * + * All requests are unauthenticated (localhost-only dashboard). + * Errors are normalized to a consistent ApiError shape. + */ + +import type { ApiErrorResponse } from '../types/api.ts' + +export class ApiError extends Error { + status: number + code?: string + details?: ApiErrorResponse['details'] + + constructor(status: number, body: ApiErrorResponse) { + super(body.error) + this.name = 'ApiError' + this.status = status + this.code = body.code + this.details = body.details + } +} + +async function parseJsonResponse(response: Response): Promise { + const text = await response.text() + try { + return JSON.parse(text) as T + } catch { + throw new ApiError(response.status, { error: 'Invalid JSON response' }) + } +} + +async function handleResponse(response: Response): Promise { + if (!response.ok) { + const body = await parseJsonResponse(response) + throw new ApiError(response.status, body) + } + return parseJsonResponse(response) +} + +export interface RequestOptions { + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' + body?: unknown + headers?: Record +} + +export async function apiRequest(pathname: string, options: RequestOptions = {}): Promise { + const response = await fetch(pathname, { + method: options.method ?? 'GET', + headers: { + 'Content-Type': 'application/json', + ...options.headers + }, + body: options.body !== undefined ? JSON.stringify(options.body) : undefined + }) + return handleResponse(response) +} + +// Convenience helpers aligned with frozen API contracts + +export function getState() { + return apiRequest('/api/state') +} + +export function getLogs(limit?: number) { + const qs = limit !== undefined ? `?limit=${limit}` : '' + return apiRequest(`/api/logs${qs}`) +} + +export function getAccounts() { + return apiRequest<{ accounts: import('../types/api.ts').AccountView[] }>('/api/accounts') +} + +export function syncAuth() { + return apiRequest('/api/sync', { method: 'POST' }) +} + +export function importCodexAuth() { + return apiRequest('/api/codex/import', { method: 'POST' }) +} + +export function startAuth(alias: string) { + return apiRequest<{ ok: true; url: string }>('/api/auth/start', { + method: 'POST', + body: { alias } + }) +} + +export function startAutoLogin(selector: string, visible?: boolean) { + return apiRequest<{ ok: true; url?: string }>('/api/auto-login/start', { + method: 'POST', + body: { selector, visible } + }) +} + +export function addAutoLoginAccount(input: { + email: string + password: string + alias?: string + chatgptPassword?: string +}) { + return apiRequest<{ ok: true }>('/api/auto-login/add', { + method: 'POST', + body: input + }) +} + +export function switchAccount(alias: string) { + return apiRequest('/api/switch', { + method: 'POST', + body: { alias } + }) +} + +export function useInCodex(alias: string) { + return apiRequest('/api/codex/use', { + method: 'POST', + body: { alias } + }) +} + +export function removeAccount(alias: string) { + return apiRequest('/api/remove', { + method: 'POST', + body: { alias } + }) +} + +export function updateAccountMeta(alias: string, tags?: string, notes?: string) { + return apiRequest('/api/account/meta', { + method: 'POST', + body: { alias, tags, notes } + }) +} + +export function refreshToken(alias?: string) { + return apiRequest('/api/token/refresh', { + method: 'POST', + body: { alias } + }) +} + +export function refreshLimits(alias?: string) { + return apiRequest('/api/limits/refresh', { + method: 'POST', + body: { alias } + }) +} + +export function stopRefreshQueue() { + return apiRequest('/api/limits/stop', { + method: 'POST' + }) +} + +export function getForceState() { + return apiRequest('/api/force') +} + +export function activateForce(alias: string, actor?: string) { + return apiRequest('/api/force', { + method: 'POST', + body: { alias, actor } + }) +} + +export function clearForce() { + return apiRequest('/api/force/clear', { + method: 'POST' + }) +} + +export function getSettings() { + return apiRequest('/api/settings') +} + +export function updateSettings( + updates: Partial & { actor?: string } +) { + return apiRequest('/api/settings', { + method: 'PUT', + body: updates + }) +} + +export function getFeatureFlags() { + return apiRequest('/api/settings/feature-flags') +} + +export function updateFeatureFlags(featureFlags: import('../types/api.ts').FeatureFlags, actor?: string) { + return apiRequest('/api/settings/feature-flags', { + method: 'PUT', + body: { featureFlags, actor } + }) +} + +export function resetSettings(actor?: string) { + return apiRequest('/api/settings/reset', { + method: 'POST', + body: { actor } + }) +} + +export function applyPreset(preset: import('../types/api.ts').WeightPreset, actor?: string) { + return apiRequest('/api/settings/preset', { + method: 'POST', + body: { preset, actor } + }) +} + +export function enableAccount(alias: string, enabled: boolean) { + return apiRequest(`/api/accounts/${encodeURIComponent(alias)}/enabled`, { + method: 'PUT', + body: { enabled } + }) +} + +export function reauthAccount(alias: string, actor?: string) { + return apiRequest(`/api/accounts/${encodeURIComponent(alias)}/reauth`, { + method: 'POST', + body: { actor } + }) +} + +export function refreshAntigravityQuota() { + return apiRequest<{ ok: true; quota: import('../types/api.ts').AntigravityQuota }>('/api/antigravity/refresh', { + method: 'POST' + }) +} + +export function refreshAntigravityQuotaAll() { + return apiRequest<{ ok: true; quota: import('../types/api.ts').AntigravityQuota }>('/api/antigravity/refresh-all', { + method: 'POST' + }) +} + +// Sticky-session additive contracts (Phase 4+) + +export function getStickySessionConfig() { + return apiRequest('/api/sticky-sessions/config') +} + +export function updateStickySessionConfig(config: import('../types/api.ts').StickySessionSettings) { + return apiRequest('/api/sticky-sessions/config', { + method: 'PUT', + body: config + }) +} + +export function getStickySessionStatus() { + return apiRequest('/api/sticky-sessions/status') +} + +export function cleanupStickySessions() { + return apiRequest('/api/sticky-sessions/cleanup', { + method: 'POST' + }) +} diff --git a/web/src/api/queries.ts b/web/src/api/queries.ts new file mode 100644 index 0000000..73a8964 --- /dev/null +++ b/web/src/api/queries.ts @@ -0,0 +1,440 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { + getState, + getLogs, + syncAuth, + importCodexAuth, + refreshToken, + refreshLimits, + stopRefreshQueue, + startAuth, + startAutoLogin, + addAutoLoginAccount, + switchAccount, + useInCodex, + enableAccount, + removeAccount, + updateAccountMeta, + reauthAccount, + getForceState, + activateForce, + clearForce, + refreshAntigravityQuota, + refreshAntigravityQuotaAll, + getSettings, + updateSettings, + getFeatureFlags, + updateFeatureFlags, + resetSettings, + applyPreset, + getStickySessionConfig, + updateStickySessionConfig, + getStickySessionStatus, + cleanupStickySessions +} from './client' +import type { DashboardState, LogsResponse, ForceState, SettingsInfo, FeatureFlagsResponse } from '../types/api' + +// Query keys +export const queryKeys = { + dashboardState: ['dashboardState'] as const, + logs: (limit?: number) => ['logs', limit] as const, + accounts: ['accounts'] as const, + settings: ['settings'] as const, + forceState: ['forceState'] as const, + featureFlags: ['featureFlags'] as const, + stickySessionConfig: ['stickySessionConfig'] as const, + stickySessionStatus: ['stickySessionStatus'] as const +} + +// Dashboard state query +export function useDashboardStateQuery(pollingInterval = 5000) { + return useQuery({ + queryKey: queryKeys.dashboardState, + queryFn: getState, + refetchInterval: (query) => (query.state.data?.queue?.running ? 1000 : pollingInterval), + staleTime: Math.min(pollingInterval / 2, 2000) + }) +} + +// Sync mutation +export function useSyncMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: syncAuth, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Refresh tokens mutation +export function useRefreshTokensMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (alias?: string) => refreshToken(alias), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Import from Codex auth.json mutation +export function useImportCodexAuthMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: importCodexAuth, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Refresh limits mutation +export function useRefreshLimitsMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (alias?: string) => refreshLimits(alias), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Stop refresh queue mutation +export function useStopRefreshQueueMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: stopRefreshQueue, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Start auth mutation +export function useStartAuthMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: startAuth, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Start auto-login mutation +export function useStartAutoLoginMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { selector: string; visible?: boolean }) => startAutoLogin(vars.selector, vars.visible), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Add auto-login account mutation +export function useAddAutoLoginAccountMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: addAutoLoginAccount, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Switch account mutation +export function useSwitchAccountMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: switchAccount, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Use stored account in Codex mutation +export function useUseInCodexMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: useInCodex, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Enable/disable account mutation +export function useEnableAccountMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { alias: string; enabled: boolean }) => enableAccount(vars.alias, vars.enabled), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Remove account mutation +export function useRemoveAccountMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: removeAccount, + onMutate: async (alias: string) => { + await queryClient.cancelQueries({ queryKey: queryKeys.dashboardState }) + + const previousState = queryClient.getQueryData(queryKeys.dashboardState) + + queryClient.setQueryData(queryKeys.dashboardState, (currentState) => { + if (!currentState) return currentState + + const codexActive = currentState.codexActive?.alias === alias + ? { ...currentState.codexActive, status: 'unknown' as const, alias: null } + : currentState.codexActive + + return { + ...currentState, + deviceAlias: currentState.deviceAlias === alias ? null : currentState.deviceAlias, + codexActive, + accounts: currentState.accounts.filter((account) => account.alias !== alias) + } + }) + + return { previousState } + }, + onError: (_error, _alias, context) => { + if (context?.previousState) { + queryClient.setQueryData(queryKeys.dashboardState, context.previousState) + } + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState, refetchType: 'all' }) + } + }) +} + +// Update account meta mutation +export function useUpdateAccountMetaMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { alias: string; tags?: string; notes?: string }) => updateAccountMeta(vars.alias, vars.tags, vars.notes), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Reauth account mutation +export function useReauthAccountMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { alias: string; actor?: string }) => reauthAccount(vars.alias, vars.actor), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Logs query +export function useLogsQuery(limit = 50) { + return useQuery({ + queryKey: queryKeys.logs(limit), + queryFn: () => getLogs(limit), + refetchInterval: 5000, + staleTime: 2000 + }) +} + +// Force state query +export function useForceStateQuery() { + return useQuery({ + queryKey: queryKeys.forceState, + queryFn: getForceState, + refetchInterval: 5000, + staleTime: 2000 + }) +} + +// Activate force mutation +export function useActivateForceMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { alias: string; actor?: string }) => activateForce(vars.alias, vars.actor), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.forceState }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Clear force mutation +export function useClearForceMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: clearForce, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.forceState }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Refresh antigravity quota mutation +export function useRefreshAntigravityMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: refreshAntigravityQuota, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Refresh all antigravity quotas mutation +export function useRefreshAntigravityAllMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: refreshAntigravityQuotaAll, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Settings query +export function useSettingsQuery() { + return useQuery({ + queryKey: queryKeys.settings, + queryFn: getSettings, + refetchInterval: 5000, + staleTime: 2000 + }) +} + +// Update settings mutation +export function useUpdateSettingsMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { updates: Parameters[0] }) => updateSettings(vars.updates), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.settings }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Feature flags query +export function useFeatureFlagsQuery() { + return useQuery({ + queryKey: queryKeys.featureFlags, + queryFn: getFeatureFlags, + refetchInterval: 5000, + staleTime: 2000 + }) +} + +// Update feature flags mutation +export function useUpdateFeatureFlagsMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { featureFlags: import('../types/api.ts').FeatureFlags; actor?: string }) => + updateFeatureFlags(vars.featureFlags, vars.actor), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.featureFlags }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Reset settings mutation +export function useResetSettingsMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (actor?: string) => resetSettings(actor), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.settings }) + queryClient.invalidateQueries({ queryKey: queryKeys.featureFlags }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Apply preset mutation +export function useApplyPresetMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (vars: { preset: import('../types/api.ts').WeightPreset; actor?: string }) => applyPreset(vars.preset, vars.actor), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.settings }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Sticky session config query +export function useStickySessionConfigQuery() { + return useQuery({ + queryKey: queryKeys.stickySessionConfig, + queryFn: getStickySessionConfig, + enabled: false // manual fetch; panel triggers on mount + }) +} + +// Sticky session status query +export function useStickySessionStatusQuery() { + return useQuery({ + queryKey: queryKeys.stickySessionStatus, + queryFn: getStickySessionStatus, + enabled: false // manual refresh; no polling + }) +} + +// Update sticky session config mutation +export function useUpdateStickySessionConfigMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (config: import('../types/api.ts').StickySessionSettings) => updateStickySessionConfig(config), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.stickySessionConfig }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} + +// Cleanup sticky sessions mutation +export function useCleanupStickySessionsMutation() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: cleanupStickySessions, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.stickySessionStatus }) + queryClient.invalidateQueries({ queryKey: queryKeys.dashboardState }) + } + }) +} diff --git a/web/src/components/AccountDrawer.css b/web/src/components/AccountDrawer.css new file mode 100644 index 0000000..d08fdf3 --- /dev/null +++ b/web/src/components/AccountDrawer.css @@ -0,0 +1,237 @@ +.account-drawer-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + z-index: 50; + display: flex; + justify-content: flex-end; +} + +.account-drawer { + width: 420px; + max-width: 90vw; + height: 100%; + background-color: var(--bg-secondary); + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + overflow-y: auto; + box-shadow: var(--shadow-xl); +} + +.account-drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-lg) var(--spacing-xl); + border-bottom: 1px solid var(--border); + background: var(--surface); +} + +.account-drawer-header h3 { + margin: 0; + font-size: 1.125rem; + font-weight: 600; + color: var(--text); + letter-spacing: -0.01em; +} + +.account-drawer-body { + padding: var(--spacing-xl); + display: flex; + flex-direction: column; + gap: var(--spacing-xl); +} + +.drawer-section { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.drawer-section h4 { + margin: 0 0 var(--spacing-xs) 0; + font-size: 0.875rem; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.drawer-field { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: var(--spacing-md); + padding: var(--spacing-sm) 0; + border-bottom: 1px solid var(--border-subtle); +} + +.drawer-field:last-child { + border-bottom: none; +} + +.drawer-label { + color: var(--text-secondary); + font-size: 0.875rem; + min-width: 100px; + font-weight: 500; +} + +.drawer-value { + word-break: break-word; + text-align: right; + color: var(--text); + font-weight: 500; +} + +.drawer-actions { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.drawer-actions button { + width: 100%; + justify-content: center; + border-radius: var(--radius); + font-weight: 500; +} + +.icon-button { + background: var(--surface); + border: 1px solid var(--border); + font-size: 1.25rem; + line-height: 1; + width: 2.25rem; + height: 2.25rem; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + color: var(--text-secondary); + border-radius: var(--radius); + transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease; +} + +.icon-button:hover { + background: var(--surface-elevated); + color: var(--text); + border-color: var(--border-strong); +} + +.icon-button:focus-visible { + outline: none; + box-shadow: var(--focus-ring); +} + +input, +textarea { + padding: var(--spacing-sm) var(--spacing-md); + background: var(--bg); + border: 1px solid var(--border-strong); + border-radius: var(--radius); + color: var(--text); + width: 100%; + font-size: 0.875rem; + box-shadow: var(--shadow-inset); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +input:focus-visible, +textarea:focus-visible { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2), var(--shadow-inset); +} + +textarea { + resize: vertical; + min-height: 80px; +} + +/* Codex active banner at top of drawer body */ +.drawer-codex-active-banner { + display: flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-md); + background: rgba(245, 158, 11, 0.08); + border: 1px solid rgba(245, 158, 11, 0.25); + border-radius: var(--radius); + font-size: 0.875rem; + color: #fbbf24; + line-height: 1.4; +} + +.drawer-codex-active-banner code { + font-family: var(--font-mono); + font-size: 0.8125rem; + opacity: 0.85; +} + +.drawer-codex-active-icon { + font-size: 1rem; + flex-shrink: 0; +} + +/* Codex badges in drawer */ +.badge--codex-active { + background: rgba(245, 158, 11, 0.12); + color: #fbbf24; + border: 1px solid rgba(245, 158, 11, 0.3); + display: inline-flex; + align-items: center; + padding: 3px 8px; + border-radius: var(--radius-full); + font-size: 0.6875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + line-height: 1; +} + +.badge--codex-inactive { + background: var(--border-subtle); + color: var(--text-muted); + border: 1px solid transparent; + display: inline-flex; + align-items: center; + padding: 3px 8px; + border-radius: var(--radius-full); + font-size: 0.6875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + line-height: 1; +} + +/* Use in Codex button — styled as secondary with amber accent when active */ +.codex-use-btn:disabled[aria-disabled="true"] { + background: rgba(245, 158, 11, 0.08); + border-color: rgba(245, 158, 11, 0.25); + color: #fbbf24; + opacity: 1; + cursor: default; +} + +/* Codex feedback messages in drawer */ +.drawer-codex-feedback { + padding: var(--spacing-sm) var(--spacing-md); + border-radius: var(--radius); + font-size: 0.8125rem; + line-height: 1.4; +} + +.drawer-codex-feedback--success { + background: var(--success-subtle); + color: #86efac; + border: 1px solid rgba(34, 197, 94, 0.2); +} + +.drawer-codex-feedback--error { + background: var(--error-subtle); + color: #fca5a5; + border: 1px solid rgba(239, 68, 68, 0.2); +} diff --git a/web/src/components/AccountDrawer.tsx b/web/src/components/AccountDrawer.tsx new file mode 100644 index 0000000..b6eae4e --- /dev/null +++ b/web/src/components/AccountDrawer.tsx @@ -0,0 +1,245 @@ +import { useState, useCallback, useEffect, useRef } from 'react' +import { isAccountEnabled } from '../lib/account-status' +import { formatResetTime } from '../lib/reset-time' +import type { AccountView, CodexActiveState } from '../types/api' +import './AccountDrawer.css' + +export interface AccountDrawerProps { + account: AccountView + isActive: boolean + codexActive?: CodexActiveState + onClose: () => void + onToggleEnable: () => void + onRemove: () => void + onUpdateMeta: (tags: string, notes: string) => void + onReauth: () => void + onRefreshTokens: () => void + onRefreshLimits: () => void + onSwitch: () => void + onUseInCodex?: () => void + useInCodexPending?: boolean + useInCodexError?: string | null + useInCodexSuccess?: boolean + isBusy: boolean +} + +export function AccountDrawer({ + account, + isActive, + codexActive, + onClose, + onToggleEnable, + onRemove, + onUpdateMeta, + onReauth, + onRefreshTokens, + onRefreshLimits, + onSwitch, + onUseInCodex, + useInCodexPending = false, + useInCodexError = null, + useInCodexSuccess = false, + isBusy +}: AccountDrawerProps): JSX.Element { + const [tags, setTags] = useState(account.tags?.join(', ') || '') + const [notes, setNotes] = useState(account.notes || '') + const closeButtonRef = useRef(null) + + const isCodexActive = codexActive?.status === 'matched' && codexActive.alias === account.alias + + const handleSaveMeta = useCallback(() => { + onUpdateMeta(tags, notes) + }, [onUpdateMeta, tags, notes]) + + const handleRemove = useCallback(() => { + if (window.confirm(`Remove account "${account.alias}" permanently?`)) { + onRemove() + } + }, [onRemove, account.alias]) + + useEffect(() => { + closeButtonRef.current?.focus() + }, []) + + useEffect(() => { + function handleKeyDown(e: KeyboardEvent) { + if (e.key === 'Escape') { + onClose() + } + } + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [onClose]) + + return ( +
+
e.stopPropagation()} + role="dialog" + aria-label="Account details" + aria-modal="true" + > +
+

{account.alias}

+ +
+ +
+ {isCodexActive && ( +
+ + Active in Codex — this account is written to ~/.codex/auth.json +
+ )} +
+
+ Email + {account.email || '-'} +
+
+ Status + + {isAccountEnabled(account) ? ( + enabled + ) : ( + disabled + )} + +
+
+ Codex + + {isCodexActive ? ( + Codex ✓ + ) : ( + Not in Codex + )} + +
+
+ Source + {account.source || '-'} +
+
+ Usage + {account.usageCount} +
+ {account.disabledAt && ( +
+ Disabled + + {new Date(account.disabledAt).toLocaleString()} by {account.disabledBy || 'unknown'} + +
+ )} + {account.lastRefresh && ( +
+ Last refresh + {new Date(account.lastRefresh).toLocaleString()} +
+ )} + {account.rateLimits?.fiveHour?.resetAt && ( +
+ 5h reset + {formatResetTime(account.rateLimits.fiveHour.resetAt)} +
+ )} + {account.rateLimits?.weekly?.resetAt && ( +
+ Weekly reset + {formatResetTime(account.rateLimits.weekly.resetAt)} +
+ )} +
+ +
+ {!isActive && isAccountEnabled(account) && ( + + )} + {onUseInCodex !== undefined && ( + + )} + {useInCodexSuccess && !isCodexActive && ( +
+ ✓ Codex account updated +
+ )} + {useInCodexError && ( +
+ {useInCodexError} +
+ )} + + + + + +
+ +
+

Tags

+ setTags(e.target.value)} + placeholder="Comma-separated tags" + aria-label="Tags" + /> +

Notes

+