skillwiki 0.9.29 → 0.9.30
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
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ExitCode,
|
|
4
|
+
FLEET_REL_PATH,
|
|
5
|
+
SATELLITE_STALE_MS,
|
|
4
6
|
TypedKnowledgeSchema,
|
|
5
7
|
appendLastOp,
|
|
6
8
|
assessSourceIdentity,
|
|
@@ -8,12 +10,15 @@ import {
|
|
|
8
10
|
configPath,
|
|
9
11
|
detectSchema,
|
|
10
12
|
err,
|
|
13
|
+
evaluateSatelliteRunHealth,
|
|
11
14
|
extractCitationMarkers,
|
|
12
15
|
extractFrontmatter,
|
|
13
16
|
extractTaxonomy,
|
|
14
17
|
findPlugin,
|
|
15
18
|
fixPathTooLong,
|
|
16
19
|
isBlockedHost,
|
|
20
|
+
isFailedRunStatus,
|
|
21
|
+
loadFleetManifest,
|
|
17
22
|
ok,
|
|
18
23
|
parseDotenvFile,
|
|
19
24
|
parseDotenvText,
|
|
@@ -21,6 +26,8 @@ import {
|
|
|
21
26
|
readCliPackageJson,
|
|
22
27
|
readLastOp,
|
|
23
28
|
readPage,
|
|
29
|
+
readSatelliteLatestRunFromText,
|
|
30
|
+
resolveFleetHostId,
|
|
24
31
|
resolveInitTimePath,
|
|
25
32
|
resolveRuntimePath,
|
|
26
33
|
runAudit,
|
|
@@ -32,6 +39,7 @@ import {
|
|
|
32
39
|
runDoctor,
|
|
33
40
|
runFleetContext,
|
|
34
41
|
runFleetValidate,
|
|
42
|
+
runFrontmatterFix,
|
|
35
43
|
runGraphBuild,
|
|
36
44
|
runIndexCheck,
|
|
37
45
|
runIndexLinkFormat,
|
|
@@ -54,19 +62,20 @@ import {
|
|
|
54
62
|
runTopicMapCheck,
|
|
55
63
|
runValidate,
|
|
56
64
|
safeWritePage,
|
|
65
|
+
satelliteLatestRunPath,
|
|
57
66
|
scanSensitiveContent,
|
|
58
67
|
scanVault,
|
|
59
68
|
splitFrontmatter,
|
|
60
69
|
triggerAutoUpdate,
|
|
61
70
|
writeCache,
|
|
62
71
|
writeDotenv
|
|
63
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-JENSKJP6.js";
|
|
64
73
|
import {
|
|
65
74
|
normalizeDistTag
|
|
66
75
|
} from "./chunk-E6UWZ3S3.js";
|
|
67
76
|
|
|
68
77
|
// src/cli.ts
|
|
69
|
-
import { join as
|
|
78
|
+
import { join as join25 } from "path";
|
|
70
79
|
import { Command } from "commander";
|
|
71
80
|
|
|
72
81
|
// src/utils/output.ts
|
|
@@ -1760,82 +1769,6 @@ ${migratedBody}${newFooter}`;
|
|
|
1760
1769
|
};
|
|
1761
1770
|
}
|
|
1762
1771
|
|
|
1763
|
-
// src/commands/frontmatter-fix.ts
|
|
1764
|
-
function isoToday() {
|
|
1765
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1766
|
-
}
|
|
1767
|
-
function fixFrontmatter(rawFm) {
|
|
1768
|
-
const additions = [];
|
|
1769
|
-
if (!/^created:/m.test(rawFm)) additions.push(`created: ${isoToday()}`);
|
|
1770
|
-
if (!/^updated:/m.test(rawFm)) additions.push(`updated: ${isoToday()}`);
|
|
1771
|
-
if (!/^tags:/m.test(rawFm)) additions.push("tags: []");
|
|
1772
|
-
if (!/^sources:/m.test(rawFm)) additions.push("sources: []");
|
|
1773
|
-
if (!/^provenance:/m.test(rawFm)) additions.push("provenance: research");
|
|
1774
|
-
if (additions.length === 0) return rawFm;
|
|
1775
|
-
return rawFm.trimEnd() + "\n" + additions.join("\n") + "\n";
|
|
1776
|
-
}
|
|
1777
|
-
function removeOrphanTagsLines(body) {
|
|
1778
|
-
return body.split("\n").filter((line) => !/^tags:\s*\[/.test(line.trim())).join("\n");
|
|
1779
|
-
}
|
|
1780
|
-
async function runFrontmatterFix(input) {
|
|
1781
|
-
const scan = await scanVault(input.vault);
|
|
1782
|
-
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
1783
|
-
const fixed = [];
|
|
1784
|
-
const skipped = [];
|
|
1785
|
-
let unchanged = 0;
|
|
1786
|
-
for (const page of scan.data.typedKnowledge) {
|
|
1787
|
-
const text = await readPage(page);
|
|
1788
|
-
const split = splitFrontmatter(text);
|
|
1789
|
-
if (!split.ok) {
|
|
1790
|
-
skipped.push(page.relPath);
|
|
1791
|
-
continue;
|
|
1792
|
-
}
|
|
1793
|
-
const { rawFrontmatter, body } = split.data;
|
|
1794
|
-
const newFm = fixFrontmatter(rawFrontmatter);
|
|
1795
|
-
const newBody = removeOrphanTagsLines(body);
|
|
1796
|
-
const newText = `---
|
|
1797
|
-
${newFm}
|
|
1798
|
-
---
|
|
1799
|
-
${newBody}`;
|
|
1800
|
-
if (newText === text) {
|
|
1801
|
-
unchanged++;
|
|
1802
|
-
continue;
|
|
1803
|
-
}
|
|
1804
|
-
if (!input.dryRun) {
|
|
1805
|
-
const w = await safeWritePage(page.absPath, newText);
|
|
1806
|
-
if (!w.ok) {
|
|
1807
|
-
skipped.push(page.relPath);
|
|
1808
|
-
continue;
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
fixed.push(page.relPath);
|
|
1812
|
-
}
|
|
1813
|
-
const exitCode = fixed.length > 0 ? ExitCode.MIGRATION_APPLIED : ExitCode.OK;
|
|
1814
|
-
const hintLines = [`scanned: ${fixed.length + skipped.length + unchanged}`];
|
|
1815
|
-
if (fixed.length > 0) hintLines.push(`fixed: ${fixed.length}`);
|
|
1816
|
-
if (skipped.length > 0) hintLines.push(`skipped (parse error): ${skipped.length}`);
|
|
1817
|
-
if (unchanged > 0) hintLines.push(`unchanged: ${unchanged}`);
|
|
1818
|
-
if (input.dryRun && fixed.length > 0) hintLines.push("(dry run \u2014 no files written)");
|
|
1819
|
-
if (!input.dryRun && fixed.length > 0) {
|
|
1820
|
-
appendLastOp(input.vault, {
|
|
1821
|
-
operation: "frontmatter-fix",
|
|
1822
|
-
summary: `normalized frontmatter on ${fixed.length} page(s)`,
|
|
1823
|
-
files: fixed,
|
|
1824
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
return {
|
|
1828
|
-
exitCode,
|
|
1829
|
-
result: ok({
|
|
1830
|
-
scanned: fixed.length + skipped.length + unchanged,
|
|
1831
|
-
fixed,
|
|
1832
|
-
skipped,
|
|
1833
|
-
unchanged,
|
|
1834
|
-
humanHint: hintLines.join("\n")
|
|
1835
|
-
})
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
1772
|
// src/commands/update.ts
|
|
1840
1773
|
import { execSync } from "child_process";
|
|
1841
1774
|
import { join as join11 } from "path";
|
|
@@ -2418,7 +2351,10 @@ async function runSessionBrief(input) {
|
|
|
2418
2351
|
const transcripts = await loadTranscriptInfo(scan.data.raw);
|
|
2419
2352
|
const workItems = await loadWorkItems(scan.data.workItems);
|
|
2420
2353
|
const digests = await loadTrendDigests(scan.data.typedKnowledge);
|
|
2421
|
-
const healthWarnings =
|
|
2354
|
+
const healthWarnings = [
|
|
2355
|
+
...await loadHealthWarnings(input.vault),
|
|
2356
|
+
...satelliteHealthWarnings(await loadSatelliteHealth(input.vault))
|
|
2357
|
+
];
|
|
2422
2358
|
const memoryTopics = project ? await loadMemoryTopics(input.vault, project) : [];
|
|
2423
2359
|
const latestLogs = newest(transcripts.filter((t) => t.kind === "session-log"), 3);
|
|
2424
2360
|
const unclaimedCaptures = newest(transcripts.filter((t) => {
|
|
@@ -2628,6 +2564,31 @@ function appendTextSection(lines, title, items, empty) {
|
|
|
2628
2564
|
}
|
|
2629
2565
|
lines.push("");
|
|
2630
2566
|
}
|
|
2567
|
+
async function loadSatelliteHealth(vaultPath) {
|
|
2568
|
+
const health = evaluateSatelliteRunHealth(vaultPath, /* @__PURE__ */ new Date());
|
|
2569
|
+
if (!health.failed && !health.stale) {
|
|
2570
|
+
const runPath2 = satelliteLatestRunPath(vaultPath);
|
|
2571
|
+
const hasRun = await readIfExists(runPath2);
|
|
2572
|
+
if (!hasRun) return null;
|
|
2573
|
+
}
|
|
2574
|
+
if (health.failed) {
|
|
2575
|
+
const fc = health.failureClass && health.failureClass.length > 0 ? health.failureClass : "unknown";
|
|
2576
|
+
return `agent-memory-trends: last run failed (${fc})`;
|
|
2577
|
+
}
|
|
2578
|
+
if (health.stale && health.finishedAt) {
|
|
2579
|
+
const finishedMs = Date.parse(health.finishedAt);
|
|
2580
|
+
if (!Number.isNaN(finishedMs)) {
|
|
2581
|
+
const ageHours = (Date.now() - finishedMs) / (60 * 60 * 1e3);
|
|
2582
|
+
const hoursRounded = Math.floor(ageHours);
|
|
2583
|
+
const datePart = health.finishedAt.slice(0, 10);
|
|
2584
|
+
return `agent-memory-trends: no run in ${hoursRounded}h (last: ${datePart})`;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
return null;
|
|
2588
|
+
}
|
|
2589
|
+
function satelliteHealthWarnings(warning) {
|
|
2590
|
+
return warning ? [warning] : [];
|
|
2591
|
+
}
|
|
2631
2592
|
async function loadHealthWarnings(vault) {
|
|
2632
2593
|
const text = await readIfExists(join15(vault, ".skillwiki", "health.json"));
|
|
2633
2594
|
if (!text) return [];
|
|
@@ -4356,16 +4317,333 @@ written: ${outPath}`
|
|
|
4356
4317
|
};
|
|
4357
4318
|
}
|
|
4358
4319
|
|
|
4359
|
-
// src/
|
|
4360
|
-
import { existsSync as existsSync10 } from "fs";
|
|
4320
|
+
// src/commands/fleet-health.ts
|
|
4321
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
4322
|
+
import { execSync as nodeExecSync } from "child_process";
|
|
4323
|
+
import { hostname as nodeHostname, platform as nodePlatform } from "os";
|
|
4361
4324
|
import { join as join23 } from "path";
|
|
4325
|
+
var SSH_TIMEOUT_MS = 15e3;
|
|
4326
|
+
var TIMER_UNIT = "agent-memory-trends.timer";
|
|
4327
|
+
var SERVICE_UNIT = "agent-memory-trends.service";
|
|
4328
|
+
var SYSTEMD_SERVICE_FAILED_CLASS = "SYSTEMD_SERVICE_FAILED";
|
|
4329
|
+
function defaultDeps() {
|
|
4330
|
+
return {
|
|
4331
|
+
platform: () => nodePlatform(),
|
|
4332
|
+
execSync: nodeExecSync
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
4335
|
+
function formatAgeHours(finishedAt) {
|
|
4336
|
+
if (!finishedAt) return "never";
|
|
4337
|
+
const ts = Date.parse(finishedAt);
|
|
4338
|
+
if (!Number.isFinite(ts)) return "unknown";
|
|
4339
|
+
const hours = Math.floor((Date.now() - ts) / (60 * 60 * 1e3));
|
|
4340
|
+
if (hours < 1) return "<1h";
|
|
4341
|
+
return `${hours}h`;
|
|
4342
|
+
}
|
|
4343
|
+
function deriveRunFields(parsed) {
|
|
4344
|
+
if (!parsed) {
|
|
4345
|
+
return {
|
|
4346
|
+
last_run_status: "unknown",
|
|
4347
|
+
last_run_age: "never",
|
|
4348
|
+
failure_class: "-",
|
|
4349
|
+
runUnhealthy: false
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
const finishedAt = parsed.finished_at;
|
|
4353
|
+
const age = formatAgeHours(finishedAt);
|
|
4354
|
+
const fc = parsed.failure_class != null && String(parsed.failure_class).length > 0 ? String(parsed.failure_class) : "-";
|
|
4355
|
+
if (isFailedRunStatus(parsed.status ?? "")) {
|
|
4356
|
+
return {
|
|
4357
|
+
last_run_status: "fail",
|
|
4358
|
+
last_run_age: age,
|
|
4359
|
+
failure_class: fc === "-" ? "fail" : fc,
|
|
4360
|
+
runUnhealthy: true
|
|
4361
|
+
};
|
|
4362
|
+
}
|
|
4363
|
+
if (finishedAt) {
|
|
4364
|
+
const ts = Date.parse(finishedAt);
|
|
4365
|
+
if (Number.isFinite(ts) && Date.now() - ts > SATELLITE_STALE_MS) {
|
|
4366
|
+
return {
|
|
4367
|
+
last_run_status: "success",
|
|
4368
|
+
last_run_age: age,
|
|
4369
|
+
failure_class: "-",
|
|
4370
|
+
runUnhealthy: true
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
if (parsed.status === "success" || parsed.status === "ok") {
|
|
4375
|
+
return {
|
|
4376
|
+
last_run_status: "success",
|
|
4377
|
+
last_run_age: age,
|
|
4378
|
+
failure_class: "-",
|
|
4379
|
+
runUnhealthy: false
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
return {
|
|
4383
|
+
last_run_status: parsed.status ? "unknown" : "none",
|
|
4384
|
+
last_run_age: age,
|
|
4385
|
+
failure_class: "-",
|
|
4386
|
+
runUnhealthy: false
|
|
4387
|
+
};
|
|
4388
|
+
}
|
|
4389
|
+
function systemctlPropLocal(deps, unit, prop) {
|
|
4390
|
+
if (deps.platform() !== "linux") return null;
|
|
4391
|
+
try {
|
|
4392
|
+
return deps.execSync(`systemctl ${prop} ${unit}`, {
|
|
4393
|
+
encoding: "utf8",
|
|
4394
|
+
timeout: 2e3,
|
|
4395
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4396
|
+
}).trim();
|
|
4397
|
+
} catch {
|
|
4398
|
+
return null;
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
function systemctlIsActiveLocal(deps, unit) {
|
|
4402
|
+
const out = systemctlPropLocal(deps, unit, "is-active");
|
|
4403
|
+
if (out === null) return "unknown";
|
|
4404
|
+
return out === "active" ? "active" : "inactive";
|
|
4405
|
+
}
|
|
4406
|
+
function systemctlIsFailedLocal(deps, unit) {
|
|
4407
|
+
return systemctlPropLocal(deps, unit, "is-failed") === "failed";
|
|
4408
|
+
}
|
|
4409
|
+
function applyServiceFailedOverlay(run, serviceFailed) {
|
|
4410
|
+
if (!serviceFailed) return run;
|
|
4411
|
+
return {
|
|
4412
|
+
...run,
|
|
4413
|
+
last_run_status: "fail",
|
|
4414
|
+
failure_class: run.failure_class === "-" ? SYSTEMD_SERVICE_FAILED_CLASS : run.failure_class,
|
|
4415
|
+
runUnhealthy: true
|
|
4416
|
+
};
|
|
4417
|
+
}
|
|
4418
|
+
function probeLocal(vaultPath, deps) {
|
|
4419
|
+
const latestPath = satelliteLatestRunPath(vaultPath);
|
|
4420
|
+
let parsed = null;
|
|
4421
|
+
if (existsSync10(latestPath)) {
|
|
4422
|
+
try {
|
|
4423
|
+
const wire = readSatelliteLatestRunFromText(readFileSync6(latestPath, "utf8"));
|
|
4424
|
+
if (wire) {
|
|
4425
|
+
parsed = {
|
|
4426
|
+
status: wire.status,
|
|
4427
|
+
finished_at: wire.finishedAt,
|
|
4428
|
+
failure_class: wire.failureClass ?? null
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4431
|
+
} catch {
|
|
4432
|
+
parsed = null;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
const timer = systemctlIsActiveLocal(deps, TIMER_UNIT);
|
|
4436
|
+
const serviceFailed = systemctlIsFailedLocal(deps, SERVICE_UNIT);
|
|
4437
|
+
const run = applyServiceFailedOverlay(deriveRunFields(parsed), serviceFailed);
|
|
4438
|
+
const timerBad = deps.platform() === "linux" && timer === "inactive";
|
|
4439
|
+
return {
|
|
4440
|
+
timer,
|
|
4441
|
+
last_run_status: run.last_run_status,
|
|
4442
|
+
last_run_age: run.last_run_age,
|
|
4443
|
+
failure_class: run.failure_class,
|
|
4444
|
+
reachable: "yes",
|
|
4445
|
+
runUnhealthy: run.runUnhealthy,
|
|
4446
|
+
timerBad
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
function probeRemote(sshAlias, vaultPath, deps) {
|
|
4450
|
+
const latest = satelliteLatestRunPath(vaultPath);
|
|
4451
|
+
const remoteCmd = [
|
|
4452
|
+
`cat ${shellQuote2(latest)} 2>/dev/null || true`,
|
|
4453
|
+
"echo __SW_TIMER__",
|
|
4454
|
+
`systemctl is-active ${TIMER_UNIT} 2>/dev/null || echo inactive`,
|
|
4455
|
+
"echo __SW_FAILED__",
|
|
4456
|
+
`systemctl is-failed ${SERVICE_UNIT} 2>/dev/null || echo unknown`
|
|
4457
|
+
].join("\n");
|
|
4458
|
+
const sshCmd = `ssh -o ConnectTimeout=10 ${sshAlias} ${shellQuote2(remoteCmd)}`;
|
|
4459
|
+
try {
|
|
4460
|
+
const out = deps.execSync(sshCmd, {
|
|
4461
|
+
encoding: "utf8",
|
|
4462
|
+
timeout: SSH_TIMEOUT_MS,
|
|
4463
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4464
|
+
});
|
|
4465
|
+
const timerMarker = "__SW_TIMER__";
|
|
4466
|
+
const failedMarker = "__SW_FAILED__";
|
|
4467
|
+
const timerIdx = out.indexOf(timerMarker);
|
|
4468
|
+
const jsonPart = timerIdx >= 0 ? out.slice(0, timerIdx) : out;
|
|
4469
|
+
const afterTimer = timerIdx >= 0 ? out.slice(timerIdx + timerMarker.length) : "";
|
|
4470
|
+
const failedIdx = afterTimer.indexOf(failedMarker);
|
|
4471
|
+
const timerRaw = failedIdx >= 0 ? afterTimer.slice(0, failedIdx) : afterTimer;
|
|
4472
|
+
const failedRaw = failedIdx >= 0 ? afterTimer.slice(failedIdx + failedMarker.length) : "";
|
|
4473
|
+
const jsonText = jsonPart.trim();
|
|
4474
|
+
const parsed = jsonText.startsWith("{") ? (() => {
|
|
4475
|
+
const wire = readSatelliteLatestRunFromText(jsonText);
|
|
4476
|
+
if (!wire) return null;
|
|
4477
|
+
return {
|
|
4478
|
+
status: wire.status,
|
|
4479
|
+
finished_at: wire.finishedAt,
|
|
4480
|
+
failure_class: wire.failureClass ?? null
|
|
4481
|
+
};
|
|
4482
|
+
})() : null;
|
|
4483
|
+
const timerLine = timerRaw.trim().split("\n")[0]?.trim() ?? "unknown";
|
|
4484
|
+
let timer = "unknown";
|
|
4485
|
+
if (timerLine === "active") timer = "active";
|
|
4486
|
+
else if (timerLine === "inactive") timer = "inactive";
|
|
4487
|
+
const failedLine = failedRaw.trim().split("\n")[0]?.trim() ?? "unknown";
|
|
4488
|
+
const serviceFailed = failedLine === "failed";
|
|
4489
|
+
const run = applyServiceFailedOverlay(deriveRunFields(parsed), serviceFailed);
|
|
4490
|
+
const timerBad = timer === "inactive";
|
|
4491
|
+
return {
|
|
4492
|
+
timer,
|
|
4493
|
+
last_run_status: run.last_run_status,
|
|
4494
|
+
last_run_age: run.last_run_age,
|
|
4495
|
+
failure_class: run.failure_class,
|
|
4496
|
+
reachable: "yes",
|
|
4497
|
+
runUnhealthy: run.runUnhealthy,
|
|
4498
|
+
timerBad
|
|
4499
|
+
};
|
|
4500
|
+
} catch {
|
|
4501
|
+
return {
|
|
4502
|
+
timer: "unknown",
|
|
4503
|
+
last_run_status: "unknown",
|
|
4504
|
+
last_run_age: "never",
|
|
4505
|
+
failure_class: "-",
|
|
4506
|
+
reachable: "no",
|
|
4507
|
+
runUnhealthy: true,
|
|
4508
|
+
timerBad: true
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
function shellQuote2(s) {
|
|
4513
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4514
|
+
}
|
|
4515
|
+
function satelliteHosts(manifest) {
|
|
4516
|
+
const out = [];
|
|
4517
|
+
for (const [id, host] of Object.entries(manifest.hosts)) {
|
|
4518
|
+
const sat = host.maintenance?.skillwiki_satellite;
|
|
4519
|
+
if (sat?.enabled === true) {
|
|
4520
|
+
out.push({ id, vaultPath: sat.vault_path, sshAlias: sat.ssh_alias, host });
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
4524
|
+
}
|
|
4525
|
+
function hasDeclaredSshAccess(host, localHostId, satelliteAlias) {
|
|
4526
|
+
const profile = host.access?.from?.[localHostId];
|
|
4527
|
+
if (!profile) return false;
|
|
4528
|
+
if (profile.status !== "configured" && profile.status !== "local") return false;
|
|
4529
|
+
const aliases = profile.ssh_aliases ?? [];
|
|
4530
|
+
return aliases.includes(satelliteAlias);
|
|
4531
|
+
}
|
|
4532
|
+
function formatTable(rows) {
|
|
4533
|
+
if (rows.length === 0) return "no satellite hosts configured";
|
|
4534
|
+
const header = "host | timer | last_run_status | last_run_age | failure_class | reachable";
|
|
4535
|
+
const lines = rows.map(
|
|
4536
|
+
(r) => `${r.host} | ${r.timer} | ${r.last_run_status} | ${r.last_run_age} | ${r.failure_class} | ${r.reachable}`
|
|
4537
|
+
);
|
|
4538
|
+
return [header, ...lines].join("\n");
|
|
4539
|
+
}
|
|
4540
|
+
function rowHealthy(reachable, timer, runUnhealthy, timerBad, platform2) {
|
|
4541
|
+
if (reachable === "no-access") return true;
|
|
4542
|
+
if (reachable === "no") return false;
|
|
4543
|
+
if (runUnhealthy) return false;
|
|
4544
|
+
if (platform2 === "linux" && timerBad) return false;
|
|
4545
|
+
if (platform2 === "linux" && timer === "inactive") return false;
|
|
4546
|
+
return true;
|
|
4547
|
+
}
|
|
4548
|
+
async function runFleetHealth(input) {
|
|
4549
|
+
const deps = input.deps ?? defaultDeps();
|
|
4550
|
+
const env = input.env ?? process.env;
|
|
4551
|
+
const home = input.home ?? env.HOME ?? "";
|
|
4552
|
+
const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
|
|
4553
|
+
const vault = input.vault ?? env.WIKI_PATH;
|
|
4554
|
+
const file = input.file ?? (vault ? join23(vault, FLEET_REL_PATH) : void 0);
|
|
4555
|
+
if (!file) {
|
|
4556
|
+
return {
|
|
4557
|
+
exitCode: ExitCode.NO_VAULT_CONFIGURED,
|
|
4558
|
+
result: err("NO_VAULT_CONFIGURED", { message: "vault path or --file required" })
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
const loaded = await loadFleetManifest(file);
|
|
4562
|
+
if (!loaded.ok) {
|
|
4563
|
+
if (loaded.error === "FILE_NOT_FOUND") {
|
|
4564
|
+
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: file }) };
|
|
4565
|
+
}
|
|
4566
|
+
return {
|
|
4567
|
+
exitCode: ExitCode.FLEET_MANIFEST_INVALID,
|
|
4568
|
+
result: err("INVALID_FLEET_MANIFEST", { path: file })
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4571
|
+
const resolved = await resolveFleetHostId({
|
|
4572
|
+
manifest: loaded.manifest,
|
|
4573
|
+
hostId: input.hostId,
|
|
4574
|
+
env,
|
|
4575
|
+
home,
|
|
4576
|
+
osHostname
|
|
4577
|
+
});
|
|
4578
|
+
const localHostId = resolved.hostId;
|
|
4579
|
+
const targets = satelliteHosts(loaded.manifest);
|
|
4580
|
+
if (targets.length === 0) {
|
|
4581
|
+
const humanHint2 = "no satellite hosts configured";
|
|
4582
|
+
return {
|
|
4583
|
+
exitCode: ExitCode.OK,
|
|
4584
|
+
result: ok({ hosts: [], humanHint: humanHint2 })
|
|
4585
|
+
};
|
|
4586
|
+
}
|
|
4587
|
+
const rows = [];
|
|
4588
|
+
for (const t of targets) {
|
|
4589
|
+
const isLocal = localHostId === t.id;
|
|
4590
|
+
if (isLocal) {
|
|
4591
|
+
const p = probeLocal(t.vaultPath, deps);
|
|
4592
|
+
const healthy = rowHealthy(p.reachable, p.timer, p.runUnhealthy, p.timerBad, deps.platform());
|
|
4593
|
+
rows.push({
|
|
4594
|
+
host: t.id,
|
|
4595
|
+
timer: p.timer,
|
|
4596
|
+
last_run_status: p.last_run_status,
|
|
4597
|
+
last_run_age: p.last_run_age,
|
|
4598
|
+
failure_class: p.failure_class,
|
|
4599
|
+
reachable: p.reachable,
|
|
4600
|
+
healthy
|
|
4601
|
+
});
|
|
4602
|
+
} else {
|
|
4603
|
+
if (!hasDeclaredSshAccess(t.host, localHostId, t.sshAlias)) {
|
|
4604
|
+
rows.push({
|
|
4605
|
+
host: t.id,
|
|
4606
|
+
timer: "unknown",
|
|
4607
|
+
last_run_status: "unknown",
|
|
4608
|
+
last_run_age: "never",
|
|
4609
|
+
failure_class: "-",
|
|
4610
|
+
reachable: "no-access",
|
|
4611
|
+
healthy: true
|
|
4612
|
+
});
|
|
4613
|
+
continue;
|
|
4614
|
+
}
|
|
4615
|
+
const p = probeRemote(t.sshAlias, t.vaultPath, deps);
|
|
4616
|
+
const healthy = rowHealthy(p.reachable, p.timer, p.runUnhealthy, p.timerBad, "linux");
|
|
4617
|
+
rows.push({
|
|
4618
|
+
host: t.id,
|
|
4619
|
+
timer: p.timer,
|
|
4620
|
+
last_run_status: p.last_run_status,
|
|
4621
|
+
last_run_age: p.last_run_age,
|
|
4622
|
+
failure_class: p.failure_class,
|
|
4623
|
+
reachable: p.reachable,
|
|
4624
|
+
healthy
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
const allHealthy = rows.every((r) => r.healthy);
|
|
4629
|
+
const humanHint = formatTable(rows);
|
|
4630
|
+
const exitCode = allHealthy ? ExitCode.OK : ExitCode.FLEET_SATELLITE_HEALTH_FAILED;
|
|
4631
|
+
return {
|
|
4632
|
+
exitCode,
|
|
4633
|
+
result: ok({ hosts: rows, humanHint })
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
// src/utils/auto-commit.ts
|
|
4638
|
+
import { existsSync as existsSync11 } from "fs";
|
|
4639
|
+
import { join as join24 } from "path";
|
|
4362
4640
|
async function postCommit(vault, exitCode) {
|
|
4363
4641
|
if (exitCode !== 0) return;
|
|
4364
4642
|
const home = process.env.HOME ?? "";
|
|
4365
4643
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
4366
4644
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
4367
4645
|
if (autoCommit === "false") return;
|
|
4368
|
-
if (!
|
|
4646
|
+
if (!existsSync11(join24(vault, ".git"))) return;
|
|
4369
4647
|
const lastOps = readLastOp(vault);
|
|
4370
4648
|
if (lastOps.length === 0) return;
|
|
4371
4649
|
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
@@ -4411,7 +4689,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
4411
4689
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
4412
4690
|
});
|
|
4413
4691
|
program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
4414
|
-
const out = opts.out ??
|
|
4692
|
+
const out = opts.out ?? join25(vault, ".skillwiki", "graph.json");
|
|
4415
4693
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
4416
4694
|
});
|
|
4417
4695
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -4860,6 +5138,20 @@ fleetCmd.command("context [vault]").description("render compact Runtime Host Con
|
|
|
4860
5138
|
user: process.env.USER
|
|
4861
5139
|
}));
|
|
4862
5140
|
});
|
|
5141
|
+
fleetCmd.command("health [vault]").description("read-only health probe for skillwiki satellite hosts").option("--file <path>", "fleet manifest path").option("--host-id <id>", "explicit current fleet host id").option("--json", "emit JSON result").action(async (vault, opts) => {
|
|
5142
|
+
const r = await runFleetHealth({
|
|
5143
|
+
vault,
|
|
5144
|
+
file: opts.file,
|
|
5145
|
+
hostId: opts.hostId,
|
|
5146
|
+
json: !!opts.json,
|
|
5147
|
+
env: process.env,
|
|
5148
|
+
home: process.env.HOME ?? "",
|
|
5149
|
+
cwd: process.cwd(),
|
|
5150
|
+
osHostname: process.env.HOSTNAME,
|
|
5151
|
+
user: process.env.USER
|
|
5152
|
+
});
|
|
5153
|
+
emit(r, vault);
|
|
5154
|
+
});
|
|
4863
5155
|
program.command("mcp").description("start stdio Model Context Protocol server (read-only vault tools)").action(async () => {
|
|
4864
5156
|
await runSkillwikiMcpStdio();
|
|
4865
5157
|
});
|
package/dist/skillwiki-mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.30",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 18 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|