dahrk-node 0.1.14 → 0.1.16
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/main.js +625 -246
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
5
|
-
import { existsSync as existsSync14, realpathSync as realpathSync5 } from "fs";
|
|
5
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, realpathSync as realpathSync5 } from "fs";
|
|
6
6
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
7
7
|
import { homedir as homedir7, platform as osPlatform4 } from "os";
|
|
8
8
|
import { basename as basename2, join as join17 } from "path";
|
|
@@ -506,9 +506,9 @@ var userMsg = (text) => ({
|
|
|
506
506
|
});
|
|
507
507
|
var sessionIdOf = (msg) => "session_id" in msg && typeof msg.session_id === "string" ? msg.session_id : void 0;
|
|
508
508
|
var policyCanUseTool = async (ctx, toolName, input) => {
|
|
509
|
-
const
|
|
510
|
-
if (
|
|
511
|
-
return { behavior: "deny", message:
|
|
509
|
+
const verdict2 = ctx.authorizeToolUse?.(toolName, input);
|
|
510
|
+
if (verdict2?.verdict === "deny") {
|
|
511
|
+
return { behavior: "deny", message: verdict2.reason ?? `tool "${toolName}" denied by policy ${verdict2.policy}` };
|
|
512
512
|
}
|
|
513
513
|
return { behavior: "allow", updatedInput: input };
|
|
514
514
|
};
|
|
@@ -801,15 +801,15 @@ function createClaudeRunner() {
|
|
|
801
801
|
})
|
|
802
802
|
};
|
|
803
803
|
try {
|
|
804
|
-
let
|
|
804
|
+
let out2 = "";
|
|
805
805
|
const q = query({ prompt: SUMMARISE_PROMPT, options });
|
|
806
806
|
active = q;
|
|
807
807
|
for await (const msg of q) {
|
|
808
808
|
const found = sessionIdOf(msg);
|
|
809
809
|
if (found) sessionId = found;
|
|
810
|
-
if (msg.type === "result" && msg.subtype === "success")
|
|
810
|
+
if (msg.type === "result" && msg.subtype === "success") out2 += msg.result;
|
|
811
811
|
}
|
|
812
|
-
return
|
|
812
|
+
return out2.trim() || "(no summary produced)";
|
|
813
813
|
} catch (e) {
|
|
814
814
|
return `(summary unavailable: ${e.message})`;
|
|
815
815
|
} finally {
|
|
@@ -1311,15 +1311,15 @@ function createPiRunner(deps = {}) {
|
|
|
1311
1311
|
const s = session;
|
|
1312
1312
|
if (s.agent) s.agent.state.tools = [];
|
|
1313
1313
|
const state = newPiBufferState();
|
|
1314
|
-
let
|
|
1314
|
+
let out2;
|
|
1315
1315
|
const unsub = s.subscribe((ev) => {
|
|
1316
1316
|
const r = consumePiEvent(ev, state, true);
|
|
1317
|
-
if (r.responseText)
|
|
1317
|
+
if (r.responseText) out2 = r.responseText;
|
|
1318
1318
|
});
|
|
1319
1319
|
try {
|
|
1320
1320
|
await s.prompt(SUMMARISE_PROMPT);
|
|
1321
1321
|
captureSessionId(s);
|
|
1322
|
-
return (
|
|
1322
|
+
return (out2 ?? "").trim() || "(no summary produced)";
|
|
1323
1323
|
} catch (e) {
|
|
1324
1324
|
return `(summary unavailable: ${e.message})`;
|
|
1325
1325
|
} finally {
|
|
@@ -1354,7 +1354,9 @@ async function defaultCreatePiSession(ctx) {
|
|
|
1354
1354
|
let model;
|
|
1355
1355
|
if (ctx.config.model) {
|
|
1356
1356
|
const resolved = resolveCliModel({ cliModel: ctx.config.model, modelRegistry });
|
|
1357
|
-
if (!resolved?.error)
|
|
1357
|
+
if (!resolved?.error) {
|
|
1358
|
+
model = pickAuthedModel(resolved?.model, modelRegistry.getAvailable());
|
|
1359
|
+
}
|
|
1358
1360
|
}
|
|
1359
1361
|
const stageComplete = defineTool({
|
|
1360
1362
|
name: PI_STAGE_COMPLETE_TOOL,
|
|
@@ -1379,6 +1381,17 @@ var PROVIDER_BY_ENV = {
|
|
|
1379
1381
|
GEMINI_API_KEY: "google",
|
|
1380
1382
|
GOOGLE_API_KEY: "google"
|
|
1381
1383
|
};
|
|
1384
|
+
function modelFamily(id) {
|
|
1385
|
+
const last = id.split(".").pop() ?? id;
|
|
1386
|
+
return last.replace(/-v\d+:\d+$/, "").toLowerCase();
|
|
1387
|
+
}
|
|
1388
|
+
function pickAuthedModel(resolved, available) {
|
|
1389
|
+
if (!resolved || !available?.length) return resolved;
|
|
1390
|
+
const providers = new Set(available.map((m) => m.provider));
|
|
1391
|
+
if (providers.has(resolved.provider)) return resolved;
|
|
1392
|
+
const family = modelFamily(resolved.id);
|
|
1393
|
+
return available.find((m) => modelFamily(m.id) === family) ?? resolved;
|
|
1394
|
+
}
|
|
1382
1395
|
|
|
1383
1396
|
// ../../packages/executor-worktree/src/git-service.ts
|
|
1384
1397
|
import { execFileSync } from "child_process";
|
|
@@ -1482,15 +1495,15 @@ function createGitService(opts = {}) {
|
|
|
1482
1495
|
const withTokenUser = (gitUrl) => /^https:\/\/[^@/]+@/.test(gitUrl) ? gitUrl : gitUrl.replace(/^https:\/\//, "https://x-access-token@");
|
|
1483
1496
|
const mirrorPathFor = (repoId) => join3(mirrorsDir, sanitizeBranchName(repoId));
|
|
1484
1497
|
const listWorktrees = (mirror) => {
|
|
1485
|
-
let
|
|
1498
|
+
let out2;
|
|
1486
1499
|
try {
|
|
1487
|
-
|
|
1500
|
+
out2 = git(mirror, ["worktree", "list", "--porcelain"]);
|
|
1488
1501
|
} catch {
|
|
1489
1502
|
return [];
|
|
1490
1503
|
}
|
|
1491
1504
|
const entries = [];
|
|
1492
1505
|
let path = "";
|
|
1493
|
-
for (const line of
|
|
1506
|
+
for (const line of out2.split("\n")) {
|
|
1494
1507
|
if (line.startsWith("worktree ")) path = line.slice(9).trim();
|
|
1495
1508
|
else if (line.startsWith("branch ")) {
|
|
1496
1509
|
const branch = line.slice(7).trim().replace(/^refs\/heads\//, "");
|
|
@@ -1848,13 +1861,13 @@ function lastUsedMs(worktreePath) {
|
|
|
1848
1861
|
return newest;
|
|
1849
1862
|
}
|
|
1850
1863
|
function registeredWorktrees(mirror) {
|
|
1851
|
-
let
|
|
1864
|
+
let out2;
|
|
1852
1865
|
try {
|
|
1853
|
-
|
|
1866
|
+
out2 = gitOut(mirror, ["worktree", "list", "--porcelain"]);
|
|
1854
1867
|
} catch {
|
|
1855
1868
|
return [];
|
|
1856
1869
|
}
|
|
1857
|
-
return
|
|
1870
|
+
return out2.split("\n").filter((l) => l.startsWith("worktree ")).map((l) => canonical(l.slice(9).trim())).filter((p) => p && p !== canonical(mirror));
|
|
1858
1871
|
}
|
|
1859
1872
|
function createWorktreeReaper(opts) {
|
|
1860
1873
|
const log = opts.logger ?? noop;
|
|
@@ -2015,16 +2028,16 @@ import { createHash as createHash2 } from "crypto";
|
|
|
2015
2028
|
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2016
2029
|
import { dirname as dirname2, join as join6, relative, sep } from "path";
|
|
2017
2030
|
function readManifestFiles(dir) {
|
|
2018
|
-
const
|
|
2031
|
+
const out2 = [];
|
|
2019
2032
|
const walk = (cur) => {
|
|
2020
2033
|
for (const entry of readdirSync2(cur, { withFileTypes: true })) {
|
|
2021
2034
|
const abs = join6(cur, entry.name);
|
|
2022
2035
|
if (entry.isDirectory()) walk(abs);
|
|
2023
|
-
else
|
|
2036
|
+
else out2.push(relative(dir, abs).split(sep).join("/"));
|
|
2024
2037
|
}
|
|
2025
2038
|
};
|
|
2026
2039
|
walk(dir);
|
|
2027
|
-
return
|
|
2040
|
+
return out2.sort();
|
|
2028
2041
|
}
|
|
2029
2042
|
|
|
2030
2043
|
// ../../packages/executor-worktree/src/overlay.ts
|
|
@@ -2195,11 +2208,11 @@ function fileJobLedger(file, warn = console.warn) {
|
|
|
2195
2208
|
};
|
|
2196
2209
|
}
|
|
2197
2210
|
function announceableJobs(entries) {
|
|
2198
|
-
const
|
|
2211
|
+
const out2 = [];
|
|
2199
2212
|
for (const e of entries) {
|
|
2200
|
-
if (e.payloadVersion)
|
|
2213
|
+
if (e.payloadVersion) out2.push({ jobId: e.jobId, payloadVersion: e.payloadVersion });
|
|
2201
2214
|
}
|
|
2202
|
-
return
|
|
2215
|
+
return out2;
|
|
2203
2216
|
}
|
|
2204
2217
|
function jobLedgerFile(stateDir2) {
|
|
2205
2218
|
return join8(stateDir2, "jobs.json");
|
|
@@ -2357,18 +2370,18 @@ function scrubValue(value, depth = 0) {
|
|
|
2357
2370
|
}
|
|
2358
2371
|
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return value;
|
|
2359
2372
|
if (value instanceof Error) {
|
|
2360
|
-
const
|
|
2361
|
-
|
|
2362
|
-
if (value.stack)
|
|
2363
|
-
return
|
|
2373
|
+
const out2 = new Error(scrubString(value.message));
|
|
2374
|
+
out2.name = value.name;
|
|
2375
|
+
if (value.stack) out2.stack = scrubString(value.stack);
|
|
2376
|
+
return out2;
|
|
2364
2377
|
}
|
|
2365
2378
|
if (Array.isArray(value)) return value.map((v) => scrubValue(v, depth + 1));
|
|
2366
2379
|
if (typeof value === "object") {
|
|
2367
|
-
const
|
|
2380
|
+
const out2 = {};
|
|
2368
2381
|
for (const [k, v] of Object.entries(value)) {
|
|
2369
|
-
|
|
2382
|
+
out2[k] = isSensitiveKey(k) ? REDACTED : scrubValue(v, depth + 1);
|
|
2370
2383
|
}
|
|
2371
|
-
return
|
|
2384
|
+
return out2;
|
|
2372
2385
|
}
|
|
2373
2386
|
return value;
|
|
2374
2387
|
}
|
|
@@ -2602,11 +2615,11 @@ function withinRoots(raw, roots, need) {
|
|
|
2602
2615
|
}
|
|
2603
2616
|
function gitCommonDir(worktreePath) {
|
|
2604
2617
|
try {
|
|
2605
|
-
const
|
|
2618
|
+
const out2 = execFileSync3("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
2606
2619
|
cwd: worktreePath,
|
|
2607
2620
|
stdio: ["ignore", "pipe", "ignore"]
|
|
2608
2621
|
}).toString().trim();
|
|
2609
|
-
return
|
|
2622
|
+
return out2 || void 0;
|
|
2610
2623
|
} catch {
|
|
2611
2624
|
return void 0;
|
|
2612
2625
|
}
|
|
@@ -2615,8 +2628,8 @@ var pnpmStoreCache;
|
|
|
2615
2628
|
function pnpmStore() {
|
|
2616
2629
|
if (pnpmStoreCache) return pnpmStoreCache.path;
|
|
2617
2630
|
try {
|
|
2618
|
-
const
|
|
2619
|
-
pnpmStoreCache = { path:
|
|
2631
|
+
const out2 = execFileSync3("pnpm", ["store", "path"], { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).toString().trim();
|
|
2632
|
+
pnpmStoreCache = { path: out2 || void 0 };
|
|
2620
2633
|
} catch {
|
|
2621
2634
|
pnpmStoreCache = { path: void 0 };
|
|
2622
2635
|
}
|
|
@@ -2912,12 +2925,12 @@ function scanCommand(cmd, roots, cwd = roots.cwd) {
|
|
|
2912
2925
|
}
|
|
2913
2926
|
plain.push(t);
|
|
2914
2927
|
}
|
|
2915
|
-
const
|
|
2916
|
-
if (
|
|
2917
|
-
cwdNow = expandPath(
|
|
2928
|
+
const out2 = scanSimple(plain, roots, cwdNow);
|
|
2929
|
+
if (out2.kind === "cd") {
|
|
2930
|
+
cwdNow = expandPath(out2.to, cwdNow);
|
|
2918
2931
|
continue;
|
|
2919
2932
|
}
|
|
2920
|
-
if (
|
|
2933
|
+
if (out2.kind !== "ok") return out2;
|
|
2921
2934
|
}
|
|
2922
2935
|
return { kind: "ok" };
|
|
2923
2936
|
}
|
|
@@ -2988,14 +3001,14 @@ var PATH_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "N
|
|
|
2988
3001
|
function pathsIn(input) {
|
|
2989
3002
|
if (!input || typeof input !== "object") return [];
|
|
2990
3003
|
const o = input;
|
|
2991
|
-
const
|
|
3004
|
+
const out2 = [];
|
|
2992
3005
|
for (const f of PATH_FIELDS) {
|
|
2993
|
-
if (typeof o[f] === "string" && o[f])
|
|
3006
|
+
if (typeof o[f] === "string" && o[f]) out2.push(o[f]);
|
|
2994
3007
|
}
|
|
2995
3008
|
if (o.changes && typeof o.changes === "object") {
|
|
2996
|
-
|
|
3009
|
+
out2.push(...Object.keys(o.changes));
|
|
2997
3010
|
}
|
|
2998
|
-
return
|
|
3011
|
+
return out2;
|
|
2999
3012
|
}
|
|
3000
3013
|
function fsConfineRule(roots) {
|
|
3001
3014
|
return {
|
|
@@ -3587,10 +3600,10 @@ function createStageRunner(deps) {
|
|
|
3587
3600
|
return `${tool3}\0`;
|
|
3588
3601
|
}
|
|
3589
3602
|
};
|
|
3590
|
-
const policyReason = (
|
|
3591
|
-
const recordDeny = (
|
|
3603
|
+
const policyReason = (verdict2) => verdict2.reason ?? `tool action denied by ${verdict2.policy}`;
|
|
3604
|
+
const recordDeny = (verdict2, toolUseId) => {
|
|
3592
3605
|
denied = true;
|
|
3593
|
-
const reason = policyReason(
|
|
3606
|
+
const reason = policyReason(verdict2);
|
|
3594
3607
|
if (toolUseId) {
|
|
3595
3608
|
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "observation", runtime, toolUseId, isError: true, output: { error: reason } }));
|
|
3596
3609
|
}
|
|
@@ -3598,13 +3611,13 @@ function createStageRunner(deps) {
|
|
|
3598
3611
|
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: reason });
|
|
3599
3612
|
};
|
|
3600
3613
|
const authorizeToolUse = (tool3, input) => {
|
|
3601
|
-
const
|
|
3602
|
-
if (
|
|
3603
|
-
recordDeny(
|
|
3614
|
+
const verdict2 = evaluatePolicies({ kind: "action", stageId, tool: tool3, input }, rules);
|
|
3615
|
+
if (verdict2.verdict === "deny") {
|
|
3616
|
+
recordDeny(verdict2);
|
|
3604
3617
|
} else {
|
|
3605
3618
|
authorisedActions.push(actionKey(tool3, input));
|
|
3606
3619
|
}
|
|
3607
|
-
return
|
|
3620
|
+
return verdict2;
|
|
3608
3621
|
};
|
|
3609
3622
|
const onTrace = (event) => {
|
|
3610
3623
|
if (event.type === "action") {
|
|
@@ -3613,14 +3626,14 @@ function createStageRunner(deps) {
|
|
|
3613
3626
|
if (authorised >= 0) {
|
|
3614
3627
|
authorisedActions.splice(authorised, 1);
|
|
3615
3628
|
} else {
|
|
3616
|
-
const
|
|
3629
|
+
const verdict2 = evaluatePolicies(
|
|
3617
3630
|
{ kind: "action", stageId, tool: event.tool, input: event.input },
|
|
3618
3631
|
rules
|
|
3619
3632
|
);
|
|
3620
|
-
if (
|
|
3633
|
+
if (verdict2.verdict === "deny") {
|
|
3621
3634
|
streamEvent(writer.append(event));
|
|
3622
|
-
recordDeny(
|
|
3623
|
-
if (
|
|
3635
|
+
recordDeny(verdict2, event.toolUseId);
|
|
3636
|
+
if (verdict2.policy === "fs_confine" && runtime !== "claude-code") escapedUnblocked = true;
|
|
3624
3637
|
return;
|
|
3625
3638
|
}
|
|
3626
3639
|
}
|
|
@@ -4486,6 +4499,10 @@ function parseCli(argv) {
|
|
|
4486
4499
|
"hub-url": { type: "string" },
|
|
4487
4500
|
ephemeral: { type: "boolean", default: false },
|
|
4488
4501
|
foreground: { type: "boolean", default: false },
|
|
4502
|
+
// `stop` / `restart`: take the node down even though it has work in flight.
|
|
4503
|
+
force: { type: "boolean", default: false },
|
|
4504
|
+
// `status`: machine-readable output.
|
|
4505
|
+
json: { type: "boolean", default: false },
|
|
4489
4506
|
help: { type: "boolean", default: false }
|
|
4490
4507
|
},
|
|
4491
4508
|
allowPositionals: false
|
|
@@ -4495,6 +4512,7 @@ function parseCli(argv) {
|
|
|
4495
4512
|
}
|
|
4496
4513
|
if (values.help) return { kind: "help", command };
|
|
4497
4514
|
const ephemeral = values.ephemeral ?? false;
|
|
4515
|
+
const force = values.force ?? false;
|
|
4498
4516
|
const flags = {
|
|
4499
4517
|
...values.token ? { token: values.token } : {},
|
|
4500
4518
|
...values.name ? { name: values.name } : {},
|
|
@@ -4504,7 +4522,9 @@ function parseCli(argv) {
|
|
|
4504
4522
|
// a supervisor that restarts it on boot. It is a foreground node by definition.
|
|
4505
4523
|
foreground: (values.foreground ?? false) || ephemeral
|
|
4506
4524
|
};
|
|
4507
|
-
if (command === "stop") return { kind: "stop" };
|
|
4525
|
+
if (command === "stop") return { kind: "stop", force };
|
|
4526
|
+
if (command === "restart") return { kind: "restart", flags, force };
|
|
4527
|
+
if (command === "status") return { kind: "status", flags: { ...flags, json: values.json ?? false } };
|
|
4508
4528
|
return { kind: command, flags };
|
|
4509
4529
|
}
|
|
4510
4530
|
var LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"];
|
|
@@ -4639,6 +4659,7 @@ function parseUpdate(flagArgs) {
|
|
|
4639
4659
|
args: flagArgs,
|
|
4640
4660
|
options: {
|
|
4641
4661
|
check: { type: "boolean", default: false },
|
|
4662
|
+
verbose: { type: "boolean", default: false },
|
|
4642
4663
|
help: { type: "boolean", default: false }
|
|
4643
4664
|
},
|
|
4644
4665
|
allowPositionals: false
|
|
@@ -4647,7 +4668,7 @@ function parseUpdate(flagArgs) {
|
|
|
4647
4668
|
return { kind: "error", message: e.message };
|
|
4648
4669
|
}
|
|
4649
4670
|
if (values.help) return { kind: "help", command: "update" };
|
|
4650
|
-
return { kind: "update", flags: { check: values.check ?? false } };
|
|
4671
|
+
return { kind: "update", flags: { check: values.check ?? false, verbose: values.verbose ?? false } };
|
|
4651
4672
|
}
|
|
4652
4673
|
function usage(bin, command) {
|
|
4653
4674
|
if (command === "start") {
|
|
@@ -4682,26 +4703,39 @@ function usage(bin, command) {
|
|
|
4682
4703
|
}
|
|
4683
4704
|
if (command === "stop") {
|
|
4684
4705
|
return [
|
|
4685
|
-
`Usage: ${bin} stop`,
|
|
4706
|
+
`Usage: ${bin} stop [--force]`,
|
|
4686
4707
|
"",
|
|
4687
4708
|
"Stop the node. It stays stopped across reboots until you run `dahrk start` again - a stop that",
|
|
4688
4709
|
"quietly undid itself at the next boot would not be a stop.",
|
|
4689
4710
|
"",
|
|
4711
|
+
"A node that is running a stage refuses to stop, because stopping it would kill work that costs real",
|
|
4712
|
+
"agent time. It lists what is in flight and leaves the node up; --force stops it anyway.",
|
|
4713
|
+
"",
|
|
4690
4714
|
"The service stays installed, so starting it again is instant. To remove it entirely, use",
|
|
4691
|
-
"`dahrk service uninstall`. To stop a node running in a terminal (--foreground), press Ctrl-C."
|
|
4715
|
+
"`dahrk service uninstall`. To stop a node running in a terminal (--foreground), press Ctrl-C.",
|
|
4716
|
+
"",
|
|
4717
|
+
"Options:",
|
|
4718
|
+
" --force Stop even if the node has stages in flight (this kills them)."
|
|
4692
4719
|
].join("\n");
|
|
4693
4720
|
}
|
|
4694
4721
|
if (command === "restart") {
|
|
4695
4722
|
return [
|
|
4696
4723
|
`Usage: ${bin} restart [options]`,
|
|
4697
4724
|
"",
|
|
4698
|
-
"Stop the node and start it again. Picks up a new client version, a rotated
|
|
4699
|
-
"have just installed (the node detects runtimes at boot, so a fresh `claude`
|
|
4725
|
+
"Stop the node and start it again, then report its status. Picks up a new client version, a rotated",
|
|
4726
|
+
"token, or a runtime you have just installed (the node detects runtimes at boot, so a fresh `claude`",
|
|
4727
|
+
"on PATH needs a restart).",
|
|
4728
|
+
"",
|
|
4729
|
+
"This is what picks up a `dahrk update`: a running node keeps executing the build it started with, so",
|
|
4730
|
+
"`dahrk start` on it does nothing at all.",
|
|
4731
|
+
"",
|
|
4732
|
+
"Like `stop`, it refuses on a node with stages in flight rather than killing them; --force overrides.",
|
|
4700
4733
|
"",
|
|
4701
4734
|
"Options:",
|
|
4702
4735
|
" --token <token> Re-enrol with a new token (or set DAHRK_ENROL_TOKEN).",
|
|
4703
4736
|
" --hub-url <url> Hub WebSocket URL (or set DAHRK_HUB_URL).",
|
|
4704
|
-
" --name <name> Display-name override (else the hub assigns one)."
|
|
4737
|
+
" --name <name> Display-name override (else the hub assigns one).",
|
|
4738
|
+
" --force Restart even if the node has stages in flight (this kills them)."
|
|
4705
4739
|
].join("\n");
|
|
4706
4740
|
}
|
|
4707
4741
|
if (command === "logs") {
|
|
@@ -4793,26 +4827,38 @@ function usage(bin, command) {
|
|
|
4793
4827
|
}
|
|
4794
4828
|
if (command === "status") {
|
|
4795
4829
|
return [
|
|
4796
|
-
`Usage: ${bin} status`,
|
|
4830
|
+
`Usage: ${bin} status [--json]`,
|
|
4831
|
+
"",
|
|
4832
|
+
"Report this node's local state: whether it is running (and how), whether it is enrolled and as whom,",
|
|
4833
|
+
"its node id, the runtimes it can serve with their versions, and what it is working on right now.",
|
|
4834
|
+
"",
|
|
4835
|
+
"Local only - it dials nothing, so it is instant and works offline. That is also why the hub line says",
|
|
4836
|
+
"when the node was LAST known to be connected (from its log) rather than claiming it is connected now,",
|
|
4837
|
+
"which this command cannot know without dialling. Use `doctor` for that: it checks the hub is",
|
|
4838
|
+
"reachable and the token still valid.",
|
|
4797
4839
|
"",
|
|
4798
|
-
"
|
|
4799
|
-
"it
|
|
4840
|
+
"Exits non-zero only when the node is installed but not running and nobody asked for that (i.e. it is",
|
|
4841
|
+
"crash-looping), so it works as a health check in a script. A node you deliberately stopped is not a",
|
|
4842
|
+
"failure.",
|
|
4800
4843
|
"",
|
|
4801
|
-
"
|
|
4802
|
-
"
|
|
4803
|
-
"not running, so it works as a health check in a script."
|
|
4844
|
+
"Options:",
|
|
4845
|
+
" --json Print the facts as JSON (for a script) instead of the report."
|
|
4804
4846
|
].join("\n");
|
|
4805
4847
|
}
|
|
4806
4848
|
if (command === "update") {
|
|
4807
4849
|
return [
|
|
4808
|
-
`Usage: ${bin} update [--check]`,
|
|
4850
|
+
`Usage: ${bin} update [--check] [--verbose]`,
|
|
4809
4851
|
"",
|
|
4810
4852
|
"Update this client in place to the latest published release. Detects how it was installed",
|
|
4811
4853
|
"(npm / Homebrew / curl) and runs the right upgrade, or prints the exact command when it cannot.",
|
|
4812
4854
|
"Reports current -> latest, and a no-op when already current.",
|
|
4813
4855
|
"",
|
|
4856
|
+
"A node that is already running keeps executing the OLD build until it is restarted, so if one is up",
|
|
4857
|
+
"this offers to restart it for you (and tells you to run `dahrk restart` when there is nobody to ask).",
|
|
4858
|
+
"",
|
|
4814
4859
|
"Options:",
|
|
4815
|
-
" --check Report whether an update is available without applying it (dry run)."
|
|
4860
|
+
" --check Report whether an update is available without applying it (dry run).",
|
|
4861
|
+
" --verbose Show the package manager's own output, which is hidden unless the upgrade fails."
|
|
4816
4862
|
].join("\n");
|
|
4817
4863
|
}
|
|
4818
4864
|
return [
|
|
@@ -4821,10 +4867,10 @@ function usage(bin, command) {
|
|
|
4821
4867
|
"Commands:",
|
|
4822
4868
|
" start Run the node, and keep it running (installs the service). --token to enrol, once.",
|
|
4823
4869
|
" stop Stop the node. It stays stopped until the next `start`.",
|
|
4824
|
-
" restart Stop it and start it again.",
|
|
4870
|
+
" restart Stop it and start it again. This is what picks up a `dahrk update`.",
|
|
4825
4871
|
" logs Show what the node is doing (-f to follow, --run <id> to narrow to one run).",
|
|
4826
4872
|
" diagnose Write a support bundle you can read, and send on if you choose. Uploads nothing.",
|
|
4827
|
-
" status Is
|
|
4873
|
+
" status Is it up, is it enrolled, what is it working on? (local, dials nothing)",
|
|
4828
4874
|
" run Run a workflow locally (engine-backed), e.g. `run preflight`.",
|
|
4829
4875
|
" doctor Preflight checks: Node, runtimes, hub reachability, token validity.",
|
|
4830
4876
|
" service Install/uninstall the always-on service by hand (`start` does this for you).",
|
|
@@ -4840,6 +4886,80 @@ function usage(bin, command) {
|
|
|
4840
4886
|
// src/diagnose.ts
|
|
4841
4887
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
4842
4888
|
import { join as join12 } from "path";
|
|
4889
|
+
|
|
4890
|
+
// src/ui.ts
|
|
4891
|
+
import { styleText } from "util";
|
|
4892
|
+
function detectCapabilities(env = process.env, isTty = Boolean(process.stdout.isTTY)) {
|
|
4893
|
+
const forced = env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== "0";
|
|
4894
|
+
const colour = forced || isTty && env.NO_COLOR === void 0 && env.TERM !== "dumb";
|
|
4895
|
+
const locale = `${env.LC_ALL ?? ""}${env.LC_CTYPE ?? ""}${env.LANG ?? ""}`;
|
|
4896
|
+
const unicode = process.platform !== "win32" && (locale === "" || /UTF-?8/i.test(locale));
|
|
4897
|
+
return { colour, unicode };
|
|
4898
|
+
}
|
|
4899
|
+
var caps = detectCapabilities();
|
|
4900
|
+
var paint = (style, text) => (
|
|
4901
|
+
// `validateStream: false` because our own gate above has already decided; the option is ignored by the
|
|
4902
|
+
// Node 22.0-22.7 signature, which had no stream check at all, so this is correct on every supported Node.
|
|
4903
|
+
caps.colour ? styleText(style, text, { validateStream: false }) : text
|
|
4904
|
+
);
|
|
4905
|
+
var dim = (s) => paint("dim", s);
|
|
4906
|
+
var bold = (s) => paint("bold", s);
|
|
4907
|
+
var green = (s) => paint("green", s);
|
|
4908
|
+
var red = (s) => paint("red", s);
|
|
4909
|
+
function amber(s) {
|
|
4910
|
+
if (!caps.colour) return s;
|
|
4911
|
+
const depth = process.stdout.getColorDepth?.() ?? 4;
|
|
4912
|
+
return depth >= 24 ? `\x1B[38;2;245;165;36m${s}\x1B[39m` : paint("yellow", s);
|
|
4913
|
+
}
|
|
4914
|
+
var GLYPH = {
|
|
4915
|
+
ok: { unicode: "\u2714", ascii: "OK" },
|
|
4916
|
+
warn: { unicode: "\u25B2", ascii: "!" },
|
|
4917
|
+
fail: { unicode: "\u2716", ascii: "x" },
|
|
4918
|
+
info: { unicode: "\u2022", ascii: "-" }
|
|
4919
|
+
};
|
|
4920
|
+
var COLOUR = {
|
|
4921
|
+
ok: green,
|
|
4922
|
+
warn: amber,
|
|
4923
|
+
fail: red,
|
|
4924
|
+
info: dim
|
|
4925
|
+
};
|
|
4926
|
+
var symbol = (level) => COLOUR[level](caps.unicode ? GLYPH[level].unicode : GLYPH[level].ascii);
|
|
4927
|
+
var arrow = () => caps.unicode ? "\u2192" : "->";
|
|
4928
|
+
var verdict = (level, text) => ` ${symbol(level)} ${bold(text)}`;
|
|
4929
|
+
var LABEL_WIDTH = 11;
|
|
4930
|
+
function kv(label, value) {
|
|
4931
|
+
const gutter = label ? dim(label.padEnd(LABEL_WIDTH)) : " ".repeat(LABEL_WIDTH);
|
|
4932
|
+
return ` ${gutter}${value}`;
|
|
4933
|
+
}
|
|
4934
|
+
var hint = (text) => ` ${dim(text)}`;
|
|
4935
|
+
var hints = (pairs) => hint(pairs.map(([label, cmd]) => `${label}: ${cmd}`).join(" "));
|
|
4936
|
+
var out = (line) => void process.stdout.write(`${line}
|
|
4937
|
+
`);
|
|
4938
|
+
var stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
4939
|
+
function humanDuration(ms) {
|
|
4940
|
+
const s = Math.max(0, Math.round(ms / 1e3));
|
|
4941
|
+
if (s < 60) return `${s}s`;
|
|
4942
|
+
const m = Math.floor(s / 60);
|
|
4943
|
+
if (m < 60) return `${m}m`;
|
|
4944
|
+
const h = Math.floor(m / 60);
|
|
4945
|
+
if (h < 24) return m % 60 === 0 ? `${h}h` : `${h}h ${m % 60}m`;
|
|
4946
|
+
const d = Math.floor(h / 24);
|
|
4947
|
+
return h % 24 === 0 ? `${d}d` : `${d}d ${h % 24}h`;
|
|
4948
|
+
}
|
|
4949
|
+
var ago = (ms) => ms < 5e3 ? "just now" : `${humanDuration(ms)} ago`;
|
|
4950
|
+
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
4951
|
+
async function confirm(question) {
|
|
4952
|
+
const { createInterface } = await import("readline/promises");
|
|
4953
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4954
|
+
try {
|
|
4955
|
+
const answer = (await rl.question(` ${question} ${dim("[Y/n]")} `)).trim().toLowerCase();
|
|
4956
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
4957
|
+
} finally {
|
|
4958
|
+
rl.close();
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
4961
|
+
|
|
4962
|
+
// src/diagnose.ts
|
|
4843
4963
|
var BUNDLE_LOG_LINES = 2e3;
|
|
4844
4964
|
function tailJsonl(raw, n) {
|
|
4845
4965
|
const records = [];
|
|
@@ -4945,8 +5065,7 @@ var defaultDiagnoseDeps = (paths, clientVersion, doctor) => ({
|
|
|
4945
5065
|
if (!existsSync8(dir)) mkdirSync8(dir, { recursive: true });
|
|
4946
5066
|
writeFileSync7(p, content, { mode: 384 });
|
|
4947
5067
|
},
|
|
4948
|
-
out
|
|
4949
|
-
`)
|
|
5068
|
+
out
|
|
4950
5069
|
});
|
|
4951
5070
|
function defaultBundlePath(cwd, now) {
|
|
4952
5071
|
return join12(cwd, `dahrk-diagnose-${now.toISOString().replace(/[:.]/g, "-")}.json`);
|
|
@@ -4954,7 +5073,7 @@ function defaultBundlePath(cwd, now) {
|
|
|
4954
5073
|
|
|
4955
5074
|
// src/doctor.ts
|
|
4956
5075
|
var MIN_NODE_MAJOR = 22;
|
|
4957
|
-
var
|
|
5076
|
+
var LEVEL = { pass: "ok", warn: "warn", fail: "fail" };
|
|
4958
5077
|
function checkNode(nodeVersion) {
|
|
4959
5078
|
const major = Number.parseInt(nodeVersion.replace(/^v/, "").split(".")[0] ?? "", 10);
|
|
4960
5079
|
if (Number.isNaN(major)) {
|
|
@@ -5052,16 +5171,15 @@ function checkToken(tokenPresent, hubUrl, probe2) {
|
|
|
5052
5171
|
function formatReport(checks) {
|
|
5053
5172
|
const failed = checks.filter((c) => c.status === "fail").length;
|
|
5054
5173
|
const warned = checks.filter((c) => c.status === "warn").length;
|
|
5055
|
-
const lines = checks.map((c) =>
|
|
5056
|
-
const summary = failed > 0 ?
|
|
5057
|
-
return ["
|
|
5174
|
+
const lines = checks.map((c) => ` ${symbol(LEVEL[c.status])} ${c.label}${c.detail ? `: ${dim(c.detail)}` : ""}`);
|
|
5175
|
+
const summary = failed > 0 ? verdict("fail", `${failed} check${failed === 1 ? "" : "s"} failed${warned ? `, ${warned} warning${warned === 1 ? "" : "s"}` : ""}.`) : warned > 0 ? verdict("warn", `Passed with ${warned} warning${warned === 1 ? "" : "s"}.`) : verdict("ok", "All checks green.");
|
|
5176
|
+
return ["", ...lines, "", summary].join("\n");
|
|
5058
5177
|
}
|
|
5059
5178
|
var defaultDeps = () => ({
|
|
5060
5179
|
nodeVersion: process.versions.node,
|
|
5061
5180
|
probeRuntimes: probeRuntimeStatuses,
|
|
5062
5181
|
probeHub,
|
|
5063
|
-
out
|
|
5064
|
-
`)
|
|
5182
|
+
out
|
|
5065
5183
|
});
|
|
5066
5184
|
async function runDoctor(inputs, deps = {}) {
|
|
5067
5185
|
const d = { ...defaultDeps(), ...deps };
|
|
@@ -5146,15 +5264,15 @@ function logsCommand(inputs) {
|
|
|
5146
5264
|
var LEVEL_RANK = { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 };
|
|
5147
5265
|
var levelName = (n) => Object.entries(LEVEL_RANK).find(([, rank]) => rank === n)?.[0] ?? String(n);
|
|
5148
5266
|
function parseRecords(raw) {
|
|
5149
|
-
const
|
|
5267
|
+
const out2 = [];
|
|
5150
5268
|
for (const line of raw.split("\n")) {
|
|
5151
5269
|
if (!line.trim()) continue;
|
|
5152
5270
|
try {
|
|
5153
|
-
|
|
5271
|
+
out2.push(JSON.parse(line));
|
|
5154
5272
|
} catch {
|
|
5155
5273
|
}
|
|
5156
5274
|
}
|
|
5157
|
-
return
|
|
5275
|
+
return out2;
|
|
5158
5276
|
}
|
|
5159
5277
|
function filterRecords(records, q) {
|
|
5160
5278
|
const min = q.level ? LEVEL_RANK[q.level] ?? 0 : 0;
|
|
@@ -5230,8 +5348,7 @@ var defaultLogsDeps = (files, jsonlFile) => ({
|
|
|
5230
5348
|
child.on("error", () => resolve3(1));
|
|
5231
5349
|
child.on("close", (code) => resolve3(code ?? 0));
|
|
5232
5350
|
}),
|
|
5233
|
-
out
|
|
5234
|
-
`)
|
|
5351
|
+
out
|
|
5235
5352
|
});
|
|
5236
5353
|
|
|
5237
5354
|
// src/process-safety.ts
|
|
@@ -5362,15 +5479,18 @@ function worst(checks) {
|
|
|
5362
5479
|
if (checks.some((c) => c.status === "warn")) return "warn";
|
|
5363
5480
|
return "pass";
|
|
5364
5481
|
}
|
|
5365
|
-
function renderStage(index, total,
|
|
5366
|
-
const head = `[${index}/${total}] ${
|
|
5367
|
-
const findings =
|
|
5482
|
+
function renderStage(index, total, stage, out2) {
|
|
5483
|
+
const head = ` ${dim(`[${index}/${total}]`)} ${stage.label}`;
|
|
5484
|
+
const findings = stage.checks.filter((c) => c.status !== "pass");
|
|
5368
5485
|
if (findings.length === 0) {
|
|
5369
|
-
|
|
5486
|
+
out2(`${head} ${symbol("ok")}`);
|
|
5370
5487
|
return;
|
|
5371
5488
|
}
|
|
5372
|
-
|
|
5373
|
-
for (const f of findings)
|
|
5489
|
+
out2(head);
|
|
5490
|
+
for (const f of findings) {
|
|
5491
|
+
const level = f.status === "fail" ? "fail" : "warn";
|
|
5492
|
+
out2(` ${symbol(level)} ${f.label}: ${dim(f.detail ?? f.status)}`);
|
|
5493
|
+
}
|
|
5374
5494
|
}
|
|
5375
5495
|
async function runPreflight(inputs, deps = {}) {
|
|
5376
5496
|
const d = { ...defaultDeps2(), ...deps };
|
|
@@ -5422,8 +5542,7 @@ var defaultDeps2 = () => ({
|
|
|
5422
5542
|
probeHub,
|
|
5423
5543
|
gatherHost: gatherHostFacts,
|
|
5424
5544
|
newRunId: () => randomUUID2(),
|
|
5425
|
-
out
|
|
5426
|
-
`)
|
|
5545
|
+
out
|
|
5427
5546
|
});
|
|
5428
5547
|
function commandPresent(cmd) {
|
|
5429
5548
|
try {
|
|
@@ -5760,16 +5879,18 @@ function parseServiceStatus(manager, unitExists, probe2) {
|
|
|
5760
5879
|
const running = active === "active";
|
|
5761
5880
|
return running && pid > 0 ? { installed: true, running: true, pid } : { installed: true, running };
|
|
5762
5881
|
}
|
|
5763
|
-
function printUnsupported(
|
|
5764
|
-
|
|
5765
|
-
|
|
5882
|
+
function printUnsupported(out2) {
|
|
5883
|
+
out2(verdict("fail", "dahrk service is supported on macOS (launchd) and Linux (systemd) only."));
|
|
5884
|
+
out2("");
|
|
5885
|
+
out2(hint("On other platforms, run the node under a process manager such as pm2 (see the README)."));
|
|
5766
5886
|
return 1;
|
|
5767
5887
|
}
|
|
5768
5888
|
function runCommands(commands, d) {
|
|
5769
5889
|
for (const cmd of commands) {
|
|
5770
|
-
const code = d.run(cmd.argv);
|
|
5890
|
+
const { code, output } = d.run(cmd.argv);
|
|
5771
5891
|
if (code !== 0 && !cmd.ignoreFailure) {
|
|
5772
|
-
d.out(`
|
|
5892
|
+
d.out(verdict("fail", `command failed (${code}): ${cmd.argv.join(" ")}`));
|
|
5893
|
+
for (const line of output.split("\n")) if (line.trim()) d.out(` ${dim(line)}`);
|
|
5773
5894
|
return code;
|
|
5774
5895
|
}
|
|
5775
5896
|
}
|
|
@@ -5777,13 +5898,13 @@ function runCommands(commands, d) {
|
|
|
5777
5898
|
}
|
|
5778
5899
|
async function runServiceInstall(inputs, deps = {}) {
|
|
5779
5900
|
const d = { ...defaultDeps3(), ...deps };
|
|
5780
|
-
d.out("dahrk service install");
|
|
5781
5901
|
d.out("");
|
|
5782
5902
|
const manager = detectManager(d.platform);
|
|
5783
5903
|
if (manager === "unsupported") return printUnsupported(d.out);
|
|
5784
5904
|
if (!inputs.token) {
|
|
5785
|
-
d.out("No enrolment token
|
|
5786
|
-
d.out("
|
|
5905
|
+
d.out(verdict("fail", "No enrolment token."));
|
|
5906
|
+
d.out("");
|
|
5907
|
+
d.out(hint("Pass --token <token>, or set DAHRK_ENROL_TOKEN. Get one at https://app.dahrk.ai."));
|
|
5787
5908
|
return 2;
|
|
5788
5909
|
}
|
|
5789
5910
|
const plan = buildPlan({
|
|
@@ -5802,26 +5923,41 @@ async function runServiceInstall(inputs, deps = {}) {
|
|
|
5802
5923
|
d.mkdirp(dirOf(plan.filePath));
|
|
5803
5924
|
d.writeFile(plan.filePath, plan.content);
|
|
5804
5925
|
} catch (e) {
|
|
5805
|
-
d.out(`Could not write the service file at ${plan.filePath}
|
|
5926
|
+
d.out(verdict("fail", `Could not write the service file at ${plan.filePath}`));
|
|
5927
|
+
d.out("");
|
|
5928
|
+
d.out(hint(e.message));
|
|
5806
5929
|
return 1;
|
|
5807
5930
|
}
|
|
5808
|
-
d.out(`Wrote ${plan.manager} service: ${plan.filePath}`);
|
|
5809
5931
|
const code = runCommands(plan.installCommands, d);
|
|
5810
|
-
d.out("");
|
|
5811
5932
|
if (code !== 0) {
|
|
5812
|
-
d.out(
|
|
5813
|
-
d.out(
|
|
5933
|
+
d.out("");
|
|
5934
|
+
d.out(hint(`The service file is in place at ${plan.filePath}. Fix the error above and re-run.`));
|
|
5814
5935
|
return code;
|
|
5815
5936
|
}
|
|
5816
|
-
d.out("Installed. The node
|
|
5817
|
-
d.out(
|
|
5818
|
-
d.out("
|
|
5937
|
+
d.out(verdict("ok", "Installed. The node starts on boot and restarts on failure."));
|
|
5938
|
+
d.out("");
|
|
5939
|
+
d.out(kv("unit", plan.filePath));
|
|
5940
|
+
d.out("");
|
|
5941
|
+
d.out(hints([["logs", plan.logHint], ["uninstall", "dahrk service uninstall"]]));
|
|
5819
5942
|
return 0;
|
|
5820
5943
|
}
|
|
5821
5944
|
function availabilityCommand(manager) {
|
|
5822
5945
|
return manager === "systemd" ? ["systemctl", "--user", "show", "-p", "Version"] : void 0;
|
|
5823
5946
|
}
|
|
5824
|
-
|
|
5947
|
+
function liveNodePid(d) {
|
|
5948
|
+
const manager = detectManager(d.platform);
|
|
5949
|
+
if (manager !== "unsupported") {
|
|
5950
|
+
const unit = unitPath(manager, d.homeDir);
|
|
5951
|
+
if (d.fileExists(unit)) {
|
|
5952
|
+
const status = parseServiceStatus(manager, true, d.capture(statusCommand(manager)));
|
|
5953
|
+
if (status.running && status.pid) return status.pid;
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5956
|
+
const held = parseLock(d.readFile(d.lockFile));
|
|
5957
|
+
return held !== void 0 && d.isAlive(held) ? held : void 0;
|
|
5958
|
+
}
|
|
5959
|
+
var nodeIsRunning = (deps = {}) => liveNodePid({ ...defaultDeps3(), ...deps }) !== void 0;
|
|
5960
|
+
async function runNodeStart(inputs, deps = {}, opts = {}) {
|
|
5825
5961
|
const d = { ...defaultDeps3(), ...deps };
|
|
5826
5962
|
const manager = detectManager(d.platform);
|
|
5827
5963
|
if (manager === "unsupported") {
|
|
@@ -5834,8 +5970,9 @@ async function runNodeStart(inputs, deps = {}) {
|
|
|
5834
5970
|
const filePath = unitPath(manager, d.homeDir);
|
|
5835
5971
|
const unitExists = d.fileExists(filePath);
|
|
5836
5972
|
if (!inputs.token && !unitExists) {
|
|
5837
|
-
d.out("No enrolment token
|
|
5838
|
-
d.out("
|
|
5973
|
+
d.out(verdict("fail", "No enrolment token."));
|
|
5974
|
+
d.out("");
|
|
5975
|
+
d.out(hint("Pass --token <token>, or set DAHRK_ENROL_TOKEN. Get one at https://app.dahrk.ai."));
|
|
5839
5976
|
return { kind: "error", code: 2 };
|
|
5840
5977
|
}
|
|
5841
5978
|
const plan = buildPlan({
|
|
@@ -5851,12 +5988,10 @@ async function runNodeStart(inputs, deps = {}) {
|
|
|
5851
5988
|
});
|
|
5852
5989
|
const canRender = inputs.token !== void 0;
|
|
5853
5990
|
const current = canRender && unitExists && unitIsCurrent(plan, d.readFile(filePath));
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
d.out(` logs: ${plan.logHint}`);
|
|
5859
|
-
return { kind: "running", code: 0 };
|
|
5991
|
+
if (!opts.alwaysLoad) {
|
|
5992
|
+
const probe2 = unitExists ? d.capture(statusCommand(manager)) : { code: 1, stdout: "" };
|
|
5993
|
+
const status = parseServiceStatus(manager, unitExists, probe2);
|
|
5994
|
+
if (current && status.running) return { kind: "running", code: 0, already: true };
|
|
5860
5995
|
}
|
|
5861
5996
|
if (canRender && !current) {
|
|
5862
5997
|
try {
|
|
@@ -5867,28 +6002,65 @@ async function runNodeStart(inputs, deps = {}) {
|
|
|
5867
6002
|
d.out(`Could not write the service file at ${plan.filePath}: ${e.message}`);
|
|
5868
6003
|
return { kind: "error", code: 1 };
|
|
5869
6004
|
}
|
|
5870
|
-
d.out(unitExists ? `Updated ${plan.manager} service
|
|
6005
|
+
d.out(hint(unitExists ? `Updated the ${plan.manager} service.` : `Installed the ${plan.manager} service.`));
|
|
5871
6006
|
}
|
|
5872
6007
|
const code = runCommands(plan.installCommands, d);
|
|
5873
6008
|
if (code !== 0) {
|
|
5874
|
-
d.out(`The service file is in place but starting it failed
|
|
6009
|
+
d.out(hint(`The service file is in place at ${plan.filePath}, but starting it failed.`));
|
|
5875
6010
|
return { kind: "error", code };
|
|
5876
6011
|
}
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
6012
|
+
return { kind: "running", code: 0, already: false };
|
|
6013
|
+
}
|
|
6014
|
+
async function runNodeRestart(inputs, deps = {}) {
|
|
6015
|
+
const d = { ...defaultDeps3(), ...deps };
|
|
6016
|
+
const manager = detectManager(d.platform);
|
|
6017
|
+
if (manager === "unsupported") {
|
|
6018
|
+
return { kind: "foreground", reason: "no supported supervisor on this platform (launchd / systemd)" };
|
|
6019
|
+
}
|
|
6020
|
+
const busy = guardBusy(d, inputs.force ?? false, "restart");
|
|
6021
|
+
if (busy) return { kind: "error", code: busy };
|
|
6022
|
+
const plan = buildPlan({
|
|
6023
|
+
manager,
|
|
6024
|
+
nodeBin: d.nodeBin,
|
|
6025
|
+
scriptPath: d.scriptPath,
|
|
6026
|
+
token: "-",
|
|
6027
|
+
homeDir: d.homeDir,
|
|
6028
|
+
logDir: d.logDir
|
|
6029
|
+
});
|
|
6030
|
+
if (d.fileExists(plan.filePath)) runCommands(plan.stopCommands, d);
|
|
6031
|
+
return runNodeStart(inputs, deps, { alwaysLoad: true });
|
|
6032
|
+
}
|
|
6033
|
+
function guardBusy(d, force, verb) {
|
|
6034
|
+
const live = liveNodePid(d);
|
|
6035
|
+
const jobs = live === void 0 ? [] : d.inFlightJobs().filter((j) => j.nodePid === live);
|
|
6036
|
+
if (jobs.length === 0) return void 0;
|
|
6037
|
+
if (force) {
|
|
6038
|
+
d.out(verdict("warn", `Forcing ${verb} with ${jobs.length} job(s) in flight.`));
|
|
6039
|
+
return void 0;
|
|
6040
|
+
}
|
|
6041
|
+
d.out(verdict("warn", `This node is running ${jobs.length} job(s); \`${verb}\` would kill them.`));
|
|
6042
|
+
d.out("");
|
|
6043
|
+
for (const j of jobs) {
|
|
6044
|
+
const where = j.stageId ? `${j.runId} / ${j.stageId}` : j.runId;
|
|
6045
|
+
d.out(kv("", `${where} ${dim(humanDuration(Date.now() - j.startedAt))}`));
|
|
6046
|
+
}
|
|
6047
|
+
d.out("");
|
|
6048
|
+
d.out(hint(`Wait for them to finish, or \`dahrk ${verb} --force\` to interrupt them anyway.`));
|
|
6049
|
+
return BUSY_NODE;
|
|
5881
6050
|
}
|
|
6051
|
+
var BUSY_NODE = 4;
|
|
5882
6052
|
var STOP_FOREIGN_NODE = 3;
|
|
5883
6053
|
function foreignNodePid(lock, servicePid, isAlive2) {
|
|
5884
6054
|
const held = parseLock(lock);
|
|
5885
6055
|
if (held === void 0 || held === servicePid) return void 0;
|
|
5886
6056
|
return isAlive2(held) ? held : void 0;
|
|
5887
6057
|
}
|
|
5888
|
-
async function runNodeStop(deps = {}) {
|
|
6058
|
+
async function runNodeStop(inputs = {}, deps = {}) {
|
|
5889
6059
|
const d = { ...defaultDeps3(), ...deps };
|
|
5890
6060
|
const manager = detectManager(d.platform);
|
|
5891
6061
|
if (manager === "unsupported") return printUnsupported(d.out);
|
|
6062
|
+
const busy = guardBusy(d, inputs.force ?? false, "stop");
|
|
6063
|
+
if (busy) return busy;
|
|
5892
6064
|
const plan = buildPlan({
|
|
5893
6065
|
manager,
|
|
5894
6066
|
nodeBin: d.nodeBin,
|
|
@@ -5898,28 +6070,31 @@ async function runNodeStop(deps = {}) {
|
|
|
5898
6070
|
logDir: d.logDir
|
|
5899
6071
|
});
|
|
5900
6072
|
if (!d.fileExists(plan.filePath)) {
|
|
5901
|
-
d.out("No service installed, so there is nothing to stop.");
|
|
5902
|
-
d.out("
|
|
6073
|
+
d.out(verdict("info", "No service installed, so there is nothing to stop."));
|
|
6074
|
+
d.out("");
|
|
6075
|
+
d.out(hint("A node running in a terminal (`dahrk start --foreground`) stops with Ctrl-C."));
|
|
5903
6076
|
return reportForeignNode(d, void 0) ?? 0;
|
|
5904
6077
|
}
|
|
5905
6078
|
const servicePid = parseServiceStatus(manager, true, d.capture(statusCommand(manager))).pid;
|
|
5906
6079
|
runCommands(plan.stopCommands, d);
|
|
5907
|
-
d.out("Node stopped.
|
|
6080
|
+
d.out(verdict("ok", "Node stopped."));
|
|
6081
|
+
d.out("");
|
|
6082
|
+
d.out(hint("It stays stopped across reboots until you run `dahrk start`."));
|
|
5908
6083
|
return reportForeignNode(d, servicePid) ?? 0;
|
|
5909
6084
|
}
|
|
5910
6085
|
function reportForeignNode(d, servicePid) {
|
|
5911
6086
|
const pid = foreignNodePid(d.readFile(d.lockFile), servicePid, d.isAlive);
|
|
5912
6087
|
if (pid === void 0) return void 0;
|
|
5913
6088
|
d.out("");
|
|
5914
|
-
d.out(`
|
|
5915
|
-
d.out("
|
|
5916
|
-
d.out("
|
|
5917
|
-
d.out("
|
|
6089
|
+
d.out(verdict("warn", `A node is STILL RUNNING on this host (pid ${pid}). \`dahrk stop\` cannot stop it.`));
|
|
6090
|
+
d.out("");
|
|
6091
|
+
d.out(hint("Something else supervises it: pm2, a container, or `dahrk start --foreground` in a terminal."));
|
|
6092
|
+
d.out(hint("It is not a stray copy - it holds this node's identity, so it is still connected to the hub"));
|
|
6093
|
+
d.out(hint("and still taking Jobs. Stop it where it was started, or kill the pid."));
|
|
5918
6094
|
return STOP_FOREIGN_NODE;
|
|
5919
6095
|
}
|
|
5920
6096
|
async function runServiceUninstall(deps = {}) {
|
|
5921
6097
|
const d = { ...defaultDeps3(), ...deps };
|
|
5922
|
-
d.out("dahrk service uninstall");
|
|
5923
6098
|
d.out("");
|
|
5924
6099
|
const manager = detectManager(d.platform);
|
|
5925
6100
|
if (manager === "unsupported") return printUnsupported(d.out);
|
|
@@ -5932,17 +6107,19 @@ async function runServiceUninstall(deps = {}) {
|
|
|
5932
6107
|
logDir: d.logDir
|
|
5933
6108
|
});
|
|
5934
6109
|
if (!d.fileExists(plan.filePath)) {
|
|
5935
|
-
d.out(
|
|
6110
|
+
d.out(verdict("info", "No service installed. Nothing to do."));
|
|
5936
6111
|
return 0;
|
|
5937
6112
|
}
|
|
5938
6113
|
runCommands(plan.uninstallCommands, d);
|
|
5939
6114
|
try {
|
|
5940
6115
|
d.removeFile(plan.filePath);
|
|
5941
6116
|
} catch (e) {
|
|
5942
|
-
d.out(`Deregistered, but could not delete ${plan.filePath}
|
|
6117
|
+
d.out(verdict("warn", `Deregistered, but could not delete ${plan.filePath}`));
|
|
6118
|
+
d.out("");
|
|
6119
|
+
d.out(hint(e.message));
|
|
5943
6120
|
return 1;
|
|
5944
6121
|
}
|
|
5945
|
-
d.out(
|
|
6122
|
+
d.out(verdict("ok", "Removed. The node will no longer start on boot."));
|
|
5946
6123
|
return 0;
|
|
5947
6124
|
}
|
|
5948
6125
|
function dirOf(path) {
|
|
@@ -5981,6 +6158,10 @@ var defaultDeps3 = () => ({
|
|
|
5981
6158
|
logDir: logDir(process.env),
|
|
5982
6159
|
lockFile: lockFile(process.env),
|
|
5983
6160
|
isAlive,
|
|
6161
|
+
// Read-only here, and best-effort by construction: a missing or corrupt ledger reads as "no jobs", which
|
|
6162
|
+
// degrades the guard to today's behaviour rather than blocking a stop the operator needs.
|
|
6163
|
+
inFlightJobs: () => fileJobLedger(jobLedgerFile(stateDir(process.env)), () => {
|
|
6164
|
+
}).all(),
|
|
5984
6165
|
// Snapshot the operator's PATH at install time so the daemon finds git + the runtime CLIs (Homebrew /
|
|
5985
6166
|
// npm-global bins) that a supervisor's minimal PATH would otherwise hide.
|
|
5986
6167
|
pathEnv: process.env.PATH,
|
|
@@ -6001,14 +6182,21 @@ var defaultDeps3 = () => ({
|
|
|
6001
6182
|
},
|
|
6002
6183
|
removeFile: (path) => rmSync7(path, { force: true }),
|
|
6003
6184
|
fileExists: (path) => existsSync13(path),
|
|
6185
|
+
// Captured, never inherited. A supervisor's stderr is ours to relay when it matters, not to leak
|
|
6186
|
+
// unconditionally: `runCommands` decides who needs to read it.
|
|
6004
6187
|
run: (argv) => {
|
|
6005
6188
|
const [cmd, ...args] = argv;
|
|
6006
6189
|
try {
|
|
6007
|
-
execFileSync7(cmd, args, {
|
|
6008
|
-
|
|
6190
|
+
const stdout = execFileSync7(cmd, args, {
|
|
6191
|
+
encoding: "utf8",
|
|
6192
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6193
|
+
});
|
|
6194
|
+
return { code: 0, output: stdout };
|
|
6009
6195
|
} catch (e) {
|
|
6010
6196
|
const status = e.status;
|
|
6011
|
-
|
|
6197
|
+
const { stdout, stderr } = e;
|
|
6198
|
+
const output = `${stdout?.toString() ?? ""}${stderr?.toString() ?? ""}`;
|
|
6199
|
+
return { code: typeof status === "number" ? status : 1, output };
|
|
6012
6200
|
}
|
|
6013
6201
|
},
|
|
6014
6202
|
capture: (argv) => {
|
|
@@ -6024,8 +6212,7 @@ var defaultDeps3 = () => ({
|
|
|
6024
6212
|
return { code: typeof status === "number" ? status : 1, stdout: "" };
|
|
6025
6213
|
}
|
|
6026
6214
|
},
|
|
6027
|
-
out
|
|
6028
|
-
`)
|
|
6215
|
+
out
|
|
6029
6216
|
});
|
|
6030
6217
|
|
|
6031
6218
|
// src/update.ts
|
|
@@ -6073,58 +6260,80 @@ function isNewer(latest, current) {
|
|
|
6073
6260
|
}
|
|
6074
6261
|
return false;
|
|
6075
6262
|
}
|
|
6076
|
-
function printChannelCommands(
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6263
|
+
function printChannelCommands(out2) {
|
|
6264
|
+
out2(kv("npm", CHANNEL_COMMANDS.npm));
|
|
6265
|
+
out2(kv("Homebrew", CHANNEL_COMMANDS.homebrew));
|
|
6266
|
+
out2(kv("curl", CHANNEL_COMMANDS.curl));
|
|
6080
6267
|
}
|
|
6081
6268
|
async function runUpdate(inputs, deps = {}) {
|
|
6082
6269
|
const d = { ...defaultDeps4(), ...deps };
|
|
6083
6270
|
const current = inputs.currentVersion;
|
|
6084
|
-
d.out("dahrk update");
|
|
6085
6271
|
d.out("");
|
|
6086
6272
|
let latest;
|
|
6087
6273
|
try {
|
|
6088
6274
|
latest = await d.fetchLatest();
|
|
6089
6275
|
} catch (e) {
|
|
6090
|
-
d.out(`Could not determine the latest version: ${e.message}`);
|
|
6091
|
-
d.out(
|
|
6276
|
+
d.out(verdict("fail", `Could not determine the latest version: ${e.message}`));
|
|
6277
|
+
d.out("");
|
|
6278
|
+
d.out(hint(`You are on ${current}. See https://www.npmjs.com/package/dahrk-node for releases.`));
|
|
6092
6279
|
return 1;
|
|
6093
6280
|
}
|
|
6281
|
+
d.saveResult({ updateCheckedAt: new Date(d.now()).toISOString(), updateLatest: latest });
|
|
6094
6282
|
if (!isNewer(latest, current)) {
|
|
6095
|
-
d.out(`Already on the latest version (${current}).`);
|
|
6283
|
+
d.out(verdict("ok", `Already on the latest version (${current}).`));
|
|
6096
6284
|
return 0;
|
|
6097
6285
|
}
|
|
6098
|
-
d.out(`Update available: ${current}
|
|
6286
|
+
d.out(verdict("warn", `Update available: ${current} ${arrow()} ${latest}`));
|
|
6099
6287
|
const channel = detectChannel(d.binPath);
|
|
6100
6288
|
const cmd = upgradeCommand(channel);
|
|
6101
6289
|
if (inputs.check) {
|
|
6102
6290
|
d.out("");
|
|
6103
6291
|
if (cmd) {
|
|
6104
|
-
d.out(`Run \`dahrk update\` to upgrade (${channel}: ${cmd.display}).`);
|
|
6292
|
+
d.out(hint(`Run \`dahrk update\` to upgrade (${channel}: ${cmd.display}).`));
|
|
6105
6293
|
} else {
|
|
6106
|
-
d.out("Run `dahrk update`, or upgrade with the command for your install channel:");
|
|
6294
|
+
d.out(hint("Run `dahrk update`, or upgrade with the command for your install channel:"));
|
|
6107
6295
|
printChannelCommands(d.out);
|
|
6108
6296
|
}
|
|
6109
6297
|
return 0;
|
|
6110
6298
|
}
|
|
6111
6299
|
if (!cmd) {
|
|
6112
6300
|
d.out("");
|
|
6113
|
-
d.out("Could not tell how this client was installed. Upgrade with the command for your channel:");
|
|
6301
|
+
d.out(hint("Could not tell how this client was installed. Upgrade with the command for your channel:"));
|
|
6114
6302
|
printChannelCommands(d.out);
|
|
6115
6303
|
return 0;
|
|
6116
6304
|
}
|
|
6117
6305
|
d.out("");
|
|
6118
|
-
d.out(`Upgrading via ${channel}: ${cmd.display}`);
|
|
6306
|
+
d.out(hint(`Upgrading via ${channel}: ${cmd.display}`));
|
|
6307
|
+
const { code, output } = d.runUpgrade(cmd.argv);
|
|
6119
6308
|
d.out("");
|
|
6120
|
-
|
|
6309
|
+
if (code !== 0) {
|
|
6310
|
+
d.out(verdict("fail", `Upgrade failed (exit ${code}).`));
|
|
6311
|
+
for (const line of output.split("\n")) if (line.trim()) d.out(` ${dim(line)}`);
|
|
6312
|
+
d.out("");
|
|
6313
|
+
d.out(hint(`Run it yourself to retry: ${cmd.display}`));
|
|
6314
|
+
return code;
|
|
6315
|
+
}
|
|
6316
|
+
if (inputs.verbose) {
|
|
6317
|
+
for (const line of output.split("\n")) if (line.trim()) d.out(` ${dim(line)}`);
|
|
6318
|
+
}
|
|
6319
|
+
d.out(verdict("ok", `Upgraded to ${latest}.`));
|
|
6320
|
+
await offerRestart(d);
|
|
6321
|
+
return 0;
|
|
6322
|
+
}
|
|
6323
|
+
async function offerRestart(d) {
|
|
6324
|
+
if (!d.nodeRunning()) return;
|
|
6325
|
+
if (!d.interactive()) {
|
|
6326
|
+
d.out(hint("A node is running on the old build. Run `dahrk restart` to pick this up."));
|
|
6327
|
+
return;
|
|
6328
|
+
}
|
|
6121
6329
|
d.out("");
|
|
6122
|
-
if (
|
|
6123
|
-
d.out(
|
|
6124
|
-
|
|
6125
|
-
d.out(`Upgrade exited with code ${code}. Run it yourself to see the error: ${cmd.display}`);
|
|
6330
|
+
if (!await d.confirm("A node is running on the old build. Restart it now?")) {
|
|
6331
|
+
d.out(hint("Left running on the old build. Run `dahrk restart` when you are ready."));
|
|
6332
|
+
return;
|
|
6126
6333
|
}
|
|
6127
|
-
|
|
6334
|
+
const code = await d.restart();
|
|
6335
|
+
if (code !== 0) d.out(hint("The node is still on the old build. Run `dahrk restart` once it is free."));
|
|
6336
|
+
else d.out(verdict("ok", "Node restarted on the new build."));
|
|
6128
6337
|
}
|
|
6129
6338
|
async function fetchLatestVersion(signal) {
|
|
6130
6339
|
const res = await fetch(LATEST_URL, {
|
|
@@ -6139,23 +6348,45 @@ async function fetchLatestVersion(signal) {
|
|
|
6139
6348
|
function spawnUpgrade(argv) {
|
|
6140
6349
|
const [cmd, ...args] = argv;
|
|
6141
6350
|
try {
|
|
6142
|
-
execFileSync8(cmd, args, {
|
|
6143
|
-
|
|
6351
|
+
const stdout = execFileSync8(cmd, args, {
|
|
6352
|
+
encoding: "utf8",
|
|
6353
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6354
|
+
});
|
|
6355
|
+
return { code: 0, output: stdout };
|
|
6144
6356
|
} catch (e) {
|
|
6145
6357
|
const status = e.status;
|
|
6146
|
-
|
|
6358
|
+
const { stdout, stderr } = e;
|
|
6359
|
+
return {
|
|
6360
|
+
code: typeof status === "number" ? status : 1,
|
|
6361
|
+
output: `${stdout?.toString() ?? ""}${stderr?.toString() ?? ""}`
|
|
6362
|
+
};
|
|
6147
6363
|
}
|
|
6148
6364
|
}
|
|
6149
6365
|
var defaultDeps4 = () => ({
|
|
6150
6366
|
binPath: process.argv[1],
|
|
6151
6367
|
fetchLatest: fetchLatestVersion,
|
|
6152
6368
|
runUpgrade: spawnUpgrade,
|
|
6153
|
-
|
|
6154
|
-
`
|
|
6369
|
+
// The same cache the daemon's periodic check writes (`updateCheckDeps` in main.ts), so both writers agree
|
|
6370
|
+
// on where "what the registry last said" lives and `status` has one place to read it from.
|
|
6371
|
+
saveResult: (patch) => writeState(process.env, patch),
|
|
6372
|
+
now: () => Date.now(),
|
|
6373
|
+
nodeRunning: nodeIsRunning,
|
|
6374
|
+
interactive: isInteractive,
|
|
6375
|
+
confirm,
|
|
6376
|
+
// No inputs: the installed unit already carries this node's token and overrides in its env block, so a
|
|
6377
|
+
// restart re-registers exactly what was there before, with the new client on disk behind it. `desired` is
|
|
6378
|
+
// untouched on purpose - the node was running, and it still is.
|
|
6379
|
+
restart: async () => {
|
|
6380
|
+
const outcome = await runNodeRestart({});
|
|
6381
|
+
if (outcome.kind === "running") return 0;
|
|
6382
|
+
return outcome.kind === "error" ? outcome.code : 1;
|
|
6383
|
+
},
|
|
6384
|
+
out
|
|
6155
6385
|
});
|
|
6156
6386
|
|
|
6157
6387
|
// src/update-check.ts
|
|
6158
|
-
var DEFAULT_INTERVAL_MS =
|
|
6388
|
+
var DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
6389
|
+
var STALE_AFTER_INTERVALS = 4;
|
|
6159
6390
|
var FETCH_TIMEOUT_MS = 1500;
|
|
6160
6391
|
function checkSuppressed(env) {
|
|
6161
6392
|
return Boolean(env.DAHRK_NO_UPDATE_CHECK || env.NO_UPDATE_NOTIFIER || env.CI);
|
|
@@ -6188,7 +6419,8 @@ async function checkForUpdate(currentVersion, deps) {
|
|
|
6188
6419
|
if (checkSuppressed(deps.env)) return void 0;
|
|
6189
6420
|
const state = deps.readState();
|
|
6190
6421
|
if (!shouldCheck(deps.now(), state.updateCheckedAt, checkIntervalMs(deps.env), deps.env)) {
|
|
6191
|
-
|
|
6422
|
+
const cached = cachedUpdate(state, currentVersion, deps.binPath);
|
|
6423
|
+
return cached.kind === "available" ? cached : void 0;
|
|
6192
6424
|
}
|
|
6193
6425
|
let latest;
|
|
6194
6426
|
try {
|
|
@@ -6200,88 +6432,179 @@ async function checkForUpdate(currentVersion, deps) {
|
|
|
6200
6432
|
if (!isNewer(latest, currentVersion)) return void 0;
|
|
6201
6433
|
return { current: currentVersion, latest, channel: detectChannel(deps.binPath) };
|
|
6202
6434
|
}
|
|
6435
|
+
var isStale = (checkedAt, now, intervalMs) => now - checkedAt >= intervalMs * STALE_AFTER_INTERVALS;
|
|
6203
6436
|
function cachedUpdate(state, currentVersion, binPath) {
|
|
6204
|
-
const latest = state
|
|
6205
|
-
|
|
6206
|
-
|
|
6437
|
+
const { updateLatest: latest, updateCheckedAt } = state;
|
|
6438
|
+
const checkedAt = updateCheckedAt ? Date.parse(updateCheckedAt) : NaN;
|
|
6439
|
+
if (!latest || !Number.isFinite(checkedAt)) return { kind: "unknown" };
|
|
6440
|
+
return isNewer(latest, currentVersion) ? { kind: "available", checkedAt, current: currentVersion, latest, channel: detectChannel(binPath) } : { kind: "current", checkedAt };
|
|
6207
6441
|
}
|
|
6208
6442
|
|
|
6209
6443
|
// src/status.ts
|
|
6210
|
-
|
|
6444
|
+
function presenceVerdict(f) {
|
|
6445
|
+
switch (f.presence.kind) {
|
|
6446
|
+
case "running": {
|
|
6447
|
+
const pid = f.presence.pid ? ` (pid ${f.presence.pid})` : "";
|
|
6448
|
+
return { level: "ok", text: `Node running${pid}` };
|
|
6449
|
+
}
|
|
6450
|
+
case "foreign":
|
|
6451
|
+
return { level: "ok", text: `Node running under another supervisor (pid ${f.presence.pid})` };
|
|
6452
|
+
case "stopped":
|
|
6453
|
+
return { level: "warn", text: "Node stopped" };
|
|
6454
|
+
case "not-installed":
|
|
6455
|
+
return { level: "warn", text: "Node not installed" };
|
|
6456
|
+
case "crashed":
|
|
6457
|
+
return { level: "fail", text: "Node is installed but NOT running - it is failing to start or crash-looping" };
|
|
6458
|
+
case "no-supervisor":
|
|
6459
|
+
return { level: "warn", text: "No supervisor on this host" };
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
function presenceHints(f) {
|
|
6463
|
+
switch (f.presence.kind) {
|
|
6464
|
+
case "running":
|
|
6465
|
+
case "foreign":
|
|
6466
|
+
return [hints([["logs", "dahrk logs -f"], ["stop", "dahrk stop"]])];
|
|
6467
|
+
case "stopped":
|
|
6468
|
+
return [hints([["start", "dahrk start"]])];
|
|
6469
|
+
case "not-installed":
|
|
6470
|
+
return [hints([["start", "dahrk start"]])];
|
|
6471
|
+
case "crashed":
|
|
6472
|
+
return [hints([["logs", "dahrk logs -f"], ["check", "dahrk doctor"], ["report", "dahrk diagnose"]])];
|
|
6473
|
+
case "no-supervisor":
|
|
6474
|
+
return [hints([["run it here", "dahrk start --foreground"]])];
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6211
6477
|
function renderStatus(f) {
|
|
6212
|
-
const
|
|
6478
|
+
const v = presenceVerdict(f);
|
|
6479
|
+
const lines = ["", verdict(v.level, v.text)];
|
|
6480
|
+
if (f.update?.kind === "available") {
|
|
6481
|
+
lines.push(
|
|
6482
|
+
verdict("warn", `Update available: ${f.clientVersion} ${arrow()} ${f.update.latest}`) + ` ${dim("run `dahrk update`")}`
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6485
|
+
lines.push("");
|
|
6213
6486
|
const { nodeId, name, tenantId, enrolToken } = f.state;
|
|
6214
6487
|
if (enrolToken) {
|
|
6215
|
-
|
|
6216
|
-
|
|
6488
|
+
const who = name ? name : "yes";
|
|
6489
|
+
lines.push(kv("Enrolled", tenantId ? `${who} ${dim("\xB7")} ${tenantId}` : who));
|
|
6217
6490
|
} else if (f.envToken) {
|
|
6218
|
-
lines.push(
|
|
6491
|
+
lines.push(kv("Enrolled", "via DAHRK_ENROL_TOKEN (caches on the next successful start)"));
|
|
6219
6492
|
} else {
|
|
6220
|
-
lines.push(
|
|
6493
|
+
lines.push(kv("Enrolled", `no ${dim("run `dahrk start --token <token>` once to enrol")}`));
|
|
6221
6494
|
}
|
|
6222
|
-
lines.push(
|
|
6223
|
-
lines.push(
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
)
|
|
6228
|
-
);
|
|
6229
|
-
lines.push(bullet("Hub", f.hubUrl));
|
|
6495
|
+
lines.push(kv("Node id", nodeId ?? dim("not yet minted (first `dahrk start` mints one)")));
|
|
6496
|
+
lines.push(kv("Client", `${f.clientVersion}${currency(f)}`));
|
|
6497
|
+
const conn = f.connection ? ` ${dim(`(${f.connection.event} ${ago(f.now - f.connection.at)}${f.connection.detail ? `, ${f.connection.detail}` : ""})`)}` : "";
|
|
6498
|
+
lines.push(kv("Hub", `${f.hubUrl}${conn}`));
|
|
6499
|
+
const installed = f.runtimes.filter((r) => r.installed);
|
|
6230
6500
|
lines.push(
|
|
6231
|
-
|
|
6501
|
+
kv(
|
|
6232
6502
|
"Runtimes",
|
|
6233
|
-
|
|
6503
|
+
installed.length > 0 ? installed.map((r) => r.version ? `${r.runtime} ${dim(r.version)}` : r.runtime).join(", ") : `${dim("none detected - this node will serve no Jobs (install claude / codex / pi)")}`
|
|
6234
6504
|
)
|
|
6235
6505
|
);
|
|
6236
|
-
if (
|
|
6237
|
-
lines.push(
|
|
6238
|
-
} else if (!f.service.installed) {
|
|
6239
|
-
lines.push(bullet("Node", "not installed - run `dahrk start` to run it always-on"));
|
|
6240
|
-
} else if (f.service.running) {
|
|
6241
|
-
const pid = f.service.pid ? ` (pid ${f.service.pid})` : "";
|
|
6242
|
-
lines.push(bullet("Node", `running${pid}`));
|
|
6243
|
-
} else if (f.state.desired === "stopped") {
|
|
6244
|
-
lines.push(bullet("Node", "stopped - run `dahrk start` to bring it back"));
|
|
6506
|
+
if (f.jobs.length === 0) {
|
|
6507
|
+
lines.push(kv("Work", dim("idle")));
|
|
6245
6508
|
} else {
|
|
6246
|
-
|
|
6247
|
-
|
|
6509
|
+
const [first, ...rest] = f.jobs;
|
|
6510
|
+
lines.push(kv("Work", jobLine(first, f.now)));
|
|
6511
|
+
for (const j of rest) lines.push(kv("", jobLine(j, f.now)));
|
|
6248
6512
|
}
|
|
6249
|
-
lines.push("",
|
|
6513
|
+
lines.push(...presenceHints(f).flatMap((h) => ["", h]));
|
|
6514
|
+
lines.push("", dim(` State file: ${f.stateFile}`));
|
|
6250
6515
|
return lines;
|
|
6251
6516
|
}
|
|
6517
|
+
function currency(f) {
|
|
6518
|
+
const u = f.update;
|
|
6519
|
+
if (!u) return "";
|
|
6520
|
+
if (u.kind === "unknown") {
|
|
6521
|
+
return ` ${dim("update status unknown - run `dahrk update --check`")}`;
|
|
6522
|
+
}
|
|
6523
|
+
const age = ago(f.now - u.checkedAt);
|
|
6524
|
+
if (isStale(u.checkedAt, f.now, f.updateIntervalMs)) {
|
|
6525
|
+
return ` ${dim(`as of ${age} - run \`dahrk update --check\` to refresh`)}`;
|
|
6526
|
+
}
|
|
6527
|
+
if (u.kind === "available") return ` ${dim(`(checked ${age})`)}`;
|
|
6528
|
+
return ` ${symbol("ok")} ${dim(`up to date (checked ${age})`)}`;
|
|
6529
|
+
}
|
|
6530
|
+
function jobLine(j, now) {
|
|
6531
|
+
const where = j.stageId ? `${j.runId} ${dim("/")} ${j.stageId}` : `${j.runId} ${dim(`(${j.kind})`)}`;
|
|
6532
|
+
return `${where} ${dim(humanDuration(now - j.startedAt))}`;
|
|
6533
|
+
}
|
|
6252
6534
|
function isUnhealthy(f) {
|
|
6253
|
-
return
|
|
6535
|
+
return f.presence.kind === "crashed";
|
|
6254
6536
|
}
|
|
6255
|
-
|
|
6537
|
+
function resolvePresence(service, lockedPid, desired) {
|
|
6538
|
+
if (service?.running) return { kind: "running", ...service.pid ? { pid: service.pid } : {} };
|
|
6539
|
+
if (lockedPid !== void 0) return { kind: "foreign", pid: lockedPid };
|
|
6540
|
+
if (!service) return { kind: "no-supervisor" };
|
|
6541
|
+
if (!service.installed) return { kind: "not-installed" };
|
|
6542
|
+
return desired === "stopped" ? { kind: "stopped" } : { kind: "crashed" };
|
|
6543
|
+
}
|
|
6544
|
+
async function gatherFacts(inputs, deps) {
|
|
6256
6545
|
const manager = detectManager(deps.platform);
|
|
6257
6546
|
let service;
|
|
6258
|
-
let logHint;
|
|
6259
6547
|
if (manager !== "unsupported") {
|
|
6260
6548
|
const unit = unitPath(manager, deps.homeDir);
|
|
6261
6549
|
const exists = deps.fileExists(unit);
|
|
6262
6550
|
const probe2 = exists ? deps.capture(statusCommand(manager)) : { code: 1, stdout: "" };
|
|
6263
6551
|
service = parseServiceStatus(manager, exists, probe2);
|
|
6264
|
-
logHint = "dahrk logs -f";
|
|
6265
6552
|
}
|
|
6266
6553
|
const state = readState(stateFile(deps.env));
|
|
6554
|
+
const lockedPid = deps.lockedPid();
|
|
6555
|
+
const presence = resolvePresence(service, lockedPid, state.desired);
|
|
6556
|
+
const livePid = presence.kind === "running" || presence.kind === "foreign" ? presence.pid : void 0;
|
|
6557
|
+
const jobs = livePid === void 0 ? [] : deps.jobs().filter((j) => j.nodePid === livePid);
|
|
6267
6558
|
const update = checkSuppressed(deps.env) ? void 0 : cachedUpdate(state, inputs.clientVersion, deps.binPath);
|
|
6268
|
-
const
|
|
6559
|
+
const connection = deps.connection();
|
|
6560
|
+
return {
|
|
6269
6561
|
clientVersion: inputs.clientVersion,
|
|
6270
6562
|
hubUrl: inputs.hubUrl,
|
|
6271
6563
|
stateFile: stateFile(deps.env),
|
|
6272
6564
|
state,
|
|
6273
6565
|
envToken: Boolean(deps.env.DAHRK_ENROL_TOKEN),
|
|
6274
|
-
runtimes: await deps.
|
|
6566
|
+
runtimes: await deps.probeRuntimes(),
|
|
6567
|
+
presence,
|
|
6568
|
+
jobs,
|
|
6569
|
+
updateIntervalMs: checkIntervalMs(deps.env),
|
|
6570
|
+
now: deps.now(),
|
|
6275
6571
|
...service ? { service } : {},
|
|
6276
|
-
...
|
|
6572
|
+
...connection ? { connection } : {},
|
|
6277
6573
|
...update ? { update } : {}
|
|
6278
6574
|
};
|
|
6575
|
+
}
|
|
6576
|
+
var CONNECTION_MARKERS = [
|
|
6577
|
+
{ prefix: "EDGE_WELCOMED", event: "welcomed", detail: false },
|
|
6578
|
+
{ prefix: "EDGE_CONNECTED", event: "connected", detail: false },
|
|
6579
|
+
{ prefix: "EDGE_DISCONNECTED", event: "disconnected", detail: true },
|
|
6580
|
+
{ prefix: "EDGE_STALE", event: "went stale", detail: false }
|
|
6581
|
+
];
|
|
6582
|
+
function lastConnection(records) {
|
|
6583
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
6584
|
+
const r = records[i];
|
|
6585
|
+
if (typeof r?.msg !== "string") continue;
|
|
6586
|
+
const marker = CONNECTION_MARKERS.find((m) => r.msg === m.prefix || r.msg.startsWith(`${m.prefix}:`));
|
|
6587
|
+
if (!marker) continue;
|
|
6588
|
+
const at = typeof r.time === "string" ? Date.parse(r.time) : typeof r.time === "number" ? r.time : NaN;
|
|
6589
|
+
if (Number.isNaN(at)) continue;
|
|
6590
|
+
const detail = marker.detail ? r.msg.slice(marker.prefix.length + 1).split(/\s+/)[0] ?? "" : "";
|
|
6591
|
+
return { event: marker.event, at, ...detail ? { detail } : {} };
|
|
6592
|
+
}
|
|
6593
|
+
return void 0;
|
|
6594
|
+
}
|
|
6595
|
+
async function runStatus(inputs, deps) {
|
|
6596
|
+
const facts = await gatherFacts(inputs, deps);
|
|
6597
|
+
if (inputs.json) {
|
|
6598
|
+
const { enrolToken: _omitted, ...state } = facts.state;
|
|
6599
|
+
deps.out(JSON.stringify({ ...facts, state, healthy: !isUnhealthy(facts) }, null, 2));
|
|
6600
|
+
return isUnhealthy(facts) ? 1 : 0;
|
|
6601
|
+
}
|
|
6279
6602
|
for (const line of renderStatus(facts)) deps.out(line);
|
|
6280
6603
|
return isUnhealthy(facts) ? 1 : 0;
|
|
6281
6604
|
}
|
|
6282
6605
|
|
|
6283
6606
|
// src/main.ts
|
|
6284
|
-
var CLIENT_VERSION = "0.1.
|
|
6607
|
+
var CLIENT_VERSION = "0.1.16";
|
|
6285
6608
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
6286
6609
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
6287
6610
|
var RUNTIMES2 = ["claude-code", "codex", "pi"];
|
|
@@ -6367,20 +6690,40 @@ async function start(flags) {
|
|
|
6367
6690
|
const env = envWithFlags(process.env, flags);
|
|
6368
6691
|
if (wantsForeground(env, flags)) return startForeground(env, flags);
|
|
6369
6692
|
await offerUpdate(env);
|
|
6370
|
-
const outcome = await runNodeStart(
|
|
6371
|
-
...resolveEnrolToken(env) ? { token: resolveEnrolToken(env) } : {},
|
|
6372
|
-
...env.DAHRK_NODE_NAME ? { name: env.DAHRK_NODE_NAME } : {},
|
|
6373
|
-
...env.DAHRK_HUB_URL ? { hubUrl: env.DAHRK_HUB_URL } : {}
|
|
6374
|
-
});
|
|
6693
|
+
const outcome = await runNodeStart(serviceInputs(env));
|
|
6375
6694
|
if (outcome.kind === "error") return outcome.code;
|
|
6376
6695
|
if (outcome.kind === "running") {
|
|
6377
6696
|
setDesired(env, "running");
|
|
6697
|
+
await printStatus(env);
|
|
6378
6698
|
return 0;
|
|
6379
6699
|
}
|
|
6380
|
-
|
|
6381
|
-
|
|
6700
|
+
out("");
|
|
6701
|
+
out(verdict("warn", `${outcome.reason}; running the node in this terminal instead.`));
|
|
6702
|
+
out("");
|
|
6703
|
+
out(hint("Use `dahrk start --foreground` (or DAHRK_FOREGROUND=1) to ask for this explicitly."));
|
|
6382
6704
|
return startForeground(env, flags);
|
|
6383
6705
|
}
|
|
6706
|
+
var serviceInputs = (env) => ({
|
|
6707
|
+
...resolveEnrolToken(env) ? { token: resolveEnrolToken(env) } : {},
|
|
6708
|
+
...env.DAHRK_NODE_NAME ? { name: env.DAHRK_NODE_NAME } : {},
|
|
6709
|
+
...env.DAHRK_HUB_URL ? { hubUrl: env.DAHRK_HUB_URL } : {}
|
|
6710
|
+
});
|
|
6711
|
+
async function restart(flags, force) {
|
|
6712
|
+
const env = envWithFlags(process.env, flags);
|
|
6713
|
+
out("");
|
|
6714
|
+
out(hint("Restarting the node..."));
|
|
6715
|
+
const outcome = await runNodeRestart({ ...serviceInputs(env), force });
|
|
6716
|
+
if (outcome.kind === "error") return outcome.code;
|
|
6717
|
+
if (outcome.kind === "foreground") {
|
|
6718
|
+
out(verdict("warn", `${outcome.reason}; there is no service to restart.`));
|
|
6719
|
+
out("");
|
|
6720
|
+
out(hint("A node running in a terminal is restarted by stopping it (Ctrl-C) and starting it again."));
|
|
6721
|
+
return 1;
|
|
6722
|
+
}
|
|
6723
|
+
setDesired(env, "running");
|
|
6724
|
+
await printStatus(env);
|
|
6725
|
+
return 0;
|
|
6726
|
+
}
|
|
6384
6727
|
async function startForeground(env, flags) {
|
|
6385
6728
|
if (!flags.ephemeral) {
|
|
6386
6729
|
const lock = acquireLock(defaultLockDeps(lockFile(env)));
|
|
@@ -6446,11 +6789,14 @@ async function startForeground(env, flags) {
|
|
|
6446
6789
|
});
|
|
6447
6790
|
return 0;
|
|
6448
6791
|
}
|
|
6449
|
-
async function stop(env) {
|
|
6450
|
-
const code = await runNodeStop();
|
|
6792
|
+
async function stop(env, force) {
|
|
6793
|
+
const code = await runNodeStop({ force });
|
|
6451
6794
|
if (code === 0 || code === STOP_FOREIGN_NODE) setDesired(env, "stopped");
|
|
6452
6795
|
return code;
|
|
6453
6796
|
}
|
|
6797
|
+
var updateStateDeps = (env) => ({
|
|
6798
|
+
saveResult: (patch) => writeState(env, patch)
|
|
6799
|
+
});
|
|
6454
6800
|
function updateCheckDeps(env) {
|
|
6455
6801
|
return {
|
|
6456
6802
|
env,
|
|
@@ -6461,25 +6807,15 @@ function updateCheckDeps(env) {
|
|
|
6461
6807
|
fetchLatest: fetchLatestVersion
|
|
6462
6808
|
};
|
|
6463
6809
|
}
|
|
6464
|
-
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
6465
|
-
async function confirm(question) {
|
|
6466
|
-
const { createInterface } = await import("readline/promises");
|
|
6467
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
6468
|
-
try {
|
|
6469
|
-
const answer = (await rl.question(`${question} [Y/n] `)).trim().toLowerCase();
|
|
6470
|
-
return answer === "" || answer === "y" || answer === "yes";
|
|
6471
|
-
} finally {
|
|
6472
|
-
rl.close();
|
|
6473
|
-
}
|
|
6474
|
-
}
|
|
6475
6810
|
async function offerUpdate(env) {
|
|
6476
6811
|
const available = await checkForUpdate(CLIENT_VERSION, updateCheckDeps(env));
|
|
6477
6812
|
if (!available) return;
|
|
6478
|
-
|
|
6813
|
+
out("");
|
|
6814
|
+
out(renderUpdateNotice(available));
|
|
6479
6815
|
if (!isInteractive()) return;
|
|
6480
|
-
if (!await confirm("
|
|
6481
|
-
const code = await runUpdate({ currentVersion: CLIENT_VERSION, check: false });
|
|
6482
|
-
if (code !== 0)
|
|
6816
|
+
if (!await confirm("Update now?")) return;
|
|
6817
|
+
const code = await runUpdate({ currentVersion: CLIENT_VERSION, check: false }, updateStateDeps(env));
|
|
6818
|
+
if (code !== 0) out(hint("Update failed; starting the node on the current version anyway."));
|
|
6483
6819
|
}
|
|
6484
6820
|
function scheduleUpdateChecks(env) {
|
|
6485
6821
|
if (checkSuppressed(env)) return;
|
|
@@ -6498,7 +6834,15 @@ function statusDeps(env) {
|
|
|
6498
6834
|
homeDir: homedir7(),
|
|
6499
6835
|
env,
|
|
6500
6836
|
binPath: process.argv[1],
|
|
6501
|
-
|
|
6837
|
+
// The rich probe, with versions - the same one `doctor` runs. An explicit DAHRK_RUNTIMES override still
|
|
6838
|
+
// wins, and is reported as installed-without-a-version, because a pinned runtime was never probed.
|
|
6839
|
+
probeRuntimes: async () => {
|
|
6840
|
+
const override = list(env.DAHRK_RUNTIMES).filter(isRuntime2);
|
|
6841
|
+
if (override.length > 0) {
|
|
6842
|
+
return RUNTIMES2.map((r) => ({ runtime: r, cmd: r, installed: override.includes(r) }));
|
|
6843
|
+
}
|
|
6844
|
+
return probeRuntimeStatuses();
|
|
6845
|
+
},
|
|
6502
6846
|
fileExists: (path) => existsSync14(path),
|
|
6503
6847
|
capture: (argv) => {
|
|
6504
6848
|
const [cmd, ...args] = argv;
|
|
@@ -6510,10 +6854,35 @@ function statusDeps(env) {
|
|
|
6510
6854
|
return { code: typeof status === "number" ? status : 1, stdout: "" };
|
|
6511
6855
|
}
|
|
6512
6856
|
},
|
|
6513
|
-
|
|
6514
|
-
|
|
6857
|
+
lockedPid: () => {
|
|
6858
|
+
const held = parseLock(readFileOrUndefined(lockFile(env)));
|
|
6859
|
+
return held !== void 0 && isAlive(held) ? held : void 0;
|
|
6860
|
+
},
|
|
6861
|
+
// Best-effort by construction: a missing or unreadable ledger reads as "no jobs in flight", which shows
|
|
6862
|
+
// an idle node rather than failing the whole report over a file that only exists while work is running.
|
|
6863
|
+
jobs: () => fileJobLedger(jobLedgerFile(stateDir(env)), () => {
|
|
6864
|
+
}).all(),
|
|
6865
|
+
connection: () => {
|
|
6866
|
+
const raw = readFileOrUndefined(jsonlLogFile(env));
|
|
6867
|
+
return raw ? lastConnection(parseRecords(raw)) : void 0;
|
|
6868
|
+
},
|
|
6869
|
+
now: () => Date.now(),
|
|
6870
|
+
out
|
|
6515
6871
|
};
|
|
6516
6872
|
}
|
|
6873
|
+
function readFileOrUndefined(path) {
|
|
6874
|
+
try {
|
|
6875
|
+
return readFileSync11(path, "utf8");
|
|
6876
|
+
} catch {
|
|
6877
|
+
return void 0;
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6880
|
+
async function printStatus(env) {
|
|
6881
|
+
return runStatus(
|
|
6882
|
+
{ clientVersion: CLIENT_VERSION, hubUrl: env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL },
|
|
6883
|
+
statusDeps(env)
|
|
6884
|
+
);
|
|
6885
|
+
}
|
|
6517
6886
|
var KNOWN_WORKFLOWS = ["preflight"];
|
|
6518
6887
|
async function runWorkflow(flags) {
|
|
6519
6888
|
if (flags.workflow !== "preflight") {
|
|
@@ -6523,7 +6892,7 @@ async function runWorkflow(flags) {
|
|
|
6523
6892
|
return 2;
|
|
6524
6893
|
}
|
|
6525
6894
|
const env = applyEnvAliases(process.env);
|
|
6526
|
-
const hubUrl = flags.hubUrl ?? env.DAHRK_HUB_URL;
|
|
6895
|
+
const hubUrl = flags.hubUrl ?? env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL;
|
|
6527
6896
|
const token = flags.token ?? resolveEnrolToken(env);
|
|
6528
6897
|
return runPreflight({
|
|
6529
6898
|
...flags.repo ? { repoPath: flags.repo } : {},
|
|
@@ -6552,7 +6921,10 @@ async function main() {
|
|
|
6552
6921
|
case "doctor": {
|
|
6553
6922
|
const env = envWithFlags(process.env, parsed.flags);
|
|
6554
6923
|
process.exitCode = await runDoctor({
|
|
6555
|
-
|
|
6924
|
+
// The same default the node itself dials. Without this, `dahrk doctor` on a stock install FAILED
|
|
6925
|
+
// with "no hub URL configured" while the node was connected to that very hub - it was reporting
|
|
6926
|
+
// that an env var was unset, not that anything was wrong.
|
|
6927
|
+
hubUrl: env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL,
|
|
6556
6928
|
// Same resolution as `start`, so doctor checks the token the node would actually present:
|
|
6557
6929
|
// the flag/env if given, else the one cached by the last successful enrolment.
|
|
6558
6930
|
token: resolveEnrolToken(env),
|
|
@@ -6563,7 +6935,11 @@ async function main() {
|
|
|
6563
6935
|
case "status": {
|
|
6564
6936
|
const env = envWithFlags(process.env, parsed.flags);
|
|
6565
6937
|
process.exitCode = await runStatus(
|
|
6566
|
-
{
|
|
6938
|
+
{
|
|
6939
|
+
clientVersion: CLIENT_VERSION,
|
|
6940
|
+
hubUrl: env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL,
|
|
6941
|
+
json: parsed.flags.json
|
|
6942
|
+
},
|
|
6567
6943
|
statusDeps(env)
|
|
6568
6944
|
);
|
|
6569
6945
|
break;
|
|
@@ -6588,24 +6964,24 @@ async function main() {
|
|
|
6588
6964
|
break;
|
|
6589
6965
|
}
|
|
6590
6966
|
case "update":
|
|
6591
|
-
process.exitCode = await runUpdate(
|
|
6967
|
+
process.exitCode = await runUpdate(
|
|
6968
|
+
{
|
|
6969
|
+
currentVersion: CLIENT_VERSION,
|
|
6970
|
+
check: parsed.flags.check,
|
|
6971
|
+
verbose: parsed.flags.verbose
|
|
6972
|
+
},
|
|
6973
|
+
updateStateDeps(applyEnvAliases(process.env))
|
|
6974
|
+
);
|
|
6592
6975
|
break;
|
|
6593
6976
|
case "start":
|
|
6594
6977
|
process.exitCode = await start(parsed.flags);
|
|
6595
6978
|
break;
|
|
6596
6979
|
case "stop":
|
|
6597
|
-
process.exitCode = await stop(applyEnvAliases(process.env));
|
|
6980
|
+
process.exitCode = await stop(applyEnvAliases(process.env), parsed.force);
|
|
6598
6981
|
break;
|
|
6599
|
-
case "restart":
|
|
6600
|
-
|
|
6601
|
-
const code = await stop(env);
|
|
6602
|
-
if (code !== 0) {
|
|
6603
|
-
process.exitCode = code;
|
|
6604
|
-
break;
|
|
6605
|
-
}
|
|
6606
|
-
process.exitCode = await start(parsed.flags);
|
|
6982
|
+
case "restart":
|
|
6983
|
+
process.exitCode = await restart(parsed.flags, parsed.force);
|
|
6607
6984
|
break;
|
|
6608
|
-
}
|
|
6609
6985
|
case "logs": {
|
|
6610
6986
|
const env = applyEnvAliases(process.env);
|
|
6611
6987
|
process.exitCode = await runLogs(parsed.flags, defaultLogsDeps(logFiles(env), jsonlLogFile(env)));
|
|
@@ -6620,7 +6996,10 @@ async function main() {
|
|
|
6620
6996
|
...resolveEnrolToken(env) ? { token: resolveEnrolToken(env) } : {},
|
|
6621
6997
|
hubUrl: env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL
|
|
6622
6998
|
},
|
|
6623
|
-
|
|
6999
|
+
// Stripped: stdout is a TTY here (the operator is watching), so the doctor correctly colours its
|
|
7000
|
+
// report - but this copy is going into a JSON file that someone will open in an editor and paste
|
|
7001
|
+
// into an issue, and escape codes have no business being in it.
|
|
7002
|
+
{ out: (line) => void doctorLines.push(stripAnsi(line)) }
|
|
6624
7003
|
);
|
|
6625
7004
|
return doctorLines;
|
|
6626
7005
|
};
|