hermes-launch 1.0.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 +371 -166
- package/package.json +1 -1
package/bin/hermes-launch.js
CHANGED
|
@@ -10,34 +10,41 @@
|
|
|
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
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
29
|
+
// Windows Terminal / VS Code support ansi; raw cmd.exe doesn't
|
|
30
|
+
const USE_COLOR = !IS_WIN || (process.env.TERM_PROGRAM || '').includes('vscode') || process.env.WT_SESSION;
|
|
31
|
+
const BOLD = USE_COLOR ? '\x1b[1m' : '';
|
|
32
|
+
const DIM = USE_COLOR ? '\x1b[2m' : '';
|
|
33
|
+
const GREEN = USE_COLOR ? '\x1b[32m' : '';
|
|
34
|
+
const YELLOW = USE_COLOR ? '\x1b[33m' : '';
|
|
35
|
+
const RED = USE_COLOR ? '\x1b[31m' : '';
|
|
36
|
+
const CYAN = USE_COLOR ? '\x1b[36m' : '';
|
|
37
|
+
const RESET = USE_COLOR ? '\x1b[0m' : '';
|
|
30
38
|
|
|
31
39
|
// ——— helpers ———
|
|
32
40
|
|
|
33
41
|
function printBanner() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(banner);
|
|
42
|
+
console.log(`
|
|
43
|
+
${CYAN}╔════════════════════════════════════════╗${RESET}
|
|
44
|
+
${CYAN}║${RESET} 🐚 Hermes Launch ${CYAN}║${RESET}
|
|
45
|
+
${CYAN}║${RESET} ${DIM}zero-to-AI-agent in seconds${RESET} ${CYAN}║${RESET}
|
|
46
|
+
${CYAN}╚════════════════════════════════════════╝${RESET}
|
|
47
|
+
`);
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
function ask(query) {
|
|
@@ -46,7 +53,12 @@ function ask(query) {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
function run(cmd, opts = {}) {
|
|
49
|
-
const defaults = {
|
|
56
|
+
const defaults = {
|
|
57
|
+
stdio: opts.silent ? 'pipe' : 'inherit',
|
|
58
|
+
timeout: 120_000,
|
|
59
|
+
shell: IS_WIN ? true : false,
|
|
60
|
+
...opts,
|
|
61
|
+
};
|
|
50
62
|
try {
|
|
51
63
|
const out = execSync(cmd, defaults);
|
|
52
64
|
return { ok: true, out: out?.toString()?.trim() || '' };
|
|
@@ -55,6 +67,10 @@ function run(cmd, opts = {}) {
|
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
function runPwsh(cmd, opts = {}) {
|
|
71
|
+
return run(`powershell -NoProfile -Command "${cmd}"`, opts);
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
function spinner(ms) {
|
|
59
75
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
60
76
|
}
|
|
@@ -68,89 +84,259 @@ function step(msg) {
|
|
|
68
84
|
};
|
|
69
85
|
}
|
|
70
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
|
+
|
|
71
99
|
function printSummary(info) {
|
|
100
|
+
const dashUrl = `http://localhost:${info.port || 9119}`;
|
|
101
|
+
|
|
72
102
|
console.log(`\n${BOLD}${GREEN} ✅ Hermes Agent is live!${RESET}\n`);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
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}`);
|
|
76
112
|
console.log(` ${CYAN}hermes${RESET} interactive chat`);
|
|
77
113
|
console.log(` ${CYAN}hermes chat -q "..."${RESET} one-shot query`);
|
|
78
114
|
console.log(` ${CYAN}hermes --continue${RESET} resume last session\n`);
|
|
79
|
-
|
|
115
|
+
|
|
116
|
+
console.log(` ${BOLD}⚡ Key commands${RESET}`);
|
|
80
117
|
console.log(` ${CYAN}hermes model${RESET} change model/provider`);
|
|
81
118
|
console.log(` ${CYAN}hermes gateway setup${RESET} connect Telegram/Discord`);
|
|
82
119
|
console.log(` ${CYAN}hermes tools list${RESET} see available tools`);
|
|
83
|
-
console.log(` ${CYAN}hermes cron${RESET} schedule recurring tasks`);
|
|
84
120
|
console.log(` ${CYAN}hermes doctor${RESET} health check\n`);
|
|
85
|
-
|
|
121
|
+
|
|
122
|
+
console.log(` ${BOLD}📖 Docs${RESET}`);
|
|
86
123
|
console.log(` ${CYAN}https://hermes-agent.nousresearch.com/docs${RESET}\n`);
|
|
124
|
+
|
|
87
125
|
if (info.messaging) {
|
|
88
|
-
console.log(` ${BOLD}Messaging${RESET}`);
|
|
126
|
+
console.log(` ${BOLD}💬 Messaging${RESET}`);
|
|
89
127
|
for (const [platform, status] of Object.entries(info.messaging)) {
|
|
90
128
|
console.log(` ${platform}: ${status}`);
|
|
91
129
|
}
|
|
92
130
|
console.log();
|
|
93
131
|
}
|
|
132
|
+
if (info.windowsNote) {
|
|
133
|
+
console.log(` ${YELLOW} ${info.windowsNote}${RESET}\n`);
|
|
134
|
+
}
|
|
94
135
|
}
|
|
95
136
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
137
|
+
// ——— platform detection ———
|
|
138
|
+
|
|
139
|
+
function detectPackageManager() {
|
|
140
|
+
if (IS_WIN) {
|
|
141
|
+
if (run('winget --version', { silent: true }).ok) return 'winget';
|
|
142
|
+
if (run('choco --version', { silent: true }).ok) return 'choco';
|
|
143
|
+
if (run('scoop --version', { silent: true }).ok) return 'scoop';
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
if (IS_MAC) {
|
|
147
|
+
if (run('brew --version', { silent: true }).ok) return 'brew';
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
// Linux
|
|
151
|
+
if (run('apt-get --version', { silent: true }).ok) return 'apt';
|
|
152
|
+
if (run('dnf --version', { silent: true }).ok) return 'dnf';
|
|
153
|
+
if (run('pacman --version', { silent: true }).ok) return 'pacman';
|
|
154
|
+
return null;
|
|
100
155
|
}
|
|
101
156
|
|
|
102
|
-
// ———
|
|
157
|
+
// ——— prerequisite checks + auto-install ———
|
|
158
|
+
|
|
159
|
+
async function checkPython() {
|
|
160
|
+
const pyCandidates = IS_WIN
|
|
161
|
+
? ['python', 'py -3', 'python3']
|
|
162
|
+
: ['python3.14', 'python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3'];
|
|
163
|
+
|
|
164
|
+
for (const bin of pyCandidates) {
|
|
165
|
+
const r = run(`${bin} --version`, { silent: true });
|
|
166
|
+
if (r.ok) {
|
|
167
|
+
const match = r.out.match(/(\d+)\.(\d+)/);
|
|
168
|
+
if (match) {
|
|
169
|
+
const major = parseInt(match[1]), minor = parseInt(match[2]);
|
|
170
|
+
if (major >= 3 && minor >= 10) {
|
|
171
|
+
return { ok: true, bin, version: `${major}.${minor}` };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { ok: false, bin: null, version: null };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function installPython(pkgMan) {
|
|
180
|
+
const s = step('Installing Python 3.12+');
|
|
181
|
+
try {
|
|
182
|
+
if (IS_WIN) {
|
|
183
|
+
if (pkgMan === 'winget') {
|
|
184
|
+
const r = runPwsh('winget install --accept-source-agreements --accept-package-agreements Python.Python.3.12', { timeout: 180_000 });
|
|
185
|
+
if (!r.ok) throw new Error(r.out);
|
|
186
|
+
} else if (pkgMan === 'choco') {
|
|
187
|
+
run('choco install python --yes', { timeout: 180_000 });
|
|
188
|
+
} else if (pkgMan === 'scoop') {
|
|
189
|
+
run('scoop install python', { timeout: 180_000 });
|
|
190
|
+
} else {
|
|
191
|
+
s.fail('No package manager found. Install Python from https://python.org');
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
process.env.PATH = `${process.env.USERPROFILE}\\AppData\\Local\\Programs\\Python\\Python312\\;${process.env.USERPROFILE}\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\;${process.env.PATH}`;
|
|
195
|
+
} else if (IS_MAC) {
|
|
196
|
+
run('brew install python@3.12', { timeout: 180_000 });
|
|
197
|
+
run('brew link --overwrite python@3.12', { silent: true, timeout: 30_000 });
|
|
198
|
+
} else {
|
|
199
|
+
if (pkgMan === 'apt') {
|
|
200
|
+
run('apt-get update -qq && apt-get install -y python3 python3-pip python3-venv', { timeout: 180_000 });
|
|
201
|
+
} else if (pkgMan === 'dnf') {
|
|
202
|
+
run('dnf install -y python3 python3-pip', { timeout: 180_000 });
|
|
203
|
+
} else if (pkgMan === 'pacman') {
|
|
204
|
+
run('pacman -S --noconfirm python python-pip', { timeout: 180_000 });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
s.ok();
|
|
208
|
+
return true;
|
|
209
|
+
} catch (e) {
|
|
210
|
+
s.fail(e.message);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function checkCurl() {
|
|
216
|
+
const candidates = IS_WIN ? ['curl.exe', 'curl'] : ['curl'];
|
|
217
|
+
for (const bin of candidates) {
|
|
218
|
+
if (run(`${bin} --version`, { silent: true }).ok) return { ok: true, bin };
|
|
219
|
+
}
|
|
220
|
+
return { ok: false, bin: null };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function installCurl(pkgMan) {
|
|
224
|
+
const s = step('Installing curl');
|
|
225
|
+
try {
|
|
226
|
+
if (IS_WIN) {
|
|
227
|
+
if (pkgMan === 'winget') {
|
|
228
|
+
runPwsh('winget install --accept-source-agreements cURL.cURL', { timeout: 120_000 });
|
|
229
|
+
} else if (pkgMan === 'choco') {
|
|
230
|
+
run('choco install curl --yes', { timeout: 120_000 });
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
run('command -v curl || (apt-get install -y curl 2>/dev/null || yum install -y curl 2>/dev/null)', { timeout: 120_000 });
|
|
234
|
+
}
|
|
235
|
+
s.ok();
|
|
236
|
+
return true;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
s.fail(e.message);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function checkGit() {
|
|
244
|
+
const candidates = IS_WIN ? ['git.exe', 'git'] : ['git'];
|
|
245
|
+
for (const bin of candidates) {
|
|
246
|
+
if (run(`${bin} --version`, { silent: true }).ok) return { ok: true, bin };
|
|
247
|
+
}
|
|
248
|
+
return { ok: false, bin: null };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function installGit(pkgMan) {
|
|
252
|
+
const s = step('Installing git');
|
|
253
|
+
try {
|
|
254
|
+
if (IS_WIN) {
|
|
255
|
+
if (pkgMan === 'winget') {
|
|
256
|
+
runPwsh('winget install --accept-source-agreements --accept-package-agreements Git.Git', { timeout: 180_000 });
|
|
257
|
+
} else if (pkgMan === 'choco') {
|
|
258
|
+
run('choco install git --yes', { timeout: 180_000 });
|
|
259
|
+
} else if (pkgMan === 'scoop') {
|
|
260
|
+
run('scoop install git', { timeout: 180_000 });
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
run('command -v git || (apt-get install -y git 2>/dev/null || brew install git 2>/dev/null)', { timeout: 120_000 });
|
|
264
|
+
}
|
|
265
|
+
s.ok();
|
|
266
|
+
return true;
|
|
267
|
+
} catch (e) {
|
|
268
|
+
s.fail(e.message);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
103
272
|
|
|
104
273
|
async function checkPrereqs() {
|
|
105
274
|
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
|
-
};
|
|
112
275
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
276
|
+
const pkgMan = detectPackageManager();
|
|
277
|
+
|
|
278
|
+
// 1. Python
|
|
279
|
+
const py = await checkPython();
|
|
280
|
+
if (!py.ok) {
|
|
281
|
+
if (pkgMan) {
|
|
282
|
+
process.stdout.write(`\n`);
|
|
283
|
+
const installed = await installPython(pkgMan);
|
|
284
|
+
if (!installed) {
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
s.fail('Python 3.10+ is required but not found');
|
|
289
|
+
if (IS_WIN) {
|
|
290
|
+
console.log(` ${YELLOW}Install: https://python.org or run: winget install Python.Python.3.12${RESET}`);
|
|
291
|
+
} else if (IS_MAC) {
|
|
292
|
+
console.log(` ${YELLOW}Install: brew install python@3.12${RESET}`);
|
|
293
|
+
} else {
|
|
294
|
+
console.log(` ${YELLOW}Install: sudo apt install python3 python3-pip python3-venv${RESET}`);
|
|
295
|
+
}
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
118
298
|
}
|
|
119
299
|
|
|
120
|
-
//
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
pyBin = bin;
|
|
129
|
-
break;
|
|
300
|
+
// 2. curl
|
|
301
|
+
const curlCheck = await checkCurl();
|
|
302
|
+
if (!curlCheck.ok) {
|
|
303
|
+
if (pkgMan) {
|
|
304
|
+
await installCurl(pkgMan);
|
|
305
|
+
} else {
|
|
306
|
+
s.fail('curl is required');
|
|
307
|
+
process.exit(1);
|
|
130
308
|
}
|
|
131
309
|
}
|
|
132
310
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
|
|
311
|
+
// 3. git
|
|
312
|
+
const gitCheck = await checkGit();
|
|
313
|
+
if (!gitCheck.ok) {
|
|
314
|
+
if (pkgMan) {
|
|
315
|
+
await installGit(pkgMan);
|
|
316
|
+
} else {
|
|
317
|
+
s.fail('git is required');
|
|
138
318
|
process.exit(1);
|
|
139
319
|
}
|
|
140
320
|
}
|
|
141
321
|
|
|
142
322
|
s.ok();
|
|
143
|
-
|
|
144
|
-
return checks;
|
|
323
|
+
return { ...py, pkgMan, curlBin: curlCheck.bin || '', gitBin: gitCheck.bin || '' };
|
|
145
324
|
}
|
|
146
325
|
|
|
147
|
-
|
|
326
|
+
// ——— Hermes install ———
|
|
327
|
+
|
|
328
|
+
async function installHermes(quick, platformInfo) {
|
|
148
329
|
const s = step('Checking Hermes installation');
|
|
149
|
-
const hasHermes = run('hermes --version', { silent: true }).ok;
|
|
150
330
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
331
|
+
const hermesCheck = IS_WIN
|
|
332
|
+
? (run('hermes --version', { silent: true }).ok || run('hermes.exe --version', { silent: true }).ok)
|
|
333
|
+
: run('hermes --version', { silent: true }).ok;
|
|
334
|
+
|
|
335
|
+
if (hermesCheck) {
|
|
336
|
+
const ver = run('hermes --version', { silent: true }).ok
|
|
337
|
+
? run('hermes --version', { silent: true }).out
|
|
338
|
+
: 'installed';
|
|
339
|
+
s.skip(`${ver} already installed`);
|
|
154
340
|
return true;
|
|
155
341
|
}
|
|
156
342
|
|
|
@@ -158,27 +344,58 @@ async function installHermes(quick) {
|
|
|
158
344
|
console.log();
|
|
159
345
|
const ans = await ask(` Hermes not found. Install it now? [Y/n] `);
|
|
160
346
|
if (ans.toLowerCase() === 'n') {
|
|
161
|
-
console.log(` ${YELLOW}Aborted
|
|
347
|
+
console.log(` ${YELLOW}Aborted.${RESET}`);
|
|
162
348
|
process.exit(0);
|
|
163
349
|
}
|
|
164
350
|
}
|
|
165
351
|
|
|
166
352
|
const si = step('Downloading and installing Hermes');
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
353
|
+
|
|
354
|
+
if (IS_WIN) {
|
|
355
|
+
try {
|
|
356
|
+
const pyBin = platformInfo.bin || 'python';
|
|
357
|
+
run(`${pyBin} -m pip install --upgrade pip`, { silent: true, timeout: 60_000 });
|
|
358
|
+
|
|
359
|
+
const r = run(`${pyBin} -m pip install hermes-agent`, { timeout: 180_000 });
|
|
360
|
+
if (!r.ok) throw new Error(r.out);
|
|
361
|
+
|
|
362
|
+
const pathsToAdd = [
|
|
363
|
+
resolve(process.env.APPDATA || '', 'Python', 'Scripts'),
|
|
364
|
+
resolve(homedir(), '.local', 'bin'),
|
|
365
|
+
];
|
|
366
|
+
for (const p of pathsToAdd) {
|
|
367
|
+
if (!process.env.PATH?.includes(p)) {
|
|
368
|
+
process.env.PATH = `${p};${process.env.PATH}`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
si.ok();
|
|
373
|
+
return true;
|
|
374
|
+
} catch (e) {
|
|
375
|
+
si.fail(e.message);
|
|
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`);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
try {
|
|
382
|
+
const cmd = `curl -fsSL ${INSTALL_URL} | bash`;
|
|
383
|
+
const result = run(cmd, { timeout: 180_000, silent: false });
|
|
384
|
+
if (!result.ok) {
|
|
385
|
+
si.fail(result.out);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
si.ok();
|
|
389
|
+
return true;
|
|
390
|
+
} catch (e) {
|
|
391
|
+
si.fail(e.message);
|
|
172
392
|
process.exit(1);
|
|
173
393
|
}
|
|
174
|
-
si.ok();
|
|
175
|
-
return true;
|
|
176
|
-
} catch (e) {
|
|
177
|
-
si.fail(e.message);
|
|
178
|
-
process.exit(1);
|
|
179
394
|
}
|
|
180
395
|
}
|
|
181
396
|
|
|
397
|
+
// ——— config ———
|
|
398
|
+
|
|
182
399
|
async function checkConfig(quick) {
|
|
183
400
|
const s = step('Checking Hermes configuration');
|
|
184
401
|
|
|
@@ -189,7 +406,6 @@ async function checkConfig(quick) {
|
|
|
189
406
|
|
|
190
407
|
const raw = readFileSync(CONFIG_PATH, 'utf-8');
|
|
191
408
|
|
|
192
|
-
// Check if config has a model set (not commented out)
|
|
193
409
|
const hasModel = /^model:\s*\n/m.test(raw);
|
|
194
410
|
|
|
195
411
|
if (!hasModel || raw.includes('default: ""') || raw.includes("default: ''")) {
|
|
@@ -204,17 +420,13 @@ async function checkConfig(quick) {
|
|
|
204
420
|
async function runSetup(quick) {
|
|
205
421
|
if (quick) {
|
|
206
422
|
const s = step('Running headless setup');
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
`hermes config set display.streaming.enabled true 2>/dev/null || true`,
|
|
211
|
-
];
|
|
212
|
-
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 });
|
|
213
426
|
s.ok();
|
|
214
427
|
return;
|
|
215
428
|
}
|
|
216
429
|
|
|
217
|
-
// Interactive: guide the user through setup
|
|
218
430
|
console.log(`\n ${BOLD}Let's configure your Hermes Agent.${RESET}\n`);
|
|
219
431
|
console.log(` ${DIM}(You can skip any step and configure later with \`hermes setup\`)${RESET}\n`);
|
|
220
432
|
|
|
@@ -223,18 +435,15 @@ async function runSetup(quick) {
|
|
|
223
435
|
const s = step('Running setup wizard');
|
|
224
436
|
try {
|
|
225
437
|
const result = run('hermes setup', { stdio: 'inherit', timeout: 300_000 });
|
|
226
|
-
if (!result.ok)
|
|
227
|
-
|
|
228
|
-
} else {
|
|
229
|
-
s.ok();
|
|
230
|
-
}
|
|
438
|
+
if (!result.ok) s.fail(result.out);
|
|
439
|
+
else s.ok();
|
|
231
440
|
} catch (e) {
|
|
232
441
|
s.fail(e.message);
|
|
233
442
|
}
|
|
234
443
|
} else {
|
|
235
|
-
// Minimal quick config
|
|
236
444
|
const s = step('Applying minimal configuration');
|
|
237
|
-
|
|
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 });
|
|
238
447
|
s.skip('minimal config applied');
|
|
239
448
|
}
|
|
240
449
|
}
|
|
@@ -242,70 +451,71 @@ async function runSetup(quick) {
|
|
|
242
451
|
async function enableToolsets(quick) {
|
|
243
452
|
const s = step('Enabling tool sets');
|
|
244
453
|
|
|
245
|
-
|
|
246
|
-
const
|
|
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;
|
|
454
|
+
const toolsets = ['web', 'terminal', 'file', 'skills', 'session_search', 'delegate_task', 'memory', 'vision', 'tts', 'clarify'];
|
|
455
|
+
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
260
456
|
for (const t of toolsets) {
|
|
261
|
-
|
|
262
|
-
if (!r.ok) ok = false;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (ok) {
|
|
266
|
-
s.ok();
|
|
267
|
-
} else {
|
|
268
|
-
// Some may have failed if already enabled — that's fine
|
|
269
|
-
s.ok();
|
|
457
|
+
run(`hermes tools enable ${t} ${noop}`, { silent: true });
|
|
270
458
|
}
|
|
271
459
|
|
|
272
|
-
|
|
273
|
-
|
|
460
|
+
run(`hermes config set display.streaming.enabled true ${noop}`, { silent: true });
|
|
461
|
+
s.ok();
|
|
274
462
|
}
|
|
275
463
|
|
|
464
|
+
// ——— FIXED: dashboard launches as a detached process that survives after hermes-launch exits ———
|
|
465
|
+
|
|
276
466
|
async function startDashboard(quick) {
|
|
277
467
|
const s = step('Starting web dashboard');
|
|
278
|
-
const port =
|
|
468
|
+
const port = '9119';
|
|
469
|
+
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
470
|
+
const dashUrl = `http://localhost:${port}`;
|
|
279
471
|
|
|
280
|
-
// Check if
|
|
281
|
-
const statusCheck = run(
|
|
472
|
+
// Check if already running
|
|
473
|
+
const statusCheck = run(`hermes dashboard --status ${noop}`, { silent: true });
|
|
282
474
|
if (statusCheck.ok && statusCheck.out.length > 0 && !statusCheck.out.includes('No running')) {
|
|
283
475
|
s.skip('already running');
|
|
476
|
+
if (!quick) openBrowser(dashUrl);
|
|
284
477
|
return { port, alreadyRunning: true };
|
|
285
478
|
}
|
|
286
479
|
|
|
287
480
|
try {
|
|
481
|
+
// Spawn detached — survives after this script exits
|
|
288
482
|
const proc = spawn(
|
|
289
483
|
'hermes',
|
|
290
|
-
['dashboard', '--port', port, '--tui',
|
|
484
|
+
['dashboard', '--port', port, '--tui', '--no-open', '--skip-build'],
|
|
291
485
|
{
|
|
292
|
-
stdio: '
|
|
293
|
-
detached:
|
|
486
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
487
|
+
detached: true,
|
|
488
|
+
shell: IS_WIN,
|
|
294
489
|
env: { ...process.env },
|
|
295
490
|
}
|
|
296
491
|
);
|
|
297
492
|
|
|
298
|
-
//
|
|
299
|
-
|
|
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 });
|
|
300
505
|
|
|
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 });
|
|
303
506
|
if (verify.out === '0') {
|
|
304
|
-
s.skip('
|
|
507
|
+
s.skip('started in background (may take a few seconds)');
|
|
305
508
|
} else {
|
|
306
509
|
s.ok();
|
|
307
510
|
}
|
|
308
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
|
+
|
|
309
519
|
return { port, alreadyRunning: false };
|
|
310
520
|
} catch (e) {
|
|
311
521
|
s.fail(e.message);
|
|
@@ -315,7 +525,8 @@ async function startDashboard(quick) {
|
|
|
315
525
|
|
|
316
526
|
async function checkGatewayStatus() {
|
|
317
527
|
const s = step('Checking messaging gateway');
|
|
318
|
-
const
|
|
528
|
+
const noop = IS_WIN ? 'ver>nul' : '2>/dev/null || true';
|
|
529
|
+
const result = run(`hermes gateway status ${noop}`, { silent: true });
|
|
319
530
|
if (result.ok) {
|
|
320
531
|
const out = result.out;
|
|
321
532
|
const hasTelegram = out.includes('telegram') && (out.includes('running') || out.includes('connected'));
|
|
@@ -332,27 +543,6 @@ async function checkGatewayStatus() {
|
|
|
332
543
|
return [];
|
|
333
544
|
}
|
|
334
545
|
|
|
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
546
|
// ——— main ———
|
|
357
547
|
|
|
358
548
|
async function main() {
|
|
@@ -376,16 +566,24 @@ async function main() {
|
|
|
376
566
|
|
|
377
567
|
${BOLD}What it does:${RESET}
|
|
378
568
|
1. Check prerequisites (Python, curl, git)
|
|
379
|
-
2. Install
|
|
380
|
-
3.
|
|
381
|
-
4.
|
|
382
|
-
5.
|
|
383
|
-
6.
|
|
569
|
+
2. Install missing dependencies automatically (Windows: winget/choco)
|
|
570
|
+
3. Install Hermes Agent (if not present)
|
|
571
|
+
4. Run setup wizard to configure model/tools
|
|
572
|
+
5. Enable useful toolsets (web, terminal, file, skills, memory, ...)
|
|
573
|
+
6. Start the web dashboard on port 9119
|
|
574
|
+
7. Open browser, print next steps
|
|
384
575
|
|
|
385
576
|
${BOLD}After launch:${RESET}
|
|
386
577
|
hermes start chatting
|
|
387
578
|
hermes gateway setup add Telegram/Discord
|
|
388
579
|
hermes model change AI provider
|
|
580
|
+
|
|
581
|
+
${BOLD}Windows notes:${RESET}
|
|
582
|
+
- Python auto-installed via winget if missing
|
|
583
|
+
- Web dashboard opens in browser
|
|
584
|
+
- For full functionality, WSL2 is recommended:
|
|
585
|
+
wsl --install -d Ubuntu
|
|
586
|
+
Then run: npx hermes-launch inside WSL
|
|
389
587
|
`);
|
|
390
588
|
process.exit(0);
|
|
391
589
|
}
|
|
@@ -397,11 +595,8 @@ async function main() {
|
|
|
397
595
|
}
|
|
398
596
|
|
|
399
597
|
if (flags.dashboard) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (result.error) {
|
|
403
|
-
process.exit(1);
|
|
404
|
-
}
|
|
598
|
+
const result = await startDashboard(false);
|
|
599
|
+
if (result.error) process.exit(1);
|
|
405
600
|
printSummary({ port: result.port, messaging: {} });
|
|
406
601
|
process.exit(0);
|
|
407
602
|
}
|
|
@@ -409,11 +604,15 @@ async function main() {
|
|
|
409
604
|
// ——— full flow ———
|
|
410
605
|
printBanner();
|
|
411
606
|
|
|
412
|
-
|
|
413
|
-
|
|
607
|
+
if (IS_WIN) {
|
|
608
|
+
console.log(` ${DIM}Windows detected — auto-installing missing deps${RESET}\n`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// 1. Prerequisites (auto-installs Python, git, curl if missing)
|
|
612
|
+
const platformInfo = await checkPrereqs();
|
|
414
613
|
|
|
415
|
-
// 2. Install
|
|
416
|
-
await installHermes(flags.quick);
|
|
614
|
+
// 2. Install Hermes
|
|
615
|
+
await installHermes(flags.quick, platformInfo);
|
|
417
616
|
|
|
418
617
|
// 3. Check config
|
|
419
618
|
const needsSetup = await checkConfig(flags.quick);
|
|
@@ -426,19 +625,25 @@ async function main() {
|
|
|
426
625
|
// 5. Enable tools
|
|
427
626
|
await enableToolsets(flags.quick);
|
|
428
627
|
|
|
429
|
-
// 6. Start dashboard
|
|
628
|
+
// 6. Start dashboard (detached — survives after script exits)
|
|
430
629
|
const dashResult = await startDashboard(flags.quick);
|
|
431
630
|
|
|
432
631
|
// 7. Check gateway
|
|
433
632
|
const platforms = await checkGatewayStatus();
|
|
434
633
|
|
|
435
634
|
// 8. Show summary
|
|
436
|
-
|
|
635
|
+
const summary = {
|
|
437
636
|
port: dashResult.port,
|
|
438
637
|
messaging: platforms.length > 0
|
|
439
638
|
? Object.fromEntries(platforms.map(p => [p, `${GREEN}connected${RESET}`]))
|
|
440
639
|
: { none: `${YELLOW}not configured${RESET} (run \`hermes gateway setup\`)` },
|
|
441
|
-
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
if (IS_WIN) {
|
|
643
|
+
summary.windowsNote = 'On Windows, some tools work best in WSL2. Run `wsl --install -d Ubuntu` for the full experience.';
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
printSummary(summary);
|
|
442
647
|
}
|
|
443
648
|
|
|
444
649
|
main().catch(e => {
|