hermium 0.1.10 → 0.3.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 (216) hide show
  1. package/README.md +56 -0
  2. package/bin/hermium.mjs +201 -136
  3. package/dist/api.mjs +3513 -0
  4. package/dist/{web-server → server}/_chunks/ssr-renderer.mjs +1 -1
  5. package/dist/{web-server → server}/_libs/base-ui__react.mjs +338 -34
  6. package/dist/{web-server → server}/_libs/base-ui__utils.mjs +38 -33
  7. package/dist/server/_libs/comma-separated-tokens.mjs +10 -0
  8. package/dist/{web-server → server}/_libs/floating-ui__core.mjs +3 -3
  9. package/dist/{web-server → server}/_libs/floating-ui__dom.mjs +4 -4
  10. package/dist/{web-server → server}/_libs/floating-ui__react-dom.mjs +3 -3
  11. package/dist/{web-server → server}/_libs/floating-ui__utils.mjs +38 -38
  12. package/dist/{web-server → server}/_libs/h3.mjs +3 -3
  13. package/dist/server/_libs/hast-util-is-element.mjs +75 -0
  14. package/dist/{web-server → server}/_libs/hast-util-to-jsx-runtime.mjs +2 -2
  15. package/dist/server/_libs/hast-util-to-text.mjs +305 -0
  16. package/dist/server/_libs/highlight.js.mjs +14756 -0
  17. package/dist/server/_libs/lowlight.mjs +262 -0
  18. package/dist/{web-server → server}/_libs/mdast-util-from-markdown.mjs +1 -1
  19. package/dist/{web-server → server}/_libs/mdast-util-gfm-autolink-literal+[...].mjs +2 -2
  20. package/dist/{web-server → server}/_libs/mdast-util-to-hast.mjs +1 -1
  21. package/dist/{web-server → server}/_libs/micromark-core-commonmark.mjs +24 -24
  22. package/dist/{web-server → server}/_libs/micromark-extension-gfm-autolink-literal+[...].mjs +1 -1
  23. package/dist/{web-server → server}/_libs/micromark-extension-gfm-footnote+[...].mjs +2 -2
  24. package/dist/{web-server → server}/_libs/micromark-extension-gfm-table.mjs +1 -1
  25. package/dist/{web-server → server}/_libs/micromark-extension-gfm-task-list-item+[...].mjs +1 -1
  26. package/dist/{web-server → server}/_libs/micromark-factory-destination.mjs +1 -1
  27. package/dist/{web-server → server}/_libs/micromark-factory-label.mjs +1 -1
  28. package/dist/{web-server → server}/_libs/micromark-factory-space.mjs +1 -1
  29. package/dist/{web-server → server}/_libs/micromark-factory-whitespace.mjs +1 -1
  30. package/dist/{web-server → server}/_libs/micromark-util-character.mjs +12 -12
  31. package/dist/{web-server → server}/_libs/micromark-util-classify-character+[...].mjs +1 -1
  32. package/dist/{web-server → server}/_libs/micromark-util-html-tag-name.mjs +2 -2
  33. package/dist/{web-server → server}/_libs/micromark-util-sanitize-uri.mjs +1 -1
  34. package/dist/{web-server → server}/_libs/micromark.mjs +3 -3
  35. package/dist/{web-server → server}/_libs/property-information.mjs +2 -3
  36. package/dist/{web-server → server}/_libs/react-dom.mjs +1 -1
  37. package/dist/{web-server → server}/_libs/react.mjs +42 -42
  38. package/dist/server/_libs/rehype-highlight.mjs +94 -0
  39. package/dist/server/_libs/space-separated-tokens.mjs +6 -0
  40. package/dist/server/_libs/tabler__icons-react.mjs +140 -0
  41. package/dist/server/_libs/tailwind-merge.mjs +3255 -0
  42. package/dist/server/_libs/tanstack__history.mjs +29 -0
  43. package/dist/{web-server → server}/_libs/tanstack__react-router.mjs +3 -3
  44. package/dist/{web-server → server}/_libs/tanstack__router-core.mjs +47 -741
  45. package/dist/server/_libs/unist-util-find-after.mjs +41 -0
  46. package/dist/{web-server → server}/_libs/unist-util-position.mjs +2 -2
  47. package/dist/{web-server → server}/_libs/use-sync-external-store.mjs +1 -1
  48. package/dist/server/_libs/zustand.mjs +43 -0
  49. package/dist/server/_ssr/ChatInputBlock-Bu2-iop_.mjs +220 -0
  50. package/dist/server/_ssr/MarkdownMessage-CNS7OSKN.mjs +68 -0
  51. package/dist/server/_ssr/chat._sessionId-P02iSfut.mjs +477 -0
  52. package/dist/{web-server/_ssr/index-CoDfv1vI.mjs → server/_ssr/chat.index-BYB_48NC.mjs} +6 -8
  53. package/dist/server/_ssr/index-C1mT_2d8.mjs +4890 -0
  54. package/dist/server/_ssr/index-DFV9_oCk.mjs +43 -0
  55. package/dist/server/_ssr/memory-CW_fSOG9.mjs +257 -0
  56. package/dist/{web-server/_ssr/router-CS6Zq3md.mjs → server/_ssr/router-CUAfx91O.mjs} +1009 -1108
  57. package/dist/server/_ssr/settings-DoXurzvn.mjs +10 -0
  58. package/dist/server/_ssr/skills-Cs7A5ZwO.mjs +422 -0
  59. package/dist/server/_ssr/theme-BK4-7E2h.mjs +42 -0
  60. package/dist/server/_ssr/usage-Bs2-LXGz.mjs +298 -0
  61. package/dist/server/_tanstack-start-manifest_v-C7Upe2TI.mjs +4 -0
  62. package/dist/server/index.mjs +502 -240
  63. package/package.json +4 -3
  64. package/dist/public/assets/IconAlertCircle-BW147gsG.js +0 -1
  65. package/dist/public/assets/IconAlertTriangle-DCoTLVSd.js +0 -1
  66. package/dist/public/assets/IconCheck-DAO7Fpl9.js +0 -1
  67. package/dist/public/assets/IconCode-BqfTl5wU.js +0 -1
  68. package/dist/public/assets/IconLoader2-B_pehSXN.js +0 -1
  69. package/dist/public/assets/IconRefresh-BbMGoMV8.js +0 -1
  70. package/dist/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  71. package/dist/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  72. package/dist/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  73. package/dist/public/assets/index-BL9a2Xg9.js +0 -1
  74. package/dist/public/assets/index-BSwXjgjr.js +0 -1
  75. package/dist/public/assets/index-BWl8tn18.js +0 -1
  76. package/dist/public/assets/index-CQh8SXb2.js +0 -94
  77. package/dist/public/assets/index-Cxd-kSUY.js +0 -1
  78. package/dist/public/assets/index-DA5SH--7.js +0 -2
  79. package/dist/public/assets/index-DCHbvtBS.js +0 -1
  80. package/dist/public/assets/index-DCYXJZEe.js +0 -1
  81. package/dist/public/assets/index-DFDfp0ca.js +0 -1
  82. package/dist/public/assets/index-GuAAqSCJ.js +0 -14
  83. package/dist/public/assets/index-WIDirTHx.js +0 -29
  84. package/dist/public/assets/index-X3XZcAzy.js +0 -1
  85. package/dist/public/assets/input-7TQEEJq6.js +0 -1
  86. package/dist/public/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  87. package/dist/public/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  88. package/dist/public/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  89. package/dist/public/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  90. package/dist/public/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  91. package/dist/public/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  92. package/dist/public/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  93. package/dist/public/assets/models-BlUb1eaf.js +0 -1
  94. package/dist/public/assets/styles-PHtUFHUr.css +0 -1
  95. package/dist/public/assets/switch-B_iYUUM3.js +0 -1
  96. package/dist/public/assets/syntax-highlighter-DcBYfnEK.js +0 -6
  97. package/dist/public/assets/textarea-Dm4aaRqS.js +0 -1
  98. package/dist/public/favicon.ico +0 -0
  99. package/dist/public/favicon.png +0 -0
  100. package/dist/public/manifest.json +0 -25
  101. package/dist/public/nous-logo.png +0 -0
  102. package/dist/public/robots.txt +0 -3
  103. package/dist/web-server/_libs/babel__runtime.mjs +0 -237
  104. package/dist/web-server/_libs/character-entities-legacy.mjs +0 -111
  105. package/dist/web-server/_libs/character-reference-invalid.mjs +0 -33
  106. package/dist/web-server/_libs/comma-separated-tokens.mjs +0 -31
  107. package/dist/web-server/_libs/cookie-es.mjs +0 -44
  108. package/dist/web-server/_libs/hast-util-parse-selector.mjs +0 -39
  109. package/dist/web-server/_libs/hastscript.mjs +0 -200
  110. package/dist/web-server/_libs/is-alphabetical.mjs +0 -7
  111. package/dist/web-server/_libs/is-alphanumerical.mjs +0 -8
  112. package/dist/web-server/_libs/is-decimal.mjs +0 -7
  113. package/dist/web-server/_libs/is-hexadecimal.mjs +0 -7
  114. package/dist/web-server/_libs/lowlight.mjs +0 -1
  115. package/dist/web-server/_libs/parse-entities.mjs +0 -245
  116. package/dist/web-server/_libs/react-syntax-highlighter.mjs +0 -941
  117. package/dist/web-server/_libs/refractor.mjs +0 -2425
  118. package/dist/web-server/_libs/seroval-plugins.mjs +0 -58
  119. package/dist/web-server/_libs/seroval.mjs +0 -1775
  120. package/dist/web-server/_libs/space-separated-tokens.mjs +0 -11
  121. package/dist/web-server/_libs/tabler__icons-react.mjs +0 -233
  122. package/dist/web-server/_libs/tanstack__history.mjs +0 -204
  123. package/dist/web-server/_libs/tanstack__query-core.mjs +0 -2552
  124. package/dist/web-server/_libs/tanstack__react-query.mjs +0 -190
  125. package/dist/web-server/_libs/zod.mjs +0 -3915
  126. package/dist/web-server/_libs/zustand.mjs +0 -343
  127. package/dist/web-server/_ssr/index--Bo2_ipW.mjs +0 -277
  128. package/dist/web-server/_ssr/index-BEvygh5x.mjs +0 -612
  129. package/dist/web-server/_ssr/index-BFaKxYfN.mjs +0 -40
  130. package/dist/web-server/_ssr/index-BdrMzRTd.mjs +0 -513
  131. package/dist/web-server/_ssr/index-Cwp8Gyl1.mjs +0 -1812
  132. package/dist/web-server/_ssr/index-DQsfKYjV.mjs +0 -449
  133. package/dist/web-server/_ssr/index-Dbs9k8Ea.mjs +0 -251
  134. package/dist/web-server/_ssr/index-Dk93oyZD.mjs +0 -213
  135. package/dist/web-server/_ssr/index-KNJ7huw_.mjs +0 -369
  136. package/dist/web-server/_ssr/index.mjs +0 -1558
  137. package/dist/web-server/_ssr/input-BV1DMASc.mjs +0 -20
  138. package/dist/web-server/_ssr/models-MzrvbL2i.mjs +0 -43
  139. package/dist/web-server/_ssr/start-HYkvq4Ni.mjs +0 -4
  140. package/dist/web-server/_ssr/switch-Bd-Sg0HG.mjs +0 -33
  141. package/dist/web-server/_ssr/syntax-highlighter-5vezNTce.mjs +0 -62
  142. package/dist/web-server/_ssr/textarea-CB4kQp9w.mjs +0 -18
  143. package/dist/web-server/_tanstack-start-manifest_v-m5lY48LR.mjs +0 -4
  144. package/dist/web-server/index.mjs +0 -611
  145. /package/dist/{web-server → server}/__23tanstack-start-plugin-adapters-Cwee5PKy.mjs +0 -0
  146. /package/dist/{web-server → server}/_libs/bail.mjs +0 -0
  147. /package/dist/{web-server → server}/_libs/ccount.mjs +0 -0
  148. /package/dist/{web-server → server}/_libs/character-entities.mjs +0 -0
  149. /package/dist/{web-server → server}/_libs/class-variance-authority.mjs +0 -0
  150. /package/dist/{web-server → server}/_libs/clsx.mjs +0 -0
  151. /package/dist/{web-server/_libs/croner.mjs → server/_libs/cookie-es.mjs} +0 -0
  152. /package/dist/{web-server/_libs/crossws.mjs → server/_libs/croner.mjs} +0 -0
  153. /package/dist/{web-server/_libs/fault.mjs → server/_libs/crossws.mjs} +0 -0
  154. /package/dist/{web-server → server}/_libs/decode-named-character-reference+[...].mjs +0 -0
  155. /package/dist/{web-server → server}/_libs/devlop.mjs +0 -0
  156. /package/dist/{web-server → server}/_libs/escape-string-regexp.mjs +0 -0
  157. /package/dist/{web-server → server}/_libs/estree-util-is-identifier-name.mjs +0 -0
  158. /package/dist/{web-server → server}/_libs/extend.mjs +0 -0
  159. /package/dist/{web-server → server}/_libs/hast-util-whitespace.mjs +0 -0
  160. /package/dist/{web-server → server}/_libs/hookable.mjs +0 -0
  161. /package/dist/{web-server → server}/_libs/html-url-attributes.mjs +0 -0
  162. /package/dist/{web-server → server}/_libs/inline-style-parser.mjs +0 -0
  163. /package/dist/{web-server → server}/_libs/is-plain-obj.mjs +0 -0
  164. /package/dist/{web-server → server}/_libs/isbot.mjs +0 -0
  165. /package/dist/{web-server → server}/_libs/longest-streak.mjs +0 -0
  166. /package/dist/{web-server → server}/_libs/markdown-table.mjs +0 -0
  167. /package/dist/{web-server → server}/_libs/mdast-util-find-and-replace.mjs +0 -0
  168. /package/dist/{web-server → server}/_libs/mdast-util-gfm-footnote.mjs +0 -0
  169. /package/dist/{web-server → server}/_libs/mdast-util-gfm-strikethrough.mjs +0 -0
  170. /package/dist/{web-server → server}/_libs/mdast-util-gfm-table.mjs +0 -0
  171. /package/dist/{web-server → server}/_libs/mdast-util-gfm-task-list-item.mjs +0 -0
  172. /package/dist/{web-server → server}/_libs/mdast-util-gfm.mjs +0 -0
  173. /package/dist/{web-server → server}/_libs/mdast-util-phrasing.mjs +0 -0
  174. /package/dist/{web-server → server}/_libs/mdast-util-to-markdown.mjs +0 -0
  175. /package/dist/{web-server → server}/_libs/mdast-util-to-string.mjs +0 -0
  176. /package/dist/{web-server → server}/_libs/micromark-extension-gfm-strikethrough+[...].mjs +0 -0
  177. /package/dist/{web-server → server}/_libs/micromark-extension-gfm-tagfilter+[...].mjs +0 -0
  178. /package/dist/{web-server → server}/_libs/micromark-extension-gfm.mjs +0 -0
  179. /package/dist/{web-server → server}/_libs/micromark-factory-title.mjs +0 -0
  180. /package/dist/{web-server → server}/_libs/micromark-util-chunked.mjs +0 -0
  181. /package/dist/{web-server → server}/_libs/micromark-util-combine-extensions+[...].mjs +0 -0
  182. /package/dist/{web-server → server}/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -0
  183. /package/dist/{web-server → server}/_libs/micromark-util-decode-string.mjs +0 -0
  184. /package/dist/{web-server → server}/_libs/micromark-util-encode.mjs +0 -0
  185. /package/dist/{web-server → server}/_libs/micromark-util-normalize-identifier+[...].mjs +0 -0
  186. /package/dist/{web-server → server}/_libs/micromark-util-resolve-all.mjs +0 -0
  187. /package/dist/{web-server → server}/_libs/micromark-util-subtokenize.mjs +0 -0
  188. /package/dist/{web-server → server}/_libs/ocache.mjs +0 -0
  189. /package/dist/{web-server → server}/_libs/ohash.mjs +0 -0
  190. /package/dist/{web-server → server}/_libs/react-markdown.mjs +0 -0
  191. /package/dist/{web-server → server}/_libs/remark-gfm.mjs +0 -0
  192. /package/dist/{web-server → server}/_libs/remark-parse.mjs +0 -0
  193. /package/dist/{web-server → server}/_libs/remark-rehype.mjs +0 -0
  194. /package/dist/{web-server → server}/_libs/reselect.mjs +0 -0
  195. /package/dist/{web-server → server}/_libs/rou3.mjs +0 -0
  196. /package/dist/{web-server/_libs/format.mjs → server/_libs/seroval-plugins.mjs} +0 -0
  197. /package/dist/{web-server/_libs/highlight.js.mjs → server/_libs/seroval.mjs} +0 -0
  198. /package/dist/{web-server → server}/_libs/srvx.mjs +0 -0
  199. /package/dist/{web-server → server}/_libs/style-to-js.mjs +0 -0
  200. /package/dist/{web-server → server}/_libs/style-to-object.mjs +0 -0
  201. /package/dist/{web-server → server}/_libs/tanstack__react-store.mjs +0 -0
  202. /package/dist/{web-server → server}/_libs/tanstack__store.mjs +0 -0
  203. /package/dist/{web-server → server}/_libs/trim-lines.mjs +0 -0
  204. /package/dist/{web-server → server}/_libs/trough.mjs +0 -0
  205. /package/dist/{web-server → server}/_libs/ufo.mjs +0 -0
  206. /package/dist/{web-server → server}/_libs/unctx.mjs +0 -0
  207. /package/dist/{web-server → server}/_libs/ungap__structured-clone.mjs +0 -0
  208. /package/dist/{web-server → server}/_libs/unified.mjs +0 -0
  209. /package/dist/{web-server → server}/_libs/unist-util-is.mjs +0 -0
  210. /package/dist/{web-server → server}/_libs/unist-util-stringify-position.mjs +0 -0
  211. /package/dist/{web-server → server}/_libs/unist-util-visit-parents.mjs +0 -0
  212. /package/dist/{web-server → server}/_libs/unist-util-visit.mjs +0 -0
  213. /package/dist/{web-server → server}/_libs/unstorage.mjs +0 -0
  214. /package/dist/{web-server → server}/_libs/vfile-message.mjs +0 -0
  215. /package/dist/{web-server → server}/_libs/vfile.mjs +0 -0
  216. /package/dist/{web-server → server}/_libs/zwitch.mjs +0 -0
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Hermium CLI
2
+
3
+ Self-hosted AI chat dashboard for Hermes Agent.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g hermium
9
+ ```
10
+
11
+ Or with Bun:
12
+
13
+ ```bash
14
+ bun install -g hermium
15
+ ```
16
+
17
+ ## Requirements
18
+
19
+ - [Bun](https://bun.sh) >= 1.2
20
+
21
+ ## Commands
22
+
23
+ ```bash
24
+ hermium start # Start the server (daemon)
25
+ hermium stop # Stop the server
26
+ hermium restart # Restart the server
27
+ hermium status # Show running status
28
+ hermium build # Build from source (requires repo)
29
+ hermium dev # Run in dev mode (requires repo)
30
+ hermium help # Show help
31
+ hermium version # Show version
32
+ ```
33
+
34
+ ## Options
35
+
36
+ ```bash
37
+ hermium start --port 47474 # Custom port (default: 47474)
38
+ ```
39
+
40
+ ## Development
41
+
42
+ To run from source:
43
+
44
+ ```bash
45
+ git clone https://github.com/abboskhonov/hermium.git
46
+ cd hermium
47
+ bun install
48
+ bun run dev
49
+ ```
50
+
51
+ To build the CLI package for publishing:
52
+
53
+ ```bash
54
+ cd packages/cli
55
+ bun run build
56
+ ```
package/bin/hermium.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { spawn, execSync } from 'child_process'
3
3
  import { resolve, dirname, join } from 'path'
4
4
  import { fileURLToPath } from 'url'
5
- import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync, existsSync, statSync } from 'fs'
5
+ import { readFileSync, writeFileSync, unlinkSync, mkdirSync, existsSync, statSync, openSync } from 'fs'
6
6
  import { homedir } from 'os'
7
7
  import pc from 'picocolors'
8
8
 
@@ -10,15 +10,9 @@ const pkgDir = resolve(dirname(fileURLToPath(import.meta.url)), '..')
10
10
  const pkg = JSON.parse(readFileSync(resolve(pkgDir, 'package.json'), 'utf-8'))
11
11
  const CURRENT_VERSION = pkg.version
12
12
 
13
- const __dirname = dirname(fileURLToPath(import.meta.url))
14
- const apiEntry = resolve(pkgDir, 'dist', 'server', 'index.mjs')
15
- const webEntry = resolve(pkgDir, 'dist', 'web-server', 'index.mjs')
16
- const webServerDir = resolve(pkgDir, 'dist', 'web-server')
17
- const WEB_UI_HOME = resolve(homedir(), '.hermium')
18
- const API_PID_FILE = join(WEB_UI_HOME, 'api.pid')
19
- const WEB_PID_FILE = join(WEB_UI_HOME, 'web.pid')
20
- const API_LOG_FILE = join(WEB_UI_HOME, 'api.log')
21
- const WEB_LOG_FILE = join(WEB_UI_HOME, 'web.log')
13
+ const HERMIUM_HOME = resolve(homedir(), '.hermium')
14
+ const PID_FILE = join(HERMIUM_HOME, 'hermium.pid')
15
+ const LOG_FILE = join(HERMIUM_HOME, 'hermium.log')
22
16
  const DEFAULT_API_PORT = 47474
23
17
  const DEFAULT_WEB_PORT = 42424
24
18
 
@@ -44,7 +38,7 @@ function getRuntimeCmd() {
44
38
  // ─── Version check ─────────────────────────────────────────────────────────
45
39
 
46
40
  async function checkLatestVersion() {
47
- const cacheFile = join(WEB_UI_HOME, '.version-check')
41
+ const cacheFile = join(HERMIUM_HOME, '.version-check')
48
42
  const now = Date.now()
49
43
  try {
50
44
  const cache = JSON.parse(readFileSync(cacheFile, 'utf-8'))
@@ -74,16 +68,18 @@ async function checkLatestVersion() {
74
68
 
75
69
  // ─── Helpers ───────────────────────────────────────────────────────────────
76
70
 
77
- function readPid(file) {
71
+ function readPid() {
78
72
  try {
79
- const pid = parseInt(readFileSync(file, 'utf-8').trim())
80
- return Number.isFinite(pid) ? pid : null
73
+ const raw = readFileSync(PID_FILE, 'utf-8').trim()
74
+ const [apiPid, webPid] = raw.split(',').map(s => parseInt(s.trim()))
75
+ return { apiPid: Number.isFinite(apiPid) ? apiPid : null, webPid: Number.isFinite(webPid) ? webPid : null }
81
76
  } catch {
82
- return null
77
+ return { apiPid: null, webPid: null }
83
78
  }
84
79
  }
85
80
 
86
81
  function isRunning(pid) {
82
+ if (!pid) return false
87
83
  try {
88
84
  process.kill(pid, 0)
89
85
  return true
@@ -92,130 +88,136 @@ function isRunning(pid) {
92
88
  }
93
89
  }
94
90
 
95
- function writePid(file, pid) {
96
- mkdirSync(WEB_UI_HOME, { recursive: true })
97
- writeFileSync(file, String(pid))
91
+ function getPids() {
92
+ const { apiPid, webPid } = readPid()
93
+ const apiRunning = apiPid && isRunning(apiPid)
94
+ const webRunning = webPid && isRunning(webPid)
95
+ if (!apiRunning && !webRunning) {
96
+ removePid()
97
+ return { apiPid: null, webPid: null }
98
+ }
99
+ return { apiPid: apiRunning ? apiPid : null, webPid: webRunning ? webPid : null }
98
100
  }
99
101
 
100
- function removePid(file) {
101
- try { unlinkSync(file) } catch {}
102
+ function writePid(apiPid, webPid) {
103
+ mkdirSync(HERMIUM_HOME, { recursive: true })
104
+ writeFileSync(PID_FILE, `${apiPid},${webPid}`)
102
105
  }
103
106
 
104
- function getPid(file) {
105
- const pid = readPid(file)
106
- if (pid && isRunning(pid)) return pid
107
- removePid(file)
108
- return null
107
+ function removePid() {
108
+ try { unlinkSync(PID_FILE) } catch {}
109
109
  }
110
110
 
111
- function getApiPid() { return getPid(API_PID_FILE) }
112
- function getWebPid() { return getPid(WEB_PID_FILE) }
111
+ function getApiPort() {
112
+ const idx = process.argv.indexOf('--port')
113
+ if (idx !== -1 && process.argv[idx + 1]) {
114
+ const p = parseInt(process.argv[idx + 1])
115
+ if (!isNaN(p)) return p
116
+ }
117
+ return DEFAULT_API_PORT
118
+ }
113
119
 
114
- function getPort(argName, defaultPort) {
115
- const idx = process.argv.indexOf(argName)
120
+ function getWebPort() {
121
+ const idx = process.argv.indexOf('--web-port')
116
122
  if (idx !== -1 && process.argv[idx + 1]) {
117
123
  const p = parseInt(process.argv[idx + 1])
118
124
  if (!isNaN(p)) return p
119
125
  }
120
- return defaultPort
126
+ return DEFAULT_WEB_PORT
121
127
  }
122
128
 
123
- function getRunningPort(pid) {
124
- if (!pid) return null
129
+ function rotateLog() {
125
130
  try {
126
- if (process.platform === 'win32') {
127
- const out = execSync(`netstat -aon -p tcp | findstr LISTENING | findstr " ${pid}$"`, { encoding: 'utf-8' }).trim()
128
- const line = out.split('\n').find(Boolean)
129
- const address = line?.trim().split(/\s+/)[1]
130
- const port = address?.split(':').pop()
131
- return port ? parseInt(port, 10) : null
132
- }
133
- const out = execSync(`lsof -Pan -p ${pid} -iTCP -sTCP:LISTEN`, { encoding: 'utf-8' }).trim()
134
- const lines = out.split('\n').slice(1)
135
- for (const line of lines) {
136
- const match = line.match(/:(\d+)\s+\(LISTEN\)$/)
137
- if (match) return parseInt(match[1], 10)
131
+ const st = statSync(LOG_FILE)
132
+ if (st.size > 3 * 1024 * 1024) {
133
+ const content = readFileSync(LOG_FILE, 'utf-8')
134
+ const kept = content.split('\n').slice(-2000)
135
+ writeFileSync(LOG_FILE, kept.join('\n'))
136
+ console.log(pc.cyan(` ↻ Rotated ${LOG_FILE.replace(homedir(), '~')}`))
138
137
  }
139
138
  } catch {}
140
- return null
141
139
  }
142
140
 
143
- function rotateLog(file) {
141
+ function stopPid(pid) {
142
+ if (!pid) return false
144
143
  try {
145
- const st = statSync(file)
146
- if (st.size > 3 * 1024 * 1024) {
147
- const content = readFileSync(file, 'utf-8')
148
- const kept = content.split('\n').slice(-2000)
149
- writeFileSync(file, kept.join('\n'))
150
- console.log(pc.cyan(` ↻ Rotated ${file.replace(homedir(), '~')}`))
144
+ process.kill(pid, 'SIGTERM')
145
+ const start = Date.now()
146
+ while (isRunning(pid) && Date.now() - start < 5000) {
147
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100)
151
148
  }
152
- } catch {}
149
+ if (isRunning(pid)) process.kill(pid, 'SIGKILL')
150
+ return true
151
+ } catch {
152
+ return true
153
+ }
153
154
  }
154
155
 
155
- function spawnServer({ name, entry, logFile, pidFile, port, env, runtime, cwd }) {
156
+ function spawnServer({ name, entry, logFile, env, runtime }) {
156
157
  if (!existsSync(entry)) {
157
158
  console.log(pc.red(` ✗ ${name} not found: ${entry}`))
158
159
  console.log(` Run "hermium build" first (or check your installation)`)
159
160
  process.exit(1)
160
161
  }
161
162
 
162
- rotateLog(logFile)
163
+ rotateLog()
163
164
 
164
165
  const logFd = openSync(logFile, 'a')
165
166
  const child = spawn(runtime, [entry], {
166
167
  detached: true,
167
168
  stdio: ['ignore', logFd, logFd],
168
169
  env: { ...process.env, ...env, NODE_ENV: 'production' },
169
- cwd: cwd || dirname(entry),
170
+ cwd: dirname(entry),
170
171
  })
171
172
 
172
173
  child.on('error', (err) => {
173
174
  console.error(pc.red(` ✗ Failed to start ${name}: ${err.message}`))
174
- removePid(pidFile)
175
+ removePid()
175
176
  process.exit(1)
176
177
  })
177
178
 
178
179
  child.unref()
179
- writePid(pidFile, child.pid)
180
180
  return child.pid
181
181
  }
182
182
 
183
- function stopPid(pid, name) {
184
- if (!pid) return false
185
- try {
186
- process.kill(pid, 'SIGTERM')
187
- const start = Date.now()
188
- while (isRunning(pid) && Date.now() - start < 5000) {
189
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100)
190
- }
191
- if (isRunning(pid)) process.kill(pid, 'SIGKILL')
192
- return true
193
- } catch {
194
- return true
195
- }
183
+ // ─── Path resolution ───────────────────────────────────────────────────────
184
+
185
+ function findApiEntry() {
186
+ const bundled = resolve(pkgDir, 'dist', 'api.mjs')
187
+ if (existsSync(bundled)) return bundled
188
+
189
+ const monorepo = resolve(pkgDir, '..', '..', 'packages', 'api', 'dist', 'index.js')
190
+ if (existsSync(monorepo)) return monorepo
191
+
192
+ return null
193
+ }
194
+
195
+ function findWebServer() {
196
+ const bundled = resolve(pkgDir, 'dist', 'server', 'index.mjs')
197
+ if (existsSync(bundled)) return bundled
198
+
199
+ const monorepo = resolve(pkgDir, '..', '..', 'packages', 'web', '.output', 'server', 'index.mjs')
200
+ if (existsSync(monorepo)) return monorepo
201
+
202
+ return null
196
203
  }
197
204
 
198
205
  // ─── Commands ──────────────────────────────────────────────────────────────
199
206
 
200
207
  function cmdStatus() {
201
- const apiPid = getApiPid()
202
- const webPid = getWebPid()
203
- const apiPort = apiPid ? getRunningPort(apiPid) : null
204
- const webPort = webPid ? getRunningPort(webPid) : null
205
-
208
+ const { apiPid, webPid } = getPids()
206
209
  if (apiPid || webPid) {
207
210
  console.log(pc.green(` ✓ Hermium is running`))
208
- if (apiPid) console.log(` API : PID ${apiPid}, port ${apiPort || 'unknown'}`)
209
- if (webPid) console.log(` Web : PID ${webPid}, port ${webPort || 'unknown'}`)
210
- console.log(` Logs : ${WEB_UI_HOME}`)
211
+ if (apiPid) console.log(` API : PID ${apiPid}, port ${getApiPort()}`)
212
+ if (webPid) console.log(` Web : PID ${webPid}, port ${getWebPort()}`)
213
+ console.log(` Logs : ${LOG_FILE.replace(homedir(), '~')}`)
211
214
  } else {
212
215
  console.log(pc.yellow(` ⊘ Hermium is not running`))
213
216
  }
214
217
  }
215
218
 
216
219
  async function cmdStart() {
217
- const existingApi = getApiPid()
218
- const existingWeb = getWebPid()
220
+ const { apiPid: existingApi, webPid: existingWeb } = getPids()
219
221
  if (existingApi || existingWeb) {
220
222
  console.log(pc.yellow(` ✗ Hermium is already running`))
221
223
  if (existingApi) console.log(` API PID: ${existingApi}`)
@@ -224,80 +226,91 @@ async function cmdStart() {
224
226
  process.exit(1)
225
227
  }
226
228
 
227
- const apiPort = getPort('--port', DEFAULT_API_PORT)
228
- const webPort = getPort('--web-port', DEFAULT_WEB_PORT)
229
+ const apiPort = getApiPort()
230
+ const webPort = getWebPort()
229
231
  const runtime = getRuntimeCmd()
232
+ const apiEntry = findApiEntry()
233
+ const webServer = findWebServer()
230
234
 
231
- mkdirSync(WEB_UI_HOME, { recursive: true })
235
+ if (!apiEntry) {
236
+ console.log(pc.red(` ✗ API server not found. Run "hermium build" first.`))
237
+ process.exit(1)
238
+ }
232
239
 
233
- console.log(pc.cyan(` ⏳ Starting Hermium (API:${apiPort}, Web:${webPort})...`))
240
+ if (!webServer) {
241
+ console.log(pc.red(` ✗ Web server not found. Run "hermium build" first.`))
242
+ process.exit(1)
243
+ }
244
+
245
+ mkdirSync(HERMIUM_HOME, { recursive: true })
246
+
247
+ console.log(pc.cyan(` ⏳ Starting Hermium...`))
234
248
 
235
- // 1. Start API server
236
249
  const apiPid = spawnServer({
237
250
  name: 'API server',
238
251
  entry: apiEntry,
239
- logFile: API_LOG_FILE,
240
- pidFile: API_PID_FILE,
241
- port: apiPort,
252
+ logFile: LOG_FILE,
242
253
  runtime,
243
254
  env: {
244
- HERMIUM_PORT: String(apiPort),
245
- HERMIUM_WEB_PORT: String(webPort),
246
- HERMIUM_VERSION: CURRENT_VERSION,
247
- WEB_STATIC_DIR: resolve(pkgDir, 'dist', 'public'),
255
+ PORT: String(apiPort),
256
+ APP_VERSION: CURRENT_VERSION,
257
+ AUTH_DISABLED: '1',
248
258
  },
249
259
  })
250
260
 
251
- // 2. Start web SSR server
252
261
  const webPid = spawnServer({
253
262
  name: 'Web server',
254
- entry: webEntry,
255
- logFile: WEB_LOG_FILE,
256
- pidFile: WEB_PID_FILE,
257
- port: webPort,
263
+ entry: webServer,
264
+ logFile: LOG_FILE,
258
265
  runtime,
259
- cwd: webServerDir,
260
- env: { PORT: String(webPort) },
266
+ env: {
267
+ PORT: String(webPort),
268
+ NITRO_PORT: String(webPort),
269
+ HERMIUM_API_URL: `http://127.0.0.1:${apiPort}`,
270
+ APP_VERSION: CURRENT_VERSION,
271
+ },
261
272
  })
