@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.
Files changed (233) hide show
  1. package/README.md +213 -0
  2. package/bin/hermes-webui.mjs +588 -0
  3. package/package.json +25 -0
  4. package/scripts/sync-vendor.mjs +74 -0
  5. package/templates/launchd/com.bitseek.hermes-webui.plist +21 -0
  6. package/templates/systemd/hermes-webui.service +13 -0
  7. package/templates/windows/hermes-webui-task.ps1 +3 -0
  8. package/vendor/agent-frontend-shell/.bitseek-source.json +6 -0
  9. package/vendor/agent-frontend-shell/.dockerignore +7 -0
  10. package/vendor/agent-frontend-shell/.env.docker.example +89 -0
  11. package/vendor/agent-frontend-shell/.env.example +34 -0
  12. package/vendor/agent-frontend-shell/.github/FUNDING.yml +3 -0
  13. package/vendor/agent-frontend-shell/.github/workflows/browser-smoke.yml +42 -0
  14. package/vendor/agent-frontend-shell/.github/workflows/docker-smoke.yml +233 -0
  15. package/vendor/agent-frontend-shell/.github/workflows/native-windows-startup.yml +132 -0
  16. package/vendor/agent-frontend-shell/.github/workflows/release.yml +57 -0
  17. package/vendor/agent-frontend-shell/.github/workflows/tests.yml +88 -0
  18. package/vendor/agent-frontend-shell/.vscode/launch.json +59 -0
  19. package/vendor/agent-frontend-shell/.vscode/settings.json +13 -0
  20. package/vendor/agent-frontend-shell/AGENTS.md +80 -0
  21. package/vendor/agent-frontend-shell/ARCHITECTURE.md +1658 -0
  22. package/vendor/agent-frontend-shell/BUGS.md +52 -0
  23. package/vendor/agent-frontend-shell/CHANGELOG.md +7295 -0
  24. package/vendor/agent-frontend-shell/CONTRIBUTING.md +205 -0
  25. package/vendor/agent-frontend-shell/CONTRIBUTORS.md +107 -0
  26. package/vendor/agent-frontend-shell/DESIGN.md +173 -0
  27. package/vendor/agent-frontend-shell/Dockerfile +91 -0
  28. package/vendor/agent-frontend-shell/LICENSE +21 -0
  29. package/vendor/agent-frontend-shell/README-CUSTOM.md +76 -0
  30. package/vendor/agent-frontend-shell/README.md +705 -0
  31. package/vendor/agent-frontend-shell/ROADMAP.md +351 -0
  32. package/vendor/agent-frontend-shell/SPRINTS.md +147 -0
  33. package/vendor/agent-frontend-shell/TESTING.md +1932 -0
  34. package/vendor/agent-frontend-shell/THEMES.md +170 -0
  35. package/vendor/agent-frontend-shell/api/__init__.py +1 -0
  36. package/vendor/agent-frontend-shell/api/agent_health.py +392 -0
  37. package/vendor/agent-frontend-shell/api/agent_sessions.py +782 -0
  38. package/vendor/agent-frontend-shell/api/auth.py +592 -0
  39. package/vendor/agent-frontend-shell/api/background.py +87 -0
  40. package/vendor/agent-frontend-shell/api/clarify.py +238 -0
  41. package/vendor/agent-frontend-shell/api/commands.py +124 -0
  42. package/vendor/agent-frontend-shell/api/compression_anchor.py +134 -0
  43. package/vendor/agent-frontend-shell/api/config.py +5178 -0
  44. package/vendor/agent-frontend-shell/api/dashboard_probe.py +255 -0
  45. package/vendor/agent-frontend-shell/api/extensions.py +253 -0
  46. package/vendor/agent-frontend-shell/api/gateway_chat.py +435 -0
  47. package/vendor/agent-frontend-shell/api/gateway_watcher.py +230 -0
  48. package/vendor/agent-frontend-shell/api/goals.py +608 -0
  49. package/vendor/agent-frontend-shell/api/helpers.py +474 -0
  50. package/vendor/agent-frontend-shell/api/kanban_bridge.py +1255 -0
  51. package/vendor/agent-frontend-shell/api/metering.py +194 -0
  52. package/vendor/agent-frontend-shell/api/models.py +4210 -0
  53. package/vendor/agent-frontend-shell/api/oauth.py +770 -0
  54. package/vendor/agent-frontend-shell/api/onboarding.py +1046 -0
  55. package/vendor/agent-frontend-shell/api/passkeys.py +365 -0
  56. package/vendor/agent-frontend-shell/api/profiles.py +1499 -0
  57. package/vendor/agent-frontend-shell/api/providers.py +2175 -0
  58. package/vendor/agent-frontend-shell/api/request_diagnostics.py +160 -0
  59. package/vendor/agent-frontend-shell/api/rollback.py +320 -0
  60. package/vendor/agent-frontend-shell/api/routes.py +13990 -0
  61. package/vendor/agent-frontend-shell/api/run_journal.py +284 -0
  62. package/vendor/agent-frontend-shell/api/runner_client.py +156 -0
  63. package/vendor/agent-frontend-shell/api/runtime_adapter.py +431 -0
  64. package/vendor/agent-frontend-shell/api/session_discoverability.py +640 -0
  65. package/vendor/agent-frontend-shell/api/session_events.py +45 -0
  66. package/vendor/agent-frontend-shell/api/session_lifecycle.py +208 -0
  67. package/vendor/agent-frontend-shell/api/session_ops.py +207 -0
  68. package/vendor/agent-frontend-shell/api/session_recovery.py +655 -0
  69. package/vendor/agent-frontend-shell/api/skill_usage.py +32 -0
  70. package/vendor/agent-frontend-shell/api/startup.py +128 -0
  71. package/vendor/agent-frontend-shell/api/state_sync.py +187 -0
  72. package/vendor/agent-frontend-shell/api/streaming.py +7048 -0
  73. package/vendor/agent-frontend-shell/api/system_health.py +167 -0
  74. package/vendor/agent-frontend-shell/api/terminal.py +410 -0
  75. package/vendor/agent-frontend-shell/api/turn_journal.py +214 -0
  76. package/vendor/agent-frontend-shell/api/updates.py +1261 -0
  77. package/vendor/agent-frontend-shell/api/upload.py +322 -0
  78. package/vendor/agent-frontend-shell/api/usage.py +26 -0
  79. package/vendor/agent-frontend-shell/api/workspace.py +867 -0
  80. package/vendor/agent-frontend-shell/api/workspace_git.py +1261 -0
  81. package/vendor/agent-frontend-shell/api/worktrees.py +357 -0
  82. package/vendor/agent-frontend-shell/bootstrap.py +492 -0
  83. package/vendor/agent-frontend-shell/ctl.sh +427 -0
  84. package/vendor/agent-frontend-shell/docker-compose.custom.yml +26 -0
  85. package/vendor/agent-frontend-shell/docker-compose.three-container.yml +168 -0
  86. package/vendor/agent-frontend-shell/docker-compose.two-container.yml +147 -0
  87. package/vendor/agent-frontend-shell/docker-compose.yml +57 -0
  88. package/vendor/agent-frontend-shell/docker_init.bash +459 -0
  89. package/vendor/agent-frontend-shell/docs/CONTRACTS.md +207 -0
  90. package/vendor/agent-frontend-shell/docs/EXTENSIONS.md +212 -0
  91. package/vendor/agent-frontend-shell/docs/ISSUES.md +23 -0
  92. package/vendor/agent-frontend-shell/docs/UIUX-GUIDE.md +196 -0
  93. package/vendor/agent-frontend-shell/docs/advanced-chat-setup.md +83 -0
  94. package/vendor/agent-frontend-shell/docs/docker.md +337 -0
  95. package/vendor/agent-frontend-shell/docs/onboarding-agent-checklist.md +207 -0
  96. package/vendor/agent-frontend-shell/docs/onboarding.md +202 -0
  97. package/vendor/agent-frontend-shell/docs/remote-access.md +75 -0
  98. package/vendor/agent-frontend-shell/docs/rfcs/README.md +53 -0
  99. package/vendor/agent-frontend-shell/docs/rfcs/agent-source-boundary.md +70 -0
  100. package/vendor/agent-frontend-shell/docs/rfcs/canonical-session-resolution.md +124 -0
  101. package/vendor/agent-frontend-shell/docs/rfcs/hermes-run-adapter-contract.md +1079 -0
  102. package/vendor/agent-frontend-shell/docs/rfcs/turn-journal.md +195 -0
  103. package/vendor/agent-frontend-shell/docs/rfcs/webui-run-state-consistency-contract.md +157 -0
  104. package/vendor/agent-frontend-shell/docs/supervisor.md +280 -0
  105. package/vendor/agent-frontend-shell/docs/troubleshooting.md +132 -0
  106. package/vendor/agent-frontend-shell/docs/ui-ux/index.html +863 -0
  107. package/vendor/agent-frontend-shell/docs/ui-ux/two-stage-proposal.html +768 -0
  108. package/vendor/agent-frontend-shell/docs/why-hermes.md +489 -0
  109. package/vendor/agent-frontend-shell/docs/workspace-git.md +92 -0
  110. package/vendor/agent-frontend-shell/docs/wsl-autostart.md +126 -0
  111. package/vendor/agent-frontend-shell/eslint.runtime-guard.config.mjs +35 -0
  112. package/vendor/agent-frontend-shell/extensions/bitseek-design-system.md +330 -0
  113. package/vendor/agent-frontend-shell/extensions/branding/assets/apple-touch-icon.png +0 -0
  114. package/vendor/agent-frontend-shell/extensions/branding/assets/empty-logo.svg +739 -0
  115. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-192.png +0 -0
  116. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-32.png +0 -0
  117. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.png +0 -0
  118. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.svg +745 -0
  119. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.ico +0 -0
  120. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.svg +745 -0
  121. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v2.svg +751 -0
  122. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v3.svg +739 -0
  123. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon.svg +745 -0
  124. package/vendor/agent-frontend-shell/extensions/branding/branding.js +112 -0
  125. package/vendor/agent-frontend-shell/extensions/branding/config.json +14 -0
  126. package/vendor/agent-frontend-shell/extensions/branding/manifest.json +53 -0
  127. package/vendor/agent-frontend-shell/extensions/index.js +67 -0
  128. package/vendor/agent-frontend-shell/extensions/loader/hermes-loader.js +77 -0
  129. package/vendor/agent-frontend-shell/extensions/manifest.json +16 -0
  130. package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.css +333 -0
  131. package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.js +487 -0
  132. package/vendor/agent-frontend-shell/extensions/pages/manifest.json +6 -0
  133. package/vendor/agent-frontend-shell/extensions/pages/registry.css +56 -0
  134. package/vendor/agent-frontend-shell/extensions/pages/registry.js +302 -0
  135. package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.css +93 -0
  136. package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.js +98 -0
  137. package/vendor/agent-frontend-shell/install.sh +63 -0
  138. package/vendor/agent-frontend-shell/mcp_server.py +567 -0
  139. package/vendor/agent-frontend-shell/package.json +12 -0
  140. package/vendor/agent-frontend-shell/pyproject.toml +56 -0
  141. package/vendor/agent-frontend-shell/pytest.ini +3 -0
  142. package/vendor/agent-frontend-shell/requirements.txt +5 -0
  143. package/vendor/agent-frontend-shell/server.py +624 -0
  144. package/vendor/agent-frontend-shell/start.ps1 +210 -0
  145. package/vendor/agent-frontend-shell/start.sh +65 -0
  146. package/vendor/agent-frontend-shell/static/apple-touch-icon.png +0 -0
  147. package/vendor/agent-frontend-shell/static/boot.js +1990 -0
  148. package/vendor/agent-frontend-shell/static/commands.js +1402 -0
  149. package/vendor/agent-frontend-shell/static/favicon-192.png +0 -0
  150. package/vendor/agent-frontend-shell/static/favicon-32.png +0 -0
  151. package/vendor/agent-frontend-shell/static/favicon-512.png +0 -0
  152. package/vendor/agent-frontend-shell/static/favicon-512.svg +18 -0
  153. package/vendor/agent-frontend-shell/static/favicon.ico +0 -0
  154. package/vendor/agent-frontend-shell/static/favicon.svg +20 -0
  155. package/vendor/agent-frontend-shell/static/i18n.js +15389 -0
  156. package/vendor/agent-frontend-shell/static/icons.js +92 -0
  157. package/vendor/agent-frontend-shell/static/index.html +1506 -0
  158. package/vendor/agent-frontend-shell/static/login.js +177 -0
  159. package/vendor/agent-frontend-shell/static/manifest.json +53 -0
  160. package/vendor/agent-frontend-shell/static/messages.js +3521 -0
  161. package/vendor/agent-frontend-shell/static/onboarding.js +800 -0
  162. package/vendor/agent-frontend-shell/static/panels.js +7995 -0
  163. package/vendor/agent-frontend-shell/static/pwa-startup.js +83 -0
  164. package/vendor/agent-frontend-shell/static/sessions.js +5165 -0
  165. package/vendor/agent-frontend-shell/static/style.css +4774 -0
  166. package/vendor/agent-frontend-shell/static/sw.js +173 -0
  167. package/vendor/agent-frontend-shell/static/terminal.js +632 -0
  168. package/vendor/agent-frontend-shell/static/ui.js +8997 -0
  169. package/vendor/agent-frontend-shell/static/vendor/js-yaml/4.1.0/js-yaml.min.js +2 -0
  170. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.ttf +0 -0
  171. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff +0 -0
  172. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  173. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  174. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  175. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  176. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  177. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  178. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  179. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  180. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  181. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  182. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  183. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  184. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  185. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.ttf +0 -0
  186. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff +0 -0
  187. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff2 +0 -0
  188. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  189. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  190. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  191. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.ttf +0 -0
  192. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff +0 -0
  193. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff2 +0 -0
  194. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.ttf +0 -0
  195. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff +0 -0
  196. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff2 +0 -0
  197. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  198. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  199. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  200. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.ttf +0 -0
  201. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff +0 -0
  202. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff2 +0 -0
  203. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  204. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  205. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  206. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  207. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  208. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  209. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  210. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  211. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  212. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.ttf +0 -0
  213. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff +0 -0
  214. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff2 +0 -0
  215. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.ttf +0 -0
  216. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff +0 -0
  217. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  218. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.ttf +0 -0
  219. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff +0 -0
  220. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  221. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.ttf +0 -0
  222. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff +0 -0
  223. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  224. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.ttf +0 -0
  225. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff +0 -0
  226. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  227. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  228. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  229. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  230. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.css +1 -0
  231. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.js +1 -0
  232. package/vendor/agent-frontend-shell/static/vendor/smd.min.js +29 -0
  233. 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>