@lumi.ai/runner 0.6.2 → 0.6.3
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 +24 -4
- package/dist/cli.js +116 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,9 @@ Run this first when something isn't working. Everything it checks used to be dis
|
|
|
84
84
|
running job*, surfacing as a failed task on your board minutes later: a missing `claude` binary,
|
|
85
85
|
unsaved Ship credentials, a machine no captain approved. It checks Node ≥ 20, your stored session,
|
|
86
86
|
per-Ship approval and credentials, the engine binary, `git`/`gh` when an agent uses GitHub, server
|
|
87
|
-
reachability, the background service,
|
|
87
|
+
reachability, the background service, whether that service still points at a CLI that exists, and
|
|
88
|
+
whether the **service's own `PATH`** can still reach the binaries your jobs spawn — which is a
|
|
89
|
+
different question from whether your shell can (see below).
|
|
88
90
|
|
|
89
91
|
### Background service
|
|
90
92
|
|
|
@@ -99,6 +101,21 @@ reachability, the background service, and whether that service still points at a
|
|
|
99
101
|
Your `PATH` is captured into the unit at install time. launchd and systemd start processes with a
|
|
100
102
|
minimal environment, so without that `claude`, `git` and `gh` would not be found.
|
|
101
103
|
|
|
104
|
+
**It is captured once, and a restart does not refresh it.** `service restart` re-runs the unit
|
|
105
|
+
exactly as written, so a daemon keeps the `PATH` of whatever shell first installed it. Install a
|
|
106
|
+
tool somewhere new afterwards — `~/.local/bin`, a Homebrew prefix, an nvm switch — and the daemon
|
|
107
|
+
cannot see it, while every check you can run by hand (`which claude`, `lumi-runner doctor`) is
|
|
108
|
+
answered by your *current* shell and looks fine. The symptom is a job that fails minutes later
|
|
109
|
+
with `spawn claude ENOENT`.
|
|
110
|
+
|
|
111
|
+
Two things close that gap: `doctor` reports it as **Service PATH**, and `lumi-runner setup` now
|
|
112
|
+
reinstalls the service (rather than merely restarting it) when your environment has drifted from
|
|
113
|
+
the installed unit. To fix it directly, from a shell where the tool works:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
lumi-runner service install # rewrites the unit with your current PATH
|
|
117
|
+
```
|
|
118
|
+
|
|
102
119
|
## How many jobs at once
|
|
103
120
|
|
|
104
121
|
One, until you say otherwise — the same behaviour this daemon has always had.
|
|
@@ -187,8 +204,11 @@ always as a warning — being out of date never fails the preflight.
|
|
|
187
204
|
- **Idle sleep is inhibited** (`caffeinate` / `systemd-inhibit`, best-effort on Windows), so a
|
|
188
205
|
laptop doesn't suspend mid-session. It cannot veto you choosing Shut Down — no background process
|
|
189
206
|
gets that veto on macOS, and it shouldn't.
|
|
190
|
-
- **
|
|
191
|
-
stopped with work in flight
|
|
207
|
+
- **Desktop notifications are off by default.** They fire on job start, finish, terminal failure and
|
|
208
|
+
on a daemon stopped with work in flight — which on a busy machine is an interruption every few
|
|
209
|
+
minutes carrying nothing the task board and the Daemons live log do not already show. Turn them on
|
|
210
|
+
with `lumi-runner config set notifications on`. (Sleep inhibition is a separate setting and stays
|
|
211
|
+
on: "don't close the lid" is `keepAwake`, not a notification.)
|
|
192
212
|
- **SIGTERM releases the job.** The daemon aborts every running session, hands each job back to the
|
|
193
213
|
queue with its retry budget **unspent**, and only then writes itself offline. Stopping the daemon
|
|
194
214
|
never costs you an attempt.
|
|
@@ -254,7 +274,7 @@ claiming from one queue.
|
|
|
254
274
|
|---|---|
|
|
255
275
|
| `LUMI_RUNNER_HOME` | Config + log directory (default `~/.lumi-runner`) |
|
|
256
276
|
| `CREW_CLAUDE_BIN` | Path to the Claude binary (default: `claude` from `PATH`) |
|
|
257
|
-
| `CREW_NO_NOTIFY` |
|
|
277
|
+
| `CREW_NO_NOTIFY` | Force desktop notifications off, whatever the config says (they are off by default) |
|
|
258
278
|
| `CREW_NO_POWER` | Disable sleep inhibition |
|
|
259
279
|
| `LUMI_RUNNER_REGISTRY` | npm registry to check for updates (default `https://registry.npmjs.org`) |
|
|
260
280
|
| `LUMI_RUNNER_CHANNEL` | Force the update channel: `latest` or `dev` |
|
package/dist/cli.js
CHANGED
|
@@ -251,9 +251,9 @@ function str(input, key) {
|
|
|
251
251
|
const value = input[key];
|
|
252
252
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
253
253
|
}
|
|
254
|
-
function basename(
|
|
255
|
-
const parts =
|
|
256
|
-
return parts[parts.length - 1] ??
|
|
254
|
+
function basename(path9) {
|
|
255
|
+
const parts = path9.split(/[\\/]/).filter(Boolean);
|
|
256
|
+
return parts[parts.length - 1] ?? path9;
|
|
257
257
|
}
|
|
258
258
|
function hostOf(url) {
|
|
259
259
|
try {
|
|
@@ -341,8 +341,8 @@ function builtinDetail(tool, input) {
|
|
|
341
341
|
case "Write":
|
|
342
342
|
case "Edit":
|
|
343
343
|
case "MultiEdit": {
|
|
344
|
-
const
|
|
345
|
-
return
|
|
344
|
+
const path9 = str(input, "file_path");
|
|
345
|
+
return path9 ? basename(path9) : void 0;
|
|
346
346
|
}
|
|
347
347
|
case "Glob":
|
|
348
348
|
case "Grep":
|
|
@@ -759,12 +759,20 @@ function forgetShip(config2, shipId) {
|
|
|
759
759
|
function allowsLocalMcp(config2, shipId) {
|
|
760
760
|
return Array.isArray(config2.allowLocalMcp) && config2.allowLocalMcp.includes(shipId);
|
|
761
761
|
}
|
|
762
|
+
var TOGGLE_DEFAULTS = {
|
|
763
|
+
notifications: false,
|
|
764
|
+
keepAwake: true,
|
|
765
|
+
autoUpdate: true
|
|
766
|
+
};
|
|
767
|
+
function notificationsEnabled(config2) {
|
|
768
|
+
return config2?.notifications ?? TOGGLE_DEFAULTS.notifications;
|
|
769
|
+
}
|
|
762
770
|
function mcpUrl(config2) {
|
|
763
771
|
return process.env.CREW_MCP_URL || config2.mcpUrl || `https://us-central1-${config2.projectId}.cloudfunctions.net/workspaceMcp`;
|
|
764
772
|
}
|
|
765
773
|
|
|
766
774
|
// src/version.ts
|
|
767
|
-
var RUNNER_VERSION = true ? "0.6.
|
|
775
|
+
var RUNNER_VERSION = true ? "0.6.3" : "0.0.0-dev";
|
|
768
776
|
|
|
769
777
|
// src/auth.ts
|
|
770
778
|
import { signInWithCustomToken } from "firebase/auth";
|
|
@@ -1030,7 +1038,7 @@ var WINDOWS_SCRIPT = [
|
|
|
1030
1038
|
].join(" ");
|
|
1031
1039
|
function enabled() {
|
|
1032
1040
|
if (process.env.CREW_NO_NOTIFY === "1") return false;
|
|
1033
|
-
return loadConfig()
|
|
1041
|
+
return notificationsEnabled(loadConfig());
|
|
1034
1042
|
}
|
|
1035
1043
|
function notify(title, body) {
|
|
1036
1044
|
if (!enabled()) return;
|
|
@@ -1890,7 +1898,7 @@ async function claudeHealthCheck() {
|
|
|
1890
1898
|
const done = (health) => {
|
|
1891
1899
|
if (settled) return;
|
|
1892
1900
|
settled = true;
|
|
1893
|
-
resolve(health);
|
|
1901
|
+
resolve({ ...health, binary: bin });
|
|
1894
1902
|
};
|
|
1895
1903
|
let stdout = "";
|
|
1896
1904
|
const child = spawn3(bin, ["--version"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -2428,11 +2436,11 @@ function redactTranscript(transcript, knownSecrets) {
|
|
|
2428
2436
|
return out;
|
|
2429
2437
|
}
|
|
2430
2438
|
async function uploadTranscript(storage, shipId, jobId, redacted) {
|
|
2431
|
-
const
|
|
2432
|
-
await uploadBytes(storageRef(storage,
|
|
2439
|
+
const path9 = `crew/${shipId}/transcripts/${jobId}.jsonl`;
|
|
2440
|
+
await uploadBytes(storageRef(storage, path9), new TextEncoder().encode(redacted), {
|
|
2433
2441
|
contentType: "application/x-ndjson"
|
|
2434
2442
|
});
|
|
2435
|
-
return
|
|
2443
|
+
return path9;
|
|
2436
2444
|
}
|
|
2437
2445
|
function utcDay(millis) {
|
|
2438
2446
|
return new Date(millis).toISOString().slice(0, 10);
|
|
@@ -2771,6 +2779,10 @@ function serviceEnv() {
|
|
|
2771
2779
|
}
|
|
2772
2780
|
return env;
|
|
2773
2781
|
}
|
|
2782
|
+
function serviceEnvDrift(current, installed) {
|
|
2783
|
+
if (!installed) return [];
|
|
2784
|
+
return Object.keys(current).filter((name) => current[name] !== installed[name]).sort();
|
|
2785
|
+
}
|
|
2774
2786
|
function removeLegacyService() {
|
|
2775
2787
|
const removed = [];
|
|
2776
2788
|
if (process.platform === "darwin") {
|
|
@@ -2869,6 +2881,24 @@ function parsePlistCliPath(plist) {
|
|
|
2869
2881
|
const args = [...block[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => unescapeXml(m[1]));
|
|
2870
2882
|
return args.length >= 2 ? args[1] : void 0;
|
|
2871
2883
|
}
|
|
2884
|
+
function parsePlistEnv(plist) {
|
|
2885
|
+
const block = /<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/.exec(plist);
|
|
2886
|
+
if (!block) return void 0;
|
|
2887
|
+
const env = {};
|
|
2888
|
+
for (const m of block[1].matchAll(/<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/g)) {
|
|
2889
|
+
env[unescapeXml(m[1])] = unescapeXml(m[2]);
|
|
2890
|
+
}
|
|
2891
|
+
return env;
|
|
2892
|
+
}
|
|
2893
|
+
function parseSystemdEnv(unit) {
|
|
2894
|
+
const env = {};
|
|
2895
|
+
let found = false;
|
|
2896
|
+
for (const m of unit.matchAll(/^Environment="([^="]+)=([^"]*)"\s*$/gm)) {
|
|
2897
|
+
env[m[1]] = m[2];
|
|
2898
|
+
found = true;
|
|
2899
|
+
}
|
|
2900
|
+
return found ? env : void 0;
|
|
2901
|
+
}
|
|
2872
2902
|
function parseSystemdCliPath(unit) {
|
|
2873
2903
|
const line = /^ExecStart=(.*)$/m.exec(unit);
|
|
2874
2904
|
if (!line) return void 0;
|
|
@@ -2877,16 +2907,17 @@ function parseSystemdCliPath(unit) {
|
|
|
2877
2907
|
const parts = command.slice(0, -" start".length).split(" ");
|
|
2878
2908
|
return parts.length === 2 ? parts[1] : void 0;
|
|
2879
2909
|
}
|
|
2880
|
-
function execFacts(unitPath, parse) {
|
|
2910
|
+
function execFacts(unitPath, parse, parseEnv) {
|
|
2881
2911
|
let text;
|
|
2882
2912
|
try {
|
|
2883
2913
|
text = fs4.readFileSync(unitPath, "utf8");
|
|
2884
2914
|
} catch {
|
|
2885
2915
|
return {};
|
|
2886
2916
|
}
|
|
2917
|
+
const unitEnv = parseEnv(text);
|
|
2887
2918
|
const execPath = parse(text);
|
|
2888
|
-
if (!execPath || !path4.isAbsolute(execPath)) return {};
|
|
2889
|
-
return { execPath, execMissing: !fs4.existsSync(execPath) };
|
|
2919
|
+
if (!execPath || !path4.isAbsolute(execPath)) return { ...unitEnv ? { unitEnv } : {} };
|
|
2920
|
+
return { execPath, execMissing: !fs4.existsSync(execPath), ...unitEnv ? { unitEnv } : {} };
|
|
2890
2921
|
}
|
|
2891
2922
|
function systemdUnit() {
|
|
2892
2923
|
const env = serviceEnv();
|
|
@@ -2913,7 +2944,7 @@ function serviceStatus() {
|
|
|
2913
2944
|
if (process.platform === "darwin") {
|
|
2914
2945
|
const unitPath = launchAgentPath();
|
|
2915
2946
|
if (!fs4.existsSync(unitPath)) return { state: "not-installed", detail: "No LaunchAgent installed." };
|
|
2916
|
-
const exec = execFacts(unitPath, parsePlistCliPath);
|
|
2947
|
+
const exec = execFacts(unitPath, parsePlistCliPath, parsePlistEnv);
|
|
2917
2948
|
const printed = run("launchctl", ["print", `gui/${uid()}/${SERVICE_LABEL}`]);
|
|
2918
2949
|
if (!printed.ok) {
|
|
2919
2950
|
return { state: "installed", detail: "LaunchAgent present but not loaded.", unitPath, ...exec };
|
|
@@ -2934,7 +2965,7 @@ function serviceStatus() {
|
|
|
2934
2965
|
state: active.out === "active" ? "running" : "installed",
|
|
2935
2966
|
detail: `systemd user unit is ${active.out || "unknown"}.`,
|
|
2936
2967
|
unitPath,
|
|
2937
|
-
...execFacts(unitPath, parseSystemdCliPath)
|
|
2968
|
+
...execFacts(unitPath, parseSystemdCliPath, parseSystemdEnv)
|
|
2938
2969
|
};
|
|
2939
2970
|
}
|
|
2940
2971
|
if (process.platform === "win32") {
|
|
@@ -4437,7 +4468,7 @@ function glyph(level) {
|
|
|
4437
4468
|
|
|
4438
4469
|
// src/cli/commands/config.ts
|
|
4439
4470
|
var TOGGLES = {
|
|
4440
|
-
notifications: "Desktop notifications when a job starts, finishes or fails",
|
|
4471
|
+
notifications: "Desktop notifications when a job starts, finishes or fails (off by default)",
|
|
4441
4472
|
keepAwake: "Keep this machine awake while a job is running",
|
|
4442
4473
|
autoUpdate: "Install new versions of the runner by itself and restart (PRD \xA715.37)"
|
|
4443
4474
|
};
|
|
@@ -4487,7 +4518,7 @@ function parseParallel(value) {
|
|
|
4487
4518
|
async function runConfigList() {
|
|
4488
4519
|
const config2 = requireConfig();
|
|
4489
4520
|
const values = Object.fromEntries(
|
|
4490
|
-
Object.keys(TOGGLES).map((key) => [key, config2[key]
|
|
4521
|
+
Object.keys(TOGGLES).map((key) => [key, config2[key] ?? TOGGLE_DEFAULTS[key]])
|
|
4491
4522
|
);
|
|
4492
4523
|
const machine = machineCap(config2);
|
|
4493
4524
|
const ships = config2.ships.map((shipId) => ({
|
|
@@ -4646,6 +4677,8 @@ function setParallel(config2, value, ship2) {
|
|
|
4646
4677
|
|
|
4647
4678
|
// src/cli/commands/doctor.ts
|
|
4648
4679
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
4680
|
+
import fs7 from "node:fs";
|
|
4681
|
+
import path8 from "node:path";
|
|
4649
4682
|
import { collection as collection6, doc as doc8, getDoc as getDoc7, getDocs as getDocs5 } from "firebase/firestore";
|
|
4650
4683
|
|
|
4651
4684
|
// src/cli/session.ts
|
|
@@ -4717,8 +4750,36 @@ function serviceCheckFrom(status) {
|
|
|
4717
4750
|
}
|
|
4718
4751
|
return warn("service", "Background service", `Unrecognised service state: ${status.state}.`);
|
|
4719
4752
|
}
|
|
4720
|
-
function
|
|
4721
|
-
|
|
4753
|
+
function serviceBinaryCheckFrom(input) {
|
|
4754
|
+
const { status, binaries, resolves } = input;
|
|
4755
|
+
if (status.state === "not-installed" || status.state === "unsupported") return null;
|
|
4756
|
+
const pathValue = status.unitEnv?.PATH;
|
|
4757
|
+
if (pathValue === void 0) return null;
|
|
4758
|
+
const relative = [...new Set(binaries)].filter((b) => !path8.isAbsolute(b));
|
|
4759
|
+
if (relative.length === 0) return null;
|
|
4760
|
+
const missing = relative.filter((b) => !resolves(b, pathValue));
|
|
4761
|
+
const id = "service:path";
|
|
4762
|
+
const label = "Service PATH";
|
|
4763
|
+
if (missing.length === 0) {
|
|
4764
|
+
return ok(id, label, `The daemon can reach ${relative.join(", ")}.`);
|
|
4765
|
+
}
|
|
4766
|
+
return warn(
|
|
4767
|
+
id,
|
|
4768
|
+
label,
|
|
4769
|
+
`The daemon's PATH is missing ${missing.join(", ")} \u2014 it was captured when the service was installed, and jobs will fail with \`spawn ${missing[0]} ENOENT\` even though this terminal finds it.`,
|
|
4770
|
+
"Run `lumi-runner service install` from a shell where these work, to rewrite the unit with the current PATH."
|
|
4771
|
+
);
|
|
4772
|
+
}
|
|
4773
|
+
function resolvesOnPath(binary, pathValue) {
|
|
4774
|
+
for (const dir of pathValue.split(path8.delimiter)) {
|
|
4775
|
+
if (!dir) continue;
|
|
4776
|
+
try {
|
|
4777
|
+
fs7.accessSync(path8.join(dir, binary), fs7.constants.X_OK);
|
|
4778
|
+
return true;
|
|
4779
|
+
} catch {
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
return false;
|
|
4722
4783
|
}
|
|
4723
4784
|
function versionCheckFrom(input) {
|
|
4724
4785
|
const { current, latest, channel, autoUpdate } = input;
|
|
@@ -4886,14 +4947,17 @@ async function runDoctor() {
|
|
|
4886
4947
|
for (const id of shipResults.engines) engines.add(id);
|
|
4887
4948
|
}
|
|
4888
4949
|
const needsGithub = shipResults.needsGithub;
|
|
4950
|
+
const jobBinaries = [];
|
|
4889
4951
|
for (const engineId of engines) {
|
|
4890
4952
|
const health = await getDriver(engineId).healthCheck();
|
|
4953
|
+
if (health.binary) jobBinaries.push(health.binary);
|
|
4891
4954
|
checks.push(
|
|
4892
4955
|
health.ok ? ok(`engine:${engineId}`, `Engine "${engineId}"`, health.detail) : fail(`engine:${engineId}`, `Engine "${engineId}"`, health.detail, health.fix)
|
|
4893
4956
|
);
|
|
4894
4957
|
}
|
|
4895
4958
|
if (needsGithub) {
|
|
4896
4959
|
for (const binary of ["git", "gh"]) {
|
|
4960
|
+
jobBinaries.push(binary);
|
|
4897
4961
|
checks.push(
|
|
4898
4962
|
onPath(binary) ? ok(`bin:${binary}`, `\`${binary}\``, "On PATH.") : fail(
|
|
4899
4963
|
`bin:${binary}`,
|
|
@@ -4905,7 +4969,14 @@ async function runDoctor() {
|
|
|
4905
4969
|
}
|
|
4906
4970
|
}
|
|
4907
4971
|
checks.push(await checkMcp(mcpUrl(config2)));
|
|
4908
|
-
|
|
4972
|
+
const service2 = serviceStatus();
|
|
4973
|
+
checks.push(serviceCheckFrom(service2));
|
|
4974
|
+
const servicePath = serviceBinaryCheckFrom({
|
|
4975
|
+
status: service2,
|
|
4976
|
+
binaries: jobBinaries,
|
|
4977
|
+
resolves: resolvesOnPath
|
|
4978
|
+
});
|
|
4979
|
+
if (servicePath) checks.push(servicePath);
|
|
4909
4980
|
checks.push(await checkVersion(config2));
|
|
4910
4981
|
progress.stop("Checks complete.");
|
|
4911
4982
|
return report2(checks);
|
|
@@ -5161,6 +5232,17 @@ async function runServiceUninstall() {
|
|
|
5161
5232
|
say.success("Service removed. The daemon will not start on its own any more.");
|
|
5162
5233
|
return 0;
|
|
5163
5234
|
}
|
|
5235
|
+
async function runServiceRepair(reason) {
|
|
5236
|
+
const result = installService();
|
|
5237
|
+
restartService();
|
|
5238
|
+
if (isJson()) {
|
|
5239
|
+
emitJson({ repaired: true, reason, unitPath: result.unitPath, notes: result.notes, ...serviceStatus() });
|
|
5240
|
+
return 0;
|
|
5241
|
+
}
|
|
5242
|
+
say.success(`Service reinstalled (${reason}): ${result.unitPath}`);
|
|
5243
|
+
for (const note2 of result.notes) say.warn(note2);
|
|
5244
|
+
return 0;
|
|
5245
|
+
}
|
|
5164
5246
|
async function runServiceRestart() {
|
|
5165
5247
|
restartService();
|
|
5166
5248
|
if (isJson()) {
|
|
@@ -5185,20 +5267,20 @@ async function runServiceStatus() {
|
|
|
5185
5267
|
}
|
|
5186
5268
|
|
|
5187
5269
|
// src/cli/commands/uninstall.ts
|
|
5188
|
-
import
|
|
5270
|
+
import fs8 from "node:fs";
|
|
5189
5271
|
async function runUninstall(options) {
|
|
5190
5272
|
const before = serviceStatus();
|
|
5191
5273
|
const dir = configDir();
|
|
5192
5274
|
const hadService = before.state !== "not-installed" && before.state !== "unsupported";
|
|
5193
5275
|
if (hadService) uninstallService();
|
|
5194
5276
|
let purged = false;
|
|
5195
|
-
if (options.purge &&
|
|
5277
|
+
if (options.purge && fs8.existsSync(dir)) {
|
|
5196
5278
|
const confirmed = await promptConfirm({
|
|
5197
5279
|
message: `Delete ${dir}? This machine loses its identity \u2014 a captain has to approve it again after reinstalling.`,
|
|
5198
5280
|
initialValue: false
|
|
5199
5281
|
});
|
|
5200
5282
|
if (confirmed) {
|
|
5201
|
-
|
|
5283
|
+
fs8.rmSync(dir, { recursive: true, force: true });
|
|
5202
5284
|
purged = true;
|
|
5203
5285
|
}
|
|
5204
5286
|
}
|
|
@@ -5335,7 +5417,8 @@ async function runSetup(options) {
|
|
|
5335
5417
|
}
|
|
5336
5418
|
say.step("Checking this machine\u2026");
|
|
5337
5419
|
const doctorExit = await runDoctor();
|
|
5338
|
-
const
|
|
5420
|
+
const status = serviceStatus();
|
|
5421
|
+
const service2 = status.state;
|
|
5339
5422
|
if (service2 === "not-installed") {
|
|
5340
5423
|
const install = await promptConfirm({
|
|
5341
5424
|
message: "Start the daemon automatically whenever this machine is on?",
|
|
@@ -5345,8 +5428,14 @@ async function runSetup(options) {
|
|
|
5345
5428
|
if (install) await runServiceInstall();
|
|
5346
5429
|
else say.info("Skipped. Run `lumi-runner start` manually, or `lumi-runner service install` later.");
|
|
5347
5430
|
} else if (service2 === "running" || service2 === "installed") {
|
|
5348
|
-
|
|
5349
|
-
|
|
5431
|
+
const drift = serviceEnvDrift(serviceEnv(), status.unitEnv);
|
|
5432
|
+
if (drift.length > 0) {
|
|
5433
|
+
say.step(`Reinstalling the service \u2014 ${drift.join(", ")} changed since it was installed\u2026`);
|
|
5434
|
+
await runServiceRepair(`${drift.join(", ")} changed`);
|
|
5435
|
+
} else {
|
|
5436
|
+
say.step("Restarting the daemon so it picks up this configuration\u2026");
|
|
5437
|
+
await runServiceRestart();
|
|
5438
|
+
}
|
|
5350
5439
|
}
|
|
5351
5440
|
say.outro(
|
|
5352
5441
|
doctorExit === 0 ? "Ready. This machine will claim jobs for its Ships." : "Setup finished, but some checks failed \u2014 fix those and re-run `lumi-runner doctor`."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumi.ai/runner",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lumi Crew runner daemon — claims jobs from your Ships and executes them as headless Claude sessions on your own machine.",
|
|
6
6
|
"//name": "The ONLY package in this monorepo published to the public registry, so it is the one that does not follow the internal @lumi/crew-* convention: `@lumi` is not a scope we own, `@lumi.ai` is (the npm org). The workspace DIRECTORY stays packages/crew/runner — renaming the package is not renaming the folder.",
|