diff --git a/cli/scripts/build-all.ts b/cli/scripts/build-all.ts deleted file mode 100644 index 8cfc1d5..0000000 --- a/cli/scripts/build-all.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { $ } from 'bun' - -const targets = [ - { target: 'bun-darwin-arm64', out: 'hoist-darwin-arm64' }, - { target: 'bun-darwin-x64', out: 'hoist-darwin-x64' }, - { target: 'bun-linux-arm64', out: 'hoist-linux-arm64' }, - { target: 'bun-linux-x64', out: 'hoist-linux-x64' }, - { target: 'bun-windows-x64', out: 'hoist-windows-x64.exe' }, -] - -await $`mkdir -p dist` - -for (const { target, out } of targets) { - console.log(`→ building ${target}`) - try { - await $`bun build src/index.ts --compile --outfile=dist/${out} --target=${target}`.cwd(process.cwd()) - } catch (err) { - console.error(` ✗ ${target}: ${(err as Error).message}`) - } -} - -console.log('✓ done') diff --git a/cli/src/index.ts b/cli/src/index.ts index 61d5c2b..05c18c6 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -4,6 +4,7 @@ import { discoverAll, findHarness, installHarness, + HARNESS_CATALOG, } from './lib/harnesses' import { deleteSecret, @@ -13,9 +14,18 @@ import { secretIdFor, setSecret, } from './lib/vault' -import { PROVIDER_CATALOG, findProvider } from './lib/providers' -import { probeProvider } from './lib/probes' -import { readConfig, writeConfig } from './lib/vault' +import { + findProvider, + findGateway, + GATEWAY_CATALOG, + listGateways, + selectAnthropicEndpoint, + selectOpenAIEndpoint, + unresolvedPlaceholders, + type ProviderEntry, +} from './lib/providers' +import { probeAnthropic } from './lib/probes' +import { applyWiring, type WireResult } from './lib/wiring' const VERSION = '0.0.0-dev' @@ -78,7 +88,8 @@ program console.log(`Unknown provider: ${providerId}`) process.exit(1) } - const input = await p.password({ message: `Paste ${provider.envKeys[0]}:`, mask: '*' }) + const envKeys = provider.envKeys ?? [] + const input = await p.password({ message: `Paste ${envKeys[0] ?? 'token'}:`, mask: '*' }) if (!input || p.isCancel(input)) { p.outro('Cancelled.') return @@ -104,7 +115,15 @@ program } const s = p.spinner() s.start(`Probing ${provider.label}…`) - const result = await probeProvider(provider, value, provider.defaultBaseUrl) + const result = + provider.probeKind === 'anthropicModels' + ? await probeAnthropic({ apiKey: value, baseUrl: provider.defaultBaseUrl ?? 'https://api.anthropic.com' }) + : { + valid: false, + status: 'error' as const, + detail: `CLI probe for "${provider.id}" not implemented yet. Use the app's Validate button.`, + checkedAt: new Date().toISOString(), + } s.stop(`${result.valid ? '✓' : '✗'} ${result.status}${result.detail ? ` · ${result.detail}` : ''}`) process.exit(result.valid ? 0 : 1) } @@ -121,53 +140,183 @@ program console.log(`Unknown action: ${action}`) }) -program - .command('gateway') - .description('Manage AI gateway routing.') - .argument('[action]', 'show | set | clear', 'show') - .argument('[baseUrl]', 'Gateway base URL') - .action(async (action: string, baseUrl?: string) => { - const config = await readConfig() - if (action === 'show') { - if (!config.gateway?.baseUrl) { - console.log('No gateway configured.') - return - } - console.log(`${config.gateway.provider ?? 'default'}: ${config.gateway.baseUrl}`) - return +const gateway = program.command('gateway').description('Manage AI gateway routing & per-harness wiring.') + +gateway + .command('list') + .description('List known AI gateways.') + .action(() => { + for (const g of listGateways()) { + console.log(`${pad(g.id, 24)} ${g.label}`) + console.log(`${' '.repeat(24)} base: ${g.baseUrl || '(custom)'}`) + const ph = g.placeholders.length ? `placeholders: ${g.placeholders.join(', ')}` : null + const ep = `endpoints: ${Object.entries(g.endpoints).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join(', ') || '(none)'}` + const auth = `auth: ${g.auth.header}${g.auth.scheme && g.auth.scheme !== 'raw' ? ` (${g.auth.scheme})` : ''} · env: ${g.auth.envVar}` + console.log(`${' '.repeat(24)} ${ep}`) + console.log(`${' '.repeat(24)} ${auth}`) + if (ph) console.log(`${' '.repeat(24)} ${ph}`) + if (g.notes) console.log(`${' '.repeat(24)} ${g.notes}`) + console.log() } - if (action === 'set') { - if (!baseUrl) { - console.log('Usage: hoist gateway set ') + }) + +gateway + .command('show') + .description('Show current gateway configuration.') + .action(() => { + console.log('No gateway manager yet. Use `hoist gateway use ` to apply one.') + }) + +gateway + .command('use ') + .description('Apply a gateway to the selected harnesses (writes Claude Code, Codex, OpenCode config).') + .option('-p, --provider ', 'Provider id (default: anthropic)') + .option('-u, --base-url ', 'Override the gateway base URL (placeholder-substitute)') + .option('-k, --api-key ', 'API key (prompted if omitted)') + .option('-H, --harness ', 'Comma-separated harness ids (default: installed harnesses)', 'installed') + .option('--dry-run', 'Print effective base URL & intended writes without touching files') + .action( + async (gatewayId: string, opts: { provider?: string; baseUrl?: string; apiKey?: string; harness?: string; dryRun?: boolean }) => { + const gatewayEntry: import('./lib/providers').GatewayEntry | null = + gatewayId === 'direct' ? null : findGateway(gatewayId) ?? null + if (gatewayId !== 'direct' && !gatewayEntry) { + console.log(`Unknown gateway: ${gatewayId}`) process.exit(1) } - config.gateway = { baseUrl, provider: 'anthropic' } - await writeConfig(config) - console.log(`Gateway set: ${baseUrl}`) - console.log(`To route Claude Code: export ANTHROPIC_BASE_URL=${baseUrl}`) - return + const provider: ProviderEntry | undefined = findProvider(opts.provider ?? 'anthropic') + if (!provider) { + console.log(`Unknown provider: ${opts.provider ?? 'anthropic'}`) + process.exit(1) + } + + const resolvedBaseUrl = opts.baseUrl ?? gatewayEntry?.baseUrl ?? provider.defaultBaseUrl ?? '' + const placeholders = unresolvedPlaceholders(resolvedBaseUrl) + if (placeholders.length > 0) { + console.log(`Base URL still has placeholders: ${placeholders.map((p) => `<${p}>`).join(', ')}`) + console.log(`Re-run with \`hoist gateway use ${gatewayId} --base-url \`.`) + process.exit(1) + } + + const effectiveBaseUrl = (() => { + if (!gatewayEntry) return resolvedBaseUrl.replace(/\/+$/, '') + const anthropic = selectAnthropicEndpoint(gatewayEntry, resolvedBaseUrl) + if (provider.probeKind === 'anthropicModels' && anthropic) return anthropic + return selectOpenAIEndpoint(gatewayEntry, resolvedBaseUrl) + })() + + const apiKey = + opts.apiKey ?? + (await getSecret(secretIdFor(provider.id))) ?? + (await p.password({ message: `Paste ${provider.envKeys?.[0] ?? 'token'}:`, mask: '*' })) + + if (!apiKey || p.isCancel(apiKey)) { + console.log('No API key.') + process.exit(1) + } + // Persist into vault for next time. + await setSecret(secretIdFor(provider.id), String(apiKey), provider.label) + + // Decide harnesses + const harnessIds = opts.harness ?? 'installed' + const harnesses: typeof HARNESS_CATALOG = + harnessIds === 'installed' + ? (await discoverAll()).filter((h) => h.installed).map((h) => h.spec) + : harnessIds.split(',').map((id) => findHarness(id.trim())).filter((s): s is NonNullable => !!s) + if (harnesses.length === 0) { + console.log('No harnesses to apply wiring to.') + return + } + + console.log(`\n→ Effective base URL: ${effectiveBaseUrl}`) + console.log(`→ Provider: ${provider.label} (${provider.id})`) + console.log(`→ Gateway: ${gatewayEntry?.label ?? 'direct (no gateway)'}`) + console.log(`→ Harnesses: ${harnesses.map((h) => h.id).join(', ')}\n`) + if (opts.dryRun) { + console.log('(dry-run, no files written)') + return + } + + const allResults: { harnessId: string; results: WireResult[] }[] = [] + for (const h of harnesses) { + try { + const results = await applyWiring({ + apiKey: String(apiKey), + baseUrl: effectiveBaseUrl, + harness: h, + provider, + gateway: gatewayEntry, + }) + allResults.push({ harnessId: h.id, results }) + } catch (err) { + console.log(`✗ ${h.name}: ${errMsg(err)}`) + } + } + for (const { harnessId, results } of allResults) { + for (const r of results) { + console.log(`✓ ${harnessId}: ${r.path || '(no file)'}${r.note ? ` · ${r.note}` : ''}`) + } + } + }, + ) + +const harness = program.command('harness').description('Inspect agent harness configuration.') + +harness + .command('list') + .description('List known harnesses.') + .action(() => { + for (const h of HARNESS_CATALOG) { + console.log(`${pad(h.id, 14)} ${h.name}`) } - if (action === 'clear') { - delete config.gateway - await writeConfig(config) - console.log('Gateway cleared.') - return + }) + +harness + .command('config [id]') + .description('Show path + excerpt of a harness config file (claude-code | codex | opencode).') + .action(async (id?: string) => { + const ids = id ? [id] : HARNESS_CATALOG.map((h) => h.id) + for (const x of ids) { + const home = process.env.HOME ?? '' + const path = + x === 'claude-code' + ? `${home}/.claude/settings.json` + : x === 'codex' + ? `${home}/.codex/config.toml` + : x === 'opencode' + ? `${process.env.XDG_CONFIG_HOME ?? `${home}/.config`}/opencode/opencode.json` + : '?' + console.log(`\n— ${x}`) + console.log(` ${path}`) + try { + const { readFile } = await import('node:fs/promises') + const blob = await readFile(path, 'utf8') + const truncated = blob.length > 600 ? blob.slice(0, 600) + '\n…(truncated)' : blob + console.log(' ---') + console.log(truncated.split('\n').map((line) => ' ' + line).join('\n')) + } catch (err) { + const msg = (err as NodeJS.ErrnoException).code === 'ENOENT' ? '(not found)' : String((err as Error).message ?? err) + console.log(` ${msg}`) + } } }) program .command('list') - .description('Show providers and harnesses known to Hoist.') + .description('Show providers, gateways and harnesses known to Hoist.') .action(async () => { - console.log('\nHarnesses:') const harnesses = await discoverAll() + console.log('\nHarnesses:') for (const h of harnesses) { const status = h.installed ? `installed · ${h.version ?? 'unknown'}` : 'not installed' console.log(` ${pad(h.spec.id, 14)} ${pad(h.spec.name, 16)} ${status}`) } console.log('\nProviders:') - for (const provider of PROVIDER_CATALOG) { - console.log(` ${pad(provider.id, 12)} ${provider.label} (${provider.envKeys.join(', ')})`) + for (const p of (await import('./lib/providers')).PROVIDER_CATALOG) { + console.log(` ${pad(p.id, 16)} ${p.label} (${(p.envKeys ?? []).join(', ') || 'cloud creds'})`) + } + console.log('\nGateways:') + for (const g of GATEWAY_CATALOG) { + console.log(` ${pad(g.id, 24)} ${g.label} ${g.baseUrl || '(custom)'}`) } }) @@ -179,8 +328,6 @@ function errMsg(err: unknown): string { return err instanceof Error ? err.message : String(err) } -// Commander fontkit is not actually needed; removing import. - program.parseAsync(process.argv).catch((err) => { console.error(errMsg(err)) process.exit(1) diff --git a/cli/src/lib/harnesses.ts b/cli/src/lib/harnesses.ts index 3fcc69f..1b23ed4 100644 --- a/cli/src/lib/harnesses.ts +++ b/cli/src/lib/harnesses.ts @@ -2,6 +2,7 @@ import { spawn } from 'node:child_process' import { access, constants } from 'node:fs/promises' import { dirname, join } from 'node:path' import which from 'which' + export interface HarnessSpec { id: string name: string diff --git a/cli/src/lib/probes.ts b/cli/src/lib/probes.ts index c62e5be..d3cbcfd 100644 --- a/cli/src/lib/probes.ts +++ b/cli/src/lib/probes.ts @@ -16,12 +16,12 @@ export async function probeProvider(provider: ProviderEntry, apiKey: string, bas return { valid: false, status: 'invalid', detail: 'No API key supplied.', checkedAt } } if (provider.id === 'anthropic') { - return probeAnthropic(apiKey, baseUrl ?? provider.defaultBaseUrl ?? 'https://api.anthropic.com') + return probeAnthropic({ apiKey, baseUrl: baseUrl ?? provider.defaultBaseUrl ?? 'https://api.anthropic.com' }) } return { valid: false, status: 'error', detail: `No probe implemented for "${provider.id}".`, checkedAt } } -async function probeAnthropic(apiKey: string, baseUrl: string): Promise { +export async function probeAnthropic({ apiKey, baseUrl }: { apiKey: string; baseUrl: string }): Promise { const checkedAt = new Date().toISOString() const controller = new AbortController() const timer = setTimeout(() => controller.abort(), TIMEOUT_MS) diff --git a/cli/src/lib/providers.ts b/cli/src/lib/providers.ts index e735cc5..11576d6 100644 --- a/cli/src/lib/providers.ts +++ b/cli/src/lib/providers.ts @@ -1,30 +1,26 @@ -export interface ProviderEntry { - id: string - label: string - aliases?: string[] - featured?: boolean - envKeys: string[] - baseUrlEnv?: string - defaultBaseUrl?: string - notes?: string -} +import { PROVIDER_CATALOG, findProvider, type ProviderEntry } from '../../../src/main/providers/catalog' +export { PROVIDER_CATALOG, findProvider, type ProviderEntry } from '../../../src/main/providers/catalog' + +import { GATEWAY_CATALOG, findGateway, type GatewayEntry } from '../../../src/main/gateways' +export { GATEWAY_CATALOG, findGateway, type GatewayEntry } from '../../../src/main/gateways' + +import { + selectAnthropicEndpoint, + selectOpenAIEndpoint, + unresolvedPlaceholders, +} from '../../../src/main/gateways/resolve' -export const PROVIDER_CATALOG: ProviderEntry[] = [ - { - id: 'anthropic', - label: 'Anthropic', - aliases: ['claude'], - featured: true, - envKeys: ['ANTHROPIC_API_KEY'], - baseUrlEnv: 'ANTHROPIC_BASE_URL', - defaultBaseUrl: 'https://api.anthropic.com', - notes: 'Claude API. Look for `sk-ant-…` keys.', - }, -] +export { selectAnthropicEndpoint, selectOpenAIEndpoint, unresolvedPlaceholders } + +/** CLI-context helper: list provider entries with envKeys defaulted to []. */ +export function listProviders(): (ProviderEntry & { envKeys: string[] })[] { + return PROVIDER_CATALOG.map((p) => ({ ...p, envKeys: p.envKeys ?? [] })) +} -export function findProvider(idOrAlias: string): ProviderEntry | undefined { - const lower = idOrAlias.toLowerCase() - return PROVIDER_CATALOG.find( - (p) => p.id === lower || p.aliases?.some((a) => a.toLowerCase() === lower), - ) +/** CLI-context helper: list gateway entries with `placeholders` resolved. */ +export function listGateways() { + return GATEWAY_CATALOG.map((g) => ({ + ...g, + placeholders: unresolvedPlaceholders(g.baseUrl), + })) } diff --git a/cli/src/lib/wiring.ts b/cli/src/lib/wiring.ts new file mode 100644 index 0000000..ac556f7 --- /dev/null +++ b/cli/src/lib/wiring.ts @@ -0,0 +1,6 @@ +import { + applyWiring, + type WireOptions, + type WireResult, +} from '../../../src/main/wiring' +export { applyWiring, type WireOptions, type WireResult } diff --git a/cli/tsconfig.json b/cli/tsconfig.json index a91d39a..f0bd209 100644 --- a/cli/tsconfig.json +++ b/cli/tsconfig.json @@ -16,5 +16,5 @@ "@hoist/shared/*": ["src/shared/*"] } }, - "include": ["src", "../src/shared"] + "include": ["src", "../src/main", "../src/shared", "../src/main/providers/catalog.source.json", "../src/main/gateways/catalog.source.json"] } diff --git a/package-lock.json b/package-lock.json index fa82b61..1f7d364 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,15 @@ "version": "0.0.0", "license": "UNLICENSED", "dependencies": { - "@types/which": "^3.0.4", "react": "^19.1.0", "react-dom": "^19.1.0", - "which": "^7.0.0" + "which": "^5.0.0" }, "devDependencies": { + "@types/node": "^22.10.0", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", + "@types/which": "^3.0.4", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react": "^4.4.1", @@ -29,13 +30,13 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -44,9 +45,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -54,21 +55,21 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "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", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -84,15 +85,25 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/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/@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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -102,14 +113,14 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -118,10 +129,20 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/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/@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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -129,29 +150,29 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -161,9 +182,9 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -171,9 +192,9 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -181,9 +202,9 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -191,9 +212,9 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -201,27 +222,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -231,13 +252,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -247,13 +268,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -263,33 +284,33 @@ } }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "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==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "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", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -297,14 +318,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -346,17 +367,10 @@ "node": ">=10.12.0" } }, - "node_modules/@electron/asar/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/@electron/asar/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -399,6 +413,16 @@ "global-agent": "^3.0.0" } }, + "node_modules/@electron/get/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/@electron/notarize": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", @@ -583,19 +607,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/@electron/rebuild/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/@electron/rebuild/node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -625,27 +636,10 @@ "node": ">=16.4" } }, - "node_modules/@electron/universal/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/@electron/universal/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", "dependencies": { @@ -670,22 +664,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@electron/universal/node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -1191,13 +1169,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -1255,13 +1226,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/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/@humanwhocodes/config-array/node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -1591,19 +1555,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@npmcli/fs/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/@npmcli/move-file": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", @@ -1638,9 +1589,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", "cpu": [ "arm" ], @@ -1652,9 +1603,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", "cpu": [ "arm64" ], @@ -1666,9 +1617,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", "cpu": [ "arm64" ], @@ -1680,9 +1631,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", "cpu": [ "x64" ], @@ -1694,9 +1645,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", "cpu": [ "arm64" ], @@ -1708,9 +1659,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", "cpu": [ "x64" ], @@ -1722,9 +1673,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", "cpu": [ "arm" ], @@ -1739,9 +1690,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", "cpu": [ "arm" ], @@ -1756,9 +1707,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", "cpu": [ "arm64" ], @@ -1773,9 +1724,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", "cpu": [ "arm64" ], @@ -1790,9 +1741,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", "cpu": [ "loong64" ], @@ -1807,9 +1758,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", "cpu": [ "loong64" ], @@ -1824,9 +1775,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", "cpu": [ "ppc64" ], @@ -1841,9 +1792,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", "cpu": [ "ppc64" ], @@ -1858,9 +1809,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", "cpu": [ "riscv64" ], @@ -1875,9 +1826,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", "cpu": [ "riscv64" ], @@ -1892,9 +1843,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", "cpu": [ "s390x" ], @@ -1909,9 +1860,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", "cpu": [ "x64" ], @@ -1926,9 +1877,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", "cpu": [ "x64" ], @@ -1943,9 +1894,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", "cpu": [ "x64" ], @@ -1957,9 +1908,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", "cpu": [ "arm64" ], @@ -1971,9 +1922,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", "cpu": [ "arm64" ], @@ -1985,9 +1936,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", "cpu": [ "ia32" ], @@ -1999,9 +1950,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", "cpu": [ "x64" ], @@ -2013,9 +1964,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", "cpu": [ "x64" ], @@ -2131,9 +2082,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -2172,9 +2123,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", - "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2194,9 +2145,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dev": true, "license": "MIT", "dependencies": { @@ -2235,6 +2186,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz", "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==", + "dev": true, "license": "MIT" }, "node_modules/@types/yauzl": { @@ -2400,52 +2352,6 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", @@ -2707,6 +2613,29 @@ "electron-builder-squirrel-windows": "25.1.8" } }, + "node_modules/app-builder-lib/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/app-builder-lib/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/app-builder-lib/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -2735,17 +2664,20 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/app-builder-lib/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/app-builder-lib/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/app-builder-lib/node_modules/universalify": { @@ -2933,14 +2865,11 @@ } }, "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "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", - "engines": { - "node": "18 || 20 || >=22" - } + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -2964,9 +2893,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.27", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", - "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3015,16 +2944,13 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -3041,9 +2967,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -3061,10 +2987,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -3223,23 +3149,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/cacache/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/cacache/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/cacache/node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -3338,9 +3247,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -3601,15 +3510,15 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", - "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", "dev": true, "license": "MIT", "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", - "shell-quote": "1.8.3", + "shell-quote": "1.9.0", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" @@ -3636,23 +3545,6 @@ "typescript": "^5.4.3" } }, - "node_modules/config-file-ts/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/config-file-ts/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/config-file-ts/node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -3675,22 +3567,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/config-file-ts/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/config-file-ts/node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -3968,17 +3844,10 @@ "p-limit": "^3.1.0 " } }, - "node_modules/dir-compare/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/dir-compare/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -4368,9 +4237,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.349", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", - "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", + "version": "1.5.393", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", + "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", "dev": true, "license": "ISC" }, @@ -4440,9 +4309,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -4628,13 +4497,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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/eslint/node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -4833,24 +4695,6 @@ "pend": "~1.2.0" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4874,23 +4718,6 @@ "minimatch": "^5.0.1" } }, - "node_modules/filelist/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/filelist/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -4987,17 +4814,17 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -5202,17 +5029,10 @@ "node": ">=10.13.0" } }, - "node_modules/glob/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/glob/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -5252,20 +5072,6 @@ "node": ">=10.0" } }, - "node_modules/global-agent/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", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -5282,19 +5088,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -5448,9 +5241,9 @@ "license": "ISC" }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -5805,12 +5598,12 @@ } }, "node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "license": "BlueOak-1.0.0", "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/jackspeak": { @@ -5855,10 +5648,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -6248,19 +6051,6 @@ "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -6318,16 +6108,16 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6475,9 +6265,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -6511,9 +6301,9 @@ } }, "node_modules/node-abi": { - "version": "3.90.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.90.0.tgz", - "integrity": "sha512-pZNQT7UnYlMwMBy5N1lV5X/YLTbZM5ncytN3xL7CHEzhDN8uVe0u55yaPUJICIJjaCW8NrM5BFdqr7HLweStNA==", + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", "dev": true, "license": "MIT", "dependencies": { @@ -6523,19 +6313,6 @@ "node": ">=10" } }, - "node_modules/node-abi/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/node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -6554,19 +6331,6 @@ "semver": "^7.3.5" } }, - "node_modules/node-api-version/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/node-gyp": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", @@ -6600,19 +6364,6 @@ "dev": true, "license": "ISC" }, - "node_modules/node-gyp/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/node-gyp/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6630,11 +6381,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/nopt": { "version": "6.0.0", @@ -6954,13 +6708,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -6982,9 +6736,9 @@ } }, "node_modules/postcss": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", - "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "dev": true, "funding": [ { @@ -7002,7 +6756,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -7115,24 +6869,24 @@ } }, "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.5" + "react": "^19.2.7" } }, "node_modules/react-refresh": { @@ -7184,25 +6938,6 @@ "minimatch": "^5.1.0" } }, - "node_modules/readdir-glob/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", - "peer": true - }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/readdir-glob/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -7347,13 +7082,13 @@ } }, "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -7363,31 +7098,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" } }, @@ -7480,13 +7215,16 @@ "license": "MIT" }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver-compare": { @@ -7514,6 +7252,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -7545,9 +7297,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "dev": true, "license": "MIT", "engines": { @@ -7577,19 +7329,6 @@ "node": ">=10" } }, - "node_modules/simple-update-notifier/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/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -7628,9 +7367,9 @@ } }, "node_modules/socks": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", - "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", "dev": true, "license": "MIT", "dependencies": { @@ -7953,9 +7692,9 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -7969,10 +7708,41 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", "engines": { @@ -8056,12 +7826,11 @@ } }, "node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", - "optional": true, "engines": { "node": ">=10" }, @@ -8198,9 +7967,9 @@ } }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { @@ -8272,6 +8041,37 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -8283,18 +8083,18 @@ } }, "node_modules/which": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-7.0.0.tgz", - "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "license": "ISC", "dependencies": { - "isexe": "^4.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/wide-align": { diff --git a/package.json b/package.json index a04bd85..305625d 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,9 @@ "start": "electron dist/main/index.js", "lint": "eslint src --ext .ts,.tsx", "typecheck": "tsc -p tsconfig.node.json --noEmit && tsc -p tsconfig.json --noEmit", + "gen:catalog": "node scripts/gen-catalog.mjs", + "prebuild": "npm run gen:catalog", + "prebuild:renderer": "npm run gen:catalog", "package": "npm run build && electron-builder" }, "dependencies": { diff --git a/scripts/gen-catalog.mjs b/scripts/gen-catalog.mjs new file mode 100644 index 0000000..270c143 --- /dev/null +++ b/scripts/gen-catalog.mjs @@ -0,0 +1,72 @@ +#!/usr/bin/env -S node --no-warnings +'use strict' + +import { readFile, writeFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import { dirname, resolve } from 'node:path' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const ROOT = resolve(__dirname, '..') +const SOURCES = [ + { + src: resolve(ROOT, 'src/main/providers/catalog.source.json'), + out: resolve(ROOT, 'src/main/providers/catalog.generated.ts'), + type: 'ProviderEntry', + name: 'PROVIDER_CATALOG', + collection: 'providers', + heading: 'src/main/providers/catalog.source.json', + }, + { + src: resolve(ROOT, 'src/main/gateways/catalog.source.json'), + out: resolve(ROOT, 'src/main/gateways/catalog.generated.ts'), + type: 'GatewayEntry', + name: 'GATEWAY_CATALOG', + collection: 'gateways', + heading: 'src/main/gateways/catalog.source.json', + }, +] + +function jsonToTs(value, indent = 2) { + if (value === null) return 'null' + if (typeof value === 'string') return JSON.stringify(value) + if (typeof value === 'number' || typeof value === 'boolean') return String(value) + if (Array.isArray(value)) { + if (value.length === 0) return '[]' + const pad = ' '.repeat(indent) + const inner = value.map((v) => `${pad} ${jsonToTs(v, indent + 2)}`).join(',\n') + return `[\n${inner}\n${pad}]` + } + if (typeof value === 'object') { + const keys = Object.keys(value) + if (keys.length === 0) return '{}' + const pad = ' '.repeat(indent) + const inner = keys.map((k) => `${pad} ${JSON.stringify(k)}: ${jsonToTs(value[k], indent + 2)}`).join(',\n') + return `{\n${inner}\n${pad}}` + } + throw new Error(`Unsupported value type: ${typeof value}`) +} + +async function generate(src, out, typeName, exportName, collection, heading) { + const raw = await readFile(src, 'utf8') + const parsed = JSON.parse(raw) + const entries = parsed[collection] + const header = `/** + * GENERATED FILE — DO NOT EDIT BY HAND. + * + * To add or modify entries, edit: + * ${heading} + * and run \`npm run gen:catalog\`. + */ + +import type { ${typeName} } from './types' + +export const ${exportName}: readonly ${typeName}[] = +${jsonToTs(entries, 2)} as const +` + await writeFile(out, header, 'utf8') + console.log(`✓ ${out.replace(ROOT + '/', '')} (${entries.length} entries)`) +} + +for (const s of SOURCES) { + await generate(s.src, s.out, s.type, s.name, s.collection, s.heading) +} diff --git a/src/main/fsutil.ts b/src/main/fsutil.ts new file mode 100644 index 0000000..16aaa80 --- /dev/null +++ b/src/main/fsutil.ts @@ -0,0 +1,58 @@ +import { mkdir, readFile, writeFile, chmod, rename } from 'node:fs/promises' +import { dirname, join } from 'node:path' + +/** + * Atomically write a JSON file at the given path. Writes to `.tmp` + * then renames over the target. On Linux/macOS also tightens perms to 0600. + */ +export async function writeJsonAtomic(path: string, value: unknown): Promise { + const tmp = `${path}.tmp` + await mkdir(dirname(path), { recursive: true }) + await writeFile(tmp, JSON.stringify(value, null, 2), { mode: 0o600 }) + await rename(tmp, path) + try { + await chmod(path, 0o600) + } catch { + // Windows: chmod is a no-op. + } +} + +/** Atomically write a non-JSON text file. */ +export async function writeTextAtomic(path: string, value: string): Promise { + const tmp = `${path}.tmp` + await mkdir(dirname(path), { recursive: true }) + await writeFile(tmp, value, { mode: 0o600 }) + await rename(tmp, path) + try { + await chmod(path, 0o600) + } catch { + // Windows: chmod is a no-op. + } +} + +export async function readJson(path: string): Promise { + const blob = await readFile(path, 'utf8') + return JSON.parse(blob) +} + +export async function readJsonOrNull(path: string): Promise { + try { + const blob = await readFile(path, 'utf8') + return JSON.parse(blob) + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null + throw err + } +} + +export async function exists(path: string): Promise { + try { + await readFile(path) + return true + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') return false + throw err + } +} + +export { join } diff --git a/src/main/gateways/catalog.generated.ts b/src/main/gateways/catalog.generated.ts new file mode 100644 index 0000000..233f07b --- /dev/null +++ b/src/main/gateways/catalog.generated.ts @@ -0,0 +1,237 @@ +/** + * GENERATED FILE — DO NOT EDIT BY HAND. + * + * To add or modify entries, edit: + * src/main/gateways/catalog.source.json + * and run `npm run gen:catalog`. + */ + +import type { GatewayEntry } from './types' + +export const GATEWAY_CATALOG: readonly GatewayEntry[] = +[ + { + "id": "truefoundry", + "label": "TrueFoundry AI Gateway", + "baseUrl": "https://gateway.truefoundry.ai", + "selfHostedHint": "Or your org URL, e.g. https://.truefoundry.cloud/api/llm", + "docUrl": "https://www.truefoundry.com/docs/ai-gateway/quick-start", + "endpoints": { + "openai": "/", + "anthropic": "/proxy/v1/messages" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "TFY_API_KEY" + }, + "modelIdFormat": "/", + "nativeProviders": [ + "anthropic", + "openai", + "bedrock", + "vertex", + "azure-foundry" + ], + "notes": "Gateway exposes native Anthropic /v1/messages passthrough; CLAUDE_CODE compat." + }, + { + "id": "litellm", + "label": "LiteLLM Proxy", + "baseUrl": "http://localhost:4000", + "selfHostedHint": "Default port 4000. Self-host: docker run ghcr.io/berriai/litellm-main", + "docUrl": "https://docs.litellm.ai/docs/proxy/quick_start", + "endpoints": { + "openai": "/v1/chat/completions", + "anthropic": "/v1/messages" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "LITELLM_API_KEY" + }, + "modelIdFormat": "/ (e.g. anthropic/claude-3-5-sonnet-20240620)", + "nativeProviders": [ + "anthropic", + "openai", + "azure", + "vertex", + "bedrock" + ], + "notes": "Both /v1/messages (Anthropic) and /v1/chat/completions (OpenAI) are first-class." + }, + { + "id": "cloudflare-ai-gateway", + "label": "Cloudflare AI Gateway", + "baseUrl": "https://gateway.ai.cloudflare.com/v1/", + "selfHostedHint": "Path includes your account_id; final segment is optional path.", + "docUrl": "https://developers.cloudflare.com/ai-gateway/", + "endpoints": { + "openai": "/", + "anthropic": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "CF_API_TOKEN" + }, + "modelIdFormat": "/ (e.g. openai/gpt-4o, anthropic/claude-sonnet-4)", + "nativeProviders": [ + "openai", + "anthropic", + "workers-ai" + ], + "notes": "Single endpoint for OpenAI, Anthropic, Workers AI. Unique account_id per gateway." + }, + { + "id": "vercel-ai-gateway", + "label": "Vercel AI Gateway", + "baseUrl": "https://api.vercel.com/v1/ai", + "selfHostedHint": "", + "docUrl": "https://vercel.com/docs/ai-gateway", + "endpoints": { + "openai": "/v1/chat/completions" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "VERCEL_API_KEY" + }, + "modelIdFormat": "/ (e.g. openai/gpt-4o)", + "nativeProviders": [ + "openai", + "anthropic", + "google" + ], + "notes": "Use OIDC token from Vercel login or a manual API key." + }, + { + "id": "openrouter", + "label": "OpenRouter", + "baseUrl": "https://openrouter.ai/api/v1", + "selfHostedHint": "", + "docUrl": "https://openrouter.ai/docs", + "endpoints": { + "openai": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "OPENROUTER_API_KEY" + }, + "modelIdFormat": "/ (e.g. anthropic/claude-3.5-sonnet)", + "nativeProviders": [ + "openai", + "anthropic", + "google", + "meta", + "mistral" + ], + "notes": "Single API for 100+ models. Budget headers in probe." + }, + { + "id": "together", + "label": "Together AI", + "baseUrl": "https://api.together.xyz/v1", + "selfHostedHint": "", + "docUrl": "https://docs.together.ai/docs/openai-api-compatibility", + "endpoints": { + "openai": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "TOGETHER_API_KEY" + }, + "modelIdFormat": "/ (e.g. meta-llama/Llama-3.1-70B-Instruct)", + "nativeProviders": [ + "openai-compat" + ], + "notes": "Direct OpenAI-compatible inference, not a unifying gateway. Use as provider catalog." + }, + { + "id": "opencode-zen", + "label": "OpenCode Zen", + "baseUrl": "https://opencode.ai/zen/v1", + "selfHostedHint": "", + "docUrl": "https://opencode.ai/docs/providers", + "endpoints": { + "openai": "/", + "anthropic": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "OPENCODE_ZEN_API_KEY" + }, + "modelIdFormat": "", + "nativeProviders": [ + "anthropic", + "openai", + "google" + ], + "notes": "Curated catalog from OpenCode. Recommended for OpenCode users." + }, + { + "id": "zenlayer", + "label": "ZenLayer AI Gateway", + "baseUrl": "https://gateway.theturbo.ai", + "selfHostedHint": "", + "docUrl": "https://docs.console.zenlayer.com/welcome/ai-gateway", + "endpoints": { + "openai": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "ZENLAYER_API_KEY" + }, + "modelIdFormat": "Model IDs from Zenlayer catalog", + "nativeProviders": [ + "openai", + "anthropic", + "google" + ], + "notes": "Enterprise AI gateway from ZenLayer." + }, + { + "id": "claude-code-compatible", + "label": "Claude Code-compatible (custom)", + "baseUrl": "", + "selfHostedHint": "Any endpoint that speaks the Anthropic /v1/messages protocol.", + "docUrl": "https://docs.anthropic.com/en/api/messages", + "endpoints": { + "anthropic": "/v1/messages" + }, + "auth": { + "header": "x-api-key", + "scheme": "raw", + "envVar": "ANTHROPIC_API_KEY" + }, + "modelIdFormat": "", + "nativeProviders": [ + "anthropic" + ], + "notes": "Use for kimi.com, acme.com etc. that expose Anthropic API directly." + }, + { + "id": "custom-openai", + "label": "Custom OpenAI-compatible endpoint", + "baseUrl": "", + "selfHostedHint": "Anything speaking /v1/chat/completions: Ollama, LM Studio, internal gateway.", + "docUrl": "", + "endpoints": { + "openai": "/v1" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "PROVIDER_API_KEY" + }, + "modelIdFormat": "", + "nativeProviders": [ + "openai-compat" + ], + "notes": "Set base URL on the gateway form." + } + ] as const diff --git a/src/main/gateways/catalog.source.json b/src/main/gateways/catalog.source.json new file mode 100644 index 0000000..8cb870d --- /dev/null +++ b/src/main/gateways/catalog.source.json @@ -0,0 +1,188 @@ +{ + "gateways": [ + { + "id": "truefoundry", + "label": "TrueFoundry AI Gateway", + "baseUrl": "https://gateway.truefoundry.ai", + "selfHostedHint": "Or your org URL, e.g. https://.truefoundry.cloud/api/llm", + "docUrl": "https://www.truefoundry.com/docs/ai-gateway/quick-start", + "endpoints": { + "openai": "/", + "anthropic": "/proxy/v1/messages" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "TFY_API_KEY" + }, + "modelIdFormat": "/", + "nativeProviders": ["anthropic", "openai", "bedrock", "vertex", "azure-foundry"], + "notes": "Gateway exposes native Anthropic /v1/messages passthrough; CLAUDE_CODE compat." + }, + { + "id": "litellm", + "label": "LiteLLM Proxy", + "baseUrl": "http://localhost:4000", + "selfHostedHint": "Default port 4000. Self-host: docker run ghcr.io/berriai/litellm-main", + "docUrl": "https://docs.litellm.ai/docs/proxy/quick_start", + "endpoints": { + "openai": "/v1/chat/completions", + "anthropic": "/v1/messages" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "LITELLM_API_KEY" + }, + "modelIdFormat": "/ (e.g. anthropic/claude-3-5-sonnet-20240620)", + "nativeProviders": ["anthropic", "openai", "azure", "vertex", "bedrock"], + "notes": "Both /v1/messages (Anthropic) and /v1/chat/completions (OpenAI) are first-class." + }, + { + "id": "cloudflare-ai-gateway", + "label": "Cloudflare AI Gateway", + "baseUrl": "https://gateway.ai.cloudflare.com/v1/", + "selfHostedHint": "Path includes your account_id; final segment is optional path.", + "docUrl": "https://developers.cloudflare.com/ai-gateway/", + "endpoints": { + "openai": "/", + "anthropic": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "CF_API_TOKEN" + }, + "modelIdFormat": "/ (e.g. openai/gpt-4o, anthropic/claude-sonnet-4)", + "nativeProviders": ["openai", "anthropic", "workers-ai"], + "notes": "Single endpoint for OpenAI, Anthropic, Workers AI. Unique account_id per gateway." + }, + { + "id": "vercel-ai-gateway", + "label": "Vercel AI Gateway", + "baseUrl": "https://api.vercel.com/v1/ai", + "selfHostedHint": "", + "docUrl": "https://vercel.com/docs/ai-gateway", + "endpoints": { + "openai": "/v1/chat/completions" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "VERCEL_API_KEY" + }, + "modelIdFormat": "/ (e.g. openai/gpt-4o)", + "nativeProviders": ["openai", "anthropic", "google"], + "notes": "Use OIDC token from Vercel login or a manual API key." + }, + { + "id": "openrouter", + "label": "OpenRouter", + "baseUrl": "https://openrouter.ai/api/v1", + "selfHostedHint": "", + "docUrl": "https://openrouter.ai/docs", + "endpoints": { + "openai": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "OPENROUTER_API_KEY" + }, + "modelIdFormat": "/ (e.g. anthropic/claude-3.5-sonnet)", + "nativeProviders": ["openai", "anthropic", "google", "meta", "mistral"], + "notes": "Single API for 100+ models. Budget headers in probe." + }, + { + "id": "together", + "label": "Together AI", + "baseUrl": "https://api.together.xyz/v1", + "selfHostedHint": "", + "docUrl": "https://docs.together.ai/docs/openai-api-compatibility", + "endpoints": { + "openai": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "TOGETHER_API_KEY" + }, + "modelIdFormat": "/ (e.g. meta-llama/Llama-3.1-70B-Instruct)", + "nativeProviders": ["openai-compat"], + "notes": "Direct OpenAI-compatible inference, not a unifying gateway. Use as provider catalog." + }, + { + "id": "opencode-zen", + "label": "OpenCode Zen", + "baseUrl": "https://opencode.ai/zen/v1", + "selfHostedHint": "", + "docUrl": "https://opencode.ai/docs/providers", + "endpoints": { + "openai": "/", + "anthropic": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "OPENCODE_ZEN_API_KEY" + }, + "modelIdFormat": "", + "nativeProviders": ["anthropic", "openai", "google"], + "notes": "Curated catalog from OpenCode. Recommended for OpenCode users." + }, + { + "id": "zenlayer", + "label": "ZenLayer AI Gateway", + "baseUrl": "https://gateway.theturbo.ai", + "selfHostedHint": "", + "docUrl": "https://docs.console.zenlayer.com/welcome/ai-gateway", + "endpoints": { + "openai": "/" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "ZENLAYER_API_KEY" + }, + "modelIdFormat": "Model IDs from Zenlayer catalog", + "nativeProviders": ["openai", "anthropic", "google"], + "notes": "Enterprise AI gateway from ZenLayer." + }, + { + "id": "claude-code-compatible", + "label": "Claude Code-compatible (custom)", + "baseUrl": "", + "selfHostedHint": "Any endpoint that speaks the Anthropic /v1/messages protocol.", + "docUrl": "https://docs.anthropic.com/en/api/messages", + "endpoints": { + "anthropic": "/v1/messages" + }, + "auth": { + "header": "x-api-key", + "scheme": "raw", + "envVar": "ANTHROPIC_API_KEY" + }, + "modelIdFormat": "", + "nativeProviders": ["anthropic"], + "notes": "Use for kimi.com, acme.com etc. that expose Anthropic API directly." + }, + { + "id": "custom-openai", + "label": "Custom OpenAI-compatible endpoint", + "baseUrl": "", + "selfHostedHint": "Anything speaking /v1/chat/completions: Ollama, LM Studio, internal gateway.", + "docUrl": "", + "endpoints": { + "openai": "/v1" + }, + "auth": { + "header": "Authorization", + "scheme": "Bearer", + "envVar": "PROVIDER_API_KEY" + }, + "modelIdFormat": "", + "nativeProviders": ["openai-compat"], + "notes": "Set base URL on the gateway form." + } + ] +} diff --git a/src/main/gateways/index.ts b/src/main/gateways/index.ts new file mode 100644 index 0000000..4d6af94 --- /dev/null +++ b/src/main/gateways/index.ts @@ -0,0 +1,15 @@ +import { GATEWAY_CATALOG as _GATEWAY_CATALOG } from './catalog.generated' +import type { GatewayEntry } from './types' + +export type { + GatewayEntry, + GatewayAuth, + GatewayEndpoints, + GatewayAuthScheme, +} from './types' + +export const GATEWAY_CATALOG: readonly GatewayEntry[] = _GATEWAY_CATALOG + +export function findGateway(id: string): GatewayEntry | undefined { + return GATEWAY_CATALOG.find((g) => g.id === id) +} diff --git a/src/main/gateways/resolve.ts b/src/main/gateways/resolve.ts new file mode 100644 index 0000000..c6230e3 --- /dev/null +++ b/src/main/gateways/resolve.ts @@ -0,0 +1,27 @@ +export function selectOpenAIEndpoint(gateway: { endpoints: { openai?: string } }, baseUrl: string): string { + const suffix = gateway.endpoints.openai ?? '/' + const cleanBase = baseUrl.replace(/\/+$/, '') + const cleanSuffix = suffix.replace(/^\/+/, '') + if (cleanSuffix === '') return cleanBase + return `${cleanBase}/${cleanSuffix}` +} + +export function selectAnthropicEndpoint( + gateway: { endpoints: { anthropic?: string } }, + baseUrl: string, +): string | null { + const suffix = gateway.endpoints.anthropic + if (!suffix) return null + const cleanBase = baseUrl.replace(/\/+$/, '') + const cleanSuffix = suffix.replace(/^\/+/, '') + return `${cleanBase}/${cleanSuffix}` +} + +/** + * For a gateway base URL that contains ``, raise an error + * telling the user what they need to substitute. + */ +export function unresolvedPlaceholders(baseUrl: string): string[] { + const m = baseUrl.match(/<([a-z_-]+)>/gi) ?? [] + return m.map((s) => s.slice(1, -1)) +} diff --git a/src/main/gateways/types.ts b/src/main/gateways/types.ts new file mode 100644 index 0000000..9b408c5 --- /dev/null +++ b/src/main/gateways/types.ts @@ -0,0 +1,37 @@ +export type GatewayAuthScheme = 'Bearer' | 'raw' | 'header-only' + +export interface GatewayAuth { + /** HTTP header name, e.g. "Authorization" or "x-api-key" */ + header: string + /** Header value scheme */ + scheme: GatewayAuthScheme + /** Environment variable name to read the credential from */ + envVar: string +} + +export interface GatewayEndpoints { + /** Path the gateway exposes for OpenAI-compatible chat completions */ + openai?: string + /** Path the gateway exposes for Anthropic Messages API */ + anthropic?: string +} + +export interface GatewayEntry { + id: string + label: string + /** + * Default base URL. May contain placeholders like `` or ``. + * The UI surfaces these and lets the user fill them in. + */ + baseUrl: string + /** Hint string shown next to the base URL input (e.g. for self-host). */ + selfHostedHint?: string + docUrl?: string + endpoints: GatewayEndpoints + auth: GatewayAuth + /** How model IDs are shaped, e.g. "/". */ + modelIdFormat: string + /** Provider routes this gateway understands natively. */ + nativeProviders: string[] + notes?: string +} diff --git a/src/main/ipc.ts b/src/main/ipc.ts index ae9445a..586a4c5 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -6,9 +6,16 @@ import type { SecretBackend } from './secrets/backend' import { runProbe } from './probes' import { discoverAll, installHarness } from './installer' import { HARNESS_CATALOG, findHarness } from './providers/harnesses' -import { PROVIDER_CATALOG } from './providers/catalog' +import { PROVIDER_CATALOG, findProvider } from './providers/catalog' +import { applyWiring } from './wiring' +import { GATEWAY_CATALOG } from './gateways' +import { selectAnthropicEndpoint, selectOpenAIEndpoint, unresolvedPlaceholders } from './gateways/resolve' import { CHANNELS } from '../shared/channels' import type { InstalledTool } from '../shared/types' +import { claudeCodeSettingsPath } from './wiring/claudeCode' +import { codexConfigPath } from './wiring/codex' +import { openCodeConfigPath } from './wiring/openCode' +import { readFile } from 'node:fs/promises' export { CHANNELS } @@ -34,6 +41,15 @@ interface ProbeRequest { baseUrl?: string } +interface GatewayApplyRequest { + gatewayId: string | null + providerId: string + baseUrl: string + apiKey: string + harnessIds: string[] + label?: string +} + export function registerIpcHandlers(): void { ipcMain.handle(CHANNELS.vaultList, async () => { const backend = getBackend() @@ -105,8 +121,102 @@ export function registerIpcHandlers(): void { } }) + ipcMain.handle(CHANNELS.harnessConfigShow, async (_evt, id: string) => { + const spec = findHarness(id) + if (!spec) return { ok: false as const, error: `Unknown harness "${id}"`, harnessId: id, exists: false } + const cfg = + id === 'claude-code' + ? { path: claudeCodeSettingsPath(), editor: 'jsonEnv' as const } + : id === 'codex' + ? { path: codexConfigPath(), editor: 'toml' as const } + : id === 'opencode' + ? { path: openCodeConfigPath(), editor: 'jsonProvider' as const } + : null + if (!cfg) return { ok: false as const, harnessId: id, exists: false, error: 'No config editor for this harness' } + let excerpt: string | undefined + let exists = false + try { + const blob = await readFile(cfg.path, 'utf8') + exists = true + excerpt = blob.length > 1200 ? blob.slice(0, 1200) + '\n…' : blob + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err + } + return { + ok: true as const, + harnessId: id, + path: cfg.path, + exists, + excerpt, + notes: [ + cfg.editor === 'jsonEnv' ? 'Hoist writes the `env` block; your other settings are preserved.' : '', + cfg.editor === 'toml' ? 'Hoist writes a `[model_providers.]` block; surrounding TOML is preserved.' : '', + cfg.editor === 'jsonProvider' ? 'Hoist writes a `provider.` block; existing providers are preserved.' : '', + ].filter(Boolean), + } + }) + ipcMain.handle(CHANNELS.providerList, () => PROVIDER_CATALOG) + ipcMain.handle(CHANNELS.gatewayList, () => + GATEWAY_CATALOG.map((g) => ({ + ...g, + placeholders: unresolvedPlaceholders(g.baseUrl), + })), + ) + + ipcMain.handle(CHANNELS.gatewayApply, async (_evt, req: GatewayApplyRequest) => { + const placeholders = unresolvedPlaceholders(req.baseUrl) + if (placeholders.length > 0) { + return { ok: false as const, error: `Base URL still has placeholders: ${placeholders.map((p) => `<${p}>`).join(', ')}`, unresolvedPlaceholders: placeholders } + } + const provider = findProvider(req.providerId) + if (!provider) { + return { ok: false as const, error: `Unknown provider "${req.providerId}"` } + } + const gateway = req.gatewayId ? GATEWAY_CATALOG.find((g) => g.id === req.gatewayId) ?? null : null + + const effectiveBaseUrl = (() => { + if (!gateway) return req.baseUrl.replace(/\/+$/, '') + const anthropicEndpoint = selectAnthropicEndpoint(gateway, req.baseUrl) + if (provider.probeKind === 'anthropicModels' && anthropicEndpoint) return anthropicEndpoint + const openaiEndpoint = selectOpenAIEndpoint(gateway, req.baseUrl) + return openaiEndpoint + })() + + const harnesses = req.harnessIds + .map((id) => findHarness(id)) + .filter((h): h is NonNullable => !!h) + + const wiring: { harnessId: string; harnessName: string; ok: boolean; error?: string; path?: string; note?: string; envHint?: Record }[] = [] + + for (const harness of harnesses) { + try { + const results = await applyWiring({ + apiKey: req.apiKey, + baseUrl: effectiveBaseUrl, + harness, + provider, + gateway, + }) + for (const r of results) { + wiring.push({ + harnessId: harness.id, + harnessName: harness.name, + ok: r.changed, + path: r.path || undefined, + note: r.note, + envHint: r.envHint, + }) + } + } catch (err) { + wiring.push({ harnessId: harness.id, harnessName: harness.name, ok: false, error: errMsg(err) }) + } + } + + return { ok: true as const, wiring, effectiveBaseUrl } + }) + ipcMain.handle(CHANNELS.probeRun, async (_evt, req: ProbeRequest) => { try { let apiKey = req.apiKey diff --git a/src/main/providers/catalog.generated.ts b/src/main/providers/catalog.generated.ts new file mode 100644 index 0000000..95373dc --- /dev/null +++ b/src/main/providers/catalog.generated.ts @@ -0,0 +1,241 @@ +/** + * GENERATED FILE — DO NOT EDIT BY HAND. + * + * To add or modify entries, edit: + * src/main/providers/catalog.source.json + * and run `npm run gen:catalog`. + */ + +import type { ProviderEntry } from './types' + +export const PROVIDER_CATALOG: readonly ProviderEntry[] = +[ + { + "id": "anthropic", + "label": "Anthropic", + "aliases": [ + "claude" + ], + "featured": true, + "envKeys": [ + "ANTHROPIC_API_KEY" + ], + "baseUrlEnv": "ANTHROPIC_BASE_URL", + "authType": "api_key", + "probeKind": "anthropicModels", + "defaultBaseUrl": "https://api.anthropic.com", + "notes": "Claude API. Keys look like `sk-ant-…`." + }, + { + "id": "openai", + "label": "OpenAI", + "featured": true, + "envKeys": [ + "OPENAI_API_KEY" + ], + "baseUrlEnv": "OPENAI_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.openai.com/v1", + "notes": "GPT models. Keys look like `sk-…`." + }, + { + "id": "google", + "label": "Google Gemini", + "aliases": [ + "gemini", + "google-gemini" + ], + "envKeys": [ + "GEMINI_API_KEY", + "GOOGLE_API_KEY" + ], + "baseUrlEnv": "GOOGLE_BASE_URL", + "authType": "api_key", + "probeKind": "geminiModels", + "defaultBaseUrl": "https://generativelanguage.googleapis.com", + "notes": "Google AI Studio (Gemini Developer API)." + }, + { + "id": "vertex", + "label": "Google Vertex AI", + "aliases": [ + "vertex-ai", + "gcp-vertex" + ], + "envKeys": [], + "authType": "cloud_creds", + "probeKind": "gcp", + "notes": "Uses ADC. Configure project + region in your gateway." + }, + { + "id": "bedrock", + "label": "AWS Bedrock", + "aliases": [ + "aws-bedrock" + ], + "envKeys": [], + "authType": "cloud_creds", + "probeKind": "aws", + "notes": "Uses AWS credentials from the standard chain." + }, + { + "id": "azure-openai", + "label": "Azure OpenAI", + "aliases": [ + "azure", + "azure-openai" + ], + "envKeys": [ + "AZURE_OPENAI_API_KEY", + "AZURE_API_KEY" + ], + "baseUrlEnv": "AZURE_OPENAI_ENDPOINT", + "authType": "api_key", + "probeKind": "azure", + "notes": "Set endpoint via env or gateway config." + }, + { + "id": "azure-ai-foundry", + "label": "Azure AI Foundry (Claude)", + "aliases": [ + "azure-ai", + "foundry" + ], + "envKeys": [ + "AZURE_API_KEY" + ], + "baseUrlEnv": "ANTHROPIC_BASE_URL", + "authType": "api_key", + "probeKind": "anthropicModels", + "notes": "Anthropic endpoint over Azure Foundry: `https://.services.ai.azure.com/anthropic`." + }, + { + "id": "groq", + "label": "Groq", + "envKeys": [ + "GROQ_API_KEY" + ], + "baseUrlEnv": "GROQ_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.groq.com/openai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "mistral", + "label": "Mistral AI", + "envKeys": [ + "MISTRAL_API_KEY" + ], + "baseUrlEnv": "MISTRAL_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.mistral.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "cohere", + "label": "Cohere", + "envKeys": [ + "COHERE_API_KEY" + ], + "baseUrlEnv": "CO_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.cohere.com/v1", + "notes": "OpenAI-compatible v2." + }, + { + "id": "together", + "label": "Together AI", + "aliases": [ + "together-ai" + ], + "envKeys": [ + "TOGETHER_API_KEY" + ], + "baseUrlEnv": "TOGETHER_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.together.xyz/v1", + "notes": "100+ open models, OpenAI-compatible." + }, + { + "id": "fireworks", + "label": "Fireworks AI", + "envKeys": [ + "FIREWORKS_API_KEY" + ], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.fireworks.ai/inference/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "deepseek", + "label": "DeepSeek", + "envKeys": [ + "DEEPSEEK_API_KEY" + ], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.deepseek.com/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "xai", + "label": "xAI (Grok)", + "envKeys": [ + "XAI_API_KEY" + ], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.x.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "openrouter", + "label": "OpenRouter", + "envKeys": [ + "OPENROUTER_API_KEY" + ], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://openrouter.ai/api/v1", + "notes": "100+ models through one OpenAI-compatible endpoint." + }, + { + "id": "cerebras", + "label": "Cerebras", + "envKeys": [ + "CEREBRAS_API_KEY" + ], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.cerebras.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "sambanova", + "label": "SambaNova", + "envKeys": [ + "SAMBANOVA_API_KEY" + ], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.sambanova.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "custom-openai", + "label": "Custom OpenAI-compatible", + "envKeys": [ + "CUSTOM_API_KEY" + ], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "", + "notes": "Bring-your-own endpoint. Set base URL in Gateway." + } + ] as const diff --git a/src/main/providers/catalog.source.json b/src/main/providers/catalog.source.json new file mode 100644 index 0000000..6b861d6 --- /dev/null +++ b/src/main/providers/catalog.source.json @@ -0,0 +1,180 @@ +{ + "providers": [ + { + "id": "anthropic", + "label": "Anthropic", + "aliases": ["claude"], + "featured": true, + "envKeys": ["ANTHROPIC_API_KEY"], + "baseUrlEnv": "ANTHROPIC_BASE_URL", + "authType": "api_key", + "probeKind": "anthropicModels", + "defaultBaseUrl": "https://api.anthropic.com", + "notes": "Claude API. Keys look like `sk-ant-…`." + }, + { + "id": "openai", + "label": "OpenAI", + "featured": true, + "envKeys": ["OPENAI_API_KEY"], + "baseUrlEnv": "OPENAI_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.openai.com/v1", + "notes": "GPT models. Keys look like `sk-…`." + }, + { + "id": "google", + "label": "Google Gemini", + "aliases": ["gemini", "google-gemini"], + "envKeys": ["GEMINI_API_KEY", "GOOGLE_API_KEY"], + "baseUrlEnv": "GOOGLE_BASE_URL", + "authType": "api_key", + "probeKind": "geminiModels", + "defaultBaseUrl": "https://generativelanguage.googleapis.com", + "notes": "Google AI Studio (Gemini Developer API)." + }, + { + "id": "vertex", + "label": "Google Vertex AI", + "aliases": ["vertex-ai", "gcp-vertex"], + "envKeys": [], + "authType": "cloud_creds", + "probeKind": "gcp", + "notes": "Uses ADC. Configure project + region in your gateway." + }, + { + "id": "bedrock", + "label": "AWS Bedrock", + "aliases": ["aws-bedrock"], + "envKeys": [], + "authType": "cloud_creds", + "probeKind": "aws", + "notes": "Uses AWS credentials from the standard chain." + }, + { + "id": "azure-openai", + "label": "Azure OpenAI", + "aliases": ["azure", "azure-openai"], + "envKeys": ["AZURE_OPENAI_API_KEY", "AZURE_API_KEY"], + "baseUrlEnv": "AZURE_OPENAI_ENDPOINT", + "authType": "api_key", + "probeKind": "azure", + "notes": "Set endpoint via env or gateway config." + }, + { + "id": "azure-ai-foundry", + "label": "Azure AI Foundry (Claude)", + "aliases": ["azure-ai", "foundry"], + "envKeys": ["AZURE_API_KEY"], + "baseUrlEnv": "ANTHROPIC_BASE_URL", + "authType": "api_key", + "probeKind": "anthropicModels", + "notes": "Anthropic endpoint over Azure Foundry: `https://.services.ai.azure.com/anthropic`." + }, + { + "id": "groq", + "label": "Groq", + "envKeys": ["GROQ_API_KEY"], + "baseUrlEnv": "GROQ_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.groq.com/openai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "mistral", + "label": "Mistral AI", + "envKeys": ["MISTRAL_API_KEY"], + "baseUrlEnv": "MISTRAL_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.mistral.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "cohere", + "label": "Cohere", + "envKeys": ["COHERE_API_KEY"], + "baseUrlEnv": "CO_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.cohere.com/v1", + "notes": "OpenAI-compatible v2." + }, + { + "id": "together", + "label": "Together AI", + "aliases": ["together-ai"], + "envKeys": ["TOGETHER_API_KEY"], + "baseUrlEnv": "TOGETHER_BASE_URL", + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.together.xyz/v1", + "notes": "100+ open models, OpenAI-compatible." + }, + { + "id": "fireworks", + "label": "Fireworks AI", + "envKeys": ["FIREWORKS_API_KEY"], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.fireworks.ai/inference/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "deepseek", + "label": "DeepSeek", + "envKeys": ["DEEPSEEK_API_KEY"], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.deepseek.com/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "xai", + "label": "xAI (Grok)", + "envKeys": ["XAI_API_KEY"], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.x.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "openrouter", + "label": "OpenRouter", + "envKeys": ["OPENROUTER_API_KEY"], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://openrouter.ai/api/v1", + "notes": "100+ models through one OpenAI-compatible endpoint." + }, + { + "id": "cerebras", + "label": "Cerebras", + "envKeys": ["CEREBRAS_API_KEY"], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.cerebras.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "sambanova", + "label": "SambaNova", + "envKeys": ["SAMBANOVA_API_KEY"], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "https://api.sambanova.ai/v1", + "notes": "OpenAI-compatible." + }, + { + "id": "custom-openai", + "label": "Custom OpenAI-compatible", + "envKeys": ["CUSTOM_API_KEY"], + "authType": "api_key", + "probeKind": "openaiModels", + "defaultBaseUrl": "", + "notes": "Bring-your-own endpoint. Set base URL in Gateway." + } + ] +} diff --git a/src/main/providers/catalog.ts b/src/main/providers/catalog.ts index 8d5a6e6..9c61644 100644 --- a/src/main/providers/catalog.ts +++ b/src/main/providers/catalog.ts @@ -1,32 +1,13 @@ -import type { BudgetProbeKind, ProbeKind } from '../probes/types' +import { PROVIDER_CATALOG as _PROVIDER_CATALOG } from './catalog.generated' +import type { ProviderEntry } from './types' -export type AuthType = 'api_key' | 'oauth' | 'cloud_creds' | 'none' +export type { ProviderEntry, AuthType } from './types' -export interface ProviderEntry { - id: string - label: string - aliases?: string[] - featured?: boolean - envKeys: string[] - baseUrlEnv?: string - authType: AuthType - probeKind: ProbeKind - budgetProbeKind?: BudgetProbeKind - defaultBaseUrl?: string - notes?: string -} +export const PROVIDER_CATALOG: readonly ProviderEntry[] = _PROVIDER_CATALOG -export const PROVIDER_CATALOG: ProviderEntry[] = [ - { - id: 'anthropic', - label: 'Anthropic', - aliases: ['claude'], - featured: true, - envKeys: ['ANTHROPIC_API_KEY'], - baseUrlEnv: 'ANTHROPIC_BASE_URL', - authType: 'api_key', - probeKind: 'anthropicModels', - defaultBaseUrl: 'https://api.anthropic.com', - notes: 'Claude API. Look for `sk-ant-…` keys.', - }, -] +export function findProvider(idOrAlias: string): ProviderEntry | undefined { + const lower = idOrAlias.toLowerCase() + return PROVIDER_CATALOG.find( + (p) => p.id === lower || p.aliases?.some((a) => a.toLowerCase() === lower), + ) +} diff --git a/src/main/providers/types.ts b/src/main/providers/types.ts new file mode 100644 index 0000000..cd162b9 --- /dev/null +++ b/src/main/providers/types.ts @@ -0,0 +1,18 @@ +import type { BudgetProbeKind, ProbeKind } from '../probes/types' + +export type AuthType = 'api_key' | 'oauth' | 'cloud_creds' | 'none' + +export interface ProviderEntry { + id: string + label: string + aliases?: string[] + featured?: boolean + /** Optional for cloud-cred providers (Bedrock, Vertex) where no env key applies. */ + envKeys?: string[] + baseUrlEnv?: string + authType: AuthType + probeKind: ProbeKind + budgetProbeKind?: BudgetProbeKind + defaultBaseUrl?: string + notes?: string +} diff --git a/src/main/wiring/claudeCode.ts b/src/main/wiring/claudeCode.ts new file mode 100644 index 0000000..577c03c --- /dev/null +++ b/src/main/wiring/claudeCode.ts @@ -0,0 +1,79 @@ +import { homedir } from 'node:os' +import { join } from 'node:path' +import { readJsonOrNull, writeJsonAtomic } from '../fsutil' +import type { ToolInstallSpec } from '../../shared/types' + +export interface ClaudeCodeConfig { + env?: Record + permissions?: { allow?: string[]; deny?: string[] } + hooks?: Record + [key: string]: unknown +} + +export function claudeCodeSettingsPath(): string { + return join(homedir(), '.claude', 'settings.json') +} + +export interface ClaudeCodeWiring { + /** Env vars to write for the gateway */ + env: Record +} + +/** + * Compute the env vars hoist should write into Claude Code's `settings.json` + * to route through a gateway + use the vault-stored key for this provider. + * + * Prefers Hoist-managed keys, falls back to provider-direct. + */ +export function claudeCodeEnvFor(provider: string, apiKey: string, baseUrl?: string): ClaudeCodeWiring { + const env: Record = {} + switch (provider) { + case 'anthropic': + case 'azure-ai-foundry': + env.ANTHROPIC_BASE_URL = baseUrl ?? '' + env.ANTHROPIC_AUTH_TOKEN = apiKey + // Remove the legacy direct key so the gateway key wins. + delete env.ANTHROPIC_API_KEY + break + case 'openai': + env.ANTHROPIC_BASE_URL = baseUrl ?? '' + env.ANTHROPIC_AUTH_TOKEN = apiKey + break + default: + env.ANTHROPIC_BASE_URL = baseUrl ?? '' + env.ANTHROPIC_AUTH_TOKEN = apiKey + break + } + return { env } +} + +/** + * Apply a wiring to Claude Code's settings.json. Reads the file first, merges + * `env`, writes atomically. Does not touch unrelated blocks. + */ +export async function applyClaudeCodeWiring( + wiring: ClaudeCodeWiring, + _spec?: ToolInstallSpec, +): Promise<{ path: string; changed: boolean }> { + const path = claudeCodeSettingsPath() + const existing = (await readJsonOrNull(path)) as ClaudeCodeConfig | null + const next: ClaudeCodeConfig = { + ...(existing ?? {}), + env: { + ...((existing?.env as Record | undefined) ?? {}), + ...wiring.env, + }, + } + await writeJsonAtomic(path, next) + return { path, changed: true } +} + +export async function clearClaudeCodeHoistEnv(): Promise { + const path = claudeCodeSettingsPath() + const existing = (await readJsonOrNull(path)) as ClaudeCodeConfig | null + if (!existing?.env) return + const HOIST_KEYS = ['ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN'] + const env = { ...(existing.env as Record) } + for (const k of HOIST_KEYS) delete env[k] + await writeJsonAtomic(path, { ...existing, env }) +} diff --git a/src/main/wiring/codex.ts b/src/main/wiring/codex.ts new file mode 100644 index 0000000..716a108 --- /dev/null +++ b/src/main/wiring/codex.ts @@ -0,0 +1,67 @@ +import { homedir } from 'node:os' +import { join } from 'node:path' +import { readFile } from 'node:fs/promises' +import { exists, readJsonOrNull, writeTextAtomic } from '../fsutil' + +export function codexConfigPath(): string { + return join(homedir(), '.codex', 'config.toml') +} +export function codexAuthPath(): string { + return join(homedir(), '.codex', 'auth.json') +} + +export interface CodexAuthShape { + OPENAI_API_KEY?: string + [key: string]: unknown +} + +/** + * Codex picks OPENAI_API_KEY from auth.json or from env. We never write the + * key directly into a non-secure config; we leave it to the vault + env loader. + */ +export interface CodexWiring { + /** base_url override Codex uses for the proxy endpoint. */ + baseUrl: string +} + +/** + * Minimal in-place TOML edit for a config.toml. Reads existing contents, + * preserves comments, replaces or appends `model_provider` and `model` keys. + * + * Codex supports a per-model `provider` block in config.toml that lets you + * point at any OpenAI-compatible URL without leaking env vars. + */ +export async function applyCodexWiring( + wiring: CodexWiring, + providerId = 'openai', + modelId = 'gpt-4o', +): Promise<{ path: string; changed: boolean; content: string }> { + const path = codexConfigPath() + const text = (await exists(path)) ? await readFile(path, 'utf8') : '' + const next = upsertCodexProvider(text, providerId, modelId, wiring.baseUrl) + await writeTextAtomic(path, next) + return { path, changed: true, content: next } +} + +function upsertCodexProvider(text: string, providerId: string, modelId: string, baseUrl: string): string { + const providerSection = `[model_providers.${providerId}]\nbase_url = "${baseUrl}"\nwire_api = "chat"\nrequires_api_key = true\n\n` + const modelLine = `model = "${modelId}"\nprovider = "${providerId}"\n` + let out = text + + // Drop any existing [model_providers.] block so re-runs stay clean. + const providerSectionRegex = new RegExp(`(\\n|\\A)\\[model_providers\\.${providerId}\\][\\s\\S]*?(?=\\n\\[|\\Z)`, 'm') + out = out.replace(providerSectionRegex, '') + + // Drop a previous `provider = ""` line if present. + const providerLineRegex = new RegExp(`^provider\\s*=\\s*"?${providerId}"?\\s*\\n`, 'm') + out = out.replace(providerLineRegex, '') + + // Append our section at the bottom, separated by a blank line. + out = out.replace(/\s*$/, '') + out += `\n\n# hoist-managed\n${providerSection}${modelLine}` + return out +} + +export async function readCodexAuthShape(): Promise { + return ((await readJsonOrNull(codexAuthPath())) as CodexAuthShape | null) ?? null +} diff --git a/src/main/wiring/index.ts b/src/main/wiring/index.ts new file mode 100644 index 0000000..f381d56 --- /dev/null +++ b/src/main/wiring/index.ts @@ -0,0 +1,60 @@ +import type { GatewayEntry } from '../gateways/types' +import type { ProviderEntry } from '../providers/types' +import type { ToolInstallSpec } from '../../shared/types' +import { applyClaudeCodeWiring, claudeCodeEnvFor } from './claudeCode' +import { applyCodexWiring } from './codex' +import { applyOpenCodeWiring } from './openCode' + +export interface WireOptions { + /** Vault-stored API key for the chosen provider. */ + apiKey: string + /** Effective base URL after gateway resolution. */ + baseUrl: string + /** Short harness id (matches HARNESS_CATALOG). */ + harness: ToolInstallSpec + /** Provider the user picked (e.g. "anthropic"). */ + provider: ProviderEntry + /** Gateway they routed through (or null = direct). */ + gateway: GatewayEntry | null +} + +export interface WireResult { + path: string + changed: boolean + content?: string + envHint?: Record + note?: string +} + +/** + * Apply wiring to a single harness. Routes the call to the right writer. + */ +export async function applyWiring(opts: WireOptions): Promise { + const { apiKey, baseUrl, harness, provider } = opts + const out: WireResult[] = [] + + if (harness.id === 'claude-code') { + const wiring = claudeCodeEnvFor(provider.id, apiKey, baseUrl) + const r = await applyClaudeCodeWiring(wiring) + out.push({ ...r, envHint: wiring.env }) + } else if (harness.id === 'codex') { + const r = await applyCodexWiring({ baseUrl }, 'openai', 'gpt-4o') + out.push({ ...r, note: 'Codex reads OPENAI_API_KEY from auth.json or env.' }) + } else if (harness.id === 'opencode') { + const adapter = + provider.probeKind === 'anthropicModels' ? '@ai-sdk/anthropic' : '@ai-sdk/openai-compatible' + const modelId = provider.id === 'anthropic' ? 'claude-sonnet-4-20250514' : 'gpt-4o' + const r = await applyOpenCodeWiring({ + providerId: 'hoist-' + provider.id, + adapter, + baseUrl, + apiKey, + modelId, + }) + out.push({ ...r, note: 'OpenCode reads opencode.json on next start.' }) + } else { + out.push({ changed: false, path: '', note: `No wiring for harness "${harness.id}". Export ANTHROPIC_BASE_URL / OPENAI_BASE_URL manually.` }) + } + + return out +} diff --git a/src/main/wiring/openCode.ts b/src/main/wiring/openCode.ts new file mode 100644 index 0000000..fbe9746 --- /dev/null +++ b/src/main/wiring/openCode.ts @@ -0,0 +1,55 @@ +import { homedir } from 'node:os' +import { join } from 'node:path' +import { readJsonOrNull, writeJsonAtomic } from '../fsutil' + +export function openCodeConfigPath(): string { + return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'opencode', 'opencode.json') +} + +export interface OpenCodeWiring { + /** Provider ID in OpenCode config; defaults to "anthropic". */ + providerId: string + /** SDK adapter to use. Defaults to `@ai-sdk/anthropic` for anthropic, `@ai-sdk/openai-compatible` otherwise. */ + adapter: '@ai-sdk/anthropic' | '@ai-sdk/openai-compatible' | string + baseUrl: string + apiKey: string + modelId: string +} + +export async function applyOpenCodeWiring( + wiring: OpenCodeWiring, +): Promise<{ path: string; changed: boolean }> { + const path = openCodeConfigPath() + const existing = (await readJsonOrNull(path)) as Record | null + const next = { ...(existing ?? {}) } + + const providers = ((next.provider as Record> | undefined) ?? {}) + providers[wiring.providerId] = { + npm: wiring.adapter, + name: wiring.providerId, + options: { + baseURL: wiring.baseUrl, + apiKey: wiring.apiKey, + }, + models: { + [wiring.modelId]: { name: wiring.modelId }, + }, + } + next.provider = providers + next.model = `${wiring.providerId}/${wiring.modelId}` + + await writeJsonAtomic(path, next) + return { path, changed: true } +} + +export async function clearOpenCodeProvider(providerId: string): Promise<{ path: string; changed: boolean }> { + const path = openCodeConfigPath() + const existing = (await readJsonOrNull(path)) as Record | null + if (!existing) return { path, changed: false } + const providers = (existing.provider as Record | undefined) ?? {} + if (!(providerId in providers)) return { path, changed: false } + delete providers[providerId] + existing.provider = providers + await writeJsonAtomic(path, existing) + return { path, changed: true } +} diff --git a/src/preload/api.ts b/src/preload/api.ts index 386e044..f8be051 100644 --- a/src/preload/api.ts +++ b/src/preload/api.ts @@ -12,10 +12,15 @@ export interface HoistAPI { list: () => Promise discover: () => Promise> install: (id: string) => Promise + configShow: (harnessId: string) => Promise } provider: { list: () => Promise } + gateway: { + list: () => Promise + apply: (req: GatewayApplyRequest) => Promise + } probe: { run: (req: ProbeRequest) => Promise } @@ -65,6 +70,19 @@ export interface HarnessInstallResponse { tool?: InstalledToolSummary } +export interface HarnessConfigView { + ok: boolean + error?: string + harnessId: string + path?: string + exists: boolean + /** Excerpt of the current config relevant to hoist's wiring. */ + excerpt?: string + /** Computed env vars hoist will write. */ + envHint?: Record + notes?: string[] +} + export interface ProviderSummary { id: string label: string @@ -75,6 +93,52 @@ export interface ProviderSummary { notes?: string } +export interface GatewaySummary { + id: string + label: string + baseUrl: string + selfHostedHint?: string + docUrl?: string + endpoints: { openai?: string; anthropic?: string } + auth: { header: string; scheme: string; envVar: string } + modelIdFormat: string + nativeProviders: string[] + notes?: string + /** Placeholders in `baseUrl` the user must fill in (e.g. ""). */ + placeholders: string[] +} + +export interface GatewayApplyRequest { + gatewayId: string | null + providerId: string + /** Resolved gateway base URL (placeholders filled). */ + baseUrl: string + /** Resolved API key to inject into harnesses. */ + apiKey: string + /** Harness ids to apply to (e.g. ["claude-code","codex","opencode"]). */ + harnessIds: string[] + /** Display label for the config record. */ + label?: string +} + +export interface HarnessWiringResult { + harnessId: string + harnessName: string + ok: boolean + error?: string + path?: string + note?: string + envHint?: Record +} + +export interface GatewayApplyResponse { + ok: boolean + error?: string + wiring?: HarnessWiringResult[] + effectiveBaseUrl?: string + unresolvedPlaceholders?: string[] +} + export interface ProbeRequest { providerId: string secretId?: string diff --git a/src/preload/index.ts b/src/preload/index.ts index a8c37c6..740b977 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -14,10 +14,15 @@ const api: HoistAPI = { list: () => ipcRenderer.invoke(CHANNELS.harnessList), discover: () => ipcRenderer.invoke(CHANNELS.harnessDiscover), install: (id) => ipcRenderer.invoke(CHANNELS.harnessInstall, id), + configShow: (id) => ipcRenderer.invoke(CHANNELS.harnessConfigShow, id), }, provider: { list: () => ipcRenderer.invoke(CHANNELS.providerList), }, + gateway: { + list: () => ipcRenderer.invoke(CHANNELS.gatewayList), + apply: (req) => ipcRenderer.invoke(CHANNELS.gatewayApply, req), + }, probe: { run: (req) => ipcRenderer.invoke(CHANNELS.probeRun, req), }, diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 9ea5bbd..15581ed 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -334,28 +334,291 @@ function KeysStep({ onBack, onNext }: { onBack: () => void; onNext: () => void } } function GatewayStep({ onBack, onNext }: { onBack: () => void; onNext: () => void }) { + const [gateways, setGateways] = useState<{ + id: string + label: string + baseUrl: string + selfHostedHint?: string + docUrl?: string + endpoints: { openai?: string; anthropic?: string } + auth: { header: string; scheme: string; envVar: string } + modelIdFormat: string + nativeProviders: string[] + notes?: string + placeholders: string[] + }[]>([]) + const [providers, setProviders] = useState<{ + id: string + label: string + envKeys: string[] + defaultBaseUrl?: string + notes?: string + }[]>([]) + const [harnesses, setHarnesses] = useState<{ id: string; name: string }[]>([]) + const [discovered, setDiscovered] = useState>({}) + + const [gatewayId, setGatewayId] = useState('direct') + const [providerId, setProviderId] = useState('anthropic') const [baseUrl, setBaseUrl] = useState('') + const [apiKey, setApiKey] = useState('') + const [harnessIds, setHarnessIds] = useState>({}) + const [applying, setApplying] = useState(false) + const [results, setResults] = useState<{ + harnessId: string + harnessName: string + ok: boolean + error?: string + path?: string + note?: string + envHint?: Record + }[]>([]) + const [error, setError] = useState(null) + const [effectiveBaseUrl, setEffectiveBaseUrl] = useState(null) + + useEffect(() => { + refreshAll() + }, []) + + async function refreshAll() { + const [g, p, h, d] = await Promise.all([ + window.hoist.gateway.list(), + window.hoist.provider.list(), + window.hoist.harness.list(), + window.hoist.harness.discover(), + ]) + setGateways(g) + setProviders(p) + setHarnesses(h.map((x) => ({ id: x.id, name: x.name }))) + const map = h.map((x) => ({ id: x.id, name: x.name })) + setHarnessIds( + map.reduce>((acc, x) => { + acc[x.id] = !!d[x.id]?.path + return acc + }, {}), + ) + setDiscovered( + h.reduce>((acc, _id, i) => { + const x = h[i] + acc[x.id] = { installed: !!d[x.id]?.path, version: d[x.id]?.version ?? null } + return acc + }, {}), + ) + const initialProvider = p.find((x) => x.id === 'anthropic') ?? p[0] + if (initialProvider) { + setProviderId(initialProvider.id) + setBaseUrl(initialProvider.defaultBaseUrl ?? '') + } + } + + const selectedGateway = gatewayId === 'direct' ? null : gateways.find((g) => g.id === gatewayId) + + function pickGateway(g: { id: string; baseUrl: string } | null) { + if (!g) { + const direct = providers.find((p) => p.id === providerId) + setBaseUrl(direct?.defaultBaseUrl ?? '') + } else { + setBaseUrl(g.baseUrl) + } + } + + function pickProvider(p: { id: string; defaultBaseUrl?: string }) { + setProviderId(p.id) + if (gatewayId === 'direct') setBaseUrl(p.defaultBaseUrl ?? '') + } + + async function apply() { + setError(null) + setResults([]) + setEffectiveBaseUrl(null) + setApplying(true) + try { + const selectedHarnessIds = harnesses.filter((h) => harnessIds[h.id]).map((h) => h.id) + if (selectedHarnessIds.length === 0) { + setError('Select at least one harness to apply.') + return + } + if (!apiKey.trim()) { + setError('Paste an API key (or gateway token) first.') + return + } + const res = await window.hoist.gateway.apply({ + gatewayId: gatewayId === 'direct' ? null : gatewayId, + providerId, + baseUrl, + apiKey: apiKey.trim(), + harnessIds: selectedHarnessIds, + }) + if (!res.ok) { + setError(res.error ?? 'Apply failed.') + return + } + setResults(res.wiring ?? []) + setEffectiveBaseUrl(res.effectiveBaseUrl ?? baseUrl) + } catch (err) { + setError(errMsg(err)) + } finally { + setApplying(false) + } + } + + const providerObj = providers.find((p) => p.id === providerId) + return (

Gateway routing

-

Point your tools at an enterprise AI gateway (writes ANTHROPIC_BASE_URL config in a later release).

- -
-
Base URL
- setBaseUrl(e.target.value)} - /> +

Point your tools at a hosted gateway or paste any OpenAI/Anthropic-compatible URL. Hoist writes per-harness config (Claude Code ~/.claude/settings.json, Codex config.toml, OpenCode opencode.json).

+ +
1. Gateway
+
+ + {gateways.map((g) => ( + + ))} +
+ +
2. Provider & base URL
+
+
+
Provider
+ +
+ +
+
Base URL
+ setBaseUrl(e.target.value)} + /> + {selectedGateway?.selfHostedHint && ( +
{selectedGateway.selfHostedHint}
+ )} + {selectedGateway && ( +
+ + Endpoint: {selectedGateway.endpoints.anthropic ?? selectedGateway.endpoints.openai ?? '/v1'} · Auth: {selectedGateway.auth.header} + {selectedGateway.auth.scheme && selectedGateway.auth.scheme !== 'raw' ? ` (${selectedGateway.auth.scheme})` : ''} · Env: {selectedGateway.auth.envVar} + + {selectedGateway.docUrl && ( + docs ↗ + )} +
+ )} + {providerObj?.notes && ( +
{providerObj.notes}
+ )} +
+ +
+
API key / token
+ setApiKey(e.target.value)} + /> +
Encrypted at rest in OS keychain via safeStorage.
+
+
+ +
3. Apply to harnesses
+
+ {harnesses.map((h) => { + const inst = discovered[h.id] + return ( + + ) + })}
+ {error &&
{error}
} + + {results.length > 0 && ( +
+
Wiring results
+ {results.map((r, i) => ( +
+
{r.harnessName}
+ {r.error &&
{r.error}
} + {r.path &&
{r.path}
} + {r.note &&
{r.note}
} + {r.envHint && ( +
+                  {Object.entries(r.envHint).map(([k, v]) => `${k} = ${v}`).join('\n')}
+                
+ )} +
+ ))} + {effectiveBaseUrl && ( +
Effective base URL: {effectiveBaseUrl}
+ )} +
+ )} +
- +
@@ -633,4 +896,118 @@ const styles: Record = { fontSize: 13, marginBottom: 16, }, + subheading: { + fontSize: 12, + fontWeight: 600, + textTransform: 'uppercase' as const, + letterSpacing: '0.05em', + color: 'var(--text-muted)', + marginBottom: 10, + marginTop: 24, + }, + grid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', + gap: 8, + marginBottom: 16, + }, + gatewayCard: { + textAlign: 'left' as const, + background: 'var(--surface)', + border: '1px solid var(--border)', + borderRadius: 10, + padding: '12px 14px', + color: 'var(--text)', + cursor: 'pointer', + }, + gatewayCardActive: { + borderColor: 'var(--accent)', + boxShadow: '0 0 0 1px var(--accent-glow)', + }, + gatewayCardTitle: { + fontSize: 13, + fontWeight: 600, + }, + gatewayCardSub: { + fontSize: 11, + color: 'var(--text-muted)', + marginTop: 2, + fontFamily: 'ui-monospace, SFMono-Regular, monospace', + wordBreak: 'break-all' as const, + }, + gatewayCardWarn: { + fontSize: 11, + color: '#fbbf24', + marginTop: 4, + }, + gatewayDoc: { + marginTop: 8, + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + flexWrap: 'wrap' as const, + gap: 8, + fontSize: 12, + }, + docLink: { + color: 'var(--accent)', + textDecoration: 'none', + fontSize: 11, + }, + select: { + width: '100%', + background: 'var(--bg)', + color: 'var(--text)', + border: '1px solid var(--border)', + borderRadius: 8, + padding: '10px 14px', + fontSize: 14, + outline: 'none', + marginTop: 8, + fontFamily: 'inherit', + }, + harnessRow: { + display: 'flex', + alignItems: 'center', + gap: 12, + background: 'var(--surface)', + border: '1px solid var(--border)', + borderRadius: 10, + padding: '12px 14px', + fontSize: 13, + cursor: 'pointer', + }, + harnessName: { + flex: 1, + fontWeight: 500, + }, + resultsBlock: { + marginTop: 16, + marginBottom: 16, + }, + resultRow: { + border: '1px solid var(--border)', + borderRadius: 10, + padding: '12px 16px', + marginBottom: 6, + fontSize: 13, + }, + resultRowOk: { + borderColor: 'rgba(74, 222, 128, 0.3)', + background: 'rgba(74, 222, 128, 0.04)', + }, + resultRowBad: { + borderColor: 'rgba(255, 107, 107, 0.3)', + background: 'rgba(255, 107, 107, 0.04)', + }, + codeBlock: { + background: 'var(--bg)', + border: '1px solid var(--border)', + borderRadius: 6, + padding: '8px 12px', + margin: '8px 0 0', + fontSize: 11, + fontFamily: 'ui-monospace, SFMono-Regular, monospace', + overflow: 'auto', + }, } diff --git a/src/shared/channels.ts b/src/shared/channels.ts index bffcdad..d031cac 100644 --- a/src/shared/channels.ts +++ b/src/shared/channels.ts @@ -8,6 +8,9 @@ export const CHANNELS = { harnessInstall: 'harness:install', providerList: 'provider:list', probeRun: 'probe:run', + gatewayList: 'gateway:list', + gatewayApply: 'gateway:apply', + harnessConfigShow: 'harness:configShow', } as const export type ChannelName = (typeof CHANNELS)[keyof typeof CHANNELS]