diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 5feb020..226b503 100755 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,6 +1,9 @@ #!/usr/bin/env tsx import { Command } from 'commander'; import chalk from 'chalk'; +import { readFileSync } from 'node:fs'; + +const { version: pkgVersion } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8')) as { version: string }; import { makeAuthCommand } from './commands/auth.js'; import { makeModelCommand } from './commands/model.js'; import { makeProjectCommand } from './commands/project.js'; @@ -18,7 +21,7 @@ program chalk.bold('Routerly.ai') + ' — One gateway. Any AI model. Total control.\n' + chalk.gray('Proxy, route and cost-track AI model calls from OpenAI/Anthropic-compatible clients.') ) - .version('0.0.1'); + .version(pkgVersion); program.addCommand(makeStatusCommand()); program.addCommand(makeAuthCommand()); diff --git a/packages/dashboard/src/pages/TestPage.tsx b/packages/dashboard/src/pages/TestPage.tsx index 63eca53..4f7b9ed 100644 --- a/packages/dashboard/src/pages/TestPage.tsx +++ b/packages/dashboard/src/pages/TestPage.tsx @@ -75,7 +75,7 @@ export function TestPage() { abortControllerRef.current = controller; const payload = { - model: 'gpt-4o', + model: matchedProject?.routingModelId || matchedProject?.models?.[0]?.modelId || '', messages: newMessages, stream: true, }; diff --git a/packages/dashboard/src/pages/project/ProjectGeneralTab.tsx b/packages/dashboard/src/pages/project/ProjectGeneralTab.tsx index 7dfd1bf..d3bd704 100644 --- a/packages/dashboard/src/pages/project/ProjectGeneralTab.tsx +++ b/packages/dashboard/src/pages/project/ProjectGeneralTab.tsx @@ -59,7 +59,7 @@ export function ProjectGeneralTab() { const payload = isEdit ? { name: form.name, - routingModelId: project!.routingModelId || 'gpt-4o', + ...(project!.routingModelId ? { routingModelId: project!.routingModelId } : {}), models: project!.models.map(m => ({ modelId: m.modelId })), timeoutMs: parseInt(form.timeoutMs), } diff --git a/packages/dashboard/src/pages/project/ProjectTestTab.tsx b/packages/dashboard/src/pages/project/ProjectTestTab.tsx index 7ef35c8..6ceac4d 100644 --- a/packages/dashboard/src/pages/project/ProjectTestTab.tsx +++ b/packages/dashboard/src/pages/project/ProjectTestTab.tsx @@ -110,7 +110,7 @@ export function ProjectTestTab() { abortControllerRef.current = controller; const payload = { - model: project?.routingModelId || 'gpt-4o', + model: project?.routingModelId || project?.models?.[0]?.modelId || '', messages: newMessages, stream: true, }; diff --git a/packages/dashboard/src/pages/project/ProjectTokenCreatePage.tsx b/packages/dashboard/src/pages/project/ProjectTokenCreatePage.tsx index 5155438..0e736bc 100644 --- a/packages/dashboard/src/pages/project/ProjectTokenCreatePage.tsx +++ b/packages/dashboard/src/pages/project/ProjectTokenCreatePage.tsx @@ -23,8 +23,26 @@ export function ProjectTokenCreatePage() { const allLabels = Array.from(new Set((project.tokens || []).flatMap(t => t.labels || []))).sort(); async function copyToClipboard(token: string) { - try { await navigator.clipboard.writeText(token); setCopied(true); setTimeout(() => setCopied(false), 2000); } - catch { setErr('Failed to copy to clipboard.'); } + const success = () => { setCopied(true); setTimeout(() => setCopied(false), 2000); }; + try { + await navigator.clipboard.writeText(token); + success(); + } catch { + // Fallback for non-secure contexts (HTTP, docker self-hosted via IP) + try { + const el = document.createElement('textarea'); + el.value = token; + el.style.cssText = 'position:fixed;top:0;left:0;opacity:0;pointer-events:none'; + document.body.appendChild(el); + el.focus(); + el.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(el); + if (ok) { success(); } else { setErr('Copy failed — please select and copy the token manually.'); } + } catch { + setErr('Copy failed — please select and copy the token manually.'); + } + } } async function handleCreate(e: React.FormEvent) { diff --git a/packages/service/src/routes/api.ts b/packages/service/src/routes/api.ts index 5708154..966dc52 100644 --- a/packages/service/src/routes/api.ts +++ b/packages/service/src/routes/api.ts @@ -1,5 +1,6 @@ import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'; import { createHash } from 'node:crypto'; +import { readFileSync } from 'node:fs'; import bcrypt from 'bcrypt'; import { v4 as uuidv4 } from 'uuid'; import { randomBytes } from 'node:crypto'; @@ -10,6 +11,8 @@ import type { ModelConfig, ProjectConfig, UserConfig, RoleConfig, Permission, Pr import { getTrace } from '../routing/traceStore.js'; import { sendTestNotification } from '../notifications/sender.js'; +const { version: pkgVersion } = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8')) as { version: string }; + // SHA-256 for random tokens only (refresh tokens are not user-chosen passwords) function hashToken(t: string): string { return createHash('sha256').update(t).digest('hex'); @@ -805,7 +808,7 @@ export const apiRoutes: FastifyPluginAsync = async (fastify) => { // ─── GET /api/system/info ─────────────────────────────────────────────────── fastify.get('/api/system/info', async (_req, reply) => { return reply.send({ - version: '0.0.1', + version: pkgVersion, nodeVersion: process.version, platform: process.platform, configDir: CONFIG_PATHS.config, diff --git a/packages/service/src/server.ts b/packages/service/src/server.ts index 44762e3..7be3cd5 100644 --- a/packages/service/src/server.ts +++ b/packages/service/src/server.ts @@ -3,6 +3,7 @@ import cors from '@fastify/cors'; import staticFiles from '@fastify/static'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; import authPlugin from './plugins/auth.js'; import { loadSecret } from './plugins/jwt.js'; import { openaiRoutes } from './routes/openai.js'; @@ -11,6 +12,7 @@ import { apiRoutes } from './routes/api.js'; import { initConfigDirs, readConfig } from './config/loader.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); +const { version: pkgVersion } = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8')) as { version: string }; export async function buildServer() { const settings = await readConfig('settings'); @@ -72,7 +74,7 @@ export async function buildServer() { // ─── Health check ───────────────────────────────────────────────────────── fastify.get('/health', async () => ({ status: 'ok', - version: '0.0.1', + version: pkgVersion, timestamp: new Date().toISOString(), }));