hermium 0.1.2 → 0.1.4

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 (150) hide show
  1. package/bin/hermium.mjs +184 -145
  2. package/dist/server/index.mjs +65 -65
  3. package/dist/web-server/__23tanstack-start-plugin-adapters-Cwee5PKy.mjs +6 -0
  4. package/dist/web-server/_chunks/ssr-renderer.mjs +22 -0
  5. package/dist/web-server/_libs/babel__runtime.mjs +237 -0
  6. package/dist/web-server/_libs/bail.mjs +8 -0
  7. package/dist/web-server/_libs/base-ui__react.mjs +9554 -0
  8. package/dist/web-server/_libs/base-ui__utils.mjs +1101 -0
  9. package/dist/web-server/_libs/ccount.mjs +16 -0
  10. package/dist/web-server/_libs/character-entities-legacy.mjs +111 -0
  11. package/dist/web-server/_libs/character-entities.mjs +2130 -0
  12. package/dist/web-server/_libs/character-reference-invalid.mjs +33 -0
  13. package/dist/web-server/_libs/class-variance-authority.mjs +44 -0
  14. package/dist/web-server/_libs/clsx.mjs +16 -0
  15. package/dist/web-server/_libs/comma-separated-tokens.mjs +31 -0
  16. package/dist/web-server/_libs/cookie-es.mjs +44 -0
  17. package/dist/web-server/_libs/croner.mjs +1 -0
  18. package/dist/web-server/_libs/crossws.mjs +1 -0
  19. package/dist/web-server/_libs/decode-named-character-reference+[...].mjs +8 -0
  20. package/dist/web-server/_libs/devlop.mjs +8 -0
  21. package/dist/web-server/_libs/escape-string-regexp.mjs +9 -0
  22. package/dist/web-server/_libs/estree-util-is-identifier-name.mjs +11 -0
  23. package/dist/web-server/_libs/extend.mjs +97 -0
  24. package/dist/web-server/_libs/fault.mjs +1 -0
  25. package/dist/web-server/_libs/floating-ui__core.mjs +663 -0
  26. package/dist/web-server/_libs/floating-ui__dom.mjs +624 -0
  27. package/dist/web-server/_libs/floating-ui__react-dom.mjs +279 -0
  28. package/dist/web-server/_libs/floating-ui__utils.mjs +322 -0
  29. package/dist/web-server/_libs/format.mjs +1 -0
  30. package/dist/web-server/_libs/h3.mjs +408 -0
  31. package/dist/web-server/_libs/hast-util-parse-selector.mjs +39 -0
  32. package/dist/web-server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
  33. package/dist/web-server/_libs/hast-util-whitespace.mjs +10 -0
  34. package/dist/web-server/_libs/hastscript.mjs +200 -0
  35. package/dist/web-server/_libs/highlight.js.mjs +1 -0
  36. package/dist/web-server/_libs/hookable.mjs +1 -0
  37. package/dist/web-server/_libs/html-url-attributes.mjs +26 -0
  38. package/dist/web-server/_libs/inline-style-parser.mjs +142 -0
  39. package/dist/web-server/_libs/is-alphabetical.mjs +7 -0
  40. package/dist/web-server/_libs/is-alphanumerical.mjs +8 -0
  41. package/dist/web-server/_libs/is-decimal.mjs +7 -0
  42. package/dist/web-server/_libs/is-hexadecimal.mjs +7 -0
  43. package/dist/web-server/_libs/is-plain-obj.mjs +10 -0
  44. package/dist/web-server/_libs/isbot.mjs +21 -0
  45. package/dist/web-server/_libs/longest-streak.mjs +25 -0
  46. package/dist/web-server/_libs/lowlight.mjs +1 -0
  47. package/dist/web-server/_libs/markdown-table.mjs +142 -0
  48. package/dist/web-server/_libs/mdast-util-find-and-replace.mjs +109 -0
  49. package/dist/web-server/_libs/mdast-util-from-markdown.mjs +717 -0
  50. package/dist/web-server/_libs/mdast-util-gfm-autolink-literal+[...].mjs +156 -0
  51. package/dist/web-server/_libs/mdast-util-gfm-footnote.mjs +117 -0
  52. package/dist/web-server/_libs/mdast-util-gfm-strikethrough.mjs +54 -0
  53. package/dist/web-server/_libs/mdast-util-gfm-table.mjs +157 -0
  54. package/dist/web-server/_libs/mdast-util-gfm-task-list-item.mjs +77 -0
  55. package/dist/web-server/_libs/mdast-util-gfm.mjs +29 -0
  56. package/dist/web-server/_libs/mdast-util-phrasing.mjs +30 -0
  57. package/dist/web-server/_libs/mdast-util-to-hast.mjs +710 -0
  58. package/dist/web-server/_libs/mdast-util-to-markdown.mjs +798 -0
  59. package/dist/web-server/_libs/mdast-util-to-string.mjs +38 -0
  60. package/dist/web-server/_libs/micromark-core-commonmark.mjs +2259 -0
  61. package/dist/web-server/_libs/micromark-extension-gfm-autolink-literal+[...].mjs +344 -0
  62. package/dist/web-server/_libs/micromark-extension-gfm-footnote+[...].mjs +279 -0
  63. package/dist/web-server/_libs/micromark-extension-gfm-strikethrough+[...].mjs +98 -0
  64. package/dist/web-server/_libs/micromark-extension-gfm-table.mjs +491 -0
  65. package/dist/web-server/_libs/micromark-extension-gfm-tagfilter+[...].mjs +1 -0
  66. package/dist/web-server/_libs/micromark-extension-gfm-task-list-item+[...].mjs +77 -0
  67. package/dist/web-server/_libs/micromark-extension-gfm.mjs +18 -0
  68. package/dist/web-server/_libs/micromark-factory-destination.mjs +94 -0
  69. package/dist/web-server/_libs/micromark-factory-label.mjs +63 -0
  70. package/dist/web-server/_libs/micromark-factory-space.mjs +24 -0
  71. package/dist/web-server/_libs/micromark-factory-title.mjs +65 -0
  72. package/dist/web-server/_libs/micromark-factory-whitespace.mjs +22 -0
  73. package/dist/web-server/_libs/micromark-util-character.mjs +44 -0
  74. package/dist/web-server/_libs/micromark-util-chunked.mjs +36 -0
  75. package/dist/web-server/_libs/micromark-util-classify-character+[...].mjs +12 -0
  76. package/dist/web-server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
  77. package/dist/web-server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
  78. package/dist/web-server/_libs/micromark-util-decode-string.mjs +21 -0
  79. package/dist/web-server/_libs/micromark-util-encode.mjs +1 -0
  80. package/dist/web-server/_libs/micromark-util-html-tag-name.mjs +69 -0
  81. package/dist/web-server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
  82. package/dist/web-server/_libs/micromark-util-resolve-all.mjs +15 -0
  83. package/dist/web-server/_libs/micromark-util-sanitize-uri.mjs +41 -0
  84. package/dist/web-server/_libs/micromark-util-subtokenize.mjs +346 -0
  85. package/dist/web-server/_libs/micromark.mjs +906 -0
  86. package/dist/web-server/_libs/ocache.mjs +1 -0
  87. package/dist/web-server/_libs/ohash.mjs +1 -0
  88. package/dist/web-server/_libs/parse-entities.mjs +245 -0
  89. package/dist/web-server/_libs/property-information.mjs +1210 -0
  90. package/dist/web-server/_libs/react-dom.mjs +10779 -0
  91. package/dist/web-server/_libs/react-markdown.mjs +147 -0
  92. package/dist/web-server/_libs/react-syntax-highlighter.mjs +941 -0
  93. package/dist/web-server/_libs/react.mjs +513 -0
  94. package/dist/web-server/_libs/refractor.mjs +2425 -0
  95. package/dist/web-server/_libs/remark-gfm.mjs +20 -0
  96. package/dist/web-server/_libs/remark-parse.mjs +19 -0
  97. package/dist/web-server/_libs/remark-rehype.mjs +21 -0
  98. package/dist/web-server/_libs/reselect.mjs +1 -0
  99. package/dist/web-server/_libs/rou3.mjs +8 -0
  100. package/dist/web-server/_libs/seroval-plugins.mjs +58 -0
  101. package/dist/web-server/_libs/seroval.mjs +1775 -0
  102. package/dist/web-server/_libs/space-separated-tokens.mjs +11 -0
  103. package/dist/web-server/_libs/srvx.mjs +781 -0
  104. package/dist/web-server/_libs/style-to-js.mjs +72 -0
  105. package/dist/web-server/_libs/style-to-object.mjs +38 -0
  106. package/dist/web-server/_libs/tabler__icons-react.mjs +224 -0
  107. package/dist/web-server/_libs/tanstack__history.mjs +204 -0
  108. package/dist/web-server/_libs/tanstack__query-core.mjs +2552 -0
  109. package/dist/web-server/_libs/tanstack__react-query.mjs +190 -0
  110. package/dist/web-server/_libs/tanstack__react-router.mjs +1120 -0
  111. package/dist/web-server/_libs/tanstack__react-store.mjs +2 -0
  112. package/dist/web-server/_libs/tanstack__router-core.mjs +4288 -0
  113. package/dist/web-server/_libs/tanstack__store.mjs +1 -0
  114. package/dist/web-server/_libs/trim-lines.mjs +41 -0
  115. package/dist/web-server/_libs/trough.mjs +85 -0
  116. package/dist/web-server/_libs/ufo.mjs +54 -0
  117. package/dist/web-server/_libs/unctx.mjs +1 -0
  118. package/dist/web-server/_libs/ungap__structured-clone.mjs +224 -0
  119. package/dist/web-server/_libs/unified.mjs +661 -0
  120. package/dist/web-server/_libs/unist-util-is.mjs +100 -0
  121. package/dist/web-server/_libs/unist-util-position.mjs +27 -0
  122. package/dist/web-server/_libs/unist-util-stringify-position.mjs +27 -0
  123. package/dist/web-server/_libs/unist-util-visit-parents.mjs +83 -0
  124. package/dist/web-server/_libs/unist-util-visit.mjs +24 -0
  125. package/dist/web-server/_libs/unstorage.mjs +1 -0
  126. package/dist/web-server/_libs/use-sync-external-store.mjs +139 -0
  127. package/dist/web-server/_libs/vfile-message.mjs +138 -0
  128. package/dist/web-server/_libs/vfile.mjs +467 -0
  129. package/dist/web-server/_libs/zod.mjs +3915 -0
  130. package/dist/web-server/_libs/zustand.mjs +343 -0
  131. package/dist/web-server/_libs/zwitch.mjs +1 -0
  132. package/dist/web-server/_ssr/index-BLK6uN4p.mjs +612 -0
  133. package/dist/web-server/_ssr/index-BkkxTg0a.mjs +1855 -0
  134. package/dist/web-server/_ssr/index-Bp9a_nTf.mjs +66 -0
  135. package/dist/web-server/_ssr/index-C8t8AZQG.mjs +513 -0
  136. package/dist/web-server/_ssr/index-DSIu0x-q.mjs +449 -0
  137. package/dist/web-server/_ssr/index-DqFrn6kj.mjs +278 -0
  138. package/dist/web-server/_ssr/index-EKE8NFy_.mjs +189 -0
  139. package/dist/web-server/_ssr/index-JzLhPyir.mjs +213 -0
  140. package/dist/web-server/_ssr/index-wTy_4MhH.mjs +369 -0
  141. package/dist/web-server/_ssr/index.mjs +1558 -0
  142. package/dist/web-server/_ssr/input-BQFduUUo.mjs +20 -0
  143. package/dist/web-server/_ssr/router-59cN5lqo.mjs +1998 -0
  144. package/dist/web-server/_ssr/start-HYkvq4Ni.mjs +4 -0
  145. package/dist/web-server/_ssr/switch-Bim4kX8N.mjs +33 -0
  146. package/dist/web-server/_ssr/syntax-highlighter-5vezNTce.mjs +62 -0
  147. package/dist/web-server/_ssr/textarea-CK0ROhfF.mjs +18 -0
  148. package/dist/web-server/_tanstack-start-manifest_v-DLw6M7p4.mjs +4 -0
  149. package/dist/web-server/index.mjs +611 -0
  150. package/package.json +1 -1
