@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,427 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}"
|
|
6
|
+
PID_FILE="${HERMES_WEBUI_PID_FILE:-${HERMES_HOME}/webui.pid}"
|
|
7
|
+
LOG_FILE="${HERMES_WEBUI_LOG_FILE:-${HERMES_HOME}/webui.log}"
|
|
8
|
+
STATE_FILE="${HERMES_WEBUI_CTL_STATE_FILE:-${HERMES_HOME}/webui.ctl.env}"
|
|
9
|
+
DEFAULT_STATE_DIR="${HERMES_WEBUI_STATE_DIR:-${HERMES_HOME}/webui}"
|
|
10
|
+
DEFAULT_LAUNCHD_LABEL="${HERMES_WEBUI_LAUNCHD_LABEL:-com.parantoux.hermes-webui}"
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
Usage: ./ctl.sh <command> [args]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
start [bootstrap args...] Start Hermes WebUI as a background daemon
|
|
18
|
+
stop Stop the daemon started by ctl.sh
|
|
19
|
+
restart [bootstrap args...] Stop, then start again
|
|
20
|
+
status Show daemon, host/port, log, and health status
|
|
21
|
+
logs [--lines N] [--follow|--no-follow]
|
|
22
|
+
Show the daemon log (defaults to tail -n 100 -f)
|
|
23
|
+
EOF
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ensure_home() {
|
|
27
|
+
mkdir -p "${HERMES_HOME}" "${DEFAULT_STATE_DIR}"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_load_repo_dotenv_preserving_env() {
|
|
31
|
+
local env_file="${REPO_ROOT}/.env"
|
|
32
|
+
[[ -f "${env_file}" ]] || return 0
|
|
33
|
+
|
|
34
|
+
local -a preserved=()
|
|
35
|
+
local line key value
|
|
36
|
+
while IFS= read -r line || [[ -n "${line}" ]]; do
|
|
37
|
+
line="${line#${line%%[![:space:]]*}}"
|
|
38
|
+
[[ -z "${line}" || "${line}" == \#* || "${line}" != *=* ]] && continue
|
|
39
|
+
key="${line%%=*}"
|
|
40
|
+
key="${key#export }"
|
|
41
|
+
key="${key//[[:space:]]/}"
|
|
42
|
+
[[ "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
|
43
|
+
if [[ -n "${!key+x}" ]]; then
|
|
44
|
+
value="${!key}"
|
|
45
|
+
preserved+=("${key}=${value}")
|
|
46
|
+
fi
|
|
47
|
+
done < "${env_file}"
|
|
48
|
+
|
|
49
|
+
set -a
|
|
50
|
+
# shellcheck source=/dev/null
|
|
51
|
+
source "${env_file}"
|
|
52
|
+
set +a
|
|
53
|
+
|
|
54
|
+
local assignment
|
|
55
|
+
if [[ ${#preserved[@]} -gt 0 ]]; then
|
|
56
|
+
for assignment in "${preserved[@]}"; do
|
|
57
|
+
export "${assignment}"
|
|
58
|
+
done
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_find_python() {
|
|
63
|
+
if [[ -n "${HERMES_WEBUI_PYTHON:-}" ]]; then
|
|
64
|
+
printf '%s\n' "${HERMES_WEBUI_PYTHON}"
|
|
65
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
66
|
+
command -v python3
|
|
67
|
+
elif command -v python >/dev/null 2>&1; then
|
|
68
|
+
command -v python
|
|
69
|
+
else
|
|
70
|
+
echo "[ctl] Python 3 is required to run bootstrap.py" >&2
|
|
71
|
+
return 1
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_parse_launch_binding() {
|
|
76
|
+
CTL_HOST="${HERMES_WEBUI_HOST:-127.0.0.1}"
|
|
77
|
+
CTL_PORT="${HERMES_WEBUI_PORT:-8787}"
|
|
78
|
+
local arg next_is_host=0 saw_port=0
|
|
79
|
+
for arg in "$@"; do
|
|
80
|
+
if (( next_is_host )); then
|
|
81
|
+
CTL_HOST="${arg}"
|
|
82
|
+
next_is_host=0
|
|
83
|
+
continue
|
|
84
|
+
fi
|
|
85
|
+
case "${arg}" in
|
|
86
|
+
--host)
|
|
87
|
+
next_is_host=1
|
|
88
|
+
;;
|
|
89
|
+
--host=*)
|
|
90
|
+
CTL_HOST="${arg#--host=}"
|
|
91
|
+
;;
|
|
92
|
+
--*)
|
|
93
|
+
;;
|
|
94
|
+
*)
|
|
95
|
+
if (( ! saw_port )) && [[ "${arg}" =~ ^[0-9]+$ ]]; then
|
|
96
|
+
CTL_PORT="${arg}"
|
|
97
|
+
saw_port=1
|
|
98
|
+
fi
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
101
|
+
done
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_build_bootstrap_args() {
|
|
105
|
+
CTL_BOOTSTRAP_ARGS=()
|
|
106
|
+
local arg next_is_host=0 saw_port=0
|
|
107
|
+
for arg in "$@"; do
|
|
108
|
+
if (( next_is_host )); then
|
|
109
|
+
next_is_host=0
|
|
110
|
+
continue
|
|
111
|
+
fi
|
|
112
|
+
case "${arg}" in
|
|
113
|
+
--host)
|
|
114
|
+
next_is_host=1
|
|
115
|
+
;;
|
|
116
|
+
--host=*)
|
|
117
|
+
;;
|
|
118
|
+
--*)
|
|
119
|
+
CTL_BOOTSTRAP_ARGS+=("${arg}")
|
|
120
|
+
;;
|
|
121
|
+
*)
|
|
122
|
+
if (( ! saw_port )) && [[ "${arg}" =~ ^[0-9]+$ ]]; then
|
|
123
|
+
saw_port=1
|
|
124
|
+
else
|
|
125
|
+
CTL_BOOTSTRAP_ARGS+=("${arg}")
|
|
126
|
+
fi
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
done
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_write_state() {
|
|
133
|
+
local pid="$1" host="$2" port="$3" python_exe="${4:-}"
|
|
134
|
+
local state_dir="${HERMES_WEBUI_STATE_DIR:-${DEFAULT_STATE_DIR}}"
|
|
135
|
+
{
|
|
136
|
+
printf 'PID=%q\n' "${pid}"
|
|
137
|
+
printf 'REPO_ROOT=%q\n' "${REPO_ROOT}"
|
|
138
|
+
printf 'PYTHON_EXE=%q\n' "${python_exe}"
|
|
139
|
+
printf 'HOST=%q\n' "${host}"
|
|
140
|
+
printf 'PORT=%q\n' "${port}"
|
|
141
|
+
printf 'LOG_FILE=%q\n' "${LOG_FILE}"
|
|
142
|
+
printf 'STATE_DIR=%q\n' "${state_dir}"
|
|
143
|
+
printf 'STARTED_AT=%q\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
144
|
+
} > "${STATE_FILE}"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_load_state_if_present() {
|
|
148
|
+
if [[ -f "${STATE_FILE}" ]]; then
|
|
149
|
+
# shellcheck source=/dev/null
|
|
150
|
+
source "${STATE_FILE}"
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_pid_from_file() {
|
|
155
|
+
[[ -f "${PID_FILE}" ]] || return 1
|
|
156
|
+
local pid
|
|
157
|
+
pid="$(tr -d '[:space:]' < "${PID_FILE}")"
|
|
158
|
+
[[ "${pid}" =~ ^[0-9]+$ ]] || return 1
|
|
159
|
+
printf '%s\n' "${pid}"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_is_alive() {
|
|
163
|
+
local pid="$1"
|
|
164
|
+
kill -0 "${pid}" >/dev/null 2>&1
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_proc_args() {
|
|
168
|
+
local pid="$1"
|
|
169
|
+
ps -p "${pid}" -o args= 2>/dev/null || true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_is_owned_webui_pid() {
|
|
173
|
+
local pid="$1" args state_repo="" state_python=""
|
|
174
|
+
[[ -f "${STATE_FILE}" ]] || return 1
|
|
175
|
+
_load_state_if_present
|
|
176
|
+
state_repo="${REPO_ROOT:-}"
|
|
177
|
+
state_python="${PYTHON_EXE:-}"
|
|
178
|
+
[[ "${state_repo}" == "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ]] || return 1
|
|
179
|
+
args="$(_proc_args "${pid}")"
|
|
180
|
+
[[ -n "${args}" ]] || return 1
|
|
181
|
+
[[ "${args}" == *"${state_repo}/bootstrap.py"* || "${args}" == *"${state_repo}/server.py"* || "${args}" == *"${state_repo}/start.sh"* || ( -n "${state_python}" && "${args}" == *"${state_python}"* ) ]]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_current_pid() {
|
|
185
|
+
local pid
|
|
186
|
+
pid="$(_pid_from_file)" || return 1
|
|
187
|
+
if _is_alive "${pid}" && _is_owned_webui_pid "${pid}"; then
|
|
188
|
+
printf '%s\n' "${pid}"
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
return 1
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_clear_stale_pid() {
|
|
195
|
+
if [[ -f "${PID_FILE}" ]]; then
|
|
196
|
+
rm -f "${PID_FILE}" "${STATE_FILE}"
|
|
197
|
+
echo "[ctl] Removed stale PID file: ${PID_FILE}"
|
|
198
|
+
fi
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_pid_listens_on_port() {
|
|
202
|
+
# Best-effort check that PID $1 has a listening socket on TCP port $2.
|
|
203
|
+
# macOS (where launchd exists) ships lsof; if we can't determine ownership we
|
|
204
|
+
# return 2 ("unknown") so the caller can fall back conservatively rather than
|
|
205
|
+
# guess. Never blocks on a hard failure.
|
|
206
|
+
local pid="$1" port="$2"
|
|
207
|
+
[[ "${pid}" =~ ^[0-9]+$ && "${port}" =~ ^[0-9]+$ ]] || return 2
|
|
208
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
209
|
+
if lsof -nP -p "${pid}" -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
210
|
+
return 0 # PID is listening on that port → real conflict
|
|
211
|
+
fi
|
|
212
|
+
return 1 # PID is alive but NOT listening on that port → no conflict
|
|
213
|
+
fi
|
|
214
|
+
return 2 # can't determine
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
_launchd_webui_pid() {
|
|
218
|
+
[[ "${HERMES_WEBUI_CTL_ALLOW_LAUNCHD_CONFLICT:-0}" == "1" ]] && return 1
|
|
219
|
+
command -v launchctl >/dev/null 2>&1 || return 1
|
|
220
|
+
local label="${HERMES_WEBUI_LAUNCHD_LABEL:-${DEFAULT_LAUNCHD_LABEL}}"
|
|
221
|
+
[[ -n "${label}" ]] || return 1
|
|
222
|
+
local uid launchd_out pid
|
|
223
|
+
uid="$(id -u)"
|
|
224
|
+
launchd_out="$(launchctl print "gui/${uid}/${label}" 2>/dev/null)" || return 1
|
|
225
|
+
pid="$(printf '%s\n' "${launchd_out}" | awk '/^[[:space:]]*pid = / {print $3; exit}')"
|
|
226
|
+
[[ "${pid}" =~ ^[0-9]+$ ]] || return 1
|
|
227
|
+
(( pid > 0 )) || return 1
|
|
228
|
+
_is_alive "${pid}" || return 1
|
|
229
|
+
# Only treat the launchd job as a conflict for the port we are about to bind.
|
|
230
|
+
# A second instance on a DIFFERENT port (e.g. HERMES_WEBUI_PORT=8788 for a
|
|
231
|
+
# test build) does not collide with the launchd-managed default and must be
|
|
232
|
+
# allowed to start (#3291 over-block fix). When port ownership can't be
|
|
233
|
+
# determined (no lsof), fall back to the conservative previous behavior of
|
|
234
|
+
# only guarding the default port so non-default ports are never wrongly blocked.
|
|
235
|
+
local want_port="${CTL_PORT:-${HERMES_WEBUI_PORT:-8787}}"
|
|
236
|
+
_pid_listens_on_port "${pid}" "${want_port}"
|
|
237
|
+
case "$?" in
|
|
238
|
+
0) printf '%s\n' "${pid}"; return 0 ;; # launchd job listens on our port → block
|
|
239
|
+
1) return 1 ;; # launchd job on a different port → allow
|
|
240
|
+
*) # unknown: only guard the default port
|
|
241
|
+
if [[ "${want_port}" == "8787" ]]; then
|
|
242
|
+
printf '%s\n' "${pid}"; return 0
|
|
243
|
+
fi
|
|
244
|
+
return 1 ;;
|
|
245
|
+
esac
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
start_cmd() {
|
|
249
|
+
ensure_home
|
|
250
|
+
_load_repo_dotenv_preserving_env
|
|
251
|
+
export HERMES_WEBUI_STATE_DIR="${HERMES_WEBUI_STATE_DIR:-${DEFAULT_STATE_DIR}}"
|
|
252
|
+
mkdir -p "${HERMES_WEBUI_STATE_DIR}"
|
|
253
|
+
_parse_launch_binding "$@"
|
|
254
|
+
_build_bootstrap_args "$@"
|
|
255
|
+
export HERMES_WEBUI_HOST="${CTL_HOST}"
|
|
256
|
+
export HERMES_WEBUI_PORT="${CTL_PORT}"
|
|
257
|
+
|
|
258
|
+
local existing_pid
|
|
259
|
+
if existing_pid="$(_current_pid 2>/dev/null)"; then
|
|
260
|
+
echo "[ctl] Hermes WebUI is already running (PID ${existing_pid})"
|
|
261
|
+
return 0
|
|
262
|
+
fi
|
|
263
|
+
local launchd_pid
|
|
264
|
+
if launchd_pid="$(_launchd_webui_pid 2>/dev/null)"; then
|
|
265
|
+
echo "[ctl] Refusing to start a second Hermes WebUI while launchd job ${HERMES_WEBUI_LAUNCHD_LABEL:-${DEFAULT_LAUNCHD_LABEL}} is running (PID ${launchd_pid})." >&2
|
|
266
|
+
echo "[ctl] Use launchctl kickstart -k gui/$(id -u)/${HERMES_WEBUI_LAUNCHD_LABEL:-${DEFAULT_LAUNCHD_LABEL}} or disable the launchd job before using ctl.sh start." >&2
|
|
267
|
+
return 2
|
|
268
|
+
fi
|
|
269
|
+
_clear_stale_pid >/dev/null 2>&1 || true
|
|
270
|
+
|
|
271
|
+
local python_exe pid
|
|
272
|
+
python_exe="$(_find_python)"
|
|
273
|
+
: >> "${LOG_FILE}"
|
|
274
|
+
(
|
|
275
|
+
cd "${REPO_ROOT}"
|
|
276
|
+
trap '' HUP
|
|
277
|
+
export HERMES_WEBUI_PRESERVE_ENV=1
|
|
278
|
+
exec nohup "${python_exe}" "${REPO_ROOT}/bootstrap.py" --no-browser --foreground --host "${CTL_HOST}" "${CTL_PORT}" ${CTL_BOOTSTRAP_ARGS[@]+"${CTL_BOOTSTRAP_ARGS[@]}"}
|
|
279
|
+
) >> "${LOG_FILE}" 2>&1 &
|
|
280
|
+
pid=$!
|
|
281
|
+
|
|
282
|
+
printf '%s\n' "${pid}" > "${PID_FILE}"
|
|
283
|
+
_write_state "${pid}" "${CTL_HOST}" "${CTL_PORT}" "${python_exe}"
|
|
284
|
+
sleep 0.15
|
|
285
|
+
if ! _is_alive "${pid}"; then
|
|
286
|
+
echo "[ctl] Hermes WebUI failed to stay running. Log: ${LOG_FILE}" >&2
|
|
287
|
+
rm -f "${PID_FILE}" "${STATE_FILE}"
|
|
288
|
+
return 1
|
|
289
|
+
fi
|
|
290
|
+
echo "[ctl] Started Hermes WebUI (PID ${pid})"
|
|
291
|
+
echo "[ctl] Bound: ${CTL_HOST}:${CTL_PORT}"
|
|
292
|
+
echo "[ctl] Log: ${LOG_FILE}"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
stop_cmd() {
|
|
296
|
+
ensure_home
|
|
297
|
+
local pid
|
|
298
|
+
if ! pid="$(_pid_from_file 2>/dev/null)"; then
|
|
299
|
+
echo "[ctl] Hermes WebUI is stopped"
|
|
300
|
+
rm -f "${PID_FILE}" "${STATE_FILE}"
|
|
301
|
+
return 0
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
if ! _is_alive "${pid}" || ! _is_owned_webui_pid "${pid}"; then
|
|
305
|
+
_clear_stale_pid
|
|
306
|
+
return 0
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
echo "[ctl] Stopping Hermes WebUI (PID ${pid})"
|
|
310
|
+
kill "${pid}" >/dev/null 2>&1 || true
|
|
311
|
+
local i
|
|
312
|
+
for i in {1..50}; do
|
|
313
|
+
if ! _is_alive "${pid}"; then
|
|
314
|
+
rm -f "${PID_FILE}" "${STATE_FILE}"
|
|
315
|
+
echo "[ctl] Stopped"
|
|
316
|
+
return 0
|
|
317
|
+
fi
|
|
318
|
+
sleep 0.1
|
|
319
|
+
done
|
|
320
|
+
|
|
321
|
+
echo "[ctl] Process did not exit after SIGTERM; sending SIGKILL" >&2
|
|
322
|
+
kill -KILL "${pid}" >/dev/null 2>&1 || true
|
|
323
|
+
rm -f "${PID_FILE}" "${STATE_FILE}"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
_health_line() {
|
|
327
|
+
local host="$1" port="$2" url result
|
|
328
|
+
url="http://${host}:${port}/health"
|
|
329
|
+
if command -v curl >/dev/null 2>&1; then
|
|
330
|
+
if result="$(curl -fsS --max-time 2 "${url}" 2>/dev/null)"; then
|
|
331
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
332
|
+
printf '%s' "${result}" | python3 -c 'import json,sys
|
|
333
|
+
try:
|
|
334
|
+
data=json.load(sys.stdin)
|
|
335
|
+
sessions=data.get("sessions", data.get("session_count", "?"))
|
|
336
|
+
active=data.get("active_streams", "?")
|
|
337
|
+
status=data.get("status", "ok")
|
|
338
|
+
print(f"ok ({sessions} sessions, {active} active streams)" if status == "ok" else status)
|
|
339
|
+
except Exception:
|
|
340
|
+
print("ok")'
|
|
341
|
+
else
|
|
342
|
+
echo "ok"
|
|
343
|
+
fi
|
|
344
|
+
else
|
|
345
|
+
echo "unreachable (${url})"
|
|
346
|
+
fi
|
|
347
|
+
else
|
|
348
|
+
echo "unknown (curl not found; ${url})"
|
|
349
|
+
fi
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
status_cmd() {
|
|
353
|
+
ensure_home
|
|
354
|
+
_load_state_if_present
|
|
355
|
+
local host="${HOST:-${HERMES_WEBUI_HOST:-127.0.0.1}}"
|
|
356
|
+
local port="${PORT:-${HERMES_WEBUI_PORT:-8787}}"
|
|
357
|
+
local log_path="${LOG_FILE}"
|
|
358
|
+
local pid uptime health
|
|
359
|
+
|
|
360
|
+
if pid="$(_current_pid 2>/dev/null)"; then
|
|
361
|
+
uptime="$(ps -p "${pid}" -o etime= 2>/dev/null | sed 's/^ *//' || true)"
|
|
362
|
+
health="$(_health_line "${host}" "${port}")"
|
|
363
|
+
echo "● hermes-webui — running"
|
|
364
|
+
echo " PID: ${pid}"
|
|
365
|
+
echo " Uptime: ${uptime:-unknown}"
|
|
366
|
+
echo " Bound: ${host}:${port}"
|
|
367
|
+
echo " Log: ${log_path}"
|
|
368
|
+
echo " Health: ${health}"
|
|
369
|
+
else
|
|
370
|
+
[[ -f "${PID_FILE}" ]] && _clear_stale_pid >/dev/null 2>&1 || true
|
|
371
|
+
echo "● hermes-webui — stopped"
|
|
372
|
+
echo " PID: -"
|
|
373
|
+
echo " Bound: ${host}:${port}"
|
|
374
|
+
echo " Log: ${log_path}"
|
|
375
|
+
echo " Health: not checked"
|
|
376
|
+
fi
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
logs_cmd() {
|
|
380
|
+
ensure_home
|
|
381
|
+
local lines=100 follow=1
|
|
382
|
+
while [[ $# -gt 0 ]]; do
|
|
383
|
+
case "$1" in
|
|
384
|
+
--lines)
|
|
385
|
+
shift
|
|
386
|
+
lines="${1:-}"
|
|
387
|
+
[[ "${lines}" =~ ^[0-9]+$ ]] || { echo "[ctl] --lines requires a number" >&2; return 2; }
|
|
388
|
+
;;
|
|
389
|
+
--lines=*)
|
|
390
|
+
lines="${1#--lines=}"
|
|
391
|
+
[[ "${lines}" =~ ^[0-9]+$ ]] || { echo "[ctl] --lines requires a number" >&2; return 2; }
|
|
392
|
+
;;
|
|
393
|
+
--follow|-f)
|
|
394
|
+
follow=1
|
|
395
|
+
;;
|
|
396
|
+
--no-follow)
|
|
397
|
+
follow=0
|
|
398
|
+
;;
|
|
399
|
+
*)
|
|
400
|
+
echo "[ctl] Unknown logs option: $1" >&2
|
|
401
|
+
return 2
|
|
402
|
+
;;
|
|
403
|
+
esac
|
|
404
|
+
shift
|
|
405
|
+
done
|
|
406
|
+
touch "${LOG_FILE}"
|
|
407
|
+
if (( follow )); then
|
|
408
|
+
tail -n "${lines}" -f "${LOG_FILE}"
|
|
409
|
+
else
|
|
410
|
+
tail -n "${lines}" "${LOG_FILE}"
|
|
411
|
+
fi
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
cmd="${1:-}"
|
|
415
|
+
if [[ $# -gt 0 ]]; then
|
|
416
|
+
shift
|
|
417
|
+
fi
|
|
418
|
+
|
|
419
|
+
case "${cmd}" in
|
|
420
|
+
start) start_cmd "$@" ;;
|
|
421
|
+
stop) stop_cmd ;;
|
|
422
|
+
restart) stop_cmd; start_cmd "$@" ;;
|
|
423
|
+
status) status_cmd ;;
|
|
424
|
+
logs) logs_cmd "$@" ;;
|
|
425
|
+
-h|--help|help|"") usage ;;
|
|
426
|
+
*) echo "[ctl] Unknown command: ${cmd}" >&2; usage >&2; exit 2 ;;
|
|
427
|
+
esac
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
hermes-webui-custom:
|
|
5
|
+
image: ghcr.io/yourusername/hermes-webui:custom
|
|
6
|
+
container_name: hermes-webui-custom
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
ports:
|
|
9
|
+
- "127.0.0.1:8787:8787"
|
|
10
|
+
environment:
|
|
11
|
+
- HERMES_WEBUI_HOST=0.0.0.0
|
|
12
|
+
- HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD:-}
|
|
13
|
+
- WANTED_UID=${UID:-1000}
|
|
14
|
+
- WANTED_GID=${GID:-1000}
|
|
15
|
+
volumes:
|
|
16
|
+
- hermes-data:/home/hermeswebui/.hermes
|
|
17
|
+
- ${HOME}/workspace:/workspace
|
|
18
|
+
healthcheck:
|
|
19
|
+
test: ["CMD", "curl", "-f", "http://127.0.0.1:8787/health"]
|
|
20
|
+
interval: 30s
|
|
21
|
+
timeout: 10s
|
|
22
|
+
retries: 3
|
|
23
|
+
|
|
24
|
+
volumes:
|
|
25
|
+
hermes-data:
|
|
26
|
+
driver: local
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Three-container Docker Compose: Hermes Agent + Dashboard + WebUI
|
|
2
|
+
#
|
|
3
|
+
# QUICK START:
|
|
4
|
+
# docker compose -f docker-compose.three-container.yml up -d
|
|
5
|
+
# Open http://localhost:8787 (chat) and http://localhost:9119 (dashboard)
|
|
6
|
+
#
|
|
7
|
+
# This extends the two-container setup with the Hermes Dashboard for
|
|
8
|
+
# monitoring agent activity, sessions, and resource usage.
|
|
9
|
+
#
|
|
10
|
+
# Services:
|
|
11
|
+
# hermes-agent — gateway API on port 8642 (CLI, Telegram, cron, tools)
|
|
12
|
+
# hermes-dashboard — monitoring dashboard on port 9119
|
|
13
|
+
# hermes-webui — browser chat interface on port 8787
|
|
14
|
+
#
|
|
15
|
+
# All three share the same hermes-home volume so config, sessions,
|
|
16
|
+
# skills, and memory are consistent across all surfaces.
|
|
17
|
+
#
|
|
18
|
+
# WHEN NOT TO USE THIS:
|
|
19
|
+
# - You hit "Permission denied" trying to share an existing ~/.hermes directory
|
|
20
|
+
# → use docker-compose.yml (single-container) instead, OR
|
|
21
|
+
# → keep this file but switch to NAMED VOLUMES (the default) instead of bind mounts
|
|
22
|
+
# - You're on Podman 3.4 or older without keep-id namespace support
|
|
23
|
+
# → see https://github.com/sunnysktsang/hermes-suite for an all-in-one image
|
|
24
|
+
#
|
|
25
|
+
# KNOWN LIMITATION (#681): tools triggered from the WebUI run in the WebUI
|
|
26
|
+
# container, not the agent container. See docker-compose.two-container.yml
|
|
27
|
+
# for context.
|
|
28
|
+
#
|
|
29
|
+
# NOTE ON VOLUMES:
|
|
30
|
+
# This file uses named Docker volumes (hermes-home, hermes-agent-src) which
|
|
31
|
+
# work out of the box. If you prefer bind mounts (e.g. to an existing
|
|
32
|
+
# directory), see docker-compose.two-container.yml for a bind-mount example.
|
|
33
|
+
# When using bind mounts, ALL THREE containers must mount the same host path
|
|
34
|
+
# AND run as the same UID/GID (set via the UID/GID env vars below).
|
|
35
|
+
|
|
36
|
+
services:
|
|
37
|
+
hermes-agent:
|
|
38
|
+
image: nousresearch/hermes-agent:latest
|
|
39
|
+
container_name: hermes-agent
|
|
40
|
+
command: gateway run
|
|
41
|
+
ports:
|
|
42
|
+
- "127.0.0.1:8642:8642"
|
|
43
|
+
volumes:
|
|
44
|
+
# Persist config, state, sessions, skills, memory across restarts
|
|
45
|
+
- hermes-home:/home/hermes/.hermes
|
|
46
|
+
# Expose agent source so the WebUI can install dependencies from it
|
|
47
|
+
- hermes-agent-src:/opt/hermes
|
|
48
|
+
environment:
|
|
49
|
+
- HERMES_HOME=/home/hermes/.hermes
|
|
50
|
+
# Align UID/GID across containers sharing the hermes-home volume.
|
|
51
|
+
# Defaults to 1000 to match WANTED_UID/WANTED_GID in the webui service.
|
|
52
|
+
- HERMES_UID=${UID:-1000}
|
|
53
|
+
- HERMES_GID=${GID:-1000}
|
|
54
|
+
# Bind-mount permission handling for the agent — narrow set of overrides.
|
|
55
|
+
# NOTE: The agent's HERMES_HOME_MODE applies to the HERMES_HOME *directory*
|
|
56
|
+
# mode (default 0700) — NOT to credential files like the WebUI's variant.
|
|
57
|
+
# If you set this, you MUST keep the owner-execute bit so the agent can
|
|
58
|
+
# traverse its own home directory. 0640 BREAKS the agent (no x bit → no
|
|
59
|
+
# traversal). Use 0750 for group-traversable or 0701 for x-only.
|
|
60
|
+
# The agent's container detection (/.dockerenv) already auto-skips
|
|
61
|
+
# credential chmod inside Docker, so HERMES_SKIP_CHMOD is redundant here.
|
|
62
|
+
# - HERMES_HOME_MODE=0750
|
|
63
|
+
restart: unless-stopped
|
|
64
|
+
deploy:
|
|
65
|
+
resources:
|
|
66
|
+
limits:
|
|
67
|
+
memory: 4G
|
|
68
|
+
cpus: "2.0"
|
|
69
|
+
networks:
|
|
70
|
+
- hermes-net
|
|
71
|
+
|
|
72
|
+
hermes-dashboard:
|
|
73
|
+
image: nousresearch/hermes-agent:latest
|
|
74
|
+
container_name: hermes-dashboard
|
|
75
|
+
command: dashboard --host 0.0.0.0 --insecure
|
|
76
|
+
ports:
|
|
77
|
+
- "127.0.0.1:9119:9119"
|
|
78
|
+
volumes:
|
|
79
|
+
- hermes-home:/home/hermes/.hermes
|
|
80
|
+
environment:
|
|
81
|
+
- HERMES_HOME=/home/hermes/.hermes
|
|
82
|
+
# Align UID/GID across containers sharing the hermes-home volume.
|
|
83
|
+
# Defaults to 1000 to match WANTED_UID/WANTED_GID in the webui service.
|
|
84
|
+
- HERMES_UID=${UID:-1000}
|
|
85
|
+
- HERMES_GID=${GID:-1000}
|
|
86
|
+
# Dashboard connects to the gateway for health/session data
|
|
87
|
+
- GATEWAY_HEALTH_URL=http://hermes-agent:8642
|
|
88
|
+
depends_on:
|
|
89
|
+
- hermes-agent
|
|
90
|
+
restart: unless-stopped
|
|
91
|
+
deploy:
|
|
92
|
+
resources:
|
|
93
|
+
limits:
|
|
94
|
+
memory: 512M
|
|
95
|
+
cpus: "0.5"
|
|
96
|
+
networks:
|
|
97
|
+
- hermes-net
|
|
98
|
+
|
|
99
|
+
hermes-webui:
|
|
100
|
+
image: ghcr.io/nesquena/hermes-webui:latest
|
|
101
|
+
container_name: hermes-webui
|
|
102
|
+
depends_on:
|
|
103
|
+
- hermes-agent
|
|
104
|
+
ports:
|
|
105
|
+
# Expose on localhost only. Remove 127.0.0.1: to expose on all interfaces
|
|
106
|
+
# (set HERMES_WEBUI_PASSWORD if doing so).
|
|
107
|
+
- "127.0.0.1:8787:8787"
|
|
108
|
+
volumes:
|
|
109
|
+
# Same hermes home as the agent — shares config, sessions, state
|
|
110
|
+
- hermes-home:/home/hermeswebui/.hermes
|
|
111
|
+
# Agent source mounted where docker_init.bash expects it.
|
|
112
|
+
# Mounted read-only — the WebUI only reads this volume to install
|
|
113
|
+
# the agent's Python dependencies at startup (`uv pip install`).
|
|
114
|
+
# Read-only enforces that defence-in-depth at the kernel layer.
|
|
115
|
+
- hermes-agent-src:/home/hermeswebui/.hermes/hermes-agent:ro
|
|
116
|
+
# Workspace directory — browse and edit files from the WebUI.
|
|
117
|
+
# Adapt the host path to your project directory.
|
|
118
|
+
# ${HOME} is used rather than `~` so the default resolves the same way
|
|
119
|
+
# across Linux, macOS, WSL2, and Docker Desktop on Windows.
|
|
120
|
+
- ${HERMES_WORKSPACE:-${HOME}/workspace}:/workspace
|
|
121
|
+
environment:
|
|
122
|
+
- HERMES_WEBUI_HOST=0.0.0.0
|
|
123
|
+
- HERMES_WEBUI_PORT=8787
|
|
124
|
+
- HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui
|
|
125
|
+
# Match your host user's UID/GID for correct file permissions.
|
|
126
|
+
# Run `id -u` and `id -g` to find your values.
|
|
127
|
+
# On macOS, UIDs start at 501 (not 1000) — set these in a .env file:
|
|
128
|
+
# echo "UID=$(id -u)" >> .env && echo "GID=$(id -g)" >> .env
|
|
129
|
+
- WANTED_UID=${UID:-1000}
|
|
130
|
+
- WANTED_GID=${GID:-1000}
|
|
131
|
+
# NOTE: When using bind-mount volumes shared across containers, ALL containers
|
|
132
|
+
# that write to the same host directory must run as the same UID/GID.
|
|
133
|
+
# If hermes-agent initialises the state dir as root (UID 0), hermes-webui
|
|
134
|
+
# will get a PermissionError accessing those paths — including a crash on every
|
|
135
|
+
# HTTP request if the auth signing-key file is unreadable. Either set WANTED_UID
|
|
136
|
+
# to match the agent container's UID, or use a named Docker volume (preferred).
|
|
137
|
+
# Optional: set a password for remote access
|
|
138
|
+
# - HERMES_WEBUI_PASSWORD=your-secret-password
|
|
139
|
+
# Bind-mount permission handling for the WebUI (fixes #1389, #1399).
|
|
140
|
+
# NOTE: WebUI's HERMES_HOME_MODE is a credential-file threshold (allow
|
|
141
|
+
# group bits on .env/.signing_key/etc.), DIFFERENT from the agent's
|
|
142
|
+
# which applies to the HERMES_HOME directory itself. 0640 is correct
|
|
143
|
+
# for the WebUI; do NOT copy this value to the agent service block.
|
|
144
|
+
# - HERMES_SKIP_CHMOD=1
|
|
145
|
+
# - HERMES_HOME_MODE=0640
|
|
146
|
+
restart: unless-stopped
|
|
147
|
+
networks:
|
|
148
|
+
- hermes-net
|
|
149
|
+
|
|
150
|
+
networks:
|
|
151
|
+
hermes-net:
|
|
152
|
+
driver: bridge
|
|
153
|
+
|
|
154
|
+
volumes:
|
|
155
|
+
# IMPORTANT — upgrading the agent image:
|
|
156
|
+
# The `hermes-agent-src` volume is initialised from the agent image's
|
|
157
|
+
# `/opt/hermes` on first `up`, and Docker reuses the volume verbatim on
|
|
158
|
+
# later runs — even after `docker pull` of a newer agent image. After
|
|
159
|
+
# upgrading the agent image, run:
|
|
160
|
+
#
|
|
161
|
+
# docker compose -f docker-compose.three-container.yml down
|
|
162
|
+
# docker volume rm <project>_hermes-agent-src
|
|
163
|
+
# docker compose -f docker-compose.three-container.yml pull
|
|
164
|
+
# docker compose -f docker-compose.three-container.yml up -d
|
|
165
|
+
#
|
|
166
|
+
# The full procedure (and why) is documented in docs/docker.md.
|
|
167
|
+
hermes-home:
|
|
168
|
+
hermes-agent-src:
|