diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index a85c663115c..ae57ab45cc4 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "sync-assets": "rm -rf public/fonts public/ds-assets && cp -r node_modules/@nous-research/ui/dist/fonts public/fonts && cp -r node_modules/@nous-research/ui/dist/assets public/ds-assets", + "sync-assets": "node scripts/sync-assets.cjs", "predev": "npm run sync-assets", "prebuild": "npm run sync-assets", "dev": "vite", diff --git a/apps/dashboard/scripts/sync-assets.cjs b/apps/dashboard/scripts/sync-assets.cjs new file mode 100644 index 00000000000..ad4ba2ee4c6 --- /dev/null +++ b/apps/dashboard/scripts/sync-assets.cjs @@ -0,0 +1,46 @@ +#!/usr/bin/env node +/** + * Copy font and asset folders from @nous-research/ui into public/ for Vite. + * + * Locates @nous-research/ui by walking up from this script looking for + * node_modules/@nous-research/ui — works whether the dep is co-located + * (non-workspace layout) or hoisted to the repo root (npm workspaces). + */ +const fs = require('node:fs') +const path = require('node:path') + +const DASHBOARD_ROOT = path.resolve(__dirname, '..') + +function locateUiPackage() { + let dir = DASHBOARD_ROOT + const { root } = path.parse(dir) + while (true) { + const candidate = path.join(dir, 'node_modules', '@nous-research', 'ui') + if (fs.existsSync(path.join(candidate, 'package.json'))) { + return candidate + } + if (dir === root) break + dir = path.dirname(dir) + } + throw new Error( + '@nous-research/ui not found. Run `npm install` from the repo root.' + ) +} + +const uiRoot = locateUiPackage() +const distRoot = path.join(uiRoot, 'dist') + +const mappings = [ + ['fonts', path.join(DASHBOARD_ROOT, 'public', 'fonts')], + ['assets', path.join(DASHBOARD_ROOT, 'public', 'ds-assets')], +] + +for (const [srcName, destPath] of mappings) { + const srcPath = path.join(distRoot, srcName) + if (!fs.existsSync(srcPath)) { + throw new Error(`Missing ${srcPath} in @nous-research/ui — rebuild that package.`) + } + fs.rmSync(destPath, { recursive: true, force: true }) + fs.cpSync(srcPath, destPath, { recursive: true }) + console.log(`synced ${path.relative(DASHBOARD_ROOT, destPath)}`) +} diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index dfb603a9ba6..ea469c7333a 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -1118,9 +1118,56 @@ function installMediaPermissions() { }) } +function resolveRemoteBackend() { + const rawUrl = process.env.HERMES_DESKTOP_REMOTE_URL + const rawToken = process.env.HERMES_DESKTOP_REMOTE_TOKEN + if (!rawUrl) return null + if (!rawToken) { + throw new Error( + 'HERMES_DESKTOP_REMOTE_URL is set but HERMES_DESKTOP_REMOTE_TOKEN is not. ' + + 'Both must be provided to connect to a remote Hermes backend.' + ) + } + + let parsed + try { + parsed = new URL(rawUrl) + } catch (error) { + throw new Error(`HERMES_DESKTOP_REMOTE_URL is not a valid URL: ${error.message}`) + } + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + throw new Error(`HERMES_DESKTOP_REMOTE_URL must be http:// or https://, got ${parsed.protocol}`) + } + + const baseUrl = `${parsed.protocol}//${parsed.host}` + const wsScheme = parsed.protocol === 'https:' ? 'wss' : 'ws' + const wsUrl = `${wsScheme}://${parsed.host}/api/ws?token=${encodeURIComponent(rawToken)}` + + return { baseUrl, token: rawToken, wsUrl } +} + async function startHermes() { if (connectionPromise) return connectionPromise + const remote = resolveRemoteBackend() + if (remote) { + connectionPromise = (async () => { + rememberLog(`Using remote Hermes backend at ${remote.baseUrl}`) + await waitForHermes(remote.baseUrl, remote.token) + return { + baseUrl: remote.baseUrl, + token: remote.token, + wsUrl: remote.wsUrl, + logs: hermesLog.slice(-80), + windowButtonPosition: getWindowButtonPosition() + } + })().catch(error => { + connectionPromise = null + throw error + }) + return connectionPromise + } + connectionPromise = (async () => { const port = await pickPort() const token = crypto.randomBytes(32).toString('base64url')