hermes-launch 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/hermes-launch.js +75 -63
- package/package.json +1 -1
package/bin/hermes-launch.js
CHANGED
|
@@ -26,8 +26,7 @@ const HERMES_HOME = IS_WIN
|
|
|
26
26
|
const CONFIG_PATH = resolve(HERMES_HOME, 'config.yaml');
|
|
27
27
|
const INSTALL_URL = 'https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh';
|
|
28
28
|
|
|
29
|
-
// Windows
|
|
30
|
-
// terminals that support them (Windows Terminal, VS Code terminal, etc.)
|
|
29
|
+
// Windows Terminal / VS Code support ansi; raw cmd.exe doesn't
|
|
31
30
|
const USE_COLOR = !IS_WIN || (process.env.TERM_PROGRAM || '').includes('vscode') || process.env.WT_SESSION;
|
|
32
31
|
const BOLD = USE_COLOR ? '\x1b[1m' : '';
|
|
33
32
|
const DIM = USE_COLOR ? '\x1b[2m' : '';
|
|
@@ -57,7 +56,7 @@ function run(cmd, opts = {}) {
|
|
|
57
56
|
const defaults = {
|
|
58
57
|
stdio: opts.silent ? 'pipe' : 'inherit',
|
|
59
58
|
timeout: 120_000,
|
|
60
|
-
shell: IS_WIN ? true : false,
|
|
59
|
+
shell: IS_WIN ? true : false,
|
|
61
60
|
...opts,
|
|
62
61
|
};
|
|
63
62
|
try {
|
|
@@ -69,7 +68,6 @@ function run(cmd, opts = {}) {
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
function runPwsh(cmd, opts = {}) {
|
|
72
|
-
// Run a PowerShell command — used on Windows for winget, etc.
|
|
73
71
|
return run(`powershell -NoProfile -Command "${cmd}"`, opts);
|
|
74
72
|
}
|
|
75
73
|
|
|
@@ -86,30 +84,53 @@ function step(msg) {
|
|
|
86
84
|
};
|
|
87
85
|
}
|
|
88
86
|
|
|
87
|
+
function openBrowser(url) {
|
|
88
|
+
try {
|
|
89
|
+
if (IS_WIN) {
|
|
90
|
+
execSync(`start "" "${url}"`, { shell: 'cmd.exe', timeout: 5000, stdio: 'ignore' });
|
|
91
|
+
} else if (IS_MAC) {
|
|
92
|
+
execSync(`open "${url}"`, { timeout: 5000, stdio: 'ignore' });
|
|
93
|
+
} else {
|
|
94
|
+
execSync(`xdg-open "${url}" 2>/dev/null || sensible-browser "${url}" 2>/dev/null || true`, { timeout: 5000, stdio: 'ignore' });
|
|
95
|
+
}
|
|
96
|
+
} catch (_) { /* best-effort */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
function printSummary(info) {
|
|
100
|
+
const dashUrl = `http://localhost:${info.port || 9119}`;
|
|
101
|
+
|
|
90
102
|
console.log(`\n${BOLD}${GREEN} ✅ Hermes Agent is live!${RESET}\n`);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
console.log(` ${BOLD}
|
|
103
|
+
|
|
104
|
+
// Big visible dashboard URL
|
|
105
|
+
console.log(` ${BOLD}🌐 Web Dashboard${RESET}`);
|
|
106
|
+
console.log(` ${CYAN} ┌──────────────────────────────────────────┐${RESET}`);
|
|
107
|
+
console.log(` ${CYAN} │${RESET} ${BOLD}${dashUrl}${RESET} ${CYAN}│${RESET}`);
|
|
108
|
+
console.log(` ${CYAN} └──────────────────────────────────────────┘${RESET}`);
|
|
109
|
+
console.log(` ${DIM} (Opened in your browser)${RESET}\n`);
|
|
110
|
+
|
|
111
|
+
console.log(` ${BOLD}📟 CLI${RESET}`);
|
|
94
112
|
console.log(` ${CYAN}hermes${RESET} interactive chat`);
|
|
95
113
|
console.log(` ${CYAN}hermes chat -q "..."${RESET} one-shot query`);
|
|
96
114
|
console.log(` ${CYAN}hermes --continue${RESET} resume last session\n`);
|
|
97
|
-
|
|
115
|
+
|
|
116
|
+
console.log(` ${BOLD}⚡ Key commands${RESET}`);
|
|
98
117
|
console.log(` ${CYAN}hermes model${RESET} change model/provider`);
|
|
99
118
|
console.log(` ${CYAN}hermes gateway setup${RESET} connect Telegram/Discord`);
|
|
100
119
|
console.log(` ${CYAN}hermes tools list${RESET} see available tools`);
|
|
101
120
|
console.log(` ${CYAN}hermes doctor${RESET} health check\n`);
|
|
102
|
-
|
|
121
|
+
|
|
122
|
+
console.log(` ${BOLD}📖 Docs${RESET}`);
|
|
103
123
|
console.log(` ${CYAN}https://hermes-agent.nousresearch.com/docs${RESET}\n`);
|
|
124
|
+
|
|
104
125
|
if (info.messaging) {
|
|
105
|
-
console.log(` ${BOLD}Messaging${RESET}`);
|
|
126
|
+
console.log(` ${BOLD}💬 Messaging${RESET}`);
|
|
106
127
|
for (const [platform, status] of Object.entries(info.messaging)) {
|
|
107
128
|
console.log(` ${platform}: ${status}`);
|
|
108
129
|
}
|
|
109
130
|
console.log();
|
|
110
131
|
}
|
|
111
132
|
if (info.windowsNote) {
|
|
112
|
-
console.log(` ${YELLOW}${info.windowsNote}${RESET}\n`);
|
|
133
|
+
console.log(` ${YELLOW} ${info.windowsNote}${RESET}\n`);
|
|
113
134
|
}
|
|
114
135
|
}
|
|
115
136
|
|
|
@@ -117,7 +138,6 @@ function printSummary(info) {
|
|
|
117
138
|
|
|
118
139
|
function detectPackageManager() {
|
|
119
140
|
if (IS_WIN) {
|
|
120
|
-
// winget ships with Windows 11 and recent Win10
|
|
121
141
|
if (run('winget --version', { silent: true }).ok) return 'winget';
|
|
122
142
|
if (run('choco --version', { silent: true }).ok) return 'choco';
|
|
123
143
|
if (run('scoop --version', { silent: true }).ok) return 'scoop';
|
|
@@ -134,14 +154,9 @@ function detectPackageManager() {
|
|
|
134
154
|
return null;
|
|
135
155
|
}
|
|
136
156
|
|
|
137
|
-
function winHasWsl() {
|
|
138
|
-
return run('wsl --version', { silent: true }).ok || run('wsl -l', { silent: true }).ok;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
157
|
// ——— prerequisite checks + auto-install ———
|
|
142
158
|
|
|
143
159
|
async function checkPython() {
|
|
144
|
-
// Windows uses "python" and "py", not "python3"
|
|
145
160
|
const pyCandidates = IS_WIN
|
|
146
161
|
? ['python', 'py -3', 'python3']
|
|
147
162
|
: ['python3.14', 'python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3'];
|
|
@@ -176,14 +191,11 @@ async function installPython(pkgMan) {
|
|
|
176
191
|
s.fail('No package manager found. Install Python from https://python.org');
|
|
177
192
|
return false;
|
|
178
193
|
}
|
|
179
|
-
// Refresh PATH so we can find python immediately
|
|
180
194
|
process.env.PATH = `${process.env.USERPROFILE}\\AppData\\Local\\Programs\\Python\\Python312\\;${process.env.USERPROFILE}\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\;${process.env.PATH}`;
|
|
181
195
|
} else if (IS_MAC) {
|
|
182
196
|
run('brew install python@3.12', { timeout: 180_000 });
|
|
183
|
-
// brew python3.12 is often keg-only — symlink or use full path
|
|
184
197
|
run('brew link --overwrite python@3.12', { silent: true, timeout: 30_000 });
|
|
185
198
|
} else {
|
|
186
|
-
// Linux
|
|
187
199
|
if (pkgMan === 'apt') {
|
|
188
200
|
run('apt-get update -qq && apt-get install -y python3 python3-pip python3-venv', { timeout: 180_000 });
|
|
189
201
|
} else if (pkgMan === 'dnf') {
|
|
@@ -212,14 +224,12 @@ async function installCurl(pkgMan) {
|
|
|
212
224
|
const s = step('Installing curl');
|
|
213
225
|
try {
|
|
214
226
|
if (IS_WIN) {
|
|
215
|
-
// Windows 10+ has curl.exe built-in. If missing, use winget.
|
|
216
227
|
if (pkgMan === 'winget') {
|
|
217
228
|
runPwsh('winget install --accept-source-agreements cURL.cURL', { timeout: 120_000 });
|
|
218
229
|
} else if (pkgMan === 'choco') {
|
|
219
230
|
run('choco install curl --yes', { timeout: 120_000 });
|
|
220
231
|
}
|
|
221
232
|
} else {
|
|
222
|
-
// macOS/Linux — curl should be present but just in case
|
|
223
233
|
run('command -v curl || (apt-get install -y curl 2>/dev/null || yum install -y curl 2>/dev/null)', { timeout: 120_000 });
|
|
224
234
|
}
|
|
225
235
|
s.ok();
|
|
@@ -318,7 +328,6 @@ async function checkPrereqs() {
|
|
|
318
328
|
async function installHermes(quick, platformInfo) {
|
|
319
329
|
const s = step('Checking Hermes installation');
|
|
320
330
|
|
|
321
|
-
// On Windows, try both "hermes" and "hermes.exe"
|
|
322
331
|
const hermesCheck = IS_WIN
|
|
323
332
|
? (run('hermes --version', { silent: true }).ok || run('hermes.exe --version', { silent: true }).ok)
|
|
324
333
|
: run('hermes --version', { silent: true }).ok;
|
|
@@ -343,17 +352,13 @@ async function installHermes(quick, platformInfo) {
|
|
|
343
352
|
const si = step('Downloading and installing Hermes');
|
|
344
353
|
|
|
345
354
|
if (IS_WIN) {
|
|
346
|
-
// Windows: install via pip (the bash script won't work without WSL)
|
|
347
355
|
try {
|
|
348
|
-
// Ensure pip is available
|
|
349
356
|
const pyBin = platformInfo.bin || 'python';
|
|
350
357
|
run(`${pyBin} -m pip install --upgrade pip`, { silent: true, timeout: 60_000 });
|
|
351
358
|
|
|
352
359
|
const r = run(`${pyBin} -m pip install hermes-agent`, { timeout: 180_000 });
|
|
353
360
|
if (!r.ok) throw new Error(r.out);
|
|
354
361
|
|
|
355
|
-
// After pip install, check if "hermes" command is available
|
|
356
|
-
// On Windows, scripts may be in %APPDATA%\Python\Scripts or %USERPROFILE%\.local\bin
|
|
357
362
|
const pathsToAdd = [
|
|
358
363
|
resolve(process.env.APPDATA || '', 'Python', 'Scripts'),
|
|
359
364
|
resolve(homedir(), '.local', 'bin'),
|
|
@@ -367,21 +372,12 @@ async function installHermes(quick, platformInfo) {
|
|
|
367
372
|
si.ok();
|
|
368
373
|
return true;
|
|
369
374
|
} catch (e) {
|
|
370
|
-
// If pip fails, suggest WSL which is the recommended path
|
|
371
375
|
si.fail(e.message);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
console.log(` ${CYAN}wsl -d Ubuntu -e bash -c "curl -fsSL ${INSTALL_URL} | bash"${RESET}\n`);
|
|
375
|
-
} else {
|
|
376
|
-
console.log(`\n ${YELLOW}Install WSL + Ubuntu, then run this inside WSL:${RESET}`);
|
|
377
|
-
console.log(` ${CYAN}curl -fsSL ${INSTALL_URL} | bash${RESET}`);
|
|
378
|
-
console.log(` ${YELLOW}Or install WSL first:${RESET}`);
|
|
379
|
-
console.log(` ${CYAN}wsl --install${RESET}\n`);
|
|
380
|
-
}
|
|
376
|
+
console.log(`\n ${YELLOW}Pip install failed. Try inside WSL instead:${RESET}`);
|
|
377
|
+
console.log(` ${CYAN}wsl -d Ubuntu -e bash -c "curl -fsSL ${INSTALL_URL} | bash"${RESET}\n`);
|
|
381
378
|
process.exit(1);
|
|
382
379
|
}
|
|
383
380
|
} else {
|
|
384
|
-
// macOS / Linux
|
|
385
381
|
try {
|
|
386
382
|
const cmd = `curl -fsSL ${INSTALL_URL} | bash`;
|
|
387
383
|
const result = run(cmd, { timeout: 180_000, silent: false });
|
|
@@ -410,7 +406,6 @@ async function checkConfig(quick) {
|
|
|
410
406
|
|
|
411
407
|
const raw = readFileSync(CONFIG_PATH, 'utf-8');
|
|
412
408
|
|
|
413
|
-
// Check if config has a model set (not commented out)
|
|
414
409
|
const hasModel = /^model:\s*\n/m.test(raw);
|
|
415
410
|
|
|
416
411
|
if (!hasModel || raw.includes('default: ""') || raw.includes("default: ''")) {
|
|
@@ -425,11 +420,9 @@ async function checkConfig(quick) {
|
|
|
425
420
|
async function runSetup(quick) {
|
|
426
421
|
if (quick) {
|
|
427
422
|
const s = step('Running headless setup');
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
];
|
|
432
|
-
for (const cmd of setCmds) run(cmd, { silent: true });
|
|
423
|
+
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
424
|
+
run(`hermes tools enable web terminal file skills session_search delegate_task memory clarify vision ${noop}`, { silent: true });
|
|
425
|
+
run(`hermes config set display.streaming.enabled true ${noop}`, { silent: true });
|
|
433
426
|
s.ok();
|
|
434
427
|
return;
|
|
435
428
|
}
|
|
@@ -449,7 +442,8 @@ async function runSetup(quick) {
|
|
|
449
442
|
}
|
|
450
443
|
} else {
|
|
451
444
|
const s = step('Applying minimal configuration');
|
|
452
|
-
|
|
445
|
+
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
446
|
+
run(`hermes tools enable web terminal file skills session_search delegate_task memory ${noop}`, { silent: true });
|
|
453
447
|
s.skip('minimal config applied');
|
|
454
448
|
}
|
|
455
449
|
}
|
|
@@ -458,8 +452,6 @@ async function enableToolsets(quick) {
|
|
|
458
452
|
const s = step('Enabling tool sets');
|
|
459
453
|
|
|
460
454
|
const toolsets = ['web', 'terminal', 'file', 'skills', 'session_search', 'delegate_task', 'memory', 'vision', 'tts', 'clarify'];
|
|
461
|
-
|
|
462
|
-
// Windows: hermes commands use shell redirect
|
|
463
455
|
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
464
456
|
for (const t of toolsets) {
|
|
465
457
|
run(`hermes tools enable ${t} ${noop}`, { silent: true });
|
|
@@ -469,41 +461,61 @@ async function enableToolsets(quick) {
|
|
|
469
461
|
s.ok();
|
|
470
462
|
}
|
|
471
463
|
|
|
464
|
+
// ——— FIXED: dashboard launches as a detached process that survives after hermes-launch exits ———
|
|
465
|
+
|
|
472
466
|
async function startDashboard(quick) {
|
|
473
467
|
const s = step('Starting web dashboard');
|
|
474
468
|
const port = '9119';
|
|
475
|
-
|
|
476
469
|
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
470
|
+
const dashUrl = `http://localhost:${port}`;
|
|
477
471
|
|
|
472
|
+
// Check if already running
|
|
478
473
|
const statusCheck = run(`hermes dashboard --status ${noop}`, { silent: true });
|
|
479
474
|
if (statusCheck.ok && statusCheck.out.length > 0 && !statusCheck.out.includes('No running')) {
|
|
480
475
|
s.skip('already running');
|
|
476
|
+
if (!quick) openBrowser(dashUrl);
|
|
481
477
|
return { port, alreadyRunning: true };
|
|
482
478
|
}
|
|
483
479
|
|
|
484
480
|
try {
|
|
481
|
+
// Spawn detached — survives after this script exits
|
|
485
482
|
const proc = spawn(
|
|
486
483
|
'hermes',
|
|
487
|
-
['dashboard', '--port', port, '--tui',
|
|
484
|
+
['dashboard', '--port', port, '--tui', '--no-open', '--skip-build'],
|
|
488
485
|
{
|
|
489
|
-
stdio: '
|
|
490
|
-
detached:
|
|
486
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
487
|
+
detached: true,
|
|
491
488
|
shell: IS_WIN,
|
|
492
489
|
env: { ...process.env },
|
|
493
490
|
}
|
|
494
491
|
);
|
|
495
492
|
|
|
496
|
-
|
|
493
|
+
// Release the child — it lives on its own
|
|
494
|
+
proc.on('error', () => {});
|
|
495
|
+
if (proc.unref) proc.unref();
|
|
496
|
+
|
|
497
|
+
// Give it time to boot
|
|
498
|
+
await spinner(4000);
|
|
499
|
+
|
|
500
|
+
// Verify it's actually responding
|
|
501
|
+
const curlBin = IS_WIN
|
|
502
|
+
? 'curl.exe -s -o nul -w "%{http_code}"'
|
|
503
|
+
: 'curl -s -o /dev/null -w "%{http_code}"';
|
|
504
|
+
const verify = run(`${curlBin} ${dashUrl}/ 2>/dev/null || echo 0`, { silent: true });
|
|
497
505
|
|
|
498
|
-
// Verify — use curl or curl.exe
|
|
499
|
-
const curlBin = IS_WIN ? 'curl.exe -s -o nul -w "%{http_code}"' : 'curl -s -o /dev/null -w "%{http_code}"';
|
|
500
|
-
const verify = run(`${curlBin} http://127.0.0.1:${port}/ 2>/dev/null || echo 0`, { silent: true });
|
|
501
506
|
if (verify.out === '0') {
|
|
502
|
-
s.skip('
|
|
507
|
+
s.skip('started in background (may take a few seconds)');
|
|
503
508
|
} else {
|
|
504
509
|
s.ok();
|
|
505
510
|
}
|
|
506
511
|
|
|
512
|
+
// Open browser (unless --quick)
|
|
513
|
+
if (!quick) {
|
|
514
|
+
process.stdout.write(` ${CYAN}→${RESET} Opening web dashboard in browser ... `);
|
|
515
|
+
openBrowser(dashUrl);
|
|
516
|
+
process.stdout.write(`${GREEN}done${RESET}\n`);
|
|
517
|
+
}
|
|
518
|
+
|
|
507
519
|
return { port, alreadyRunning: false };
|
|
508
520
|
} catch (e) {
|
|
509
521
|
s.fail(e.message);
|
|
@@ -554,12 +566,12 @@ async function main() {
|
|
|
554
566
|
|
|
555
567
|
${BOLD}What it does:${RESET}
|
|
556
568
|
1. Check prerequisites (Python, curl, git)
|
|
557
|
-
2. Install missing dependencies automatically
|
|
569
|
+
2. Install missing dependencies automatically (Windows: winget/choco)
|
|
558
570
|
3. Install Hermes Agent (if not present)
|
|
559
571
|
4. Run setup wizard to configure model/tools
|
|
560
|
-
5. Enable useful toolsets
|
|
572
|
+
5. Enable useful toolsets (web, terminal, file, skills, memory, ...)
|
|
561
573
|
6. Start the web dashboard on port 9119
|
|
562
|
-
7.
|
|
574
|
+
7. Open browser, print next steps
|
|
563
575
|
|
|
564
576
|
${BOLD}After launch:${RESET}
|
|
565
577
|
hermes start chatting
|
|
@@ -567,8 +579,8 @@ async function main() {
|
|
|
567
579
|
hermes model change AI provider
|
|
568
580
|
|
|
569
581
|
${BOLD}Windows notes:${RESET}
|
|
570
|
-
- Python
|
|
571
|
-
-
|
|
582
|
+
- Python auto-installed via winget if missing
|
|
583
|
+
- Web dashboard opens in browser
|
|
572
584
|
- For full functionality, WSL2 is recommended:
|
|
573
585
|
wsl --install -d Ubuntu
|
|
574
586
|
Then run: npx hermes-launch inside WSL
|
|
@@ -583,7 +595,7 @@ async function main() {
|
|
|
583
595
|
}
|
|
584
596
|
|
|
585
597
|
if (flags.dashboard) {
|
|
586
|
-
const result = await startDashboard(
|
|
598
|
+
const result = await startDashboard(false);
|
|
587
599
|
if (result.error) process.exit(1);
|
|
588
600
|
printSummary({ port: result.port, messaging: {} });
|
|
589
601
|
process.exit(0);
|
|
@@ -613,7 +625,7 @@ async function main() {
|
|
|
613
625
|
// 5. Enable tools
|
|
614
626
|
await enableToolsets(flags.quick);
|
|
615
627
|
|
|
616
|
-
// 6. Start dashboard
|
|
628
|
+
// 6. Start dashboard (detached — survives after script exits)
|
|
617
629
|
const dashResult = await startDashboard(flags.quick);
|
|
618
630
|
|
|
619
631
|
// 7. Check gateway
|