262
273
 
263
- console.log(` API PID: ${apiPid}`)
264
- console.log(` Web PID: ${webPid}`)
274
+ writePid(apiPid, webPid)
275
+
276
+ console.log(` API PID: ${apiPid} (port ${apiPort})`)
277
+ console.log(` Web PID: ${webPid} (port ${webPort})`)
265
278
 
266
- // Poll web server health
279
+ // Poll health
267
280
  const maxWait = 30000
268
281
  const interval = 500
269
282
  let waited = 0
270
- const webUrl = `http://localhost:${webPort}`
283
+ const apiUrl = `http://127.0.0.1:${apiPort}`
284
+ const webUrl = `http://127.0.0.1:${webPort}`
271
285
 
272
286
  function poll() {
273
287
  waited += interval
274
288
 
275
- if (!isRunning(apiPid)) {
276
- console.log(pc.red(` ✗ API server crashed`))
277
- console.log(` Check log: ${API_LOG_FILE}`)
278
- stopPid(webPid, 'web')
279
- removePid(API_PID_FILE)
280
- removePid(WEB_PID_FILE)
281
- process.exit(1)
282
- }
283
- if (!isRunning(webPid)) {
284
- console.log(pc.red(` ✗ Web server crashed`))
285
- console.log(` Check log: ${WEB_LOG_FILE}`)
286
- stopPid(apiPid, 'api')
287
- removePid(API_PID_FILE)
288
- removePid(WEB_PID_FILE)
289
+ if (!isRunning(apiPid) || !isRunning(webPid)) {
290
+ console.log(pc.red(` ✗ Server crashed`))
291
+ console.log(` Check log: ${LOG_FILE}`)
292
+ removePid()
289
293
  process.exit(1)
290
294
  }
291
295
 
292
- fetch(`${webUrl}/api/health`).catch(() => null).then((res) => {
293
- // Web server doesn't have /api/health, this will 404 but we just want to check it's listening
294
- if (waited < maxWait) {
295
- setTimeout(poll, interval)
296
+ fetch(`${apiUrl}/api/hermes/sessions`).catch(() => null).then((res) => {
297
+ if (!res || !res.ok) {
298
+ if (waited < maxWait) {
299
+ setTimeout(poll, interval)
300
+ } else {
301
+ console.log(pc.green(` ✓ Hermium started`))
302
+ console.log(` API : ${apiUrl}`)
303
+ console.log(` Web : ${webUrl}`)
304
+ console.log(` Logs: ${LOG_FILE.replace(homedir(), '~')}`)
305
+ checkLatestVersion()
306
+ }
296
307
  } else {
297
308
  console.log(pc.green(` ✓ Hermium started`))
298
- console.log(` ${webUrl}`)
299
- console.log(` Logs: ${WEB_UI_HOME}`)
300
- checkLatestVersion() // non-blocking
309
+ console.log(` API : ${apiUrl}`)
310
+ console.log(` Web : ${webUrl}`)
311
+ console.log(` Logs: ${LOG_FILE.replace(homedir(), '~')}`)
312
+ checkLatestVersion()
313
+ // Open browser
301
314
  const openCmd =
302
315
  process.platform === 'win32' ? `start ${webUrl}` :
303
316
  process.platform === 'darwin' ? `open ${webUrl}` :
@@ -311,24 +324,21 @@ async function cmdStart() {
311
324
  }
312
325
 
313
326
  function cmdStop() {
314
- const apiPid = getApiPid()
315
- const webPid = getWebPid()
316
-
327
+ const { apiPid, webPid } = getPids()
317
328
  if (!apiPid && !webPid) {
318
329
  console.log(pc.yellow(` ⊘ Hermium is not running`))
319
330
  return
320
331
  }
321
332
 
322
333
  if (webPid) {
323
- stopPid(webPid, 'web')
324
- removePid(WEB_PID_FILE)
334
+ stopPid(webPid)
325
335
  console.log(pc.green(` ✓ Web server stopped (PID: ${webPid})`))
326
336
  }
327
337
  if (apiPid) {
328
- stopPid(apiPid, 'api')
329
- removePid(API_PID_FILE)
338
+ stopPid(apiPid)
330
339
  console.log(pc.green(` ✓ API server stopped (PID: ${apiPid})`))
331
340
  }
341
+ removePid()
332
342
  }
333
343
 
334
344
  function cmdRestart() {
@@ -337,6 +347,53 @@ function cmdRestart() {
337
347
  cmdStart()
338
348
  }
339
349
 
350
+ function cmdBuild() {
351
+ console.log(pc.cyan(` 🔨 Building Hermium...`))
352
+ const runtime = getRuntimeCmd()
353
+
354
+ const rootDir = resolve(pkgDir, '..', '..')
355
+ const isMonorepo = existsSync(resolve(rootDir, 'packages', 'api')) && existsSync(resolve(rootDir, 'packages', 'web'))
356
+
357
+ if (!isMonorepo) {
358
+ console.log(pc.yellow(` ⚠ Not in a monorepo. Build must be done from the Hermium source repository.`))
359
+ process.exit(1)
360
+ }
361
+
362
+ try {
363
+ console.log(` → Building shared...`)
364
+ execSync(`${runtime} run build:shared`, { cwd: rootDir, stdio: 'inherit' })
365
+ console.log(` → Building API...`)
366
+ execSync(`${runtime} run build:api`, { cwd: rootDir, stdio: 'inherit' })
367
+ console.log(` → Building Web...`)
368
+ execSync(`${runtime} run build:web`, { cwd: rootDir, stdio: 'inherit' })
369
+ console.log(` → Building CLI...`)
370
+ execSync(`${runtime} run build:cli`, { cwd: rootDir, stdio: 'inherit' })
371
+ console.log(pc.green(` ✓ Build complete`))
372
+ } catch (err) {
373
+ console.log(pc.red(` ✗ Build failed`))
374
+ process.exit(1)
375
+ }
376
+ }
377
+
378
+ function cmdDev() {
379
+ console.log(pc.cyan(` 🔥 Starting Hermium in dev mode...`))
380
+ const runtime = getRuntimeCmd()
381
+
382
+ const rootDir = resolve(pkgDir, '..', '..')
383
+ const isMonorepo = existsSync(resolve(rootDir, 'packages', 'api')) && existsSync(resolve(rootDir, 'packages', 'web'))
384
+
385
+ if (!isMonorepo) {
386
+ console.log(pc.yellow(` ⚠ Not in a monorepo. Dev mode must be run from the Hermium source repository.`))
387
+ process.exit(1)
388
+ }
389
+
390
+ try {
391
+ execSync(`${runtime} run dev`, { cwd: rootDir, stdio: 'inherit' })
392
+ } catch {
393
+ process.exit(1)
394
+ }
395
+ }
396
+
340
397
  function cmdHelp() {
341
398
  console.log(`
342
399
  ${pc.bold('Hermium CLI')} — Self-hosted AI chat for Hermes Agent
@@ -349,11 +406,14 @@ ${pc.bold('Commands:')}
349
406
  stop Stop Hermium servers
350
407
  restart Restart Hermium servers
351
408
  status Show running status
409
+ build Build from source (requires repo)
410
+ dev Run in dev mode (requires repo)
352
411
  help Show this help message
412
+ version Show version
353
413
 
354
414
  ${pc.bold('Options:')}
355
- --port <n> API port (default: 47474)
356
- --web-port <n> Web port (default: 42424)
415
+ --port <n> API server port (default: 47474)
416
+ --web-port <n> Web server port (default: 42424)
357
417
 
358
418
  ${pc.bold('Examples:')}
359
419
  hermium start
@@ -364,8 +424,7 @@ ${pc.bold('Examples:')}
364
424
  }
365
425
 
366
426
  function cmdVersion() {
367
- const pkg = JSON.parse(readFileSync(resolve(pkgDir, 'package.json'), 'utf-8'))
368
- console.log(pkg.version)
427
+ console.log(CURRENT_VERSION)
369
428
  }
370
429
 
371
430
  // ─── Entrypoint ─────────────────────────────────────────────────────────────
@@ -385,6 +444,12 @@ switch (command) {
385
444
  case 'status':
386
445
  cmdStatus()
387
446
  break
447
+ case 'build':
448
+ cmdBuild()
449
+ break
450
+ case 'dev':
451
+ cmdDev()
452
+ break
388
453
  case 'version':
389
454
  case '--version':
390
455
  case '-v':