hermes-launch 1.0.0 → 1.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/bin/hermes-launch.js +342 -149
  2. package/package.json +1 -1
@@ -10,34 +10,42 @@
10
10
  */
11
11
 
12
12
  import { execSync, spawn } from 'child_process';
13
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
14
- import { homedir, platform, hostname } from 'os';
13
+ import { readFileSync, existsSync } from 'fs';
14
+ import { homedir, platform as osPlatform } from 'os';
15
15
  import { resolve } from 'path';
16
16
  import { createInterface } from 'readline';
17
17
 
18
- const HERMES_HOME = resolve(homedir(), '.hermes');
18
+ const PLATFORM = osPlatform(); // win32, darwin, linux
19
+ const IS_WIN = PLATFORM === 'win32';
20
+ const IS_MAC = PLATFORM === 'darwin';
21
+ const IS_LINUX = PLATFORM === 'linux';
22
+
23
+ const HERMES_HOME = IS_WIN
24
+ ? resolve(process.env.USERPROFILE || homedir(), '.hermes')
25
+ : resolve(homedir(), '.hermes');
19
26
  const CONFIG_PATH = resolve(HERMES_HOME, 'config.yaml');
20
27
  const INSTALL_URL = 'https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh';
21
28
 
22
- const BOLD = '\x1b[1m';
23
- const DIM = '\x1b[2m';
24
- const GREEN = '\x1b[32m';
25
- const YELLOW = '\x1b[33m';
26
- const RED = '\x1b[31m';
27
- const CYAN = '\x1b[36m';
28
- const RESET = '\x1b[0m';
29
- const CLEAR_LINE = '\x1b[K';
29
+ // Windows: node strips ansi codes in cmd/powershell by default, but keep for
30
+ // terminals that support them (Windows Terminal, VS Code terminal, etc.)
31
+ const USE_COLOR = !IS_WIN || (process.env.TERM_PROGRAM || '').includes('vscode') || process.env.WT_SESSION;
32
+ const BOLD = USE_COLOR ? '\x1b[1m' : '';
33
+ const DIM = USE_COLOR ? '\x1b[2m' : '';
34
+ const GREEN = USE_COLOR ? '\x1b[32m' : '';
35
+ const YELLOW = USE_COLOR ? '\x1b[33m' : '';
36
+ const RED = USE_COLOR ? '\x1b[31m' : '';
37
+ const CYAN = USE_COLOR ? '\x1b[36m' : '';
38
+ const RESET = USE_COLOR ? '\x1b[0m' : '';
30
39
 
31
40
  // ——— helpers ———
32
41
 
33
42
  function printBanner() {
34
- const banner = `
35
- ╔════════════════════════════════════════╗
36
- ${CYAN}🐚 Hermes Launch ${RESET}
37
- ${DIM}zero-to-AI-agent in seconds${RESET}
38
- ╚════════════════════════════════════════╝
39
- `;
40
- console.log(banner);
43
+ console.log(`
44
+ ${CYAN}╔════════════════════════════════════════╗${RESET}
45
+ ${CYAN}║${RESET} 🐚 Hermes Launch ${CYAN}║${RESET}
46
+ ${CYAN}║${RESET} ${DIM}zero-to-AI-agent in seconds${RESET} ${CYAN}║${RESET}
47
+ ${CYAN}╚════════════════════════════════════════╝${RESET}
48
+ `);
41
49
  }
42
50
 
