@ours.network/install 0.12.0-nightly.9 → 0.13.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/install.mjs +61 -10
- package/package.json +1 -1
package/install.mjs
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// bootstrap, and the `ours-install` command once the stack is on the machine).
|
|
4
4
|
//
|
|
5
5
|
// ONE installer for the WHOLE stack — ours core (the daemon) + the harness plugins (Claude Code /
|
|
6
|
-
// Codex) + ours-fleet + the Telegram connector — for someone who ALREADY has Claude
|
|
6
|
+
// Codex / Hermes) + ours-fleet + the Telegram connector — for someone who ALREADY has Claude,
|
|
7
|
+
// Codex, and/or Hermes.
|
|
7
8
|
// Its whole job: install the stack cleanly, then hand back ONE copy-paste prompt the user drops
|
|
8
9
|
// into their agent to finish all real configuration conversationally. No tokens, no port editing,
|
|
9
10
|
// no config files. See packages/installer/README.md and the UX spec for the full contract.
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
// installed/started/restarted — it prints exactly what it WOULD do. That is the safe way to walk
|
|
17
18
|
// the whole flow on a machine you don't want to touch (and how the tests drive it).
|
|
18
19
|
import { spawn, spawnSync } from 'node:child_process';
|
|
19
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
20
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
20
21
|
import { homedir, userInfo, platform as osPlatform, release as osRelease } from 'node:os';
|
|
21
22
|
import { join, dirname } from 'node:path';
|
|
22
23
|
import { banner, heading, ok, info, warn, c, box, withSpinner, openTty, makeWriter, closeSync } from './lib/ui.mjs';
|
|
@@ -135,6 +136,21 @@ function detectHarness(name) {
|
|
|
135
136
|
return { name, ...verdict };
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
// Hermes detection is DIFFERENT from Claude Code / Codex: Hermes has no driven CLI. Its ours plugin
|
|
140
|
+
// (`ours-hermes-install`) never calls a `hermes` binary — it just writes ~/.hermes/config.yaml + the
|
|
141
|
+
// skills — so "is it drivable?" is the wrong question. Per the Hermes plugin's own prerequisites,
|
|
142
|
+
// presence == the config dir (~/.hermes, override with HERMES_DIR) exists. We still run the alias-safe
|
|
143
|
+
// CLI probe in case a real `hermes` command is on PATH, purely to enrich detection; either signal
|
|
144
|
+
// makes it installable. No config dir and no CLI → absent (skipped, like an uninstalled harness).
|
|
145
|
+
function detectHermes() {
|
|
146
|
+
const dir = process.env.HERMES_DIR || join(homedir(), '.hermes');
|
|
147
|
+
const dirPresent = existsSync(dir);
|
|
148
|
+
const cli = detectHarness('hermes'); // best-effort — Hermes usually has no `--version` CLI
|
|
149
|
+
const status = dirPresent || cli.status === 'ok' ? 'ok' : 'absent';
|
|
150
|
+
const detail = dirPresent ? `config dir ${dir} present` : cli.detail;
|
|
151
|
+
return { name: 'hermes', label: 'Hermes', status, detail };
|
|
152
|
+
}
|
|
153
|
+
|
|
138
154
|
// Set by main() so the top-level catch can route a Ctrl+C (InstallCancelled) through the same
|
|
139
155
|
// clean-exit path as the SIGINT handler.
|
|
140
156
|
let cancelHandler = null;
|
|
@@ -152,7 +168,7 @@ const USAGE = `ours-install — the unified ours.network stack installer.
|
|
|
152
168
|
ours-install [--dry-run] [--help] [--version]
|
|
153
169
|
|
|
154
170
|
Guided ~3-minute setup for the whole stack: ours core (the daemon), the harness
|
|
155
|
-
plugins (Claude Code + Codex), ours-fleet, and the Telegram connector — then one
|
|
171
|
+
plugins (Claude Code + Codex + Hermes), ours-fleet, and the Telegram connector — then one
|
|
156
172
|
copy-paste hand-off prompt. You approve each step; re-run any time to add a piece
|
|
157
173
|
or update.
|
|
158
174
|
|
|
@@ -225,7 +241,10 @@ async function main() {
|
|
|
225
241
|
{ name: 'claude', label: 'Claude Code' },
|
|
226
242
|
{ name: 'codex', label: 'Codex' },
|
|
227
243
|
];
|
|
244
|
+
// Claude Code + Codex are driven CLIs (alias-safe --version probe); Hermes is config-dir based
|
|
245
|
+
// (see detectHermes) so it gets its own detector, appended after them.
|
|
228
246
|
const harnesses = harnessSpecs.map((h) => ({ ...h, ...detectHarness(h.name) }));
|
|
247
|
+
harnesses.push(detectHermes());
|
|
229
248
|
for (const h of harnesses) {
|
|
230
249
|
if (h.status === 'ok') line(ok(`'${h.name}' → real program (its plugin can be installed)`));
|
|
231
250
|
else if (h.status === 'alias') line(warn(`'${h.name}' → ${h.detail} (I won't call it — see the note below; you can still install it by hand)`));
|
|
@@ -236,7 +255,7 @@ async function main() {
|
|
|
236
255
|
const anyHarness = harnesses.some((h) => h.status !== 'absent');
|
|
237
256
|
if (!anyHarness) {
|
|
238
257
|
line('');
|
|
239
|
-
line(warn('No Claude Code or
|
|
258
|
+
line(warn('No Claude Code, Codex, or Hermes found on this machine.'));
|
|
240
259
|
line(info('Install one of them first, then re-run ours-install to wire it up.'));
|
|
241
260
|
finish(ttyFd); return;
|
|
242
261
|
}
|
|
@@ -431,17 +450,39 @@ async function main() {
|
|
|
431
450
|
} else { failCodex(); record({ key: 'codex', label: 'Codex plugin + ours-codex', state: 'failed', note: 'marketplace/install step failed' }); }
|
|
432
451
|
return true;
|
|
433
452
|
}
|
|
453
|
+
// Hermes: NOT a driven CLI. Its plugin install is `npm i -g @ours.network/hermes@<channel>` then
|
|
454
|
+
// `ours-hermes-install`, which writes ~/.hermes/config.yaml (the ours MCP server) + the skills. No
|
|
455
|
+
// marketplace/plugin-add, no alias-safety gate — nothing here calls the `hermes` binary. We pass
|
|
456
|
+
// --skip-daemon because the unified installer already owns the daemon (Step 1, chosen port);
|
|
457
|
+
// ours-hermes-install would otherwise re-ensure/restart it. Same never-dead-end contract as above.
|
|
458
|
+
async function installHermes() {
|
|
459
|
+
const go = yes(' Install the ours plugin into Hermes?', true);
|
|
460
|
+
if (!go) { line(info('skipped — re-run ours-install to add it.')); record({ key: 'hermes', label: 'Hermes plugin', state: 'skipped' }); return false; }
|
|
461
|
+
const npmOk = await actSpin(`installing ${spec('hermes')}…`, `npm i -g ${spec('hermes')} (provides ours-hermes-install)`, () => runAsync(NPM, ['i', '-g', spec('hermes')]));
|
|
462
|
+
const inst = npmOk.ok
|
|
463
|
+
? await act('ours-hermes-install --skip-daemon (writes ~/.hermes: ours MCP server + skills)', async () => run('ours-hermes-install', ['--skip-daemon'], { capture: true }))
|
|
464
|
+
: npmOk;
|
|
465
|
+
if (inst.ok) {
|
|
466
|
+
line(ok('Hermes plugin installed — the ours MCP server + skills are registered in ~/.hermes. No problems.'));
|
|
467
|
+
line(info("(run '/reload-mcp' in Hermes to load the ours tools.)"));
|
|
468
|
+
record({ key: 'hermes', label: 'Hermes plugin', state: 'installed', note: 'run /reload-mcp' });
|
|
469
|
+
} else { failHermes(); record({ key: 'hermes', label: 'Hermes plugin', state: 'failed', note: 'npm/ours-hermes-install step failed' }); }
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
434
472
|
|
|
435
473
|
// ============================================================================================
|
|
436
|
-
// STEP 2 / 4 — harness plugins (Claude Code + Codex). The installer drives the plugin
|
|
474
|
+
// STEP 2 / 4 — harness plugins (Claude Code + Codex + Hermes). The installer drives the plugin
|
|
475
|
+
// CLIs for Claude/Codex; Hermes installs via npm + ours-hermes-install (no CLI driving).
|
|
437
476
|
// ============================================================================================
|
|
438
477
|
line(heading('2/4 — harness plugins'));
|
|
439
|
-
line(info('These teach Claude Code and
|
|
440
|
-
line(info("to message people and set things up. I'll install them for you — no commands to type."));
|
|
478
|
+
line(info('These teach Claude Code, Codex, and Hermes the ours skills, so you can just talk to your'));
|
|
479
|
+
line(info("agent to message people and set things up. I'll install them for you — no commands to type."));
|
|
441
480
|
for (const h of harnesses) {
|
|
442
481
|
if (h.status === 'absent') continue; // nothing to offer; pre-flight already noted it
|
|
443
482
|
line('');
|
|
444
|
-
const acted = h.name === 'claude' ? await installClaude(h)
|
|
483
|
+
const acted = h.name === 'claude' ? await installClaude(h)
|
|
484
|
+
: h.name === 'codex' ? await installCodex(h)
|
|
485
|
+
: await installHermes(h);
|
|
445
486
|
cont(acted);
|
|
446
487
|
}
|
|
447
488
|
|
|
@@ -472,7 +513,7 @@ async function main() {
|
|
|
472
513
|
const r = await act(`claude plugin install ${CLAUDE_FLEET_PLUGIN}`, async () => run('claude', ['plugin', 'install', CLAUDE_FLEET_PLUGIN], { capture: true }));
|
|
473
514
|
if (r.ok) { line(ok('Claude Code fleet plugin installed — you can spawn agents from Claude Code. No problems.')); fleetIn.push('Claude Code'); }
|
|
474
515
|
else failClaudeFleet();
|
|
475
|
-
} else
|
|
516
|
+
} else if (h.name === 'codex') {
|
|
476
517
|
if (h.status !== 'ok') { manualCodexFleet(h); continue; }
|
|
477
518
|
await act(`codex plugin marketplace add ${CODEX_MARKET}`, async () => run('codex', ['plugin', 'marketplace', 'add', CODEX_MARKET], { capture: true }));
|
|
478
519
|
const r = await act(`codex plugin add ${CODEX_FLEET_PLUGIN}`, async () => run('codex', ['plugin', 'add', CODEX_FLEET_PLUGIN], { capture: true }));
|
|
@@ -483,7 +524,10 @@ async function main() {
|
|
|
483
524
|
// Only claim the skill is present where the fleet plugin actually installed.
|
|
484
525
|
if (init.ok && fleetIn.length) line(ok(`ours-fleet ready — ${fleetIn.join(' + ')} now know the fleet skill. No problems.`));
|
|
485
526
|
else if (init.ok) line(info('ours-fleet CLI is installed; add the fleet plugin to your harness with the commands above.'));
|
|
486
|
-
|
|
527
|
+
// Fleet plugins target Claude Code + Codex only (Hermes has none) — so "no fleet-capable harness
|
|
528
|
+
// to install into" means every claude/codex is absent, NOT every harness (Hermes doesn't count).
|
|
529
|
+
const fleetCapableAbsent = harnesses.filter((h) => h.name === 'claude' || h.name === 'codex').every((h) => h.status === 'absent');
|
|
530
|
+
const fleetOk = init.ok && (fleetIn.length > 0 || fleetCapableAbsent);
|
|
487
531
|
record({ key: 'fleet', label: 'ours-fleet', state: fleetOk ? 'installed' : 'failed', version: globalVersion('@ours.network/fleet'), note: fleetIn.length ? fleetIn.join(' + ') : (init.ok ? 'CLI only — add plugin manually' : 'ours-fleet init failed') });
|
|
488
532
|
} else {
|
|
489
533
|
line(info('skipped cleanly — re-run ours-install any time to add it.'));
|
|
@@ -560,6 +604,13 @@ function failCodex() {
|
|
|
560
604
|
line(' ' + c.cyan('npm i -g @ours.network/codex'));
|
|
561
605
|
line(info('Your daemon and other steps are intact. Continuing.'));
|
|
562
606
|
}
|
|
607
|
+
function failHermes() {
|
|
608
|
+
line(warn('Couldn\'t install the Hermes plugin automatically (network or npm).'));
|
|
609
|
+
line(info('Install it by hand — run these two, then run /reload-mcp in Hermes:'));
|
|
610
|
+
line(' ' + c.cyan('npm i -g @ours.network/hermes'));
|
|
611
|
+
line(' ' + c.cyan('ours-hermes-install'));
|
|
612
|
+
line(info('Your daemon and other steps are intact. Continuing.'));
|
|
613
|
+
}
|
|
563
614
|
// --- fleet HARNESS PLUGIN never-dead-end messaging ---------------------------------------------
|
|
564
615
|
function manualClaudeFleet(h) {
|
|
565
616
|
line(warn(h.status === 'alias'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/install",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The unified ours.network stack installer (ours-install): one guided ~3-minute flow for ours core (the daemon) + the harness plugins (Claude Code / Codex) + ours-fleet + the Telegram connector, then a single copy-paste hand-off prompt. Self-contained (Node built-ins only); run as `ours-install` or via curl|bash (install.sh).",
|
|
6
6
|
"type": "module",
|