@bitseek/hermes-webui 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -0
- package/bin/hermes-webui.mjs +588 -0
- package/package.json +25 -0
- package/scripts/sync-vendor.mjs +74 -0
- package/templates/launchd/com.bitseek.hermes-webui.plist +21 -0
- package/templates/systemd/hermes-webui.service +13 -0
- package/templates/windows/hermes-webui-task.ps1 +3 -0
- package/vendor/agent-frontend-shell/.bitseek-source.json +6 -0
- package/vendor/agent-frontend-shell/.dockerignore +7 -0
- package/vendor/agent-frontend-shell/.env.docker.example +89 -0
- package/vendor/agent-frontend-shell/.env.example +34 -0
- package/vendor/agent-frontend-shell/.github/FUNDING.yml +3 -0
- package/vendor/agent-frontend-shell/.github/workflows/browser-smoke.yml +42 -0
- package/vendor/agent-frontend-shell/.github/workflows/docker-smoke.yml +233 -0
- package/vendor/agent-frontend-shell/.github/workflows/native-windows-startup.yml +132 -0
- package/vendor/agent-frontend-shell/.github/workflows/release.yml +57 -0
- package/vendor/agent-frontend-shell/.github/workflows/tests.yml +88 -0
- package/vendor/agent-frontend-shell/.vscode/launch.json +59 -0
- package/vendor/agent-frontend-shell/.vscode/settings.json +13 -0
- package/vendor/agent-frontend-shell/AGENTS.md +80 -0
- package/vendor/agent-frontend-shell/ARCHITECTURE.md +1658 -0
- package/vendor/agent-frontend-shell/BUGS.md +52 -0
- package/vendor/agent-frontend-shell/CHANGELOG.md +7295 -0
- package/vendor/agent-frontend-shell/CONTRIBUTING.md +205 -0
- package/vendor/agent-frontend-shell/CONTRIBUTORS.md +107 -0
- package/vendor/agent-frontend-shell/DESIGN.md +173 -0
- package/vendor/agent-frontend-shell/Dockerfile +91 -0
- package/vendor/agent-frontend-shell/LICENSE +21 -0
- package/vendor/agent-frontend-shell/README-CUSTOM.md +76 -0
- package/vendor/agent-frontend-shell/README.md +705 -0
- package/vendor/agent-frontend-shell/ROADMAP.md +351 -0
- package/vendor/agent-frontend-shell/SPRINTS.md +147 -0
- package/vendor/agent-frontend-shell/TESTING.md +1932 -0
- package/vendor/agent-frontend-shell/THEMES.md +170 -0
- package/vendor/agent-frontend-shell/api/__init__.py +1 -0
- package/vendor/agent-frontend-shell/api/agent_health.py +392 -0
- package/vendor/agent-frontend-shell/api/agent_sessions.py +782 -0
- package/vendor/agent-frontend-shell/api/auth.py +592 -0
- package/vendor/agent-frontend-shell/api/background.py +87 -0
- package/vendor/agent-frontend-shell/api/clarify.py +238 -0
- package/vendor/agent-frontend-shell/api/commands.py +124 -0
- package/vendor/agent-frontend-shell/api/compression_anchor.py +134 -0
- package/vendor/agent-frontend-shell/api/config.py +5178 -0
- package/vendor/agent-frontend-shell/api/dashboard_probe.py +255 -0
- package/vendor/agent-frontend-shell/api/extensions.py +253 -0
- package/vendor/agent-frontend-shell/api/gateway_chat.py +435 -0
- package/vendor/agent-frontend-shell/api/gateway_watcher.py +230 -0
- package/vendor/agent-frontend-shell/api/goals.py +608 -0
- package/vendor/agent-frontend-shell/api/helpers.py +474 -0
- package/vendor/agent-frontend-shell/api/kanban_bridge.py +1255 -0
- package/vendor/agent-frontend-shell/api/metering.py +194 -0
- package/vendor/agent-frontend-shell/api/models.py +4210 -0
- package/vendor/agent-frontend-shell/api/oauth.py +770 -0
- package/vendor/agent-frontend-shell/api/onboarding.py +1046 -0
- package/vendor/agent-frontend-shell/api/passkeys.py +365 -0
- package/vendor/agent-frontend-shell/api/profiles.py +1499 -0
- package/vendor/agent-frontend-shell/api/providers.py +2175 -0
- package/vendor/agent-frontend-shell/api/request_diagnostics.py +160 -0
- package/vendor/agent-frontend-shell/api/rollback.py +320 -0
- package/vendor/agent-frontend-shell/api/routes.py +13990 -0
- package/vendor/agent-frontend-shell/api/run_journal.py +284 -0
- package/vendor/agent-frontend-shell/api/runner_client.py +156 -0
- package/vendor/agent-frontend-shell/api/runtime_adapter.py +431 -0
- package/vendor/agent-frontend-shell/api/session_discoverability.py +640 -0
- package/vendor/agent-frontend-shell/api/session_events.py +45 -0
- package/vendor/agent-frontend-shell/api/session_lifecycle.py +208 -0
- package/vendor/agent-frontend-shell/api/session_ops.py +207 -0
- package/vendor/agent-frontend-shell/api/session_recovery.py +655 -0
- package/vendor/agent-frontend-shell/api/skill_usage.py +32 -0
- package/vendor/agent-frontend-shell/api/startup.py +128 -0
- package/vendor/agent-frontend-shell/api/state_sync.py +187 -0
- package/vendor/agent-frontend-shell/api/streaming.py +7048 -0
- package/vendor/agent-frontend-shell/api/system_health.py +167 -0
- package/vendor/agent-frontend-shell/api/terminal.py +410 -0
- package/vendor/agent-frontend-shell/api/turn_journal.py +214 -0
- package/vendor/agent-frontend-shell/api/updates.py +1261 -0
- package/vendor/agent-frontend-shell/api/upload.py +322 -0
- package/vendor/agent-frontend-shell/api/usage.py +26 -0
- package/vendor/agent-frontend-shell/api/workspace.py +867 -0
- package/vendor/agent-frontend-shell/api/workspace_git.py +1261 -0
- package/vendor/agent-frontend-shell/api/worktrees.py +357 -0
- package/vendor/agent-frontend-shell/bootstrap.py +492 -0
- package/vendor/agent-frontend-shell/ctl.sh +427 -0
- package/vendor/agent-frontend-shell/docker-compose.custom.yml +26 -0
- package/vendor/agent-frontend-shell/docker-compose.three-container.yml +168 -0
- package/vendor/agent-frontend-shell/docker-compose.two-container.yml +147 -0
- package/vendor/agent-frontend-shell/docker-compose.yml +57 -0
- package/vendor/agent-frontend-shell/docker_init.bash +459 -0
- package/vendor/agent-frontend-shell/docs/CONTRACTS.md +207 -0
- package/vendor/agent-frontend-shell/docs/EXTENSIONS.md +212 -0
- package/vendor/agent-frontend-shell/docs/ISSUES.md +23 -0
- package/vendor/agent-frontend-shell/docs/UIUX-GUIDE.md +196 -0
- package/vendor/agent-frontend-shell/docs/advanced-chat-setup.md +83 -0
- package/vendor/agent-frontend-shell/docs/docker.md +337 -0
- package/vendor/agent-frontend-shell/docs/onboarding-agent-checklist.md +207 -0
- package/vendor/agent-frontend-shell/docs/onboarding.md +202 -0
- package/vendor/agent-frontend-shell/docs/remote-access.md +75 -0
- package/vendor/agent-frontend-shell/docs/rfcs/README.md +53 -0
- package/vendor/agent-frontend-shell/docs/rfcs/agent-source-boundary.md +70 -0
- package/vendor/agent-frontend-shell/docs/rfcs/canonical-session-resolution.md +124 -0
- package/vendor/agent-frontend-shell/docs/rfcs/hermes-run-adapter-contract.md +1079 -0
- package/vendor/agent-frontend-shell/docs/rfcs/turn-journal.md +195 -0
- package/vendor/agent-frontend-shell/docs/rfcs/webui-run-state-consistency-contract.md +157 -0
- package/vendor/agent-frontend-shell/docs/supervisor.md +280 -0
- package/vendor/agent-frontend-shell/docs/troubleshooting.md +132 -0
- package/vendor/agent-frontend-shell/docs/ui-ux/index.html +863 -0
- package/vendor/agent-frontend-shell/docs/ui-ux/two-stage-proposal.html +768 -0
- package/vendor/agent-frontend-shell/docs/why-hermes.md +489 -0
- package/vendor/agent-frontend-shell/docs/workspace-git.md +92 -0
- package/vendor/agent-frontend-shell/docs/wsl-autostart.md +126 -0
- package/vendor/agent-frontend-shell/eslint.runtime-guard.config.mjs +35 -0
- package/vendor/agent-frontend-shell/extensions/bitseek-design-system.md +330 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/apple-touch-icon.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/empty-logo.svg +739 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-192.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-32.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.ico +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v2.svg +751 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v3.svg +739 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/branding.js +112 -0
- package/vendor/agent-frontend-shell/extensions/branding/config.json +14 -0
- package/vendor/agent-frontend-shell/extensions/branding/manifest.json +53 -0
- package/vendor/agent-frontend-shell/extensions/index.js +67 -0
- package/vendor/agent-frontend-shell/extensions/loader/hermes-loader.js +77 -0
- package/vendor/agent-frontend-shell/extensions/manifest.json +16 -0
- package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.css +333 -0
- package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.js +487 -0
- package/vendor/agent-frontend-shell/extensions/pages/manifest.json +6 -0
- package/vendor/agent-frontend-shell/extensions/pages/registry.css +56 -0
- package/vendor/agent-frontend-shell/extensions/pages/registry.js +302 -0
- package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.css +93 -0
- package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.js +98 -0
- package/vendor/agent-frontend-shell/install.sh +63 -0
- package/vendor/agent-frontend-shell/mcp_server.py +567 -0
- package/vendor/agent-frontend-shell/package.json +12 -0
- package/vendor/agent-frontend-shell/pyproject.toml +56 -0
- package/vendor/agent-frontend-shell/pytest.ini +3 -0
- package/vendor/agent-frontend-shell/requirements.txt +5 -0
- package/vendor/agent-frontend-shell/server.py +624 -0
- package/vendor/agent-frontend-shell/start.ps1 +210 -0
- package/vendor/agent-frontend-shell/start.sh +65 -0
- package/vendor/agent-frontend-shell/static/apple-touch-icon.png +0 -0
- package/vendor/agent-frontend-shell/static/boot.js +1990 -0
- package/vendor/agent-frontend-shell/static/commands.js +1402 -0
- package/vendor/agent-frontend-shell/static/favicon-192.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-32.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-512.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-512.svg +18 -0
- package/vendor/agent-frontend-shell/static/favicon.ico +0 -0
- package/vendor/agent-frontend-shell/static/favicon.svg +20 -0
- package/vendor/agent-frontend-shell/static/i18n.js +15389 -0
- package/vendor/agent-frontend-shell/static/icons.js +92 -0
- package/vendor/agent-frontend-shell/static/index.html +1506 -0
- package/vendor/agent-frontend-shell/static/login.js +177 -0
- package/vendor/agent-frontend-shell/static/manifest.json +53 -0
- package/vendor/agent-frontend-shell/static/messages.js +3521 -0
- package/vendor/agent-frontend-shell/static/onboarding.js +800 -0
- package/vendor/agent-frontend-shell/static/panels.js +7995 -0
- package/vendor/agent-frontend-shell/static/pwa-startup.js +83 -0
- package/vendor/agent-frontend-shell/static/sessions.js +5165 -0
- package/vendor/agent-frontend-shell/static/style.css +4774 -0
- package/vendor/agent-frontend-shell/static/sw.js +173 -0
- package/vendor/agent-frontend-shell/static/terminal.js +632 -0
- package/vendor/agent-frontend-shell/static/ui.js +8997 -0
- package/vendor/agent-frontend-shell/static/vendor/js-yaml/4.1.0/js-yaml.min.js +2 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.css +1 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.js +1 -0
- package/vendor/agent-frontend-shell/static/vendor/smd.min.js +29 -0
- package/vendor/agent-frontend-shell/static/workspace.js +680 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createReadStream,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
statSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs'
|
|
12
|
+
import { access, constants } from 'node:fs/promises'
|
|
13
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
14
|
+
import { delimiter, dirname, join, resolve } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
import { homedir, platform } from 'node:os'
|
|
17
|
+
import { createInterface } from 'node:readline'
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
const packageRoot = resolve(__dirname, '..')
|
|
21
|
+
const vendorRoot = resolve(packageRoot, 'vendor', 'agent-frontend-shell')
|
|
22
|
+
const bootstrapPath = resolve(vendorRoot, 'bootstrap.py')
|
|
23
|
+
const serverPath = resolve(vendorRoot, 'server.py')
|
|
24
|
+
const requirementsPath = resolve(vendorRoot, 'requirements.txt')
|
|
25
|
+
const sourceInfoPath = resolve(vendorRoot, '.bitseek-source.json')
|
|
26
|
+
const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8'))
|
|
27
|
+
|
|
28
|
+
const DEFAULT_HOST = '127.0.0.1'
|
|
29
|
+
const DEFAULT_PORT = 8787
|
|
30
|
+
|
|
31
|
+
function usage() {
|
|
32
|
+
console.log(`@bitseek/hermes-webui v${packageJson.version}
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
hermes-webui start [--foreground] [--port <port>] [--host <host>] [--open]
|
|
36
|
+
hermes-webui stop [--port <port>]
|
|
37
|
+
hermes-webui restart [--port <port>] [--host <host>]
|
|
38
|
+
hermes-webui status [--port <port>]
|
|
39
|
+
hermes-webui logs [--lines <n>] [--follow]
|
|
40
|
+
hermes-webui doctor [--port <port>]
|
|
41
|
+
hermes-webui version
|
|
42
|
+
|
|
43
|
+
Environment:
|
|
44
|
+
HERMES_HOME Defaults to ~/.hermes
|
|
45
|
+
HERMES_WEBUI_STATE_DIR Defaults to $HERMES_HOME/webui
|
|
46
|
+
HERMES_WEBUI_PYTHON Use an existing Python interpreter
|
|
47
|
+
HERMES_WEBUI_VENV_DIR Defaults to $HERMES_WEBUI_STATE_DIR/venv
|
|
48
|
+
HERMES_WEBUI_AGENT_DIR Optional Hermes Agent checkout
|
|
49
|
+
`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseArgs(argv) {
|
|
53
|
+
const command = argv[2] || 'start'
|
|
54
|
+
const options = {
|
|
55
|
+
command,
|
|
56
|
+
host: process.env.HERMES_WEBUI_HOST || DEFAULT_HOST,
|
|
57
|
+
port: Number(process.env.HERMES_WEBUI_PORT || DEFAULT_PORT),
|
|
58
|
+
foreground: false,
|
|
59
|
+
open: false,
|
|
60
|
+
follow: false,
|
|
61
|
+
lines: 100,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const rest = argv.slice(3)
|
|
65
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
66
|
+
const arg = rest[i]
|
|
67
|
+
if (arg === '--foreground') options.foreground = true
|
|
68
|
+
else if (arg === '--open') options.open = true
|
|
69
|
+
else if (arg === '--follow') options.follow = true
|
|
70
|
+
else if (arg === '--host') options.host = rest[++i]
|
|
71
|
+
else if (arg.startsWith('--host=')) options.host = arg.slice('--host='.length)
|
|
72
|
+
else if (arg === '--port') options.port = Number(rest[++i])
|
|
73
|
+
else if (arg.startsWith('--port=')) options.port = Number(arg.slice('--port='.length))
|
|
74
|
+
else if (arg === '--lines') options.lines = Number(rest[++i])
|
|
75
|
+
else if (arg.startsWith('--lines=')) options.lines = Number(arg.slice('--lines='.length))
|
|
76
|
+
else if (/^\d+$/.test(arg)) options.port = Number(arg)
|
|
77
|
+
else if (arg === '-h' || arg === '--help') options.command = 'help'
|
|
78
|
+
else {
|
|
79
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535) {
|
|
84
|
+
throw new Error(`Invalid port: ${options.port}`)
|
|
85
|
+
}
|
|
86
|
+
if (!Number.isInteger(options.lines) || options.lines < 1) {
|
|
87
|
+
throw new Error(`Invalid --lines: ${options.lines}`)
|
|
88
|
+
}
|
|
89
|
+
return options
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function expandHome(value) {
|
|
93
|
+
if (!value) return value
|
|
94
|
+
if (value === '~') return homedir()
|
|
95
|
+
if (value.startsWith('~/')) return join(homedir(), value.slice(2))
|
|
96
|
+
return value
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function hermesHome() {
|
|
100
|
+
return resolve(expandHome(process.env.HERMES_HOME || join(homedir(), '.hermes')))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function stateDir() {
|
|
104
|
+
return resolve(expandHome(process.env.HERMES_WEBUI_STATE_DIR || join(hermesHome(), 'webui')))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function statePaths() {
|
|
108
|
+
const dir = stateDir()
|
|
109
|
+
return {
|
|
110
|
+
dir,
|
|
111
|
+
pid: join(dir, 'server.pid'),
|
|
112
|
+
log: join(dir, 'server.log'),
|
|
113
|
+
runtime: join(dir, 'runtime.json'),
|
|
114
|
+
venv: resolve(expandHome(process.env.HERMES_WEBUI_VENV_DIR || join(dir, 'venv'))),
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readJson(path) {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
121
|
+
} catch {
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sourceInfo() {
|
|
127
|
+
return readJson(sourceInfoPath)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function runtimeInfo() {
|
|
131
|
+
return readJson(statePaths().runtime)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function writeRuntimeInfo(info) {
|
|
135
|
+
const paths = statePaths()
|
|
136
|
+
mkdirSync(paths.dir, { recursive: true })
|
|
137
|
+
writeFileSync(paths.runtime, `${JSON.stringify(info, null, 2)}\n`, { mode: 0o600 })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function readPid() {
|
|
141
|
+
try {
|
|
142
|
+
const raw = readFileSync(statePaths().pid, 'utf8').trim()
|
|
143
|
+
const pid = Number(raw)
|
|
144
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null
|
|
145
|
+
} catch {
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function writePid(pid) {
|
|
151
|
+
const paths = statePaths()
|
|
152
|
+
mkdirSync(paths.dir, { recursive: true })
|
|
153
|
+
writeFileSync(paths.pid, `${pid}\n`, { mode: 0o600 })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function removePid() {
|
|
157
|
+
rmSync(statePaths().pid, { force: true })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isRunning(pid) {
|
|
161
|
+
if (!pid) return false
|
|
162
|
+
try {
|
|
163
|
+
process.kill(pid, 0)
|
|
164
|
+
return true
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return err?.code === 'EPERM'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function commandExists(command) {
|
|
171
|
+
const result = spawnSync(platform() === 'win32' ? 'where' : 'sh', platform() === 'win32'
|
|
172
|
+
? [command]
|
|
173
|
+
: ['-c', 'command -v "$1" >/dev/null 2>&1', 'sh', command], {
|
|
174
|
+
stdio: 'ignore',
|
|
175
|
+
})
|
|
176
|
+
return result.status === 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function findSystemPython() {
|
|
180
|
+
const candidates = platform() === 'win32' ? ['python.exe', 'python'] : ['python3', 'python']
|
|
181
|
+
for (const candidate of candidates) {
|
|
182
|
+
const result = spawnSync(candidate, ['--version'], { stdio: 'ignore' })
|
|
183
|
+
if (result.status === 0) return candidate
|
|
184
|
+
}
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function venvPython(venvDir) {
|
|
189
|
+
return platform() === 'win32'
|
|
190
|
+
? join(venvDir, 'Scripts', 'python.exe')
|
|
191
|
+
: join(venvDir, 'bin', 'python')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function runChecked(command, args, options = {}) {
|
|
195
|
+
const result = spawnSync(command, args, {
|
|
196
|
+
stdio: options.stdio || 'inherit',
|
|
197
|
+
env: options.env || process.env,
|
|
198
|
+
cwd: options.cwd,
|
|
199
|
+
encoding: options.encoding,
|
|
200
|
+
})
|
|
201
|
+
if (result.status !== 0) {
|
|
202
|
+
throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`)
|
|
203
|
+
}
|
|
204
|
+
return result
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function pythonImportsOk(python) {
|
|
208
|
+
const result = spawnSync(python, ['-c', 'import yaml; import cryptography; print("ok")'], {
|
|
209
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
210
|
+
encoding: 'utf8',
|
|
211
|
+
})
|
|
212
|
+
return result.status === 0
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function preparePython() {
|
|
216
|
+
if (process.env.HERMES_WEBUI_PYTHON) {
|
|
217
|
+
return resolve(expandHome(process.env.HERMES_WEBUI_PYTHON))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const paths = statePaths()
|
|
221
|
+
const python = venvPython(paths.venv)
|
|
222
|
+
if (!existsSync(python)) {
|
|
223
|
+
const basePython = findSystemPython()
|
|
224
|
+
if (!basePython) throw new Error('Python 3 is required but was not found in PATH')
|
|
225
|
+
mkdirSync(paths.dir, { recursive: true })
|
|
226
|
+
runChecked(basePython, ['-m', 'venv', '--system-site-packages', paths.venv])
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!pythonImportsOk(python)) {
|
|
230
|
+
runChecked(python, ['-m', 'pip', 'install', '-r', requirementsPath])
|
|
231
|
+
}
|
|
232
|
+
return python
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function resolveAgentDir() {
|
|
236
|
+
const candidates = [
|
|
237
|
+
process.env.HERMES_WEBUI_AGENT_DIR,
|
|
238
|
+
join(hermesHome(), 'hermes-agent'),
|
|
239
|
+
resolve(packageRoot, '..', 'hermes-agent'),
|
|
240
|
+
].filter(Boolean)
|
|
241
|
+
|
|
242
|
+
for (const candidate of candidates) {
|
|
243
|
+
const resolved = resolve(expandHome(candidate))
|
|
244
|
+
if (existsSync(join(resolved, 'run_agent.py'))) return resolved
|
|
245
|
+
}
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function buildEnv(python, options) {
|
|
250
|
+
const paths = statePaths()
|
|
251
|
+
const env = {
|
|
252
|
+
...process.env,
|
|
253
|
+
HERMES_HOME: hermesHome(),
|
|
254
|
+
HERMES_WEBUI_STATE_DIR: paths.dir,
|
|
255
|
+
HERMES_WEBUI_HOST: options.host,
|
|
256
|
+
HERMES_WEBUI_PORT: String(options.port),
|
|
257
|
+
HERMES_WEBUI_PYTHON: python,
|
|
258
|
+
HERMES_WEBUI_PRESERVE_ENV: '1',
|
|
259
|
+
PYTHONUNBUFFERED: '1',
|
|
260
|
+
}
|
|
261
|
+
const agentDir = resolveAgentDir()
|
|
262
|
+
if (agentDir) {
|
|
263
|
+
env.HERMES_WEBUI_AGENT_DIR = agentDir
|
|
264
|
+
env.PYTHONPATH = env.PYTHONPATH ? `${agentDir}${delimiter}${env.PYTHONPATH}` : agentDir
|
|
265
|
+
}
|
|
266
|
+
if (process.env.BITSEEK_HERMES_BUNDLE_MANAGED) {
|
|
267
|
+
env.BITSEEK_HERMES_BUNDLE_MANAGED = process.env.BITSEEK_HERMES_BUNDLE_MANAGED
|
|
268
|
+
}
|
|
269
|
+
return env
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function healthCheck(host, port, timeoutMs = 1000) {
|
|
273
|
+
const controller = new AbortController()
|
|
274
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
275
|
+
try {
|
|
276
|
+
const res = await fetch(`http://${host}:${port}/health`, {
|
|
277
|
+
signal: controller.signal,
|
|
278
|
+
cache: 'no-store',
|
|
279
|
+
})
|
|
280
|
+
if (!res.ok) return { ok: false, status: res.status }
|
|
281
|
+
const text = await res.text()
|
|
282
|
+
return { ok: text.includes('"status": "ok"'), status: res.status, body: text }
|
|
283
|
+
} catch (err) {
|
|
284
|
+
return { ok: false, error: err?.message || String(err) }
|
|
285
|
+
} finally {
|
|
286
|
+
clearTimeout(timer)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function waitForHealth(host, port, child, timeoutMs = 30000) {
|
|
291
|
+
const started = Date.now()
|
|
292
|
+
while (Date.now() - started < timeoutMs) {
|
|
293
|
+
if (child && !isRunning(child.pid)) {
|
|
294
|
+
return { ok: false, error: 'process exited before health check passed' }
|
|
295
|
+
}
|
|
296
|
+
const result = await healthCheck(host, port, 1500)
|
|
297
|
+
if (result.ok) return result
|
|
298
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 500))
|
|
299
|
+
}
|
|
300
|
+
return { ok: false, error: `health check did not pass within ${timeoutMs / 1000}s` }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function start(options) {
|
|
304
|
+
if (!existsSync(bootstrapPath) || !existsSync(serverPath)) {
|
|
305
|
+
throw new Error('Vendored agent-frontend-shell runtime is missing. Run npm run sync:vendor.')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const existing = readPid()
|
|
309
|
+
if (existing && isRunning(existing)) {
|
|
310
|
+
throw new Error(`Hermes WebUI is already running (PID ${existing})`)
|
|
311
|
+
}
|
|
312
|
+
removePid()
|
|
313
|
+
|
|
314
|
+
const occupied = await portOccupied(options.host, options.port)
|
|
315
|
+
if (occupied) {
|
|
316
|
+
throw new Error(`Port ${options.port} is already occupied; not managed by this wrapper`)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const paths = statePaths()
|
|
320
|
+
mkdirSync(paths.dir, { recursive: true })
|
|
321
|
+
const python = preparePython()
|
|
322
|
+
const env = buildEnv(python, options)
|
|
323
|
+
const args = [serverPath]
|
|
324
|
+
|
|
325
|
+
if (options.foreground) {
|
|
326
|
+
const child = spawn(python, args, {
|
|
327
|
+
stdio: 'inherit',
|
|
328
|
+
env,
|
|
329
|
+
cwd: vendorRoot,
|
|
330
|
+
})
|
|
331
|
+
writePid(child.pid)
|
|
332
|
+
writeRuntimeInfo({
|
|
333
|
+
pid: child.pid,
|
|
334
|
+
mode: 'foreground',
|
|
335
|
+
host: options.host,
|
|
336
|
+
port: options.port,
|
|
337
|
+
log: null,
|
|
338
|
+
python,
|
|
339
|
+
hermes_home: hermesHome(),
|
|
340
|
+
state_dir: paths.dir,
|
|
341
|
+
agent_dir: env.HERMES_WEBUI_AGENT_DIR || null,
|
|
342
|
+
vendor: sourceInfo(),
|
|
343
|
+
started_at: new Date().toISOString(),
|
|
344
|
+
})
|
|
345
|
+
process.on('SIGINT', () => child.kill('SIGINT'))
|
|
346
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'))
|
|
347
|
+
child.on('exit', (code, signal) => {
|
|
348
|
+
removePid()
|
|
349
|
+
process.exit(code ?? (signal ? 128 : 1))
|
|
350
|
+
})
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const logFd = openSync(paths.log, 'a')
|
|
355
|
+
const child = spawn(python, args, {
|
|
356
|
+
detached: true,
|
|
357
|
+
stdio: ['ignore', logFd, logFd],
|
|
358
|
+
env,
|
|
359
|
+
cwd: vendorRoot,
|
|
360
|
+
})
|
|
361
|
+
child.unref()
|
|
362
|
+
writePid(child.pid)
|
|
363
|
+
writeRuntimeInfo({
|
|
364
|
+
pid: child.pid,
|
|
365
|
+
mode: 'daemon',
|
|
366
|
+
host: options.host,
|
|
367
|
+
port: options.port,
|
|
368
|
+
log: paths.log,
|
|
369
|
+
python,
|
|
370
|
+
hermes_home: hermesHome(),
|
|
371
|
+
state_dir: paths.dir,
|
|
372
|
+
agent_dir: env.HERMES_WEBUI_AGENT_DIR || null,
|
|
373
|
+
vendor: sourceInfo(),
|
|
374
|
+
started_at: new Date().toISOString(),
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
console.log(`Starting Bitseek Hermes WebUI (PID ${child.pid}, port ${options.port})...`)
|
|
378
|
+
const health = await waitForHealth(options.host, options.port, child)
|
|
379
|
+
if (!health.ok) {
|
|
380
|
+
console.log(`Health check failed: ${health.error || health.status || 'unknown error'}`)
|
|
381
|
+
console.log(`Log: ${paths.log}`)
|
|
382
|
+
process.exit(1)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.log('Bitseek Hermes WebUI started')
|
|
386
|
+
console.log(`URL: http://${options.host === '127.0.0.1' ? 'localhost' : options.host}:${options.port}`)
|
|
387
|
+
console.log(`Log: ${paths.log}`)
|
|
388
|
+
if (options.open) openBrowser(options.host, options.port)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function stop() {
|
|
392
|
+
const pid = readPid()
|
|
393
|
+
if (!pid) {
|
|
394
|
+
const info = runtimeInfo()
|
|
395
|
+
if (info?.port) {
|
|
396
|
+
const occupied = await portOccupied(info.host || DEFAULT_HOST, Number(info.port))
|
|
397
|
+
if (occupied) {
|
|
398
|
+
console.log(`WebUI pid file missing. Port ${info.port} is occupied, not managed by this wrapper.`)
|
|
399
|
+
process.exit(1)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
console.log('Bitseek Hermes WebUI is not running')
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!isRunning(pid)) {
|
|
407
|
+
removePid()
|
|
408
|
+
console.log(`Bitseek Hermes WebUI was not running; cleaned stale PID ${pid}`)
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
process.kill(pid, 'SIGTERM')
|
|
413
|
+
for (let i = 0; i < 20; i += 1) {
|
|
414
|
+
if (!isRunning(pid)) break
|
|
415
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250))
|
|
416
|
+
}
|
|
417
|
+
if (isRunning(pid)) {
|
|
418
|
+
process.kill(pid, 'SIGKILL')
|
|
419
|
+
}
|
|
420
|
+
removePid()
|
|
421
|
+
console.log(`Bitseek Hermes WebUI stopped (PID ${pid})`)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function restart(options) {
|
|
425
|
+
await stop()
|
|
426
|
+
await start(options)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function status(options) {
|
|
430
|
+
const paths = statePaths()
|
|
431
|
+
const pid = readPid()
|
|
432
|
+
const running = pid ? isRunning(pid) : false
|
|
433
|
+
const info = runtimeInfo() || {}
|
|
434
|
+
const host = info.host || options.host
|
|
435
|
+
const port = Number(info.port || options.port)
|
|
436
|
+
const health = running ? await healthCheck(host, port, 1500) : { ok: false }
|
|
437
|
+
const vendor = sourceInfo()
|
|
438
|
+
|
|
439
|
+
console.log(`Status: ${running ? 'running' : 'stopped'}`)
|
|
440
|
+
console.log(`PID: ${pid || '-'}`)
|
|
441
|
+
console.log(`Host: ${host}`)
|
|
442
|
+
console.log(`Port: ${port}`)
|
|
443
|
+
console.log(`Health: ${health.ok ? 'ok' : 'unavailable'}`)
|
|
444
|
+
console.log(`Log: ${paths.log}`)
|
|
445
|
+
console.log(`State dir: ${paths.dir}`)
|
|
446
|
+
console.log(`Python: ${info.python || process.env.HERMES_WEBUI_PYTHON || '-'}`)
|
|
447
|
+
console.log(`Hermes home: ${info.hermes_home || hermesHome()}`)
|
|
448
|
+
console.log(`Hermes Agent dir: ${info.agent_dir || resolveAgentDir() || '-'}`)
|
|
449
|
+
console.log(`Vendor commit: ${vendor?.commit || '-'}`)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function logs(options) {
|
|
453
|
+
const path = statePaths().log
|
|
454
|
+
if (!existsSync(path)) {
|
|
455
|
+
throw new Error(`Log file does not exist: ${path}`)
|
|
456
|
+
}
|
|
457
|
+
if (options.follow) {
|
|
458
|
+
await tailFollow(path, options.lines)
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
const content = readFileSync(path, 'utf8')
|
|
462
|
+
const lines = content.split(/\r?\n/)
|
|
463
|
+
console.log(lines.slice(-options.lines).join('\n'))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function tailFollow(path, lines) {
|
|
467
|
+
const content = readFileSync(path, 'utf8')
|
|
468
|
+
console.log(content.split(/\r?\n/).slice(-lines).join('\n'))
|
|
469
|
+
const child = spawn(platform() === 'win32' ? 'powershell.exe' : 'tail', platform() === 'win32'
|
|
470
|
+
? ['-NoProfile', '-Command', `Get-Content -Wait -Tail 0 ${JSON.stringify(path)}`]
|
|
471
|
+
: ['-f', path], {
|
|
472
|
+
stdio: 'inherit',
|
|
473
|
+
})
|
|
474
|
+
child.on('exit', (code) => process.exit(code ?? 0))
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async function doctor(options) {
|
|
478
|
+
const checks = []
|
|
479
|
+
const add = (name, ok, detail = '') => checks.push({ name, ok, detail })
|
|
480
|
+
const paths = statePaths()
|
|
481
|
+
|
|
482
|
+
add('node version', /^v(2[0-4])\./.test(process.version), process.version)
|
|
483
|
+
add('vendor runtime', existsSync(vendorRoot), vendorRoot)
|
|
484
|
+
add('bootstrap.py', existsSync(bootstrapPath), bootstrapPath)
|
|
485
|
+
add('server.py', existsSync(serverPath), serverPath)
|
|
486
|
+
add('requirements.txt', existsSync(requirementsPath), requirementsPath)
|
|
487
|
+
add('source metadata', existsSync(sourceInfoPath), sourceInfoPath)
|
|
488
|
+
mkdirSync(paths.dir, { recursive: true })
|
|
489
|
+
add('state dir writable', await writable(paths.dir), paths.dir)
|
|
490
|
+
add('HERMES_HOME writable', await writable(hermesHome()), hermesHome())
|
|
491
|
+
let python = process.env.HERMES_WEBUI_PYTHON || (existsSync(venvPython(paths.venv)) ? venvPython(paths.venv) : findSystemPython())
|
|
492
|
+
if (python && !pythonImportsOk(python)) {
|
|
493
|
+
try {
|
|
494
|
+
python = preparePython()
|
|
495
|
+
} catch {
|
|
496
|
+
// Keep the original python value so the failed check below points at it.
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
add('python available', Boolean(python), python || 'not found')
|
|
500
|
+
if (python) add('webui requirements import', pythonImportsOk(python), python)
|
|
501
|
+
const agentDir = resolveAgentDir()
|
|
502
|
+
add('Hermes Agent dir', Boolean(agentDir), agentDir || 'not found')
|
|
503
|
+
add('run_agent.py', Boolean(agentDir && existsSync(join(agentDir, 'run_agent.py'))), agentDir ? join(agentDir, 'run_agent.py') : 'not found')
|
|
504
|
+
add('port available', !(await portOccupied(options.host, options.port)), `${options.host}:${options.port}`)
|
|
505
|
+
add('no vendored .env', !existsSync(join(vendorRoot, '.env')), join(vendorRoot, '.env'))
|
|
506
|
+
add('no vendored .git', !existsSync(join(vendorRoot, '.git')), join(vendorRoot, '.git'))
|
|
507
|
+
add('no vendored .venv', !existsSync(join(vendorRoot, '.venv')), join(vendorRoot, '.venv'))
|
|
508
|
+
add('no vendored node_modules', !existsSync(join(vendorRoot, 'node_modules')), join(vendorRoot, 'node_modules'))
|
|
509
|
+
|
|
510
|
+
for (const check of checks) {
|
|
511
|
+
console.log(`${check.ok ? 'ok' : 'fail'} - ${check.name}${check.detail ? `: ${check.detail}` : ''}`)
|
|
512
|
+
}
|
|
513
|
+
if (checks.some((check) => !check.ok)) process.exit(1)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function writable(path) {
|
|
517
|
+
try {
|
|
518
|
+
mkdirSync(path, { recursive: true })
|
|
519
|
+
await access(path, constants.W_OK)
|
|
520
|
+
return true
|
|
521
|
+
} catch {
|
|
522
|
+
return false
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function portOccupied(host, port) {
|
|
527
|
+
const result = await healthCheck(host, port, 300)
|
|
528
|
+
if (result.ok || result.status) return true
|
|
529
|
+
|
|
530
|
+
return new Promise((resolveOccupied) => {
|
|
531
|
+
const tester = spawn(process.execPath, ['-e', `
|
|
532
|
+
const net = require('net');
|
|
533
|
+
const server = net.createServer();
|
|
534
|
+
server.once('error', () => process.exit(1));
|
|
535
|
+
server.once('listening', () => server.close(() => process.exit(0)));
|
|
536
|
+
server.listen(${JSON.stringify(port)}, ${JSON.stringify(host)});
|
|
537
|
+
`], { stdio: 'ignore' })
|
|
538
|
+
tester.on('exit', (code) => resolveOccupied(code !== 0))
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function openBrowser(host, port) {
|
|
543
|
+
const url = `http://${host === '127.0.0.1' ? 'localhost' : host}:${port}`
|
|
544
|
+
const opener = platform() === 'win32' ? 'cmd' : platform() === 'darwin' ? 'open' : 'xdg-open'
|
|
545
|
+
const args = platform() === 'win32' ? ['/c', 'start', '', url] : [url]
|
|
546
|
+
spawn(opener, args, { stdio: 'ignore', detached: true }).unref()
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function main() {
|
|
550
|
+
const options = parseArgs(process.argv)
|
|
551
|
+
switch (options.command) {
|
|
552
|
+
case 'help':
|
|
553
|
+
case '-h':
|
|
554
|
+
case '--help':
|
|
555
|
+
usage()
|
|
556
|
+
break
|
|
557
|
+
case 'version':
|
|
558
|
+
case '-v':
|
|
559
|
+
case '--version':
|
|
560
|
+
console.log(packageJson.version)
|
|
561
|
+
break
|
|
562
|
+
case 'start':
|
|
563
|
+
await start(options)
|
|
564
|
+
break
|
|
565
|
+
case 'stop':
|
|
566
|
+
await stop()
|
|
567
|
+
break
|
|
568
|
+
case 'restart':
|
|
569
|
+
await restart(options)
|
|
570
|
+
break
|
|
571
|
+
case 'status':
|
|
572
|
+
await status(options)
|
|
573
|
+
break
|
|
574
|
+
case 'logs':
|
|
575
|
+
await logs(options)
|
|
576
|
+
break
|
|
577
|
+
case 'doctor':
|
|
578
|
+
await doctor(options)
|
|
579
|
+
break
|
|
580
|
+
default:
|
|
581
|
+
throw new Error(`Unknown command: ${options.command}`)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
main().catch((err) => {
|
|
586
|
+
console.error(`error: ${err?.message || err}`)
|
|
587
|
+
process.exit(1)
|
|
588
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bitseek/hermes-webui",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Npm wrapper for Bitseek Hermes WebUI runtime.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"hermes-webui": "./bin/hermes-webui.mjs"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20 <25"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"scripts/",
|
|
15
|
+
"templates/",
|
|
16
|
+
"vendor/agent-frontend-shell/",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"sync:vendor": "node ./scripts/sync-vendor.mjs",
|
|
21
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
22
|
+
"check": "node --check ./bin/hermes-webui.mjs && node --check ./scripts/sync-vendor.mjs"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT"
|
|
25
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { dirname, resolve } from 'node:path'
|
|
4
|
+
import { execFileSync } from 'node:child_process'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const packageRoot = resolve(__dirname, '..')
|
|
9
|
+
const defaultSource = resolve(packageRoot, '..', 'agent-frontend-shell')
|
|
10
|
+
const sourceRoot = resolve(process.env.AGENT_FRONTEND_SHELL_DIR || defaultSource)
|
|
11
|
+
const vendorRoot = resolve(packageRoot, 'vendor', 'agent-frontend-shell')
|
|
12
|
+
|
|
13
|
+
const excludedNames = new Set([
|
|
14
|
+
'.git',
|
|
15
|
+
'.env',
|
|
16
|
+
'.venv',
|
|
17
|
+
'venv',
|
|
18
|
+
'node_modules',
|
|
19
|
+
'__pycache__',
|
|
20
|
+
'.pytest_cache',
|
|
21
|
+
'.ruff_cache',
|
|
22
|
+
'tests',
|
|
23
|
+
'hermes_data',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
function runGit(args) {
|
|
27
|
+
try {
|
|
28
|
+
return execFileSync('git', args, {
|
|
29
|
+
cwd: sourceRoot,
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
32
|
+
}).trim()
|
|
33
|
+
} catch {
|
|
34
|
+
return 'unknown'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function filter(src) {
|
|
39
|
+
const name = src.split(/[\\/]/).pop()
|
|
40
|
+
if (excludedNames.has(name)) return false
|
|
41
|
+
if (name.endsWith('.pyc')) return false
|
|
42
|
+
if (name.endsWith('.log')) return false
|
|
43
|
+
if (name.endsWith('.pid')) return false
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!existsSync(resolve(sourceRoot, 'bootstrap.py')) || !existsSync(resolve(sourceRoot, 'server.py'))) {
|
|
48
|
+
console.error(`agent-frontend-shell source not found or incomplete: ${sourceRoot}`)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
rmSync(vendorRoot, { recursive: true, force: true })
|
|
53
|
+
mkdirSync(vendorRoot, { recursive: true })
|
|
54
|
+
cpSync(sourceRoot, vendorRoot, {
|
|
55
|
+
recursive: true,
|
|
56
|
+
dereference: false,
|
|
57
|
+
filter,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const sourceInfo = {
|
|
61
|
+
repo: 'adv-org/agent-frontend-shell',
|
|
62
|
+
branch: runGit(['branch', '--show-current']),
|
|
63
|
+
commit: runGit(['rev-parse', 'HEAD']),
|
|
64
|
+
synced_at: new Date().toISOString(),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
writeFileSync(
|
|
68
|
+
resolve(vendorRoot, '.bitseek-source.json'),
|
|
69
|
+
`${JSON.stringify(sourceInfo, null, 2)}\n`,
|
|
70
|
+
{ mode: 0o644 },
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
console.log(`Synced ${sourceRoot} -> ${vendorRoot}`)
|
|
74
|
+
console.log(`${sourceInfo.repo}@${sourceInfo.branch} ${sourceInfo.commit}`)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key>
|
|
6
|
+
<string>com.bitseek.hermes-webui</string>
|
|
7
|
+
<key>ProgramArguments</key>
|
|
8
|
+
<array>
|
|
9
|
+
<string>/Users/USER/.hermes/local-bundle/runtime/node/bin/node</string>
|
|
10
|
+
<string>/Users/USER/.hermes/local-bundle/node-global/bin/hermes-webui</string>
|
|
11
|
+
<string>start</string>
|
|
12
|
+
<string>--foreground</string>
|
|
13
|
+
<string>--port</string>
|
|
14
|
+
<string>8787</string>
|
|
15
|
+
</array>
|
|
16
|
+
<key>RunAtLoad</key>
|
|
17
|
+
<true/>
|
|
18
|
+
<key>KeepAlive</key>
|
|
19
|
+
<true/>
|
|
20
|
+
</dict>
|
|
21
|
+
</plist>
|