43
51
  function ask(query) {
@@ -46,7 +54,12 @@ function ask(query) {
46
54
  }
47
55
 
48
56
  function run(cmd, opts = {}) {
49
- const defaults = { stdio: opts.silent ? 'pipe' : 'inherit', timeout: 120_000, ...opts };
57
+ const defaults = {
58
+ stdio: opts.silent ? 'pipe' : 'inherit',
59
+ timeout: 120_000,
60
+ shell: IS_WIN ? true : false, // Windows needs shell for PATH resolution
61
+ ...opts,
62
+ };
50
63
  try {
51
64
  const out = execSync(cmd, defaults);
52
65
  return { ok: true, out: out?.toString()?.trim() || '' };
@@ -55,6 +68,11 @@ function run(cmd, opts = {}) {
55
68
  }
56
69
  }
57
70
 
71
+ function runPwsh(cmd, opts = {}) {
72
+ // Run a PowerShell command — used on Windows for winget, etc.
73
+ return run(`powershell -NoProfile -Command "${cmd}"`, opts);
74
+ }
75
+
58
76
  function spinner(ms) {
59
77
  return new Promise(resolve => setTimeout(resolve, ms));
60
78
  }
@@ -80,7 +98,6 @@ function printSummary(info) {
80
98
  console.log(` ${CYAN}hermes model${RESET} change model/provider`);
81
99
  console.log(` ${CYAN}hermes gateway setup${RESET} connect Telegram/Discord`);
82
100
  console.log(` ${CYAN}hermes tools list${RESET} see available tools`);
83
- console.log(` ${CYAN}hermes cron${RESET} schedule recurring tasks`);
84
101
  console.log(` ${CYAN}hermes doctor${RESET} health check\n`);
85
102
  console.log(` ${BOLD}Docs${RESET}`);
86
103
  console.log(` ${CYAN}https://hermes-agent.nousresearch.com/docs${RESET}\n`);
@@ -91,66 +108,226 @@ function printSummary(info) {
91
108
  }
92
109
  console.log();
93
110
  }
111
+ if (info.windowsNote) {
112
+ console.log(` ${YELLOW}${info.windowsNote}${RESET}\n`);
113
+ }
94
114
  }
95
115
 
96
- function printMiniBanner() {
97
- console.log(`
98
- ${CYAN} 🐚 Hermes Launch${RESET} ${DIM}— zero-to-agent${RESET}
99
- `);
116
+ // ——— platform detection ———
117
+
118
+ function detectPackageManager() {
119
+ if (IS_WIN) {
120
+ // winget ships with Windows 11 and recent Win10
121
+ if (run('winget --version', { silent: true }).ok) return 'winget';
122
+ if (run('choco --version', { silent: true }).ok) return 'choco';
123
+ if (run('scoop --version', { silent: true }).ok) return 'scoop';
124
+ return null;
125
+ }
126
+ if (IS_MAC) {
127
+ if (run('brew --version', { silent: true }).ok) return 'brew';
128
+ return null;
129
+ }
130
+ // Linux
131
+ if (run('apt-get --version', { silent: true }).ok) return 'apt';
132
+ if (run('dnf --version', { silent: true }).ok) return 'dnf';
133
+ if (run('pacman --version', { silent: true }).ok) return 'pacman';
134
+ return null;
100
135
  }
101
136
 
102
- // ——— core steps ———
137
+ function winHasWsl() {
138
+ return run('wsl --version', { silent: true }).ok || run('wsl -l', { silent: true }).ok;
139
+ }
103
140
 
104
- async function checkPrereqs() {
105
- const s = step('Checking prerequisites');
106
- const checks = {
107
- node: run('node --version', { silent: true }).ok,
108
- python3: run('python3 --version', { silent: true }).ok,
109
- curl: run('curl --version', { silent: true }).ok,
110
- git: run('git --version', { silent: true }).ok,
111
- };
141
+ // ——— prerequisite checks + auto-install ———
112
142
 
113
- const missing = Object.entries(checks).filter(([, ok]) => !ok).map(([name]) => name);
114
- if (missing.length) {
115
- s.fail(`Missing: ${missing.join(', ')}`);
116
- console.log(` Install missing tools and try again.`);
117
- process.exit(1);
118
- }
143
+ async function checkPython() {
144
+ // Windows uses "python" and "py", not "python3"
145
+ const pyCandidates = IS_WIN
146
+ ? ['python', 'py -3', 'python3']
147
+ : ['python3.14', 'python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3'];
119
148
 
120
- // Check Python version >= 3.10 — try multiple binaries
121
- const pyCandidates = ['python3.11', 'python3.12', 'python3.13', 'python3.14', 'python3.10', 'python3'];
122
- let pyVer = '';
123
- let pyBin = 'python3';
124
149
  for (const bin of pyCandidates) {
125
150
  const r = run(`${bin} --version`, { silent: true });
126
151
  if (r.ok) {
127
- pyVer = r.out;
128
- pyBin = bin;
129
- break;
152
+ const match = r.out.match(/(\d+)\.(\d+)/);
153
+ if (match) {
154
+ const major = parseInt(match[1]), minor = parseInt(match[2]);
155
+ if (major >= 3 && minor >= 10) {
156
+ return { ok: true, bin, version: `${major}.${minor}` };
157
+ }
158
+ }
159
+ }
160
+ }
161
+ return { ok: false, bin: null, version: null };
162
+ }
163
+
164
+ async function installPython(pkgMan) {
165
+ const s = step('Installing Python 3.12+');
166
+ try {
167
+ if (IS_WIN) {
168
+ if (pkgMan === 'winget') {
169
+ const r = runPwsh('winget install --accept-source-agreements --accept-package-agreements Python.Python.3.12', { timeout: 180_000 });
170
+ if (!r.ok) throw new Error(r.out);
171
+ } else if (pkgMan === 'choco') {
172
+ run('choco install python --yes', { timeout: 180_000 });
173
+ } else if (pkgMan === 'scoop') {
174
+ run('scoop install python', { timeout: 180_000 });
175
+ } else {
176
+ s.fail('No package manager found. Install Python from https://python.org');
177
+ return false;
178
+ }
179
+ // Refresh PATH so we can find python immediately
180
+ process.env.PATH = `${process.env.USERPROFILE}\\AppData\\Local\\Programs\\Python\\Python312\\;${process.env.USERPROFILE}\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\;${process.env.PATH}`;
181
+ } else if (IS_MAC) {
182
+ run('brew install python@3.12', { timeout: 180_000 });
183
+ // brew python3.12 is often keg-only — symlink or use full path
184
+ run('brew link --overwrite python@3.12', { silent: true, timeout: 30_000 });
185
+ } else {
186
+ // Linux
187
+ if (pkgMan === 'apt') {
188
+ run('apt-get update -qq && apt-get install -y python3 python3-pip python3-venv', { timeout: 180_000 });
189
+ } else if (pkgMan === 'dnf') {
190
+ run('dnf install -y python3 python3-pip', { timeout: 180_000 });
191
+ } else if (pkgMan === 'pacman') {
192
+ run('pacman -S --noconfirm python python-pip', { timeout: 180_000 });
193
+ }
194
+ }
195
+ s.ok();
196
+ return true;
197
+ } catch (e) {
198
+ s.fail(e.message);
199
+ return false;
200
+ }
201
+ }
202
+
203
+ async function checkCurl() {
204
+ const candidates = IS_WIN ? ['curl.exe', 'curl'] : ['curl'];
205
+ for (const bin of candidates) {
206
+ if (run(`${bin} --version`, { silent: true }).ok) return { ok: true, bin };
207
+ }
208
+ return { ok: false, bin: null };
209
+ }
210
+
211
+ async function installCurl(pkgMan) {
212
+ const s = step('Installing curl');
213
+ try {
214
+ if (IS_WIN) {
215
+ // Windows 10+ has curl.exe built-in. If missing, use winget.
216
+ if (pkgMan === 'winget') {
217
+ runPwsh('winget install --accept-source-agreements cURL.cURL', { timeout: 120_000 });
218
+ } else if (pkgMan === 'choco') {
219
+ run('choco install curl --yes', { timeout: 120_000 });
220
+ }
221
+ } else {
222
+ // macOS/Linux — curl should be present but just in case
223
+ run('command -v curl || (apt-get install -y curl 2>/dev/null || yum install -y curl 2>/dev/null)', { timeout: 120_000 });
130
224
  }
225
+ s.ok();
226
+ return true;
227
+ } catch (e) {
228
+ s.fail(e.message);
229
+ return false;
131
230
  }
231
+ }
232
+
233
+ async function checkGit() {
234
+ const candidates = IS_WIN ? ['git.exe', 'git'] : ['git'];
235
+ for (const bin of candidates) {
236
+ if (run(`${bin} --version`, { silent: true }).ok) return { ok: true, bin };
237
+ }
238
+ return { ok: false, bin: null };
239
+ }
132
240
 
133
- const match = pyVer.match(/(\d+)\.(\d+)/);
134
- if (match) {
135
- const major = parseInt(match[1]), minor = parseInt(match[2]);
136
- if (major < 3 || (major === 3 && minor < 10)) {
137
- s.fail(`Python 3.10+ required, got ${major}.${minor} (${pyBin})`);
241
+ async function installGit(pkgMan) {
242
+ const s = step('Installing git');
243
+ try {
244
+ if (IS_WIN) {
245
+ if (pkgMan === 'winget') {
246
+ runPwsh('winget install --accept-source-agreements --accept-package-agreements Git.Git', { timeout: 180_000 });
247
+ } else if (pkgMan === 'choco') {
248
+ run('choco install git --yes', { timeout: 180_000 });
249
+ } else if (pkgMan === 'scoop') {
250
+ run('scoop install git', { timeout: 180_000 });
251
+ }
252
+ } else {
253
+ run('command -v git || (apt-get install -y git 2>/dev/null || brew install git 2>/dev/null)', { timeout: 120_000 });
254
+ }
255
+ s.ok();
256
+ return true;
257
+ } catch (e) {
258
+ s.fail(e.message);
259
+ return false;
260
+ }
261
+ }
262
+
263
+ async function checkPrereqs() {
264
+ const s = step('Checking prerequisites');
265
+
266
+ const pkgMan = detectPackageManager();
267
+
268
+ // 1. Python
269
+ const py = await checkPython();
270
+ if (!py.ok) {
271
+ if (pkgMan) {
272
+ process.stdout.write(`\n`);
273
+ const installed = await installPython(pkgMan);
274
+ if (!installed) {
275
+ process.exit(1);
276
+ }
277
+ } else {
278
+ s.fail('Python 3.10+ is required but not found');
279
+ if (IS_WIN) {
280
+ console.log(` ${YELLOW}Install: https://python.org or run: winget install Python.Python.3.12${RESET}`);
281
+ } else if (IS_MAC) {
282
+ console.log(` ${YELLOW}Install: brew install python@3.12${RESET}`);
283
+ } else {
284
+ console.log(` ${YELLOW}Install: sudo apt install python3 python3-pip python3-venv${RESET}`);
285
+ }
286
+ process.exit(1);
287
+ }
288
+ }
289
+
290
+ // 2. curl
291
+ const curlCheck = await checkCurl();
292
+ if (!curlCheck.ok) {
293
+ if (pkgMan) {
294
+ await installCurl(pkgMan);
295
+ } else {
296
+ s.fail('curl is required');
297
+ process.exit(1);
298
+ }
299
+ }
300
+
301
+ // 3. git
302
+ const gitCheck = await checkGit();
303
+ if (!gitCheck.ok) {
304
+ if (pkgMan) {
305
+ await installGit(pkgMan);
306
+ } else {
307
+ s.fail('git is required');
138
308
  process.exit(1);
139
309
  }
140
310
  }
141
311
 
142
312
  s.ok();
143
- checks.pythonBin = pyBin;
144
- return checks;
313
+ return { ...py, pkgMan, curlBin: curlCheck.bin || '', gitBin: gitCheck.bin || '' };
145
314
  }
146
315
 
147
- async function installHermes(quick) {
316
+ // ——— Hermes install ———
317
+
318
+ async function installHermes(quick, platformInfo) {
148
319
  const s = step('Checking Hermes installation');
149
- const hasHermes = run('hermes --version', { silent: true }).ok;
150
320
 
151
- if (hasHermes) {
152
- const ver = run('hermes --version', { silent: true }).out;
153
- s.skip(`v${ver} already installed`);
321
+ // On Windows, try both "hermes" and "hermes.exe"
322
+ const hermesCheck = IS_WIN
323
+ ? (run('hermes --version', { silent: true }).ok || run('hermes.exe --version', { silent: true }).ok)
324
+ : run('hermes --version', { silent: true }).ok;
325
+
326
+ if (hermesCheck) {
327
+ const ver = run('hermes --version', { silent: true }).ok
328
+ ? run('hermes --version', { silent: true }).out
329
+ : 'installed';
330
+ s.skip(`${ver} already installed`);
154
331
  return true;
155
332
  }
156
333
 
@@ -158,27 +335,71 @@ async function installHermes(quick) {
158
335
  console.log();
159
336
  const ans = await ask(` Hermes not found. Install it now? [Y/n] `);
160
337
  if (ans.toLowerCase() === 'n') {
161
- console.log(` ${YELLOW}Aborted. Install manually: curl -fsSL ${INSTALL_URL} | bash${RESET}`);
338
+ console.log(` ${YELLOW}Aborted.${RESET}`);
162
339
  process.exit(0);
163
340
  }
164
341
  }
165
342
 
166
343
  const si = step('Downloading and installing Hermes');
167
- try {
168
- const cmd = `curl -fsSL ${INSTALL_URL} | bash`;
169
- const result = run(cmd, { timeout: 180_000, silent: false });
170
- if (!result.ok) {
171
- si.fail(result.out);
344
+
345
+ if (IS_WIN) {
346
+ // Windows: install via pip (the bash script won't work without WSL)
347
+ try {
348
+ // Ensure pip is available
349
+ const pyBin = platformInfo.bin || 'python';
350
+ run(`${pyBin} -m pip install --upgrade pip`, { silent: true, timeout: 60_000 });
351
+
352
+ const r = run(`${pyBin} -m pip install hermes-agent`, { timeout: 180_000 });
353
+ if (!r.ok) throw new Error(r.out);
354
+
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
+ const pathsToAdd = [
358
+ resolve(process.env.APPDATA || '', 'Python', 'Scripts'),
359
+ resolve(homedir(), '.local', 'bin'),
360
+ ];
361
+ for (const p of pathsToAdd) {
362
+ if (!process.env.PATH?.includes(p)) {
363
+ process.env.PATH = `${p};${process.env.PATH}`;
364
+ }
365
+ }
366
+
367
+ si.ok();
368
+ return true;
369
+ } catch (e) {
370
+ // If pip fails, suggest WSL which is the recommended path
371
+ si.fail(e.message);
372
+ if (winHasWsl()) {
373
+ console.log(`\n ${YELLOW}Pip install failed. Try installing inside WSL instead:${RESET}`);
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
+ }
381
+ process.exit(1);
382
+ }
383
+ } else {
384
+ // macOS / Linux
385
+ try {
386
+ const cmd = `curl -fsSL ${INSTALL_URL} | bash`;
387
+ const result = run(cmd, { timeout: 180_000, silent: false });
388
+ if (!result.ok) {
389
+ si.fail(result.out);
390
+ process.exit(1);
391
+ }
392
+ si.ok();
393
+ return true;
394
+ } catch (e) {
395
+ si.fail(e.message);
172
396
  process.exit(1);
173
397
  }
174
- si.ok();
175
- return true;
176
- } catch (e) {
177
- si.fail(e.message);
178
- process.exit(1);
179
398
  }
180
399
  }
181
400
 
401
+ // ——— config ———
402
+
182
403
  async function checkConfig(quick) {
183
404
  const s = step('Checking Hermes configuration');
184
405
 
@@ -204,17 +425,15 @@ async function checkConfig(quick) {
204
425
  async function runSetup(quick) {
205
426
  if (quick) {
206
427
  const s = step('Running headless setup');
207
- // For quick mode, just enable the gateway and set up with defaults
208
428
  const setCmds = [
209
- 'hermes tools enable web terminal file skills session_search delegate_task memory clarify vision 2>/dev/null || true',
210
- `hermes config set display.streaming.enabled true 2>/dev/null || true`,
429
+ 'hermes tools enable web terminal file skills session_search delegate_task memory clarify vision 2>/dev/null || ver>nul',
430
+ `hermes config set display.streaming.enabled true 2>/dev/null || cmd /c ver>nul`,
211
431
  ];
212
432
  for (const cmd of setCmds) run(cmd, { silent: true });
213
433
  s.ok();
214
434
  return;
215
435
  }
216
436
 
217
- // Interactive: guide the user through setup
218
437
  console.log(`\n ${BOLD}Let's configure your Hermes Agent.${RESET}\n`);
219
438
  console.log(` ${DIM}(You can skip any step and configure later with \`hermes setup\`)${RESET}\n`);
220
439
 
@@ -223,18 +442,14 @@ async function runSetup(quick) {
223
442
  const s = step('Running setup wizard');
224
443
  try {
225
444
  const result = run('hermes setup', { stdio: 'inherit', timeout: 300_000 });
226
- if (!result.ok) {
227
- s.fail(result.out);
228
- } else {
229
- s.ok();
230
- }
445
+ if (!result.ok) s.fail(result.out);
446
+ else s.ok();
231
447
  } catch (e) {
232
448
  s.fail(e.message);
233
449
  }
234
450
  } else {
235
- // Minimal quick config
236
451
  const s = step('Applying minimal configuration');
237
- run('hermes tools enable web terminal file skills session_search delegate_task memory 2>/dev/null || true', { silent: true });
452
+ run('hermes tools enable web terminal file skills session_search delegate_task memory 2>/dev/null || ver>nul', { silent: true });
238
453
  s.skip('minimal config applied');
239
454
  }
240
455
  }
@@ -242,43 +457,25 @@ async function runSetup(quick) {
242
457
  async function enableToolsets(quick) {
243
458
  const s = step('Enabling tool sets');
244
459
 
245
- // Core toolsets that make Hermes useful
246
- const toolsets = [
247
- 'web', // web search + content extraction
248
- 'terminal', // shell commands
249
- 'file', // file read/write/search
250
- 'skills', // skill management
251
- 'session_search', // cross-session recall
252
- 'delegate_task', // spawn subagents
253
- 'memory', // cross-session memory
254
- 'vision', // image analysis
255
- 'tts', // text-to-speech
256
- 'clarify', // ask questions
257
- ];
258
-
259
- let ok = true;
260
- for (const t of toolsets) {
261
- const r = run(`hermes tools enable ${t}`, { silent: true });
262
- if (!r.ok) ok = false;
263
- }
460
+ const toolsets = ['web', 'terminal', 'file', 'skills', 'session_search', 'delegate_task', 'memory', 'vision', 'tts', 'clarify'];
264
461
 
265
- if (ok) {
266
- s.ok();
267
- } else {
268
- // Some may have failed if already enabled — that's fine
269
- s.ok();
462
+ // Windows: hermes commands use shell redirect
463
+ const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
464
+ for (const t of toolsets) {
465
+ run(`hermes tools enable ${t} ${noop}`, { silent: true });
270
466
  }
271
467
 
272
- // Enable gateway streaming
273
- run('hermes config set display.streaming.enabled true 2>/dev/null || true', { silent: true });
468
+ run(`hermes config set display.streaming.enabled true ${noop}`, { silent: true });
469
+ s.ok();
274
470
  }
275
471
 
276
472
  async function startDashboard(quick) {
277
473
  const s = step('Starting web dashboard');
278
- const port = quick ? '9119' : '9119';
474
+ const port = '9119';
475
+
476
+ const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
279
477
 
280
- // Check if dashboard is already running
281
- const statusCheck = run('hermes dashboard --status', { silent: true });
478
+ const statusCheck = run(`hermes dashboard --status ${noop}`, { silent: true });
282
479
  if (statusCheck.ok && statusCheck.out.length > 0 && !statusCheck.out.includes('No running')) {
283
480
  s.skip('already running');
284
481
  return { port, alreadyRunning: true };
@@ -291,15 +488,16 @@ async function startDashboard(quick) {
291
488
  {
292
489
  stdio: 'inherit',
293
490
  detached: false,
491
+ shell: IS_WIN,
294
492
  env: { ...process.env },
295
493
  }
296
494
  );
297
495
 
298
- // Give it a moment to start
299
496
  await spinner(3000);
300
497
 
301
- // Verify it's up
302
- const verify = run(`curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${port}/ 2>/dev/null || echo "0"`, { silent: true });
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 });
303
501
  if (verify.out === '0') {
304
502
  s.skip('dashboard launched in background');
305
503
  } else {
@@ -315,7 +513,8 @@ async function startDashboard(quick) {
315
513
 
316
514
  async function checkGatewayStatus() {
317
515
  const s = step('Checking messaging gateway');
318
- const result = run('hermes gateway status', { silent: true });
516
+ const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
517
+ const result = run(`hermes gateway status ${noop}`, { silent: true });
319
518
  if (result.ok) {
320
519
  const out = result.out;
321
520
  const hasTelegram = out.includes('telegram') && (out.includes('running') || out.includes('connected'));
@@ -332,27 +531,6 @@ async function checkGatewayStatus() {
332
531
  return [];
333
532
  }
334
533
 
335
- async function showQuickStart() {
336
- console.log(`\n ${BOLD}${GREEN}Hermes Agent is ready to use!${RESET}\n`);
337
-
338
- console.log(` ${BOLD}Start a conversation:${RESET}`);
339
- console.log(` ${CYAN}hermes${RESET} interactive chat`);
340
- console.log(` ${CYAN}hermes -q "summarize my repo"${RESET} one-shot query\n`);
341
-
342
- console.log(` ${BOLD}First things to try:${RESET}`);
343
- console.log(` • ${CYAN}hermes model${RESET} — pick your LLM provider`);
344
- console.log(` • ${CYAN}hermes gateway setup${RESET} — connect Telegram/Discord`);
345
- console.log(` • ${CYAN}hermes dashboard${RESET} — open the web UI`);
346
- console.log(` • ${CYAN}hermes skills browse${RESET} — discover skills\n`);
347
-
348
- console.log(` ${BOLD}Pro tips:${RESET}`);
349
- console.log(` • ${DIM}Your config lives at ~/.hermes/config.yaml${RESET}`);
350
- console.log(` • ${DIM}API keys go in ~/.hermes/.env${RESET}`);
351
- console.log(` • ${DIM}Type /help in a session for all slash commands${RESET}`);
352
- console.log(` • ${DIM}Run \`hermes doctor\` if something isn't working${RESET}`);
353
- console.log();
354
- }
355
-
356
534
  // ——— main ———
357
535
 
358
536
  async function main() {
@@ -376,16 +554,24 @@ async function main() {
376
554
 
377
555
  ${BOLD}What it does:${RESET}
378
556
  1. Check prerequisites (Python, curl, git)
379
- 2. Install Hermes Agent (if not present)
380
- 3. Run setup wizard to configure model/tools
381
- 4. Enable useful toolsets
382
- 5. Start the web dashboard on port 9119
383
- 6. Print next steps
557
+ 2. Install missing dependencies automatically
558
+ 3. Install Hermes Agent (if not present)
559
+ 4. Run setup wizard to configure model/tools
560
+ 5. Enable useful toolsets
561
+ 6. Start the web dashboard on port 9119
562
+ 7. Print next steps
384
563
 
385
564
  ${BOLD}After launch:${RESET}
386
565
  hermes start chatting
387
566
  hermes gateway setup add Telegram/Discord
388
567
  hermes model change AI provider
568
+
569
+ ${BOLD}Windows notes:${RESET}
570
+ - Python is auto-installed via winget (or choco/scoop) if missing
571
+ - Recommended: use Windows Terminal for best experience
572
+ - For full functionality, WSL2 is recommended:
573
+ wsl --install -d Ubuntu
574
+ Then run: npx hermes-launch inside WSL
389
575
  `);
390
576
  process.exit(0);
391
577
  }
@@ -397,11 +583,8 @@ async function main() {
397
583
  }
398
584
 
399
585
  if (flags.dashboard) {
400
- printMiniBanner();
401
586
  const result = await startDashboard(true);
402
- if (result.error) {
403
- process.exit(1);
404
- }
587
+ if (result.error) process.exit(1);
405
588
  printSummary({ port: result.port, messaging: {} });
406
589
  process.exit(0);
407
590
  }
@@ -409,11 +592,15 @@ async function main() {
409
592
  // ——— full flow ———
410
593
  printBanner();
411
594
 
412
- // 1. Prerequisites
413
- await checkPrereqs();
595
+ if (IS_WIN) {
596
+ console.log(` ${DIM}Windows detected — auto-installing missing deps${RESET}\n`);
597
+ }
598
+
599
+ // 1. Prerequisites (auto-installs Python, git, curl if missing)
600
+ const platformInfo = await checkPrereqs();
414
601
 
415
- // 2. Install
416
- await installHermes(flags.quick);
602
+ // 2. Install Hermes
603
+ await installHermes(flags.quick, platformInfo);
417
604
 
418
605
  // 3. Check config
419
606
  const needsSetup = await checkConfig(flags.quick);
@@ -433,12 +620,18 @@ async function main() {
433
620
  const platforms = await checkGatewayStatus();
434
621
 
435
622
  // 8. Show summary
436
- printSummary({
623
+ const summary = {
437
624
  port: dashResult.port,
438
625
  messaging: platforms.length > 0
439
626
  ? Object.fromEntries(platforms.map(p => [p, `${GREEN}connected${RESET}`]))
440
627
  : { none: `${YELLOW}not configured${RESET} (run \`hermes gateway setup\`)` },
441
- });
628
+ };
629
+
630
+ if (IS_WIN) {
631
+ summary.windowsNote = 'On Windows, some tools work best in WSL2. Run `wsl --install -d Ubuntu` for the full experience.';
632
+ }
633
+
634
+ printSummary(summary);
442
635
  }
443
636
 
444
637
  main().catch(e => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermes-launch",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "One command to launch Hermes Agent — zero-to-AI-agent in seconds",
5
5
  "bin": {
6
6
  "hermes-launch": "./bin/hermes-launch.js"