@kilogent/runner 0.1.4 → 0.1.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/dist/cli.js +200 -85
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1277,7 +1277,7 @@ import os from "node:os";
|
|
|
1277
1277
|
import path from "node:path";
|
|
1278
1278
|
|
|
1279
1279
|
// src/version.ts
|
|
1280
|
-
var RUNNER_VERSION = true ? "0.1.
|
|
1280
|
+
var RUNNER_VERSION = true ? "0.1.5" : "0.0.0-dev";
|
|
1281
1281
|
var RUNNER_PACKAGE = true ? "@kilogent/runner" : "@kilogent/runner-dev";
|
|
1282
1282
|
var RUNNER_BIN = true ? "kilogent-runner" : "kilogent-runner-dev";
|
|
1283
1283
|
|
|
@@ -3273,10 +3273,10 @@ async function claudeHealthCheck(binOverride) {
|
|
|
3273
3273
|
const bin = binOverride ?? claudeBinary();
|
|
3274
3274
|
return new Promise((resolve) => {
|
|
3275
3275
|
let settled = false;
|
|
3276
|
-
const done = (
|
|
3276
|
+
const done = (health2) => {
|
|
3277
3277
|
if (settled) return;
|
|
3278
3278
|
settled = true;
|
|
3279
|
-
resolve({ ...
|
|
3279
|
+
resolve({ ...health2, binary: bin });
|
|
3280
3280
|
};
|
|
3281
3281
|
let stdout = "";
|
|
3282
3282
|
const child = spawn3(bin, ["--version"], { env: withNodeOnPath(), stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -3806,10 +3806,10 @@ async function codexHealthCheck(binOverride) {
|
|
|
3806
3806
|
const bin = binOverride ?? codexBinary();
|
|
3807
3807
|
return new Promise((resolve) => {
|
|
3808
3808
|
let settled = false;
|
|
3809
|
-
const done = (
|
|
3809
|
+
const done = (health2) => {
|
|
3810
3810
|
if (settled) return;
|
|
3811
3811
|
settled = true;
|
|
3812
|
-
resolve({ ...
|
|
3812
|
+
resolve({ ...health2, binary: bin });
|
|
3813
3813
|
};
|
|
3814
3814
|
let stdout = "";
|
|
3815
3815
|
const child = spawn4(bin, ["--version"], { env: withNodeOnPath(), stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -4026,7 +4026,7 @@ function filterLocal(servers, input) {
|
|
|
4026
4026
|
continue;
|
|
4027
4027
|
}
|
|
4028
4028
|
input.log(
|
|
4029
|
-
`MCP connection "${server.key}" skipped: local MCP is off for this Ship on this machine \u2014 run
|
|
4029
|
+
`MCP connection "${server.key}" skipped: local MCP is off for this Ship on this machine \u2014 run \`${RUNNER_BIN} config set local-mcp on --ship ${input.shipId}\` here to allow it.`
|
|
4030
4030
|
);
|
|
4031
4031
|
}
|
|
4032
4032
|
return kept;
|
|
@@ -4051,7 +4051,7 @@ async function probeAndReport(input) {
|
|
|
4051
4051
|
await report(input, {
|
|
4052
4052
|
ok: false,
|
|
4053
4053
|
toolNames: [],
|
|
4054
|
-
error: `This machine has not allowed this Ship to run local MCP connections. On the machine, run:
|
|
4054
|
+
error: `This machine has not allowed this Ship to run local MCP connections. On the machine, run: ${RUNNER_BIN} config set local-mcp on --ship ${shipId}`
|
|
4055
4055
|
});
|
|
4056
4056
|
return;
|
|
4057
4057
|
}
|
|
@@ -4108,7 +4108,7 @@ async function probeStdio(server) {
|
|
|
4108
4108
|
send(child, 1, "initialize", {
|
|
4109
4109
|
protocolVersion: PROTOCOL_VERSION,
|
|
4110
4110
|
capabilities: {},
|
|
4111
|
-
clientInfo: { name:
|
|
4111
|
+
clientInfo: { name: RUNNER_BIN, version: RUNNER_VERSION }
|
|
4112
4112
|
});
|
|
4113
4113
|
await replies.waitFor(1, () => stderr);
|
|
4114
4114
|
child.stdin.write(
|
|
@@ -4148,7 +4148,7 @@ async function probeHttp(server) {
|
|
|
4148
4148
|
await post(1, "initialize", {
|
|
4149
4149
|
protocolVersion: PROTOCOL_VERSION,
|
|
4150
4150
|
capabilities: {},
|
|
4151
|
-
clientInfo: { name:
|
|
4151
|
+
clientInfo: { name: RUNNER_BIN, version: RUNNER_VERSION }
|
|
4152
4152
|
});
|
|
4153
4153
|
return toolNamesFrom(await post(2, "tools/list", {}));
|
|
4154
4154
|
}
|
|
@@ -5249,9 +5249,9 @@ async function installEngine(input) {
|
|
|
5249
5249
|
return { ok: false, ran: true, detail: installed.detail };
|
|
5250
5250
|
}
|
|
5251
5251
|
const bin = engineBinPath(root, engineId, pkg.binName);
|
|
5252
|
-
const
|
|
5253
|
-
if (!
|
|
5254
|
-
const detail =
|
|
5252
|
+
const health2 = await input.healthCheck(bin);
|
|
5253
|
+
if (!health2.ok || health2.binary !== bin) {
|
|
5254
|
+
const detail = health2.ok ? `npm reported success, but the check ran ${health2.binary ?? "something else"} rather than ${bin} \u2014 the install did not land where this runner looks.` : health2.detail;
|
|
5255
5255
|
recordEngineInstall(root, engineId, { outcome: "failed", at: Date.now(), detail: detail.slice(0, 400) });
|
|
5256
5256
|
return { ok: false, ran: true, detail };
|
|
5257
5257
|
}
|
|
@@ -5378,6 +5378,7 @@ function decideUpdate(f) {
|
|
|
5378
5378
|
if (f.current === DEV_VERSION) return { update: false, reason: "dev-build" };
|
|
5379
5379
|
if (!f.autoUpdate) return { update: false, reason: "disabled" };
|
|
5380
5380
|
const service2 = f.service();
|
|
5381
|
+
if (service2.state === "unsupported") return { update: false, reason: "service-unsupported" };
|
|
5381
5382
|
if (service2.state !== "running") return { update: false, reason: "service-not-running" };
|
|
5382
5383
|
if (!service2.execPath || service2.execPath !== f.cliPath) {
|
|
5383
5384
|
return { update: false, reason: "foreign-unit" };
|
|
@@ -5401,6 +5402,8 @@ function describeRefusal(reason, f) {
|
|
|
5401
5402
|
return `Auto-update is off (\`${RUNNER_BIN} config set autoUpdate on\` to enable).`;
|
|
5402
5403
|
case "service-not-running":
|
|
5403
5404
|
return `Auto-update needs the background service (this daemon would have nothing to restart it). Run \`${RUNNER_BIN} service install\`, or \`${RUNNER_BIN} update\` by hand.`;
|
|
5405
|
+
case "service-unsupported":
|
|
5406
|
+
return `This machine has no service supervisor, so a daemon that exited would not come back \u2014 auto-update is off for it. Run \`${RUNNER_BIN} update\` and restart it yourself.`;
|
|
5404
5407
|
case "foreign-unit":
|
|
5405
5408
|
return `This process is not the one the installed service runs, so auto-update is off for it. Run \`${RUNNER_BIN} update\` if you meant to upgrade the installed copy.`;
|
|
5406
5409
|
case "no-npm":
|
|
@@ -5523,7 +5526,13 @@ async function refreshEngineTools(input) {
|
|
|
5523
5526
|
for (const engineId of input.engineIds) {
|
|
5524
5527
|
if (seen.has(engineId)) continue;
|
|
5525
5528
|
seen.add(engineId);
|
|
5526
|
-
|
|
5529
|
+
try {
|
|
5530
|
+
out.push(await refreshOne(engineId, input, now));
|
|
5531
|
+
} catch (e) {
|
|
5532
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
5533
|
+
input.log(`Engine "${engineId}" could not be checked: ${detail}`);
|
|
5534
|
+
out.push({ engineId, state: "failed", detail: detail.slice(0, 300) });
|
|
5535
|
+
}
|
|
5527
5536
|
}
|
|
5528
5537
|
return out;
|
|
5529
5538
|
}
|
|
@@ -5550,7 +5559,7 @@ async function refreshOne(engineId, input, now) {
|
|
|
5550
5559
|
target: choice.version,
|
|
5551
5560
|
platform: process.platform,
|
|
5552
5561
|
autoInstall: input.autoInstall,
|
|
5553
|
-
npmPresent: npmPresentOnce(),
|
|
5562
|
+
npmPresent: input.npmPresent ?? npmPresentOnce(),
|
|
5554
5563
|
marker,
|
|
5555
5564
|
locked: false,
|
|
5556
5565
|
record: record ?? null,
|
|
@@ -5561,17 +5570,17 @@ async function refreshOne(engineId, input, now) {
|
|
|
5561
5570
|
return describeRefused(engineId, input, marker, decision.reason);
|
|
5562
5571
|
}
|
|
5563
5572
|
input.onState?.({ engineId, state: "installing", version: choice.version });
|
|
5564
|
-
const outcome = await installEngine({
|
|
5573
|
+
const outcome = await (input.install ?? installEngine)({
|
|
5565
5574
|
root,
|
|
5566
5575
|
engineId,
|
|
5567
5576
|
version: choice.version,
|
|
5568
|
-
healthCheck: (bin) =>
|
|
5577
|
+
healthCheck: (bin) => health(input, engineId, bin),
|
|
5569
5578
|
log: log2,
|
|
5570
5579
|
now
|
|
5571
5580
|
});
|
|
5572
5581
|
if (outcome.ok) return { engineId, state: "ready", version: choice.version };
|
|
5573
5582
|
log2(`Could not install "${engineId}": ${outcome.detail.slice(0, 200)}`);
|
|
5574
|
-
const fallback = await unmanagedVersion(engineId);
|
|
5583
|
+
const fallback = await unmanagedVersion(input, engineId);
|
|
5575
5584
|
if (fallback && !policy.blocked.includes(fallback)) {
|
|
5576
5585
|
return { engineId, state: "unmanaged", version: fallback, detail: "Using a copy already on this machine \u2014 this runner cannot update or roll it back." };
|
|
5577
5586
|
}
|
|
@@ -5612,11 +5621,14 @@ function npmPresentOnce() {
|
|
|
5612
5621
|
return answer;
|
|
5613
5622
|
};
|
|
5614
5623
|
}
|
|
5615
|
-
|
|
5624
|
+
function health(input, engineId, bin) {
|
|
5625
|
+
return input.driverHealth ? input.driverHealth(engineId, bin) : getDriver(engineId).healthCheck(bin);
|
|
5626
|
+
}
|
|
5627
|
+
async function unmanagedVersion(input, engineId) {
|
|
5616
5628
|
try {
|
|
5617
|
-
const
|
|
5618
|
-
if (!
|
|
5619
|
-
const match = /(\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?)/.exec(
|
|
5629
|
+
const probe2 = await health(input, engineId);
|
|
5630
|
+
if (!probe2.ok) return null;
|
|
5631
|
+
const match = /(\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?)/.exec(probe2.detail ?? "");
|
|
5620
5632
|
return match?.[1] ?? null;
|
|
5621
5633
|
} catch {
|
|
5622
5634
|
return null;
|
|
@@ -5632,6 +5644,9 @@ function engineSet(engineIds) {
|
|
|
5632
5644
|
if (engines.size === 0) engines.add(DEFAULT_ENGINE_ID);
|
|
5633
5645
|
return engines;
|
|
5634
5646
|
}
|
|
5647
|
+
function enginesForCredentials(credentials) {
|
|
5648
|
+
return [...new Set(credentials.map((c) => c.engine).filter((id) => isKnownEngine(id)))];
|
|
5649
|
+
}
|
|
5635
5650
|
|
|
5636
5651
|
// src/service.ts
|
|
5637
5652
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -5658,6 +5673,15 @@ function run(command, args) {
|
|
|
5658
5673
|
out: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim()
|
|
5659
5674
|
};
|
|
5660
5675
|
}
|
|
5676
|
+
function systemdPresent() {
|
|
5677
|
+
try {
|
|
5678
|
+
if (!fs12.existsSync("/run/systemd/system")) return false;
|
|
5679
|
+
const probe2 = process.platform === "win32" ? "where" : "which";
|
|
5680
|
+
return spawnSync2(probe2, ["systemctl"], { stdio: "ignore" }).status === 0;
|
|
5681
|
+
} catch {
|
|
5682
|
+
return false;
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5661
5685
|
function cliPath() {
|
|
5662
5686
|
return fileURLToPath(import.meta.url);
|
|
5663
5687
|
}
|
|
@@ -5840,6 +5864,21 @@ ${envLines}
|
|
|
5840
5864
|
WantedBy=default.target
|
|
5841
5865
|
`;
|
|
5842
5866
|
}
|
|
5867
|
+
function linuxStatusFrom(facts) {
|
|
5868
|
+
if (!facts.systemdPresent) {
|
|
5869
|
+
return {
|
|
5870
|
+
state: "unsupported",
|
|
5871
|
+
detail: "This machine has no systemd, so there is no background service to install \u2014 it is a container, or a Linux that does not use one."
|
|
5872
|
+
};
|
|
5873
|
+
}
|
|
5874
|
+
if (!facts.unitExists) return { state: "not-installed", detail: "No systemd user unit installed." };
|
|
5875
|
+
return {
|
|
5876
|
+
state: facts.isActive === "active" ? "running" : "installed",
|
|
5877
|
+
detail: `systemd user unit is ${facts.isActive || "unknown"}.`,
|
|
5878
|
+
unitPath: facts.unitPath,
|
|
5879
|
+
...facts.exec ?? {}
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5843
5882
|
function serviceStatus() {
|
|
5844
5883
|
if (process.platform === "darwin") {
|
|
5845
5884
|
const unitPath = launchAgentPath();
|
|
@@ -5859,14 +5898,16 @@ function serviceStatus() {
|
|
|
5859
5898
|
}
|
|
5860
5899
|
if (process.platform === "linux") {
|
|
5861
5900
|
const unitPath = systemdUnitPath();
|
|
5862
|
-
|
|
5863
|
-
const
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5901
|
+
const present = systemdPresent();
|
|
5902
|
+
const unitExists = fs12.existsSync(unitPath);
|
|
5903
|
+
const isActive = present && unitExists ? run("systemctl", ["--user", "is-active", LINUX_UNIT]).out : "";
|
|
5904
|
+
return linuxStatusFrom({
|
|
5905
|
+
systemdPresent: present,
|
|
5906
|
+
unitExists,
|
|
5907
|
+
isActive,
|
|
5867
5908
|
unitPath,
|
|
5868
|
-
|
|
5869
|
-
};
|
|
5909
|
+
exec: unitExists ? execFacts(unitPath, parseSystemdCliPath, parseSystemdEnv) : void 0
|
|
5910
|
+
});
|
|
5870
5911
|
}
|
|
5871
5912
|
if (process.platform === "win32") {
|
|
5872
5913
|
const query7 = run("schtasks", ["/Query", "/TN", WINDOWS_TASK]);
|
|
@@ -5880,6 +5921,11 @@ function serviceStatus() {
|
|
|
5880
5921
|
return { state: "unsupported", detail: `No service integration for ${process.platform}.` };
|
|
5881
5922
|
}
|
|
5882
5923
|
function installService() {
|
|
5924
|
+
if (process.platform === "linux" && !systemdPresent()) {
|
|
5925
|
+
throw new ServiceError(
|
|
5926
|
+
`This machine has no systemd, so there is no background service to install. Run \`${RUNNER_BIN} start\` to keep the daemon up \u2014 in Docker, make that the container's command.`
|
|
5927
|
+
);
|
|
5928
|
+
}
|
|
5883
5929
|
const notes = [];
|
|
5884
5930
|
fs12.mkdirSync(logDir(), { recursive: true, mode: 448 });
|
|
5885
5931
|
for (const unit of removeLegacyService()) {
|
|
@@ -6180,6 +6226,7 @@ async function startDaemon() {
|
|
|
6180
6226
|
const shipCredentials = /* @__PURE__ */ new Map();
|
|
6181
6227
|
const heldCredentialIds = /* @__PURE__ */ new Map();
|
|
6182
6228
|
const loginsInFlight = /* @__PURE__ */ new Map();
|
|
6229
|
+
const enginesBeingEnsured = /* @__PURE__ */ new Set();
|
|
6183
6230
|
const unsubsByShip = /* @__PURE__ */ new Map();
|
|
6184
6231
|
const listenerError = (shipId, what) => (e) => {
|
|
6185
6232
|
if (isEnrolmentGone(e)) {
|
|
@@ -6355,6 +6402,15 @@ async function startDaemon() {
|
|
|
6355
6402
|
ensureEngine: (engineId) => ensureEngineForCaller(engineId)
|
|
6356
6403
|
}).catch((e) => log2(`Sign-in for ${credential.id} ended abnormally: ${e instanceof Error ? e.message : e}`)).finally(() => loginsInFlight.delete(credential.id));
|
|
6357
6404
|
}
|
|
6405
|
+
for (const engineId of enginesForCredentials(list)) {
|
|
6406
|
+
if (enginesBeingEnsured.has(engineId)) continue;
|
|
6407
|
+
const known = engineToolStates.get(engineId)?.state;
|
|
6408
|
+
if (known === "ready" || known === "installing" || known === "unmanaged") continue;
|
|
6409
|
+
enginesBeingEnsured.add(engineId);
|
|
6410
|
+
void ensureEngineForCaller(engineId).then((result) => {
|
|
6411
|
+
if (!result.ok) log2(`Engine "${engineId}" is named by a credential on Ship ${shipId}: ${result.detail}`);
|
|
6412
|
+
}).catch((e) => log2(`Could not install "${engineId}": ${e instanceof Error ? e.message : e}`)).finally(() => enginesBeingEnsured.delete(engineId));
|
|
6413
|
+
}
|
|
6358
6414
|
poke();
|
|
6359
6415
|
},
|
|
6360
6416
|
// ITS OWN ERROR HANDLER, not `listenerError`, for the reason the mcp_servers listener
|
|
@@ -7319,7 +7375,10 @@ ${harder.transcript}`
|
|
|
7319
7375
|
if (shipIds.length === 0) return;
|
|
7320
7376
|
checkingEngines = true;
|
|
7321
7377
|
try {
|
|
7322
|
-
const engineIds = engineSet(
|
|
7378
|
+
const engineIds = engineSet([
|
|
7379
|
+
...agentEngines.values(),
|
|
7380
|
+
...[...shipCredentials.values()].flatMap((list) => enginesForCredentials(list))
|
|
7381
|
+
]);
|
|
7323
7382
|
const firstShip = shipIds[0];
|
|
7324
7383
|
const policySnap = await getDoc8(
|
|
7325
7384
|
doc9(sess(firstShip).fb.db, COLLECTIONS.crewConfig, CONFIG_DOCS.engines)
|
|
@@ -7707,12 +7766,12 @@ function setUpdateSetting(config2, key, value, ship2) {
|
|
|
7707
7766
|
function setLocalMcp(config2, value, ship2) {
|
|
7708
7767
|
if (!ship2) {
|
|
7709
7768
|
throw new CliError(
|
|
7710
|
-
`"${LOCAL_MCP}" is granted one Ship at a time:
|
|
7769
|
+
`"${LOCAL_MCP}" is granted one Ship at a time: \`${RUNNER_BIN} config set ${LOCAL_MCP} ${value} --ship <shipId>\`. There is no machine-wide form \u2014 allowing a Ship to run programs here should be a decision about that Ship. \`${RUNNER_BIN} ship list\` shows the ids.`
|
|
7711
7770
|
);
|
|
7712
7771
|
}
|
|
7713
7772
|
if (!config2.ships.includes(ship2)) {
|
|
7714
7773
|
throw new CliError(
|
|
7715
|
-
`This machine does not serve Ship "${ship2}". Run
|
|
7774
|
+
`This machine does not serve Ship "${ship2}". Run \`${RUNNER_BIN} ship list\` to see which Ships it serves, or \`${RUNNER_BIN} ship add ${ship2}\` to add it.`
|
|
7716
7775
|
);
|
|
7717
7776
|
}
|
|
7718
7777
|
const allowed = parseBool(value);
|
|
@@ -7732,7 +7791,7 @@ function setLocalMcp(config2, value, ship2) {
|
|
|
7732
7791
|
" This Ship may now start MCP servers as programs on this machine, and reach servers on its loopback address. Only the connections a captain registered on that Ship, and only the ones activated on the agent running the job."
|
|
7733
7792
|
);
|
|
7734
7793
|
}
|
|
7735
|
-
say.line(
|
|
7794
|
+
say.line(` Restart the daemon for this to take effect: \`${RUNNER_BIN} service restart\`.`);
|
|
7736
7795
|
return 0;
|
|
7737
7796
|
}
|
|
7738
7797
|
function setParallel(config2, value, ship2) {
|
|
@@ -7740,7 +7799,7 @@ function setParallel(config2, value, ship2) {
|
|
|
7740
7799
|
if (ship2) {
|
|
7741
7800
|
if (!config2.ships.includes(ship2)) {
|
|
7742
7801
|
throw new CliError(
|
|
7743
|
-
`This machine does not serve Ship "${ship2}". Run
|
|
7802
|
+
`This machine does not serve Ship "${ship2}". Run \`${RUNNER_BIN} ship list\` to see which Ships it serves, or \`${RUNNER_BIN} ship add ${ship2}\` to add it.`
|
|
7744
7803
|
);
|
|
7745
7804
|
}
|
|
7746
7805
|
const overrides = { ...config2.shipParallelJobs ?? {} };
|
|
@@ -7764,10 +7823,10 @@ function setParallel(config2, value, ship2) {
|
|
|
7764
7823
|
);
|
|
7765
7824
|
if (ship2 && parsed !== null && parsed > machine) {
|
|
7766
7825
|
say.line(
|
|
7767
|
-
` Capped at the machine-wide limit of ${machine}. Raise it with
|
|
7826
|
+
` Capped at the machine-wide limit of ${machine}. Raise it with \`${RUNNER_BIN} config set ${PARALLEL} ${parsed}\`.`
|
|
7768
7827
|
);
|
|
7769
7828
|
}
|
|
7770
|
-
say.line(
|
|
7829
|
+
say.line(` Restart the daemon for this to take effect: \`${RUNNER_BIN} service restart\`.`);
|
|
7771
7830
|
return 0;
|
|
7772
7831
|
}
|
|
7773
7832
|
|
|
@@ -7827,20 +7886,27 @@ function serviceCheckFrom(status) {
|
|
|
7827
7886
|
"service",
|
|
7828
7887
|
"Background service",
|
|
7829
7888
|
`Installed, but the unit points at a CLI that no longer exists (${status.execPath}). It is registered and retrying forever without ever starting.`,
|
|
7830
|
-
|
|
7889
|
+
`Run \`${RUNNER_BIN} uninstall\` to remove it, or \`${RUNNER_BIN} service install\` to repoint it at this install.`
|
|
7831
7890
|
);
|
|
7832
7891
|
}
|
|
7833
7892
|
if (status.state === "running") return ok("service", "Background service", status.detail);
|
|
7834
7893
|
if (status.state === "installed") {
|
|
7835
|
-
return warn("service", "Background service", status.detail,
|
|
7894
|
+
return warn("service", "Background service", status.detail, `Run \`${RUNNER_BIN} service restart\`.`);
|
|
7895
|
+
}
|
|
7896
|
+
if (status.state === "unsupported") {
|
|
7897
|
+
return warn(
|
|
7898
|
+
"service",
|
|
7899
|
+
"Background service",
|
|
7900
|
+
status.detail,
|
|
7901
|
+
`Run \`${RUNNER_BIN} start\` to keep this machine online \u2014 in Docker, make that the container's command.`
|
|
7902
|
+
);
|
|
7836
7903
|
}
|
|
7837
|
-
if (status.state === "unsupported") return warn("service", "Background service", status.detail);
|
|
7838
7904
|
if (status.state === "not-installed") {
|
|
7839
7905
|
return warn(
|
|
7840
7906
|
"service",
|
|
7841
7907
|
"Background service",
|
|
7842
7908
|
"Not installed \u2014 the daemon only runs while a terminal is open.",
|
|
7843
|
-
|
|
7909
|
+
`Run \`${RUNNER_BIN} service install\` to keep this Ship online whenever the machine is.`
|
|
7844
7910
|
);
|
|
7845
7911
|
}
|
|
7846
7912
|
return warn("service", "Background service", `Unrecognised service state: ${status.state}.`);
|
|
@@ -7862,7 +7928,7 @@ function serviceBinaryCheckFrom(input) {
|
|
|
7862
7928
|
id,
|
|
7863
7929
|
label,
|
|
7864
7930
|
`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.`,
|
|
7865
|
-
|
|
7931
|
+
`Run \`${RUNNER_BIN} service install\` from a shell where these work, to rewrite the unit with the current PATH.`
|
|
7866
7932
|
);
|
|
7867
7933
|
}
|
|
7868
7934
|
function resolvesOnPath(binary, pathValue) {
|
|
@@ -7903,7 +7969,7 @@ function localMcpCheckFrom(shipId, servers, allowed) {
|
|
|
7903
7969
|
id,
|
|
7904
7970
|
label,
|
|
7905
7971
|
`${local.length} connection(s) run on this machine and are not allowed here: ${names}.`,
|
|
7906
|
-
|
|
7972
|
+
`${RUNNER_BIN} config set local-mcp on --ship ${shipId}`
|
|
7907
7973
|
);
|
|
7908
7974
|
}
|
|
7909
7975
|
async function checkVersion(config2) {
|
|
@@ -7922,7 +7988,7 @@ async function checkShips(config2) {
|
|
|
7922
7988
|
const manyIdentities = configRunnerIdentities(config2).length > 1;
|
|
7923
7989
|
if (config2.ships.length === 0) {
|
|
7924
7990
|
checks.push(
|
|
7925
|
-
fail("ships", "Ships", "This machine serves no Ships.",
|
|
7991
|
+
fail("ships", "Ships", "This machine serves no Ships.", `Run \`${RUNNER_BIN} ship add\` to pick one.`)
|
|
7926
7992
|
);
|
|
7927
7993
|
return { checks, engines, needsGithub };
|
|
7928
7994
|
}
|
|
@@ -7937,7 +8003,7 @@ async function checkShips(config2) {
|
|
|
7937
8003
|
`key:${shipId}`,
|
|
7938
8004
|
`Ship ${shipId} \u2014 key`,
|
|
7939
8005
|
e instanceof Error ? e.message : String(e),
|
|
7940
|
-
|
|
8006
|
+
`Ask a captain to approve this machine on that Ship's Daemons page, then re-run \`${RUNNER_BIN} login\`.`
|
|
7941
8007
|
)
|
|
7942
8008
|
);
|
|
7943
8009
|
continue;
|
|
@@ -7952,7 +8018,7 @@ async function checkShips(config2) {
|
|
|
7952
8018
|
`approval:${shipId}`,
|
|
7953
8019
|
`Ship ${shipId} \u2014 approval`,
|
|
7954
8020
|
"This machine has no record on that Ship.",
|
|
7955
|
-
|
|
8021
|
+
`Re-run \`${RUNNER_BIN} login\` \u2014 approving is what enrols it.`
|
|
7956
8022
|
)
|
|
7957
8023
|
);
|
|
7958
8024
|
} else if (snap.data().approved !== true) {
|
|
@@ -7989,11 +8055,11 @@ async function checkShips(config2) {
|
|
|
7989
8055
|
agents = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
7990
8056
|
} catch {
|
|
7991
8057
|
}
|
|
7992
|
-
const
|
|
8058
|
+
const credentials = await loadShipCredentials(session.fb.db, shipId).catch(() => []);
|
|
8059
|
+
const shipEngines = engineSet([...enginesForAgents(agents), ...enginesForCredentials(credentials)]);
|
|
7993
8060
|
for (const id of shipEngines) engines.add(id);
|
|
7994
8061
|
if (agents.some((agent) => effectiveAgentTools(agent).github.enabled)) needsGithub = true;
|
|
7995
8062
|
try {
|
|
7996
|
-
const credentials = await loadShipCredentials(session.fb.db, shipId).catch(() => []);
|
|
7997
8063
|
const missing = [
|
|
7998
8064
|
...new Set(
|
|
7999
8065
|
[...shipEngines].flatMap(
|
|
@@ -8032,8 +8098,8 @@ async function checkShips(config2) {
|
|
|
8032
8098
|
);
|
|
8033
8099
|
}
|
|
8034
8100
|
try {
|
|
8035
|
-
const
|
|
8036
|
-
for (const cred of
|
|
8101
|
+
const credentials2 = await loadShipCredentials(session.fb.db, shipId).catch(() => []);
|
|
8102
|
+
for (const cred of credentials2) {
|
|
8037
8103
|
if (!isMachineHeldCredential(cred) || cred.runnerId !== session.runnerId) continue;
|
|
8038
8104
|
if (!isMachineLoginEngine(cred.engine)) continue;
|
|
8039
8105
|
const driver = getDriver(cred.engine);
|
|
@@ -8098,7 +8164,7 @@ async function runDoctor() {
|
|
|
8098
8164
|
const config2 = loadConfig();
|
|
8099
8165
|
if (!config2) {
|
|
8100
8166
|
checks.push(
|
|
8101
|
-
fail("config", "Configuration", "This machine is not connected.",
|
|
8167
|
+
fail("config", "Configuration", "This machine is not connected.", `Run \`${RUNNER_BIN} setup\`.`)
|
|
8102
8168
|
);
|
|
8103
8169
|
return report2(checks);
|
|
8104
8170
|
}
|
|
@@ -8122,19 +8188,26 @@ async function runDoctor() {
|
|
|
8122
8188
|
const needsGithub = shipResults.needsGithub;
|
|
8123
8189
|
const jobBinaries = [];
|
|
8124
8190
|
for (const engineId of engines) {
|
|
8125
|
-
const
|
|
8126
|
-
if (
|
|
8191
|
+
const health2 = await getDriver(engineId).healthCheck();
|
|
8192
|
+
if (health2.binary) jobBinaries.push(health2.binary);
|
|
8127
8193
|
const managed = managedEngineBin(engineId, {});
|
|
8128
8194
|
const version = managed ? readEngineMarker(configDir(), engineId)?.version : null;
|
|
8129
|
-
const origin = managed ? ` \u2014 installed by this runner${version ? ` (${version})` : ""}` :
|
|
8195
|
+
const origin = managed ? ` \u2014 installed by this runner${version ? ` (${version})` : ""}` : health2.binary && health2.binary !== engineId ? " \u2014 from CREW_*_BIN" : " \u2014 already on this machine";
|
|
8130
8196
|
checks.push(
|
|
8131
|
-
|
|
8197
|
+
health2.ok ? ok(`engine:${engineId}`, `Engine "${engineId}"`, `${health2.detail}${origin}`) : fail(
|
|
8132
8198
|
`engine:${engineId}`,
|
|
8133
8199
|
`Engine "${engineId}"`,
|
|
8134
|
-
|
|
8200
|
+
health2.detail,
|
|
8135
8201
|
// A way forward the operator can type, ahead of the vendor's download page: this
|
|
8136
8202
|
// runner installs it itself, and the old message never said so.
|
|
8137
|
-
|
|
8203
|
+
//
|
|
8204
|
+
// THE AUTOMATIC PATH GOES FIRST since §15.85, because on a fresh machine it is the one
|
|
8205
|
+
// that will actually happen and the old wording hid it. `doctor` runs BEFORE the daemon
|
|
8206
|
+
// exists during `setup`, so this row is guaranteed to fail there — and an operator who
|
|
8207
|
+
// read "N check(s) failed" walked away believing the runner does not install engines,
|
|
8208
|
+
// minutes before it silently did. Naming the daemon first is the difference between a
|
|
8209
|
+
// failure and a step that has not happened yet.
|
|
8210
|
+
enginePackageFor(engineId) ? `This machine installs it itself once its daemon is running (\`${RUNNER_BIN} start\`). To do it now: \`${RUNNER_BIN} engine install ${engineId}\` (a few hundred MB), or ${health2.fix ?? "install the CLI yourself"}` : health2.fix
|
|
8138
8211
|
)
|
|
8139
8212
|
);
|
|
8140
8213
|
if (managed) jobBinaries.push("node");
|
|
@@ -8209,7 +8282,7 @@ async function runEngineList() {
|
|
|
8209
8282
|
for (const engineId of installableEngineIds()) {
|
|
8210
8283
|
const pkg = enginePackageFor(engineId);
|
|
8211
8284
|
const { origin, bin, version } = originOf(engineId);
|
|
8212
|
-
const
|
|
8285
|
+
const health2 = await getDriver(engineId).healthCheck();
|
|
8213
8286
|
const record = state[engineId];
|
|
8214
8287
|
rows.push({
|
|
8215
8288
|
engine: engineId,
|
|
@@ -8217,10 +8290,10 @@ async function runEngineList() {
|
|
|
8217
8290
|
package: pkg?.npmPackage ?? null,
|
|
8218
8291
|
shipped: DEFAULT_ENGINE_POLICIES[engineId]?.knownGood ?? null,
|
|
8219
8292
|
installed: version,
|
|
8220
|
-
origin: origin === "none" &&
|
|
8221
|
-
binary: bin ??
|
|
8222
|
-
ok:
|
|
8223
|
-
detail:
|
|
8293
|
+
origin: origin === "none" && health2.ok ? "path" : origin,
|
|
8294
|
+
binary: bin ?? health2.binary ?? null,
|
|
8295
|
+
ok: health2.ok,
|
|
8296
|
+
detail: health2.ok ? void 0 : health2.detail,
|
|
8224
8297
|
lastAttempt: record ? { target: record.target, outcome: record.outcome, attempts: record.attempts, detail: record.detail } : null,
|
|
8225
8298
|
diskBytes: dirSize(enginePrefixDir(root, engineId))
|
|
8226
8299
|
});
|
|
@@ -8428,7 +8501,7 @@ Code: ${pc.bold(start.displayCode)}`,
|
|
|
8428
8501
|
}
|
|
8429
8502
|
if (!approved) {
|
|
8430
8503
|
progress.stop("");
|
|
8431
|
-
throw new CliError(
|
|
8504
|
+
throw new CliError(`The approval window expired. Run \`${RUNNER_BIN} login\` again.`);
|
|
8432
8505
|
}
|
|
8433
8506
|
progress.stop("Approved.");
|
|
8434
8507
|
const { config: config2, approvedShips, pendingShips, occupiedShips, newIdentity, otherShips } = buildLoginResult(
|
|
@@ -8438,7 +8511,7 @@ Code: ${pc.bold(start.displayCode)}`,
|
|
|
8438
8511
|
options.mcpUrl
|
|
8439
8512
|
);
|
|
8440
8513
|
if (!config2.runnerId) {
|
|
8441
|
-
throw new CliError(
|
|
8514
|
+
throw new CliError(`The approval did not name this machine. Run \`${RUNNER_BIN} login\` again.`);
|
|
8442
8515
|
}
|
|
8443
8516
|
if (!config2.apiKey) {
|
|
8444
8517
|
throw new CliError(
|
|
@@ -8541,11 +8614,11 @@ function report3(result) {
|
|
|
8541
8614
|
}
|
|
8542
8615
|
if (result.otherShips && result.otherShips.length > 0) {
|
|
8543
8616
|
say.info(
|
|
8544
|
-
`Still serving ${result.otherShips.join(", ")} \u2014 this approval did not mention them.
|
|
8617
|
+
`Still serving ${result.otherShips.join(", ")} \u2014 this approval did not mention them. \`${RUNNER_BIN} ship remove <shipId>\` to stop.`
|
|
8545
8618
|
);
|
|
8546
8619
|
}
|
|
8547
8620
|
if (serviceStatus().state === "running") {
|
|
8548
|
-
say.info(
|
|
8621
|
+
say.info(`Restart the daemon for this to take effect: \`${RUNNER_BIN} service restart\`.`);
|
|
8549
8622
|
}
|
|
8550
8623
|
return 0;
|
|
8551
8624
|
}
|
|
@@ -8574,7 +8647,16 @@ async function runLogs(options) {
|
|
|
8574
8647
|
// src/cli/commands/service.ts
|
|
8575
8648
|
async function runServiceInstall() {
|
|
8576
8649
|
const before = serviceStatus();
|
|
8577
|
-
if (before.state
|
|
8650
|
+
if (before.state === "unsupported") {
|
|
8651
|
+
if (isJson()) {
|
|
8652
|
+
emitJson({ installed: false, supported: false, ...before });
|
|
8653
|
+
return 1;
|
|
8654
|
+
}
|
|
8655
|
+
say.warn(before.detail);
|
|
8656
|
+
say.info(`Run \`${RUNNER_BIN} start\` to keep the daemon up \u2014 in Docker, make that the container's command.`);
|
|
8657
|
+
return 1;
|
|
8658
|
+
}
|
|
8659
|
+
if (before.state !== "not-installed") {
|
|
8578
8660
|
const replace = await promptConfirm({
|
|
8579
8661
|
message: "A service is already installed. Reinstall it with the current settings?",
|
|
8580
8662
|
initialValue: true
|
|
@@ -8632,7 +8714,7 @@ async function runServiceStatus() {
|
|
|
8632
8714
|
say.line(` ${status.detail}`);
|
|
8633
8715
|
if (status.unitPath) say.line(` ${status.unitPath}`);
|
|
8634
8716
|
if (status.execMissing) {
|
|
8635
|
-
say.warn(`The unit runs ${status.execPath}, which no longer exists. Run
|
|
8717
|
+
say.warn(`The unit runs ${status.execPath}, which no longer exists. Run \`${RUNNER_BIN} uninstall\`.`);
|
|
8636
8718
|
}
|
|
8637
8719
|
return 0;
|
|
8638
8720
|
}
|
|
@@ -8698,14 +8780,14 @@ async function pickShips(current) {
|
|
|
8698
8780
|
const ships = await listMyShips();
|
|
8699
8781
|
if (ships.length === 0) {
|
|
8700
8782
|
throw new CliError(
|
|
8701
|
-
|
|
8783
|
+
`This machine holds no Ship keys. Run \`${RUNNER_BIN} login\` and have a captain approve it \u2014 a daemon can only serve Ships it was approved for.`
|
|
8702
8784
|
);
|
|
8703
8785
|
}
|
|
8704
8786
|
return promptMultiSelect({
|
|
8705
8787
|
message: "Which Ships should this machine serve?",
|
|
8706
8788
|
choices: ships.map((ship2) => ({ value: ship2.id, label: ship2.name || ship2.id, hint: ship2.id })),
|
|
8707
8789
|
initialValues: current.filter((id) => ships.some((s) => s.id === id)),
|
|
8708
|
-
flagHint:
|
|
8790
|
+
flagHint: `${RUNNER_BIN} ship add <shipId>`
|
|
8709
8791
|
});
|
|
8710
8792
|
}
|
|
8711
8793
|
async function runShipList() {
|
|
@@ -8715,7 +8797,7 @@ async function runShipList() {
|
|
|
8715
8797
|
return 0;
|
|
8716
8798
|
}
|
|
8717
8799
|
if (config2.ships.length === 0) {
|
|
8718
|
-
say.warn(
|
|
8800
|
+
say.warn(`This machine serves no Ships. Run \`${RUNNER_BIN} ship add\`.`);
|
|
8719
8801
|
return 0;
|
|
8720
8802
|
}
|
|
8721
8803
|
for (const shipId of config2.ships) say.line(` ${shipId}`);
|
|
@@ -8746,18 +8828,36 @@ function reportAssignment(ships) {
|
|
|
8746
8828
|
return 0;
|
|
8747
8829
|
}
|
|
8748
8830
|
say.success(ships.length > 0 ? `Serving: ${ships.join(", ")}` : "Serving no Ships.");
|
|
8749
|
-
say.info(
|
|
8831
|
+
say.info(`Restart the daemon for this to take effect: \`${RUNNER_BIN} service restart\`.`);
|
|
8750
8832
|
return 0;
|
|
8751
8833
|
}
|
|
8752
8834
|
|
|
8753
8835
|
// src/cli/commands/setup.ts
|
|
8836
|
+
function shipChoiceFrom(input) {
|
|
8837
|
+
const label = (id) => input.fetched?.find((s) => s.id === id)?.name || id;
|
|
8838
|
+
const serving = input.serving.map(label).join(", ");
|
|
8839
|
+
const question = `Serving ${serving}. Change which Ships this machine serves? (yes replaces the list)`;
|
|
8840
|
+
if (!input.fetched || input.fetched.length < input.keyCount) return { ask: true, line: question };
|
|
8841
|
+
const servingIds = new Set(input.serving);
|
|
8842
|
+
const others = input.fetched.filter((s) => !servingIds.has(s.id));
|
|
8843
|
+
const fetchedIds = new Set(input.fetched.map((s) => s.id));
|
|
8844
|
+
const unkeyed = input.serving.filter((id) => !fetchedIds.has(id));
|
|
8845
|
+
if (others.length === 0 && unkeyed.length === 0) {
|
|
8846
|
+
return { ask: false, line: `Serving ${serving}. This machine holds no other Ship keys.` };
|
|
8847
|
+
}
|
|
8848
|
+
if (others.length === 0) return { ask: true, line: question };
|
|
8849
|
+
return {
|
|
8850
|
+
ask: true,
|
|
8851
|
+
line: `Serving ${serving}. This machine also holds a key for ${others.map((s) => s.name || s.id).join(", ")}. Change which Ships it serves? (yes replaces the list)`
|
|
8852
|
+
};
|
|
8853
|
+
}
|
|
8754
8854
|
async function runSetup(options) {
|
|
8755
8855
|
if (!canPrompt()) {
|
|
8756
8856
|
throw new CliError(
|
|
8757
|
-
|
|
8857
|
+
`setup is interactive. On a headless machine run \`${RUNNER_BIN} login\`, \`${RUNNER_BIN} ship add <id>\` and \`${RUNNER_BIN} service install\` instead.`
|
|
8758
8858
|
);
|
|
8759
8859
|
}
|
|
8760
|
-
say.intro(
|
|
8860
|
+
say.intro(`${RUNNER_BIN} setup`);
|
|
8761
8861
|
const moved = migrateLegacyDir();
|
|
8762
8862
|
if (moved?.migrated) say.info(`Moved your runner config from ${moved.from} to ${moved.to}.`);
|
|
8763
8863
|
const existing = loadConfig();
|
|
@@ -8782,7 +8882,7 @@ async function runSetup(options) {
|
|
|
8782
8882
|
}
|
|
8783
8883
|
],
|
|
8784
8884
|
initialValue: "keep",
|
|
8785
|
-
flagHint:
|
|
8885
|
+
flagHint: `${RUNNER_BIN} login`
|
|
8786
8886
|
});
|
|
8787
8887
|
if (action2 === "add") {
|
|
8788
8888
|
await runLogin(options);
|
|
@@ -8805,14 +8905,24 @@ async function runSetup(options) {
|
|
|
8805
8905
|
saveConfig(config2);
|
|
8806
8906
|
say.success(`Serving: ${config2.ships.join(", ") || "(none)"}`);
|
|
8807
8907
|
} else {
|
|
8808
|
-
const
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8908
|
+
const fetched = await listMyShips().catch(() => null);
|
|
8909
|
+
const choice = shipChoiceFrom({
|
|
8910
|
+
serving: config2.ships,
|
|
8911
|
+
fetched,
|
|
8912
|
+
keyCount: Object.keys(config2.shipKeys ?? {}).length
|
|
8812
8913
|
});
|
|
8813
|
-
if (
|
|
8814
|
-
|
|
8815
|
-
|
|
8914
|
+
if (!choice.ask) {
|
|
8915
|
+
say.info(choice.line);
|
|
8916
|
+
} else {
|
|
8917
|
+
const change = await promptConfirm({
|
|
8918
|
+
message: choice.line,
|
|
8919
|
+
initialValue: false,
|
|
8920
|
+
yesFlagApplies: false
|
|
8921
|
+
});
|
|
8922
|
+
if (change) {
|
|
8923
|
+
config2.ships = await pickShips(config2.ships);
|
|
8924
|
+
saveConfig(config2);
|
|
8925
|
+
}
|
|
8816
8926
|
}
|
|
8817
8927
|
}
|
|
8818
8928
|
say.step("Checking this machine\u2026");
|
|
@@ -8826,7 +8936,7 @@ async function runSetup(options) {
|
|
|
8826
8936
|
yesFlagApplies: false
|
|
8827
8937
|
});
|
|
8828
8938
|
if (install) await runServiceInstall();
|
|
8829
|
-
else say.info(
|
|
8939
|
+
else say.info(`Skipped. Run \`${RUNNER_BIN} start\` manually, or \`${RUNNER_BIN} service install\` later.`);
|
|
8830
8940
|
} else if (service2 === "running" || service2 === "installed") {
|
|
8831
8941
|
const drift = serviceEnvDrift(serviceEnv(), status.unitEnv);
|
|
8832
8942
|
if (drift.length > 0) {
|
|
@@ -8836,9 +8946,14 @@ async function runSetup(options) {
|
|
|
8836
8946
|
say.step("Restarting the daemon so it picks up this configuration\u2026");
|
|
8837
8947
|
await runServiceRestart();
|
|
8838
8948
|
}
|
|
8949
|
+
} else if (service2 === "unsupported") {
|
|
8950
|
+
say.info(status.detail);
|
|
8951
|
+
say.info(
|
|
8952
|
+
`Run \`${RUNNER_BIN} start\` to keep this machine online \u2014 in Docker, make that the container's command so the daemon is the thing the container runs.`
|
|
8953
|
+
);
|
|
8839
8954
|
}
|
|
8840
8955
|
say.outro(
|
|
8841
|
-
doctorExit === 0 ? "Ready. This machine will claim jobs for its Ships." :
|
|
8956
|
+
doctorExit === 0 ? "Ready. This machine will claim jobs for its Ships." : `Setup finished, but some checks failed \u2014 fix those and re-run \`${RUNNER_BIN} doctor\`.`
|
|
8842
8957
|
);
|
|
8843
8958
|
return doctorExit;
|
|
8844
8959
|
}
|
|
@@ -8860,7 +8975,7 @@ async function runStatus() {
|
|
|
8860
8975
|
emitJson({ version: RUNNER_VERSION, connected: false });
|
|
8861
8976
|
return 0;
|
|
8862
8977
|
}
|
|
8863
|
-
say.warn(
|
|
8978
|
+
say.warn(`This machine is not connected. Run \`${RUNNER_BIN} setup\`.`);
|
|
8864
8979
|
return 0;
|
|
8865
8980
|
}
|
|
8866
8981
|
const progress = spinner2();
|
|
@@ -8952,7 +9067,7 @@ async function runStatus() {
|
|
|
8952
9067
|
say.line(` ${pc.bold("Service")} ${service2.detail}`);
|
|
8953
9068
|
say.line("");
|
|
8954
9069
|
if (ships.length === 0) {
|
|
8955
|
-
say.warn(
|
|
9070
|
+
say.warn(`This machine serves no Ships. Run \`${RUNNER_BIN} ship add\`.`);
|
|
8956
9071
|
return 0;
|
|
8957
9072
|
}
|
|
8958
9073
|
for (const ship2 of ships) {
|
|
@@ -9074,7 +9189,7 @@ async function runUpdate(options = {}) {
|
|
|
9074
9189
|
|
|
9075
9190
|
// src/cli/index.ts
|
|
9076
9191
|
var program = new Command();
|
|
9077
|
-
program.name(
|
|
9192
|
+
program.name(RUNNER_BIN).description("Lumi Crew runner daemon \u2014 runs agent jobs for your Ships on this machine.").version(RUNNER_VERSION).option("--json", "machine-readable output on stdout").option("-y, --yes", "assume yes for confirmations").option("--no-color", "disable coloured output").hook("preAction", (command) => {
|
|
9078
9193
|
const options = command.opts();
|
|
9079
9194
|
configureOutput(options);
|
|
9080
9195
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kilogent/runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lumi Crew runner daemon — claims jobs from your Ships and executes them as headless agent sessions (Claude Code today) 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.",
|