191 lines
5.6 KiB
TypeScript
191 lines
5.6 KiB
TypeScript
import { app } from 'electron'
|
|
import { existsSync, readdirSync } from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { homedir, platform, arch } from 'node:os'
|
|
|
|
const isWin = platform() === 'win32'
|
|
const osLabel = isWin ? 'win' : platform() === 'darwin' ? 'mac' : platform() // mac | linux | win
|
|
const archLabel = arch() // arm64 | x64
|
|
|
|
export function isPackaged() {
|
|
return app.isPackaged
|
|
}
|
|
|
|
// Bundled web-ui directory.
|
|
// dev: <repo root> (or HERMES_WEB_UI_DIR)
|
|
// prod: <resources>/webui
|
|
export function webuiDir(): string {
|
|
if (app.isPackaged) return resolve(process.resourcesPath, 'webui')
|
|
return process.env.HERMES_WEB_UI_DIR?.trim() || resolve(app.getAppPath(), '..', '..')
|
|
}
|
|
|
|
export function webuiServerEntry(): string {
|
|
return join(webuiDir(), 'dist', 'server', 'index.js')
|
|
}
|
|
|
|
export function runtimePlatformKey(): string {
|
|
return `${osLabel}-${archLabel}`
|
|
}
|
|
|
|
export function desktopRuntimeDir(): string {
|
|
const override = process.env.HERMES_DESKTOP_RUNTIME_DIR?.trim()
|
|
if (override) return resolve(override)
|
|
return join(webUiHome(), 'desktop-runtime', runtimePlatformKey())
|
|
}
|
|
|
|
function packagedResourceDir(name: string): string {
|
|
return resolve(process.resourcesPath, name)
|
|
}
|
|
|
|
// dev: packages/desktop/resources/python/<os>-<arch>
|
|
// prod: <resources>/python when present, otherwise downloaded runtime cache.
|
|
export function pythonDir(): string {
|
|
if (app.isPackaged) {
|
|
const packaged = packagedResourceDir('python')
|
|
return existsSync(packaged) ? packaged : join(desktopRuntimeDir(), 'python')
|
|
}
|
|
return resolve(app.getAppPath(), 'resources', 'python', runtimePlatformKey())
|
|
}
|
|
|
|
export function nodeDir(): string {
|
|
if (app.isPackaged) {
|
|
const packaged = packagedResourceDir('node')
|
|
return existsSync(packaged) ? packaged : join(desktopRuntimeDir(), 'node')
|
|
}
|
|
return resolve(app.getAppPath(), 'resources', 'node', runtimePlatformKey())
|
|
}
|
|
|
|
export function nodeBinDir(): string {
|
|
const dir = nodeDir()
|
|
return isWin ? dir : join(dir, 'bin')
|
|
}
|
|
|
|
export function bundledNode(): string {
|
|
return isWin ? join(nodeDir(), 'node.exe') : join(nodeBinDir(), 'node')
|
|
}
|
|
|
|
export function gitDir(): string {
|
|
if (app.isPackaged) {
|
|
const packaged = packagedResourceDir('git')
|
|
return existsSync(packaged) ? packaged : join(desktopRuntimeDir(), 'git')
|
|
}
|
|
return resolve(app.getAppPath(), 'resources', 'git', runtimePlatformKey())
|
|
}
|
|
|
|
export function gitPathDirs(): string[] {
|
|
if (!isWin) return []
|
|
const dir = gitDir()
|
|
return [
|
|
join(dir, 'cmd'),
|
|
join(dir, 'mingw64', 'bin'),
|
|
join(dir, 'usr', 'bin'),
|
|
].filter(existsSync)
|
|
}
|
|
|
|
export function bundledGit(): string | undefined {
|
|
if (!isWin) return undefined
|
|
const git = join(gitDir(), 'cmd', 'git.exe')
|
|
return existsSync(git) ? git : undefined
|
|
}
|
|
|
|
export function bundledAgentBrowserHome(): string {
|
|
return join(pythonDir(), 'agent-browser')
|
|
}
|
|
|
|
function browserExecutableNames(): Set<string> {
|
|
if (isWin) return new Set(['chrome.exe'])
|
|
if (platform() === 'darwin') return new Set(['Google Chrome for Testing', 'Google Chrome', 'Chromium', 'chrome'])
|
|
return new Set(['chrome', 'chromium', 'chromium-browser'])
|
|
}
|
|
|
|
export function bundledBrowserExecutable(): string | undefined {
|
|
const names = browserExecutableNames()
|
|
const stack = [join(bundledAgentBrowserHome(), 'browsers'), bundledAgentBrowserHome()].filter(existsSync)
|
|
const visited = new Set<string>()
|
|
|
|
while (stack.length > 0) {
|
|
const dir = stack.pop()
|
|
if (!dir || visited.has(dir)) continue
|
|
visited.add(dir)
|
|
|
|
let entries
|
|
try {
|
|
entries = readdirSync(dir, { withFileTypes: true })
|
|
} catch {
|
|
continue
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
const path = join(dir, entry.name)
|
|
if (entry.isFile() && names.has(entry.name)) return path
|
|
if (entry.isDirectory()) stack.push(path)
|
|
}
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
export function pythonBinDir(): string {
|
|
const dir = pythonDir()
|
|
return isWin ? join(dir, 'Scripts') : join(dir, 'bin')
|
|
}
|
|
|
|
export function bundledPython(): string {
|
|
const dir = pythonDir()
|
|
return isWin ? join(dir, 'python.exe') : join(dir, 'bin', 'python3')
|
|
}
|
|
|
|
export function hermesBin(): string {
|
|
return isWin ? join(pythonBinDir(), 'hermes.exe') : join(pythonBinDir(), 'hermes')
|
|
}
|
|
|
|
export function hermesBinExists(): boolean {
|
|
return existsSync(hermesBin())
|
|
}
|
|
|
|
export function desktopIcon(): string {
|
|
if (app.isPackaged) return resolve(process.resourcesPath, 'build', 'icon.png')
|
|
return resolve(app.getAppPath(), 'build', 'icon.png')
|
|
}
|
|
|
|
export function desktopWindowsTrayIcon(): string {
|
|
if (app.isPackaged) return resolve(process.resourcesPath, 'build', 'trayWindows.png')
|
|
return resolve(app.getAppPath(), 'build', 'trayWindows.png')
|
|
}
|
|
|
|
export function desktopTrayTemplateIcon(): string {
|
|
if (app.isPackaged) return resolve(process.resourcesPath, 'build', 'trayTemplate.png')
|
|
return resolve(app.getAppPath(), 'build', 'trayTemplate.png')
|
|
}
|
|
|
|
export function webUiHome(): string {
|
|
return process.env.HERMES_WEB_UI_HOME?.trim() || resolve(homedir(), '.hermes-web-ui')
|
|
}
|
|
|
|
export function hermesHome(): string {
|
|
const override = process.env.HERMES_HOME?.trim()
|
|
if (override) return resolve(override)
|
|
|
|
const defaultHome = resolve(homedir(), '.hermes')
|
|
|
|
if (isWin) {
|
|
const candidates = [
|
|
process.env.LOCALAPPDATA,
|
|
process.env.APPDATA,
|
|
]
|
|
.map(value => value?.trim())
|
|
.filter((value): value is string => !!value)
|
|
.map(value => resolve(value, 'hermes'))
|
|
|
|
for (const candidate of candidates) {
|
|
if (existsSync(candidate)) return candidate
|
|
}
|
|
}
|
|
|
|
return defaultHome
|
|
}
|
|
|
|
export function tokenFile(): string {
|
|
return join(webUiHome(), '.token')
|
|
}
|