@ours.network/install 1.0.0 → 1.0.1
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 +2 -1
- package/lib/orchestrate.mjs +14 -22
- package/lib/plan.mjs +33 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,8 @@ connector from another daemon still require explicit confirmation.
|
|
|
34
34
|
- Installs `@ours.network/cli`, `@ours.network/mcp`,
|
|
35
35
|
`@ours.network/tg-connector`, `@ours.network/cowork`, and
|
|
36
36
|
`@ours.network/fleet` on one release channel.
|
|
37
|
-
- Configures, starts, and enables the single shared daemon
|
|
37
|
+
- Configures, starts, and enables the single shared daemon with a CLI-managed
|
|
38
|
+
user systemd service on Linux or LaunchAgent on macOS.
|
|
38
39
|
- Creates the daemon's Human identity (historically called the root identity),
|
|
39
40
|
or preserves the existing one on a re-run.
|
|
40
41
|
- Installs the ours plugin into safely detected Claude Code, Codex, and Hermes
|
package/lib/orchestrate.mjs
CHANGED
|
@@ -489,11 +489,8 @@ export async function runServicePhase(args, effects, dir, port) {
|
|
|
489
489
|
const plan = planServiceInstall({
|
|
490
490
|
stateDir: dir, home: effects.home, readText: effects.readText, platform: effects.platform?.platform,
|
|
491
491
|
});
|
|
492
|
-
// NOT a refusal and NOT a failure:
|
|
493
|
-
//
|
|
494
|
-
// on any non-linux platform. The run CONTINUES, says so, and the summary marks
|
|
495
|
-
// it, because the person this hurts reboots in a fortnight and finds nothing
|
|
496
|
-
// listening.
|
|
492
|
+
// NOT a refusal and NOT a failure: this is retained for platforms with no CLI
|
|
493
|
+
// service adapter. Linux and macOS both take the normal install path.
|
|
497
494
|
if (plan.action === 'unsupported') {
|
|
498
495
|
effects.out(warn(`ours: ${plan.message}`));
|
|
499
496
|
effects.out(info(`Your daemon is installed and running now. To start it after a reboot, run: ${plan.manual.join(' ')} ${join(dir, 'config.json')}`));
|
|
@@ -507,13 +504,9 @@ export async function runServicePhase(args, effects, dir, port) {
|
|
|
507
504
|
const adopting = plan.action === 'adopt';
|
|
508
505
|
if (adopting) effects.out(info(plan.notice));
|
|
509
506
|
const command = serviceInstallCommand({ stateDir: dir, adoptLegacyUnit: adopting });
|
|
510
|
-
// The
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
// would make every re-run claim it rewrote the unit, which turns the honest
|
|
514
|
-
// "everything already correct" line into one that never appears — a screen that
|
|
515
|
-
// is wrong in the reassuring direction. So the installer does the comparison it
|
|
516
|
-
// used to delegate: it already reads this file to classify it.
|
|
507
|
+
// The service definition's bytes BEFORE are retained for Linux's established
|
|
508
|
+
// reporting path. On Darwin the CLI's JSON result is authoritative because it
|
|
509
|
+
// owns the LaunchAgent and launchctl transaction end to end.
|
|
517
510
|
const unitBefore = plan.unitPath ? effects.readText(plan.unitPath) : null;
|
|
518
511
|
let outcome;
|
|
519
512
|
try {
|
|
@@ -531,11 +524,13 @@ export async function runServicePhase(args, effects, dir, port) {
|
|
|
531
524
|
// which is the safe direction for a summary line.
|
|
532
525
|
const changed = args.dryRun
|
|
533
526
|
? false
|
|
534
|
-
:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
527
|
+
: plan.platform === 'darwin'
|
|
528
|
+
? readChanged(outcome.stdout)
|
|
529
|
+
: (() => {
|
|
530
|
+
const after = plan.unitPath ? effects.readText(plan.unitPath) : null;
|
|
531
|
+
if (unitBefore === null || after === null) return true;
|
|
532
|
+
return unitBefore !== after;
|
|
533
|
+
})();
|
|
539
534
|
return { step: { id: 'service', changed, reason: changed ? undefined : 'unit unchanged' }, plan };
|
|
540
535
|
}
|
|
541
536
|
|
|
@@ -751,9 +746,6 @@ export function runPreflight(effects) {
|
|
|
751
746
|
return { ok: false, platform: plat };
|
|
752
747
|
}
|
|
753
748
|
effects.out(ok(`Platform: ${plat.label} (supported)`));
|
|
754
|
-
if (effects.platform?.platform && effects.platform.platform !== 'linux') {
|
|
755
|
-
effects.out(info(`On ${plat.label} the daemon runs, but installing a BOOT SERVICE is not available — you will start it yourself after a reboot.`));
|
|
756
|
-
}
|
|
757
749
|
const version = String(effects.nodeVersion ?? '0');
|
|
758
750
|
if (Number.parseInt(version.split('.')[0], 10) < 20) {
|
|
759
751
|
effects.out(warn(`Node.js ${version} — ours needs v20 or newer. Update Node and re-run.`));
|
|
@@ -1123,8 +1115,8 @@ export async function runInstall(argv, effects) {
|
|
|
1123
1115
|
state: target.action === 'create' ? 'installed' : 'current',
|
|
1124
1116
|
note: `port ${target.port}`,
|
|
1125
1117
|
}];
|
|
1126
|
-
//
|
|
1127
|
-
//
|
|
1118
|
+
// A future supported runtime may still lack a CLI service adapter. Do not
|
|
1119
|
+
// render that partial state as success.
|
|
1128
1120
|
if (daemon.serviceUnsupported) {
|
|
1129
1121
|
summary.push({
|
|
1130
1122
|
key: 'service',
|
package/lib/plan.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { join, resolve, basename } from 'node:path';
|
|
|
9
9
|
export const CLI_UNIT_MARKER = '# Managed by @ours.network/cli';
|
|
10
10
|
export const SYSTEMD_USER_DIR = ['.config', 'systemd', 'user'];
|
|
11
11
|
export const DEFAULT_SYSTEMD_UNIT = 'ours.service';
|
|
12
|
+
export const DEFAULT_LAUNCHD_LABEL = 'solutions.adaptframework.ours';
|
|
12
13
|
|
|
13
14
|
// -----------------------------------------------------------------------------
|
|
14
15
|
// Which unit file does this state directory own?
|
|
@@ -46,6 +47,23 @@ export function unitPathForStateDir(stateDir, home) {
|
|
|
46
47
|
return { ...derived, path: join(home, ...SYSTEMD_USER_DIR, derived.unit) };
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Read-only launchd label derivation for accurate progress output. The CLI owns
|
|
52
|
+
* the plist path, generation, permissions, conflict handling and launchctl.
|
|
53
|
+
*
|
|
54
|
+
* SOURCE OF TRUTH IS @ours.network/cli 2.6.1 service-instance.ts. Keep this in
|
|
55
|
+
* the same table tests as the systemd derivation above.
|
|
56
|
+
*/
|
|
57
|
+
export function launchdLabelForStateDir(stateDir) {
|
|
58
|
+
const derived = unitNameForStateDir(stateDir);
|
|
59
|
+
if (!derived.ok) return derived;
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
label: derived.instance ? `${DEFAULT_LAUNCHD_LABEL}.${derived.instance}` : DEFAULT_LAUNCHD_LABEL,
|
|
63
|
+
instance: derived.instance,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
// -----------------------------------------------------------------------------
|
|
50
68
|
// The unmarked-unit case — the migration blocker
|
|
51
69
|
// -----------------------------------------------------------------------------
|
|
@@ -110,39 +128,28 @@ export function classifyUnit(text) {
|
|
|
110
128
|
* default that spreads to the other cases.
|
|
111
129
|
*/
|
|
112
130
|
export function planServiceInstall({ stateDir, home, readText, platform = 'linux' }) {
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
// `createLinuxUserSystemdAdapter()` and no platform branch at all, and that
|
|
117
|
-
// factory's FIRST line is
|
|
118
|
-
// if (deps.platform !== 'linux') throw new Error('service management is not
|
|
119
|
-
// supported on <platform>; use an external launcher for `ours daemon serve`')
|
|
120
|
-
// — verified by reading the published 0.4.1 tarball, which contains zero
|
|
121
|
-
// occurrences of launchd, LaunchAgents or plist.
|
|
122
|
-
//
|
|
123
|
-
// So calling it on macOS does not degrade, it THROWS. Before this, a Mac user
|
|
124
|
-
// was told their platform was supported, watched the CLI install, the config
|
|
125
|
-
// write and the daemon start, and then got an exception and a rolled-back
|
|
126
|
-
// config. Skipping the step leaves them a working daemon and one true sentence
|
|
127
|
-
// instead — which is the whole of this change.
|
|
128
|
-
//
|
|
129
|
-
// A real launchd adapter belongs in the SDK CLI, not here. Nothing in this
|
|
130
|
-
// package can install a launchd agent, and pretending otherwise by writing a
|
|
131
|
-
// plist ourselves would put a second service implementation in a second repo.
|
|
132
|
-
// `ours daemon install-service` supports Linux/systemd only: its adapter throws
|
|
133
|
-
// for any other platform, so calling it would fail the run rather than degrade.
|
|
134
|
-
// Skip it and say so; the daemon itself is unaffected.
|
|
135
|
-
if (platform && platform !== 'linux') {
|
|
131
|
+
// @ours.network/cli 2.6.1 dispatches this same command to user systemd on
|
|
132
|
+
// Linux and launchd on macOS. Other platforms still have no service adapter.
|
|
133
|
+
if (platform && platform !== 'linux' && platform !== 'darwin') {
|
|
136
134
|
return {
|
|
137
135
|
action: 'unsupported',
|
|
138
136
|
platform,
|
|
139
137
|
reason: 'no-service-manager',
|
|
140
|
-
message: platform
|
|
141
|
-
? 'installing a boot service is not available on macOS — the ours CLI can only manage a Linux user systemd service'
|
|
142
|
-
: `installing a boot service is not available on ${platform} — the ours CLI can only manage a Linux user systemd service`,
|
|
138
|
+
message: `installing a boot service is not available on ${platform} — the ours CLI supports Linux user systemd and macOS launchd`,
|
|
143
139
|
manual: ['ours', 'daemon', 'serve', '--config'],
|
|
144
140
|
};
|
|
145
141
|
}
|
|
142
|
+
|
|
143
|
+
if (platform === 'darwin') {
|
|
144
|
+
const derived = launchdLabelForStateDir(stateDir);
|
|
145
|
+
if (!derived.ok) {
|
|
146
|
+
return { action: 'refuse', exitCode: 2, reason: 'unusable-state-dir', message: derived.reason };
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
action: 'install', platform, unit: derived.label, instance: derived.instance,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
146
153
|
const derived = unitPathForStateDir(stateDir, home);
|
|
147
154
|
if (!derived.ok) {
|
|
148
155
|
return { action: 'refuse', exitCode: 2, reason: 'unusable-state-dir', message: derived.reason };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/install",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The all-in-one ours.network installer: one shared daemon, MCP, cowork, Telegram, Fleet, harness plugins, Human identity, stopped Fleet starter, progress UI, and guided next steps.",
|
|
6
6
|
"type": "module",
|