Files
lingxi-ai/packages/desktop/src/main/paths.ts
T
yi 7d10320a82
Build / build (push) Has been cancelled
NPM Lockfile Check / npm ci --ignore-scripts (push) Has been cancelled
Playwright / e2e (push) Has been cancelled
feat: 灵犀 Studio Web UI 定制版
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 11:29:11 +08:00

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