diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..86b7318 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +.DS_Store +.env +.env.* +node_modules +dist +packages/*/dist +release +release-local +logs +tmp +test-results +playwright-report +blob-report +docs/node_modules +docs/dist +docs/.astro diff --git a/.gitignore b/.gitignore index 291dafe..6efc5ae 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,8 @@ release tmp release-local logs -.opencat \ No newline at end of file +.opencat +test-results +playwright-report +blob-report +.tmp \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..147c7ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +ARG NODE_IMAGE=node:22-bookworm + +FROM ${NODE_IMAGE} AS build +WORKDIR /app + +COPY package.json package-lock.json ./ +COPY packages/cli/package.json packages/cli/package.json +COPY packages/core/package.json packages/core/package.json +COPY packages/electron/package.json packages/electron/package.json +COPY packages/ui/package.json packages/ui/package.json +RUN npm ci + +COPY . . +RUN npm run build:docker + +FROM ${NODE_IMAGE} AS runtime +ENV NODE_ENV=production \ + CCR_DATA_DIR=/data \ + CCR_WEB_HOST=127.0.0.1 \ + CCR_WEB_PORT=3459 \ + CCR_NGINX_PORT=8080 \ + CCR_GATEWAY_HOST=127.0.0.1 \ + CCR_GATEWAY_PORT=3456 \ + CCR_GATEWAY_CORE_PORT=3457 \ + CCR_PUBLIC_HOST=127.0.0.1 \ + CCR_PUBLIC_PORT=3458 + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates nginx \ + && rm -rf /var/lib/apt/lists/* \ + && rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf + +COPY package.json package-lock.json ./ +COPY packages/core/package.json packages/core/package.json +RUN npm ci --omit=dev --workspace=@claude-code-router/core --include-workspace-root=false \ + && npm cache clean --force + +COPY --from=build /app/packages/core/dist packages/core/dist +COPY --from=build /app/packages/ui/dist/renderer /usr/share/nginx/html +COPY docker/entrypoint.sh /usr/local/bin/ccr-docker-entrypoint +COPY docker/pm2.config.cjs docker/pm2.config.cjs + +RUN chmod +x /usr/local/bin/ccr-docker-entrypoint \ + && mkdir -p /data /run/nginx /var/lib/nginx /var/log/nginx + +VOLUME ["/data"] +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD node -e "fetch('http://127.0.0.1:' + (process.env.CCR_NGINX_PORT || '8080') + '/').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" + +ENTRYPOINT ["ccr-docker-entrypoint"] diff --git a/README.md b/README.md index 3f5ce1e..89ee179 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Claude Code Router Desktop

+

Claude Code Router

Chinese README diff --git a/README_zh.md b/README_zh.md index cd79161..dc0b436 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,4 +1,4 @@ -

Claude Code Router Desktop

+

Claude Code Router

English README diff --git a/build/build.mjs b/build/build.mjs index a07412b..ba43565 100644 --- a/build/build.mjs +++ b/build/build.mjs @@ -1,4 +1,4 @@ -import { buildBrowserRenderer, buildMain, buildRenderer, buildStyles, buildTrayRenderer, buildWebClientBridge, cleanDist, copyAppAssets, copyBrowserRendererHtml, copyMarketplacePlugins, copyModelCatalog, copyRendererHtml, copyTrayRendererHtml } from "./esbuild.config.mjs"; +import { buildBrowserRenderer, buildMain, buildRenderer, buildStyles, buildTrayRenderer, buildWebClientBridge, cleanDist, copyAppAssets, copyBrowserRendererHtml, copyMarketplacePlugins, copyModelCatalog, copyRendererHtml, copyTrayRendererHtml, syncUiRendererToRuntimeDists } from "./esbuild.config.mjs"; const mode = process.argv.includes("--dev") ? "development" : "production"; @@ -19,4 +19,6 @@ await Promise.all([ buildStyles({ minify: mode === "production" }) ]); -console.log(`Built Electron app assets in ${mode} mode.`); +syncUiRendererToRuntimeDists(); + +console.log(`Built monorepo package assets in ${mode} mode.`); diff --git a/build/dev.mjs b/build/dev.mjs index a7cf8fd..b91a341 100644 --- a/build/dev.mjs +++ b/build/dev.mjs @@ -5,12 +5,14 @@ import { spawn } from "node:child_process"; import { existsSync, readdirSync, readFileSync, statSync, watch } from "node:fs"; import path from "node:path"; import { - binPath, buildStyles, cleanDist, browserRendererHtmlInput, + cliSourceRoot, + coreSourceRoot, copyAppAssets, copyBrowserRendererHtml, + copyCliRuntimeToElectronDist, copyMarketplacePlugins, copyModelCatalog, copyRendererHtml, @@ -21,12 +23,12 @@ import { createRendererBuildOptions, createTrayRendererBuildOptions, createWebClientBridgeBuildOptions, - cssInput, - cssOutput, appAssetsInput, modelCatalogInput, projectRoot, + rendererRoot, rendererHtmlInput, + syncUiRendererToRuntimeDists, trayRendererHtmlInput, watchPlugin } from "./esbuild.config.mjs"; @@ -37,7 +39,12 @@ let pendingRestartReasons = []; const watchSignatures = new Map(); let shuttingDown = false; const restartDelayMs = 160; +const styleBuildDelayMs = 160; +const stylePollIntervalMs = 1000; const ignoredSignatureEntries = new Set([".DS_Store"]); +let styleBuildTimer = null; +let styleBuildInFlight = false; +let queuedStyleBuildReason = null; const ready = { browser: false, cli: false, @@ -46,6 +53,32 @@ const ready = { tray: false, webBridge: false }; +const devTarget = parseDevTarget(process.argv.slice(2)); +const enabled = { + cli: devTarget === "cli" || devTarget === "electron", + electron: devTarget === "electron", + ui: true +}; +const coreSharedSourceRoot = path.join(coreSourceRoot, "shared"); +const styleWatchRoots = [rendererRoot, coreSharedSourceRoot].filter((watchRoot) => existsSync(watchRoot)); +const activeReadyNames = new Set([ + ...(enabled.ui ? ["browser", "renderer", "tray", "webBridge"] : []), + ...(enabled.cli ? ["cli"] : []), + ...(enabled.electron ? ["main"] : []) +]); + +function parseDevTarget(args) { + const target = args[0] ?? "electron"; + if (target === "--help" || target === "-h") { + console.log("Usage: node build/dev.mjs [ui|cli|electron]"); + process.exit(0); + } + if (target === "ui" || target === "cli" || target === "electron") { + return target; + } + console.error(`Unknown dev target "${target}". Expected ui, cli, or electron.`); + process.exit(2); +} function logDev(message) { console.log(`[dev] ${new Date().toISOString()} ${message}`); @@ -57,6 +90,7 @@ function relativePath(file) { function readyState() { return Object.entries(ready) + .filter(([name]) => activeReadyNames.has(name)) .map(([name, value]) => `${name}:${value ? "ready" : "pending"}`) .join(" "); } @@ -163,7 +197,63 @@ function handleWatchedInput(label, watchedPath, eventType, filename, options, on } onChange(); - scheduleRestart(reason); + if (enabled.electron && options?.restart !== false) { + scheduleRestart(reason); + } +} + +function scheduleStyleBuild(reason) { + queuedStyleBuildReason = reason; + if (styleBuildTimer) { + clearTimeout(styleBuildTimer); + } + styleBuildTimer = setTimeout(() => { + styleBuildTimer = null; + void rebuildStyles(queuedStyleBuildReason ?? reason); + }, styleBuildDelayMs); +} + +async function rebuildStyles(reason) { + if (styleBuildInFlight) { + queuedStyleBuildReason = reason; + return; + } + + styleBuildInFlight = true; + queuedStyleBuildReason = null; + try { + logDev(`rebuilding styles: ${reason}`); + await buildStyles({ minify: false }); + syncUiRendererToRuntimeDists(); + if (enabled.electron) { + scheduleRestart(`styles rebuilt: ${reason}`); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logDev(`style rebuild failed: ${message}`); + } finally { + styleBuildInFlight = false; + if (queuedStyleBuildReason) { + const queuedReason = queuedStyleBuildReason; + queuedStyleBuildReason = null; + scheduleStyleBuild(queuedReason); + } + } +} + +function pollStyleWatchRoots() { + for (const styleWatchRoot of styleWatchRoots) { + const label = `styles ${relativePath(styleWatchRoot)}`; + const signature = contentSignature(styleWatchRoot); + const previousSignature = watchSignatures.get(label); + if (previousSignature === signature.key) { + continue; + } + + watchSignatures.set(label, signature.key); + logDev(`watch event: ${label}; ${signature.summary}; content=changed`); + scheduleStyleBuild(label); + } } function markReady(name, reason = `${name} esbuild completed`) { @@ -171,7 +261,7 @@ function markReady(name, reason = `${name} esbuild completed`) { ready[name] = true; } logDev(`build ready: ${reason}; ${readyState()}`); - if (ready.browser && ready.cli && ready.main && ready.renderer && ready.tray && ready.webBridge) { + if (enabled.electron && Array.from(activeReadyNames).every((readyName) => ready[readyName])) { scheduleRestart(reason); } } @@ -221,114 +311,153 @@ function restartElectron() { }); } -logDev("starting dev build"); +logDev(`starting dev build target=${devTarget} ui=${enabled.ui ? "on" : "off"} cli=${enabled.cli ? "on" : "off"} electron=${enabled.electron ? "on" : "off"}`); cleanDist(); -copyAppAssets(); -copyMarketplacePlugins(); -copyModelCatalog(); +if (enabled.electron) { + copyAppAssets(); +} +if (enabled.cli || enabled.electron) { + copyMarketplacePlugins(); + copyModelCatalog(); +} copyBrowserRendererHtml(); copyRendererHtml(); copyTrayRendererHtml(); await buildStyles({ minify: false }); - -const tailwindProcess = spawn(binPath("tailwindcss"), ["-i", cssInput, "-o", cssOutput, "--watch"], { - cwd: projectRoot, - stdio: "inherit", - shell: process.platform === "win32" -}); -logDev(`Tailwind watcher started pid=${tailwindProcess.pid ?? "unknown"} input=${relativePath(cssInput)} output=${relativePath(cssOutput)}`); -tailwindProcess.on("exit", (code, signal) => { - logDev(`Tailwind watcher exited code=${code ?? "null"} signal=${signal ?? "null"}`); -}); +syncUiRendererToRuntimeDists(); rememberWatchSignature("home html", rendererHtmlInput); rememberWatchSignature("browser html", browserRendererHtmlInput); rememberWatchSignature("tray html", trayRendererHtmlInput); -rememberWatchSignature("app assets", appAssetsInput); -if (existsSync(modelCatalogInput)) { +for (const styleWatchRoot of styleWatchRoots) { + rememberWatchSignature(`styles ${relativePath(styleWatchRoot)}`, styleWatchRoot); +} +if (enabled.electron) { + rememberWatchSignature("app assets", appAssetsInput); +} +if ((enabled.cli || enabled.electron) && existsSync(modelCatalogInput)) { rememberWatchSignature("model catalog", modelCatalogInput); } const htmlWatcher = watch(rendererHtmlInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("home html", rendererHtmlInput, eventType, filename, undefined, copyRendererHtml); + handleWatchedInput("home html", rendererHtmlInput, eventType, filename, undefined, () => { + copyRendererHtml(); + syncUiRendererToRuntimeDists(); + }); }); const browserHtmlWatcher = watch(browserRendererHtmlInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("browser html", browserRendererHtmlInput, eventType, filename, undefined, copyBrowserRendererHtml); + handleWatchedInput("browser html", browserRendererHtmlInput, eventType, filename, undefined, () => { + copyBrowserRendererHtml(); + syncUiRendererToRuntimeDists(); + }); }); const trayHtmlWatcher = watch(trayRendererHtmlInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("tray html", trayRendererHtmlInput, eventType, filename, undefined, copyTrayRendererHtml); + handleWatchedInput("tray html", trayRendererHtmlInput, eventType, filename, undefined, () => { + copyTrayRendererHtml(); + syncUiRendererToRuntimeDists(); + }); }); -const appAssetsWatcher = watch(appAssetsInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("app assets", appAssetsInput, eventType, filename, { isDirectory: true }, copyAppAssets); -}); +const stylePoller = setInterval(pollStyleWatchRoots, stylePollIntervalMs); -const modelCatalogWatcher = existsSync(modelCatalogInput) +const appAssetsWatcher = enabled.electron + ? watch(appAssetsInput, { persistent: true }, (eventType, filename) => { + handleWatchedInput("app assets", appAssetsInput, eventType, filename, { isDirectory: true }, copyAppAssets); + }) + : { close: () => undefined }; + +const modelCatalogWatcher = (enabled.cli || enabled.electron) && existsSync(modelCatalogInput) ? watch(modelCatalogInput, { persistent: true }, (eventType, filename) => { handleWatchedInput("model catalog", modelCatalogInput, eventType, filename, undefined, copyModelCatalog); }) : { close: () => undefined }; -const mainContext = await esbuild.context( - createMainBuildOptions({ - mode: "development", - plugins: [watchPlugin("main", (name) => markReady(name))] - }) -); +const contexts = []; -const cliContext = await esbuild.context( - createCliBuildOptions({ - mode: "development", - plugins: [watchPlugin("cli", (name) => markReady(name))] - }) -); - -const rendererContext = await esbuild.context( - createRendererBuildOptions({ - mode: "development", - plugins: [ - watchPlugin("renderer", (name) => { - copyRendererHtml(); - markReady(name); +if (enabled.electron) { + contexts.push( + await esbuild.context( + createMainBuildOptions({ + mode: "development", + plugins: [watchPlugin("main", (name) => markReady(name))] }) - ] - }) -); + ) + ); +} -const trayRendererContext = await esbuild.context( - createTrayRendererBuildOptions({ - mode: "development", - plugins: [ - watchPlugin("tray", (name) => { - copyTrayRendererHtml(); - markReady(name); +if (enabled.cli) { + contexts.push( + await esbuild.context( + createCliBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("cli", (name) => { + if (enabled.electron) { + copyCliRuntimeToElectronDist(); + } + markReady(name); + }) + ] }) - ] - }) -); + ) + ); +} -const browserRendererContext = await esbuild.context( - createBrowserRendererBuildOptions({ - mode: "development", - plugins: [ - watchPlugin("browser", (name) => { - copyBrowserRendererHtml(); - markReady(name); +if (enabled.ui) { + contexts.push( + await esbuild.context( + createRendererBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("renderer", (name) => { + copyRendererHtml(); + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] }) - ] - }) -); + ), + await esbuild.context( + createTrayRendererBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("tray", (name) => { + copyTrayRendererHtml(); + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] + }) + ), + await esbuild.context( + createBrowserRendererBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("browser", (name) => { + copyBrowserRendererHtml(); + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] + }) + ), + await esbuild.context( + createWebClientBridgeBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("webBridge", (name) => { + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] + }) + ) + ); +} -const webClientBridgeContext = await esbuild.context( - createWebClientBridgeBuildOptions({ - mode: "development", - plugins: [watchPlugin("webBridge", (name) => markReady(name))] - }) -); - -await Promise.all([mainContext.watch(), cliContext.watch(), rendererContext.watch(), trayRendererContext.watch(), browserRendererContext.watch(), webClientBridgeContext.watch()]); +await Promise.all(contexts.map((context) => context.watch())); logDev("watchers are active"); async function shutdown() { @@ -340,13 +469,16 @@ async function shutdown() { if (electronProcess) { electronProcess.kill(); } - tailwindProcess.kill(); + if (styleBuildTimer) { + clearTimeout(styleBuildTimer); + } htmlWatcher.close(); browserHtmlWatcher.close(); trayHtmlWatcher.close(); + clearInterval(stylePoller); appAssetsWatcher.close(); modelCatalogWatcher.close(); - await Promise.all([mainContext.dispose(), cliContext.dispose(), rendererContext.dispose(), trayRendererContext.dispose(), browserRendererContext.dispose(), webClientBridgeContext.dispose()]); + await Promise.all(contexts.map((context) => context.dispose())); process.exit(0); } diff --git a/build/docker-build.mjs b/build/docker-build.mjs new file mode 100644 index 0000000..75d4ed1 --- /dev/null +++ b/build/docker-build.mjs @@ -0,0 +1,37 @@ +import { + buildBrowserRenderer, + buildCoreServer, + buildRenderer, + buildStyles, + buildTrayRenderer, + buildWebClientBridge, + cleanDist, + copyBrowserRendererHtml, + copyMarketplacePlugins, + copyModelCatalog, + copyRendererHtml, + copyTrayRendererHtml, + syncUiRendererToRuntimeDists +} from "./esbuild.config.mjs"; + +const mode = process.argv.includes("--dev") ? "development" : "production"; + +cleanDist(); +copyMarketplacePlugins(); +copyModelCatalog(); +copyBrowserRendererHtml(); +copyRendererHtml(); +copyTrayRendererHtml(); + +await Promise.all([ + buildCoreServer({ mode }), + buildBrowserRenderer({ mode }), + buildRenderer({ mode }), + buildTrayRenderer({ mode }), + buildWebClientBridge({ mode }), + buildStyles({ minify: mode === "production" }) +]); + +syncUiRendererToRuntimeDists(); + +console.log(`Built Docker core server and UI assets in ${mode} mode.`); diff --git a/build/esbuild.config.mjs b/build/esbuild.config.mjs index bcf3a1a..86ad8b0 100644 --- a/build/esbuild.config.mjs +++ b/build/esbuild.config.mjs @@ -8,16 +8,43 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const projectRoot = path.resolve(__dirname, ".."); -export const distDir = path.join(projectRoot, "dist"); -export const mainOutDir = path.join(distDir, "main"); -export const rendererOutDir = path.join(distDir, "renderer"); -export const appAssetsDir = path.join(distDir, "assets"); +export const packagesRoot = path.join(projectRoot, "packages"); +export const cliRoot = path.join(packagesRoot, "cli"); +export const coreRoot = path.join(packagesRoot, "core"); +export const electronRoot = path.join(packagesRoot, "electron"); +export const uiRoot = path.join(packagesRoot, "ui"); +export const cliSourceRoot = path.join(cliRoot, "src"); +export const coreSourceRoot = path.join(coreRoot, "src"); +export const electronSourceRoot = path.join(electronRoot, "src"); +export const uiSourceRoot = path.join(uiRoot, "src"); +export const legacyDistDir = path.join(projectRoot, "dist"); +export const cliDistDir = path.join(cliRoot, "dist"); +export const coreDistDir = path.join(coreRoot, "dist"); +export const electronDistDir = path.join(electronRoot, "dist"); +export const uiDistDir = path.join(uiRoot, "dist"); +export const distDir = electronDistDir; +export const cliMainOutDir = path.join(cliDistDir, "main"); +export const coreMainOutDir = path.join(coreDistDir, "main"); +export const electronMainOutDir = path.join(electronDistDir, "main"); +export const mainOutDir = electronMainOutDir; +export const rendererOutDir = path.join(uiDistDir, "renderer"); +export const cliRendererOutDir = path.join(cliDistDir, "renderer"); +export const coreRendererOutDir = path.join(coreDistDir, "renderer"); +export const electronRendererOutDir = path.join(electronDistDir, "renderer"); +export const runtimeRendererOutDirs = [cliRendererOutDir, coreRendererOutDir, electronRendererOutDir]; +export const appAssetsDir = path.join(electronDistDir, "assets"); export const rendererAssetsDir = path.join(rendererOutDir, "assets"); -export const marketplacePluginsDir = path.join(distDir, "marketplace", "plugins"); -export const appAssetsInput = path.join(projectRoot, "assets"); -export const modelCatalogInput = path.join(projectRoot, "models.json"); -export const modelCatalogOutput = path.join(distDir, "models.json"); -export const rendererRoot = path.join(projectRoot, "src", "renderer"); +export const cliMarketplacePluginsDir = path.join(cliDistDir, "marketplace", "plugins"); +export const coreMarketplacePluginsDir = path.join(coreDistDir, "marketplace", "plugins"); +export const electronMarketplacePluginsDir = path.join(electronDistDir, "marketplace", "plugins"); +export const marketplacePluginsDir = electronMarketplacePluginsDir; +export const appAssetsInput = path.join(electronRoot, "assets"); +export const modelCatalogInput = path.join(coreRoot, "models.json"); +export const cliModelCatalogOutput = path.join(cliDistDir, "models.json"); +export const coreModelCatalogOutput = path.join(coreDistDir, "models.json"); +export const electronModelCatalogOutput = path.join(electronDistDir, "models.json"); +export const modelCatalogOutput = electronModelCatalogOutput; +export const rendererRoot = uiSourceRoot; export const rendererHtmlInput = path.join(rendererRoot, "pages", "home", "index.html"); export const rendererHtmlOutput = path.join(rendererOutDir, "pages", "home", "index.html"); export const browserRendererHtmlInput = path.join(rendererRoot, "pages", "browser", "index.html"); @@ -30,8 +57,10 @@ export const webClientBridgeOutput = path.join(rendererAssetsDir, "web-client-br const lightweightMcpBundleNames = ["browser-web-search-proxy-mcp.js", "fusion-vision-mcp.js", "fusion-tool-fallback-mcp.js"]; const lightweightMcpBundleMaxBytes = 128 * 1024; const forbiddenLightweightMcpInputs = [ - { prefix: "src/main/", reason: "main-process modules can pull in config, Electron, or native storage side effects" }, - { prefix: "src/renderer/", reason: "renderer modules do not belong in stdio MCP subprocesses" }, + { prefix: "packages/core/src/config/", reason: "config modules can pull in native storage side effects" }, + { prefix: "packages/core/src/storage/", reason: "native SQLite storage is not allowed in lightweight MCP subprocesses" }, + { prefix: "packages/electron/src/", reason: "Electron runtime modules are not allowed in lightweight MCP subprocesses" }, + { prefix: "packages/ui/src/", reason: "UI modules do not belong in stdio MCP subprocesses" }, { prefix: "node_modules/better-sqlite3/", reason: "native SQLite is not allowed in lightweight MCP subprocesses" }, { prefix: "node_modules/electron/", reason: "Electron runtime modules are not allowed in lightweight MCP subprocesses" } ]; @@ -45,15 +74,26 @@ const nodeExternals = [ ]; export function cleanDist() { - rmSync(distDir, { force: true, recursive: true }); + rmSync(legacyDistDir, { force: true, recursive: true }); + rmSync(cliDistDir, { force: true, recursive: true }); + rmSync(coreDistDir, { force: true, recursive: true }); + rmSync(electronDistDir, { force: true, recursive: true }); + rmSync(uiDistDir, { force: true, recursive: true }); ensureDist(); } export function ensureDist() { - mkdirSync(mainOutDir, { recursive: true }); + mkdirSync(cliMainOutDir, { recursive: true }); + mkdirSync(coreMainOutDir, { recursive: true }); + mkdirSync(electronMainOutDir, { recursive: true }); mkdirSync(appAssetsDir, { recursive: true }); - mkdirSync(marketplacePluginsDir, { recursive: true }); + mkdirSync(cliMarketplacePluginsDir, { recursive: true }); + mkdirSync(coreMarketplacePluginsDir, { recursive: true }); + mkdirSync(electronMarketplacePluginsDir, { recursive: true }); mkdirSync(rendererAssetsDir, { recursive: true }); + for (const outputDir of runtimeRendererOutDirs) { + mkdirSync(path.join(outputDir, "assets"), { recursive: true }); + } mkdirSync(path.dirname(rendererHtmlOutput), { recursive: true }); mkdirSync(path.dirname(browserRendererHtmlOutput), { recursive: true }); mkdirSync(path.dirname(trayRendererHtmlOutput), { recursive: true }); @@ -69,12 +109,16 @@ export function copyAppAssets() { export function copyModelCatalog() { ensureDist(); if (existsSync(modelCatalogInput)) { - cpSync(modelCatalogInput, modelCatalogOutput); + cpSync(modelCatalogInput, cliModelCatalogOutput); + cpSync(modelCatalogInput, coreModelCatalogOutput); + cpSync(modelCatalogInput, electronModelCatalogOutput); } } export function copyRendererHtml() { - copyRendererPageHtml(rendererHtmlInput, rendererHtmlOutput, "main.js"); + copyRendererPageHtml(rendererHtmlInput, rendererHtmlOutput, "main.js", { + beforeModuleScriptTags: [' '] + }); } export function copyTrayRendererHtml() { @@ -89,14 +133,25 @@ export function copyMarketplacePlugins() { ensureDist(); for (const filename of ["claude-design-plugin.cjs", "cursor-proxy-plugin.cjs"]) { const source = path.join(projectRoot, "examples", "plugins", filename); - const target = path.join(marketplacePluginsDir, filename); if (existsSync(source)) { - cpSync(source, target); + cpSync(source, path.join(cliMarketplacePluginsDir, filename)); + cpSync(source, path.join(coreMarketplacePluginsDir, filename)); + cpSync(source, path.join(electronMarketplacePluginsDir, filename)); } } } -function copyRendererPageHtml(input, output, scriptName) { +export function syncUiRendererToRuntimeDists() { + ensureDist(); + for (const outputDir of runtimeRendererOutDirs) { + rmSync(outputDir, { force: true, recursive: true }); + if (existsSync(rendererOutDir)) { + cpSync(rendererOutDir, outputDir, { recursive: true }); + } + } +} + +function copyRendererPageHtml(input, output, scriptName, options = {}) { ensureDist(); const source = readFileSync(input, "utf8"); const styleTag = ' '; @@ -105,6 +160,12 @@ function copyRendererPageHtml(input, output, scriptName) { ? source.replace(' ', scriptTag) : source.replace("", `${scriptTag}\n `); + for (const extraScriptTag of options.beforeModuleScriptTags ?? []) { + if (!hasScriptTag(html, extraScriptTag)) { + html = html.replace(scriptTag, `${extraScriptTag}\n${scriptTag}`); + } + } + if (!html.includes('href="../../assets/main.css"')) { html = html.replace("", `${styleTag}\n `); } @@ -112,18 +173,23 @@ function copyRendererPageHtml(input, output, scriptName) { writeFileSync(output, html, "utf8"); } +function hasScriptTag(html, scriptTag) { + const sourceMatch = scriptTag.match(/\bsrc="([^"]+)"/); + return sourceMatch ? html.includes(sourceMatch[1]) : html.includes(scriptTag); +} + export function createMainBuildOptions({ mode = "production", plugins = [] } = {}) { return { absWorkingDir: projectRoot, bundle: true, entryNames: "[name]", entryPoints: [ - path.join(projectRoot, "src", "main", "main.ts"), - path.join(projectRoot, "src", "main", "browser-preload.ts"), - path.join(projectRoot, "src", "server", "mcp", "browser-web-search-proxy-mcp.ts"), - path.join(projectRoot, "src", "server", "mcp", "fusion-vision-mcp.ts"), - path.join(projectRoot, "src", "server", "mcp", "fusion-tool-fallback-mcp.ts"), - path.join(projectRoot, "src", "main", "preload.ts") + path.join(electronSourceRoot, "main", "main.ts"), + path.join(electronSourceRoot, "main", "browser-preload.ts"), + path.join(coreSourceRoot, "mcp", "browser-web-search-proxy-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-vision-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-tool-fallback-mcp.ts"), + path.join(electronSourceRoot, "main", "preload.ts") ], external: nodeExternals, format: "cjs", @@ -131,9 +197,9 @@ export function createMainBuildOptions({ mode = "production", plugins = [] } = { logLevel: "info", metafile: true, minify: mode === "production", - outdir: mainOutDir, + outdir: electronMainOutDir, platform: "node", - plugins, + plugins: [packageAliasPlugin(), ...plugins], sourcemap: mode !== "production", target: "node22" }; @@ -144,15 +210,42 @@ export function createCliBuildOptions({ mode = "production", plugins = [] } = {} absWorkingDir: projectRoot, bundle: true, entryNames: "[name]", - entryPoints: [path.join(projectRoot, "src", "main", "cli.ts")], + entryPoints: [ + path.join(cliSourceRoot, "cli.ts"), + path.join(coreSourceRoot, "mcp", "fusion-vision-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-tool-fallback-mcp.ts") + ], external: nodeExternals.filter((moduleName) => moduleName !== "electron"), format: "cjs", legalComments: "none", logLevel: "info", minify: mode === "production", - outdir: mainOutDir, + outdir: cliMainOutDir, platform: "node", - plugins: [forbidCliElectronPlugin(), ...plugins], + plugins: [forbidCliElectronPlugin(), packageAliasPlugin(), ...plugins], + sourcemap: mode !== "production", + target: "node22" + }; +} + +export function createCoreServerBuildOptions({ mode = "production", plugins = [] } = {}) { + return { + absWorkingDir: projectRoot, + bundle: true, + entryNames: "[name]", + entryPoints: [ + path.join(coreSourceRoot, "entrypoints", "server.ts"), + path.join(coreSourceRoot, "mcp", "fusion-vision-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-tool-fallback-mcp.ts") + ], + external: nodeExternals.filter((moduleName) => moduleName !== "electron"), + format: "cjs", + legalComments: "none", + logLevel: "info", + minify: mode === "production", + outdir: coreMainOutDir, + platform: "node", + plugins: [forbidCliElectronPlugin(), packageAliasPlugin(), ...plugins], sourcemap: mode !== "production", target: "node22" }; @@ -183,7 +276,7 @@ export function createRendererBuildOptions({ mode = "production", plugins = [] } minify: mode === "production", outfile: path.join(rendererAssetsDir, "main.js"), platform: "browser", - plugins: [rendererAliasPlugin(), ...plugins], + plugins: [rendererAliasPlugin(), packageAliasPlugin(), ...plugins], publicPath: "../../assets", sourcemap: mode !== "production", target: "chrome120" @@ -210,14 +303,14 @@ export function createWebClientBridgeBuildOptions({ mode = "production", plugins return { absWorkingDir: projectRoot, bundle: true, - entryPoints: [path.join(projectRoot, "src", "main", "web-client-bridge.ts")], + entryPoints: [path.join(uiSourceRoot, "web-client-bridge.ts")], format: "iife", legalComments: "none", logLevel: "info", minify: mode === "production", outfile: webClientBridgeOutput, platform: "browser", - plugins, + plugins: [packageAliasPlugin(), ...plugins], sourcemap: mode !== "production", target: "chrome120" }; @@ -239,11 +332,21 @@ export function watchPlugin(name, onEnd) { export async function buildMain(options = {}) { const [mainBuildResult] = await Promise.all([ esbuild.build(createMainBuildOptions(options)), - esbuild.build(createCliBuildOptions(options)) + buildCoreServer(options), + buildCli(options) ]); + copyCliRuntimeToElectronDist(); validateLightweightMcpBundles(mainBuildResult.metafile); } +export async function buildCli(options = {}) { + await esbuild.build(createCliBuildOptions(options)); +} + +export async function buildCoreServer(options = {}) { + await esbuild.build(createCoreServerBuildOptions(options)); +} + export async function buildRenderer(options = {}) { await esbuild.build(createRendererBuildOptions(options)); } @@ -260,6 +363,14 @@ export async function buildWebClientBridge(options = {}) { await esbuild.build(createWebClientBridgeBuildOptions(options)); } +export function copyCliRuntimeToElectronDist() { + ensureDist(); + const cliRuntime = path.join(cliMainOutDir, "cli.js"); + if (existsSync(cliRuntime)) { + cpSync(cliRuntime, path.join(electronMainOutDir, "cli.js")); + } +} + export async function buildStyles({ minify = false } = {}) { ensureDist(); const args = ["-i", cssInput, "-o", cssOutput]; @@ -305,6 +416,26 @@ function rendererAliasPlugin() { }; } +function packageAliasPlugin() { + return { + name: "ccr-package-alias", + setup(build) { + build.onResolve({ filter: /^@ccr\/cli\// }, (args) => { + return { path: resolvePackageImport(cliSourceRoot, args.path.slice("@ccr/cli/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/core\// }, (args) => { + return { path: resolvePackageImport(coreSourceRoot, args.path.slice("@ccr/core/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/electron\// }, (args) => { + return { path: resolvePackageImport(electronSourceRoot, args.path.slice("@ccr/electron/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/ui\// }, (args) => { + return { path: resolvePackageImport(uiSourceRoot, args.path.slice("@ccr/ui/".length)) }; + }); + } + }; +} + function forbidCliElectronPlugin() { return { name: "forbid-cli-electron", @@ -371,19 +502,23 @@ function normalizeBuildPath(value) { } function resolveRendererImport(importPath) { - const basePath = path.resolve(rendererRoot, importPath); + return resolvePackageImport(rendererRoot, importPath); +} + +function resolvePackageImport(rootDir, importPath) { + const packageBasePath = path.resolve(rootDir, importPath); const candidates = [ - basePath, - `${basePath}.tsx`, - `${basePath}.ts`, - `${basePath}.jsx`, - `${basePath}.js`, - `${basePath}.json`, - `${basePath}.css`, - path.join(basePath, "index.tsx"), - path.join(basePath, "index.ts"), - path.join(basePath, "index.jsx"), - path.join(basePath, "index.js") + packageBasePath, + `${packageBasePath}.tsx`, + `${packageBasePath}.ts`, + `${packageBasePath}.jsx`, + `${packageBasePath}.js`, + `${packageBasePath}.json`, + `${packageBasePath}.css`, + path.join(packageBasePath, "index.tsx"), + path.join(packageBasePath, "index.ts"), + path.join(packageBasePath, "index.jsx"), + path.join(packageBasePath, "index.js") ]; for (const candidate of candidates) { @@ -392,5 +527,5 @@ function resolveRendererImport(importPath) { } } - return basePath; + return packageBasePath; } diff --git a/build/test.mjs b/build/test.mjs index 8319a30..55c04d9 100644 --- a/build/test.mjs +++ b/build/test.mjs @@ -6,7 +6,9 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(__dirname, ".."); const testsOutDir = path.join(projectRoot, "dist", "tests"); -const rendererRoot = path.join(projectRoot, "src", "renderer"); +const rendererRoot = path.join(projectRoot, "packages", "ui", "src"); +const cliSourceRoot = path.join(projectRoot, "packages", "cli", "src"); +const coreSourceRoot = path.join(projectRoot, "packages", "core", "src"); const testSuites = [ { name: "main", testDir: path.join(projectRoot, "tests", "main") }, { name: "renderer", testDir: path.join(projectRoot, "tests", "renderer") } @@ -53,7 +55,7 @@ for (const suite of selectedSuites) { logLevel: "info", outdir: path.join(testsOutDir, suite.name), platform: "node", - plugins: [rendererAliasPlugin()], + plugins: [rendererAliasPlugin(), packageAliasPlugin()], target: "node22" }); } @@ -87,8 +89,26 @@ function rendererAliasPlugin() { }; } +function packageAliasPlugin() { + return { + name: "test-package-alias", + setup(build) { + build.onResolve({ filter: /^@ccr\/cli\// }, (args) => { + return { path: resolvePackageImport(cliSourceRoot, args.path.slice("@ccr/cli/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/core\// }, (args) => { + return { path: resolvePackageImport(coreSourceRoot, args.path.slice("@ccr/core/".length)) }; + }); + } + }; +} + function resolveRendererImport(importPath) { - const basePath = path.resolve(rendererRoot, importPath); + return resolvePackageImport(rendererRoot, importPath); +} + +function resolvePackageImport(rootDir, importPath) { + const basePath = path.resolve(rootDir, importPath); const candidates = [ basePath, `${basePath}.tsx`, diff --git a/components.json b/components.json index e6f7108..a1acbd1 100644 --- a/components.json +++ b/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/renderer/styles/globals.css", + "css": "packages/ui/src/styles/globals.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8a8e7a8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +services: + ccr: + build: + context: . + dockerfile: Dockerfile + image: claude-code-router:local + environment: + CCR_WEB_HOST: 127.0.0.1 + CCR_WEB_PORT: 3459 + CCR_NGINX_PORT: 8080 + CCR_GATEWAY_HOST: 127.0.0.1 + CCR_GATEWAY_PORT: 3456 + CCR_GATEWAY_CORE_PORT: 3457 + CCR_PUBLIC_HOST: 127.0.0.1 + CCR_PUBLIC_PORT: 3458 + ports: + - "3458:8080" + volumes: + - ccr-data:/data + restart: unless-stopped + +volumes: + ccr-data: diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..0901836 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,74 @@ +# Docker deployment + +This image runs the core server package with PM2 and serves the built UI package +through Nginx. Nginx is the only published entrypoint: it serves the UI, proxies +management API calls to the internal core server, and proxies gateway API calls +to the internal gateway listener. + +## Build and run + +```sh +docker compose up --build +``` + +Then open: + +- Web UI: +- Gateway endpoint: + +The container stores config and SQLite databases under `/data`, backed by the +`ccr-data` volume in `docker-compose.yml`. + +On a fresh data volume, the Web UI starts immediately. The gateway port is +available through the same Nginx port, but the gateway only starts after at +least one provider and model are configured. + +## Image scripts + +```sh +npm run docker:build +npm run docker:run +``` + +## Smoke test + +```sh +npm run test:docker +``` + +The smoke test builds the image, starts an isolated temporary container with a +special-character `CCR_WEB_AUTH_TOKEN`, verifies that only the Nginx port is +published, checks UI and RPC authentication, confirms legacy Docker config is +migrated to the public Nginx router endpoint, and removes its temporary +container and volume. Set `CCR_DOCKER_TEST_SKIP_BUILD=1` to reuse an already +built image. + +The Dockerfile defaults to `node:22-bookworm` for reliable native SQLite +installation. To use a different Node base image: + +```sh +docker build --build-arg NODE_IMAGE=node:22-bookworm-slim -t claude-code-router:local . +``` + +## Environment + +| Variable | Default | Description | +| --- | --- | --- | +| `CCR_WEB_HOST` | `127.0.0.1` | Internal core server bind host. | +| `CCR_WEB_PORT` | `3459` | Internal core server port. | +| `CCR_NGINX_PORT` | `8080` | Nginx port that serves UI and proxies management/gateway requests. | +| `CCR_WEB_AUTH_TOKEN` | generated | Shared management UI token used by Nginx redirects and the core server. | +| `CCR_GATEWAY_HOST` | `127.0.0.1` | Internal gateway bind host. | +| `CCR_GATEWAY_PORT` | `3456` | Internal gateway port proxied by Nginx. | +| `CCR_GATEWAY_CORE_PORT` | `3457` | Internal core gateway port used for first-run config. | +| `CCR_PUBLIC_HOST` | `127.0.0.1` | Host used for the first-run public router endpoint. | +| `CCR_PUBLIC_PORT` | `3458` | Port used for the first-run public router endpoint. | +| `CCR_PUBLIC_BASE_URL` | `http://127.0.0.1:3458` | Full public router endpoint override. | +| `CCR_DATA_DIR` | `/data` | Container data root. | +| `CCR_NO_GATEWAY` | `0` | Set to `1` to run only the Web UI management service. | +| `CCR_DOCKER_INIT_CONFIG` | `1` | Set to `0` to disable first-run `config.json` bootstrap. | +| `CCR_DOCKER_SYNC_PUBLIC_ENDPOINT` | `1` | Sync existing Docker config to the Nginx public router endpoint on startup. | + +The first-run bootstrap writes a minimal legacy `config.json` only when neither +`config.json` nor `config.sqlite` exists in the mounted data directory. Once the +UI saves settings into SQLite, existing persisted configuration takes priority. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..c4b8983 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,207 @@ +#!/bin/sh +set -eu + +CCR_DATA_DIR="${CCR_DATA_DIR:-/data}" +CCR_WEB_HOST="${CCR_WEB_HOST:-127.0.0.1}" +CCR_WEB_PORT="${CCR_WEB_PORT:-3459}" +CCR_NGINX_PORT="${CCR_NGINX_PORT:-8080}" +CCR_GATEWAY_HOST="${CCR_GATEWAY_HOST:-127.0.0.1}" +CCR_GATEWAY_PORT="${CCR_GATEWAY_PORT:-3456}" +CCR_GATEWAY_CORE_PORT="${CCR_GATEWAY_CORE_PORT:-3457}" +CCR_PUBLIC_HOST="${CCR_PUBLIC_HOST:-127.0.0.1}" +CCR_PUBLIC_PORT="${CCR_PUBLIC_PORT:-3458}" +CCR_PUBLIC_BASE_URL="${CCR_PUBLIC_BASE_URL:-http://${CCR_PUBLIC_HOST}:${CCR_PUBLIC_PORT}}" +CCR_NO_GATEWAY="${CCR_NO_GATEWAY:-0}" + +if [ -z "${CCR_WEB_AUTH_TOKEN:-}" ]; then + CCR_WEB_AUTH_TOKEN="$(node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('base64url'))")" +fi +CCR_WEB_AUTH_TOKEN_QUERY="$(node -e "process.stdout.write(encodeURIComponent(process.argv[1] || ''))" "${CCR_WEB_AUTH_TOKEN}")" + +export HOME="${CCR_DATA_DIR}" +export CCR_DATA_DIR +export CCR_GATEWAY_CORE_PORT +export CCR_GATEWAY_HOST +export CCR_GATEWAY_PORT +export CCR_NGINX_PORT +export CCR_NO_GATEWAY +export CCR_PUBLIC_BASE_URL +export CCR_PUBLIC_HOST +export CCR_PUBLIC_PORT +export CCR_WEB_AUTH_TOKEN +export CCR_WEB_AUTH_TOKEN_QUERY +export CCR_WEB_HOST +export CCR_WEB_PORT + +CONFIG_DIR="${HOME}/.claude-code-router" +CONFIG_FILE="${CONFIG_DIR}/config.json" +APP_CONFIG_DB_FILE="${CONFIG_DIR}/config.sqlite" + +mkdir -p "${CONFIG_DIR}" "${CONFIG_DIR}/app-data" /run/nginx /var/lib/nginx /var/log/nginx + +if [ "${CCR_DOCKER_INIT_CONFIG:-1}" != "0" ] && [ ! -f "${CONFIG_FILE}" ] && [ ! -f "${APP_CONFIG_DB_FILE}" ]; then + node - <<'NODE' +const fs = require("node:fs"); +const path = require("node:path"); + +const configDir = path.join(process.env.HOME, ".claude-code-router"); +const configFile = path.join(configDir, "config.json"); +const gatewayHost = process.env.CCR_GATEWAY_HOST || "0.0.0.0"; +const gatewayPort = Number(process.env.CCR_GATEWAY_PORT || "3456"); +const gatewayCorePort = Number(process.env.CCR_GATEWAY_CORE_PORT || "3457"); +const publicBaseUrl = (process.env.CCR_PUBLIC_BASE_URL || `http://127.0.0.1:${process.env.CCR_PUBLIC_PORT || "3458"}`).replace(/\/+$/, ""); + +fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); +fs.writeFileSync(configFile, `${JSON.stringify({ + HOST: gatewayHost, + PORT: gatewayPort, + gateway: { + coreHost: "127.0.0.1", + corePort: gatewayCorePort, + enabled: true, + host: gatewayHost, + port: gatewayPort + }, + routerEndpoint: publicBaseUrl +}, null, 2)}\n`, { mode: 0o600 }); +NODE +fi + +if [ "${CCR_DOCKER_SYNC_PUBLIC_ENDPOINT:-1}" != "0" ]; then + node - <<'NODE' +const fs = require("node:fs"); +const path = require("node:path"); + +const configDir = path.join(process.env.HOME, ".claude-code-router"); +const configFile = path.join(configDir, "config.json"); +const appConfigDbFile = path.join(configDir, "config.sqlite"); +const gatewayHost = process.env.CCR_GATEWAY_HOST || "127.0.0.1"; +const gatewayPort = Number(process.env.CCR_GATEWAY_PORT || "3456"); +const gatewayCorePort = Number(process.env.CCR_GATEWAY_CORE_PORT || "3457"); +const publicBaseUrl = (process.env.CCR_PUBLIC_BASE_URL || `http://127.0.0.1:${process.env.CCR_PUBLIC_PORT || "3458"}`).replace(/\/+$/, ""); + +function syncConfig(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return value; + } + value.HOST = gatewayHost; + value.PORT = gatewayPort; + value.gateway = { + ...(value.gateway && typeof value.gateway === "object" && !Array.isArray(value.gateway) ? value.gateway : {}), + coreHost: "127.0.0.1", + corePort: gatewayCorePort, + enabled: true, + host: gatewayHost, + port: gatewayPort + }; + value.routerEndpoint = publicBaseUrl; + return value; +} + +function syncJsonFile() { + if (!fs.existsSync(configFile)) { + return; + } + const parsed = JSON.parse(fs.readFileSync(configFile, "utf8")); + fs.writeFileSync(configFile, `${JSON.stringify(syncConfig(parsed), null, 2)}\n`, { mode: 0o600 }); +} + +function syncSqliteConfig() { + if (!fs.existsSync(appConfigDbFile)) { + return; + } + let Database; + try { + Database = require("better-sqlite3"); + } catch { + return; + } + const db = new Database(appConfigDbFile); + try { + const row = db.prepare("select value_json from app_config where key = ?").get("default"); + if (!row?.value_json) { + return; + } + const parsed = JSON.parse(row.value_json); + db.prepare("update app_config set value_json = ?, updated_at = ? where key = ?") + .run(JSON.stringify(syncConfig(parsed)), new Date().toISOString(), "default"); + } finally { + db.close(); + } +} + +syncJsonFile(); +syncSqliteConfig(); +NODE +fi + +cat > /etc/nginx/conf.d/default.conf <=6.9.0" } }, + "node_modules/@claude-code-router/core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@claude-code-router/electron": { + "resolved": "packages/electron", + "link": true + }, + "node_modules/@claude-code-router/ui": { + "resolved": "packages/ui", + "link": true + }, "node_modules/@date-io/core": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", "integrity": "sha512-+EQE8xZhRM/hsY0CDTVyayMDDY5ihc4MqXCrPxooKw19yAzUIC6uUqsZeaOFNL9YKTNxYKrJP5DFgE8o5xRCOw==", - "dev": true, "license": "MIT" }, "node_modules/@date-io/date-fns": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.17.0.tgz", "integrity": "sha512-L0hWZ/mTpy3Gx/xXJ5tq5CzHo0L7ry6KEO9/w/JWiFWFLZgiNVo3ex92gOl3zmzjHqY/3Ev+5sehAr8UnGLEng==", - "dev": true, "license": "MIT", "dependencies": { "@date-io/core": "^2.17.0" @@ -88,7 +98,6 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.17.0.tgz", "integrity": "sha512-e4nb4CDZU4k0WRVhz1Wvl7d+hFsedObSauDHKtZwU9kt7gdYEAzKgnrSCTHsEaXrDumdrkCYTeZ0Tmyk7uV4tw==", - "dev": true, "license": "MIT", "dependencies": { "@date-io/core": "^2.17.0" @@ -106,7 +115,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -119,7 +127,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", - "dev": true, "license": "MIT", "dependencies": { "@dnd-kit/accessibility": "^3.1.1", @@ -135,7 +142,6 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", - "dev": true, "license": "MIT", "dependencies": { "@dnd-kit/utilities": "^3.2.2", @@ -150,7 +156,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -1170,7 +1175,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", - "dev": true, "license": "ISC", "dependencies": { "get-stream": "^6.0.1", @@ -1184,14 +1188,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz", "integrity": "sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==", - "dev": true, "license": "ISC" }, "node_modules/@mapbox/jsonlint-lines-primitives": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -1200,7 +1202,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==", - "dev": true, "license": "BSD-3-Clause", "peerDependencies": { "mapbox-gl": ">=0.32.1 <2.0.0" @@ -1210,28 +1211,24 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", - "dev": true, "license": "ISC" }, "node_modules/@mapbox/tiny-sdf": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/@mapbox/unitbezier": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/@mapbox/vector-tile": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@mapbox/point-geometry": "~0.1.0" @@ -1241,7 +1238,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", - "dev": true, "license": "ISC", "engines": { "node": ">=6.0.0" @@ -1251,13 +1247,16 @@ "version": "3.6.3", "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.6.3.tgz", "integrity": "sha512-UVrkSOs02YLehKaehrxhAejYMurehIHPfFQvPFZmdJHglHOU4V2cCUApTVEwOksvCp161ypEqVp+9H6mGhTTcw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.0", "gl-matrix": "^3.4.0" } }, + "node_modules/@musistudio/claude-code-router": { + "resolved": "packages/cli", + "link": true + }, "node_modules/@noble/hashes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", @@ -1638,11 +1637,259 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@pm2/agent": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", + "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", + "license": "AGPL-3.0", + "dependencies": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.3.1", + "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.1.0", + "fclone": "~1.0.11", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.0", + "proxy-agent": "~6.4.0", + "semver": "~7.5.0", + "ws": "~7.5.10" + } + }, + "node_modules/@pm2/agent/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@pm2/agent/node_modules/dayjs": { + "version": "1.8.36", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", + "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", + "license": "MIT" + }, + "node_modules/@pm2/agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/agent/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@pm2/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==", + "license": "MIT", + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@pm2/io": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", + "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", + "license": "Apache-2", + "dependencies": { + "async": "~2.6.1", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "~7.5.4", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@pm2/io/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/io/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT" + }, + "node_modules/@pm2/io/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "license": "Apache-2.0" + }, + "node_modules/@pm2/js-api": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.1.tgz", + "integrity": "sha512-n9tDOz1ojyDOs05XthEXrLFVQYbbh2oAN19UakLPyEZDrUyEq05h8wIZU8+dNXBQY/KeFlWMLVA76nnX52ofRg==", + "license": "Apache-2", + "dependencies": { + "async": "^2.6.3", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "extrareqp2": "^1.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/js-api/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT" + }, + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", - "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -1669,7 +1916,6 @@ "version": "11.1.8", "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -1680,7 +1926,6 @@ "version": "2.6.5-forked.0", "resolved": "https://registry.npmjs.org/@rtsao/csstype/-/csstype-2.6.5-forked.0.tgz", "integrity": "sha512-0HwnY8uPWcCloTgdbbaJG3MbDUfNf6yKWZfCKxFv9yj2Sbp4mSKaIjC7Cr/5L4hMxvrrk85CU3wlAg7EtBBJ1Q==", - "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -1700,14 +1945,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, "license": "MIT" }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", - "dev": true, "license": "MIT" }, "node_modules/@szmarczak/http-timer": { @@ -2096,6 +2339,12 @@ "node": ">=22.0.0" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/better-sqlite3": { "version": "7.6.13", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", @@ -2123,28 +2372,24 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "dev": true, "license": "MIT", "dependencies": { "@types/d3-color": "*" @@ -2154,14 +2399,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "dev": true, "license": "MIT", "dependencies": { "@types/d3-time": "*" @@ -2171,7 +2414,6 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", - "dev": true, "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -2181,14 +2423,12 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "dev": true, "license": "MIT" }, "node_modules/@types/debug": { @@ -2215,7 +2455,6 @@ "version": "2.0.46", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", - "dev": true, "license": "MIT" }, "node_modules/@types/http-cache-semantics": { @@ -2266,14 +2505,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.31", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2304,7 +2543,6 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "dev": true, "license": "MIT" }, "node_modules/@xmldom/xmldom": { @@ -2337,7 +2575,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -2376,6 +2613,30 @@ } } }, + "node_modules/amp": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", + "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", + "license": "MIT" + }, + "node_modules/amp-message": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", + "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", + "license": "MIT", + "dependencies": { + "amp": "0.3.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2390,7 +2651,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2402,6 +2662,28 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/app-builder-lib": { "version": "26.15.3", "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", @@ -2586,11 +2868,22 @@ "node": ">=12.0.0" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, "license": "MIT" }, "node_modules/async-exit-hook": { @@ -2633,7 +2926,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", - "dev": true, "license": "MIT", "dependencies": { "core-js": "^2.5.0" @@ -2673,7 +2965,6 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", - "dev": true, "license": "MPL-2.0", "engines": { "node": ">=4" @@ -2712,7 +3003,6 @@ "version": "16.1.1", "resolved": "https://registry.npmjs.org/baseui/-/baseui-16.1.1.tgz", "integrity": "sha512-pux441GCtmYrI8NE1SYqwsDnBad47msc7T/6p9JhHYrqYKo2y52YAO+7oSoFqIdaJxoY9r443U14hDkF68p56w==", - "dev": true, "license": "MIT", "dependencies": { "@date-io/date-fns": "^2.13.1", @@ -2759,21 +3049,18 @@ "version": "2.6.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz", "integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw==", - "dev": true, "license": "MIT" }, "node_modules/baseui/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, "license": "MIT" }, "node_modules/baseui/node_modules/react-uid": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-uid/-/react-uid-2.3.0.tgz", "integrity": "sha512-tsPZ77GR0pISGYmpCLHAbZTabKXZ7zBniKPVqVMMfnXFyo39zq5g/psIlD5vLTKkjQEhWOO8JhqcHnxkwNu6eA==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^1.10.0" @@ -2795,7 +3082,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz", "integrity": "sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==", - "dev": true, "license": "MIT", "engines": { "node": ">8.0.0" @@ -2809,7 +3095,6 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.5.tgz", "integrity": "sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", @@ -2827,9 +3112,17 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, "license": "0BSD" }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/better-sqlite3": { "version": "12.11.1", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", @@ -2844,6 +3137,18 @@ "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -2885,6 +3190,12 @@ "dev": true, "license": "MIT" }, + "node_modules/bodec": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", + "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", + "license": "MIT" + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -2910,7 +3221,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2947,7 +3257,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, "node_modules/builder-util": { @@ -3062,7 +3371,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/card-validator/-/card-validator-6.2.0.tgz", "integrity": "sha512-1vYv45JaE9NmixZr4dl6ykzbFKv7imI9L7MQDs235b/a7EGbG8rrEsipeHtVvscLSUbl3RX6UP5gyOe0Y2FlHA==", - "dev": true, "license": "MIT", "dependencies": { "credit-card-type": "^8.0.0" @@ -3085,6 +3393,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "license": "MIT/X11" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -3118,6 +3456,30 @@ "node": ">=8" } }, + "node_modules/cli-tableau": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", + "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", + "dependencies": { + "chalk": "3.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/cli-tableau/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3150,7 +3512,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3160,7 +3521,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3173,7 +3533,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -3193,7 +3552,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -3234,7 +3592,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, "hasInstallScript": true, "license": "MIT" }, @@ -3249,7 +3606,12 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-8.3.0.tgz", "integrity": "sha512-czfZUpQ7W9CDxZL4yFLb1kFtM/q2lTOY975hL2aO+DC8+GRNDVSXVCHXhVFZPxiUKmQCZbFP8vIhxx5TBQaThw==", - "dev": true, + "license": "MIT" + }, + "node_modules/croner": { + "version": "4.1.97", + "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", "license": "MIT" }, "node_modules/cross-dirname": { @@ -3303,7 +3665,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", - "dev": true, "license": "MIT", "dependencies": { "hyphenate-style-name": "^1.0.2", @@ -3314,21 +3675,24 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", - "dev": true, "license": "MIT" }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/culvert": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", "license": "MIT" }, "node_modules/d3": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "3", @@ -3370,7 +3734,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "dev": true, "license": "ISC", "dependencies": { "internmap": "1 - 2" @@ -3383,7 +3746,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3393,7 +3755,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3410,7 +3771,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "dev": true, "license": "ISC", "dependencies": { "d3-path": "1 - 3" @@ -3423,7 +3783,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3433,7 +3792,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "^3.2.0" @@ -3446,7 +3804,6 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "dev": true, "license": "ISC", "dependencies": { "delaunator": "5" @@ -3459,7 +3816,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3469,7 +3825,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3483,7 +3838,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "dev": true, "license": "ISC", "dependencies": { "commander": "7", @@ -3509,7 +3863,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -3519,7 +3872,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "dev": true, "license": "ISC", "dependencies": { "d3-dsv": "1 - 3" @@ -3532,7 +3884,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3547,7 +3898,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3557,7 +3907,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "2.5.0 - 3" @@ -3570,7 +3919,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3580,7 +3928,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3" @@ -3593,7 +3940,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3603,7 +3949,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3613,7 +3958,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3623,7 +3967,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3633,7 +3976,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", @@ -3650,7 +3992,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -3664,7 +4005,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3674,7 +4014,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dev": true, "license": "ISC", "dependencies": { "d3-path": "^3.1.0" @@ -3687,7 +4026,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "2 - 3" @@ -3700,7 +4038,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dev": true, "license": "ISC", "dependencies": { "d3-time": "1 - 3" @@ -3713,7 +4050,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3723,7 +4059,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -3743,7 +4078,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3756,11 +4090,19 @@ "node": ">=12" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.21.0" @@ -3777,12 +4119,17 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.8.tgz", "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==", - "dev": true, "license": "MIT", "peerDependencies": { "date-fns": ">=2.0.0" } }, + "node_modules/dayjs": { + "version": "1.11.15", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", + "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3804,7 +4151,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "dev": true, "license": "MIT" }, "node_modules/decompress-response": { @@ -3891,11 +4237,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delaunator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", - "dev": true, "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -3945,7 +4304,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "dev": true, "license": "MIT" }, "node_modules/diff": { @@ -4016,7 +4374,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", @@ -4081,7 +4438,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", - "dev": true, "license": "ISC" }, "node_modules/ejs": { @@ -4311,6 +4667,18 @@ "node": ">=10.13.0" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -4345,7 +4713,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4384,7 +4751,6 @@ "version": "1.47.1", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", - "dev": true, "license": "MIT", "workspaces": [ "docs", @@ -4455,9 +4821,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=10" }, @@ -4465,11 +4829,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", + "license": "MIT" + }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, "license": "MIT" }, "node_modules/expand-template": { @@ -4488,6 +4909,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/extrareqp2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", + "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -4500,6 +4930,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, "node_modules/fast-json-stringify": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.4.0.tgz", @@ -4591,11 +5027,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", + "license": "MIT" + }, "node_modules/file-selector": { "version": "0.1.19", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz", "integrity": "sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.1" @@ -4654,7 +5095,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4681,7 +5121,6 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.6.tgz", "integrity": "sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -4690,6 +5129,26 @@ "node": ">=10" } }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -4711,7 +5170,6 @@ "version": "12.40.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz", "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==", - "dev": true, "license": "MIT", "dependencies": { "motion-dom": "^12.40.0", @@ -4762,11 +5220,24 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4776,7 +5247,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==", - "dev": true, "license": "ISC" }, "node_modules/get-caller-file": { @@ -4832,7 +5302,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -4841,6 +5310,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/git-node-fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", + "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", + "license": "MIT" + }, + "node_modules/git-sha1": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", + "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", + "license": "MIT" + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -4851,7 +5346,6 @@ "version": "3.4.4", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", - "dev": true, "license": "MIT" }, "node_modules/glob": { @@ -4871,6 +5365,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -4957,14 +5463,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==", - "dev": true, "license": "ISC" }, "node_modules/hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -4974,7 +5478,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5027,7 +5530,6 @@ "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": { "function-bind": "^1.1.2" @@ -5060,7 +5562,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -5088,7 +5589,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -5102,14 +5602,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5142,7 +5640,6 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -5177,7 +5674,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-5.1.2.tgz", "integrity": "sha512-PYUF+94gDfhy+LsQxM0g3d6Hge4l1pAqOSOiZuHWzMvQEGsbRQ/ck2WioLqrY2ZkHyPgVUXxn+hrkF7D6QUGbA==", - "dev": true, "license": "MIT", "dependencies": { "css-in-js-utils": "^2.0.0" @@ -5187,7 +5683,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -5197,12 +5692,20 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", @@ -5212,11 +5715,37 @@ "node": ">= 10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5236,7 +5765,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -5249,7 +5777,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5289,7 +5816,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5323,11 +5849,22 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-git": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", + "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", + "license": "MIT", + "dependencies": { + "bodec": "^0.1.0", + "culvert": "^0.1.2", + "git-sha1": "^0.1.2", + "pako": "^0.2.5" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -5388,7 +5925,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, "license": "ISC", "optional": true }, @@ -5421,14 +5957,12 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", - "dev": true, "license": "MIT" }, "node_modules/kdbush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==", - "dev": true, "license": "ISC" }, "node_modules/keyv": { @@ -5759,7 +6293,6 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, "license": "MIT" }, "node_modules/lodash.escaperegexp": { @@ -5779,7 +6312,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -5802,7 +6334,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -5815,7 +6346,6 @@ "version": "1.20.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.20.0.tgz", "integrity": "sha512-jhXLeC/7m0/tjL1nzMdKk6x256zWA6AtbhTVreHOiKPoeX2d6MK4FbyIQPpVq0E6iPWBisyy1TW+pEge/uMEuQ==", - "dev": true, "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -5835,7 +6365,6 @@ "version": "1.13.3", "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.3.tgz", "integrity": "sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==", - "dev": true, "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", @@ -5893,7 +6422,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz", "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==", - "dev": true, "license": "MIT" }, "node_modules/micromatch": { @@ -6006,7 +6534,6 @@ "version": "2.7.3", "resolved": "https://registry.npmjs.org/mjolnir.js/-/mjolnir.js-2.7.3.tgz", "integrity": "sha512-Z5z/+FzZqOSO3juSVKV3zcm4R2eAlWwlKMcqHmyFEJAaLILNcDKnIbnb4/kbcGyIuhtdWrzu8WOIR7uM6I34aw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hammerjs": "^2.0.41", @@ -6041,14 +6568,18 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-2.0.5.tgz", "integrity": "sha512-ST0PnThzWKcgSLyc+ugLVql45PvESt3Ul/wrdV/OPc/6Pr8dbLAIJsN1cIp41FLzbN+srVTNIRn+5Cju0nyV6A==", - "dev": true, + "license": "MIT" + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT" }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -6058,7 +6589,6 @@ "version": "12.40.0", "resolved": "https://registry.npmjs.org/motion/-/motion-12.40.0.tgz", "integrity": "sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA==", - "dev": true, "license": "MIT", "dependencies": { "framer-motion": "^12.40.0", @@ -6085,7 +6615,6 @@ "version": "12.40.0", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz", "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==", - "dev": true, "license": "MIT", "dependencies": { "motion-utils": "^12.39.0" @@ -6095,7 +6624,6 @@ "version": "12.39.0", "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", - "dev": true, "license": "MIT" }, "node_modules/mri": { @@ -6118,15 +6646,67 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", - "dev": true, "license": "MIT" }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, + "node_modules/needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-abi": { "version": "4.31.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.31.0.tgz", @@ -6260,6 +6840,15 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -6277,7 +6866,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6356,6 +6944,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -6376,6 +7002,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -6405,7 +7037,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "ieee754": "^1.1.12", @@ -6441,7 +7072,6 @@ "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" @@ -6450,6 +7080,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidusage": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", + "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pidusage/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/pino": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", @@ -6518,6 +7180,38 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -6533,11 +7227,204 @@ "node": ">=10.4.0" } }, + "node_modules/pm2": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.14.tgz", + "integrity": "sha512-wX1FiFkzuT2H/UUEA8QNXDAA9MMHDsK/3UHj6Dkd5U7kxyigKDA5gyDw78ycTQZAuGCLWyUX5FiXEuVQWafukA==", + "license": "AGPL-3.0", + "dependencies": { + "@pm2/agent": "~2.1.1", + "@pm2/blessed": "0.1.81", + "@pm2/io": "~6.1.0", + "@pm2/js-api": "~0.8.0", + "@pm2/pm2-version-check": "^1.0.4", + "ansis": "4.0.0-node10", + "async": "3.2.6", + "chokidar": "3.6.0", + "cli-tableau": "2.0.1", + "commander": "2.15.1", + "croner": "4.1.97", + "dayjs": "1.11.15", + "debug": "4.4.3", + "enquirer": "2.3.6", + "eventemitter2": "5.0.1", + "fclone": "1.0.11", + "js-yaml": "4.1.1", + "mkdirp": "1.0.4", + "needle": "2.4.0", + "pidusage": "3.0.2", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.1", + "pm2-deploy": "~1.0.2", + "pm2-multimeter": "^0.1.2", + "promptly": "2.2.0", + "semver": "7.7.2", + "source-map-support": "0.5.21", + "sprintf-js": "1.1.2", + "vizion": "~2.2.1" + }, + "bin": { + "pm2": "bin/pm2", + "pm2-dev": "bin/pm2-dev", + "pm2-docker": "bin/pm2-docker", + "pm2-runtime": "bin/pm2-runtime" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "pm2-sysmonit": "^1.2.8" + } + }, + "node_modules/pm2-axon": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", + "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", + "license": "MIT", + "dependencies": { + "amp": "~0.3.1", + "amp-message": "~0.1.1", + "debug": "^4.3.1", + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-axon-rpc": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", + "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-deploy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", + "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", + "license": "MIT", + "dependencies": { + "run-series": "^1.1.8", + "tv4": "^1.3.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pm2-multimeter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", + "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", + "license": "MIT/X11", + "dependencies": { + "charm": "~0.1.1" + } + }, + "node_modules/pm2-sysmonit": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", + "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", + "license": "Apache", + "optional": true, + "dependencies": { + "async": "^3.2.0", + "debug": "^4.3.1", + "pidusage": "^2.0.21", + "systeminformation": "^5.7", + "tx2": "~1.0.4" + } + }, + "node_modules/pm2-sysmonit/node_modules/pidusage": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", + "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pm2-sysmonit/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/pm2/node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "license": "MIT" + }, + "node_modules/pm2/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==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/pm2/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pm2/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pm2/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "license": "BSD-3-Clause" + }, "node_modules/polished": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.17.8" @@ -6551,7 +7438,6 @@ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -6592,7 +7478,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", - "dev": true, "license": "ISC" }, "node_modules/prebuild-install": { @@ -6700,11 +7585,19 @@ "node": ">=10" } }, + "node_modules/promptly": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", + "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", + "license": "MIT", + "dependencies": { + "read": "^1.0.4" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -6716,7 +7609,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", - "dev": true, "license": "MIT", "dependencies": { "react-is": "^16.3.2", @@ -6730,14 +7622,12 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/proper-lockfile": { @@ -6756,7 +7646,40 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, "node_modules/pump": { @@ -6812,7 +7735,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", - "dev": true, "license": "ISC" }, "node_modules/rc": { @@ -6834,7 +7756,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -6847,7 +7768,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.8.tgz", "integrity": "sha512-ma2FePH0z3px2+WOu6h+YycZcEvFmmxIlAb62cF52bG86eMySciO/EQZeQMXd07kPCYB0a1dWDT5J+KE9mCDUw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.13" @@ -6860,7 +7780,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -6874,7 +7793,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-9.0.0.tgz", "integrity": "sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw==", - "dev": true, "license": "MIT", "dependencies": { "attr-accept": "^1.1.3", @@ -6893,7 +7811,6 @@ "version": "2.13.7", "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.7.tgz", "integrity": "sha512-20lpZHEQrXPb+pp1tzd4ULL6DyO5D2KnR0G69tTDdydrmNhU7pdFmbQUYVyHUgp+xN29IuFR0PVuhOmvaZL9Og==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", @@ -6917,7 +7834,6 @@ "version": "7.79.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.79.0.tgz", "integrity": "sha512-mhYp/MTmXvzYX6AJcJVko0rktoIhhmRnEouObj4wF5i/tCttgJvnp1+9wRkpITZjDTqpo4IOSJqu0dBlPlV/Lw==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -6934,7 +7850,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-input-mask/-/react-input-mask-2.0.4.tgz", "integrity": "sha512-1hwzMr/aO9tXfiroiVCx5EtKohKwLk/NT8QlJXHQ4N+yJJFyUuMT+zfTpLBwX/lK3PkuMlievIffncpMZ3HGRQ==", - "dev": true, "license": "MIT", "dependencies": { "invariant": "^2.2.4", @@ -6949,7 +7864,6 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "dev": true, "license": "MIT", "peer": true }, @@ -6957,14 +7871,12 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "dev": true, "license": "MIT" }, "node_modules/react-map-gl": { "version": "5.2.13", "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-5.2.13.tgz", "integrity": "sha512-yYqpZJADB7o5epiYpkcUED5MWevFnOsVn9mgKfizxjsenLMpVNgNgb/x0e9DS4VZtpOLWW2ZXYBB4BJZJlZmTQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", @@ -6986,7 +7898,6 @@ "version": "1.0.26", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz", "integrity": "sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0", @@ -6997,7 +7908,6 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/react-movable/-/react-movable-3.4.1.tgz", "integrity": "sha512-VTrBKjRWV4yVUDGj5dIZIhf07fyoFDXzIV6xs3KNNHpgPbkPT7FNu/Vbe11ZjHmT48uOykCmRMpS66DrzRbZZg==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "*", @@ -7008,7 +7918,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-multi-ref/-/react-multi-ref-1.0.2.tgz", "integrity": "sha512-6oS5yzrZ4UrdMHbF6QAnnaoIe9h8R+Xv4m8uJWVK8/Q4RCc6RTT0XJ/LZ7llVgFcVbnDHeUAcVIhtRgFyzjJpA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.4" @@ -7018,7 +7927,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/react-range/-/react-range-1.10.0.tgz", "integrity": "sha512-kDo0LiBUHIQIP8menx0UoxTnHr7UXBYpIYl/DR9jCaO1o29VwvCLpkP/qOTNQz5hkJadPg1uEM07XJcJ1XGoKw==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "*", @@ -7029,7 +7937,6 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", - "dev": true, "license": "MIT", "dependencies": { "@types/use-sync-external-store": "^0.0.6", @@ -7053,7 +7960,6 @@ "version": "9.22.6", "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz", "integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.7.2", @@ -7072,12 +7978,23 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/read-binary-file-arch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", @@ -7107,6 +8024,18 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -7120,7 +8049,6 @@ "version": "3.8.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", - "dev": true, "license": "MIT", "workspaces": [ "www" @@ -7151,14 +8079,12 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "dev": true, "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "dev": true, "license": "MIT", "peerDependencies": { "redux": "^5.0.0" @@ -7183,6 +8109,20 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", + "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/resedit": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", @@ -7205,16 +8145,35 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "dev": true, "license": "MIT" }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "dev": true, "license": "MIT" }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -7226,7 +8185,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", - "dev": true, "license": "MIT", "dependencies": { "protocol-buffers-schema": "^3.3.1" @@ -7375,14 +8333,32 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", - "dev": true, "license": "Unlicense" }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/safe-buffer": { @@ -7426,7 +8402,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sanitize-filename": { @@ -7452,7 +8427,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -7540,11 +8514,16 @@ "node": ">=8" } }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, "license": "ISC" }, "node_modules/simple-concat": { @@ -7605,6 +8584,44 @@ "node": ">=10" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", @@ -7618,7 +8635,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7638,7 +8654,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -7722,7 +8737,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/styletron-engine-atomic/-/styletron-engine-atomic-1.6.2.tgz", "integrity": "sha512-vWSIxpCkYbVD3xhnxYwnJDJePD8p8pJCu7g1zFxdQIM4z34Eo1zj3LgXJI6yeNMrJ7PkE56SYK6ZOo3hpTOBzA==", - "dev": true, "license": "MIT", "dependencies": { "inline-style-prefixer": "^5.1.0", @@ -7733,7 +8747,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/styletron-react/-/styletron-react-6.1.1.tgz", "integrity": "sha512-K04BwKZTrdRG/wR5BaFG8z0bFu1jkT2HAp0UP5ZeMAKW6Ix8J3yuROWLoLUMZafaRRQ9LjiLpIl65u75L7YZow==", - "dev": true, "license": "MIT", "dependencies": { "prop-types": "^15.6.0", @@ -7747,7 +8760,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/styletron-standard/-/styletron-standard-3.1.0.tgz", "integrity": "sha512-Cr2q0IFsag6OaIeD/LBNRuCxNTPa/WtTbKP1X3o50mDudN8FGwmD5h1sMJ/Bu5+mO/2NfrNAv9V9zUXn6lXXMA==", - "dev": true, "license": "MIT", "dependencies": { "@rtsao/csstype": "2.6.5-forked.0", @@ -7772,7 +8784,6 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", - "dev": true, "license": "ISC", "dependencies": { "kdbush": "^3.0.0" @@ -7782,7 +8793,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -7791,11 +8801,49 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/systeminformation": { + "version": "5.31.12", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.12.tgz", + "integrity": "sha512-qnTtO5wHrKeKE/MvQ6iIt6XAV+5fgt/kPIQf27DYgjVQQuHUfWkV4Gu6k04ZpEzAMuyQ3ZsovY7Ivhp+E9JyWw==", + "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, "node_modules/tailwind-merge": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -7966,7 +9014,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, "license": "MIT" }, "node_modules/tiny-typed-emitter": { @@ -8027,7 +9074,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==", - "dev": true, "license": "ISC" }, "node_modules/tmp": { @@ -8054,7 +9100,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8086,7 +9131,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/tunnel-agent": { @@ -8101,6 +9145,34 @@ "node": "*" } }, + "node_modules/tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "license": [ + { + "type": "Public Domain", + "url": "http://geraintluff.github.io/tv4/LICENSE.txt" + }, + { + "type": "MIT", + "url": "http://jsonary.com/LICENSE.txt" + } + ], + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tx2": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", + "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", + "license": "MIT", + "optional": true, + "dependencies": { + "json-stringify-safe": "^5.0.1" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -8187,7 +9259,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -8209,7 +9280,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "dev": true, "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", @@ -8232,7 +9302,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -8255,7 +9324,6 @@ "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", - "dev": true, "license": "MIT AND ISC", "dependencies": { "@types/d3-array": "^3.0.3", @@ -8279,17 +9347,39 @@ "resolved": "https://registry.npmjs.org/viewport-mercator-project/-/viewport-mercator-project-7.0.4.tgz", "integrity": "sha512-0jzpL6pIMocCKWg1C3mqi/N4UPgZC3FzwghEm1H+XsUo8hNZAyJc3QR7YqC816ibOR8aWT5pCsV+gCu8/BMJgg==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, "license": "MIT", "dependencies": { "@math.gl/web-mercator": "^3.5.5" } }, + "node_modules/vizion": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", + "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", + "license": "Apache-2.0", + "dependencies": { + "async": "^2.6.3", + "git-node-fs": "^1.0.0", + "ini": "^1.3.5", + "js-git": "^0.7.8" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vizion/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", - "dev": true, "license": "MIT", "dependencies": { "@mapbox/point-geometry": "0.1.0", @@ -8301,7 +9391,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -8406,7 +9495,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC" }, "node_modules/yargs": { @@ -8450,6 +9538,68 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "packages/cli": { + "name": "@musistudio/claude-code-router", + "version": "3.0.7", + "license": "MIT", + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "undici": "^7.27.2" + }, + "bin": { + "ccr": "dist/main/cli.js" + }, + "engines": { + "node": ">=22" + } + }, + "packages/core": { + "name": "@claude-code-router/core", + "version": "3.0.7", + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "pm2": "^6.0.13", + "undici": "^7.27.2" + }, + "bin": { + "ccr-core-server": "dist/main/server.js" + }, + "engines": { + "node": ">=22" + } + }, + "packages/electron": { + "name": "@claude-code-router/electron", + "version": "3.0.7", + "dependencies": { + "electron-updater": "^6.8.9" + } + }, + "packages/ui": { + "name": "@claude-code-router/ui", + "version": "3.0.7", + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "baseui": "^16.1.1", + "clsx": "^2.1.1", + "lucide-react": "^1.17.0", + "motion": "^12.40.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "recharts": "^3.8.1", + "styletron-engine-atomic": "^1.6.2", + "styletron-react": "^6.1.1", + "tailwind-merge": "^3.6.0" + } } } } diff --git a/package.json b/package.json index 29a63fe..aa9c7e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { - "name": "claude-code-router", + "name": "claude-code-router-monorepo", "version": "3.0.7", + "private": true, "license": "MIT", "description": "Local Claude Code Router gateway with CLI and web management UI.", "repository": { @@ -18,15 +19,9 @@ "gateway", "router" ], - "main": "dist/main/main.js", - "bin": { - "ccr": "dist/main/cli.js" - }, - "files": [ - "dist", - "LICENSE", - "README.md", - "README_zh.md" + "main": "packages/electron/dist/main/main.js", + "workspaces": [ + "packages/*" ], "engines": { "node": ">=22" @@ -35,9 +30,13 @@ "access": "public" }, "scripts": { - "dev": "node build/dev.mjs", - "build": "npm run build:assets && electron-builder", + "dev": "npm run dev:cli", + "dev:ui": "node build/dev.mjs ui", + "dev:cli": "node build/dev.mjs cli", + "dev:electron": "node build/dev.mjs electron", + "build": "node build/build.mjs && electron-builder", "build:assets": "node build/build.mjs", + "build:docker": "node build/docker-build.mjs", "build:app:mac": "npm run build:app:mac:local", "build:app:mac:local": "npm run build:assets && electron-builder --config build/electron-builder.local.cjs --mac --publish never", "build:app:mac:release": "node build/macos-release-preflight.mjs && npm run build:assets && electron-builder --mac --publish never", @@ -45,7 +44,12 @@ "prepack": "npm run build:assets", "prepublishOnly": "npm run typecheck", "preview": "npm run build:assets && electron .", + "docker:build": "docker build -t claude-code-router:local .", + "docker:run": "docker run --rm -p 3458:8080 -v ccr-data:/data claude-code-router:local", "test": "node build/test.mjs && node build/run-tests.mjs", + "test:docker": "node tests/docker/docker-smoke.mjs", + "test:e2e": "npm run build:assets && playwright test", + "test:e2e:install": "playwright install chromium", "test:main": "node build/test.mjs main && node build/run-tests.mjs main", "test:renderer": "node build/test.mjs renderer && node build/run-tests.mjs renderer", "typecheck": "tsc --noEmit", @@ -63,6 +67,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@playwright/test": "^1.61.1", "@tailwindcss/cli": "^4.3.0", "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.10.2", diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 0000000..ea7e8ac --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 musistudio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..3f5ce1e --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,329 @@ +

Claude Code Router Desktop

+ +

+ Chinese README + Discord + X + License + Documentation +

+ +

+ Claude Code Router Desktop screenshot +

+ +Claude Code Router Desktop is a local gateway and desktop control panel for routing agent requests from Claude Code, Codex, ZCode, and compatible clients to the model provider you actually want to use. + +## Why Use CCR + +- Use one local endpoint for multiple agent tools instead of configuring every client separately. +- Route requests with default routing, conditional rules, fallback targets, and request rewrites instead of editing client configuration by hand. +- Mix providers without changing your workflow. CCR supports OpenAI-compatible APIs, Anthropic Messages, Gemini Generate Content, OpenRouter, DeepSeek, SiliconFlow, Moonshot, Kimi Code, Mistral, Z.AI, Bailian, and custom providers. +- Control cost and reliability with fallback routing, API key rotation, usage statistics, and request logs. + +## Features + +- **Overview dashboard**: inspect system status, usage widgets, account balances, model distribution, and share cards. +- **Provider management**: add provider presets or custom endpoints, probe protocol support, test model connectivity, manage credentials, and monitor supported account balances where available. +- **Routing rules**: configure default routing, conditional and model-prefix rules, fallback handling, and request rewrites. +- **Agent Config**: configure Claude Code, Codex, and ZCode launch entries, models, scopes, and multi-instance app profiles. +- **Gateway compatibility**: translate supported client requests through the local CCR model gateway. +- **Proxy mode**: capture supported API traffic through a local proxy with optional system proxy integration and network capture. +- **Fusion models**: combine a base model with vision, web search, or MCP tools into a reusable selectable model. + +## Documentation + +Read the full documentation at [ccrdesk.top](https://ccrdesk.top/). + +## Download And Install + +1. Open the [GitHub Releases page](https://github.com/musistudio/claude-code-router/releases). +2. Download the package for your platform: + - macOS Apple Silicon: `Claude-Code-Router_-mac-Apple-Silicon-arm64.dmg` or `.zip` + - macOS Intel: `Claude-Code-Router_-mac-Intel-x64.dmg` or `.zip` + - Windows: `Claude Code Router_.exe` + - Linux: `Claude Code Router_.AppImage` +3. Install and launch **Claude Code Router**. +4. On first launch, CCR creates its local configuration database: + - macOS/Linux: `~/.claude-code-router/config.sqlite` + - Windows: `%APPDATA%\Claude Code Router\config.sqlite` + +CCR stores runtime configuration in SQLite. A legacy `config.json` is read only once for migration when no SQLite config exists. + +After the service is started from the **Server** page, CCR listens on `http://localhost:8080` by default. The **Server** page controls the gateway `Host`, `Port`, proxy mode, system proxy, network capture, and CA certificate status. + +## Quick Start + +CCR can be configured entirely from the desktop UI. Use this setup order for a clean first run. + +### 1. Add a provider + +Open **Providers**, click **Add Provider**, then choose a built-in preset or **Other / custom API endpoint**. Fill in the provider name, base URL, protocol, API key, and model list. Run protocol probing and model connectivity checks when available, then save the provider. + +### 2. Configure routing + +Open **Routing** to add conditional rules, configure request rewrites, and set fallback behavior. + +Use **Add Routing Rule** for request conditions, model-prefix routing, or rule-level fallback targets. + +### 3. Start the gateway + +Open **Server** and click **Start**. After the page shows Running, CCR listens on `http://localhost:8080`. Enable **Auto start** if you want CCR to start the local gateway whenever the desktop app opens. + +### 4. Connect your agent tool + +Open **Agent Config** and choose the client you want to use. Configure Claude Code, Codex, or ZCode, select the target model and effect scope, then apply the config. For app entries, use the **Open Agent** action to open the target app through CCR. + +### 5. Monitor and adjust + +Use **Settings → Logs & Observability** to enable request logs and agent observability. Use **Logs** to confirm `request model`, `resolved provider`, `resolved model`, status, tokens, latency, and errors; use the tray window for quick token and account status. + +## Acknowledgements + +Codex support is powered by [musistudio/codexl](https://github.com/musistudio/codexl). + +## Support & Sponsoring + +
+ +

If you find this project helpful, please consider sponsoring its development. Your support is greatly appreciated.

+ + + + + + +
+ + Support on Ko-fi + +
+ One-time support via Ko-fi +
+ + Sponsor with PayPal + +
+ International sponsorship +
+ + + + + + +
+ Alipay +
+ Alipay QR code +
+ WeChat Pay +
+ WeChat Pay QR code +
+ +
+ +### Our Sponsors + +
+ +

A huge thank you to all our sponsors for their generous support.

+ + + + + + + + + + + + +
+ + Zhipu icon +
+ Z智谱 +
+
+ + AIHubmix icon +
+ AIHubmix +
+
+ + BurnCloud icon +
+ BurnCloud +
+
+ + 302.AI icon +
+ 302.AI +
+
+ + RunAPI icon +
+ RunAPI +
+
+ + TeamoRouter icon +
+ TeamoRouter +
+
+ +

Community Sponsors

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@Simon Leischnig@duanshuaimin@vrgitadmin@*o@ceilwoo@*说
@*更@K*g@R*R@bobleer@*苗@*划
@Clarence-pan@carter003@S*r@*晖@*敏@Z*z
@*然@cluic@*苗@PromptExpert@*应@yusnake
@*飞@董*@*汀@*涯@*:-)@**磊
@*琢@*成@Z*o@*琨@congzhangzh@*_
@Z*m@*鑫@c*y@*昕@witsice@b*g
@*亿@*辉@JACK@*光@W*l@kesku
@biguncle@二吉吉@a*g@*林@*咸@*明
@S*y@f*o@*智@F*t@r*c@qierkang
@*军@snrise-z@*王@greatheart1000@*王@zcutlip
@Peng-YM@*更@*.@F*t@*政@*铭
@*叶@七*o@*青@**晨@*远@*霄
@**吉@**飞@**驰@x*g@**东@*落
@哆*k@*涛@苗大@*呢@d*u@crizcraig
s*s*火*勤**锟*涛**明
*知*语*瓜
+ +If your name is masked, please contact me via my homepage email to update it with your GitHub username. + +
+ +## License + +This project is licensed under the [MIT License](LICENSE). diff --git a/packages/cli/README_zh.md b/packages/cli/README_zh.md new file mode 100644 index 0000000..cd79161 --- /dev/null +++ b/packages/cli/README_zh.md @@ -0,0 +1,328 @@ +

Claude Code Router Desktop

+ +

+ English README + Discord + X + License + 文档 +

+ +

+ Claude Code Router Desktop 项目截图 +

+ +Claude Code Router Desktop 是一个本地网关和桌面控制台,用来把 Claude Code、Codex、ZCode 以及兼容客户端的 Agent 请求路由到你真正想使用的模型服务。 + +## 为什么使用 CCR + +- 用一个本地入口连接多个 Agent 工具,不需要在每个客户端里重复配置 Provider。 +- 在不改变工作流的情况下混用不同 Provider。CCR 支持 OpenAI 兼容 API、Anthropic Messages、Gemini Generate Content、OpenRouter、DeepSeek、SiliconFlow、Moonshot、Kimi Code、Mistral、Z.AI、百炼以及自定义 Provider。 +- 通过 fallback 路由、API Key 轮换、用量统计和请求日志来控制成本和可靠性。 + +## 功能和特性 + +- **概览仪表盘**:查看系统状态、用量组件、账号余额、模型分布和分享卡片。 +- **Provider 管理**:添加预设或自定义端点,探测协议支持,检测模型连通性,管理凭据,并在可用时查看账号余额。 +- **路由规则**:配置条件路由、模型前缀规则、失败降级和请求改写。 +- **Agent配置**:为 Claude Code、Codex 和 ZCode 配置启动入口、模型、作用范围和多开 App 配置。 +- **网关兼容层**:通过本地 CCR 模型网关转换支持的客户端请求。 +- **代理模式**:通过本地代理捕获支持的 API 流量,可选系统代理和网络捕获。 +- **Fusion 组合模型**:把基础模型与视觉、联网搜索或 MCP 工具组合成新的可选模型。 + +## 文档 + +完整文档见 [ccrdesk.top](https://ccrdesk.top/)。 + +## 下载和安装 + +1. 打开 [GitHub Releases 页面](https://github.com/musistudio/claude-code-router/releases)。 +2. 按系统下载对应安装包: + - macOS Apple 芯片:`Claude-Code-Router_-mac-Apple-Silicon-arm64.dmg` 或 `.zip` + - macOS Intel 芯片:`Claude-Code-Router_-mac-Intel-x64.dmg` 或 `.zip` + - Windows:`Claude Code Router_.exe` + - Linux:`Claude Code Router_.AppImage` +3. 安装并启动 **Claude Code Router**。 +4. 首次启动后,CCR 会创建本地配置数据库: + - macOS/Linux:`~/.claude-code-router/config.sqlite` + - Windows:`%APPDATA%\Claude Code Router\config.sqlite` + +CCR 的运行配置存储在 SQLite 中。旧版 `config.json` 只会在没有 SQLite 配置时作为迁移来源读取一次。 + +从 **服务** 页面启动后,CCR 默认监听 `http://localhost:8080`。**服务** 页面负责配置网关 `Host`、`Port`、代理模式、系统代理、网络捕获和 CA 证书状态。 + +## 快速开始 + +CCR 可以完全通过桌面 UI 完成配置。首次使用建议按下面顺序操作。 + +### 1. 添加 Provider + +打开 **供应商**,点击 **添加供应商**,选择内置预设或 **其他 / 自定义 API 端点**。按表单填写 Provider 名称、基础 URL、协议、API Key 和模型列表。可用时先运行协议探测和模型连通性检查,然后保存 Provider。 + +### 2. 设置路由 + +打开 **路由**,添加条件规则,配置请求改写和失败降级。 + +如果需要更细粒度控制,使用 **添加路由规则** 添加模型前缀、请求条件或规则级失败降级目标。 + +### 3. 启动网关 + +打开 **服务**,点击 **启动**。页面显示运行中后,CCR 会在本机监听 `http://localhost:8080`。如果希望每次打开桌面应用时自动启动网关,可以启用自动启动。 + +### 4. 连接 Agent 工具 + +打开 **Agent配置**,选择要使用的客户端。配置 Claude Code、Codex 或 ZCode,选择目标模型和作用范围,然后应用配置。对于 App 入口,可以使用 **打开 Agent** 操作通过 CCR 打开目标应用。 + +### 5. 日常查看和调整 + +到 **设置 → 日志与观测** 打开请求日志和 Agent 观测。使用 **日志** 确认 `request model`、`resolved provider`、`resolved model`、状态码、tokens、耗时和错误;使用托盘窗口快速查看 Token 和账号状态。 + +## 致谢 + +对 Codex 的支持来自于 [musistudio/codexl](https://github.com/musistudio/codexl) 这个项目。 + +## 支持与赞助 + +
+ +

如果你觉得这个项目有帮助,欢迎赞助项目开发。非常感谢你的支持。

+ + + + + + +
+ + 通过 Ko-fi 赞助 + +
+ 通过 Ko-fi 单次赞助 +
+ + 通过 PayPal 赞助 + +
+ 国际赞助通道 +
+ + + + + + +
+ 支付宝 +
+ 支付宝收款码 +
+ 微信支付 +
+ 微信支付收款码 +
+ +
+ +### 我们的赞助商 + +
+ +

非常感谢所有赞助商的慷慨支持。

+ + + + + + + + + + + + +
+ + 智谱图标 +
+ Z智谱 +
+
+ + AIHubmix 图标 +
+ AIHubmix +
+
+ + BurnCloud 图标 +
+ BurnCloud +
+
+ + 302.AI 图标 +
+ 302.AI +
+
+ + RunAPI 图标 +
+ RunAPI +
+
+ + TeamoRouter 图标 +
+ TeamoRouter +
+
+ +

社区赞助者

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@Simon Leischnig@duanshuaimin@vrgitadmin@*o@ceilwoo@*说
@*更@K*g@R*R@bobleer@*苗@*划
@Clarence-pan@carter003@S*r@*晖@*敏@Z*z
@*然@cluic@*苗@PromptExpert@*应@yusnake
@*飞@董*@*汀@*涯@*:-)@**磊
@*琢@*成@Z*o@*琨@congzhangzh@*_
@Z*m@*鑫@c*y@*昕@witsice@b*g
@*亿@*辉@JACK@*光@W*l@kesku
@biguncle@二吉吉@a*g@*林@*咸@*明
@S*y@f*o@*智@F*t@r*c@qierkang
@*军@snrise-z@*王@greatheart1000@*王@zcutlip
@Peng-YM@*更@*.@F*t@*政@*铭
@*叶@七*o@*青@**晨@*远@*霄
@**吉@**飞@**驰@x*g@**东@*落
@哆*k@*涛@苗大@*呢@d*u@crizcraig
s*s*火*勤**锟*涛**明
*知*语*瓜
+ +如果你的名字被打码,请通过我的主页邮箱联系我更新为 GitHub 用户名。 + +
+ +## 许可证 + +本项目基于 [MIT License](LICENSE) 发布。 diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..9f9eb2c --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,48 @@ +{ + "name": "@musistudio/claude-code-router", + "version": "3.0.7", + "license": "MIT", + "description": "Local Claude Code Router gateway with CLI and web management UI.", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/musistudio/claude-code-router.git" + }, + "bugs": { + "url": "https://github.com/musistudio/claude-code-router/issues" + }, + "homepage": "https://github.com/musistudio/claude-code-router#readme", + "keywords": [ + "claude-code", + "codex", + "llm", + "gateway", + "router" + ], + "main": "dist/main/cli.js", + "bin": { + "ccr": "dist/main/cli.js" + }, + "files": [ + "dist", + "LICENSE", + "README.md", + "README_zh.md" + ], + "engines": { + "node": ">=22" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepack": "npm --prefix ../.. run build:assets", + "prepublishOnly": "npm --prefix ../.. run typecheck" + }, + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "undici": "^7.27.2" + } +} diff --git a/src/main/cli.ts b/packages/cli/src/cli.ts similarity index 72% rename from src/main/cli.ts rename to packages/cli/src/cli.ts index 8964d4f..8a957de 100644 --- a/src/main/cli.ts +++ b/packages/cli/src/cli.ts @@ -1,18 +1,19 @@ #!/usr/bin/env node import { spawn, spawnSync } from "node:child_process"; +import { randomBytes } from "node:crypto"; import { accessSync, constants as fsConstants, existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs"; import path from "node:path"; -import { assertAvailableGatewayModels, type ProfileConfig, type ProfileOpenSurface } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { applyClaudeAppGatewayConfig } from "./claude-app-gateway-service"; -import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "./claude-app-launch"; -import { launchCodexAppProfile, launchZcodeAppProfile } from "./codex-app-launch"; -import { loadAppConfig } from "./config"; -import { CONFIGDIR } from "./constants"; -import { applyProfileConfig, applyProfileRuntimeConfig } from "./profile-service"; -import { ensureProfileGateway } from "./profile-launch-service"; -import { buildProfileLaunchPlan, defaultProfileOpenSurface, findProfileForOpen, profileLaunchSpawnCommand, resolveProfileOpenSurface } from "./profile-launch-core"; -import { startWebManagementServer } from "./web-management-server"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { applyClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; +import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "@ccr/core/agents/claude-app/launch"; +import { launchCodexAppProfile, launchZcodeAppProfile } from "@ccr/core/agents/codex/app-launch"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { CONFIGDIR } from "@ccr/core/config/constants"; +import { applyProfileConfig, applyProfileRuntimeConfig } from "@ccr/core/profiles/service"; +import { ensureProfileGateway } from "@ccr/core/profiles/launch-service"; +import { buildProfileLaunchPlan, defaultProfileOpenSurface, findProfileForOpen, profileLaunchSpawnCommand, resolveProfileOpenSurface } from "@ccr/core/profiles/launch-core"; +import { openSystemExternal, startWebManagementServer } from "@ccr/core/web/management-server"; +import { assertAvailableGatewayModels, type ProfileConfig, type ProfileOpenSurface } from "@ccr/core/contracts/app"; type ProfileCliOptions = { agentArgs: string[]; @@ -23,7 +24,7 @@ type ProfileCliOptions = { }; type WebCliOptions = { - command: "start" | "web"; + command: "start" | "ui" | "web"; daemonChild: boolean; help: boolean; host?: string; @@ -42,14 +43,19 @@ type CliOptions = ProfileCliOptions | StopCliOptions | WebCliOptions; type ServiceState = { host?: string; pid: number; + serviceToken?: string; startedAt: string; startGateway: boolean; url: string; }; const serviceStateFileName = "service.json"; +const serviceInstanceTokenEnv = "CCR_SERVICE_INSTANCE_TOKEN"; +const serviceRpcTimeoutMs = 2_000; const serviceStartTimeoutMs = 30_000; const serviceStopTimeoutMs = 10_000; +const webAuthHeader = "x-ccr-web-auth"; +const webAuthQueryParam = "ccr_web_token"; async function main(): Promise { const delegatedExitCode = delegateManagedDesktopCliToExternalCli(); @@ -67,6 +73,14 @@ async function main(): Promise { await startService(options); return; } + if (options.command === "ui") { + if (options.help) { + printUiHelp(0); + return; + } + await openManagementUi(options); + return; + } if (options.command === "stop") { if (options.help) { printStopHelp(0); @@ -175,6 +189,9 @@ function parseArgs(args: string[]): CliOptions { if (args[0] === "start") { return parseWebArgs(args.slice(1), "start"); } + if (args[0] === "ui") { + return parseWebArgs(args.slice(1), "ui", true); + } if (args[0] === "stop") { return parseStopArgs(args.slice(1)); } @@ -244,12 +261,12 @@ function parseStopArgs(args: string[]): StopCliOptions { return options; } -function parseWebArgs(args: string[], command: WebCliOptions["command"]): WebCliOptions { +function parseWebArgs(args: string[], command: WebCliOptions["command"], defaultOpen = false): WebCliOptions { const options: WebCliOptions = { command, daemonChild: false, help: false, - open: false, + open: defaultOpen, startGateway: true }; for (let index = 0; index < args.length; index += 1) { @@ -303,24 +320,31 @@ function parseWebArgs(args: string[], command: WebCliOptions["command"]): WebCli async function startService(options: WebCliOptions): Promise { const current = readServiceState(); - if (current && isProcessRunning(current.pid)) { + const currentVerification = current ? await verifyServiceState(current) : undefined; + if (current && currentVerification?.ok) { process.stdout.write(`CCR service is already running at ${current.url} (pid ${current.pid}).\n`); + if (options.open) { + await openManagementUrl(current.url); + } return; } - clearServiceState(); + if (current) { + clearServiceState(current.pid); + } + const serviceToken = generateServiceToken(); const childArgs = [ currentCliScript(), "serve", "--daemon-child", ...(options.host ? ["--host", options.host] : []), ...(options.port ? ["--port", String(options.port)] : []), - ...(options.open ? ["--open"] : ["--no-open"]), + "--no-open", ...(options.startGateway ? [] : ["--no-gateway"]) ]; const child = spawn(process.execPath, childArgs, { detached: true, - env: serviceChildEnv(), + env: serviceChildEnv(serviceToken), stdio: "ignore", windowsHide: true }); @@ -335,10 +359,31 @@ async function startService(options: WebCliOptions): Promise { throw new Error(`CCR service did not report ready within ${serviceStartTimeoutMs}ms.`); } process.stdout.write(`CCR service started at ${state.url} (pid ${state.pid}).\n`); + if (options.open) { + await openManagementUrl(state.url); + } } -function serviceChildEnv(): NodeJS.ProcessEnv { +async function openManagementUi(options: WebCliOptions): Promise { + await startService({ + ...options, + command: "start" + }); +} + +async function openManagementUrl(url: string): Promise { + try { + await openSystemExternal(url); + process.stdout.write(`Opened CCR management UI at ${url}\n`); + } catch (error) { + process.stderr.write(`Failed to open browser: ${formatError(error)}\n`); + process.stdout.write(`CCR management UI is available at ${url}\n`); + } +} + +function serviceChildEnv(serviceToken: string): NodeJS.ProcessEnv { const env = { ...process.env }; + env[serviceInstanceTokenEnv] = serviceToken; if (process.versions.electron) { env.ELECTRON_RUN_AS_NODE = "1"; } else { @@ -355,9 +400,11 @@ async function runWebServer(options: WebCliOptions): Promise { startGateway: options.startGateway }); if (options.daemonChild) { + const serviceToken = process.env[serviceInstanceTokenEnv]?.trim() || undefined; writeServiceState({ host: options.host, pid: process.pid, + ...(serviceToken ? { serviceToken } : {}), startedAt: new Date().toISOString(), startGateway: options.startGateway, url: runtime.url @@ -389,15 +436,19 @@ async function stopService(): Promise { process.stdout.write("CCR service is not running.\n"); return; } - if (!isProcessRunning(state.pid)) { + const verification = await verifyServiceState(state); + if (!verification.ok) { clearServiceState(state.pid); process.stdout.write("CCR service is not running.\n"); return; } - process.kill(state.pid, "SIGTERM"); - const stopped = await waitForProcessExit(state.pid, serviceStopTimeoutMs); - if (!stopped && isProcessRunning(state.pid)) { - throw new Error(`CCR service pid ${state.pid} did not stop within ${serviceStopTimeoutMs}ms.`); + + await callServiceRpc(state, "quitApp"); + const stopped = verification.trustedPid + ? await waitForProcessExit(state.pid, serviceStopTimeoutMs) + : await waitForServiceUnavailable(state, serviceStopTimeoutMs); + if (!stopped) { + throw new Error(`CCR service did not stop within ${serviceStopTimeoutMs}ms.`); } clearServiceState(state.pid); process.stdout.write("CCR service stopped.\n"); @@ -407,11 +458,13 @@ function printHelp(exitCode: number): void { const output = [ "Usage:", " ccr start [--host ] [--port ] [--open] [--no-gateway]", + " ccr ui [--host ] [--port ] [--no-gateway]", " ccr stop", " ccr [cli|app] [-- ]", "", "Examples:", " ccr start", + " ccr ui", " ccr stop", " ccr Codex", " ccr default-codex -- --model gpt-5-codex", @@ -432,7 +485,31 @@ function printStartHelp(exitCode: number): void { " --port Management server port. Defaults to 3458.", " --open Open the management page in the default browser.", " --no-open Do not open the management page.", - " --no-gateway Start only the web management server." + " --no-gateway Start only the web management server.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." + ].join("\n"); + const stream = exitCode === 0 ? process.stdout : process.stderr; + stream.write(`${output}\n`); + process.exitCode = exitCode; +} + +function printUiHelp(exitCode: number): void { + const output = [ + "Usage:", + " ccr ui [--host ] [--port ] [--no-gateway]", + "", + "Starts the background CCR service if needed and opens the management UI in the default browser.", + "", + "Options:", + " --host Management server host. Defaults to 127.0.0.1.", + " --port Management server port. Defaults to 3458.", + " --no-open Start or find the service and print the management URL without opening a browser.", + " --no-gateway Start only the web management server when the service is not already running.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." ].join("\n"); const stream = exitCode === 0 ? process.stdout : process.stderr; stream.write(`${output}\n`); @@ -460,7 +537,10 @@ function printWebHelp(exitCode: number): void { " --host Management server host. Defaults to 127.0.0.1.", " --port Management server port. Defaults to 3458.", " --open Open the management page in the default browser.", - " --no-gateway Start only the web management server." + " --no-gateway Start only the web management server.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." ].join("\n"); const stream = exitCode === 0 ? process.stdout : process.stderr; stream.write(`${output}\n`); @@ -481,6 +561,7 @@ function readServiceState(): ServiceState | undefined { return { host: parsed.host, pid, + serviceToken: typeof parsed.serviceToken === "string" && parsed.serviceToken.trim() ? parsed.serviceToken.trim() : undefined, startedAt: parsed.startedAt || "", startGateway: parsed.startGateway !== false, url: parsed.url @@ -608,7 +689,7 @@ async function waitForServiceState(pid: number | undefined, timeoutMs: number): const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { const state = readServiceState(); - if (state && (!pid || state.pid === pid) && isProcessRunning(state.pid)) { + if (state && (!pid || state.pid === pid) && (await verifyServiceState(state)).ok) { return state; } await delay(150); @@ -640,6 +721,98 @@ function isProcessRunning(pid: number | undefined): boolean { } } +type ServiceStateVerification = + | { ok: true; trustedPid: boolean } + | { ok: false }; + +type ServiceIdentity = { + pid?: unknown; + serviceTokenConfigured?: unknown; + serviceTokenMatches?: unknown; +}; + +async function verifyServiceState(state: ServiceState): Promise { + if (!isProcessRunning(state.pid)) { + return { ok: false }; + } + + if (state.serviceToken) { + const identity = await callServiceRpc(state, "getServiceIdentity", [state.serviceToken]).catch(() => undefined); + if (identity?.serviceTokenMatches === true && Number(identity.pid) === state.pid) { + return { ok: true, trustedPid: true }; + } + return { ok: false }; + } + + const appInfo = await callServiceRpc<{ name?: unknown }>(state, "getAppInfo").catch(() => undefined); + return appInfo?.name === "Claude Code Router" + ? { ok: true, trustedPid: false } + : { ok: false }; +} + +async function waitForServiceUnavailable(state: ServiceState, timeoutMs: number): Promise { + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + const appInfo = await callServiceRpc<{ name?: unknown }>(state, "getAppInfo").catch(() => undefined); + if (appInfo?.name !== "Claude Code Router") { + return true; + } + await delay(150); + } + const appInfo = await callServiceRpc<{ name?: unknown }>(state, "getAppInfo").catch(() => undefined); + return appInfo?.name !== "Claude Code Router"; +} + +async function callServiceRpc(state: ServiceState, method: string, args: unknown[] = []): Promise { + const endpoint = serviceRpcEndpoint(state.url); + const authToken = serviceAuthToken(state.url); + if (!endpoint || !authToken) { + throw new Error("CCR service state does not include a usable management URL."); + } + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), serviceRpcTimeoutMs); + try { + const response = await fetch(endpoint, { + body: JSON.stringify({ args, method }), + headers: { + "content-type": "application/json", + [webAuthHeader]: authToken + }, + method: "POST", + signal: controller.signal + }); + const payload = await response.json().catch(() => undefined) as { ok?: boolean; value?: T } | undefined; + if (!response.ok || !payload?.ok) { + throw new Error(`CCR service RPC ${method} failed with HTTP ${response.status}`); + } + return payload.value as T; + } finally { + clearTimeout(timer); + } +} + +function serviceRpcEndpoint(url: string): string | undefined { + try { + const parsed = new URL(url); + return `${parsed.origin}/api/ccr/rpc`; + } catch { + return undefined; + } +} + +function serviceAuthToken(url: string): string { + try { + return new URL(url).searchParams.get(webAuthQueryParam)?.trim() ?? ""; + } catch { + return ""; + } +} + +function generateServiceToken(): string { + return randomBytes(32).toString("base64url"); +} + function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..1f70006 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/models.json b/packages/core/models.json similarity index 100% rename from models.json rename to packages/core/models.json diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..a063ef1 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,21 @@ +{ + "name": "@claude-code-router/core", + "version": "3.0.7", + "private": true, + "description": "Claude Code Router core gateway, routing, provider, and storage services.", + "main": "dist/main/server.js", + "bin": { + "ccr-core-server": "dist/main/server.js" + }, + "engines": { + "node": ">=22" + }, + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "pm2": "^6.0.13", + "undici": "^7.27.2" + } +} diff --git a/src/main/bot-gateway-env.ts b/packages/core/src/agents/bot-gateway/env.ts similarity index 98% rename from src/main/bot-gateway-env.ts rename to packages/core/src/agents/bot-gateway/env.ts index 524945b..afc2041 100644 --- a/src/main/bot-gateway-env.ts +++ b/packages/core/src/agents/bot-gateway/env.ts @@ -1,8 +1,8 @@ import os from "node:os"; import { createRequire } from "node:module"; import path from "node:path"; -import type { AppConfig, BotGatewayRuntimeConfig, ProfileConfig, ProfileOpenSurface } from "../shared/app"; -import { CONFIGDIR } from "./constants"; +import type { AppConfig, BotGatewayRuntimeConfig, ProfileConfig, ProfileOpenSurface } from "@ccr/core/contracts/app"; +import { CONFIGDIR } from "@ccr/core/config/constants"; const requireFromHere = createRequire(__filename); diff --git a/src/main/bot-handoff-scan-service.ts b/packages/core/src/agents/bot-gateway/handoff-scan-service.ts similarity index 98% rename from src/main/bot-handoff-scan-service.ts rename to packages/core/src/agents/bot-gateway/handoff-scan-service.ts index 8522eb0..59712eb 100644 --- a/src/main/bot-handoff-scan-service.ts +++ b/packages/core/src/agents/bot-gateway/handoff-scan-service.ts @@ -1,7 +1,7 @@ import { execFile } from "node:child_process"; import { promisify } from "node:util"; -import type { BotHandoffScanTarget } from "../shared/app"; -import { windowsSystemCommand } from "./windows-system"; +import type { BotHandoffScanTarget } from "@ccr/core/contracts/app"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; const execFileAsync = promisify(execFile); diff --git a/src/main/bot-gateway-qr-login-service.ts b/packages/core/src/agents/bot-gateway/qr-login-service.ts similarity index 99% rename from src/main/bot-gateway-qr-login-service.ts rename to packages/core/src/agents/bot-gateway/qr-login-service.ts index b44edca..4f60b45 100644 --- a/src/main/bot-gateway-qr-login-service.ts +++ b/packages/core/src/agents/bot-gateway/qr-login-service.ts @@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { CONFIGDIR } from "./constants"; +import { CONFIGDIR } from "@ccr/core/config/constants"; import type { BotGatewayQrLoginCancelRequest, BotGatewayQrLoginCancelResult, @@ -11,7 +11,7 @@ import type { BotGatewayQrLoginWaitRequest, BotGatewayQrLoginWaitResult, BotGatewayRuntimeConfig -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type BotGatewayClientWithRequest = { close?: () => Promise | void; diff --git a/src/main/claude-app-cdp.ts b/packages/core/src/agents/claude-app/cdp.ts similarity index 100% rename from src/main/claude-app-cdp.ts rename to packages/core/src/agents/claude-app/cdp.ts diff --git a/src/shared/claude-app-gateway.ts b/packages/core/src/agents/claude-app/gateway-routes.ts similarity index 98% rename from src/shared/claude-app-gateway.ts rename to packages/core/src/agents/claude-app/gateway-routes.ts index c4127f7..3ccda7e 100644 --- a/src/shared/claude-app-gateway.ts +++ b/packages/core/src/agents/claude-app/gateway-routes.ts @@ -1,5 +1,5 @@ -import type { AppConfig } from "./app"; -import { normalizeProfileScopeValue } from "./app"; +import type { AppConfig } from "@ccr/core/contracts/app"; +import { normalizeProfileScopeValue } from "@ccr/core/contracts/app"; export const CLAUDE_APP_FALLBACK_MODEL = "claude-sonnet-4-5"; export const CLAUDE_APP_ONE_MILLION_CONTEXT_SUFFIX = "[1m]"; diff --git a/src/main/claude-app-gateway-service.ts b/packages/core/src/agents/claude-app/gateway-service.ts similarity index 97% rename from src/main/claude-app-gateway-service.ts rename to packages/core/src/agents/claude-app/gateway-service.ts index 89d1e35..58c7eff 100644 --- a/src/main/claude-app-gateway-service.ts +++ b/packages/core/src/agents/claude-app/gateway-service.ts @@ -2,16 +2,16 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } import os from "node:os"; import path from "node:path"; import { createHash, randomBytes, randomUUID } from "node:crypto"; -import { resolveRuntimeAppPath } from "./app-paths"; -import { saveAppConfig } from "./config"; -import { CONFIGDIR } from "./constants"; +import { resolveRuntimeAppPath } from "@ccr/core/runtime/app-paths"; +import { saveAppConfig } from "@ccr/core/config/config"; +import { CONFIGDIR } from "@ccr/core/config/constants"; import { buildClaudeAppGatewayInferenceModels, type ClaudeAppGatewayInferenceModel, type ClaudeAppGatewayModelRouteOptions -} from "../shared/claude-app-gateway"; -import { NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ClaudeAppGatewayApplyResult } from "../shared/app"; -import { findModelCatalogEntry } from "../server/gateway/model-catalog"; +} from "@ccr/core/agents/claude-app/gateway-routes"; +import { NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ClaudeAppGatewayApplyResult } from "@ccr/core/contracts/app"; +import { findModelCatalogEntry } from "@ccr/core/gateway/model-catalog"; const CLAUDE_APP_CONFIG_ID = "8f69f2f1-3275-4ad8-9317-4aa7e972f311"; const CLAUDE_APP_CONFIG_NAME = "Claude Code Router"; diff --git a/src/main/claude-app-launch.ts b/packages/core/src/agents/claude-app/launch.ts similarity index 96% rename from src/main/claude-app-launch.ts rename to packages/core/src/agents/claude-app/launch.ts index f4f6ce9..cdfa57d 100644 --- a/src/main/claude-app-launch.ts +++ b/packages/core/src/agents/claude-app/launch.ts @@ -2,12 +2,12 @@ import { spawn, type ChildProcess } from "node:child_process"; import { mkdirSync, readdirSync, readFileSync, statSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { AppConfig, ProfileConfig } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { prepareClaudeAppCdpUserDataDir, reserveClaudeAppCdpPort, scheduleClaudeAppDesignCdp } from "./claude-app-cdp"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { resolveClaudeCodeSettingsFile } from "./profile-launch-core"; -import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "./windows-app-discovery"; +import type { AppConfig, ProfileConfig } from "@ccr/core/contracts/app"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { prepareClaudeAppCdpUserDataDir, reserveClaudeAppCdpPort, scheduleClaudeAppDesignCdp } from "@ccr/core/agents/claude-app/cdp"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { resolveClaudeCodeSettingsFile } from "@ccr/core/profiles/launch-core"; +import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "@ccr/core/platform/windows-app-discovery"; type ClaudeAppLookupResult = { checked: string[]; diff --git a/src/main/claude-environment.ts b/packages/core/src/agents/claude-code/environment.ts similarity index 100% rename from src/main/claude-environment.ts rename to packages/core/src/agents/claude-code/environment.ts diff --git a/src/main/codex-app-launch.ts b/packages/core/src/agents/codex/app-launch.ts similarity index 98% rename from src/main/codex-app-launch.ts rename to packages/core/src/agents/codex/app-launch.ts index 2a70b69..566770d 100644 --- a/src/main/codex-app-launch.ts +++ b/packages/core/src/agents/codex/app-launch.ts @@ -2,12 +2,12 @@ import { spawn, type ChildProcess } from "node:child_process"; import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { AppConfig, ProfileConfig } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { codexModelCatalogJson } from "./codex-model-catalog"; -import { buildProfileLaunchPlan, resolveCodexConfigFile } from "./profile-launch-core"; -import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "./windows-app-discovery"; -import { writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "./zcode-profile-config"; +import type { AppConfig, ProfileConfig } from "@ccr/core/contracts/app"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { codexModelCatalogJson } from "@ccr/core/agents/codex/model-catalog"; +import { buildProfileLaunchPlan, resolveCodexConfigFile } from "@ccr/core/profiles/launch-core"; +import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "@ccr/core/platform/windows-app-discovery"; +import { writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "@ccr/core/agents/zcode/profile-config"; type CodexAppLookupResult = { checked: string[]; diff --git a/src/main/codex-cli-middleware-runtime.ts b/packages/core/src/agents/codex/cli-middleware-runtime.ts similarity index 100% rename from src/main/codex-cli-middleware-runtime.ts rename to packages/core/src/agents/codex/cli-middleware-runtime.ts diff --git a/src/main/codex-model-catalog.ts b/packages/core/src/agents/codex/model-catalog.ts similarity index 99% rename from src/main/codex-model-catalog.ts rename to packages/core/src/agents/codex/model-catalog.ts index 07ba04d..dd51f76 100644 --- a/src/main/codex-model-catalog.ts +++ b/packages/core/src/agents/codex/model-catalog.ts @@ -1,11 +1,11 @@ -import { BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME } from "../shared/app"; -import type { AppConfig, GatewayProviderConfig, GatewayProviderProtocol, VirtualModelProfileConfig } from "../shared/app"; +import { BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME } from "@ccr/core/contracts/app"; +import type { AppConfig, GatewayProviderConfig, GatewayProviderProtocol, VirtualModelProfileConfig } from "@ccr/core/contracts/app"; import { findModelCatalogEntry, modelCatalogMaxInputTokens, readCatalogCapability, type ModelCatalogEntry -} from "../server/gateway/model-catalog"; +} from "@ccr/core/gateway/model-catalog"; const fusionModelProviderName = "Fusion"; const codexDefaultContextWindow = 128_000; diff --git a/src/main/local-agent-providers/claude-code.ts b/packages/core/src/agents/local-providers/claude-code.ts similarity index 98% rename from src/main/local-agent-providers/claude-code.ts rename to packages/core/src/agents/local-providers/claude-code.ts index ae1ad43..37c05ff 100644 --- a/src/main/local-agent-providers/claude-code.ts +++ b/packages/core/src/agents/local-providers/claude-code.ts @@ -5,7 +5,7 @@ import type { LocalAgentProviderImportResult, ProviderAccountConfig, ProviderAccountMappingConfig -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; import { bearerAuthPlugin, findOauthTokenSet, @@ -16,7 +16,7 @@ import { uniqueProviderName, uniqueStrings, type OAuthTokenSet -} from "./shared"; +} from "@ccr/core/agents/local-providers/shared"; const claudeDefaultModels = ["claude-sonnet-4-20250514"]; diff --git a/src/main/local-agent-providers/codex.ts b/packages/core/src/agents/local-providers/codex.ts similarity index 99% rename from src/main/local-agent-providers/codex.ts rename to packages/core/src/agents/local-providers/codex.ts index eb50bbc..2a16a11 100644 --- a/src/main/local-agent-providers/codex.ts +++ b/packages/core/src/agents/local-providers/codex.ts @@ -9,8 +9,8 @@ import type { ProviderAccountMappingConfig, ProviderAccountMeter, ProviderAccountMeterDetail -} from "../../shared/app"; -import { normalizeProviderBaseUrl } from "../../shared/provider-url"; +} from "@ccr/core/contracts/app"; +import { normalizeProviderBaseUrl } from "@ccr/core/providers/url"; import { isRecord, localAgentProviderApiKey, @@ -26,7 +26,7 @@ import { uniqueProviderName, uniqueStrings, type OAuthTokenSet -} from "./shared"; +} from "@ccr/core/agents/local-providers/shared"; export const codexDefaultBaseUrl = "https://chatgpt.com/backend-api/codex"; diff --git a/src/main/local-agent-provider-service.ts b/packages/core/src/agents/local-providers/service.ts similarity index 66% rename from src/main/local-agent-provider-service.ts rename to packages/core/src/agents/local-providers/service.ts index 8ef2326..d86415f 100644 --- a/src/main/local-agent-provider-service.ts +++ b/packages/core/src/agents/local-providers/service.ts @@ -2,13 +2,13 @@ import type { LocalAgentProviderCandidate, LocalAgentProviderImportRequest, LocalAgentProviderImportResult -} from "../shared/app"; -import { claudeCodeCandidate, importClaudeCodeProvider } from "./local-agent-providers/claude-code"; -import { codexCandidate, importCodexProvider } from "./local-agent-providers/codex"; -import { importZcodeProvider, zcodeCandidate } from "./local-agent-providers/zcode"; +} from "@ccr/core/contracts/app"; +import { claudeCodeCandidate, importClaudeCodeProvider } from "@ccr/core/agents/local-providers/claude-code"; +import { codexCandidate, importCodexProvider } from "@ccr/core/agents/local-providers/codex"; +import { importZcodeProvider, zcodeCandidate } from "@ccr/core/agents/local-providers/zcode"; -export { codexDefaultBaseUrl, readCodexAuth } from "./local-agent-providers/codex"; -export { localAgentProviderApiKey, type OAuthTokenSet } from "./local-agent-providers/shared"; +export { codexDefaultBaseUrl, readCodexAuth } from "@ccr/core/agents/local-providers/codex"; +export { localAgentProviderApiKey, type OAuthTokenSet } from "@ccr/core/agents/local-providers/shared"; export function getLocalAgentProviderCandidates(): LocalAgentProviderCandidate[] { return [ diff --git a/src/main/local-agent-providers/shared.ts b/packages/core/src/agents/local-providers/shared.ts similarity index 99% rename from src/main/local-agent-providers/shared.ts rename to packages/core/src/agents/local-providers/shared.ts index 34f952e..f70c238 100644 --- a/src/main/local-agent-providers/shared.ts +++ b/packages/core/src/agents/local-providers/shared.ts @@ -5,7 +5,7 @@ import type { LocalAgentProviderKind, ProviderAccountConfig, ProviderDeepLinkPayload -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; export type OAuthTokenSet = { accountId?: string; diff --git a/src/main/local-agent-providers/zcode.ts b/packages/core/src/agents/local-providers/zcode.ts similarity index 98% rename from src/main/local-agent-providers/zcode.ts rename to packages/core/src/agents/local-providers/zcode.ts index 247f754..f32f3c0 100644 --- a/src/main/local-agent-providers/zcode.ts +++ b/packages/core/src/agents/local-providers/zcode.ts @@ -4,8 +4,8 @@ import type { LocalAgentProviderCandidate, LocalAgentProviderImportResult, ProviderAccountConfig -} from "../../shared/app"; -import { findProviderPresetByBaseUrl } from "../presets"; +} from "@ccr/core/contracts/app"; +import { findProviderPresetByBaseUrl } from "@ccr/core/providers/presets/index"; import { apiKeyAuthPlugin, cloneProviderAccountConfig, @@ -21,7 +21,7 @@ import { uniqueProviderName, uniqueStrings, type ApiTokenSet -} from "./shared"; +} from "@ccr/core/agents/local-providers/shared"; type ZcodeConfiguredProvider = { apiKey: string; diff --git a/src/main/zcode-profile-config.ts b/packages/core/src/agents/zcode/profile-config.ts similarity index 98% rename from src/main/zcode-profile-config.ts rename to packages/core/src/agents/zcode/profile-config.ts index 9e62c65..c8ebb35 100644 --- a/src/main/zcode-profile-config.ts +++ b/packages/core/src/agents/zcode/profile-config.ts @@ -1,9 +1,9 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { AppConfig, ProfileConfig } from "../shared/app"; -import { normalizeRouteSelector } from "../server/gateway/claude-code-router-plugin"; -import { buildCodexModelCatalogIds } from "./codex-model-catalog"; +import type { AppConfig, ProfileConfig } from "@ccr/core/contracts/app"; +import { normalizeRouteSelector } from "@ccr/core/gateway/claude-code-router-plugin"; +import { buildCodexModelCatalogIds } from "@ccr/core/agents/codex/model-catalog"; export type ZcodeProfileConfigWriteResult = { backupFile?: string; diff --git a/src/main/api-key-store.ts b/packages/core/src/config/api-key-store.ts similarity index 97% rename from src/main/api-key-store.ts rename to packages/core/src/config/api-key-store.ts index d60bf0b..76fca9c 100644 --- a/src/main/api-key-store.ts +++ b/packages/core/src/config/api-key-store.ts @@ -1,8 +1,8 @@ import { chmodSync, existsSync, mkdirSync } from "node:fs"; import { dirname } from "node:path"; -import { API_KEYS_DB_FILE, LEGACY_API_KEYS_DB_FILES } from "./constants"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; -import type { ApiKeyConfig, ApiKeyLimitConfig } from "../shared/app"; +import { API_KEYS_DB_FILE, LEGACY_API_KEYS_DB_FILES } from "@ccr/core/config/constants"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; +import type { ApiKeyConfig, ApiKeyLimitConfig } from "@ccr/core/contracts/app"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; diff --git a/src/main/app-config-store.ts b/packages/core/src/config/app-config-store.ts similarity index 98% rename from src/main/app-config-store.ts rename to packages/core/src/config/app-config-store.ts index 837b9cc..13e1209 100644 --- a/src/main/app-config-store.ts +++ b/packages/core/src/config/app-config-store.ts @@ -1,7 +1,7 @@ import { chmodSync, existsSync, mkdirSync } from "node:fs"; import { dirname } from "node:path"; -import { APP_CONFIG_DB_FILE, LEGACY_APP_CONFIG_DB_FILES } from "./constants"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; +import { APP_CONFIG_DB_FILE, LEGACY_APP_CONFIG_DB_FILES } from "@ccr/core/config/constants"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; diff --git a/src/main/config.ts b/packages/core/src/config/config.ts similarity index 99% rename from src/main/config.ts rename to packages/core/src/config/config.ts index ef2702a..82666fd 100644 --- a/src/main/config.ts +++ b/packages/core/src/config/config.ts @@ -1,12 +1,12 @@ import { createHash, randomBytes } from "node:crypto"; import { existsSync, readFileSync } from "node:fs"; -import { loadPersistedAppConfig, replacePersistedAppConfig } from "./app-config-store"; -import { loadPersistedApiKeys, replacePersistedApiKeys } from "./api-key-store"; -import { CONFIG_FILE, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, LEGACY_WINDOWS_CONFIG_FILE } from "./constants"; -import { normalizeCodexProviderAccountConfig } from "./local-agent-providers/codex"; -import { CLAUDE_CODE_DEFAULT_ENV, CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, DEFAULT_OVERVIEW_WIDGETS, DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, OVERVIEW_WIDGET_SIZE_VALUES, ROUTER_FALLBACK_MAX_RETRY_COUNT, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS, enforceSingleEnabledGlobalProfilePerAgent } from "../shared/app"; -import { createDefaultAppConfig } from "../shared/default-config"; -import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue, providerEndpointCanReceiveProviderApiKey } from "./presets"; +import { loadPersistedAppConfig, replacePersistedAppConfig } from "@ccr/core/config/app-config-store"; +import { loadPersistedApiKeys, replacePersistedApiKeys } from "@ccr/core/config/api-key-store"; +import { CONFIG_FILE, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, LEGACY_WINDOWS_CONFIG_FILE } from "@ccr/core/config/constants"; +import { normalizeCodexProviderAccountConfig } from "@ccr/core/agents/local-providers/codex"; +import { CLAUDE_CODE_DEFAULT_ENV, CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, DEFAULT_OVERVIEW_WIDGETS, DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, OVERVIEW_WIDGET_SIZE_VALUES, ROUTER_FALLBACK_MAX_RETRY_COUNT, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS, enforceSingleEnabledGlobalProfilePerAgent } from "@ccr/core/contracts/app"; +import { createDefaultAppConfig } from "@ccr/core/config/default-config"; +import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue, providerEndpointCanReceiveProviderApiKey } from "@ccr/core/providers/presets/index"; import type { AppConfig, ApiKeyConfig, @@ -54,7 +54,7 @@ import type { TrayWidgetType, TrayWidgetVariant, TrayWindowModuleId -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type LoadedProfileConfig = Partial> & { claudeCode?: Partial; diff --git a/src/main/constants.ts b/packages/core/src/config/constants.ts similarity index 82% rename from src/main/constants.ts rename to packages/core/src/config/constants.ts index d97409b..b283061 100644 --- a/src/main/constants.ts +++ b/packages/core/src/config/constants.ts @@ -1,20 +1,18 @@ import path from "node:path"; -import { APP_NAME, APP_STORAGE_NAME, LEGACY_CONFIGDIR, resolveRuntimeAppPath } from "./app-paths"; -import { copyMissingDirectoryContents } from "./storage-migration"; +import { APP_NAME, APP_STORAGE_NAME, LEGACY_CONFIGDIR, resolveRuntimeAppPath, resolveRuntimeConfigDir, resolveRuntimeDataDir } from "@ccr/core/runtime/app-paths"; +import { copyMissingDirectoryContents } from "@ccr/core/storage/migration"; -export { IPC_CHANNELS } from "../shared/ipc-channels"; +export { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; export const LEGACY_CONFIG_FILE = path.join(LEGACY_CONFIGDIR, "config.json"); export { APP_NAME, APP_STORAGE_NAME, LEGACY_CONFIGDIR }; -export const CONFIGDIR = process.platform === "win32" - ? path.join(resolveRuntimeAppPath("appData"), APP_STORAGE_NAME) - : LEGACY_CONFIGDIR; +export const CONFIGDIR = resolveRuntimeConfigDir(); export const LEGACY_WINDOWS_CONFIGDIR = path.join(resolveRuntimeAppPath("appData"), APP_NAME); export const LEGACY_WINDOWS_CONFIG_FILE = path.join(LEGACY_WINDOWS_CONFIGDIR, "config.json"); export const CONFIG_FILE = path.join(CONFIGDIR, "config.json"); export const ONBOARDING_FINISHED_FILE = path.join(CONFIGDIR, ".onboard_finished"); -export const DATADIR = resolveRuntimeAppPath("userData"); +export const DATADIR = resolveRuntimeDataDir(); export const APP_CONFIG_DB_FILE = path.join(CONFIGDIR, "config.sqlite"); export const API_KEYS_DB_FILE = path.join(DATADIR, "api-keys.sqlite"); export const LEGACY_APP_CONFIG_DB_FILES = process.platform === "win32" ? [path.join(LEGACY_WINDOWS_CONFIGDIR, "config.sqlite")] : []; diff --git a/src/shared/default-config.ts b/packages/core/src/config/default-config.ts similarity index 99% rename from src/shared/default-config.ts rename to packages/core/src/config/default-config.ts index c7953cd..61b81d8 100644 --- a/src/shared/default-config.ts +++ b/packages/core/src/config/default-config.ts @@ -6,7 +6,7 @@ import { DEFAULT_TRAY_WINDOW_MODULES, type AppConfig, type ProxyRouteTarget -} from "./app"; +} from "@ccr/core/contracts/app"; export const DEFAULT_PROXY_TARGETS: ProxyRouteTarget[] = [ { host: "api.anthropic.com", paths: ["/v1/messages", "/v1/messages/count_tokens"] }, diff --git a/src/shared/app.ts b/packages/core/src/contracts/app.ts similarity index 100% rename from src/shared/app.ts rename to packages/core/src/contracts/app.ts diff --git a/src/shared/deep-link.ts b/packages/core/src/contracts/deep-link.ts similarity index 99% rename from src/shared/deep-link.ts rename to packages/core/src/contracts/deep-link.ts index 5b9999e..b8bb98a 100644 --- a/src/shared/deep-link.ts +++ b/packages/core/src/contracts/deep-link.ts @@ -6,8 +6,8 @@ import type { ProviderDeepLinkPayload, ProviderDeepLinkRequest, ProviderManifestDeepLinkPayload -} from "./app"; -import { providerUrlWithDefaultScheme } from "./provider-url"; +} from "@ccr/core/contracts/app"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; export const appDeepLinkProtocol = "ccr"; export const providerDeepLinkHost = "provider"; diff --git a/src/shared/i18n.ts b/packages/core/src/contracts/i18n.ts similarity index 100% rename from src/shared/i18n.ts rename to packages/core/src/contracts/i18n.ts diff --git a/src/shared/ipc-channels.ts b/packages/core/src/contracts/ipc-channels.ts similarity index 100% rename from src/shared/ipc-channels.ts rename to packages/core/src/contracts/ipc-channels.ts diff --git a/packages/core/src/entrypoints/server.ts b/packages/core/src/entrypoints/server.ts new file mode 100644 index 0000000..2059d8a --- /dev/null +++ b/packages/core/src/entrypoints/server.ts @@ -0,0 +1,129 @@ +#!/usr/bin/env node +import { startWebManagementServer } from "@ccr/core/web/management-server"; + +type CoreServerOptions = { + help: boolean; + host?: string; + open: boolean; + port?: number; + startGateway: boolean; +}; + +export async function runCoreServer(args = process.argv.slice(2)): Promise { + const options = parseCoreServerArgs(args); + if (options.help) { + printHelp(); + return; + } + + const runtime = await startWebManagementServer({ + host: options.host, + open: options.open, + port: options.port, + startGateway: options.startGateway + }); + process.stdout.write(`CCR core server is running at ${runtime.url}\n`); + + let closing = false; + const shutdown = (signal: NodeJS.Signals) => { + if (closing) { + return; + } + closing = true; + void runtime.close().finally(() => { + process.exit(signal === "SIGINT" ? 130 : 143); + }); + }; + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); + await new Promise(() => undefined); +} + +function parseCoreServerArgs(args: string[]): CoreServerOptions { + const options: CoreServerOptions = { + help: false, + open: false, + startGateway: true + }; + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === "--help" || arg === "-h") { + options.help = true; + continue; + } + if (arg === "--open") { + options.open = true; + continue; + } + if (arg === "--no-open") { + options.open = false; + continue; + } + if (arg === "--gateway") { + options.startGateway = true; + continue; + } + if (arg === "--no-gateway") { + options.startGateway = false; + continue; + } + if (arg === "--host") { + index += 1; + options.host = requiredArg(args[index], "--host"); + continue; + } + if (arg.startsWith("--host=")) { + options.host = requiredArg(arg.slice("--host=".length), "--host"); + continue; + } + if (arg === "--port") { + index += 1; + options.port = parsePort(requiredArg(args[index], "--port")); + continue; + } + if (arg.startsWith("--port=")) { + options.port = parsePort(requiredArg(arg.slice("--port=".length), "--port")); + continue; + } + throw new Error(`Unknown core server option: ${arg}`); + } + + return options; +} + +function printHelp(): void { + process.stdout.write([ + "Usage:", + " ccr-core-server [--host ] [--port ] [--no-gateway]", + "", + "Options:", + " --host Management server host. Defaults to CCR_WEB_HOST or 127.0.0.1.", + " --port Management server port. Defaults to CCR_WEB_PORT or 3458.", + " --no-gateway Start only the web management server.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." + ].join("\n") + "\n"); +} + +function requiredArg(value: string | undefined, flag: string): string { + if (!value) { + throw new Error(`Missing value for ${flag}`); + } + return value; +} + +function parsePort(value: string): number { + const port = Number(value); + if (!Number.isInteger(port) || port <= 0 || port > 65535) { + throw new Error(`Invalid port: ${value}`); + } + return port; +} + +runCoreServer().catch((error) => { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`${message}\n`); + process.exitCode = 1; +}); diff --git a/src/server/gateway/claude-code-router-plugin.ts b/packages/core/src/gateway/claude-code-router-plugin.ts similarity index 99% rename from src/server/gateway/claude-code-router-plugin.ts rename to packages/core/src/gateway/claude-code-router-plugin.ts index 18d6d51..765d9cd 100644 --- a/src/server/gateway/claude-code-router-plugin.ts +++ b/packages/core/src/gateway/claude-code-router-plugin.ts @@ -2,8 +2,8 @@ import { createRequire } from "node:module"; import { EventEmitter } from "node:events"; import os from "node:os"; import path from "node:path"; -import { availableGatewayModelIds, type AppConfig, type RouterBuiltInAgentRuleId, type RouterConfig, type RouterFallbackConfig, type RouterRule, type RouterRuleCondition, type RouterRuleRewrite } from "../../shared/app"; -import { CONFIGDIR } from "../../main/constants"; +import { availableGatewayModelIds, type AppConfig, type RouterBuiltInAgentRuleId, type RouterConfig, type RouterFallbackConfig, type RouterRule, type RouterRuleCondition, type RouterRuleRewrite } from "@ccr/core/contracts/app"; +import { CONFIGDIR } from "@ccr/core/config/constants"; type HeaderValue = string | string[] | undefined; diff --git a/src/server/gateway/model-catalog.ts b/packages/core/src/gateway/model-catalog.ts similarity index 99% rename from src/server/gateway/model-catalog.ts rename to packages/core/src/gateway/model-catalog.ts index 5220e95..b32e2e6 100644 --- a/src/server/gateway/model-catalog.ts +++ b/packages/core/src/gateway/model-catalog.ts @@ -1,4 +1,4 @@ -import { loadModelCatalogPayload } from "../../main/model-catalog-file"; +import { loadModelCatalogPayload } from "@ccr/core/models/catalog-file"; const claudeCodeDefaultContextTokens = 200_000; let modelCatalogIndex: ModelCatalogIndex | undefined; diff --git a/src/server/gateway/remote-control-service.ts b/packages/core/src/gateway/remote-control-service.ts similarity index 100% rename from src/server/gateway/remote-control-service.ts rename to packages/core/src/gateway/remote-control-service.ts diff --git a/src/server/gateway/service.ts b/packages/core/src/gateway/service.ts similarity index 99% rename from src/server/gateway/service.ts rename to packages/core/src/gateway/service.ts index 1680d7f..9ef07fb 100644 --- a/src/server/gateway/service.ts +++ b/packages/core/src/gateway/service.ts @@ -22,35 +22,35 @@ import type { VirtualModelFusionVisionConfig, VirtualModelFusionWebSearchConfig, VirtualModelFusionWebSearchProvider -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; import { CLAUDE_APP_FALLBACK_MODEL, buildClaudeAppGatewayModelRoutes, inferClaudeAppGatewayTargetModel, resolveClaudeAppGatewayRouteModel, type ClaudeAppGatewayModelRouteOptions -} from "../../shared/claude-app-gateway"; +} from "@ccr/core/agents/claude-app/gateway-routes"; import { BUILTIN_FUSION_VISION_TOOL_NAME, BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME, NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, ROUTER_FALLBACK_MAX_RETRY_COUNT, hasAvailableGatewayModels -} from "../../shared/app"; -import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue } from "../../main/presets"; -import { normalizeProviderBaseUrl as normalizeProviderBaseUrlInput } from "../../shared/provider-url"; -import { backendService } from "../backend-service"; -import { RAW_TRACE_SPOOL_DIR } from "../../main/constants"; -import { loadPersistedApiKeys } from "../../main/api-key-store"; -import { codexDefaultBaseUrl, readCodexAuth } from "../../main/local-agent-provider-service"; -import { fetchWithSystemProxy, getSystemProxyUrlForProtocol } from "../../main/system-proxy-fetch"; -import { handleNetworkCaptureMcpRequest, isNetworkCaptureMcpPath } from "../mcp/network-capture-mcp"; -import { pluginService } from "../../main/plugins/service"; -import { proxyService } from "../proxy/service"; -import { createSseErrorDetector, recordGatewayRequestLog, updateGatewayRequestLogFromRawTrace, type RequestLogRawTraceUpdateInput } from "../../main/request-log-store"; -import { recordGatewayUsageCapture } from "../../main/usage-store"; -import { ClaudeCodeRouterPlugin, normalizeRouteSelector } from "./claude-code-router-plugin"; -import { ccrRemoteControlPathPrefix, ccrRemoteControlService } from "./remote-control-service"; +} from "@ccr/core/contracts/app"; +import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue } from "@ccr/core/providers/presets/index"; +import { normalizeProviderBaseUrl as normalizeProviderBaseUrlInput } from "@ccr/core/providers/url"; +import { backendService } from "@ccr/core/plugins/backend-service"; +import { RAW_TRACE_SPOOL_DIR } from "@ccr/core/config/constants"; +import { loadPersistedApiKeys } from "@ccr/core/config/api-key-store"; +import { codexDefaultBaseUrl, readCodexAuth } from "@ccr/core/agents/local-providers/service"; +import { fetchWithSystemProxy, getSystemProxyUrlForProtocol } from "@ccr/core/proxy/system-proxy-fetch"; +import { handleNetworkCaptureMcpRequest, isNetworkCaptureMcpPath } from "@ccr/core/mcp/network-capture-mcp"; +import { pluginService } from "@ccr/core/plugins/service"; +import { proxyService } from "@ccr/core/proxy/service"; +import { createSseErrorDetector, recordGatewayRequestLog, updateGatewayRequestLogFromRawTrace, type RequestLogRawTraceUpdateInput } from "@ccr/core/observability/request-log-store"; +import { recordGatewayUsageCapture } from "@ccr/core/usage/store"; +import { ClaudeCodeRouterPlugin, normalizeRouteSelector } from "@ccr/core/gateway/claude-code-router-plugin"; +import { ccrRemoteControlPathPrefix, ccrRemoteControlService } from "@ccr/core/gateway/remote-control-service"; import { claudeCodeEffectiveMaxInputTokens, findModelCatalogEntry, @@ -59,7 +59,7 @@ import { readCatalogCapability, type ModelCatalogCapabilities, type ModelCatalogEntry -} from "./model-catalog"; +} from "@ccr/core/gateway/model-catalog"; type CoreGatewayProvider = { apikey?: string; @@ -388,11 +388,12 @@ class GatewayService { const runtimeId = randomUUID(); const upstreamProxyUrl = proxyService.getUpstreamProxyUrl("https") ?? await getSystemProxyUrlForProtocol("https"); this.child = spawnGatewayProcess(config, upstreamProxyUrl, runtimeId, this.coreAuthToken); + const managedChild = this.child; writeManagedCoreGatewayMarker(config, this.child, runtimeId); this.child.stdout?.on("data", (chunk) => console.info(`[gateway] ${chunk.toString().trimEnd()}`)); this.child.stderr?.on("data", (chunk) => console.warn(`[gateway] ${chunk.toString().trimEnd()}`)); this.child.on("exit", (code, signal) => { - void this.handleCoreGatewayExit(code, signal); + void this.handleCoreGatewayExit(managedChild, code, signal); }); } @@ -495,8 +496,8 @@ class GatewayService { }); } - private async handleCoreGatewayExit(code: number | null, signal: NodeJS.Signals | null): Promise { - if (this.status.state === "stopped") { + private async handleCoreGatewayExit(child: ChildProcess, code: number | null, signal: NodeJS.Signals | null): Promise { + if (this.child !== child || this.status.state === "stopped") { return; } removeManagedCoreGatewayMarker(this.config); diff --git a/src/server/mcp/browser-web-search-proxy-mcp.ts b/packages/core/src/mcp/browser-web-search-proxy-mcp.ts similarity index 100% rename from src/server/mcp/browser-web-search-proxy-mcp.ts rename to packages/core/src/mcp/browser-web-search-proxy-mcp.ts diff --git a/src/server/mcp/fusion-tool-fallback-mcp.ts b/packages/core/src/mcp/fusion-tool-fallback-mcp.ts similarity index 100% rename from src/server/mcp/fusion-tool-fallback-mcp.ts rename to packages/core/src/mcp/fusion-tool-fallback-mcp.ts diff --git a/src/server/mcp/fusion-vision-mcp.ts b/packages/core/src/mcp/fusion-vision-mcp.ts similarity index 100% rename from src/server/mcp/fusion-vision-mcp.ts rename to packages/core/src/mcp/fusion-vision-mcp.ts diff --git a/src/server/mcp/network-capture-mcp.ts b/packages/core/src/mcp/network-capture-mcp.ts similarity index 98% rename from src/server/mcp/network-capture-mcp.ts rename to packages/core/src/mcp/network-capture-mcp.ts index 2c6e0b7..15dd6dc 100644 --- a/src/server/mcp/network-capture-mcp.ts +++ b/packages/core/src/mcp/network-capture-mcp.ts @@ -1,7 +1,7 @@ -import packageJson from "../../../package.json"; +import packageJson from "../../package.json"; import type { IncomingMessage, ServerResponse } from "node:http"; -import type { ProxyNetworkExchange } from "../../shared/app"; -import { proxyService } from "../proxy/service"; +import type { ProxyNetworkExchange } from "@ccr/core/contracts/app"; +import { proxyService } from "@ccr/core/proxy/service"; type JsonPrimitive = boolean | null | number | string; type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; diff --git a/src/server/mcp/tool-discovery.ts b/packages/core/src/mcp/tool-discovery.ts similarity index 99% rename from src/server/mcp/tool-discovery.ts rename to packages/core/src/mcp/tool-discovery.ts index 61f0276..efe2b41 100644 --- a/src/server/mcp/tool-discovery.ts +++ b/packages/core/src/mcp/tool-discovery.ts @@ -1,11 +1,11 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; -import { fetchWithSystemProxy } from "../../main/system-proxy-fetch"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; import type { GatewayMcpRemoteServerConfig, GatewayMcpServerConfig, GatewayMcpStdioServerConfig, GatewayMcpToolInfo -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; type JsonRpcMessage = { error?: unknown; diff --git a/src/main/model-catalog-file.ts b/packages/core/src/models/catalog-file.ts similarity index 87% rename from src/main/model-catalog-file.ts rename to packages/core/src/models/catalog-file.ts index 68b5c86..08c09e1 100644 --- a/src/main/model-catalog-file.ts +++ b/packages/core/src/models/catalog-file.ts @@ -24,8 +24,11 @@ export function modelCatalogPathCandidates(): string[] { process.env.CCR_MODEL_CATALOG_PATH?.trim() || "", process.env.CCR_MODELS_JSON_PATH?.trim() || "", pathResolve(process.cwd(), "models.json"), + pathResolve(process.cwd(), "packages", "core", "models.json"), + pathResolve(process.cwd(), "packages", "cli", "models.json"), pathResolve(__dirname, "..", "models.json"), pathResolve(__dirname, "..", "assets", "models.json"), + pathResolve(__dirname, "..", "..", "models.json"), pathResolve(__dirname, "..", "..", "..", "models.json") ]); } diff --git a/src/main/model-pricing-service.ts b/packages/core/src/models/pricing-service.ts similarity index 99% rename from src/main/model-pricing-service.ts rename to packages/core/src/models/pricing-service.ts index 92c074d..e581d21 100644 --- a/src/main/model-pricing-service.ts +++ b/packages/core/src/models/pricing-service.ts @@ -1,4 +1,4 @@ -import { fetchWithSystemProxy } from "./system-proxy-fetch"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; type ModelPricingSource = "litellm" | "models.dev" | "openrouter"; diff --git a/src/main/request-log-store.ts b/packages/core/src/observability/request-log-store.ts similarity index 99% rename from src/main/request-log-store.ts rename to packages/core/src/observability/request-log-store.ts index 25c735f..edd289e 100644 --- a/src/main/request-log-store.ts +++ b/packages/core/src/observability/request-log-store.ts @@ -1,10 +1,10 @@ import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; import { StringDecoder } from "node:string_decoder"; -import { REQUEST_LOGS_DB_FILE } from "./constants"; -import { estimateUsageCostUsd } from "./model-pricing-service"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; -import { normalizeUsageInputTokens } from "./usage-normalization"; +import { REQUEST_LOGS_DB_FILE } from "@ccr/core/config/constants"; +import { estimateUsageCostUsd } from "@ccr/core/models/pricing-service"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; +import { normalizeUsageInputTokens } from "@ccr/core/usage/normalization"; import type { AgentAnalysisAgentRow, AgentAnalysisFilter, @@ -38,7 +38,7 @@ import type { RequestLogRetryAttempt, RequestLogStatusFilter, UsageStatsRange -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; diff --git a/src/main/windows-app-discovery.ts b/packages/core/src/platform/windows-app-discovery.ts similarity index 99% rename from src/main/windows-app-discovery.ts rename to packages/core/src/platform/windows-app-discovery.ts index 1737231..827e6f7 100644 --- a/src/main/windows-app-discovery.ts +++ b/packages/core/src/platform/windows-app-discovery.ts @@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process"; import { readdirSync, statSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import { windowsSystemCommand } from "./windows-system"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; export type WindowsDesktopAppDiscoveryOptions = { appDirs: string[]; diff --git a/src/main/windows-system.ts b/packages/core/src/platform/windows-system.ts similarity index 100% rename from src/main/windows-system.ts rename to packages/core/src/platform/windows-system.ts diff --git a/src/server/backend-service.ts b/packages/core/src/plugins/backend-service.ts similarity index 99% rename from src/server/backend-service.ts rename to packages/core/src/plugins/backend-service.ts index 09c142b..84abbf2 100644 --- a/src/server/backend-service.ts +++ b/packages/core/src/plugins/backend-service.ts @@ -1,7 +1,7 @@ import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs"; import http, { type IncomingMessage, type Server, type ServerResponse } from "node:http"; import path from "node:path"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase, type BetterSqliteStatement } from "../main/sqlite-native"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase, type BetterSqliteStatement } from "@ccr/core/storage/sqlite-native"; type MaybePromise = T | Promise; export type SqliteValue = bigint | Buffer | number | string | Uint8Array | null; diff --git a/src/main/plugins/service.ts b/packages/core/src/plugins/service.ts similarity index 99% rename from src/main/plugins/service.ts rename to packages/core/src/plugins/service.ts index 83dfcdc..ae9ebd9 100644 --- a/src/main/plugins/service.ts +++ b/packages/core/src/plugins/service.ts @@ -14,9 +14,9 @@ import type { ProviderAccountMeter, ProviderAccountPluginConnectorConfig, ProviderAccountSnapshot -} from "../../shared/app"; -import { backendService, type RegisteredHttpBackend, type SqliteStore, type SqliteStoreOptions } from "../../server/backend-service"; -import { CONFIGDIR, DATADIR } from "../constants"; +} from "@ccr/core/contracts/app"; +import { backendService, type RegisteredHttpBackend, type SqliteStore, type SqliteStoreOptions } from "@ccr/core/plugins/backend-service"; +import { CONFIGDIR, DATADIR } from "@ccr/core/config/constants"; type MaybePromise = T | Promise; type PluginLogger = { diff --git a/src/main/profile-launch-core.ts b/packages/core/src/profiles/launch-core.ts similarity index 98% rename from src/main/profile-launch-core.ts rename to packages/core/src/profiles/launch-core.ts index ea5bea2..ab187fd 100644 --- a/src/main/profile-launch-core.ts +++ b/packages/core/src/profiles/launch-core.ts @@ -1,7 +1,7 @@ import path from "node:path"; -import type { AppConfig, ProfileConfig, ProfileOpenSurface } from "../shared/app"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { resolveZcodeConfigFile } from "./zcode-profile-config"; +import type { AppConfig, ProfileConfig, ProfileOpenSurface } from "@ccr/core/contracts/app"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { resolveZcodeConfigFile } from "@ccr/core/agents/zcode/profile-config"; export type ProfileLaunchPlan = { args: string[]; diff --git a/src/main/profile-launch-service.ts b/packages/core/src/profiles/launch-service.ts similarity index 98% rename from src/main/profile-launch-service.ts rename to packages/core/src/profiles/launch-service.ts index a42d5aa..66bec32 100644 --- a/src/main/profile-launch-service.ts +++ b/packages/core/src/profiles/launch-service.ts @@ -2,18 +2,18 @@ import { spawn, spawnSync, type ChildProcess } from "node:child_process"; import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import { assertAvailableGatewayModels, type AppConfig, type ProfileOpenCommandResult, type ProfileOpenRequest, type ProfileOpenResult, type ProfileRuntimeEntry, type ProfileRuntimeStatus, type ProfileStopResult } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { applyClaudeAppGatewayConfig, readClaudeAppGatewayApiKeyCandidates } from "./claude-app-gateway-service"; -import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "./claude-app-launch"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { launchCodexAppProfile, launchZcodeAppProfile, refreshCodexCompatibleAppProfileFiles } from "./codex-app-launch"; -import { codexCliMiddlewareRuntimeScript } from "./codex-cli-middleware-runtime"; -import { CONFIGDIR } from "./constants"; -import { gatewayService } from "../server/gateway/service"; -import { buildProfileLaunchPlan, findProfileForOpen, profileLaunchSpawnCommand, profileOpenCommand, resolveClaudeCodeSettingsFile, resolveProfileOpenSurface } from "./profile-launch-core"; -import { applyProfileConfig, cleanupGeneratedBinBackups } from "./profile-service"; -import { broadcastWindowsEnvironmentChanged, windowsSystemCommand } from "./windows-system"; +import { assertAvailableGatewayModels, type AppConfig, type ProfileOpenCommandResult, type ProfileOpenRequest, type ProfileOpenResult, type ProfileRuntimeEntry, type ProfileRuntimeStatus, type ProfileStopResult } from "@ccr/core/contracts/app"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { applyClaudeAppGatewayConfig, readClaudeAppGatewayApiKeyCandidates } from "@ccr/core/agents/claude-app/gateway-service"; +import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "@ccr/core/agents/claude-app/launch"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { launchCodexAppProfile, launchZcodeAppProfile, refreshCodexCompatibleAppProfileFiles } from "@ccr/core/agents/codex/app-launch"; +import { codexCliMiddlewareRuntimeScript } from "@ccr/core/agents/codex/cli-middleware-runtime"; +import { CONFIGDIR } from "@ccr/core/config/constants"; +import { gatewayService } from "@ccr/core/gateway/service"; +import { buildProfileLaunchPlan, findProfileForOpen, profileLaunchSpawnCommand, profileOpenCommand, resolveClaudeCodeSettingsFile, resolveProfileOpenSurface } from "@ccr/core/profiles/launch-core"; +import { applyProfileConfig, cleanupGeneratedBinBackups } from "@ccr/core/profiles/service"; +import { broadcastWindowsEnvironmentChanged, windowsSystemCommand } from "@ccr/core/platform/windows-system"; const ccrPathBlockStart = "# >>> Claude Code Router CLI >>>"; const ccrPathBlockEnd = "# <<< Claude Code Router CLI <<<"; diff --git a/src/main/profile-service.ts b/packages/core/src/profiles/service.ts similarity index 98% rename from src/main/profile-service.ts rename to packages/core/src/profiles/service.ts index 1f877ee..c4366d1 100644 --- a/src/main/profile-service.ts +++ b/packages/core/src/profiles/service.ts @@ -2,16 +2,16 @@ import { randomBytes } from "node:crypto"; import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import { CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, enforceSingleEnabledGlobalProfilePerAgent, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ProfileApplyResult, type ProfileClientApplyStatus, type ProfileClientKind, type ProfileConfig } from "../shared/app"; -import { replacePersistedApiKeys } from "./api-key-store"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { writeCodexCompatibleAppModelCatalog } from "./codex-app-launch"; -import { codexCliMiddlewareRuntimeScript } from "./codex-cli-middleware-runtime"; -import { codexModelCatalogJson } from "./codex-model-catalog"; -import { CONFIGDIR } from "./constants"; -import { resolveZcodeConfigFile, writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "./zcode-profile-config"; -import { normalizeRouteSelector } from "../server/gateway/claude-code-router-plugin"; +import { CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, enforceSingleEnabledGlobalProfilePerAgent, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ProfileApplyResult, type ProfileClientApplyStatus, type ProfileClientKind, type ProfileConfig } from "@ccr/core/contracts/app"; +import { replacePersistedApiKeys } from "@ccr/core/config/api-key-store"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { writeCodexCompatibleAppModelCatalog } from "@ccr/core/agents/codex/app-launch"; +import { codexCliMiddlewareRuntimeScript } from "@ccr/core/agents/codex/cli-middleware-runtime"; +import { codexModelCatalogJson } from "@ccr/core/agents/codex/model-catalog"; +import { CONFIGDIR } from "@ccr/core/config/constants"; +import { resolveZcodeConfigFile, writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "@ccr/core/agents/zcode/profile-config"; +import { normalizeRouteSelector } from "@ccr/core/gateway/claude-code-router-plugin"; const managedRootStart = "# BEGIN CCR managed profile"; const managedRootEnd = "# END CCR managed profile"; diff --git a/src/main/provider-account-service.ts b/packages/core/src/providers/account-service.ts similarity index 99% rename from src/main/provider-account-service.ts rename to packages/core/src/providers/account-service.ts index 094d36b..31df114 100644 --- a/src/main/provider-account-service.ts +++ b/packages/core/src/providers/account-service.ts @@ -1,12 +1,12 @@ import { createHash, randomUUID } from "node:crypto"; -import { loadAppConfig } from "./config"; -import { attachCodexRateLimitResetCreditDetails } from "./local-agent-providers/codex"; -import { localAgentProviderApiKey, readCodexAuth } from "./local-agent-provider-service"; -import { pluginService } from "./plugins/service"; -import { getUsageTotalsSince } from "./usage-store"; -import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey } from "./presets"; -import { fetchWithSystemProxy } from "./system-proxy-fetch"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../shared/provider-url"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { attachCodexRateLimitResetCreditDetails } from "@ccr/core/agents/local-providers/codex"; +import { localAgentProviderApiKey, readCodexAuth } from "@ccr/core/agents/local-providers/service"; +import { pluginService } from "@ccr/core/plugins/service"; +import { getUsageTotalsSince } from "@ccr/core/usage/store"; +import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey } from "@ccr/core/providers/presets/index"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import type { AppConfig, GatewayProviderConfig, @@ -36,7 +36,7 @@ import type { ProviderAccountStandardConnectorConfig, ProviderCredentialConfig, ProviderAccountStatus -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type CacheEntry = { expiresAt: number; diff --git a/src/main/provider-icons.ts b/packages/core/src/providers/icons.ts similarity index 97% rename from src/main/provider-icons.ts rename to packages/core/src/providers/icons.ts index c522032..78f2c17 100644 --- a/src/main/provider-icons.ts +++ b/packages/core/src/providers/icons.ts @@ -2,10 +2,10 @@ import { createHash } from "node:crypto"; import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { PROVIDER_ICON_CACHE_DIR } from "./constants"; -import { fetchWithSystemProxy } from "./system-proxy-fetch"; -import type { ProviderIconDetectionRequest, ProviderIconDetectionResult } from "../shared/app"; -import { compactProviderUrl, providerUrlWithDefaultScheme } from "../shared/provider-url"; +import { PROVIDER_ICON_CACHE_DIR } from "@ccr/core/config/constants"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; +import type { ProviderIconDetectionRequest, ProviderIconDetectionResult } from "@ccr/core/contracts/app"; +import { compactProviderUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; const faviconServiceUrl = "https://t0.gstatic.com/faviconV2"; const maxProviderIconBytes = 1024 * 1024; diff --git a/src/main/provider-manifest-service.ts b/packages/core/src/providers/manifest-service.ts similarity index 97% rename from src/main/provider-manifest-service.ts rename to packages/core/src/providers/manifest-service.ts index f287364..40bdf89 100644 --- a/src/main/provider-manifest-service.ts +++ b/packages/core/src/providers/manifest-service.ts @@ -1,9 +1,9 @@ import { lookup } from "node:dns/promises"; import https from "node:https"; import net from "node:net"; -import { parseProviderManifestPayload } from "../shared/deep-link"; -import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey, providerIdentitySafetyIssue } from "./presets"; -import { providerUrlWithDefaultScheme } from "../shared/provider-url"; +import { parseProviderManifestPayload } from "@ccr/core/contracts/deep-link"; +import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey, providerIdentitySafetyIssue } from "@ccr/core/providers/presets/index"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import type { GatewayProviderConfig, ProviderAccountConnectorConfig, @@ -12,7 +12,7 @@ import type { ProviderDeepLinkPayload, ProviderManifestFetchRequest, ProviderManifestFetchResult -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type SafeAddress = { address: string; diff --git a/src/main/provider-model-catalog.ts b/packages/core/src/providers/model-catalog.ts similarity index 98% rename from src/main/provider-model-catalog.ts rename to packages/core/src/providers/model-catalog.ts index 87519c6..ef57208 100644 --- a/src/main/provider-model-catalog.ts +++ b/packages/core/src/providers/model-catalog.ts @@ -1,7 +1,7 @@ -import type { ProviderCatalogModelsRequest, ProviderCatalogModelsResult } from "../shared/app"; -import { providerUrlWithDefaultScheme } from "../shared/provider-url"; -import { loadModelCatalogPayload } from "./model-catalog-file"; -import { findProviderPreset, findProviderPresetByBaseUrl } from "./presets"; +import type { ProviderCatalogModelsRequest, ProviderCatalogModelsResult } from "@ccr/core/contracts/app"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; +import { loadModelCatalogPayload } from "@ccr/core/models/catalog-file"; +import { findProviderPreset, findProviderPresetByBaseUrl } from "@ccr/core/providers/presets/index"; type CatalogProviderEntry = { apiUrls: string[]; diff --git a/src/main/presets/anthropic/index.ts b/packages/core/src/providers/presets/anthropic/index.ts similarity index 93% rename from src/main/presets/anthropic/index.ts rename to packages/core/src/providers/presets/anthropic/index.ts index 7cdc93b..929a717 100644 --- a/src/main/presets/anthropic/index.ts +++ b/packages/core/src/providers/presets/anthropic/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const anthropicProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/bailian/index.ts b/packages/core/src/providers/presets/bailian/index.ts similarity index 92% rename from src/main/presets/bailian/index.ts rename to packages/core/src/providers/presets/bailian/index.ts index f52da99..5a21269 100644 --- a/src/main/presets/bailian/index.ts +++ b/packages/core/src/providers/presets/bailian/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const bailianProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/deepseek/index.ts b/packages/core/src/providers/presets/deepseek/index.ts similarity index 90% rename from src/main/presets/deepseek/index.ts rename to packages/core/src/providers/presets/deepseek/index.ts index ee87dc6..ee51cd3 100644 --- a/src/main/presets/deepseek/index.ts +++ b/packages/core/src/providers/presets/deepseek/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const deepSeekProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/gemini/index.ts b/packages/core/src/providers/presets/gemini/index.ts similarity index 93% rename from src/main/presets/gemini/index.ts rename to packages/core/src/providers/presets/gemini/index.ts index 4d92ad5..7e0fec7 100644 --- a/src/main/presets/gemini/index.ts +++ b/packages/core/src/providers/presets/gemini/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const geminiProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/index.ts b/packages/core/src/providers/presets/index.ts similarity index 59% rename from src/main/presets/index.ts rename to packages/core/src/providers/presets/index.ts index cee4dea..760c2b1 100644 --- a/src/main/presets/index.ts +++ b/packages/core/src/providers/presets/index.ts @@ -1,19 +1,19 @@ -import { anthropicProviderPreset } from "./anthropic"; -import { bailianProviderPreset } from "./bailian"; -import { deepSeekProviderPreset } from "./deepseek"; -import { geminiProviderPreset } from "./gemini"; -import { kimiCodingProviderPreset } from "./kimi-coding"; -import { mistralProviderPreset } from "./mistral"; -import { moonshotChinaProviderPreset, moonshotGlobalProviderPreset } from "./moonshot"; -import { openaiProviderPreset } from "./openai"; -import { openRouterProviderPreset } from "./openrouter"; -import { runApiProviderPreset } from "./runapi"; -import { siliconFlowProviderPreset } from "./siliconflow"; -import { teamoRouterProviderPreset } from "./teamorouter"; -import { zaiGlobalCodingProviderPreset } from "./zai-global-coding"; -import { zaiGlobalGeneralProviderPreset } from "./zai-global-general"; -import { zhipuCnCodingProviderPreset } from "./zhipu-cn-coding"; -import { zhipuCnGeneralProviderPreset } from "./zhipu-cn-general"; +import { anthropicProviderPreset } from "@ccr/core/providers/presets/anthropic/index"; +import { bailianProviderPreset } from "@ccr/core/providers/presets/bailian/index"; +import { deepSeekProviderPreset } from "@ccr/core/providers/presets/deepseek/index"; +import { geminiProviderPreset } from "@ccr/core/providers/presets/gemini/index"; +import { kimiCodingProviderPreset } from "@ccr/core/providers/presets/kimi-coding/index"; +import { mistralProviderPreset } from "@ccr/core/providers/presets/mistral/index"; +import { moonshotChinaProviderPreset, moonshotGlobalProviderPreset } from "@ccr/core/providers/presets/moonshot/index"; +import { openaiProviderPreset } from "@ccr/core/providers/presets/openai/index"; +import { openRouterProviderPreset } from "@ccr/core/providers/presets/openrouter/index"; +import { runApiProviderPreset } from "@ccr/core/providers/presets/runapi/index"; +import { siliconFlowProviderPreset } from "@ccr/core/providers/presets/siliconflow/index"; +import { teamoRouterProviderPreset } from "@ccr/core/providers/presets/teamorouter/index"; +import { zaiGlobalCodingProviderPreset } from "@ccr/core/providers/presets/zai-global-coding/index"; +import { zaiGlobalGeneralProviderPreset } from "@ccr/core/providers/presets/zai-global-general/index"; +import { zhipuCnCodingProviderPreset } from "@ccr/core/providers/presets/zhipu-cn-coding/index"; +import { zhipuCnGeneralProviderPreset } from "@ccr/core/providers/presets/zhipu-cn-general/index"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -22,8 +22,8 @@ import { providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList, providerPresetMatchesBaseUrl -} from "../../shared/provider-preset-utils"; -import type { ProviderIdentitySafetyIssue, ProviderPreset } from "../../shared/provider-presets"; +} from "@ccr/core/providers/presets/utils"; +import type { ProviderIdentitySafetyIssue, ProviderPreset } from "@ccr/core/providers/presets/types"; export const providerPresets: ProviderPreset[] = [ openaiProviderPreset, diff --git a/src/main/presets/kimi-coding/index.ts b/packages/core/src/providers/presets/kimi-coding/index.ts similarity index 87% rename from src/main/presets/kimi-coding/index.ts rename to packages/core/src/providers/presets/kimi-coding/index.ts index 6848c28..924fd10 100644 --- a/src/main/presets/kimi-coding/index.ts +++ b/packages/core/src/providers/presets/kimi-coding/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const kimiCodingProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/mistral/index.ts b/packages/core/src/providers/presets/mistral/index.ts similarity index 87% rename from src/main/presets/mistral/index.ts rename to packages/core/src/providers/presets/mistral/index.ts index ff351f4..e12bc2b 100644 --- a/src/main/presets/mistral/index.ts +++ b/packages/core/src/providers/presets/mistral/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const mistralProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/moonshot/index.ts b/packages/core/src/providers/presets/moonshot/index.ts similarity index 95% rename from src/main/presets/moonshot/index.ts rename to packages/core/src/providers/presets/moonshot/index.ts index d6887f0..b4ea02d 100644 --- a/src/main/presets/moonshot/index.ts +++ b/packages/core/src/providers/presets/moonshot/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const moonshotGlobalProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/openai/index.ts b/packages/core/src/providers/presets/openai/index.ts similarity index 93% rename from src/main/presets/openai/index.ts rename to packages/core/src/providers/presets/openai/index.ts index e2d5cf9..a4ea97f 100644 --- a/src/main/presets/openai/index.ts +++ b/packages/core/src/providers/presets/openai/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const openaiProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/openrouter/index.ts b/packages/core/src/providers/presets/openrouter/index.ts similarity index 90% rename from src/main/presets/openrouter/index.ts rename to packages/core/src/providers/presets/openrouter/index.ts index 86d56da..7570322 100644 --- a/src/main/presets/openrouter/index.ts +++ b/packages/core/src/providers/presets/openrouter/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const openRouterProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/runapi/index.ts b/packages/core/src/providers/presets/runapi/index.ts similarity index 91% rename from src/main/presets/runapi/index.ts rename to packages/core/src/providers/presets/runapi/index.ts index 1f19acb..5f982ab 100644 --- a/src/main/presets/runapi/index.ts +++ b/packages/core/src/providers/presets/runapi/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const runApiProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/siliconflow/index.ts b/packages/core/src/providers/presets/siliconflow/index.ts similarity index 89% rename from src/main/presets/siliconflow/index.ts rename to packages/core/src/providers/presets/siliconflow/index.ts index 737c338..a4af567 100644 --- a/src/main/presets/siliconflow/index.ts +++ b/packages/core/src/providers/presets/siliconflow/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const siliconFlowProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/teamorouter/index.ts b/packages/core/src/providers/presets/teamorouter/index.ts similarity index 92% rename from src/main/presets/teamorouter/index.ts rename to packages/core/src/providers/presets/teamorouter/index.ts index aa19e75..e7909fa 100644 --- a/src/main/presets/teamorouter/index.ts +++ b/packages/core/src/providers/presets/teamorouter/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const teamoRouterProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/shared/provider-presets.ts b/packages/core/src/providers/presets/types.ts similarity index 97% rename from src/shared/provider-presets.ts rename to packages/core/src/providers/presets/types.ts index e004fff..c4811ff 100644 --- a/src/shared/provider-presets.ts +++ b/packages/core/src/providers/presets/types.ts @@ -1,4 +1,4 @@ -import type { GatewayProviderProtocol, ProviderAccountConfig } from "./app"; +import type { GatewayProviderProtocol, ProviderAccountConfig } from "@ccr/core/contracts/app"; export type ProviderPresetEndpoint = { baseUrl: string; diff --git a/src/shared/provider-preset-utils.ts b/packages/core/src/providers/presets/utils.ts similarity index 96% rename from src/shared/provider-preset-utils.ts rename to packages/core/src/providers/presets/utils.ts index 7bdb9b2..362e19d 100644 --- a/src/shared/provider-preset-utils.ts +++ b/packages/core/src/providers/presets/utils.ts @@ -3,8 +3,8 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "./provider-presets"; -import { providerUrlWithDefaultScheme } from "./provider-url"; +} from "@ccr/core/providers/presets/types"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; export function findProviderPresetInList( presets: ProviderPreset[], @@ -188,7 +188,11 @@ function providerEndpointMatchesBaseUrl(endpointBaseUrl: string, baseUrl: string const endpointPath = normalizeProviderPresetPath(endpoint.pathname); const candidatePath = normalizeProviderPresetPath(candidate.pathname); - return endpointPath === "/" || candidatePath === "/" || candidatePath === endpointPath || candidatePath.startsWith(`${endpointPath}/`); + return endpointPath === "/" || + candidatePath === "/" || + candidatePath === endpointPath || + candidatePath.startsWith(`${endpointPath}/`) || + endpointPath.startsWith(`${candidatePath}/`); } function providerEndpointHost(baseUrl: string): string | undefined { diff --git a/src/main/presets/zai-global-coding/index.ts b/packages/core/src/providers/presets/zai-global-coding/index.ts similarity index 94% rename from src/main/presets/zai-global-coding/index.ts rename to packages/core/src/providers/presets/zai-global-coding/index.ts index d757fcc..24c0719 100644 --- a/src/main/presets/zai-global-coding/index.ts +++ b/packages/core/src/providers/presets/zai-global-coding/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zaiQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/presets/zai-global-general/index.ts b/packages/core/src/providers/presets/zai-global-general/index.ts similarity index 94% rename from src/main/presets/zai-global-general/index.ts rename to packages/core/src/providers/presets/zai-global-general/index.ts index 44612fd..377b564 100644 --- a/src/main/presets/zai-global-general/index.ts +++ b/packages/core/src/providers/presets/zai-global-general/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zaiQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/presets/zhipu-cn-coding/index.ts b/packages/core/src/providers/presets/zhipu-cn-coding/index.ts similarity index 94% rename from src/main/presets/zhipu-cn-coding/index.ts rename to packages/core/src/providers/presets/zhipu-cn-coding/index.ts index a28dda1..04540d2 100644 --- a/src/main/presets/zhipu-cn-coding/index.ts +++ b/packages/core/src/providers/presets/zhipu-cn-coding/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zhipuQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/presets/zhipu-cn-general/index.ts b/packages/core/src/providers/presets/zhipu-cn-general/index.ts similarity index 94% rename from src/main/presets/zhipu-cn-general/index.ts rename to packages/core/src/providers/presets/zhipu-cn-general/index.ts index c0c0e52..2db2f5d 100644 --- a/src/main/presets/zhipu-cn-general/index.ts +++ b/packages/core/src/providers/presets/zhipu-cn-general/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zhipuQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/provider-probe.ts b/packages/core/src/providers/probe.ts similarity index 99% rename from src/main/provider-probe.ts rename to packages/core/src/providers/probe.ts index aaf6d1f..e5b24d4 100644 --- a/src/main/provider-probe.ts +++ b/packages/core/src/providers/probe.ts @@ -10,15 +10,15 @@ import type { GatewayProviderProbeRequest, GatewayProviderProbeResult, GatewayProviderProtocol -} from "../shared/app"; -import { providerApiKeySafetyIssue } from "./presets"; -import { fetchWithSystemProxy } from "./system-proxy-fetch"; +} from "@ccr/core/contracts/app"; +import { providerApiKeySafetyIssue } from "@ccr/core/providers/presets/index"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; import { compactProviderUrl, parseProviderBaseUrl, providerBaseUrlForProtocol, type ParsedProviderBaseUrl -} from "../shared/provider-url"; +} from "@ccr/core/providers/url"; type ModelSource = NonNullable; diff --git a/src/shared/provider-url.ts b/packages/core/src/providers/url.ts similarity index 98% rename from src/shared/provider-url.ts rename to packages/core/src/providers/url.ts index b105f2b..afb4c3a 100644 --- a/src/shared/provider-url.ts +++ b/packages/core/src/providers/url.ts @@ -1,4 +1,4 @@ -import type { GatewayProviderProtocol } from "./app"; +import type { GatewayProviderProtocol } from "@ccr/core/contracts/app"; export type ParsedProviderBaseUrl = { anthropicBaseUrl: string; diff --git a/src/server/proxy/certificates.ts b/packages/core/src/proxy/certificates.ts similarity index 99% rename from src/server/proxy/certificates.ts rename to packages/core/src/proxy/certificates.ts index ea5ae91..57bb1d6 100644 --- a/src/server/proxy/certificates.ts +++ b/packages/core/src/proxy/certificates.ts @@ -4,7 +4,7 @@ import net from "node:net"; import os from "node:os"; import path from "node:path"; import forge from "node-forge"; -import { CERTDIR, PROXY_CA_CERT_DER_FILE, PROXY_CA_CERT_FILE, PROXY_CA_KEY_FILE } from "../../main/constants"; +import { CERTDIR, PROXY_CA_CERT_DER_FILE, PROXY_CA_CERT_FILE, PROXY_CA_KEY_FILE } from "@ccr/core/config/constants"; const pki = forge.pki; const certificateDirectoryMode = 0o700; diff --git a/src/server/proxy/service.ts b/packages/core/src/proxy/service.ts similarity index 99% rename from src/server/proxy/service.ts rename to packages/core/src/proxy/service.ts index 4db90ff..8c8cfa0 100644 --- a/src/server/proxy/service.ts +++ b/packages/core/src/proxy/service.ts @@ -21,10 +21,10 @@ import type { ProxyNetworkSnapshot, ProxyRouteTarget, ProxyStatus -} from "../../shared/app"; -import { PROXY_CA_CERT_FILE } from "../../main/constants"; -import { windowsSystemCommand } from "../../main/windows-system"; -import { pluginService, type GatewayPluginProxyRouteMatch } from "../../main/plugins/service"; +} from "@ccr/core/contracts/app"; +import { PROXY_CA_CERT_FILE } from "@ccr/core/config/constants"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; +import { pluginService, type GatewayPluginProxyRouteMatch } from "@ccr/core/plugins/service"; import { createCertificateForHost, ensureProxyCertificateAuthority, @@ -36,8 +36,8 @@ import { readProxyCertificateFingerprintSha256, readProxyCertificateAuthority, type CertificateAuthority -} from "./certificates"; -import { formatUpstreamProxy, readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "./system-proxy"; +} from "@ccr/core/proxy/certificates"; +import { formatUpstreamProxy, readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "@ccr/core/proxy/system-proxy"; type MitmServer = { host: string; diff --git a/src/main/system-proxy-fetch.ts b/packages/core/src/proxy/system-proxy-fetch.ts similarity index 97% rename from src/main/system-proxy-fetch.ts rename to packages/core/src/proxy/system-proxy-fetch.ts index fd6d941..f1e10bc 100644 --- a/src/main/system-proxy-fetch.ts +++ b/packages/core/src/proxy/system-proxy-fetch.ts @@ -1,7 +1,7 @@ import { ProxyAgent, type Dispatcher } from "undici"; -import { loadAppConfig } from "./config"; -import { readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "../server/proxy/system-proxy"; -import type { AppConfig } from "../shared/app"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "@ccr/core/proxy/system-proxy"; +import type { AppConfig } from "@ccr/core/contracts/app"; type FetchInitWithDispatcher = RequestInit & { dispatcher?: Dispatcher; diff --git a/src/server/proxy/system-proxy.ts b/packages/core/src/proxy/system-proxy.ts similarity index 99% rename from src/server/proxy/system-proxy.ts rename to packages/core/src/proxy/system-proxy.ts index 90d91eb..c17f7e8 100644 --- a/src/server/proxy/system-proxy.ts +++ b/packages/core/src/proxy/system-proxy.ts @@ -1,9 +1,9 @@ import { execFile } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import path from "node:path"; -import type { ProxySystemStatus } from "../../shared/app"; -import { DATADIR } from "../../main/constants"; -import { windowsSystemCommand } from "../../main/windows-system"; +import type { ProxySystemStatus } from "@ccr/core/contracts/app"; +import { DATADIR } from "@ccr/core/config/constants"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; export type UpstreamProxyServer = { host: string; diff --git a/src/main/app-paths.ts b/packages/core/src/runtime/app-paths.ts similarity index 78% rename from src/main/app-paths.ts rename to packages/core/src/runtime/app-paths.ts index 5307b13..874977f 100644 --- a/src/main/app-paths.ts +++ b/packages/core/src/runtime/app-paths.ts @@ -33,6 +33,24 @@ export function resolveRuntimeAppPath(name: RuntimePathName): string { return fallbackUserDataDir(); } +export function resolveRuntimeConfigDir(): string { + if (process.platform === "win32") { + return path.join(resolveRuntimeAppPath("appData"), APP_STORAGE_NAME); + } + return path.join(resolveRuntimeAppPath("home"), `.${APP_STORAGE_NAME}`); +} + +export function resolveRuntimeDataDir(): string { + const configured = readConfiguredPath("userData"); + if (configured) { + return configured; + } + if (process.platform === "win32") { + return resolveRuntimeConfigDir(); + } + return path.join(resolveRuntimeConfigDir(), "app-data"); +} + function readConfiguredPath(name: RuntimePathName): string | undefined { const key = name === "home" ? homeDirEnv @@ -59,8 +77,5 @@ function fallbackAppDataDir(): string { } function fallbackUserDataDir(): string { - if (process.platform === "win32") { - return path.join(fallbackAppDataDir(), APP_STORAGE_NAME); - } - return path.join(LEGACY_CONFIGDIR, "app-data"); + return resolveRuntimeDataDir(); } diff --git a/src/main/storage-migration.ts b/packages/core/src/storage/migration.ts similarity index 100% rename from src/main/storage-migration.ts rename to packages/core/src/storage/migration.ts diff --git a/src/main/sqlite-native.ts b/packages/core/src/storage/sqlite-native.ts similarity index 100% rename from src/main/sqlite-native.ts rename to packages/core/src/storage/sqlite-native.ts diff --git a/src/main/usage-normalization.ts b/packages/core/src/usage/normalization.ts similarity index 97% rename from src/main/usage-normalization.ts rename to packages/core/src/usage/normalization.ts index a3dfa0b..e2355b8 100644 --- a/src/main/usage-normalization.ts +++ b/packages/core/src/usage/normalization.ts @@ -1,4 +1,4 @@ -import type { GatewayProviderProtocol } from "../shared/app"; +import type { GatewayProviderProtocol } from "@ccr/core/contracts/app"; export type UsageTokenAccounting = { cacheReadTokens?: number; diff --git a/src/main/usage-store.ts b/packages/core/src/usage/store.ts similarity index 93% rename from src/main/usage-store.ts rename to packages/core/src/usage/store.ts index 45cd1f9..d35c73c 100644 --- a/src/main/usage-store.ts +++ b/packages/core/src/usage/store.ts @@ -1,10 +1,10 @@ import { mkdirSync } from "node:fs"; import { EventEmitter } from "node:events"; import { dirname } from "node:path"; -import { USAGE_DB_FILE } from "./constants"; -import { estimateUsageCostUsd } from "./model-pricing-service"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; -import { normalizeUsageInputTokens } from "./usage-normalization"; +import { USAGE_DB_FILE } from "@ccr/core/config/constants"; +import { estimateUsageCostUsd } from "@ccr/core/models/pricing-service"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; +import { normalizeUsageInputTokens } from "@ccr/core/usage/normalization"; import type { GatewayProviderProtocol, UsageComparisonRow, @@ -13,7 +13,7 @@ import type { UsageStatsRange, UsageStatsSnapshot, UsageTotals -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; @@ -90,6 +90,7 @@ type UsageSnapshot = UsageNumbers & { }; const usageEvents = new EventEmitter(); +const usageStatsRanges = new Set(["today", "24h", "7d", "30d"]); const emptyTotals: UsageTotals = { avgDurationMs: 0, cacheRatio: 0, @@ -175,10 +176,11 @@ export class UsageStore { usageEvents.emit("recorded"); } - async getStats(range: UsageStatsRange = "7d", filter: UsageStatsFilter = {}): Promise { + async getStats(range: UsageStatsRange | null | undefined = "7d", filter: UsageStatsFilter | null | undefined = {}): Promise { const database = await this.getDatabase(); const now = new Date(); - const since = getRangeSince(range, now); + const normalizedRange = normalizeUsageRange(range); + const since = getRangeSince(normalizedRange, now); const query = buildUsageWhereClause(since, filter); return { @@ -186,14 +188,14 @@ export class UsageStore { generatedAt: now.toISOString(), models: readModelRows(database, query), providerModels: readProviderModelRows(database, query), - range, + range: normalizedRange, recentRequests: readRecentRequestRows(database, query), - series: readUsageSeries(database, range, now, query), + series: readUsageSeries(database, normalizedRange, now, query), totals: readUsageTotals(database, query) }; } - async getTotalsSince(since: Date, filter: UsageStatsFilter = {}, options: UsageStatsQueryOptions = {}): Promise { + async getTotalsSince(since: Date, filter: UsageStatsFilter | null | undefined = {}, options: UsageStatsQueryOptions | null | undefined = {}): Promise { const database = await this.getDatabase(); return readUsageTotals(database, buildUsageWhereClause(since, filter, options)); } @@ -284,16 +286,16 @@ function ensureUsageSchema(database: SqlDatabase): void { database.exec("CREATE INDEX IF NOT EXISTS usage_events_credential_created_at_idx ON usage_events(credential_id, created_at)"); } -export async function getUsageStats(range?: UsageStatsRange, filter?: UsageStatsFilter): Promise { +export async function getUsageStats(range?: UsageStatsRange | null, filter?: UsageStatsFilter | null): Promise { try { return await usageStore.getStats(range, filter); } catch (error) { console.warn(`[usage] Failed to read usage stats: ${formatError(error)}`); - return emptySnapshot(range ?? "7d"); + return emptySnapshot(normalizeUsageRange(range)); } } -export async function getTodayUsageTotals(filter?: UsageStatsFilter, options?: UsageStatsQueryOptions): Promise { +export async function getTodayUsageTotals(filter?: UsageStatsFilter | null, options?: UsageStatsQueryOptions | null): Promise { try { return await usageStore.getTotalsSince(floorDay(new Date()), filter, options); } catch (error) { @@ -302,7 +304,7 @@ export async function getTodayUsageTotals(filter?: UsageStatsFilter, options?: U } } -export async function getUsageTotalsSince(since: Date, filter?: UsageStatsFilter, options?: UsageStatsQueryOptions): Promise { +export async function getUsageTotalsSince(since: Date, filter?: UsageStatsFilter | null, options?: UsageStatsQueryOptions | null): Promise { try { return await usageStore.getTotalsSince(since, filter, options); } catch (error) { @@ -346,19 +348,21 @@ export async function recordGatewayUsageCapture(input: UsageCaptureInput): Promi function buildUsageWhereClause( since: Date, - filter: UsageStatsFilter, - options: UsageStatsQueryOptions = {} + filter: UsageStatsFilter | null | undefined, + options: UsageStatsQueryOptions | null | undefined = {} ): UsageWhereClause { + const normalizedFilter = normalizeUsageFilter(filter); + const normalizedOptions = normalizeUsageQueryOptions(options); const where = ["created_at >= ?"]; const params: SqlValue[] = [since.toISOString()]; - const credential = normalizeFilterValue(filter.credential); - const provider = normalizeFilterValue(filter.provider); - const model = normalizeFilterValue(filter.model); + const credential = normalizeFilterValue(normalizedFilter.credential); + const provider = normalizeFilterValue(normalizedFilter.provider); + const model = normalizeFilterValue(normalizedFilter.model); if (provider) { where.push("provider = ?"); params.push(provider); - } else if (!options.includeProxy && filter.includeProxy !== true) { + } else if (!normalizedOptions.includeProxy && normalizedFilter.includeProxy !== true) { where.push("provider <> ?"); params.push("proxy"); } @@ -377,6 +381,26 @@ function buildUsageWhereClause( }; } +function normalizeUsageRange(range: UsageStatsRange | null | undefined): UsageStatsRange { + return range && usageStatsRanges.has(range) ? range : "7d"; +} + +function normalizeUsageFilter(filter: UsageStatsFilter | null | undefined): UsageStatsFilter { + if (!isRecord(filter)) { + return {}; + } + return { + credential: typeof filter.credential === "string" ? filter.credential : undefined, + includeProxy: filter.includeProxy === true, + model: typeof filter.model === "string" ? filter.model : undefined, + provider: typeof filter.provider === "string" ? filter.provider : undefined + }; +} + +function normalizeUsageQueryOptions(options: UsageStatsQueryOptions | null | undefined): UsageStatsQueryOptions { + return isRecord(options) && options.includeProxy === true ? { includeProxy: true } : {}; +} + function configureSqliteDatabase(database: SqlDatabase): void { database.pragma("journal_mode = WAL"); database.pragma("synchronous = NORMAL"); diff --git a/src/main/web-management-server.ts b/packages/core/src/web/management-server.ts similarity index 95% rename from src/main/web-management-server.ts rename to packages/core/src/web/management-server.ts index 6b31ff3..f5ff82d 100644 --- a/src/main/web-management-server.ts +++ b/packages/core/src/web/management-server.ts @@ -7,27 +7,27 @@ import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import packageJson from "../../package.json"; -import { loadPersistedAppSetting, replacePersistedAppSetting } from "./app-config-store"; -import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "./bot-handoff-scan-service"; -import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "./bot-gateway-qr-login-service"; -import { syncClaudeAppGatewayConfig, restoreClaudeAppGatewayConfig } from "./claude-app-gateway-service"; -import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "./config"; -import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "./constants"; -import { detectProviderIcon } from "./provider-icons"; -import { fetchProviderManifest } from "./provider-manifest-service"; -import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "./local-agent-provider-service"; -import { getProviderCatalogModels } from "./provider-model-catalog"; -import { getProviderPresets } from "./presets"; -import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "./provider-probe"; -import { applyProfileConfig } from "./profile-service"; -import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "./profile-launch-service"; -import { ensureProxyCertificateAuthority } from "../server/proxy/certificates"; -import { proxyService } from "../server/proxy/service"; -import { listMcpServerTools } from "../server/mcp/tool-discovery"; -import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "./request-log-store"; -import { getUsageStats } from "./usage-store"; -import { gatewayService } from "../server/gateway/service"; -import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "./provider-account-service"; +import { loadPersistedAppSetting, replacePersistedAppSetting } from "@ccr/core/config/app-config-store"; +import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "@ccr/core/agents/bot-gateway/handoff-scan-service"; +import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "@ccr/core/agents/bot-gateway/qr-login-service"; +import { syncClaudeAppGatewayConfig, restoreClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; +import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "@ccr/core/config/config"; +import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "@ccr/core/config/constants"; +import { detectProviderIcon } from "@ccr/core/providers/icons"; +import { fetchProviderManifest } from "@ccr/core/providers/manifest-service"; +import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "@ccr/core/agents/local-providers/service"; +import { getProviderCatalogModels } from "@ccr/core/providers/model-catalog"; +import { getProviderPresets } from "@ccr/core/providers/presets/index"; +import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "@ccr/core/providers/probe"; +import { applyProfileConfig } from "@ccr/core/profiles/service"; +import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "@ccr/core/profiles/launch-service"; +import { ensureProxyCertificateAuthority } from "@ccr/core/proxy/certificates"; +import { proxyService } from "@ccr/core/proxy/service"; +import { listMcpServerTools } from "@ccr/core/mcp/tool-discovery"; +import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "@ccr/core/observability/request-log-store"; +import { getUsageStats } from "@ccr/core/usage/store"; +import { gatewayService } from "@ccr/core/gateway/service"; +import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "@ccr/core/providers/account-service"; import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, @@ -62,9 +62,10 @@ import type { RequestLogListFilter, UsageStatsFilter, UsageStatsRange -} from "../shared/app"; +} from "@ccr/core/contracts/app"; export type WebManagementServerOptions = { + authToken?: string; host?: string; open?: boolean; port?: number; @@ -124,7 +125,7 @@ const pluginMarketplace: PluginMarketplaceEntry[] = [ export async function startWebManagementServer(options: WebManagementServerOptions = {}): Promise { const host = options.host?.trim() || readEnvString("CCR_WEB_HOST") || defaultWebHost; const requestedPort = options.port ?? readEnvPort("CCR_WEB_PORT") ?? defaultWebPort; - const authToken = randomBytes(32).toString("base64url"); + const authToken = options.authToken?.trim() || readEnvString("CCR_WEB_AUTH_TOKEN") || randomBytes(32).toString("base64url"); let security: WebManagementSecurityContext | undefined; const server = createServer((request, response) => { if (!security) { @@ -276,6 +277,13 @@ const rpcHandlers: Record = { getAppInfo: () => getCliAppInfo(), getConfig: () => loadAppConfig(), getGatewayStatus: () => gatewayService.getStatus(), + getServiceIdentity: (serviceToken) => ({ + pid: process.pid, + serviceTokenConfigured: Boolean(process.env.CCR_SERVICE_INSTANCE_TOKEN?.trim()), + serviceTokenMatches: typeof serviceToken === "string" && + Boolean(serviceToken.trim()) && + serviceToken === process.env.CCR_SERVICE_INSTANCE_TOKEN + }), getLocalAgentProviderCandidates: () => getLocalAgentProviderCandidates(), getOnboardingFinished: async () => Boolean(readString(await loadPersistedAppSetting(onboardingFinishedAtSettingKey)) || existsSync(ONBOARDING_FINISHED_FILE)), getPluginMarketplace: () => pluginMarketplace, @@ -1043,7 +1051,7 @@ async function revealFile(file: string): Promise { await openSystemExternal(pathToFileURL(path.dirname(file)).toString()); } -function openSystemExternal(target: string): Promise { +export function openSystemExternal(target: string): Promise { if (!target || target === "about:blank") { return Promise.resolve(); } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..1f70006 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/assets/logo.png b/packages/electron/assets/logo.png similarity index 100% rename from assets/logo.png rename to packages/electron/assets/logo.png diff --git a/assets/tray-cyan.png b/packages/electron/assets/tray-cyan.png similarity index 100% rename from assets/tray-cyan.png rename to packages/electron/assets/tray-cyan.png diff --git a/assets/tray-orange.png b/packages/electron/assets/tray-orange.png similarity index 100% rename from assets/tray-orange.png rename to packages/electron/assets/tray-orange.png diff --git a/assets/tray-violet.png b/packages/electron/assets/tray-violet.png similarity index 100% rename from assets/tray-violet.png rename to packages/electron/assets/tray-violet.png diff --git a/assets/tray.png b/packages/electron/assets/tray.png similarity index 100% rename from assets/tray.png rename to packages/electron/assets/tray.png diff --git a/packages/electron/package.json b/packages/electron/package.json new file mode 100644 index 0000000..08551a0 --- /dev/null +++ b/packages/electron/package.json @@ -0,0 +1,10 @@ +{ + "name": "@claude-code-router/electron", + "version": "3.0.7", + "private": true, + "description": "Claude Code Router Electron desktop shell.", + "main": "dist/main/main.js", + "dependencies": { + "electron-updater": "^6.8.9" + } +} diff --git a/src/main/app-menu.ts b/packages/electron/src/main/app-menu.ts similarity index 98% rename from src/main/app-menu.ts rename to packages/electron/src/main/app-menu.ts index de2f219..b601dbc 100644 --- a/src/main/app-menu.ts +++ b/packages/electron/src/main/app-menu.ts @@ -1,5 +1,5 @@ import { app, dialog, Menu, type BrowserWindow, type MenuItemConstructorOptions } from "electron"; -import { APP_NAME, IPC_CHANNELS } from "./constants"; +import { APP_NAME, IPC_CHANNELS } from "@ccr/core/config/constants"; import windowsManager from "./windows"; export function setupApplicationMenu(): void { diff --git a/src/main/bot-gateway-qr-window-service.ts b/packages/electron/src/main/bot-gateway-qr-window-service.ts similarity index 98% rename from src/main/bot-gateway-qr-window-service.ts rename to packages/electron/src/main/bot-gateway-qr-window-service.ts index d020130..a65d490 100644 --- a/src/main/bot-gateway-qr-window-service.ts +++ b/packages/electron/src/main/bot-gateway-qr-window-service.ts @@ -4,7 +4,7 @@ import type { BotGatewayQrWindowCloseResult, BotGatewayQrWindowOpenRequest, BotGatewayQrWindowOpenResult -} from "../shared/app"; +} from "@ccr/core/contracts/app"; const qrWindows = new Map(); diff --git a/src/main/browser-preload.ts b/packages/electron/src/main/browser-preload.ts similarity index 91% rename from src/main/browser-preload.ts rename to packages/electron/src/main/browser-preload.ts index f479d88..2e1930f 100644 --- a/src/main/browser-preload.ts +++ b/packages/electron/src/main/browser-preload.ts @@ -1,6 +1,6 @@ import { contextBridge, ipcRenderer, type IpcRendererEvent } from "electron"; -import { IPC_CHANNELS } from "../shared/ipc-channels"; -import type { BuiltInBrowserState } from "../shared/app"; +import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; +import type { BuiltInBrowserState } from "@ccr/core/contracts/app"; contextBridge.exposeInMainWorld("ccrBrowser", { back: (tabId?: string) => ipcRenderer.invoke(IPC_CHANNELS.browserBack, tabId) as Promise, diff --git a/src/main/built-in-browser.ts b/packages/electron/src/main/built-in-browser.ts similarity index 98% rename from src/main/built-in-browser.ts rename to packages/electron/src/main/built-in-browser.ts index d845d5a..a8ae1b0 100644 --- a/src/main/built-in-browser.ts +++ b/packages/electron/src/main/built-in-browser.ts @@ -14,11 +14,11 @@ import { } from "electron"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import type { AppConfig, BuiltInBrowserState, BuiltInBrowserTabState, GatewayPluginAppConfig, InstalledBrowserApp } from "../shared/app"; -import { IPC_CHANNELS } from "../shared/ipc-channels"; -import { APP_NAME } from "./constants"; -import { pluginService } from "./plugins/service"; -import { proxyService } from "../server/proxy/service"; +import type { AppConfig, BuiltInBrowserState, BuiltInBrowserTabState, GatewayPluginAppConfig, InstalledBrowserApp } from "@ccr/core/contracts/app"; +import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; +import { APP_NAME } from "@ccr/core/config/constants"; +import { pluginService } from "@ccr/core/plugins/service"; +import { proxyService } from "@ccr/core/proxy/service"; type BrowserTab = BuiltInBrowserTabState & { view: WebContentsView; diff --git a/src/main/deep-link.ts b/packages/electron/src/main/deep-link.ts similarity index 88% rename from src/main/deep-link.ts rename to packages/electron/src/main/deep-link.ts index 9304f50..6d6f1cf 100644 --- a/src/main/deep-link.ts +++ b/packages/electron/src/main/deep-link.ts @@ -1,9 +1,9 @@ import { app } from "electron"; import path from "node:path"; -import { appDeepLinkProtocol, createProviderDeepLinkRequest as createSharedProviderDeepLinkRequest, isAppDeepLinkUrl } from "../shared/deep-link"; -import type { ProviderDeepLinkRequest } from "../shared/app"; -import { IPC_CHANNELS } from "./constants"; -import { providerIdentitySafetyIssue } from "./presets"; +import { appDeepLinkProtocol, createProviderDeepLinkRequest as createSharedProviderDeepLinkRequest, isAppDeepLinkUrl } from "@ccr/core/contracts/deep-link"; +import type { ProviderDeepLinkRequest } from "@ccr/core/contracts/app"; +import { IPC_CHANNELS } from "@ccr/core/config/constants"; +import { providerIdentitySafetyIssue } from "@ccr/core/providers/presets/index"; import windowsManager from "./windows"; class DeepLinkService { diff --git a/src/main/electron-web-search-mcp.ts b/packages/electron/src/main/electron-web-search-mcp.ts similarity index 99% rename from src/main/electron-web-search-mcp.ts rename to packages/electron/src/main/electron-web-search-mcp.ts index 8fa90cd..0badf3b 100644 --- a/src/main/electron-web-search-mcp.ts +++ b/packages/electron/src/main/electron-web-search-mcp.ts @@ -1,13 +1,13 @@ import { BrowserWindow, WebContentsView, app, session, type WebContents } from "electron"; import type { IncomingMessage, ServerResponse } from "node:http"; import { join as pathJoin } from "node:path"; -import { backendService } from "../server/backend-service"; -import type { GatewayMcpServerConfig } from "../shared/app"; +import { backendService } from "@ccr/core/plugins/backend-service"; +import type { GatewayMcpServerConfig } from "@ccr/core/contracts/app"; import type { BrowserWebSearchMcpIntegration, BrowserWebSearchMcpRegistration, BrowserWebSearchProtocolRecord -} from "../server/gateway/service"; +} from "@ccr/core/gateway/service"; type JsonPrimitive = boolean | null | number | string; type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; diff --git a/src/main/ipc.ts b/packages/electron/src/main/ipc.ts similarity index 97% rename from src/main/ipc.ts rename to packages/electron/src/main/ipc.ts index 12cf675..ce6a657 100644 --- a/src/main/ipc.ts +++ b/packages/electron/src/main/ipc.ts @@ -4,35 +4,35 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync import net from "node:net"; import path from "node:path"; import { deflateSync, inflateSync } from "node:zlib"; -import { loadPersistedAppSetting, replacePersistedAppSetting } from "./app-config-store"; +import { loadPersistedAppSetting, replacePersistedAppSetting } from "@ccr/core/config/app-config-store"; import { builtInBrowserService } from "./built-in-browser"; -import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "./bot-handoff-scan-service"; -import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "./bot-gateway-qr-login-service"; +import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "@ccr/core/agents/bot-gateway/handoff-scan-service"; +import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "@ccr/core/agents/bot-gateway/qr-login-service"; import { closeBotGatewayQrWindow, openBotGatewayQrWindow } from "./bot-gateway-qr-window-service"; -import { syncClaudeAppGatewayConfig } from "./claude-app-gateway-service"; -import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "./config"; -import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, IPC_CHANNELS, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "./constants"; +import { syncClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; +import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "@ccr/core/config/config"; +import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, IPC_CHANNELS, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "@ccr/core/config/constants"; import { deepLinkService } from "./deep-link"; -import { gatewayService } from "../server/gateway/service"; -import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "./provider-account-service"; -import { detectProviderIcon } from "./provider-icons"; -import { fetchProviderManifest } from "./provider-manifest-service"; -import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "./local-agent-provider-service"; +import { gatewayService } from "@ccr/core/gateway/service"; +import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "@ccr/core/providers/account-service"; +import { detectProviderIcon } from "@ccr/core/providers/icons"; +import { fetchProviderManifest } from "@ccr/core/providers/manifest-service"; +import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "@ccr/core/agents/local-providers/service"; import { isLaunchAtLoginSupported, syncLaunchAtLogin } from "./launch-at-login"; -import { getProviderCatalogModels } from "./provider-model-catalog"; -import { getProviderPresets } from "./presets"; -import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "./provider-probe"; -import { applyProfileConfig } from "./profile-service"; -import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "./profile-launch-service"; -import { ensureProxyCertificateAuthority } from "../server/proxy/certificates"; -import { proxyService } from "../server/proxy/service"; -import { listMcpServerTools } from "../server/mcp/tool-discovery"; -import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "./request-log-store"; +import { getProviderCatalogModels } from "@ccr/core/providers/model-catalog"; +import { getProviderPresets } from "@ccr/core/providers/presets/index"; +import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "@ccr/core/providers/probe"; +import { applyProfileConfig } from "@ccr/core/profiles/service"; +import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "@ccr/core/profiles/launch-service"; +import { ensureProxyCertificateAuthority } from "@ccr/core/proxy/certificates"; +import { proxyService } from "@ccr/core/proxy/service"; +import { listMcpServerTools } from "@ccr/core/mcp/tool-discovery"; +import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "@ccr/core/observability/request-log-store"; import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; -import { getUsageStats } from "./usage-store"; +import { getUsageStats } from "@ccr/core/usage/store"; import windowsManager from "./windows"; -import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, ApiKeyConfig, AppCaptureElementPngRequest, AppCaptureElementPngResult, AppConfig, AppDataExportResult, AppImageExportTargetRequest, AppImageExportTargetResult, AppInfo, AppRenderHtmlPngRequest, AppRenderHtmlPngResult, AppSaveConfigOptions, BotGatewayQrLoginCancelRequest, BotGatewayQrLoginStartRequest, BotGatewayQrLoginWaitRequest, BotGatewayQrWindowCloseRequest, BotGatewayQrWindowOpenRequest, GatewayPluginAppConfig, GatewayProviderConnectivityCheckRequest, GatewayProviderProbeCandidatesRequest, GatewayProviderProbeRequest, GatewayStatus, LocalAgentProviderImportRequest, PluginDependency, PluginDirectorySelection, PluginMarketplaceEntry, ProfileApplyResult, ProfileOpenRequest, ProviderAccountResetRequest, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderCatalogModelsRequest, ProviderIconDetectionRequest, ProviderManifestFetchRequest, RequestLogListFilter, UsageStatsFilter, UsageStatsRange } from "../shared/app"; +import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, ApiKeyConfig, AppCaptureElementPngRequest, AppCaptureElementPngResult, AppConfig, AppDataExportResult, AppImageExportTargetRequest, AppImageExportTargetResult, AppInfo, AppRenderHtmlPngRequest, AppRenderHtmlPngResult, AppSaveConfigOptions, BotGatewayQrLoginCancelRequest, BotGatewayQrLoginStartRequest, BotGatewayQrLoginWaitRequest, BotGatewayQrWindowCloseRequest, BotGatewayQrWindowOpenRequest, GatewayPluginAppConfig, GatewayProviderConnectivityCheckRequest, GatewayProviderProbeCandidatesRequest, GatewayProviderProbeRequest, GatewayStatus, LocalAgentProviderImportRequest, PluginDependency, PluginDirectorySelection, PluginMarketplaceEntry, ProfileApplyResult, ProfileOpenRequest, ProviderAccountResetRequest, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderCatalogModelsRequest, ProviderIconDetectionRequest, ProviderManifestFetchRequest, RequestLogListFilter, UsageStatsFilter, UsageStatsRange } from "@ccr/core/contracts/app"; const pluginMarketplace: PluginMarketplaceEntry[] = [ { diff --git a/src/main/launch-at-login.ts b/packages/electron/src/main/launch-at-login.ts similarity index 92% rename from src/main/launch-at-login.ts rename to packages/electron/src/main/launch-at-login.ts index f79ea65..731cb73 100644 --- a/src/main/launch-at-login.ts +++ b/packages/electron/src/main/launch-at-login.ts @@ -1,5 +1,5 @@ import { app } from "electron"; -import type { AppConfig } from "../shared/app"; +import type { AppConfig } from "@ccr/core/contracts/app"; export function isLaunchAtLoginSupported(platform = process.platform): boolean { return platform === "darwin" || platform === "win32"; diff --git a/src/main/main-app.ts b/packages/electron/src/main/main-app.ts similarity index 95% rename from src/main/main-app.ts rename to packages/electron/src/main/main-app.ts index eb7f7f0..f71892a 100644 --- a/src/main/main-app.ts +++ b/packages/electron/src/main/main-app.ts @@ -1,14 +1,14 @@ import { app, BrowserWindow, dialog, shell } from "electron"; import { setupApplicationMenu } from "./app-menu"; -import { loadAppConfig } from "./config"; -import { restoreClaudeAppGatewayConfig, syncClaudeAppGatewayConfig } from "./claude-app-gateway-service"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { restoreClaudeAppGatewayConfig, syncClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; import { deepLinkService } from "./deep-link"; -import { gatewayService } from "../server/gateway/service"; +import { gatewayService } from "@ccr/core/gateway/service"; import "./ipc"; -import { applyProfileConfig } from "./profile-service"; -import { ensureCcrCliLauncher } from "./profile-launch-service"; +import { applyProfileConfig } from "@ccr/core/profiles/service"; +import { ensureCcrCliLauncher } from "@ccr/core/profiles/launch-service"; import { syncLaunchAtLogin } from "./launch-at-login"; -import { proxyService } from "../server/proxy/service"; +import { proxyService } from "@ccr/core/proxy/service"; import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; import { browserWebSearchMcpService } from "./electron-web-search-mcp"; diff --git a/src/main/main.ts b/packages/electron/src/main/main.ts similarity index 72% rename from src/main/main.ts rename to packages/electron/src/main/main.ts index 17baae6..8873d24 100644 --- a/src/main/main.ts +++ b/packages/electron/src/main/main.ts @@ -1,13 +1,18 @@ import { app, dialog } from "electron"; -import path from "node:path"; -import { APP_STORAGE_NAME, setRuntimeAppPaths } from "./app-paths"; -import { copyMissingDirectoryContents, sameFilesystemPath } from "./storage-migration"; +import { mkdirSync } from "node:fs"; +import { resolveRuntimeDataDir, setRuntimeAppPaths } from "@ccr/core/runtime/app-paths"; +import { copyMissingDirectoryContents, sameFilesystemPath } from "@ccr/core/storage/migration"; const appDataPath = app.getPath("appData"); -const userDataPath = configureRuntimeUserDataPath(appDataPath); +const homePath = app.getPath("home"); setRuntimeAppPaths({ appData: appDataPath, - home: app.getPath("home"), + home: homePath +}); +const userDataPath = configureRuntimeUserDataPath(app.getPath("userData")); +setRuntimeAppPaths({ + appData: appDataPath, + home: homePath, userData: userDataPath }); @@ -44,19 +49,15 @@ function reportFatalStartupError(error: unknown): void { app.exit(1); } -function configureRuntimeUserDataPath(appDataPath: string): string { - const currentUserDataPath = app.getPath("userData"); - if (process.platform !== "win32") { +function configureRuntimeUserDataPath(currentUserDataPath: string): string { + const sharedUserDataPath = resolveRuntimeDataDir(); + mkdirSync(sharedUserDataPath, { recursive: true }); + if (sameFilesystemPath(currentUserDataPath, sharedUserDataPath)) { return currentUserDataPath; } - const storageUserDataPath = path.join(appDataPath, APP_STORAGE_NAME); - if (sameFilesystemPath(currentUserDataPath, storageUserDataPath)) { - return currentUserDataPath; - } - - copyMissingDirectoryContents(currentUserDataPath, storageUserDataPath, "Windows app data directory"); - app.setPath("userData", storageUserDataPath); + copyMissingDirectoryContents(currentUserDataPath, sharedUserDataPath, "Electron app data directory"); + app.setPath("userData", sharedUserDataPath); return app.getPath("userData"); } diff --git a/src/main/preload.ts b/packages/electron/src/main/preload.ts similarity index 98% rename from src/main/preload.ts rename to packages/electron/src/main/preload.ts index 4f45200..89e10f7 100644 --- a/src/main/preload.ts +++ b/packages/electron/src/main/preload.ts @@ -1,6 +1,6 @@ import { contextBridge, ipcRenderer } from "electron"; -import { browserErrorI18nLanguage, formatLocalizedErrorMessage } from "../shared/i18n"; -import { IPC_CHANNELS } from "../shared/ipc-channels"; +import { browserErrorI18nLanguage, formatLocalizedErrorMessage } from "@ccr/core/contracts/i18n"; +import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; import type { AgentAnalysisFilter, AgentAnalysisSnapshot, @@ -73,8 +73,8 @@ import type { UsageStatsFilter, UsageStatsRange, UsageStatsSnapshot -} from "../shared/app"; -import type { ProviderPreset } from "../shared/provider-presets"; +} from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; function invoke(channel: string, ...args: unknown[]): Promise { return ipcRenderer.invoke(channel, ...args).catch((error) => { diff --git a/src/main/tray-controller.ts b/packages/electron/src/main/tray-controller.ts similarity index 98% rename from src/main/tray-controller.ts rename to packages/electron/src/main/tray-controller.ts index 7379dd0..b3d287c 100644 --- a/src/main/tray-controller.ts +++ b/packages/electron/src/main/tray-controller.ts @@ -2,12 +2,12 @@ import { BrowserWindow, Menu, Tray, app, nativeImage, screen } from "electron"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { deflateSync } from "node:zlib"; -import { loadAppConfig } from "./config"; -import { APP_NAME } from "./constants"; -import { getProviderAccountSnapshots } from "./provider-account-service"; -import { getTodayUsageTotals, onUsageRecorded } from "./usage-store"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { APP_NAME } from "@ccr/core/config/constants"; +import { getProviderAccountSnapshots } from "@ccr/core/providers/account-service"; +import { getTodayUsageTotals, onUsageRecorded } from "@ccr/core/usage/store"; import windowsManager from "./windows"; -import type { AppConfig, ProviderAccountMeter, TrayBalanceProgressConfig, TrayIconPreference } from "../shared/app"; +import type { AppConfig, ProviderAccountMeter, TrayBalanceProgressConfig, TrayIconPreference } from "@ccr/core/contracts/app"; const popoverMenuWidth = 420; const popoverPreferredHeight = 740; diff --git a/src/main/update-service.ts b/packages/electron/src/main/update-service.ts similarity index 98% rename from src/main/update-service.ts rename to packages/electron/src/main/update-service.ts index 290ba6a..a507e60 100644 --- a/src/main/update-service.ts +++ b/packages/electron/src/main/update-service.ts @@ -1,8 +1,8 @@ import { app } from "electron"; import { autoUpdater, type ProgressInfo, type UpdateDownloadedEvent, type UpdateInfo } from "electron-updater"; -import { IPC_CHANNELS } from "./constants"; +import { IPC_CHANNELS } from "@ccr/core/config/constants"; import windowsManager from "./windows"; -import type { AppUpdateStatus } from "../shared/app"; +import type { AppUpdateStatus } from "@ccr/core/contracts/app"; type InstallPreparation = () => Promise; type UpdateCheckOptions = { diff --git a/src/main/windows.ts b/packages/electron/src/main/windows.ts similarity index 99% rename from src/main/windows.ts rename to packages/electron/src/main/windows.ts index 634ec51..81f7070 100644 --- a/src/main/windows.ts +++ b/packages/electron/src/main/windows.ts @@ -2,7 +2,7 @@ import { app, BrowserWindow, screen } from "electron"; import { existsSync } from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { APP_NAME, IPC_CHANNELS, ONBOARDING_FINISHED_FILE } from "./constants"; +import { APP_NAME, IPC_CHANNELS, ONBOARDING_FINISHED_FILE } from "@ccr/core/config/constants"; type WindowName = "main" | string; type WindowBounds = { height: number; width: number; x?: number; y?: number }; diff --git a/packages/electron/tsconfig.json b/packages/electron/tsconfig.json new file mode 100644 index 0000000..1f70006 --- /dev/null +++ b/packages/electron/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..46df2e8 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,21 @@ +{ + "name": "@claude-code-router/ui", + "version": "3.0.7", + "private": true, + "description": "Claude Code Router web management UI.", + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "baseui": "^16.1.1", + "clsx": "^2.1.1", + "lucide-react": "^1.17.0", + "motion": "^12.40.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "recharts": "^3.8.1", + "styletron-engine-atomic": "^1.6.2", + "styletron-react": "^6.1.1", + "tailwind-merge": "^3.6.0" + } +} diff --git a/src/renderer/assets/agent-logos/claude-code.png b/packages/ui/src/assets/agent-logos/claude-code.png similarity index 100% rename from src/renderer/assets/agent-logos/claude-code.png rename to packages/ui/src/assets/agent-logos/claude-code.png diff --git a/src/renderer/assets/agent-logos/codex.png b/packages/ui/src/assets/agent-logos/codex.png similarity index 100% rename from src/renderer/assets/agent-logos/codex.png rename to packages/ui/src/assets/agent-logos/codex.png diff --git a/src/renderer/assets/agent-logos/zcode.png b/packages/ui/src/assets/agent-logos/zcode.png similarity index 100% rename from src/renderer/assets/agent-logos/zcode.png rename to packages/ui/src/assets/agent-logos/zcode.png diff --git a/packages/ui/src/assets/logo.png b/packages/ui/src/assets/logo.png new file mode 100644 index 0000000..76801d1 Binary files /dev/null and b/packages/ui/src/assets/logo.png differ diff --git a/src/renderer/assets/onboarding/mascot-transition.svg b/packages/ui/src/assets/onboarding/mascot-transition.svg similarity index 100% rename from src/renderer/assets/onboarding/mascot-transition.svg rename to packages/ui/src/assets/onboarding/mascot-transition.svg diff --git a/src/renderer/assets/provider-icons/anthropic.png b/packages/ui/src/assets/provider-icons/anthropic.png similarity index 100% rename from src/renderer/assets/provider-icons/anthropic.png rename to packages/ui/src/assets/provider-icons/anthropic.png diff --git a/src/renderer/assets/provider-icons/bailian.ico b/packages/ui/src/assets/provider-icons/bailian.ico similarity index 100% rename from src/renderer/assets/provider-icons/bailian.ico rename to packages/ui/src/assets/provider-icons/bailian.ico diff --git a/src/renderer/assets/provider-icons/deepseek.ico b/packages/ui/src/assets/provider-icons/deepseek.ico similarity index 100% rename from src/renderer/assets/provider-icons/deepseek.ico rename to packages/ui/src/assets/provider-icons/deepseek.ico diff --git a/src/renderer/assets/provider-icons/gemini.svg b/packages/ui/src/assets/provider-icons/gemini.svg similarity index 100% rename from src/renderer/assets/provider-icons/gemini.svg rename to packages/ui/src/assets/provider-icons/gemini.svg diff --git a/src/renderer/assets/provider-icons/mistral.webp b/packages/ui/src/assets/provider-icons/mistral.webp similarity index 100% rename from src/renderer/assets/provider-icons/mistral.webp rename to packages/ui/src/assets/provider-icons/mistral.webp diff --git a/src/renderer/assets/provider-icons/moonshot.ico b/packages/ui/src/assets/provider-icons/moonshot.ico similarity index 100% rename from src/renderer/assets/provider-icons/moonshot.ico rename to packages/ui/src/assets/provider-icons/moonshot.ico diff --git a/src/renderer/assets/provider-icons/openai.png b/packages/ui/src/assets/provider-icons/openai.png similarity index 100% rename from src/renderer/assets/provider-icons/openai.png rename to packages/ui/src/assets/provider-icons/openai.png diff --git a/src/renderer/assets/provider-icons/openrouter.ico b/packages/ui/src/assets/provider-icons/openrouter.ico similarity index 100% rename from src/renderer/assets/provider-icons/openrouter.ico rename to packages/ui/src/assets/provider-icons/openrouter.ico diff --git a/src/renderer/assets/provider-icons/runapi.jpg b/packages/ui/src/assets/provider-icons/runapi.jpg similarity index 100% rename from src/renderer/assets/provider-icons/runapi.jpg rename to packages/ui/src/assets/provider-icons/runapi.jpg diff --git a/src/renderer/assets/provider-icons/siliconflow.png b/packages/ui/src/assets/provider-icons/siliconflow.png similarity index 100% rename from src/renderer/assets/provider-icons/siliconflow.png rename to packages/ui/src/assets/provider-icons/siliconflow.png diff --git a/src/renderer/assets/provider-icons/teamorouter.png b/packages/ui/src/assets/provider-icons/teamorouter.png similarity index 100% rename from src/renderer/assets/provider-icons/teamorouter.png rename to packages/ui/src/assets/provider-icons/teamorouter.png diff --git a/src/renderer/assets/provider-icons/zai-global-coding.svg b/packages/ui/src/assets/provider-icons/zai-global-coding.svg similarity index 100% rename from src/renderer/assets/provider-icons/zai-global-coding.svg rename to packages/ui/src/assets/provider-icons/zai-global-coding.svg diff --git a/src/renderer/assets/provider-icons/zai-global-general.svg b/packages/ui/src/assets/provider-icons/zai-global-general.svg similarity index 100% rename from src/renderer/assets/provider-icons/zai-global-general.svg rename to packages/ui/src/assets/provider-icons/zai-global-general.svg diff --git a/src/renderer/assets/provider-icons/zhipu-cn-coding.png b/packages/ui/src/assets/provider-icons/zhipu-cn-coding.png similarity index 100% rename from src/renderer/assets/provider-icons/zhipu-cn-coding.png rename to packages/ui/src/assets/provider-icons/zhipu-cn-coding.png diff --git a/src/renderer/assets/provider-icons/zhipu-cn-general.png b/packages/ui/src/assets/provider-icons/zhipu-cn-general.png similarity index 100% rename from src/renderer/assets/provider-icons/zhipu-cn-general.png rename to packages/ui/src/assets/provider-icons/zhipu-cn-general.png diff --git a/packages/ui/src/assets/tray-cyan.png b/packages/ui/src/assets/tray-cyan.png new file mode 100644 index 0000000..b259c8a Binary files /dev/null and b/packages/ui/src/assets/tray-cyan.png differ diff --git a/packages/ui/src/assets/tray-orange.png b/packages/ui/src/assets/tray-orange.png new file mode 100644 index 0000000..8b7b544 Binary files /dev/null and b/packages/ui/src/assets/tray-orange.png differ diff --git a/packages/ui/src/assets/tray-violet.png b/packages/ui/src/assets/tray-violet.png new file mode 100644 index 0000000..65e5d81 Binary files /dev/null and b/packages/ui/src/assets/tray-violet.png differ diff --git a/src/renderer/components/ui/badge.tsx b/packages/ui/src/components/ui/badge.tsx similarity index 100% rename from src/renderer/components/ui/badge.tsx rename to packages/ui/src/components/ui/badge.tsx diff --git a/src/renderer/components/ui/button.tsx b/packages/ui/src/components/ui/button.tsx similarity index 100% rename from src/renderer/components/ui/button.tsx rename to packages/ui/src/components/ui/button.tsx diff --git a/src/renderer/components/ui/card.tsx b/packages/ui/src/components/ui/card.tsx similarity index 100% rename from src/renderer/components/ui/card.tsx rename to packages/ui/src/components/ui/card.tsx diff --git a/src/renderer/components/ui/checkbox.tsx b/packages/ui/src/components/ui/checkbox.tsx similarity index 100% rename from src/renderer/components/ui/checkbox.tsx rename to packages/ui/src/components/ui/checkbox.tsx diff --git a/src/renderer/components/ui/dialog.tsx b/packages/ui/src/components/ui/dialog.tsx similarity index 100% rename from src/renderer/components/ui/dialog.tsx rename to packages/ui/src/components/ui/dialog.tsx diff --git a/src/renderer/components/ui/input.tsx b/packages/ui/src/components/ui/input.tsx similarity index 100% rename from src/renderer/components/ui/input.tsx rename to packages/ui/src/components/ui/input.tsx diff --git a/src/renderer/components/ui/label.tsx b/packages/ui/src/components/ui/label.tsx similarity index 100% rename from src/renderer/components/ui/label.tsx rename to packages/ui/src/components/ui/label.tsx diff --git a/src/renderer/components/ui/popover.tsx b/packages/ui/src/components/ui/popover.tsx similarity index 100% rename from src/renderer/components/ui/popover.tsx rename to packages/ui/src/components/ui/popover.tsx diff --git a/src/renderer/components/ui/select.tsx b/packages/ui/src/components/ui/select.tsx similarity index 100% rename from src/renderer/components/ui/select.tsx rename to packages/ui/src/components/ui/select.tsx diff --git a/src/renderer/components/ui/switch.tsx b/packages/ui/src/components/ui/switch.tsx similarity index 100% rename from src/renderer/components/ui/switch.tsx rename to packages/ui/src/components/ui/switch.tsx diff --git a/src/renderer/components/ui/textarea.tsx b/packages/ui/src/components/ui/textarea.tsx similarity index 100% rename from src/renderer/components/ui/textarea.tsx rename to packages/ui/src/components/ui/textarea.tsx diff --git a/src/renderer/lib/baseui-provider.tsx b/packages/ui/src/lib/baseui-provider.tsx similarity index 100% rename from src/renderer/lib/baseui-provider.tsx rename to packages/ui/src/lib/baseui-provider.tsx diff --git a/src/renderer/lib/usage-activity.ts b/packages/ui/src/lib/usage-activity.ts similarity index 99% rename from src/renderer/lib/usage-activity.ts rename to packages/ui/src/lib/usage-activity.ts index 4af96aa..bbaae29 100644 --- a/src/renderer/lib/usage-activity.ts +++ b/packages/ui/src/lib/usage-activity.ts @@ -1,4 +1,4 @@ -import type { UsageSeriesPoint } from "../../shared/app"; +import type { UsageSeriesPoint } from "@ccr/core/contracts/app"; export type TokenActivityCell = { date: Date; diff --git a/src/renderer/lib/utils.ts b/packages/ui/src/lib/utils.ts similarity index 100% rename from src/renderer/lib/utils.ts rename to packages/ui/src/lib/utils.ts diff --git a/src/renderer/pages/browser/index.html b/packages/ui/src/pages/browser/index.html similarity index 100% rename from src/renderer/pages/browser/index.html rename to packages/ui/src/pages/browser/index.html diff --git a/src/renderer/pages/browser/main.tsx b/packages/ui/src/pages/browser/main.tsx similarity index 99% rename from src/renderer/pages/browser/main.tsx rename to packages/ui/src/pages/browser/main.tsx index bcb44d6..578481e 100644 --- a/src/renderer/pages/browser/main.tsx +++ b/packages/ui/src/pages/browser/main.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState, type FormEvent } from "react"; import { createRoot } from "react-dom/client"; import { ArrowLeft, ArrowRight, Globe2, LoaderCircle, Plus, RotateCw, Search, X } from "lucide-react"; -import type { BuiltInBrowserState } from "../../../shared/app"; +import type { BuiltInBrowserState } from "@ccr/core/contracts/app"; declare global { interface Window { diff --git a/src/renderer/pages/home/App.tsx b/packages/ui/src/pages/home/App.tsx similarity index 99% rename from src/renderer/pages/home/App.tsx rename to packages/ui/src/pages/home/App.tsx index be2163d..e149b0c 100644 --- a/src/renderer/pages/home/App.tsx +++ b/packages/ui/src/pages/home/App.tsx @@ -33,11 +33,11 @@ import { uniqueRoutingRuleId, updateApiKeyEditableConfig, UsageStatsFilter, UsageStatsRange, UsageStatsSnapshot, useEffect, useMemo, useReducedMotion, useRef, useState, validateVirtualModelDraft, ViewId, VirtualModelDraft, virtualModelProfileFromDraft -} from "./shared"; +} from "./shared/index"; import { startVisiblePolling } from "./shared/polling"; import { AppDialogStack, LightToast, MainLayout, OnboardingLayout -} from "./components"; +} from "./components/index"; type ProfileOpenDialogState = { busy?: "" | "cli" | "app"; diff --git a/src/renderer/pages/home/components/api-keys.tsx b/packages/ui/src/pages/home/components/api-keys.tsx similarity index 99% rename from src/renderer/pages/home/components/api-keys.tsx rename to packages/ui/src/pages/home/components/api-keys.tsx index 231ab72..d1b6d44 100644 --- a/src/renderer/pages/home/components/api-keys.tsx +++ b/packages/ui/src/pages/home/components/api-keys.tsx @@ -7,7 +7,7 @@ import { formatApiKeyExpiration, formatApiKeyLimits, Input, KeyRound, limitWindowOptions, LimitWindowPreset, motion, Pencil, Plus, Search, SelectControl, translateOptions, Trash2, useAppText, useMemo, useState, X -} from "../shared"; +} from "../shared/index"; export function ApiKeysView({ addApiKey, apiKeys, diff --git a/src/renderer/pages/home/components/dashboard.tsx b/packages/ui/src/pages/home/components/dashboard.tsx similarity index 99% rename from src/renderer/pages/home/components/dashboard.tsx rename to packages/ui/src/pages/home/components/dashboard.tsx index e1e2cd9..1f7f517 100644 --- a/src/renderer/pages/home/components/dashboard.tsx +++ b/packages/ui/src/pages/home/components/dashboard.tsx @@ -21,7 +21,7 @@ import { UsageSeriesPoint, UsageStatsRange, UsageStatsSnapshot, usageStatusTone, UsageTotals, useAppText, useEffect, useMemo, useRef, useSensor, useSensors, useSortable, useState, X, XAxis, YAxis -} from "../shared"; +} from "../shared/index"; import { buildTokenActivity, type TokenActivityCell } from "@/lib/usage-activity"; import { ShareCardWidget } from "./share-cards"; import { Cloud, Rocket } from "lucide-react"; diff --git a/src/renderer/pages/home/components/dialog-stack.tsx b/packages/ui/src/pages/home/components/dialog-stack.tsx similarity index 98% rename from src/renderer/pages/home/components/dialog-stack.tsx rename to packages/ui/src/pages/home/components/dialog-stack.tsx index b254698..cc33a3f 100644 --- a/src/renderer/pages/home/components/dialog-stack.tsx +++ b/packages/ui/src/pages/home/components/dialog-stack.tsx @@ -1,5 +1,5 @@ import type { ComponentProps, ReactElement } from "react"; -import { AnimatePresence, DialogStackLayer } from "../shared"; +import { AnimatePresence, DialogStackLayer } from "../shared/index"; import { AddApiKeyDialog, ApiKeyCreatedDialog, EditApiKeyDialog } from "./api-keys"; import { ConfigureClaudeDesignDialog, DeleteExtensionDialog, PluginSettingsDialog } from "./extensions"; import { AddProfileDialog, ProfileOpenDialog } from "./profiles"; diff --git a/src/renderer/pages/home/components/extensions.tsx b/packages/ui/src/pages/home/components/extensions.tsx similarity index 99% rename from src/renderer/pages/home/components/extensions.tsx rename to packages/ui/src/pages/home/components/extensions.tsx index 0535a16..05e8b00 100644 --- a/src/renderer/pages/home/components/extensions.tsx +++ b/packages/ui/src/pages/home/components/extensions.tsx @@ -7,7 +7,7 @@ import { Label, motion, normalizeClaudeDesignRuleTypeChange, PluginSettingsDraft, Plus, RouteTargetControl, Search, SelectControl, Settings, TextAreaControl, Toggle, translateOptions, Trash2, useAppText, useMemo, useState, X -} from "../shared"; +} from "../shared/index"; export function ExtensionsView({ configureExtension, config, diff --git a/src/renderer/pages/home/components/feedback.tsx b/packages/ui/src/pages/home/components/feedback.tsx similarity index 97% rename from src/renderer/pages/home/components/feedback.tsx rename to packages/ui/src/pages/home/components/feedback.tsx index 14782ec..182818b 100644 --- a/src/renderer/pages/home/components/feedback.tsx +++ b/packages/ui/src/pages/home/components/feedback.tsx @@ -1,7 +1,7 @@ import { AnimatePresence, AppToast, Check, motion, motionEase, reducedMotionTransition, useReducedMotion -} from "../shared"; +} from "../shared/index"; export function LightToast({ toast }: { toast?: AppToast }) { const shouldReduceMotion = useReducedMotion(); diff --git a/src/renderer/pages/home/components/index.ts b/packages/ui/src/pages/home/components/index.ts similarity index 100% rename from src/renderer/pages/home/components/index.ts rename to packages/ui/src/pages/home/components/index.ts diff --git a/src/renderer/pages/home/components/layout.tsx b/packages/ui/src/pages/home/components/layout.tsx similarity index 99% rename from src/renderer/pages/home/components/layout.tsx rename to packages/ui/src/pages/home/components/layout.tsx index 2dfca05..663d7ac 100644 --- a/src/renderer/pages/home/components/layout.tsx +++ b/packages/ui/src/pages/home/components/layout.tsx @@ -5,7 +5,7 @@ import { LoaderCircle, NavigationId, PanelLeftClose, PanelLeftOpen, reducedMotionTransition, ServiceControlButton, Settings, ViewId, ViewMotionShell, viewUsesInternalScroll -} from "../shared"; +} from "../shared/index"; import { ApiKeysView } from "./api-keys"; import { AgentAnalysisView, OverviewView } from "./dashboard"; import { ExtensionsView } from "./extensions"; diff --git a/src/renderer/pages/home/components/network-logs.tsx b/packages/ui/src/pages/home/components/network-logs.tsx similarity index 99% rename from src/renderer/pages/home/components/network-logs.tsx rename to packages/ui/src/pages/home/components/network-logs.tsx index c209c4f..79b5303 100644 --- a/src/renderer/pages/home/components/network-logs.tsx +++ b/packages/ui/src/pages/home/components/network-logs.tsx @@ -13,7 +13,7 @@ import { RequestLogPage, requestLogPageSizeOptions, RequestLogStatusFilter, requestLogStatusOptions, Search, Select, translateOptions, Trash2, useAppNumberLocale, useAppText, useCallback, useEffect, useMemo, useRef, useState -} from "../shared"; +} from "../shared/index"; type NetworkRequestTab = "body" | "header" | "query" | "raw" | "summary"; type NetworkResponseTab = "body" | "header" | "raw"; diff --git a/src/renderer/pages/home/components/onboarding.tsx b/packages/ui/src/pages/home/components/onboarding.tsx similarity index 99% rename from src/renderer/pages/home/components/onboarding.tsx rename to packages/ui/src/pages/home/components/onboarding.tsx index d2ee1b6..9700048 100644 --- a/src/renderer/pages/home/components/onboarding.tsx +++ b/packages/ui/src/pages/home/components/onboarding.tsx @@ -5,7 +5,7 @@ import { LoaderCircle, onboardingMascotSpriteUrl, OnboardingReadinessOptions, OnboardingStepId, onboardingStepOrder, ProviderConnectivityCheckReport, reducedMotionTransition, useAppText, useReducedMotion, useState, UserRound, X -} from "../shared"; +} from "../shared/index"; import { AddProviderForm } from "./providers"; import { AddProfileForm } from "./profiles"; diff --git a/src/renderer/pages/home/components/profiles.tsx b/packages/ui/src/pages/home/components/profiles.tsx similarity index 99% rename from src/renderer/pages/home/components/profiles.tsx rename to packages/ui/src/pages/home/components/profiles.tsx index 6e1ad5f..f62f282 100644 --- a/src/renderer/pages/home/components/profiles.tsx +++ b/packages/ui/src/pages/home/components/profiles.tsx @@ -9,7 +9,7 @@ import { Play, Power, RefreshCw, Search, Select, SelectControl, Terminal, Toggle, translateOptions, Trash2, useAppErrorText, useAppText, type ProfileOpenSurface, type ProfileRuntimeStatus, type ReactNode, type VirtualModelProfileConfig, copyTextToClipboard, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, X -} from "../shared"; +} from "../shared/index"; type ProfileActionBusy = { profileId: string; diff --git a/src/renderer/pages/home/components/providers.tsx b/packages/ui/src/pages/home/components/providers.tsx similarity index 99% rename from src/renderer/pages/home/components/providers.tsx rename to packages/ui/src/pages/home/components/providers.tsx index 8c50a3c..3cd40b3 100644 --- a/src/renderer/pages/home/components/providers.tsx +++ b/packages/ui/src/pages/home/components/providers.tsx @@ -16,9 +16,9 @@ import { resolveProviderDeepLinkPreset, ShieldCheck, splitLines, splitModelTagInput, Switch, Textarea, translatedProviderProtocolLabel, translateOptions, translateProbeProtocolMessage, Trash2, uniqueProviderName, uniqueProviderProtocols, useAppErrorText, useAppText, useEffect, useMemo, useRef, useState, X, isPlainRecord -} from "../shared"; -import { providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; -import type { LocalAgentProviderCandidate } from "../../../../shared/app"; +} from "../shared/index"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; +import type { LocalAgentProviderCandidate } from "@ccr/core/contracts/app"; export function ProvidersView({ accountSnapshots, addProvider, editProvider, notify, providers, removeProvider }: { accountSnapshots: ProviderAccountSnapshot[]; addProvider: () => void; diff --git a/src/renderer/pages/home/components/routing.tsx b/packages/ui/src/pages/home/components/routing.tsx similarity index 99% rename from src/renderer/pages/home/components/routing.tsx rename to packages/ui/src/pages/home/components/routing.tsx index 4f54c8e..a35b8e0 100644 --- a/src/renderer/pages/home/components/routing.tsx +++ b/packages/ui/src/pages/home/components/routing.tsx @@ -9,8 +9,8 @@ import { RouterBuiltInAgentRuleConfig, RouteTargetControl, routingRuleRowMatchesQuery, Search, SelectControl, Toggle, translateOptions, Trash2, uniqueStrings, useAppText, useContext, useMemo, useState, X -} from "../shared"; -import { ROUTER_FALLBACK_MAX_RETRY_COUNT } from "../../../../shared/app"; +} from "../shared/index"; +import { ROUTER_FALLBACK_MAX_RETRY_COUNT } from "@ccr/core/contracts/app"; export function RoutingView({ addRule, config, diff --git a/src/renderer/pages/home/components/server.tsx b/packages/ui/src/pages/home/components/server.tsx similarity index 99% rename from src/renderer/pages/home/components/server.tsx rename to packages/ui/src/pages/home/components/server.tsx index 71e7eb3..378d254 100644 --- a/src/renderer/pages/home/components/server.tsx +++ b/packages/ui/src/pages/home/components/server.tsx @@ -4,7 +4,7 @@ import { Input, LoaderCircle, motion, numberValue, ProxyCertificateStatus, proxyCertificateTrustSteps, ProxyStatus, RefreshCw, ServerActionBusy, ShieldCheck, StatusBadge, Toggle, translateProxyCertificateMessage, useAppText -} from "../shared"; +} from "../shared/index"; export function ServerView({ actionBusy, actionError, diff --git a/src/renderer/pages/home/components/settings.tsx b/packages/ui/src/pages/home/components/settings.tsx similarity index 99% rename from src/renderer/pages/home/components/settings.tsx rename to packages/ui/src/pages/home/components/settings.tsx index 4fba842..4a6adea 100644 --- a/src/renderer/pages/home/components/settings.tsx +++ b/packages/ui/src/pages/home/components/settings.tsx @@ -11,7 +11,7 @@ import { TrayBalanceProgressConfig, TrayComponentVariants, TrayWidgetConfig, TrayWidgetType, TrayWidgetVariant, appLogoUrl, trayMascotIconUrls, arrayMove, defaultTrayWidgetVariant, isTraySingletonWidgetType, normalizeTrayWidget, normalizeTrayWidgets, Switch, Trash2, trayWidgetVariantOptions, useEffect, useMemo, useRef, useSensor, useSensors, useSortable, useState, X -} from "../shared"; +} from "../shared/index"; const settingsPageContentWidthClassName = "mx-auto w-full max-w-[900px]"; diff --git a/src/renderer/pages/home/components/share-cards.tsx b/packages/ui/src/pages/home/components/share-cards.tsx similarity index 99% rename from src/renderer/pages/home/components/share-cards.tsx rename to packages/ui/src/pages/home/components/share-cards.tsx index b356f20..ed67437 100644 --- a/src/renderer/pages/home/components/share-cards.tsx +++ b/packages/ui/src/pages/home/components/share-cards.tsx @@ -3,7 +3,7 @@ import { formatProviderAccountMeterValue, formatUsdCost, LoaderCircle, primaryProviderAccountMeter, providerAccountMeterRemainingRatio, providerAccountSnapshotKey, ReactNode, UsageStatsRange, UsageStatsSnapshot, useRef, useState, useAppText, type OverviewWidgetType, type ProviderAccountMeter, type ProviderAccountSnapshot -} from "../shared"; +} from "../shared/index"; import { buildTokenActivity, type TokenActivityCell } from "@/lib/usage-activity"; type ShareCardTone = "amber" | "blue" | "emerald" | "indigo" | "rose" | "slate" | "teal"; diff --git a/src/renderer/pages/home/components/update.tsx b/packages/ui/src/pages/home/components/update.tsx similarity index 99% rename from src/renderer/pages/home/components/update.tsx rename to packages/ui/src/pages/home/components/update.tsx index 1d29370..a8fba2b 100644 --- a/src/renderer/pages/home/components/update.tsx +++ b/packages/ui/src/pages/home/components/update.tsx @@ -1,7 +1,7 @@ import { AppCopy, AppUpdateStatus, Button, Check, CircleAlert, cn, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Download, LoaderCircle, RefreshCw, X -} from "../shared"; +} from "../shared/index"; export type UpdateActionBusy = "" | "check" | "download" | "install"; diff --git a/src/renderer/pages/home/components/virtual-models.tsx b/packages/ui/src/pages/home/components/virtual-models.tsx similarity index 99% rename from src/renderer/pages/home/components/virtual-models.tsx rename to packages/ui/src/pages/home/components/virtual-models.tsx index 6debc9d..6e7c2c4 100644 --- a/src/renderer/pages/home/components/virtual-models.tsx +++ b/packages/ui/src/pages/home/components/virtual-models.tsx @@ -12,7 +12,7 @@ import { useRef, useState, validateMcpServerDraft, virtualModelBaseModelSummary, VirtualModelDraft, virtualModelMatchesQuery, virtualModelMatchSummary, type KeyValueDraftRow, VirtualModelProfileConfig, virtualModelToolSummary, X -} from "../shared"; +} from "../shared/index"; const virtualModelTableGridClass = "grid-cols-[minmax(180px,0.9fr)_minmax(220px,1.1fr)_minmax(220px,1.1fr)_minmax(170px,0.85fr)_112px_96px]"; const virtualModelTableMinWidthClass = "min-w-[1100px]"; diff --git a/src/renderer/pages/home/index.html b/packages/ui/src/pages/home/index.html similarity index 100% rename from src/renderer/pages/home/index.html rename to packages/ui/src/pages/home/index.html diff --git a/src/renderer/pages/home/main.tsx b/packages/ui/src/pages/home/main.tsx similarity index 100% rename from src/renderer/pages/home/main.tsx rename to packages/ui/src/pages/home/main.tsx diff --git a/src/renderer/pages/home/shared/api-keys.ts b/packages/ui/src/pages/home/shared/api-keys.ts similarity index 97% rename from src/renderer/pages/home/shared/api-keys.ts rename to packages/ui/src/pages/home/shared/api-keys.ts index 88eba12..58bf972 100644 --- a/src/renderer/pages/home/shared/api-keys.ts +++ b/packages/ui/src/pages/home/shared/api-keys.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/common.ts b/packages/ui/src/pages/home/shared/common.ts similarity index 98% rename from src/renderer/pages/home/shared/common.ts rename to packages/ui/src/pages/home/shared/common.ts index 246a6fa..d386424 100644 --- a/src/renderer/pages/home/shared/common.ts +++ b/packages/ui/src/pages/home/shared/common.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/config.ts b/packages/ui/src/pages/home/shared/config.ts similarity index 96% rename from src/renderer/pages/home/shared/config.ts rename to packages/ui/src/pages/home/shared/config.ts index 538ae25..310a373 100644 --- a/src/renderer/pages/home/shared/config.ts +++ b/packages/ui/src/pages/home/shared/config.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/controls.tsx b/packages/ui/src/pages/home/shared/controls.tsx similarity index 98% rename from src/renderer/pages/home/shared/controls.tsx rename to packages/ui/src/pages/home/shared/controls.tsx index 3d312ed..266b6d8 100644 --- a/src/renderer/pages/home/shared/controls.tsx +++ b/packages/ui/src/pages/home/shared/controls.tsx @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/extensions.ts b/packages/ui/src/pages/home/shared/extensions.ts similarity index 98% rename from src/renderer/pages/home/shared/extensions.ts rename to packages/ui/src/pages/home/shared/extensions.ts index 0c51be2..877fd31 100644 --- a/src/renderer/pages/home/shared/extensions.ts +++ b/packages/ui/src/pages/home/shared/extensions.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/external.tsx b/packages/ui/src/pages/home/shared/external.tsx similarity index 97% rename from src/renderer/pages/home/shared/external.tsx rename to packages/ui/src/pages/home/shared/external.tsx index 0b93531..4aa2f32 100644 --- a/src/renderer/pages/home/shared/external.tsx +++ b/packages/ui/src/pages/home/shared/external.tsx @@ -101,7 +101,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -118,9 +118,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -136,7 +136,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -242,7 +242,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -250,7 +250,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetByIdentityInList, @@ -259,8 +259,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/fallbacks.ts b/packages/ui/src/pages/home/shared/fallbacks.ts similarity index 94% rename from src/renderer/pages/home/shared/fallbacks.ts rename to packages/ui/src/pages/home/shared/fallbacks.ts index 87d829d..fe77298 100644 --- a/src/renderer/pages/home/shared/fallbacks.ts +++ b/packages/ui/src/pages/home/shared/fallbacks.ts @@ -6,8 +6,8 @@ import type { ProxyCertificateStatus, ProxyNetworkSnapshot, ProxyStatus -} from "../../../../shared/app"; -import { createDefaultAppConfig } from "../../../../shared/default-config"; +} from "@ccr/core/contracts/app"; +import { createDefaultAppConfig } from "@ccr/core/config/default-config"; export const fallbackInfo: AppInfo = { appConfigDbFile: "Browser preview", diff --git a/src/renderer/pages/home/shared/i18n.tsx b/packages/ui/src/pages/home/shared/i18n.tsx similarity index 99% rename from src/renderer/pages/home/shared/i18n.tsx rename to packages/ui/src/pages/home/shared/i18n.tsx index b5e7dfc..4042095 100644 --- a/src/renderer/pages/home/shared/i18n.tsx +++ b/packages/ui/src/pages/home/shared/i18n.tsx @@ -1,5 +1,5 @@ import { createContext, useContext, useMemo } from "react"; -import { translateErrorMessage } from "../../../../shared/i18n"; +import { translateErrorMessage } from "@ccr/core/contracts/i18n"; type NavigationId = "onboarding" | "overview" | "observability" | "api-keys" | "server" | "profile" | "networking" | "logs" | "providers" | "models" | "routing" | "virtual-models" | "extensions"; type ResolvedLanguage = "en" | "zh"; diff --git a/src/renderer/pages/home/shared/index.tsx b/packages/ui/src/pages/home/shared/index.tsx similarity index 90% rename from src/renderer/pages/home/shared/index.tsx rename to packages/ui/src/pages/home/shared/index.tsx index bf40b7f..678d7b6 100644 --- a/src/renderer/pages/home/shared/index.tsx +++ b/packages/ui/src/pages/home/shared/index.tsx @@ -18,4 +18,4 @@ export * from "./routing"; export * from "./virtual-models"; export * from "./extensions"; export * from "./providers"; -export type { RouterBuiltInAgentRuleConfig, RouterBuiltInAgentRuleId, RouterBuiltInRulesConfig } from "../../../../shared/app"; +export type { RouterBuiltInAgentRuleConfig, RouterBuiltInAgentRuleId, RouterBuiltInRulesConfig } from "@ccr/core/contracts/app"; diff --git a/src/renderer/pages/home/shared/logs.ts b/packages/ui/src/pages/home/shared/logs.ts similarity index 99% rename from src/renderer/pages/home/shared/logs.ts rename to packages/ui/src/pages/home/shared/logs.ts index 9066f1d..5727531 100644 --- a/src/renderer/pages/home/shared/logs.ts +++ b/packages/ui/src/pages/home/shared/logs.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/motion.tsx b/packages/ui/src/pages/home/shared/motion.tsx similarity index 100% rename from src/renderer/pages/home/shared/motion.tsx rename to packages/ui/src/pages/home/shared/motion.tsx diff --git a/src/renderer/pages/home/shared/network.ts b/packages/ui/src/pages/home/shared/network.ts similarity index 98% rename from src/renderer/pages/home/shared/network.ts rename to packages/ui/src/pages/home/shared/network.ts index 676cb2d..7782274 100644 --- a/src/renderer/pages/home/shared/network.ts +++ b/packages/ui/src/pages/home/shared/network.ts @@ -1,4 +1,4 @@ -import type { ProxyNetworkExchange } from "../../../../shared/app"; +import type { ProxyNetworkExchange } from "@ccr/core/contracts/app"; export function networkExchangeMatchesQuery(exchange: ProxyNetworkExchange, query: string): boolean { if (!query) { diff --git a/src/renderer/pages/home/shared/options.ts b/packages/ui/src/pages/home/shared/options.ts similarity index 98% rename from src/renderer/pages/home/shared/options.ts rename to packages/ui/src/pages/home/shared/options.ts index 240d6bb..2415bb1 100644 --- a/src/renderer/pages/home/shared/options.ts +++ b/packages/ui/src/pages/home/shared/options.ts @@ -18,7 +18,7 @@ import { BUILTIN_FUSION_VISION_TOOL_NAME, BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME, OVERVIEW_WIDGET_SIZE_VALUES -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentKind, AppConfig, @@ -40,7 +40,7 @@ import type { VirtualModelExecutionMode, VirtualModelFusionWebSearchProvider, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import anthropicProviderIconUrl from "@/assets/provider-icons/anthropic.png"; import bailianProviderIconUrl from "@/assets/provider-icons/bailian.ico"; import deepseekProviderIconUrl from "@/assets/provider-icons/deepseek.ico"; @@ -56,9 +56,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; type ViewId = "onboarding" | "overview" | "observability" | "api-keys" | "server" | "profile" | "networking" | "logs" | "providers" | "models" | "routing" | "virtual-models" | "extensions"; type NavigationId = ViewId; diff --git a/src/renderer/pages/home/shared/polling.ts b/packages/ui/src/pages/home/shared/polling.ts similarity index 100% rename from src/renderer/pages/home/shared/polling.ts rename to packages/ui/src/pages/home/shared/polling.ts diff --git a/src/renderer/pages/home/shared/profiles.ts b/packages/ui/src/pages/home/shared/profiles.ts similarity index 99% rename from src/renderer/pages/home/shared/profiles.ts rename to packages/ui/src/pages/home/shared/profiles.ts index bd092eb..285b464 100644 --- a/src/renderer/pages/home/shared/profiles.ts +++ b/packages/ui/src/pages/home/shared/profiles.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import zcodeLogoUrl from "@/assets/agent-logos/zcode.png"; @@ -117,9 +117,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -136,7 +136,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -235,7 +235,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -243,7 +243,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -251,8 +251,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/provider-accounts.ts b/packages/ui/src/pages/home/shared/provider-accounts.ts similarity index 97% rename from src/renderer/pages/home/shared/provider-accounts.ts rename to packages/ui/src/pages/home/shared/provider-accounts.ts index 335952f..3e726ae 100644 --- a/src/renderer/pages/home/shared/provider-accounts.ts +++ b/packages/ui/src/pages/home/shared/provider-accounts.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/providers.ts b/packages/ui/src/pages/home/shared/providers.ts similarity index 99% rename from src/renderer/pages/home/shared/providers.ts rename to packages/ui/src/pages/home/shared/providers.ts index bd3c4f4..5332284 100644 --- a/src/renderer/pages/home/shared/providers.ts +++ b/packages/ui/src/pages/home/shared/providers.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import zcodeLogoUrl from "@/assets/agent-logos/zcode.png"; @@ -117,9 +117,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -135,7 +135,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -235,7 +235,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -243,7 +243,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -251,8 +251,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/routing.ts b/packages/ui/src/pages/home/shared/routing.ts similarity index 98% rename from src/renderer/pages/home/shared/routing.ts rename to packages/ui/src/pages/home/shared/routing.ts index 397b133..bcc952a 100644 --- a/src/renderer/pages/home/shared/routing.ts +++ b/packages/ui/src/pages/home/shared/routing.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -135,7 +135,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -236,7 +236,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -244,7 +244,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -252,8 +252,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/services.tsx b/packages/ui/src/pages/home/shared/services.tsx similarity index 97% rename from src/renderer/pages/home/shared/services.tsx rename to packages/ui/src/pages/home/shared/services.tsx index 0ab4c34..21c41ed 100644 --- a/src/renderer/pages/home/shared/services.tsx +++ b/packages/ui/src/pages/home/shared/services.tsx @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/types.ts b/packages/ui/src/pages/home/shared/types.ts similarity index 97% rename from src/renderer/pages/home/shared/types.ts rename to packages/ui/src/pages/home/shared/types.ts index 2729e00..2a15c81 100644 --- a/src/renderer/pages/home/shared/types.ts +++ b/packages/ui/src/pages/home/shared/types.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -234,7 +234,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -242,7 +242,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -250,8 +250,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/usage.ts b/packages/ui/src/pages/home/shared/usage.ts similarity index 99% rename from src/renderer/pages/home/shared/usage.ts rename to packages/ui/src/pages/home/shared/usage.ts index 76cc6c6..040988d 100644 --- a/src/renderer/pages/home/shared/usage.ts +++ b/packages/ui/src/pages/home/shared/usage.ts @@ -6,7 +6,7 @@ import type { UsageStatsRange, UsageStatsSnapshot, UsageTotals -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentFilterValue } from "./options"; function positiveInteger(value: unknown): number | undefined { diff --git a/src/renderer/pages/home/shared/virtual-models.ts b/packages/ui/src/pages/home/shared/virtual-models.ts similarity index 99% rename from src/renderer/pages/home/shared/virtual-models.ts rename to packages/ui/src/pages/home/shared/virtual-models.ts index c71cdd0..05c106c 100644 --- a/src/renderer/pages/home/shared/virtual-models.ts +++ b/packages/ui/src/pages/home/shared/virtual-models.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/tray/TrayApp.tsx b/packages/ui/src/pages/tray/TrayApp.tsx similarity index 99% rename from src/renderer/pages/tray/TrayApp.tsx rename to packages/ui/src/pages/tray/TrayApp.tsx index 4592206..7374208 100644 --- a/src/renderer/pages/tray/TrayApp.tsx +++ b/packages/ui/src/pages/tray/TrayApp.tsx @@ -7,7 +7,7 @@ import { import { AccountSummaryPanel, AnimatedUsageChart, ChartShell, ModelShareChart, RingMetrics, SourceGrid, StatsGrid, TokenActivityPanel, TokenMixPanel, TrayStatusStrip -} from "./components"; +} from "./components/index"; type TrayHeaderRange = Exclude; diff --git a/src/renderer/pages/tray/TrayDetailApp.tsx b/packages/ui/src/pages/tray/TrayDetailApp.tsx similarity index 99% rename from src/renderer/pages/tray/TrayDetailApp.tsx rename to packages/ui/src/pages/tray/TrayDetailApp.tsx index af0627e..23984a6 100644 --- a/src/renderer/pages/tray/TrayDetailApp.tsx +++ b/packages/ui/src/pages/tray/TrayDetailApp.tsx @@ -5,7 +5,7 @@ import { } from "./shared"; import { TrayStatusStrip, UsageDetailPanel -} from "./components"; +} from "./components/index"; export function TrayDetailApp({ provider }: { provider?: string }) { const t = useTrayText(); diff --git a/src/renderer/pages/tray/components/account-panel.tsx b/packages/ui/src/pages/tray/components/account-panel.tsx similarity index 100% rename from src/renderer/pages/tray/components/account-panel.tsx rename to packages/ui/src/pages/tray/components/account-panel.tsx diff --git a/src/renderer/pages/tray/components/index.ts b/packages/ui/src/pages/tray/components/index.ts similarity index 100% rename from src/renderer/pages/tray/components/index.ts rename to packages/ui/src/pages/tray/components/index.ts diff --git a/src/renderer/pages/tray/components/overview-panel.tsx b/packages/ui/src/pages/tray/components/overview-panel.tsx similarity index 100% rename from src/renderer/pages/tray/components/overview-panel.tsx rename to packages/ui/src/pages/tray/components/overview-panel.tsx diff --git a/src/renderer/pages/tray/components/source-grid.tsx b/packages/ui/src/pages/tray/components/source-grid.tsx similarity index 100% rename from src/renderer/pages/tray/components/source-grid.tsx rename to packages/ui/src/pages/tray/components/source-grid.tsx diff --git a/src/renderer/pages/tray/components/status-strip.tsx b/packages/ui/src/pages/tray/components/status-strip.tsx similarity index 100% rename from src/renderer/pages/tray/components/status-strip.tsx rename to packages/ui/src/pages/tray/components/status-strip.tsx diff --git a/src/renderer/pages/tray/components/usage-detail.tsx b/packages/ui/src/pages/tray/components/usage-detail.tsx similarity index 100% rename from src/renderer/pages/tray/components/usage-detail.tsx rename to packages/ui/src/pages/tray/components/usage-detail.tsx diff --git a/src/renderer/pages/tray/components/widgets.tsx b/packages/ui/src/pages/tray/components/widgets.tsx similarity index 100% rename from src/renderer/pages/tray/components/widgets.tsx rename to packages/ui/src/pages/tray/components/widgets.tsx diff --git a/src/renderer/pages/tray/index.html b/packages/ui/src/pages/tray/index.html similarity index 100% rename from src/renderer/pages/tray/index.html rename to packages/ui/src/pages/tray/index.html diff --git a/src/renderer/pages/tray/main.tsx b/packages/ui/src/pages/tray/main.tsx similarity index 100% rename from src/renderer/pages/tray/main.tsx rename to packages/ui/src/pages/tray/main.tsx diff --git a/src/renderer/pages/tray/shared.tsx b/packages/ui/src/pages/tray/shared.tsx similarity index 98% rename from src/renderer/pages/tray/shared.tsx rename to packages/ui/src/pages/tray/shared.tsx index 2a5be7e..1976d25 100644 --- a/src/renderer/pages/tray/shared.tsx +++ b/packages/ui/src/pages/tray/shared.tsx @@ -1,12 +1,12 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; import { createRoot } from "react-dom/client"; import { LoaderCircle, Power, RefreshCw } from "lucide-react"; -import appLogoUrl from "../../../../assets/logo.png"; -import trayCyanIconUrl from "../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../assets/tray-violet.png"; -import { DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS } from "../../../shared/app"; -import { formatLocalizedErrorMessage } from "../../../shared/i18n"; +import appLogoUrl from "@/assets/logo.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; +import { DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS } from "@ccr/core/contracts/app"; +import { formatLocalizedErrorMessage } from "@ccr/core/contracts/i18n"; import type { AppConfig, ProviderAccountMeter, @@ -22,7 +22,7 @@ import type { UsageStatsRange, UsageStatsSnapshot, UsageTotals -} from "../../../shared/app"; +} from "@ccr/core/contracts/app"; export { createContext, useCallback, useContext, useEffect, useMemo, useState, createRoot, diff --git a/src/renderer/styles/globals.css b/packages/ui/src/styles/globals.css similarity index 99% rename from src/renderer/styles/globals.css rename to packages/ui/src/styles/globals.css index 420d8f9..b4f954d 100644 --- a/src/renderer/styles/globals.css +++ b/packages/ui/src/styles/globals.css @@ -3,7 +3,7 @@ @source "../components/**/*.{ts,tsx}"; @source "../lib/**/*.{ts,tsx}"; @source "../types/**/*.{ts,tsx}"; -@source "../../shared/**/*.{ts,tsx}"; +@source "../../../core/src/contracts/**/*.{ts,tsx}"; @theme inline { --color-background: var(--background); diff --git a/src/renderer/types/assets.d.ts b/packages/ui/src/types/assets.d.ts similarity index 100% rename from src/renderer/types/assets.d.ts rename to packages/ui/src/types/assets.d.ts diff --git a/src/renderer/types/electron.d.ts b/packages/ui/src/types/electron.d.ts similarity index 98% rename from src/renderer/types/electron.d.ts rename to packages/ui/src/types/electron.d.ts index 6e7ea80..97af330 100644 --- a/src/renderer/types/electron.d.ts +++ b/packages/ui/src/types/electron.d.ts @@ -72,8 +72,8 @@ import type { UsageStatsFilter, UsageStatsRange, UsageStatsSnapshot -} from "../../shared/app"; -import type { ProviderPreset } from "../../shared/provider-presets"; +} from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; declare global { interface Window { diff --git a/src/main/web-client-bridge.ts b/packages/ui/src/web-client-bridge.ts similarity index 53% rename from src/main/web-client-bridge.ts rename to packages/ui/src/web-client-bridge.ts index 57fc840..d9456f1 100644 --- a/src/main/web-client-bridge.ts +++ b/packages/ui/src/web-client-bridge.ts @@ -1,3 +1,5 @@ +type CcrApi = NonNullable; + const rpcEndpoint = "/api/ccr/rpc"; const webAuthHeader = "x-ccr-web-auth"; const webAuthQueryParam = "ccr_web_token"; @@ -10,7 +12,7 @@ type RpcResponse = async function rpc(method: string, args: unknown[] = []): Promise { const response = await fetch(rpcEndpoint, { - body: JSON.stringify({ args, method }), + body: JSON.stringify({ args: trimTrailingUndefined(args), method }), headers: { "content-type": "application/json", ...(webAuthToken ? { [webAuthHeader]: webAuthToken } : {}) @@ -32,6 +34,14 @@ async function rpc(method: string, args: unknown[] = []): Promise { return payload.value; } +function trimTrailingUndefined(args: unknown[]): unknown[] { + let end = args.length; + while (end > 0 && args[end - 1] === undefined) { + end -= 1; + } + return end === args.length ? args : args.slice(0, end); +} + function readWebAuthToken(): string { const tokenFromUrl = readWebAuthTokenFromUrl(); if (tokenFromUrl) { @@ -84,76 +94,80 @@ async function selectPluginDirectory(): Promise { return rpc("selectPluginDirectory", [directory.trim()]); } -window.ccr = { - applyClaudeAppGateway: (config) => rpc("applyClaudeAppGateway", [config]) as ReturnType["applyClaudeAppGateway"]>, - applyProfile: () => rpc("applyProfile") as ReturnType["applyProfile"]>, - cancelBotGatewayQrLogin: (request) => rpc("cancelBotGatewayQrLogin", [request]) as ReturnType["cancelBotGatewayQrLogin"]>, - checkProviderConnectivity: (request) => rpc("checkProviderConnectivity", [request]) as ReturnType["checkProviderConnectivity"]>, - clearProxyNetworkCaptures: () => rpc("clearProxyNetworkCaptures") as ReturnType["clearProxyNetworkCaptures"]>, - closeBotGatewayQrWindow: (request) => rpc("closeBotGatewayQrWindow", [request]) as ReturnType["closeBotGatewayQrWindow"]>, +const webClientBridge: CcrApi = { + applyClaudeAppGateway: (config) => rpc("applyClaudeAppGateway", [config]) as ReturnType, + applyProfile: () => rpc("applyProfile") as ReturnType, + cancelBotGatewayQrLogin: (request) => rpc("cancelBotGatewayQrLogin", [request]) as ReturnType, + checkProviderConnectivity: (request) => rpc("checkProviderConnectivity", [request]) as ReturnType, + clearProxyNetworkCaptures: () => rpc("clearProxyNetworkCaptures") as ReturnType, + closeBotGatewayQrWindow: (request) => rpc("closeBotGatewayQrWindow", [request]) as ReturnType, closeTray: () => Promise.resolve(), - detectProviderIcon: (request) => rpc("detectProviderIcon", [request]) as ReturnType["detectProviderIcon"]>, - exportData: () => rpc("exportData") as ReturnType["exportData"]>, - fetchProviderManifest: (request) => rpc("fetchProviderManifest", [request]) as ReturnType["fetchProviderManifest"]>, - getAgentAnalysis: (filter) => rpc("getAgentAnalysis", [filter]) as ReturnType["getAgentAnalysis"]>, - getAgentTracePayload: (request) => rpc("getAgentTracePayload", [request]) as ReturnType["getAgentTracePayload"]>, - getAppInfo: () => rpc("getAppInfo") as ReturnType["getAppInfo"]>, - getConfig: () => rpc("getConfig") as ReturnType["getConfig"]>, - getGatewayStatus: () => rpc("getGatewayStatus") as ReturnType["getGatewayStatus"]>, - getLocalAgentProviderCandidates: () => rpc("getLocalAgentProviderCandidates") as ReturnType["getLocalAgentProviderCandidates"]>, - getOnboardingFinished: () => rpc("getOnboardingFinished") as ReturnType["getOnboardingFinished"]>, + detectProviderIcon: (request) => rpc("detectProviderIcon", [request]) as ReturnType, + exportData: () => rpc("exportData") as ReturnType, + fetchProviderManifest: (request) => rpc("fetchProviderManifest", [request]) as ReturnType, + getAgentAnalysis: (filter) => rpc("getAgentAnalysis", [filter]) as ReturnType, + getAgentTracePayload: (request) => rpc("getAgentTracePayload", [request]) as ReturnType, + getAppInfo: () => rpc("getAppInfo") as ReturnType, + getConfig: () => rpc("getConfig") as ReturnType, + getGatewayStatus: () => rpc("getGatewayStatus") as ReturnType, + getLocalAgentProviderCandidates: () => rpc("getLocalAgentProviderCandidates") as ReturnType, + getOnboardingFinished: () => rpc("getOnboardingFinished") as ReturnType, getPendingProviderDeepLinks: () => Promise.resolve([]), - getPluginMarketplace: () => rpc("getPluginMarketplace") as ReturnType["getPluginMarketplace"]>, - getProfileOpenCommand: (request) => rpc("getProfileOpenCommand", [request]) as ReturnType["getProfileOpenCommand"]>, - getProfileRuntimeStatus: () => rpc("getProfileRuntimeStatus") as ReturnType["getProfileRuntimeStatus"]>, - getProviderAccountSnapshots: (provider, options) => rpc("getProviderAccountSnapshots", [provider, options]) as ReturnType["getProviderAccountSnapshots"]>, - getProviderCatalogModels: (request) => rpc("getProviderCatalogModels", [request]) as ReturnType["getProviderCatalogModels"]>, - getProviderPresets: () => rpc("getProviderPresets") as ReturnType["getProviderPresets"]>, - getProxyCertificateStatus: () => rpc("getProxyCertificateStatus") as ReturnType["getProxyCertificateStatus"]>, - getProxyNetworkCaptures: () => rpc("getProxyNetworkCaptures") as ReturnType["getProxyNetworkCaptures"]>, - getProxyStatus: () => rpc("getProxyStatus") as ReturnType["getProxyStatus"]>, - getRequestLogDetail: (request) => rpc("getRequestLogDetail", [request]) as ReturnType["getRequestLogDetail"]>, - getRequestLogs: (filter) => rpc("getRequestLogs", [filter]) as ReturnType["getRequestLogs"]>, - getUpdateStatus: () => rpc("getUpdateStatus") as ReturnType["getUpdateStatus"]>, - getUsageStats: (range, filter) => rpc("getUsageStats", [range, filter]) as ReturnType["getUsageStats"]>, - importLocalAgentProvider: (request) => rpc("importLocalAgentProvider", [request]) as ReturnType["importLocalAgentProvider"]>, - installProxyCertificate: () => rpc("installProxyCertificate") as ReturnType["installProxyCertificate"]>, - listMcpServerTools: (serverName) => rpc("listMcpServerTools", [serverName]) as ReturnType["listMcpServerTools"]>, + getPluginMarketplace: () => rpc("getPluginMarketplace") as ReturnType, + getProfileOpenCommand: (request) => rpc("getProfileOpenCommand", [request]) as ReturnType, + getProfileRuntimeStatus: () => rpc("getProfileRuntimeStatus") as ReturnType, + getProviderAccountSnapshots: (provider, options) => rpc("getProviderAccountSnapshots", [provider, options]) as ReturnType, + getProviderCatalogModels: (request) => rpc("getProviderCatalogModels", [request]) as ReturnType, + getProviderPresets: () => rpc("getProviderPresets") as ReturnType, + getProxyCertificateStatus: () => rpc("getProxyCertificateStatus") as ReturnType, + getProxyNetworkCaptures: () => rpc("getProxyNetworkCaptures") as ReturnType, + getProxyStatus: () => rpc("getProxyStatus") as ReturnType, + getRequestLogDetail: (request) => rpc("getRequestLogDetail", [request]) as ReturnType, + getRequestLogs: (filter) => rpc("getRequestLogs", [filter]) as ReturnType, + getUpdateStatus: () => rpc("getUpdateStatus") as ReturnType, + getUsageStats: (range, filter) => rpc("getUsageStats", [range, filter]) as ReturnType, + importLocalAgentProvider: (request) => rpc("importLocalAgentProvider", [request]) as ReturnType, + installProxyCertificate: () => rpc("installProxyCertificate") as ReturnType, + listMcpServerTools: (serverName) => rpc("listMcpServerTools", [serverName]) as ReturnType, onBeforeQuit: noopSubscription, onOpenSettingsRequest: noopSubscription, onOpenUpdateRequest: noopSubscription, onProviderDeepLink: noopSubscription, onUpdateStatusChanged: noopSubscription, - openBotGatewayQrWindow: (request) => rpc("openBotGatewayQrWindow", [request]) as ReturnType["openBotGatewayQrWindow"]>, - openBuiltInBrowser: () => rpc("openBuiltInBrowser") as ReturnType["openBuiltInBrowser"]>, + openBotGatewayQrWindow: (request) => rpc("openBotGatewayQrWindow", [request]) as ReturnType, + openBuiltInBrowser: () => rpc("openBuiltInBrowser") as ReturnType, openExternal: (url) => { window.open(url, "_blank", "noopener,noreferrer"); return Promise.resolve(); }, - openProfile: (request) => rpc("openProfile", [request]) as ReturnType["openProfile"]>, - probeProvider: (request) => rpc("probeProvider", [request]) as ReturnType["probeProvider"]>, - probeProviderCandidates: (request) => rpc("probeProviderCandidates", [request]) as ReturnType["probeProviderCandidates"]>, - quitApp: () => rpc("quitApp") as ReturnType["quitApp"]>, - restartGateway: () => rpc("restartGateway") as ReturnType["restartGateway"]>, - restartProxy: () => rpc("restartProxy") as ReturnType["restartProxy"]>, - revealProxyCertificate: () => rpc("revealProxyCertificate") as ReturnType["revealProxyCertificate"]>, - resetCodexRateLimitCredit: (request) => rpc("resetCodexRateLimitCredit", [request]) as ReturnType["resetCodexRateLimitCredit"]>, - saveApiKeys: (apiKeys) => rpc("saveApiKeys", [apiKeys]) as ReturnType["saveApiKeys"]>, - saveConfig: (config, options) => rpc("saveConfig", [config, options]) as ReturnType["saveConfig"]>, - scanBotHandoffBluetoothTargets: () => rpc("scanBotHandoffBluetoothTargets") as ReturnType["scanBotHandoffBluetoothTargets"]>, - scanBotHandoffWifiTargets: () => rpc("scanBotHandoffWifiTargets") as ReturnType["scanBotHandoffWifiTargets"]>, - selectPluginDirectory: () => selectPluginDirectory() as ReturnType["selectPluginDirectory"]>, - setOnboardingFinished: () => rpc("setOnboardingFinished") as ReturnType["setOnboardingFinished"]>, - setProxyNetworkCaptureEnabled: (enabled) => rpc("setProxyNetworkCaptureEnabled", [enabled]) as ReturnType["setProxyNetworkCaptureEnabled"]>, + openProfile: (request) => rpc("openProfile", [request]) as ReturnType, + probeProvider: (request) => rpc("probeProvider", [request]) as ReturnType, + probeProviderCandidates: (request) => rpc("probeProviderCandidates", [request]) as ReturnType, + quitApp: () => rpc("quitApp") as ReturnType, + restartGateway: () => rpc("restartGateway") as ReturnType, + restartProxy: () => rpc("restartProxy") as ReturnType, + revealProxyCertificate: () => rpc("revealProxyCertificate") as ReturnType, + resetCodexRateLimitCredit: (request) => rpc("resetCodexRateLimitCredit", [request]) as ReturnType, + saveApiKeys: (apiKeys) => rpc("saveApiKeys", [apiKeys]) as ReturnType, + saveConfig: (config, options) => rpc("saveConfig", [config, options]) as ReturnType, + scanBotHandoffBluetoothTargets: () => rpc("scanBotHandoffBluetoothTargets") as ReturnType, + scanBotHandoffWifiTargets: () => rpc("scanBotHandoffWifiTargets") as ReturnType, + selectPluginDirectory: () => selectPluginDirectory() as ReturnType, + setOnboardingFinished: () => rpc("setOnboardingFinished") as ReturnType, + setProxyNetworkCaptureEnabled: (enabled) => rpc("setProxyNetworkCaptureEnabled", [enabled]) as ReturnType, setTrayDetailOpen: () => Promise.resolve(), showMainWindow: () => Promise.resolve(), - startBotGatewayQrLogin: (request) => rpc("startBotGatewayQrLogin", [request]) as ReturnType["startBotGatewayQrLogin"]>, - startGateway: () => rpc("startGateway") as ReturnType["startGateway"]>, - stopGateway: () => rpc("stopGateway") as ReturnType["stopGateway"]>, - stopProfile: (request) => rpc("stopProfile", [request]) as ReturnType["stopProfile"]>, - testProviderAccountConnector: (request) => rpc("testProviderAccountConnector", [request]) as ReturnType["testProviderAccountConnector"]>, - updateCheck: () => rpc("updateCheck") as ReturnType["updateCheck"]>, - updateDownload: () => rpc("updateDownload") as ReturnType["updateDownload"]>, - updateInstall: () => rpc("updateInstall") as ReturnType["updateInstall"]>, - waitBotGatewayQrLogin: (request) => rpc("waitBotGatewayQrLogin", [request]) as ReturnType["waitBotGatewayQrLogin"]> + startBotGatewayQrLogin: (request) => rpc("startBotGatewayQrLogin", [request]) as ReturnType, + startGateway: () => rpc("startGateway") as ReturnType, + stopGateway: () => rpc("stopGateway") as ReturnType, + stopProfile: (request) => rpc("stopProfile", [request]) as ReturnType, + testProviderAccountConnector: (request) => rpc("testProviderAccountConnector", [request]) as ReturnType, + updateCheck: () => rpc("updateCheck") as ReturnType, + updateDownload: () => rpc("updateDownload") as ReturnType, + updateInstall: () => rpc("updateInstall") as ReturnType, + waitBotGatewayQrLogin: (request) => rpc("waitBotGatewayQrLogin", [request]) as ReturnType }; + +if (!window.ccr) { + window.ccr = webClientBridge; +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 0000000..565e465 --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"] +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..79eb488 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,25 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + expect: { + timeout: 10_000 + }, + forbidOnly: !!process.env.CI, + fullyParallel: false, + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"] + } + } + ], + reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : "list", + testDir: "./tests/e2e", + timeout: 30_000, + use: { + actionTimeout: 10_000, + trace: "retain-on-failure" + }, + workers: 1 +}); diff --git a/scripts/generate-models-json.mjs b/scripts/generate-models-json.mjs index da17fad..de9b53c 100644 --- a/scripts/generate-models-json.mjs +++ b/scripts/generate-models-json.mjs @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(__dirname, ".."); -const outputPath = path.join(projectRoot, "models.json"); +const outputPath = path.join(projectRoot, "packages", "core", "models.json"); const sources = { litellm: { diff --git a/tests/docker/docker-smoke.mjs b/tests/docker/docker-smoke.mjs new file mode 100644 index 0000000..5a60066 --- /dev/null +++ b/tests/docker/docker-smoke.mjs @@ -0,0 +1,267 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { randomUUID } from "node:crypto"; +import net from "node:net"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, "..", ".."); +const imageName = process.env.CCR_DOCKER_TEST_IMAGE || "claude-code-router:local"; +const token = process.env.CCR_DOCKER_TEST_WEB_AUTH_TOKEN || "token+/&=#?"; +const testId = randomUUID().slice(0, 8); +const containerName = `ccr-docker-smoke-${testId}`; +const volumeName = `ccr-docker-smoke-${testId}`; +const startupTimeoutMs = 45_000; + +let containerStarted = false; +let volumeCreated = false; + +try { + if (process.env.CCR_DOCKER_TEST_SKIP_BUILD !== "1") { + run("docker", ["build", "-t", imageName, "."], { cwd: projectRoot }); + } + + const hostPort = await findAvailablePort(); + run("docker", ["volume", "create", volumeName]); + volumeCreated = true; + seedLegacyDockerConfig(volumeName); + + const containerId = run("docker", [ + "run", + "-d", + "--name", + containerName, + "-p", + `${hostPort}:8080`, + "-e", + `CCR_WEB_AUTH_TOKEN=${token}`, + "-e", + "CCR_PUBLIC_HOST=127.0.0.1", + "-e", + `CCR_PUBLIC_PORT=${hostPort}`, + "-v", + `${volumeName}:/data`, + imageName + ]).trim(); + containerStarted = true; + const baseUrl = `http://127.0.0.1:${hostPort}`; + + await waitFor(async () => { + const response = await fetch(baseUrl, { redirect: "manual" }).catch(() => undefined); + return response?.status === 302; + }, "Docker Nginx entrypoint"); + + const ports = dockerPorts(containerId); + assert.ok(ports["8080/tcp"], "Docker container must publish only the Nginx port"); + assert.equal(ports["3456/tcp"], undefined, "Docker container must not publish the internal gateway port"); + + const redirect = await fetch(baseUrl, { redirect: "manual" }); + assert.equal(redirect.status, 302); + const redirectLocation = redirect.headers.get("location"); + assert.equal(redirectLocation, `/pages/home/index.html?ccr_web_token=${encodeURIComponent(token)}`); + + const page = await fetch(`${baseUrl}${redirectLocation}`); + assert.equal(page.status, 200); + const pageHtml = await page.text(); + assert.match(pageHtml, /Claude Code Router/); + assert.match(pageHtml, /web-client-bridge\.js/); + + const bridge = await fetch(`${baseUrl}/assets/web-client-bridge.js`); + assert.equal(bridge.status, 200); + assert.match(await bridge.text(), /getProviderPresets/); + + await waitFor(async () => { + const response = await rpc(baseUrl, "getAppInfo", token).catch(() => undefined); + return response?.status === 200; + }, "Docker core management RPC"); + + const unauthorized = await rpc(baseUrl, "getAppInfo"); + assert.equal(unauthorized.status, 401); + assert.equal((await unauthorized.json()).ok, false); + + const appInfo = await rpc(baseUrl, "getAppInfo", token); + assert.equal(appInfo.status, 200); + const appInfoPayload = await appInfo.json(); + assert.equal(appInfoPayload.ok, true); + assert.equal(appInfoPayload.value.configDir, "/data/.claude-code-router"); + + const presets = await rpc(baseUrl, "getProviderPresets", token); + assert.equal(presets.status, 200); + const presetsPayload = await presets.json(); + assert.equal(presetsPayload.ok, true); + assert.ok(presetsPayload.value.some((preset) => preset.id === "openai")); + + const usageStatsWithNullFilter = await rpc(baseUrl, "getUsageStats", token, ["7d", null]); + assert.equal(usageStatsWithNullFilter.status, 200); + const usageStatsPayload = await usageStatsWithNullFilter.json(); + assert.equal(usageStatsPayload.ok, true); + assert.equal(usageStatsPayload.value.range, "7d"); + assert.doesNotMatch(run("docker", ["logs", containerId]), /Failed to read usage stats/); + + const config = await rpc(baseUrl, "getConfig", token); + assert.equal(config.status, 200); + const configPayload = await config.json(); + assert.equal(configPayload.ok, true); + assert.equal(configPayload.value.routerEndpoint, baseUrl); + assert.equal(configPayload.value.HOST, "127.0.0.1"); + assert.equal(configPayload.value.PORT, 3456); + assert.equal(configPayload.value.gateway.host, "127.0.0.1"); + assert.equal(configPayload.value.gateway.port, 3456); + + const gatewayHealth = await fetch(`${baseUrl}/health`); + assert.equal(gatewayHealth.status, 502, "Gateway health should be proxied by Nginx and fail while no models are configured"); + + const openRouterConfig = { + ...configPayload.value, + Providers: [ + { + apiKey: "test-openrouter-key", + baseUrl: "https://openrouter.ai/api/v1", + capabilities: [ + { baseUrl: "https://openrouter.ai/api/v1", source: "preset", type: "openai_chat_completions" }, + { baseUrl: "https://openrouter.ai/api/v1", source: "preset", type: "openai_responses" }, + { baseUrl: "https://openrouter.ai/api", endpoint: "https://openrouter.ai/api/v1/messages", source: "detected", type: "anthropic_messages" } + ], + models: ["qwen/qwen3.5-122b-a10b"], + name: "OpenRouter", + type: "openai_responses" + } + ], + preferredProvider: "OpenRouter" + }; + const savedConfig = await rpc(baseUrl, "saveConfig", token, [openRouterConfig, { applyProfile: false }]); + assert.equal(savedConfig.status, 200); + assert.equal((await savedConfig.json()).ok, true); + + const startedGateway = await rpc(baseUrl, "startGateway", token); + assert.equal(startedGateway.status, 200); + const startedGatewayPayload = await startedGateway.json(); + assert.equal(startedGatewayPayload.ok, true); + assert.equal(startedGatewayPayload.value.state, "running"); + + const restartedGateway = await rpc(baseUrl, "startGateway", token); + assert.equal(restartedGateway.status, 200); + const restartedGatewayPayload = await restartedGateway.json(); + assert.equal(restartedGatewayPayload.ok, true); + assert.equal(restartedGatewayPayload.value.state, "running"); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + const restartedGatewayStatus = await rpc(baseUrl, "getGatewayStatus", token); + assert.equal(restartedGatewayStatus.status, 200); + const restartedGatewayStatusPayload = await restartedGatewayStatus.json(); + assert.equal(restartedGatewayStatusPayload.ok, true); + assert.equal(restartedGatewayStatusPayload.value.state, "running"); + + const runningGatewayHealth = await fetch(`${baseUrl}/health`); + assert.equal(runningGatewayHealth.status, 200); + assert.equal((await runningGatewayHealth.json()).status, "running"); + + console.log(`Docker smoke test passed for ${imageName} on ${baseUrl}`); +} finally { + if (containerStarted) { + run("docker", ["rm", "-f", containerName], { allowFailure: true }); + } + if (volumeCreated) { + run("docker", ["volume", "rm", "-f", volumeName], { allowFailure: true }); + } +} + +function seedLegacyDockerConfig(volume) { + const script = ` +const fs = require("node:fs"); +const path = require("node:path"); +const Database = require("better-sqlite3"); +const configDir = "/data/.claude-code-router"; +const config = { + HOST: "0.0.0.0", + PORT: 3456, + Providers: [], + gateway: { + coreHost: "127.0.0.1", + corePort: 3457, + enabled: true, + host: "0.0.0.0", + port: 3456 + }, + routerEndpoint: "http://127.0.0.1:3456" +}; +fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); +fs.mkdirSync(path.join(configDir, "app-data"), { recursive: true, mode: 0o700 }); +fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify(config, null, 2) + "\\n", { mode: 0o600 }); +const db = new Database(path.join(configDir, "config.sqlite")); +db.exec("create table app_config (key TEXT PRIMARY KEY, value_json TEXT NOT NULL, updated_at TEXT NOT NULL)"); +db.prepare("insert into app_config (key, value_json, updated_at) values (?, ?, ?)").run("default", JSON.stringify(config), new Date().toISOString()); +db.close(); +`; + run("docker", ["run", "--rm", "-v", `${volume}:/data`, "--entrypoint", "node", imageName, "-e", script]); +} + +async function rpc(baseUrl, method, authToken, args = []) { + return fetch(`${baseUrl}/api/ccr/rpc`, { + body: JSON.stringify({ args, method }), + headers: { + "content-type": "application/json", + ...(authToken ? { "x-ccr-web-auth": authToken } : {}) + }, + method: "POST" + }); +} + +function dockerPorts(containerId) { + const raw = run("docker", ["inspect", "--format", "{{json .NetworkSettings.Ports}}", containerId]); + return JSON.parse(raw); +} + +async function waitFor(check, label) { + const startedAt = Date.now(); + let lastError; + while (Date.now() - startedAt < startupTimeoutMs) { + try { + if (await check()) { + return; + } + } catch (error) { + lastError = error; + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + const logs = containerStarted + ? run("docker", ["logs", containerName], { allowFailure: true }) + : ""; + throw new Error(`${label} was not ready within ${startupTimeoutMs}ms.${lastError ? ` Last error: ${lastError}` : ""}\n${logs}`); +} + +function findAvailablePort() { + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + const port = typeof address === "object" && address ? address.port : undefined; + server.close(() => { + if (!port) { + reject(new Error("Failed to allocate a Docker test port.")); + return; + } + resolve(port); + }); + }); + }); +} + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: options.cwd ?? projectRoot, + encoding: "utf8", + stdio: options.stdio ?? "pipe" + }); + if (result.status !== 0 && !options.allowFailure) { + throw new Error([ + `${command} ${args.join(" ")} failed with code ${result.status ?? "unknown"}`, + result.stdout, + result.stderr + ].filter(Boolean).join("\n")); + } + return result.stdout.trim(); +} diff --git a/tests/e2e/cli-web.spec.ts b/tests/e2e/cli-web.spec.ts new file mode 100644 index 0000000..095a443 --- /dev/null +++ b/tests/e2e/cli-web.spec.ts @@ -0,0 +1,230 @@ +import { expect, test, type APIRequestContext } from "@playwright/test"; +import { spawn, type ChildProcessByStdio } from "node:child_process"; +import { mkdtempSync, rmSync } from "node:fs"; +import type { Readable } from "node:stream"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; + +const projectRoot = path.resolve(__dirname, "..", ".."); +const cliPath = path.join(projectRoot, "packages", "cli", "dist", "main", "cli.js"); +const host = "127.0.0.1"; +const cliWebAuthToken = "playwright-cli-web-auth-token"; +const startupTimeoutMs = 10_000; +const shutdownTimeoutMs = 5_000; + +type CliWebRuntime = { + baseUrl: string; + child: CliWebChild; + testHome: string; + token: string; +}; + +type CliWebChild = ChildProcessByStdio; + +let runtime: CliWebRuntime | undefined; + +test.beforeAll(async () => { + runtime = await startCliWebServer(); +}); + +test.afterAll(async () => { + if (!runtime) { + return; + } + await stopCliWebServer(runtime.child); + rmSync(runtime.testHome, { force: true, recursive: true }); + runtime = undefined; +}); + +test("uses CCR_WEB_AUTH_TOKEN for CLI web authentication", async () => { + const current = requireRuntime(); + expect(current.token).toBe(cliWebAuthToken); +}); + +test("serves the management UI in a browser", async ({ page }) => { + const current = requireRuntime(); + await page.goto(`${current.baseUrl}/?ccr_web_token=${current.token}`); + await expect(page).toHaveTitle("Claude Code Router"); + await expect(page.locator("#root")).toBeAttached(); + await expect(page.locator("body")).toContainText(/Configure provider|Connect agent|Let's start/, { timeout: 15_000 }); + await expect(page.evaluate(() => Boolean(window.ccr?.getAppInfo))).resolves.toBe(true); + await expect(page.evaluate(async () => { + const presets = await window.ccr?.getProviderPresets?.(); + return presets?.map((preset) => preset.id) ?? []; + })).resolves.toContain("openai"); + + await page.getByRole("button", { name: /Select preset provider|选择 预设供应商/ }).first().click(); + await expect(page.getByRole("option", { name: "OpenAI" })).toBeVisible(); +}); + +test("serves static assets used by the web UI", async ({ request }) => { + const current = requireRuntime(); + await expectStaticAsset(request, current.baseUrl, "/assets/main.js", "text/javascript"); + await expectStaticAsset(request, current.baseUrl, "/assets/main.css", "text/css"); + await expectStaticAsset(request, current.baseUrl, "/assets/web-client-bridge.js", "text/javascript"); +}); + +test("handles authenticated web RPC requests", async ({ request }) => { + const current = requireRuntime(); + const response = await request.post(`${current.baseUrl}/api/ccr/rpc`, { + data: { args: [], method: "getAppInfo" }, + headers: { + "x-ccr-web-auth": current.token + } + }); + const payload = await response.json(); + + expect(response.status()).toBe(200); + expect(payload.ok).toBe(true); + expect(payload.value.name).toBe("Claude Code Router"); + expect(payload.value.configDir).toContain(current.testHome); + expect(payload.value.appConfigDbFile).toContain("config.sqlite"); + expect(payload.value.usageDbFile).toContain("usage.sqlite"); +}); + +test("rejects RPC requests without the web auth token", async ({ request }) => { + const current = requireRuntime(); + const response = await request.post(`${current.baseUrl}/api/ccr/rpc`, { + data: { args: [], method: "getAppInfo" } + }); + const payload = await response.json(); + + expect(response.status()).toBe(401); + expect(payload.ok).toBe(false); +}); + +async function expectStaticAsset( + request: APIRequestContext, + baseUrl: string, + pathname: string, + expectedContentType: string +) { + const response = await request.head(`${baseUrl}${pathname}`); + expect(response.status()).toBe(200); + expect(response.headers()["content-type"]).toContain(expectedContentType); + expect(Number(response.headers()["content-length"] ?? "0")).toBeGreaterThan(0); +} + +async function startCliWebServer(): Promise { + const port = await findAvailablePort(); + const testHome = mkdtempSync(path.join(os.tmpdir(), "ccr-playwright-home-")); + const child = spawn(process.execPath, [ + cliPath, + "serve", + "--no-gateway", + "--host", + host, + "--port", + String(port), + "--no-open" + ], { + cwd: projectRoot, + env: { + ...process.env, + CCR_INTERNAL_APP_DATA_DIR: path.join(testHome, "app-data"), + CCR_INTERNAL_HOME_DIR: testHome, + CCR_INTERNAL_USER_DATA_DIR: path.join(testHome, "user-data"), + CCR_WEB_AUTH_TOKEN: cliWebAuthToken, + HOME: testHome + }, + stdio: ["ignore", "pipe", "pipe"] + }); + + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { + stdout += chunk; + }); + child.stderr.on("data", (chunk) => { + stderr += chunk; + }); + + const service = await new Promise<{ baseUrl: string; token: string }>((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`CCR web service did not start within ${startupTimeoutMs}ms.\nstdout:\n${stdout}\nstderr:\n${stderr}`)); + }, startupTimeoutMs); + + const cleanup = () => { + clearTimeout(timer); + child.stdout.off("data", onStdout); + child.off("exit", onExit); + }; + const onExit = (code: number | null, signal: NodeJS.Signals | null) => { + cleanup(); + reject(new Error(`CCR web service exited during startup code=${code ?? "null"} signal=${signal ?? "null"}.\nstdout:\n${stdout}\nstderr:\n${stderr}`)); + }; + const onStdout = () => { + const match = stdout.match(/CCR web management is running at (http:\/\/[^\s]+)/); + if (!match) { + return; + } + cleanup(); + const url = new URL(match[1]); + resolve({ + baseUrl: `${url.protocol}//${url.host}`, + token: url.searchParams.get("ccr_web_token") ?? "" + }); + }; + + child.on("exit", onExit); + child.stdout.on("data", onStdout); + }); + + if (!service.token) { + await stopCliWebServer(child); + rmSync(testHome, { force: true, recursive: true }); + throw new Error("CCR web service started without a web auth token."); + } + + return { + ...service, + child, + testHome + }; +} + +async function stopCliWebServer(child: CliWebChild): Promise { + if (child.exitCode !== null) { + return; + } + + child.kill("SIGINT"); + await new Promise((resolve) => { + const timer = setTimeout(() => { + child.kill("SIGKILL"); + resolve(); + }, shutdownTimeoutMs); + child.once("exit", () => { + clearTimeout(timer); + resolve(); + }); + }); +} + +async function findAvailablePort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.on("error", reject); + server.listen(0, host, () => { + const address = server.address(); + const port = typeof address === "object" && address ? address.port : undefined; + server.close(() => { + if (!port) { + reject(new Error("Failed to allocate a local test port.")); + return; + } + resolve(port); + }); + }); + }); +} + +function requireRuntime(): CliWebRuntime { + if (!runtime) { + throw new Error("CLI web runtime was not started."); + } + return runtime; +} diff --git a/tests/main/app-paths.test.mjs b/tests/main/app-paths.test.mjs new file mode 100644 index 0000000..54e4b7e --- /dev/null +++ b/tests/main/app-paths.test.mjs @@ -0,0 +1,59 @@ +import assert from "node:assert/strict"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { APP_STORAGE_NAME, resolveRuntimeConfigDir, resolveRuntimeDataDir } from "../../packages/core/src/runtime/app-paths.ts"; + +test("runtime config and data dirs default to the shared CCR storage", () => { + withRuntimePathEnv({ + appData: path.join(os.tmpdir(), "ccr-app-paths-app-data"), + home: path.join(os.tmpdir(), "ccr-app-paths-home") + }, () => { + const configDir = resolveRuntimeConfigDir(); + const expectedConfigDir = process.platform === "win32" + ? path.join(os.tmpdir(), "ccr-app-paths-app-data", APP_STORAGE_NAME) + : path.join(os.tmpdir(), "ccr-app-paths-home", `.${APP_STORAGE_NAME}`); + const expectedDataDir = process.platform === "win32" + ? expectedConfigDir + : path.join(expectedConfigDir, "app-data"); + + assert.equal(configDir, expectedConfigDir); + assert.equal(resolveRuntimeDataDir(), expectedDataDir); + }); +}); + +test("runtime data dir still allows explicit test and deployment overrides", () => { + withRuntimePathEnv({ + appData: path.join(os.tmpdir(), "ccr-app-paths-override-app-data"), + home: path.join(os.tmpdir(), "ccr-app-paths-override-home"), + userData: path.join(os.tmpdir(), "ccr-app-paths-override-user-data") + }, () => { + assert.equal(resolveRuntimeDataDir(), path.join(os.tmpdir(), "ccr-app-paths-override-user-data")); + }); +}); + +function withRuntimePathEnv(paths, run) { + const previous = { + appData: process.env.CCR_INTERNAL_APP_DATA_DIR, + home: process.env.CCR_INTERNAL_HOME_DIR, + userData: process.env.CCR_INTERNAL_USER_DATA_DIR + }; + try { + setOptionalEnv("CCR_INTERNAL_APP_DATA_DIR", paths.appData); + setOptionalEnv("CCR_INTERNAL_HOME_DIR", paths.home); + setOptionalEnv("CCR_INTERNAL_USER_DATA_DIR", paths.userData); + run(); + } finally { + setOptionalEnv("CCR_INTERNAL_APP_DATA_DIR", previous.appData); + setOptionalEnv("CCR_INTERNAL_HOME_DIR", previous.home); + setOptionalEnv("CCR_INTERNAL_USER_DATA_DIR", previous.userData); + } +} + +function setOptionalEnv(name, value) { + if (value === undefined) { + delete process.env[name]; + return; + } + process.env[name] = value; +} diff --git a/tests/main/bot-gateway-env.test.mjs b/tests/main/bot-gateway-env.test.mjs index a4e8acb..ad262f9 100644 --- a/tests/main/bot-gateway-env.test.mjs +++ b/tests/main/bot-gateway-env.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { botGatewayProfileEnv } from "../../src/main/bot-gateway-env.ts"; +import { botGatewayProfileEnv } from "../../packages/core/src/agents/bot-gateway/env.ts"; function botGateway(overrides = {}) { return { diff --git a/tests/main/claude-app-cdp.test.mjs b/tests/main/claude-app-cdp.test.mjs index d61252f..51decd4 100644 --- a/tests/main/claude-app-cdp.test.mjs +++ b/tests/main/claude-app-cdp.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { shouldEnableClaudeAppDesignCdp } from "../../src/main/claude-app-cdp.ts"; +import { shouldEnableClaudeAppDesignCdp } from "../../packages/core/src/agents/claude-app/cdp.ts"; test("Claude App Design CDP is opt-in even when Claude Design is configured", (t) => { const previous = process.env.CCR_CLAUDE_APP_DESIGN_CDP; diff --git a/tests/main/claude-app-launch.test.mjs b/tests/main/claude-app-launch.test.mjs index 8ad9ee6..47b9a0d 100644 --- a/tests/main/claude-app-launch.test.mjs +++ b/tests/main/claude-app-launch.test.mjs @@ -3,7 +3,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { claudeAppLaunchCommand } from "../../src/main/claude-app-launch.ts"; +import { claudeAppLaunchCommand } from "../../packages/core/src/agents/claude-app/launch.ts"; test("claudeAppLaunchCommand opens macOS app bundles through LaunchServices", (t) => { const tempDir = mkdtempForTest(); diff --git a/tests/main/claude-environment.test.mjs b/tests/main/claude-environment.test.mjs index d5eb487..bcefbf8 100644 --- a/tests/main/claude-environment.test.mjs +++ b/tests/main/claude-environment.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { claudeCodeUtcTimezoneEnvOverride, isChinaTimeZone } from "../../src/main/claude-environment.ts"; +import { claudeCodeUtcTimezoneEnvOverride, isChinaTimeZone } from "../../packages/core/src/agents/claude-code/environment.ts"; test("detects China time zones used by Claude Code", () => { assert.equal(isChinaTimeZone("Asia/Shanghai"), true); diff --git a/tests/main/codex-app-model-catalog.test.mjs b/tests/main/codex-app-model-catalog.test.mjs index fae302d..7992a89 100644 --- a/tests/main/codex-app-model-catalog.test.mjs +++ b/tests/main/codex-app-model-catalog.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { writeCodexCompatibleAppModelCatalog } from "../../src/main/codex-app-launch.ts"; +import { writeCodexCompatibleAppModelCatalog } from "../../packages/core/src/agents/codex/app-launch.ts"; test("Codex App model catalog write includes patch bridge capabilities", () => { const configDir = mkdtempSync(path.join(os.tmpdir(), "ccr-codex-app-catalog-")); diff --git a/tests/main/codex-cli-middleware-runtime.test.mjs b/tests/main/codex-cli-middleware-runtime.test.mjs index 4a06989..b8997b7 100644 --- a/tests/main/codex-cli-middleware-runtime.test.mjs +++ b/tests/main/codex-cli-middleware-runtime.test.mjs @@ -4,7 +4,7 @@ import { chmodSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { codexCliMiddlewareRuntimeScript } from "../../src/main/codex-cli-middleware-runtime.ts"; +import { codexCliMiddlewareRuntimeScript } from "../../packages/core/src/agents/codex/cli-middleware-runtime.ts"; test("generated Codex CLI middleware runtime is valid JavaScript", () => { const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-check-")); diff --git a/tests/main/codex-model-catalog.test.mjs b/tests/main/codex-model-catalog.test.mjs index 91e250c..1e68471 100644 --- a/tests/main/codex-model-catalog.test.mjs +++ b/tests/main/codex-model-catalog.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { buildCodexModelCatalog } from "../../src/main/codex-model-catalog.ts"; +import { buildCodexModelCatalog } from "../../packages/core/src/agents/codex/model-catalog.ts"; function catalogModelFor(config, slug) { const catalog = buildCodexModelCatalog(config, slug); diff --git a/tests/main/codex-patch-bridge.test.mjs b/tests/main/codex-patch-bridge.test.mjs index 0b71eb7..07879d7 100644 --- a/tests/main/codex-patch-bridge.test.mjs +++ b/tests/main/codex-patch-bridge.test.mjs @@ -4,7 +4,7 @@ import { prepareCodexApplyPatchBridgeRequest, transformCodexApplyPatchBridgeResponseValue, transformCodexApplyPatchBridgeSseEvent -} from "../../src/server/gateway/service.ts"; +} from "../../packages/core/src/gateway/service.ts"; const config = { Providers: [], diff --git a/tests/main/deep-link.test.mjs b/tests/main/deep-link.test.mjs index 95049ae..87e659f 100644 --- a/tests/main/deep-link.test.mjs +++ b/tests/main/deep-link.test.mjs @@ -6,7 +6,7 @@ import { parseProviderDeepLinkPayload, parseProviderManifestDeepLinkPayload, parseProviderManifestPayload -} from "../../src/shared/deep-link.ts"; +} from "../../packages/core/src/contracts/deep-link.ts"; function base64UrlJson(value) { return Buffer.from(JSON.stringify(value), "utf8") diff --git a/tests/main/gateway-virtual-models.test.mjs b/tests/main/gateway-virtual-models.test.mjs index 06e9895..aae1aa0 100644 --- a/tests/main/gateway-virtual-models.test.mjs +++ b/tests/main/gateway-virtual-models.test.mjs @@ -20,7 +20,7 @@ import { transformOpenAiResponsesHostedWebSearchResponseValue, transformOpenAiResponsesHostedWebSearchSseText, normalizeCoreGatewayVirtualModelProfiles -} from "../../src/server/gateway/service.ts"; +} from "../../packages/core/src/gateway/service.ts"; test("gateway config rewrites Fusion fixed base and vision models to core provider selectors", () => { const providerName = "Zhipu AI (China) - Coding Plan"; diff --git a/tests/main/local-agent-provider-codex.test.mjs b/tests/main/local-agent-provider-codex.test.mjs index e656f45..dd687fe 100644 --- a/tests/main/local-agent-provider-codex.test.mjs +++ b/tests/main/local-agent-provider-codex.test.mjs @@ -6,8 +6,8 @@ import { codexProviderAccountConfig, codexRateLimitResetCreditDetails, normalizeCodexProviderAccountConfig -} from "../../src/main/local-agent-providers/codex.ts"; -import { localAgentProviderApiKey } from "../../src/main/local-agent-providers/shared.ts"; +} from "../../packages/core/src/agents/local-providers/codex.ts"; +import { localAgentProviderApiKey } from "../../packages/core/src/agents/local-providers/shared.ts"; test("Codex provider account config includes manual reset meter", () => { const config = codexProviderAccountConfig(); diff --git a/tests/main/model-catalog-file.test.mjs b/tests/main/model-catalog-file.test.mjs index 4a012e7..26a0b16 100644 --- a/tests/main/model-catalog-file.test.mjs +++ b/tests/main/model-catalog-file.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; -import { loadModelCatalogPayload, modelCatalogPathCandidates } from "../../src/main/model-catalog-file.ts"; +import { loadModelCatalogPayload, modelCatalogPathCandidates } from "../../packages/core/src/models/catalog-file.ts"; test("modelCatalogPathCandidates prefers env paths and removes duplicates", () => { const previousCatalogPath = process.env.CCR_MODEL_CATALOG_PATH; diff --git a/tests/main/profile-launch-core.test.mjs b/tests/main/profile-launch-core.test.mjs index 1401e58..38623ba 100644 --- a/tests/main/profile-launch-core.test.mjs +++ b/tests/main/profile-launch-core.test.mjs @@ -11,7 +11,7 @@ import { resolveClaudeCodeSettingsFile, resolveCodexConfigFile, resolveProfileOpenSurface -} from "../../src/main/profile-launch-core.ts"; +} from "../../packages/core/src/profiles/launch-core.ts"; const claudeProfile = { agent: "claude-code", diff --git a/tests/main/profile-service.test.mjs b/tests/main/profile-service.test.mjs index fde0b40..477ba46 100644 --- a/tests/main/profile-service.test.mjs +++ b/tests/main/profile-service.test.mjs @@ -3,9 +3,9 @@ import { existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, rmSync, import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { createDefaultAppConfig } from "../../src/shared/default-config.ts"; -import { CONFIGDIR } from "../../src/main/constants.ts"; -import { applyProfileConfig, cleanupGeneratedBinBackups, restoreInactiveGlobalProfileConfigs } from "../../src/main/profile-service.ts"; +import { createDefaultAppConfig } from "../../packages/core/src/config/default-config.ts"; +import { CONFIGDIR } from "../../packages/core/src/config/constants.ts"; +import { applyProfileConfig, cleanupGeneratedBinBackups, restoreInactiveGlobalProfileConfigs } from "../../packages/core/src/profiles/service.ts"; test("profile service cleans stale generated bin backups only", () => { const configDir = mkdtempSync(path.join(os.tmpdir(), "ccr-generated-bin-cleanup-")); diff --git a/tests/main/provider-preset-utils.test.mjs b/tests/main/provider-preset-utils.test.mjs index 3aea090..a131d2e 100644 --- a/tests/main/provider-preset-utils.test.mjs +++ b/tests/main/provider-preset-utils.test.mjs @@ -7,7 +7,7 @@ import { providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList, providerPresetMatchesBaseUrl -} from "../../src/shared/provider-preset-utils.ts"; +} from "../../packages/core/src/providers/presets/utils.ts"; const openAiPreset = { aliases: ["OpenAI", "ChatGPT"], @@ -28,8 +28,16 @@ const anthropicPreset = { const presets = [openAiPreset, anthropicPreset]; test("provider preset matching accepts endpoint subpaths but rejects different hosts", () => { + const openRouterPreset = { + aliases: ["openrouter"], + endpoints: [{ baseUrl: "https://openrouter.ai/api/v1", protocols: ["openai_chat_completions"] }], + id: "openrouter", + name: "OpenRouter" + }; + assert.equal(providerPresetMatchesBaseUrl(openAiPreset, "https://api.openai.com/v1/chat/completions"), true); assert.equal(providerPresetMatchesBaseUrl(openAiPreset, "https://api.openai.com"), true); + assert.equal(providerPresetMatchesBaseUrl(openRouterPreset, "https://openrouter.ai/api"), true); assert.equal(providerPresetMatchesBaseUrl(openAiPreset, "https://proxy.example.com/v1"), false); assert.equal(findProviderPresetByBaseUrlInList(presets, "api.anthropic.com/v1/messages")?.id, "anthropic"); }); diff --git a/tests/main/provider-probe.test.mjs b/tests/main/provider-probe.test.mjs index 676db01..fdd0894 100644 --- a/tests/main/provider-probe.test.mjs +++ b/tests/main/provider-probe.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { isProviderProtocolEndpointSupportedForProbe } from "../../src/main/provider-probe.ts"; +import { isProviderProtocolEndpointSupportedForProbe } from "../../packages/core/src/providers/probe.ts"; test("protocol support probe does not treat Gemini auth errors as every protocol", () => { const message = "HTTP 403: API key not valid. Please pass a valid API key."; diff --git a/tests/main/provider-url.test.mjs b/tests/main/provider-url.test.mjs index f1e4d46..6270779 100644 --- a/tests/main/provider-url.test.mjs +++ b/tests/main/provider-url.test.mjs @@ -5,7 +5,7 @@ import { parseProviderBaseUrl, providerBaseUrlForProtocol, providerUrlWithDefaultScheme -} from "../../src/shared/provider-url.ts"; +} from "../../packages/core/src/providers/url.ts"; test("provider URL parsing strips endpoint paths and unsafe URL parts", () => { const parsed = parseProviderBaseUrl("https://user:secret@api.example.com/v1/chat/completions?token=secret#section"); diff --git a/tests/main/request-log-store.test.mjs b/tests/main/request-log-store.test.mjs index 901de11..d5b5895 100644 --- a/tests/main/request-log-store.test.mjs +++ b/tests/main/request-log-store.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; -import { RequestLogStore } from "../../src/main/request-log-store.ts"; +import { RequestLogStore } from "../../packages/core/src/observability/request-log-store.ts"; test("RequestLogStore keeps list rows lightweight and detail rows complete", async () => { const dir = mkdtempSync(path.join(tmpdir(), "ccr-request-log-test-")); diff --git a/tests/main/router-builtins.test.mjs b/tests/main/router-builtins.test.mjs index 095b842..ac29abe 100644 --- a/tests/main/router-builtins.test.mjs +++ b/tests/main/router-builtins.test.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { ClaudeCodeRouterPlugin } from "../../src/server/gateway/claude-code-router-plugin.ts"; -import { prepareGatewayUpstreamAttemptForTest } from "../../src/server/gateway/service.ts"; +import { ClaudeCodeRouterPlugin } from "../../packages/core/src/gateway/claude-code-router-plugin.ts"; +import { prepareGatewayUpstreamAttemptForTest } from "../../packages/core/src/gateway/service.ts"; function createRouterPlugin(options = {}) { const agent = options.agent ?? "claude-code"; diff --git a/tests/main/usage-normalization.test.mjs b/tests/main/usage-normalization.test.mjs index 0876d04..4da80e9 100644 --- a/tests/main/usage-normalization.test.mjs +++ b/tests/main/usage-normalization.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { normalizeUsageInputTokens } from "../../src/main/usage-normalization.ts"; +import { normalizeUsageInputTokens } from "../../packages/core/src/usage/normalization.ts"; test("normalizeUsageInputTokens subtracts cache tokens for OpenAI-compatible protocols", () => { const usage = normalizeUsageInputTokens( diff --git a/tests/main/usage-store.test.mjs b/tests/main/usage-store.test.mjs index ce8d20c..48c3890 100644 --- a/tests/main/usage-store.test.mjs +++ b/tests/main/usage-store.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; -import { UsageStore } from "../../src/main/usage-store.ts"; +import { UsageStore } from "../../packages/core/src/usage/store.ts"; test("UsageStore aggregates stats in SQLite without loading all events", async () => { const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-test-")); @@ -103,3 +103,35 @@ test("UsageStore excludes proxy rows by default and includes them on request", a rmSync(dir, { force: true, recursive: true }); } }); + +test("UsageStore treats null web RPC usage filters as empty filters", async () => { + const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-null-filter-test-")); + try { + const store = new UsageStore(path.join(dir, "usage.sqlite")); + + await store.record({ + createdAt: new Date().toISOString(), + durationMs: 10, + method: "POST", + model: "alpha-model", + path: "/v1/messages", + provider: "alpha", + requestId: "req-null-filter", + statusCode: 200, + usage: { + inputTokens: 3, + outputTokens: 4 + } + }); + + const stats = await store.getStats("7d", null); + assert.equal(stats.range, "7d"); + assert.equal(stats.totals.requestCount, 1); + + const defaultRangeStats = await store.getStats(null, null); + assert.equal(defaultRangeStats.range, "7d"); + assert.equal(defaultRangeStats.totals.requestCount, 1); + } finally { + rmSync(dir, { force: true, recursive: true }); + } +}); diff --git a/tests/main/web-client-bridge-html.test.mjs b/tests/main/web-client-bridge-html.test.mjs new file mode 100644 index 0000000..1a9295d --- /dev/null +++ b/tests/main/web-client-bridge-html.test.mjs @@ -0,0 +1,13 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import test from "node:test"; + +test("home renderer HTML loads the web client bridge before the app bundle", () => { + const projectRoot = process.cwd(); + const buildConfig = readFileSync(path.join(projectRoot, "build", "esbuild.config.mjs"), "utf8"); + const webBridge = readFileSync(path.join(projectRoot, "packages", "ui", "src", "web-client-bridge.ts"), "utf8"); + + assert.match(buildConfig, /beforeModuleScriptTags:\s*\[\s*'