import { delimiter, join, resolve } from 'node:path' import { homedir, tmpdir } from 'node:os' import { createHash } from 'node:crypto' export function desktopEnvironment(root: string, stateHome: string, port: number, env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv { const home = homedir() return { ...env, NODE_ENV: 'production', HERMES_DESKTOP: 'true', BIND_HOST: '127.0.0.1', PORT: String(port), HERMES_WEB_UI_HOME: stateHome, HERMES_WEBUI_STATE_DIR: stateHome, HERMES_WEB_UI_MCP_BIN: join(root, 'bin/hermes-ui-mcp.mjs'), HERMES_WEB_UI_STOP_GATEWAYS_ON_SHUTDOWN: '1', HERMES_AGENT_BRIDGE_STOP_ON_SHUTDOWN: '1', HERMES_LAN_DISCOVERY_ENABLED: 'false', HERMES_WEB_UI_DISABLE_UPDATE_CHECK: 'true', ...(process.platform === 'win32' ? {} : { HERMES_AGENT_BRIDGE_ENDPOINT: env.HERMES_AGENT_BRIDGE_ENDPOINT || `ipc://${join(tmpdir(), `hermes-desktop-${createHash('sha256').update(stateHome).digest('hex').slice(0, 16)}.sock`)}`, }), PATH: [env.PATH, join(home, '.local/bin'), join(home, '.npm-global/bin'), '/opt/homebrew/bin', '/usr/local/bin'].filter(Boolean).join(delimiter), } } export function desktopStateHome(userData: string, env: NodeJS.ProcessEnv = process.env): string { return resolve(env.HERMES_WEB_UI_HOME?.trim() || env.HERMES_WEBUI_STATE_DIR?.trim() || join(userData, 'state')) } export function isInternalUrl(value: string, origin: string): boolean { try { return new URL(value).origin === origin } catch { return false } } export function isExternalUrl(value: string): boolean { try { return ['https:', 'http:', 'mailto:'].includes(new URL(value).protocol) } catch { return false } } export function storedPort(value: unknown): number { return typeof value === 'number' && Number.isInteger(value) && value >= 1024 && value <= 65535 ? value : 0 } export function isDesktopPermissionAllowed(permission: string, requestingOrigin: string, origin: string, mediaTypes: string[] = []): boolean { if (!isInternalUrl(requestingOrigin, origin)) return false if (permission === 'notifications' || permission === 'clipboard-sanitized-write') return true return permission === 'media' && mediaTypes.length > 0 && mediaTypes.every(type => type === 'audio') }