hermes-launch 1.0.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/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # 🐚 Hermes Launch
2
+
3
+ **One command. Zero to AI agent.**
4
+
5
+ ```bash
6
+ npx hermes-launch
7
+ ```
8
+
9
+ Hermes Launch installs, configures, and starts [Hermes Agent](https://github.com/NousResearch/hermes-agent) — the open-source AI agent framework by Nous Research. No faffing with Python venvs, curl pipes, or config files.
10
+
11
+ ```bash
12
+ # Full interactive setup (recommended for first use)
13
+ npx hermes-launch
14
+
15
+ # Non-interactive — smart defaults, go fast
16
+ npx hermes-launch --quick
17
+
18
+ # Just start the web dashboard if Hermes is already installed
19
+ npx hermes-launch --dashboard
20
+ ```
21
+
22
+ ## What you get
23
+
24
+ After running `npx hermes-launch`:
25
+
26
+ - **Hermes Agent CLI** — `hermes` in your terminal. Chat, code, research, automate.
27
+ - **Web dashboard** at `http://localhost:9119` — manage config, API keys, sessions.
28
+ - **Tool sets** enabled out of the box: web search, terminal, file ops, skills, memory, vision, TTS, subagent delegation, cross-session search.
29
+ - **Streaming** enabled — see responses as they generate.
30
+ - **Zero config** — the setup wizard walks you through model/provider selection.
31
+
32
+ ## After setup
33
+
34
+ ```bash
35
+ hermes # start a conversation
36
+ hermes chat -q "analyze this repo" # one-shot
37
+ hermes model # change provider/model
38
+ hermes gateway setup # connect Telegram/Discord/Slack
39
+ hermes tools list # see what tools are available
40
+ hermes dashboard # open the web UI
41
+ hermes doctor # health check
42
+ ```
43
+
44
+ ## What's under the hood
45
+
46
+ Hermes Launch is a thin wrapper — it:
47
+
48
+ 1. Checks prerequisites (Python 3.10+, curl, git)
49
+ 2. Installs Hermes Agent via the official install script if missing
50
+ 3. Guides you through `hermes setup` to pick a model provider
51
+ 4. Enables the core toolsets
52
+ 5. Starts `hermes dashboard --tui` on port 9119
53
+ 6. Prints next steps
54
+
55
+ Everything lives in `~/.hermes/` — config, sessions, logs, skills.
56
+
57
+ ## Requirements
58
+
59
+ - **Node.js** 18+ (comes with npx)
60
+ - **Python** 3.10+ (system or homebrew)
61
+ - **curl**, **git**
62
+ - macOS, Linux, or WSL
63
+
64
+ ## Why not just curl the install script?
65
+
66
+ You can! `curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash` works great. Hermes Launch adds:
67
+
68
+ - Prerequisite checking with clear error messages
69
+ - Interactive setup flow with prompts
70
+ - Smart defaults for tool enablement
71
+ - Dashboard auto-start
72
+ - A clean post-install summary
73
+ - No pipe-to-bash pattern (safer for enterprise environments)
74
+
75
+ ## License
76
+
77
+ MIT
@@ -0,0 +1,447 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * hermes-launch — one command to install, configure, and run Hermes Agent.
4
+ *
5
+ * Usage:
6
+ * npx hermes-launch # full interactive flow
7
+ * npx hermes-launch --quick # skip prompts, use smart defaults
8
+ * npx hermes-launch --dashboard # just start the web dashboard
9
+ * npx hermes-launch --help # this message
10
+ */
11
+
12
+ import { execSync, spawn } from 'child_process';
13
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
14
+ import { homedir, platform, hostname } from 'os';
15
+ import { resolve } from 'path';
16
+ import { createInterface } from 'readline';
17
+
18
+ const HERMES_HOME = resolve(homedir(), '.hermes');
19
+ const CONFIG_PATH = resolve(HERMES_HOME, 'config.yaml');
20
+ const INSTALL_URL = 'https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh';
21
+
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';
30
+
31
+ // ——— helpers ———
32
+
33
+ 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);
41
+ }
42
+
43
+ function ask(query) {
44
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
45
+ return new Promise(resolve => rl.question(query, ans => { rl.close(); resolve(ans.trim()); }));
46
+ }
47
+
48
+ function run(cmd, opts = {}) {
49
+ const defaults = { stdio: opts.silent ? 'pipe' : 'inherit', timeout: 120_000, ...opts };
50
+ try {
51
+ const out = execSync(cmd, defaults);
52
+ return { ok: true, out: out?.toString()?.trim() || '' };
53
+ } catch (e) {
54
+ return { ok: false, out: e.stderr?.toString()?.trim() || e.message };
55
+ }
56
+ }
57
+
58
+ function spinner(ms) {
59
+ return new Promise(resolve => setTimeout(resolve, ms));
60
+ }
61
+
62
+ function step(msg) {
63
+ process.stdout.write(` ${CYAN}→${RESET} ${msg} ... `);
64
+ return {
65
+ ok: () => { process.stdout.write(`${GREEN}done${RESET}\n`); },
66
+ skip: (reason) => { process.stdout.write(`${YELLOW}skipped${RESET} (${reason})\n`); },
67
+ fail: (err) => { process.stdout.write(`${RED}failed${RESET}\n ${DIM}${err}${RESET}\n`); },
68
+ };
69
+ }
70
+
71
+ function printSummary(info) {
72
+ console.log(`\n${BOLD}${GREEN} ✅ Hermes Agent is live!${RESET}\n`);
73
+ console.log(` ${BOLD}Web Dashboard${RESET}`);
74
+ console.log(` ${CYAN}http://localhost:${info.port || 9119}${RESET}\n`);
75
+ console.log(` ${BOLD}CLI${RESET}`);
76
+ console.log(` ${CYAN}hermes${RESET} interactive chat`);
77
+ console.log(` ${CYAN}hermes chat -q "..."${RESET} one-shot query`);
78
+ console.log(` ${CYAN}hermes --continue${RESET} resume last session\n`);
79
+ console.log(` ${BOLD}Key commands${RESET}`);
80
+ console.log(` ${CYAN}hermes model${RESET} change model/provider`);
81
+ console.log(` ${CYAN}hermes gateway setup${RESET} connect Telegram/Discord`);
82
+ console.log(` ${CYAN}hermes tools list${RESET} see available tools`);
83
+ console.log(` ${CYAN}hermes cron${RESET} schedule recurring tasks`);
84
+ console.log(` ${CYAN}hermes doctor${RESET} health check\n`);
85
+ console.log(` ${BOLD}Docs${RESET}`);
86
+ console.log(` ${CYAN}https://hermes-agent.nousresearch.com/docs${RESET}\n`);
87
+ if (info.messaging) {
88
+ console.log(` ${BOLD}Messaging${RESET}`);
89
+ for (const [platform, status] of Object.entries(info.messaging)) {
90
+ console.log(` ${platform}: ${status}`);
91
+ }
92
+ console.log();
93
+ }
94
+ }
95
+
96
+ function printMiniBanner() {
97
+ console.log(`
98
+ ${CYAN} 🐚 Hermes Launch${RESET} ${DIM}— zero-to-agent${RESET}
99
+ `);
100
+ }
101
+
102
+ // ——— core steps ———
103
+
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
+ };
112
+
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
+ }
119
+
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
+ for (const bin of pyCandidates) {
125
+ const r = run(`${bin} --version`, { silent: true });
126
+ if (r.ok) {
127
+ pyVer = r.out;
128
+ pyBin = bin;
129
+ break;
130
+ }
131
+ }
132
+
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})`);
138
+ process.exit(1);
139
+ }
140
+ }
141
+
142
+ s.ok();
143
+ checks.pythonBin = pyBin;
144
+ return checks;
145
+ }
146
+
147
+ async function installHermes(quick) {
148
+ const s = step('Checking Hermes installation');
149
+ const hasHermes = run('hermes --version', { silent: true }).ok;
150
+
151
+ if (hasHermes) {
152
+ const ver = run('hermes --version', { silent: true }).out;
153
+ s.skip(`v${ver} already installed`);
154
+ return true;
155
+ }
156
+
157
+ if (!quick) {
158
+ console.log();
159
+ const ans = await ask(` Hermes not found. Install it now? [Y/n] `);
160
+ if (ans.toLowerCase() === 'n') {
161
+ console.log(` ${YELLOW}Aborted. Install manually: curl -fsSL ${INSTALL_URL} | bash${RESET}`);
162
+ process.exit(0);
163
+ }
164
+ }
165
+
166
+ 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);
172
+ process.exit(1);
173
+ }
174
+ si.ok();
175
+ return true;
176
+ } catch (e) {
177
+ si.fail(e.message);
178
+ process.exit(1);
179
+ }
180
+ }
181
+
182
+ async function checkConfig(quick) {
183
+ const s = step('Checking Hermes configuration');
184
+
185
+ if (!existsSync(CONFIG_PATH)) {
186
+ s.skip('no config yet — will be generated');
187
+ return false;
188
+ }
189
+
190
+ const raw = readFileSync(CONFIG_PATH, 'utf-8');
191
+
192
+ // Check if config has a model set (not commented out)
193
+ const hasModel = /^model:\s*\n/m.test(raw);
194
+
195
+ if (!hasModel || raw.includes('default: ""') || raw.includes("default: ''")) {
196
+ s.skip('needs model configuration');
197
+ return false;
198
+ }
199
+
200
+ s.ok();
201
+ return true;
202
+ }
203
+
204
+ async function runSetup(quick) {
205
+ if (quick) {
206
+ const s = step('Running headless setup');
207
+ // For quick mode, just enable the gateway and set up with defaults
208
+ 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`,
211
+ ];
212
+ for (const cmd of setCmds) run(cmd, { silent: true });
213
+ s.ok();
214
+ return;
215
+ }
216
+
217
+ // Interactive: guide the user through setup
218
+ console.log(`\n ${BOLD}Let's configure your Hermes Agent.${RESET}\n`);
219
+ console.log(` ${DIM}(You can skip any step and configure later with \`hermes setup\`)${RESET}\n`);
220
+
221
+ const doSetup = await ask(` Run the Hermes setup wizard? (recommended) [Y/n] `);
222
+ if (doSetup.toLowerCase() !== 'n') {
223
+ const s = step('Running setup wizard');
224
+ try {
225
+ 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
+ }
231
+ } catch (e) {
232
+ s.fail(e.message);
233
+ }
234
+ } else {
235
+ // Minimal quick config
236
+ 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 });
238
+ s.skip('minimal config applied');
239
+ }
240
+ }
241
+
242
+ async function enableToolsets(quick) {
243
+ const s = step('Enabling tool sets');
244
+
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
+ }
264
+
265
+ if (ok) {
266
+ s.ok();
267
+ } else {
268
+ // Some may have failed if already enabled — that's fine
269
+ s.ok();
270
+ }
271
+
272
+ // Enable gateway streaming
273
+ run('hermes config set display.streaming.enabled true 2>/dev/null || true', { silent: true });
274
+ }
275
+
276
+ async function startDashboard(quick) {
277
+ const s = step('Starting web dashboard');
278
+ const port = quick ? '9119' : '9119';
279
+
280
+ // Check if dashboard is already running
281
+ const statusCheck = run('hermes dashboard --status', { silent: true });
282
+ if (statusCheck.ok && statusCheck.out.length > 0 && !statusCheck.out.includes('No running')) {
283
+ s.skip('already running');
284
+ return { port, alreadyRunning: true };
285
+ }
286
+
287
+ try {
288
+ const proc = spawn(
289
+ 'hermes',
290
+ ['dashboard', '--port', port, '--tui', quick ? '--no-open' : ''],
291
+ {
292
+ stdio: 'inherit',
293
+ detached: false,
294
+ env: { ...process.env },
295
+ }
296
+ );
297
+
298
+ // Give it a moment to start
299
+ await spinner(3000);
300
+
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
+ if (verify.out === '0') {
304
+ s.skip('dashboard launched in background');
305
+ } else {
306
+ s.ok();
307
+ }
308
+
309
+ return { port, alreadyRunning: false };
310
+ } catch (e) {
311
+ s.fail(e.message);
312
+ return { port, alreadyRunning: false, error: e.message };
313
+ }
314
+ }
315
+
316
+ async function checkGatewayStatus() {
317
+ const s = step('Checking messaging gateway');
318
+ const result = run('hermes gateway status', { silent: true });
319
+ if (result.ok) {
320
+ const out = result.out;
321
+ const hasTelegram = out.includes('telegram') && (out.includes('running') || out.includes('connected'));
322
+ const hasDiscord = out.includes('discord') && (out.includes('running') || out.includes('connected'));
323
+ const platforms = [];
324
+ if (hasTelegram) platforms.push('telegram');
325
+ if (hasDiscord) platforms.push('discord');
326
+ if (platforms.length > 0) {
327
+ s.ok();
328
+ return platforms;
329
+ }
330
+ }
331
+ s.skip('not configured');
332
+ return [];
333
+ }
334
+
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
+ // ——— main ———
357
+
358
+ async function main() {
359
+ const args = process.argv.slice(2);
360
+ const flags = {
361
+ quick: args.includes('--quick') || args.includes('-q'),
362
+ dashboard: args.includes('--dashboard'),
363
+ help: args.includes('--help') || args.includes('-h'),
364
+ version: args.includes('--version') || args.includes('-v'),
365
+ };
366
+
367
+ if (flags.help) {
368
+ console.log(`
369
+ ${BOLD}hermes-launch${RESET} — one command to install and run Hermes Agent
370
+
371
+ ${BOLD}Usage:${RESET}
372
+ npx hermes-launch full interactive setup
373
+ npx hermes-launch --quick non-interactive, smart defaults
374
+ npx hermes-launch --dashboard just start the web dashboard
375
+ npx hermes-launch --help this message
376
+
377
+ ${BOLD}What it does:${RESET}
378
+ 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
384
+
385
+ ${BOLD}After launch:${RESET}
386
+ hermes start chatting
387
+ hermes gateway setup add Telegram/Discord
388
+ hermes model change AI provider
389
+ `);
390
+ process.exit(0);
391
+ }
392
+
393
+ if (flags.version) {
394
+ const pkg = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf-8'));
395
+ console.log(`hermes-launch v${pkg.version}`);
396
+ process.exit(0);
397
+ }
398
+
399
+ if (flags.dashboard) {
400
+ printMiniBanner();
401
+ const result = await startDashboard(true);
402
+ if (result.error) {
403
+ process.exit(1);
404
+ }
405
+ printSummary({ port: result.port, messaging: {} });
406
+ process.exit(0);
407
+ }
408
+
409
+ // ——— full flow ———
410
+ printBanner();
411
+
412
+ // 1. Prerequisites
413
+ await checkPrereqs();
414
+
415
+ // 2. Install
416
+ await installHermes(flags.quick);
417
+
418
+ // 3. Check config
419
+ const needsSetup = await checkConfig(flags.quick);
420
+
421
+ // 4. Setup if needed
422
+ if (!needsSetup) {
423
+ await runSetup(flags.quick);
424
+ }
425
+
426
+ // 5. Enable tools
427
+ await enableToolsets(flags.quick);
428
+
429
+ // 6. Start dashboard
430
+ const dashResult = await startDashboard(flags.quick);
431
+
432
+ // 7. Check gateway
433
+ const platforms = await checkGatewayStatus();
434
+
435
+ // 8. Show summary
436
+ printSummary({
437
+ port: dashResult.port,
438
+ messaging: platforms.length > 0
439
+ ? Object.fromEntries(platforms.map(p => [p, `${GREEN}connected${RESET}`]))
440
+ : { none: `${YELLOW}not configured${RESET} (run \`hermes gateway setup\`)` },
441
+ });
442
+ }
443
+
444
+ main().catch(e => {
445
+ console.error(`${RED}Error:${RESET}`, e.message);
446
+ process.exit(1);
447
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "hermes-launch",
3
+ "version": "1.0.0",
4
+ "description": "One command to launch Hermes Agent — zero-to-AI-agent in seconds",
5
+ "bin": {
6
+ "hermes-launch": "./bin/hermes-launch.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "src/",
11
+ "README.md"
12
+ ],
13
+ "keywords": [
14
+ "hermes",
15
+ "ai-agent",
16
+ "claude-code",
17
+ "codex",
18
+ "opencode",
19
+ "llm",
20
+ "agent",
21
+ "nous-research"
22
+ ],
23
+ "type": "module",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/NousResearch/hermes-launch"
28
+ },
29
+ "homepage": "https://github.com/NousResearch/hermes-launch",
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "preferGlobal": true
34
+ }