@ours.network/install 0.17.0-nightly.4 → 0.17.0-nightly.5
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 +34 -16
- package/lib/nightly-install.mjs +7 -1
- package/package.json +1 -1
package/install.mjs
CHANGED
|
@@ -103,13 +103,23 @@ async function actSpin(label, desc, fn) {
|
|
|
103
103
|
|
|
104
104
|
// --- daemon probes (always safe to run — read-only) --------------------------------------------
|
|
105
105
|
const daemonVersionLine = () => (run('ours-mcp', ['--version'], { capture: true }).out.split('\n')[0] || '').trim();
|
|
106
|
-
const daemonStatusText = () => run('ours-mcp', ['status'], { capture: true }).out;
|
|
106
|
+
const daemonStatusText = (env) => run('ours-mcp', ['status'], { capture: true, ...(env ? { env } : {}) }).out;
|
|
107
107
|
function daemonLifecycleState() {
|
|
108
108
|
const status = run('ours-mcp', ['status'], { capture: true });
|
|
109
109
|
if (!status.ok) return 'stopped';
|
|
110
110
|
return /^\s*pid:\s*\d+/m.test(status.out) ? 'managed' : 'external';
|
|
111
111
|
}
|
|
112
112
|
const daemonRunning = () => daemonLifecycleState() !== 'stopped';
|
|
113
|
+
// LISTENING, not merely alive — and the difference is not academic. `ours-mcp start`
|
|
114
|
+
// exits 0 when it only FINDS a live pid in the state directory (core cmdStart: it
|
|
115
|
+
// checks `isAlive(pid)` and nothing else, so a pid file left by a previous boot whose
|
|
116
|
+
// number now belongs to any other process reads as "already running"). `ours-mcp
|
|
117
|
+
// status` then exits 0 for that same pid while printing "(port not answering!)" —
|
|
118
|
+
// core cmdStatus only sets a non-zero exit for the no-pid case. So neither exit code
|
|
119
|
+
// proves a daemon is there, and a readiness line built from one can tell the user
|
|
120
|
+
// "ready — no problems" about a port nothing is bound to. The status line the daemon
|
|
121
|
+
// prints only when the port actually answered is the honest signal, so use it.
|
|
122
|
+
const daemonReachable = (env) => /\(reachable\)/.test(daemonStatusText(env));
|
|
113
123
|
// The installed version of a global package, INCLUDING any prerelease suffix. The
|
|
114
124
|
// suffix is not cosmetic here: the Rooms daemon guard compares against an exact
|
|
115
125
|
// `0.4.1-nightly.<date>.<sha>` floor, and truncating at the dash would make every
|
|
@@ -522,13 +532,13 @@ async function main() {
|
|
|
522
532
|
// WORKING daemon into no daemon at all, while the line below still said "ready": the
|
|
523
533
|
// human identity and every MCP client then fail against a port nothing is listening
|
|
524
534
|
// on. Put the one shared daemon back up and report what is actually true.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
535
|
+
// `started.ok` is the exit code of `ours-mcp start`, which is not evidence that
|
|
536
|
+
// anything is listening (see daemonReachable). Ask the daemon instead.
|
|
537
|
+
let running = DRY ? started.ok : daemonReachable();
|
|
538
|
+
if (!DRY && !svc.ok && !running) {
|
|
539
|
+
line(info('the boot-service step stopped the daemon before it failed — restarting it.'));
|
|
540
|
+
run('ours-mcp', ['start']);
|
|
541
|
+
running = daemonReachable();
|
|
532
542
|
}
|
|
533
543
|
if (running) line(ok(`ours core ready — running on port ${chosenPort}. No problems.`));
|
|
534
544
|
else line(warn(`could not auto-start — run '${c.cyan('ours-mcp start')}' to bring it up.`));
|
|
@@ -609,9 +619,11 @@ async function main() {
|
|
|
609
619
|
line(ok(`Your human identity "${name}" is created.`));
|
|
610
620
|
record({ key: 'identity', label: 'Human identity', state: 'installed', note: name });
|
|
611
621
|
} else {
|
|
612
|
-
// A freshly-started daemon may need a moment to
|
|
613
|
-
|
|
614
|
-
|
|
622
|
+
// A freshly-started daemon may need a moment to BIND ITS PORT before create-root
|
|
623
|
+
// can reach it — which is the one thing a liveness check cannot see, so this
|
|
624
|
+
// waits on the port answering rather than on a process existing.
|
|
625
|
+
let reachable = daemonReachable();
|
|
626
|
+
for (let i = 0; i < 6 && !reachable; i++) { sleepMs(400); reachable = daemonReachable(); }
|
|
615
627
|
const r = reachable ? run('ours-mcp', ['create-root', name], { capture: true }) : { ok: false, out: '', err: 'daemon not running' };
|
|
616
628
|
const outText = `${r.out} ${r.err}`;
|
|
617
629
|
const existing = outText.match(/already exists \("([^"]+)"\)/);
|
|
@@ -715,7 +727,12 @@ async function main() {
|
|
|
715
727
|
line(info('over Telegram so they talk to each other — and it all configures maximally easily.'));
|
|
716
728
|
const goFleet = yes(' Install it?', true);
|
|
717
729
|
if (goFleet) {
|
|
718
|
-
// ours-fleet
|
|
730
|
+
// ours-fleet FOLLOWS the channel, like everything else here: it publishes its own
|
|
731
|
+
// nightly dist-tag from adapt-toolkit/ours-fleet, and lib/logic.mjs maps it
|
|
732
|
+
// accordingly. (This comment used to claim the opposite — that fleet had no nightly
|
|
733
|
+
// tag and was pinned to @latest even under OURS_CHANNEL=nightly — which stopped
|
|
734
|
+
// being true when the map gained its `fleet` entry. The code always followed the
|
|
735
|
+
// map; only the comment was stale, which is the more dangerous of the two.)
|
|
719
736
|
await actSpin(`installing ${spec('fleet')}…`, `npm i -g ${spec('fleet')}`, () => runAsync(NPM, ['i', '-g', spec('fleet')]));
|
|
720
737
|
const init = await act('ours-fleet init (one-time host setup: units, dirs, linger)', async () => run('ours-fleet', ['init']));
|
|
721
738
|
if (!init.ok) line(warn(`ours-fleet host setup didn't finish — retry '${c.cyan('ours-fleet init')}'.`));
|
|
@@ -764,7 +781,7 @@ async function main() {
|
|
|
764
781
|
// Nothing to change AND it is already up: do not touch it. `install-service` STOPS
|
|
765
782
|
// the daemon before rewriting the unit, so re-running it here would bounce a healthy
|
|
766
783
|
// daemon for no reason.
|
|
767
|
-
if (!DRY &&
|
|
784
|
+
if (!DRY && daemonReachable(env)) {
|
|
768
785
|
line(ok(`Dedicated ${label} daemon already running on port ${port} — left alone.`));
|
|
769
786
|
return { endpoint: daemonEndpoint(port), stateDir, serviceName, configPath: cfgPath, running: true, port };
|
|
770
787
|
}
|
|
@@ -778,12 +795,13 @@ async function main() {
|
|
|
778
795
|
const svc = await act(`ours-mcp install-service (dedicated ${label} daemon, unit ours-${serviceName})`, async () => run('ours-mcp', ['install-service'], { env }));
|
|
779
796
|
// Same recovery as the shared daemon: install-service STOPS the daemon before it
|
|
780
797
|
// writes the unit, so a failure there leaves nothing listening on this port.
|
|
781
|
-
|
|
798
|
+
// Same rule as the shared daemon: an exit code is not evidence of a bound port.
|
|
799
|
+
let running = DRY ? started.ok : daemonReachable(env);
|
|
782
800
|
if (!DRY && !svc.ok) {
|
|
783
|
-
running = run('ours-mcp', ['status'], { capture: true, env }).ok;
|
|
784
801
|
if (!running) {
|
|
785
802
|
line(info(`the boot-service step stopped the ${label} daemon before it failed — restarting it.`));
|
|
786
|
-
|
|
803
|
+
run('ours-mcp', ['start'], { env });
|
|
804
|
+
running = daemonReachable(env);
|
|
787
805
|
}
|
|
788
806
|
line(warn(`the ${label} daemon has no boot service — retry '${c.cyan(`OURS_CONFIG=${cfgPath} OURS_SERVICE_NAME=${serviceName} ours-mcp install-service`)}'.`));
|
|
789
807
|
}
|
package/lib/nightly-install.mjs
CHANGED
|
@@ -592,7 +592,13 @@ export async function runNightlyInstaller(deps) {
|
|
|
592
592
|
if (wantFleet) {
|
|
593
593
|
const fleetPkg = await actSpin('installing ours-fleet Nightly…', `npm i -g ${pkgSpec('fleet', 'nightly')}`, () => runAsync(npm, ['i', '-g', pkgSpec('fleet', 'nightly')]));
|
|
594
594
|
if (!fleetPkg.ok) throw new Error('ours-fleet package installation failed');
|
|
595
|
-
|
|
595
|
+
// ours-fleet resolves its daemon from OURS_CONFIG / OURS_PORT / OURS_STATE_DIR,
|
|
596
|
+
// falling back to ~/.ours and 3050. It has no concept of this registry, so an
|
|
597
|
+
// init run without the selected profile's environment points every role at the
|
|
598
|
+
// historical default daemon — which, when the selected profile is not the
|
|
599
|
+
// default, is a daemon the user may not even have. Hand it the same exact
|
|
600
|
+
// environment every other selected-profile command here gets.
|
|
601
|
+
const initialized = await act(`ours-fleet init for profile ${selection.id}`, async () => run('ours-fleet', ['init'], { env: exactEnv(selection.profile) }));
|
|
596
602
|
if (!initialized.ok) throw new Error('ours-fleet init failed');
|
|
597
603
|
}
|
|
598
604
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/install",
|
|
3
|
-
"version": "0.17.0-nightly.
|
|
3
|
+
"version": "0.17.0-nightly.5",
|
|
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",
|