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.
- package/bin/hermes-launch.js +342 -149
- package/package.json +1 -1
package/bin/hermes-launch.js
CHANGED
|
@@ -10,34 +10,42 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { execSync, spawn } from 'child_process';
|
|
13
|
-
import { readFileSync, existsSync
|
|
14
|
-
import { homedir, platform
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 = {
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
137
|
+
function winHasWsl() {
|
|
138
|
+
return run('wsl --version', { silent: true }).ok || run('wsl -l', { silent: true }).ok;
|
|
139
|
+
}
|
|
103
140
|
|
|
104
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
|
|
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
|
-
|
|
144
|
-
return checks;
|
|
313
|
+
return { ...py, pkgMan, curlBin: curlCheck.bin || '', gitBin: gitCheck.bin || '' };
|
|
145
314
|
}
|
|
146
315
|
|
|
147
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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 ||
|
|
210
|
-
`hermes config set display.streaming.enabled true 2>/dev/null ||
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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 =
|
|
474
|
+
const port = '9119';
|
|
475
|
+
|
|
476
|
+
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
279
477
|
|
|
280
|
-
|
|
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
|
|
302
|
-
const
|
|
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
|
|
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
|
|
380
|
-
3.
|
|
381
|
-
4.
|
|
382
|
-
5.
|
|
383
|
-
6.
|
|
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
|
-
|
|
413
|
-
|
|
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
|
-
|
|
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 => {
|