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-T7XG3WFK.js";
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 join24 } from "path";
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 = await loadHealthWarnings(input.vault);
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/utils/auto-commit.ts
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 (!existsSync10(join23(vault, ".git"))) return;
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 ?? join24(vault, ".skillwiki", "graph.json");
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
  });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-T7XG3WFK.js";
4
+ } from "./chunk-JENSKJP6.js";
5
5
  import "./chunk-E6UWZ3S3.js";
6
6
 
7
7
  // src/mcp-entry.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.9.29",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 18 prompt-only skills backed by the deterministic skillwiki CLI.",
5
5
  "author": {
6
6
  "name": "karlorz",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",