hermes-launch 2.0.0 → 2.1.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +64 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermes-launch",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Web onboarding wizard for Hermes Agent — install, configure, and launch from your browser",
5
5
  "bin": {
6
6
  "hermes-launch": "./bin/hermes-launch.js"
package/src/server.js CHANGED
@@ -20,6 +20,36 @@ const HERMES_HOME = IS_WIN
20
20
  const CONFIG_PATH = resolve(HERMES_HOME, 'config.yaml');
21
21
  const INSTALL_URL = 'https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh';
22
22
 
23
+ function curlOpts() {
24
+ // Windows: curl.exe -s -o nul; macOS/Linux: curl -s -o /dev/null
25
+ if (IS_WIN) return { silent: 'curl.exe -s -o nul -w "%{http_code}"', devnull: 'nul 2>nul' };
26
+ return { silent: 'curl -s -o /dev/null -w "%{http_code}"', devnull: '/dev/null 2>/dev/null' };
27
+ }
28
+
29
+ function curlHealth(port) {
30
+ const c = curlOpts();
31
+ return `${c.silent} http://127.0.0.1:${port}/ ${c.devnull} || echo 0`;
32
+ }
33
+
34
+ function hermesBin() {
35
+ // Resolve hermes command — on Windows it's hermes.exe in Python Scripts
36
+ if (!IS_WIN) return 'hermes';
37
+ // Add common Python Scripts paths to PATH for this process
38
+ const scriptsDirs = [
39
+ resolve(process.env.APPDATA || '', 'Python', 'Python312', 'Scripts'),
40
+ resolve(process.env.APPDATA || '', 'Python', 'Python311', 'Scripts'),
41
+ resolve(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python312', 'Scripts'),
42
+ resolve(homedir(), 'AppData', 'Local', 'Programs', 'Python', 'Python312', 'Scripts'),
43
+ ];
44
+ for (const d of scriptsDirs) {
45
+ if (existsSync(resolve(d, 'hermes.exe'))) {
46
+ process.env.PATH = `${d};${process.env.PATH}`;
47
+ return resolve(d, 'hermes.exe');
48
+ }
49
+ }
50
+ return 'hermes'; // fallback
51
+ }
52
+
23
53
  // ——— helpers ———
24
54
 
25
55
  function run(cmd, opts = {}) {
@@ -52,7 +82,7 @@ api.status = async () => {
52
82
  }
53
83
 
54
84
  // Check dashboard
55
- const dashRunning = run('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9119/ 2>/dev/null || echo 0', { silent: true }).out === '200';
85
+ const dashRunning = run(curlHealth(9119), { silent: true }).out === '200';
56
86
 
57
87
  const tools = ['web', 'terminal', 'file', 'skills', 'session_search', 'delegate_task', 'memory', 'vision', 'tts', 'clarify'];
58
88
 
@@ -120,24 +150,39 @@ api.enableTools = async (tools) => {
120
150
  };
121
151
 
122
152
  api.startDashboard = async () => {
123
- const running = run('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9119/ 2>/dev/null || echo 0', { silent: true });
153
+ const running = run(curlHealth(9119), { silent: true });
124
154
  if (running.out === '200') return { ok: true, alreadyRunning: true, url: 'http://localhost:9119' };
125
155
 
126
- const proc = spawn('hermes', ['dashboard', '--port', '9119', '--tui', '--no-open', '--skip-build'], {
127
- stdio: ['ignore', 'ignore', 'ignore'],
156
+ const hbin = hermesBin();
157
+
158
+ // Collect stderr for error reporting
159
+ const proc = spawn(hbin, ['dashboard', '--port', '9119', '--tui', '--no-open', '--skip-build'], {
160
+ stdio: ['ignore', 'ignore', 'pipe'],
128
161
  detached: true,
129
162
  shell: IS_WIN,
130
163
  env: { ...process.env },
131
164
  });
132
- proc.on('error', () => {});
165
+
166
+ let errMsg = '';
167
+ proc.stderr.setEncoding('utf-8');
168
+ proc.stderr.on('data', d => errMsg += d);
169
+ proc.on('error', e => errMsg += e.message);
170
+
171
+ // Don't wait for child — it runs independently
133
172
  if (proc.unref) proc.unref();
134
173
 
135
- // Wait for it to come up
136
- for (let i = 0; i < 15; i++) {
174
+ // Wait for it to come up (longer on Windows)
175
+ const maxWait = IS_WIN ? 30 : 15;
176
+ for (let i = 0; i < maxWait; i++) {
137
177
  await new Promise(r => setTimeout(r, 1000));
138
- const check = run('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9119/ 2>/dev/null || echo 0', { silent: true });
178
+ const check = run(curlHealth(9119), { silent: true });
139
179
  if (check.out === '200') return { ok: true, alreadyRunning: false, url: 'http://localhost:9119' };
140
180
  }
181
+
182
+ // Report any captured errors
183
+ if (errMsg) {
184
+ return { ok: false, msg: `Dashboard failed to start: ${errMsg.substring(0, 500)}` };
185
+ }
141
186
  return { ok: true, alreadyRunning: false, url: 'http://localhost:9119', note: 'started — may take a moment' };
142
187
  };
143
188
 
@@ -334,10 +379,17 @@ const STEPS = [
334
379
  if (btn) btn.disabled = true;
335
380
  st.innerHTML = '⏳ Starting dashboard... <span class="spinner"></span>';
336
381
  const r = await api('/api/start-dashboard');
337
- st.innerHTML = '✅ Running at <a href="http://localhost:9119" class="link" target="_blank">localhost:9119</a>';
338
- document.getElementById('dash-url').textContent = 'http://localhost:9119';
339
- document.getElementById('dash-area').style.display = 'block';
340
- return true;
382
+ if (r.ok) {
383
+ st.innerHTML = '✅ Running at <a href="http://localhost:9119" class="link" target="_blank">localhost:9119</a>';
384
+ document.getElementById('dash-url').textContent = 'http://localhost:9119';
385
+ document.getElementById('dash-area').style.display = 'block';
386
+ return true;
387
+ } else {
388
+ st.innerHTML = '❌ Failed: ' + (r.msg || 'Could not start dashboard').replace(/</g, '&lt;');
389
+ st.className = 'status fail';
390
+ if (btn) btn.disabled = false;
391
+ return false;
392
+ }
341
393
  }},
342
394
  ];
343
395