Merge branch 'bb/gui' of github.com:NousResearch/hermes-agent into bb/gui

This commit is contained in:
Brooklyn Nicholson 2026-05-05 13:17:46 -05:00
commit ddf83e95b0
3 changed files with 94 additions and 1 deletions

View file

@ -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",

View file

@ -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)}`)
}

View file

@ -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')