package/bin/hermium.mjs CHANGED
@@ -7,44 +7,42 @@ import { homedir } from 'os'
7
7
  import pc from 'picocolors'
8
8
 
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url))
10
+ const pkgDir = resolve(__dirname, '..')
11
+ const apiEntry = resolve(pkgDir, 'dist', 'server', 'index.mjs')
12
+ const webEntry = resolve(pkgDir, 'dist', 'web-server', 'index.mjs')
13
+ const webServerDir = resolve(pkgDir, 'dist', 'web-server')
14
+ const WEB_UI_HOME = resolve(homedir(), '.hermium')
15
+ const API_PID_FILE = join(WEB_UI_HOME, 'api.pid')
16
+ const WEB_PID_FILE = join(WEB_UI_HOME, 'web.pid')
17
+ const API_LOG_FILE = join(WEB_UI_HOME, 'api.log')
18
+ const WEB_LOG_FILE = join(WEB_UI_HOME, 'web.log')
19
+ const DEFAULT_API_PORT = 8788
20
+ const DEFAULT_WEB_PORT = 3000
10
21
 
11
22
  // ─── Runtime detection ─────────────────────────────────────────────────────
12
23
 
13
- function detectRuntime() {
14
- // Check if bun is available on PATH
24
+ function getRuntimeCmd() {
15
25
  try {
16
26
  execSync('bun --version', { stdio: 'ignore' })
17
- return { cmd: 'bun', found: true }
27
+ return 'bun'
18
28
  } catch {
19
- // fall back to current process
20
29
  const exe = process.execPath
21
30
  const isBun = exe.includes('bun') || (process.versions && process.versions.bun)
22
- return { cmd: exe, found: !!isBun }
31
+ if (!isBun) {
32
+ console.log(pc.red(' ✗ Bun is required to run Hermium.'))
33
+ console.log(' Install: curl -fsSL https://bun.sh/install | bash')
34
+ console.log(' Or: npm install -g bun')
35
+ process.exit(1)
36
+ }
37
+ return exe
23
38
  }
24
39
  }
25
40
 
26
- function getRuntimeCmd() {
27
- const rt = detectRuntime()
28
- if (!rt.found) {
29
- console.log(pc.red(' ✗ Bun is required to run Hermium.'))
30
- console.log(' Install: curl -fsSL https://bun.sh/install | bash')
31
- console.log(' Or: npm install -g bun')
32
- process.exit(1)
33
- }
34
- return rt.cmd
35
- }
36
- const pkgDir = resolve(__dirname, '..')
37
- const serverEntry = resolve(pkgDir, 'dist', 'server', 'index.mjs')
38
- const WEB_UI_HOME = resolve(homedir(), '.hermium')
39
- const PID_FILE = join(WEB_UI_HOME, 'server.pid')
40
- const LOG_FILE = join(WEB_UI_HOME, 'server.log')
41
- const DEFAULT_PORT = 8788
42
-
43
41
  // ─── Helpers ───────────────────────────────────────────────────────────────
44
42
 
45
- function readPid() {
43
+ function readPid(file) {
46
44
  try {
47
- const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim())
45
+ const pid = parseInt(readFileSync(file, 'utf-8').trim())
48
46
  return Number.isFinite(pid) ? pid : null
49
47
  } catch {
50
48
  return null
@@ -60,37 +58,36 @@ function isRunning(pid) {
60
58
  }
61
59
  }
62
60
 
63
- function writePid(pid) {
61
+ function writePid(file, pid) {
64
62
  mkdirSync(WEB_UI_HOME, { recursive: true })
65
- writeFileSync(PID_FILE, String(pid))
63
+ writeFileSync(file, String(pid))
66
64
  }
67
65
 
68
- function removePid() {
69
- try { unlinkSync(PID_FILE) } catch {}
66
+ function removePid(file) {
67
+ try { unlinkSync(file) } catch {}
70
68
  }
71
69
 
72
- function getPid() {
73
- const pid = readPid()
70
+ function getPid(file) {
71
+ const pid = readPid(file)
74
72
  if (pid && isRunning(pid)) return pid
75
- removePid()
73
+ removePid(file)
76
74
  return null
77
75
  }
78
76
 
79
- function getPort() {
80
- const idx = process.argv.indexOf('--port')
77
+ function getApiPid() { return getPid(API_PID_FILE) }
78
+ function getWebPid() { return getPid(WEB_PID_FILE) }
79
+
80
+ function getPort(argName, defaultPort) {
81
+ const idx = process.argv.indexOf(argName)
81
82
  if (idx !== -1 && process.argv[idx + 1]) {
82
83
  const p = parseInt(process.argv[idx + 1])
83
84
  if (!isNaN(p)) return p
84
85
  }
85
- if (process.env.HERMIUM_PORT && !isNaN(process.env.HERMIUM_PORT)) {
86
- return parseInt(process.env.HERMIUM_PORT)
87
- }
88
- return DEFAULT_PORT
86
+ return defaultPort
89
87
  }
90
88
 
91
89
  function getRunningPort(pid) {
92
90
  if (!pid) return null
93
-
94
91
  try {
95
92
  if (process.platform === 'win32') {
96
93
  const out = execSync(`netstat -aon -p tcp | findstr LISTENING | findstr " ${pid}$"`, { encoding: 'utf-8' }).trim()
@@ -99,7 +96,6 @@ function getRunningPort(pid) {
99
96
  const port = address?.split(':').pop()
100
97
  return port ? parseInt(port, 10) : null
101
98
  }
102
-
103
99
  const out = execSync(`lsof -Pan -p ${pid} -iTCP -sTCP:LISTEN`, { encoding: 'utf-8' }).trim()
104
100
  const lines = out.split('\n').slice(1)
105
101
  for (const line of lines) {
@@ -110,150 +106,192 @@ function getRunningPort(pid) {
110
106
  return null
111
107
  }
112
108
 
109
+ function rotateLog(file) {
110
+ try {
111
+ const st = statSync(file)
112
+ if (st.size > 3 * 1024 * 1024) {
113
+ const content = readFileSync(file, 'utf-8')
114
+ const kept = content.split('\n').slice(-2000)
115
+ writeFileSync(file, kept.join('\n'))
116
+ console.log(pc.cyan(` ↻ Rotated ${file.replace(homedir(), '~')}`))
117
+ }
118
+ } catch {}
119
+ }
120
+
121
+ function spawnServer({ name, entry, logFile, pidFile, port, env, runtime, cwd }) {
122
+ if (!existsSync(entry)) {
123
+ console.log(pc.red(` ✗ ${name} not found: ${entry}`))
124
+ console.log(` Run "hermium build" first (or check your installation)`)
125
+ process.exit(1)
126
+ }
127
+
128
+ rotateLog(logFile)
129
+
130
+ const logFd = openSync(logFile, 'a')
131
+ const child = spawn(runtime, [entry], {
132
+ detached: true,
133
+ stdio: ['ignore', logFd, logFd],
134
+ env: { ...process.env, ...env, NODE_ENV: 'production' },
135
+ cwd: cwd || dirname(entry),
136
+ })
137
+
138
+ child.on('error', (err) => {
139
+ console.error(pc.red(` ✗ Failed to start ${name}: ${err.message}`))
140
+ removePid(pidFile)
141
+ process.exit(1)
142
+ })
143
+
144
+ child.unref()
145
+ writePid(pidFile, child.pid)
146
+ return child.pid
147
+ }
148
+
149
+ function stopPid(pid, name) {
150
+ if (!pid) return false
151
+ try {
152
+ process.kill(pid, 'SIGTERM')
153
+ const start = Date.now()
154
+ while (isRunning(pid) && Date.now() - start < 5000) {
155
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100)
156
+ }
157
+ if (isRunning(pid)) process.kill(pid, 'SIGKILL')
158
+ return true
159
+ } catch {
160
+ return true
161
+ }
162
+ }
163
+
113
164
  // ─── Commands ──────────────────────────────────────────────────────────────
114
165
 
115
166
  function cmdStatus() {
116
- const pid = getPid()
117
- if (pid) {
118
- const port = getRunningPort(pid)
167
+ const apiPid = getApiPid()
168
+ const webPid = getWebPid()
169
+ const apiPort = apiPid ? getRunningPort(apiPid) : null
170
+ const webPort = webPid ? getRunningPort(webPid) : null
171
+
172
+ if (apiPid || webPid) {
119
173
  console.log(pc.green(` ✓ Hermium is running`))
120
- console.log(` PID : ${pid}`)
121
- console.log(` Port : ${port || 'unknown'}`)
122
- console.log(` Log : ${LOG_FILE}`)
174
+ if (apiPid) console.log(` API : PID ${apiPid}, port ${apiPort || 'unknown'}`)
175
+ if (webPid) console.log(` Web : PID ${webPid}, port ${webPort || 'unknown'}`)
176
+ console.log(` Logs : ${WEB_UI_HOME}`)
123
177
  } else {
124
178
  console.log(pc.yellow(` ⊘ Hermium is not running`))
125
179
  }
126
180
  }
127
181
 
128
- function cmdStart() {
129
- const existing = getPid()
130
- if (existing) {
131
- console.log(pc.yellow(` Hermium is already running (PID: ${existing})`))
132
- console.log(` Use "hermium stop" to stop it first`)
182
+ async function cmdStart() {
183
+ const existingApi = getApiPid()
184
+ const existingWeb = getWebPid()
185
+ if (existingApi || existingWeb) {
186
+ console.log(pc.yellow(` Hermium is already running`))
187
+ if (existingApi) console.log(` API PID: ${existingApi}`)
188
+ if (existingWeb) console.log(` Web PID: ${existingWeb}`)
189
+ console.log(` Use "hermium stop" first`)
133
190
  process.exit(1)
134
191
  }
135
192
 
136
- if (!existsSync(serverEntry)) {
137
- console.log(pc.red(` Server not found: ${serverEntry}`))
138
- console.log(` Run "hermium build" first (or check your installation)`)
139
- process.exit(1)
140
- }
193
+ const apiPort = getPort('--port', DEFAULT_API_PORT)
194
+ const webPort = getPort('--web-port', DEFAULT_WEB_PORT)
195
+ const runtime = getRuntimeCmd()
141
196
 
142
- const port = getPort()
143
197
  mkdirSync(WEB_UI_HOME, { recursive: true })
144
198
 
145
- // Rotate log if over 3 MB
146
- try {
147
- const st = statSync(LOG_FILE)
148
- if (st.size > 3 * 1024 * 1024) {
149
- const content = readFileSync(LOG_FILE, 'utf-8')
150
- const kept = content.split('\n').slice(-2000)
151
- writeFileSync(LOG_FILE, kept.join('\n'))
152
- console.log(pc.cyan(` ↻ Log rotated`))
153
- }
154
- } catch {}
199
+ console.log(pc.cyan(` ⏳ Starting Hermium (API:${apiPort}, Web:${webPort})...`))
155
200
 
156
- const runtime = getRuntimeCmd()
157
- const logFd = openSync(LOG_FILE, 'a')
158
- const child = spawn(runtime, [serverEntry], {
159
- detached: true,
160
- stdio: ['ignore', logFd, logFd],
201
+ // 1. Start API server
202
+ const apiPid = spawnServer({
203
+ name: 'API server',
204
+ entry: apiEntry,
205
+ logFile: API_LOG_FILE,
206
+ pidFile: API_PID_FILE,
207
+ port: apiPort,
208
+ runtime,
161
209
  env: {
162
- ...process.env,
163
- NODE_ENV: 'production',
210
+ HERMIUM_PORT: String(apiPort),
211
+ HERMIUM_WEB_PORT: String(webPort),
164
212
  WEB_STATIC_DIR: resolve(pkgDir, 'dist', 'web'),
165
- HERMIUM_PORT: String(port),
166
213
  },
167
214
  })
168
215
 
169
- child.on('error', (err) => {
170
- console.error(pc.red(` Failed to start: ${err.message}`))
171
- removePid()
172
- process.exit(1)
216
+ // 2. Start web SSR server
217
+ const webPid = spawnServer({
218
+ name: 'Web server',
219
+ entry: webEntry,
220
+ logFile: WEB_LOG_FILE,
221
+ pidFile: WEB_PID_FILE,
222
+ port: webPort,
223
+ runtime,
224
+ cwd: webServerDir,
225
+ env: { PORT: String(webPort) },
173
226
  })
174
227
 
175
- child.unref()
176
- writePid(child.pid)
228
+ console.log(` API PID: ${apiPid}`)
229
+ console.log(` Web PID: ${webPid}`)
177
230
 
178
- // Poll health endpoint
179
- const healthUrl = `http://127.0.0.1:${port}/api/health`
231
+ // Poll web server health
180
232
  const maxWait = 30000
181
233
  const interval = 500
182
234
  let waited = 0
183
-
184
- console.log(pc.cyan(` ⏳ Starting Hermium (PID: ${child.pid}, port: ${port})...`))
235
+ const webUrl = `http://localhost:${webPort}`
185
236
 
186
237
  function poll() {
187
238
  waited += interval
188
- if (!isRunning(child.pid)) {
189
- console.log(pc.red(` ✗ Failed to start`))
190
- console.log(` Check log: ${LOG_FILE}`)
191
- removePid()
239
+
240
+ if (!isRunning(apiPid)) {
241
+ console.log(pc.red(` API server crashed`))
242
+ console.log(` Check log: ${API_LOG_FILE}`)
243
+ stopPid(webPid, 'web')
244
+ removePid(API_PID_FILE)
245
+ removePid(WEB_PID_FILE)
246
+ process.exit(1)
247
+ }
248
+ if (!isRunning(webPid)) {
249
+ console.log(pc.red(` ✗ Web server crashed`))
250
+ console.log(` Check log: ${WEB_LOG_FILE}`)
251
+ stopPid(apiPid, 'api')
252
+ removePid(API_PID_FILE)
253
+ removePid(WEB_PID_FILE)
192
254
  process.exit(1)
193
255
  }
194
256
 
195
- fetch(healthUrl)
196
- .then((res) => {
197
- if (res.ok) {
198
- const url = `http://localhost:${port}`
199
- console.log(pc.green(` ✓ Hermium started`))
200
- console.log(` ${url}`)
201
- console.log(` Log: ${LOG_FILE}`)
202
- // Auto-open browser
203
- const openCmd =
204
- process.platform === 'win32'
205
- ? `start ${url}`
206
- : process.platform === 'darwin'
207
- ? `open ${url}`
208
- : `xdg-open ${url}`
209
- try {
210
- execSync(openCmd, { stdio: 'ignore' })
211
- } catch {}
212
- } else if (waited < maxWait) {
213
- setTimeout(poll, interval)
214
- } else {
215
- console.log(pc.red(` ✗ Timed out waiting for server`))
216
- console.log(` Check log: ${LOG_FILE}`)
217
- removePid()
218
- process.exit(1)
219
- }
220
- })
221
- .catch(() => {
222
- if (waited < maxWait) {
223
- setTimeout(poll, interval)
224
- } else {
225
- console.log(pc.red(` ✗ Timed out waiting for server`))
226
- console.log(` Check log: ${LOG_FILE}`)
227
- removePid()
228
- process.exit(1)
229
- }
230
- })
257
+ fetch(`${webUrl}/api/health`).catch(() => null).then((res) => {
258
+ // Web server doesn't have /api/health, this will 404 but we just want to check it's listening
259
+ if (waited < maxWait) {
260
+ setTimeout(poll, interval)
261
+ } else {
262
+ console.log(pc.green(` ✓ Hermium started`))
263
+ console.log(` ${webUrl}`)
264
+ console.log(` Logs: ${WEB_UI_HOME}`)
265
+ const openCmd =
266
+ process.platform === 'win32' ? `start ${webUrl}` :
267
+ process.platform === 'darwin' ? `open ${webUrl}` :
268
+ `xdg-open ${webUrl}`
269
+ try { execSync(openCmd, { stdio: 'ignore' }) } catch {}
270
+ }
271
+ })
231
272
  }
232
273
 
233
274
  setTimeout(poll, interval)
234
275
  }
235
276
 
236
277
  function cmdStop() {
237
- const pid = getPid()
238
- if (!pid) {
278
+ const apiPid = getApiPid()
279
+ const webPid = getWebPid()
280
+
281
+ if (!apiPid && !webPid) {
239
282
  console.log(pc.yellow(` ⊘ Hermium is not running`))
240
283
  return
241
284
  }
242
285
 
243
- try {
244
- process.kill(pid, 'SIGTERM')
245
- const start = Date.now()
246
- while (isRunning(pid) && Date.now() - start < 5000) {
247
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100)
248
- }
249
- if (isRunning(pid)) {
250
- process.kill(pid, 'SIGKILL')
251
- }
252
- removePid()
253
- console.log(pc.green(` ✓ Hermium stopped (PID: ${pid})`))
254
- } catch {
255
- removePid()
256
- console.log(pc.green(` ✓ Hermium stopped (PID: ${pid})`))
286
+ if (webPid) {
287
+ stopPid(webPid, 'web')
288
+ removePid(WEB_PID_FILE)
289
+ console.log(pc.green(` ✓ Web server stopped (PID: ${webPid})`))
290
+ }
291
+ if (apiPid) {
292
+ stopPid(apiPid, 'api')
293
+ removePid(API_PID_FILE)
294
+ console.log(pc.green(` ✓ API server stopped (PID: ${apiPid})`))
257
295
  }
258
296
  }
259
297
 
@@ -271,18 +309,19 @@ ${pc.bold('Usage:')}
271
309
  hermium <command> [options]
272
310
 
273
311
  ${pc.bold('Commands:')}
274
- start Start Hermium server (daemon)
275
- stop Stop Hermium server
276
- restart Restart Hermium server
312
+ start Start Hermium servers (daemon)
313
+ stop Stop Hermium servers
314
+ restart Restart Hermium servers
277
315
  status Show running status
278
316
  help Show this help message
279
317
 
280
318
  ${pc.bold('Options:')}
281
- --port <n> Port to run on (default: 8788)
319
+ --port <n> API port (default: 8788)
320
+ --web-port <n> Web port (default: 3000)
282
321
 
283
322
  ${pc.bold('Examples:')}
284
323
  hermium start
285
- hermium start --port 3000
324
+ hermium start --port 9000 --web-port 3001
286
325
  hermium stop
287
326
  hermium status
288
327
  `)