skillwiki 0.9.28 → 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/{chunk-IP3RKVMF.js → chunk-JENSKJP6.js} +484 -149
- package/dist/cli.js +416 -94
- package/dist/skillwiki-mcp.js +1 -1
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -2
- package/skills/package.json +1 -1
- package/skills/.mcp.json +0 -7
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,11 +10,15 @@ import {
|
|
|
8
10
|
configPath,
|
|
9
11
|
detectSchema,
|
|
10
12
|
err,
|
|
13
|
+
evaluateSatelliteRunHealth,
|
|
11
14
|
extractCitationMarkers,
|
|
12
15
|
extractFrontmatter,
|
|
13
16
|
extractTaxonomy,
|
|
17
|
+
findPlugin,
|
|
14
18
|
fixPathTooLong,
|
|
15
19
|
isBlockedHost,
|
|
20
|
+
isFailedRunStatus,
|
|
21
|
+
loadFleetManifest,
|
|
16
22
|
ok,
|
|
17
23
|
parseDotenvFile,
|
|
18
24
|
parseDotenvText,
|
|
@@ -20,6 +26,8 @@ import {
|
|
|
20
26
|
readCliPackageJson,
|
|
21
27
|
readLastOp,
|
|
22
28
|
readPage,
|
|
29
|
+
readSatelliteLatestRunFromText,
|
|
30
|
+
resolveFleetHostId,
|
|
23
31
|
resolveInitTimePath,
|
|
24
32
|
resolveRuntimePath,
|
|
25
33
|
runAudit,
|
|
@@ -31,6 +39,7 @@ import {
|
|
|
31
39
|
runDoctor,
|
|
32
40
|
runFleetContext,
|
|
33
41
|
runFleetValidate,
|
|
42
|
+
runFrontmatterFix,
|
|
34
43
|
runGraphBuild,
|
|
35
44
|
runIndexCheck,
|
|
36
45
|
runIndexLinkFormat,
|
|
@@ -53,19 +62,20 @@ import {
|
|
|
53
62
|
runTopicMapCheck,
|
|
54
63
|
runValidate,
|
|
55
64
|
safeWritePage,
|
|
65
|
+
satelliteLatestRunPath,
|
|
56
66
|
scanSensitiveContent,
|
|
57
67
|
scanVault,
|
|
58
68
|
splitFrontmatter,
|
|
59
69
|
triggerAutoUpdate,
|
|
60
70
|
writeCache,
|
|
61
71
|
writeDotenv
|
|
62
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-JENSKJP6.js";
|
|
63
73
|
import {
|
|
64
74
|
normalizeDistTag
|
|
65
75
|
} from "./chunk-E6UWZ3S3.js";
|
|
66
76
|
|
|
67
77
|
// src/cli.ts
|
|
68
|
-
import { join as
|
|
78
|
+
import { join as join25 } from "path";
|
|
69
79
|
import { Command } from "commander";
|
|
70
80
|
|
|
71
81
|
// src/utils/output.ts
|
|
@@ -297,6 +307,28 @@ async function runInstall(input) {
|
|
|
297
307
|
if (entries.length === 0) {
|
|
298
308
|
return { exitCode: ExitCode.PREFLIGHT_FAILED, result: err("PREFLIGHT_FAILED", { reason: "no skills found" }) };
|
|
299
309
|
}
|
|
310
|
+
const defaultTarget = join2(input.home, ".claude", "skills");
|
|
311
|
+
const isDefaultTarget = resolve(input.target) === resolve(defaultTarget);
|
|
312
|
+
const plugin = input.force || !isDefaultTarget ? null : findPlugin(input.home);
|
|
313
|
+
if (plugin) {
|
|
314
|
+
const manifest_path2 = join2(input.target, "wiki-manifest.json");
|
|
315
|
+
const hintLines2 = [
|
|
316
|
+
`deferred to plugin: skillwiki@llm-wiki v${plugin.version}`,
|
|
317
|
+
`plugin provides skills at ${plugin.installPath}`,
|
|
318
|
+
`use --force to install CLI copies into ${input.target} anyway`
|
|
319
|
+
];
|
|
320
|
+
return {
|
|
321
|
+
exitCode: ExitCode.OK,
|
|
322
|
+
result: ok({
|
|
323
|
+
installed: [],
|
|
324
|
+
backed_up: [],
|
|
325
|
+
manifest_path: manifest_path2,
|
|
326
|
+
version_warnings: [],
|
|
327
|
+
deferred_to_plugin: true,
|
|
328
|
+
humanHint: hintLines2.join("\n")
|
|
329
|
+
})
|
|
330
|
+
};
|
|
331
|
+
}
|
|
300
332
|
const installed = [];
|
|
301
333
|
const backed_up = [];
|
|
302
334
|
const version_warnings = [];
|
|
@@ -377,7 +409,7 @@ async function runInstall(input) {
|
|
|
377
409
|
for (const w of version_warnings) hintLines.push(` ${w}`);
|
|
378
410
|
}
|
|
379
411
|
const exitCode = version_warnings.length > 0 ? ExitCode.SKILL_VERSION_MISMATCH : ExitCode.OK;
|
|
380
|
-
return { exitCode, result: ok({ installed, backed_up, manifest_path, version_warnings, humanHint: hintLines.join("\n") }) };
|
|
412
|
+
return { exitCode, result: ok({ installed, backed_up, manifest_path, version_warnings, deferred_to_plugin: false, humanHint: hintLines.join("\n") }) };
|
|
381
413
|
}
|
|
382
414
|
|
|
383
415
|
// src/commands/path.ts
|
|
@@ -1737,82 +1769,6 @@ ${migratedBody}${newFooter}`;
|
|
|
1737
1769
|
};
|
|
1738
1770
|
}
|
|
1739
1771
|
|
|
1740
|
-
// src/commands/frontmatter-fix.ts
|
|
1741
|
-
function isoToday() {
|
|
1742
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1743
|
-
}
|
|
1744
|
-
function fixFrontmatter(rawFm) {
|
|
1745
|
-
const additions = [];
|
|
1746
|
-
if (!/^created:/m.test(rawFm)) additions.push(`created: ${isoToday()}`);
|
|
1747
|
-
if (!/^updated:/m.test(rawFm)) additions.push(`updated: ${isoToday()}`);
|
|
1748
|
-
if (!/^tags:/m.test(rawFm)) additions.push("tags: []");
|
|
1749
|
-
if (!/^sources:/m.test(rawFm)) additions.push("sources: []");
|
|
1750
|
-
if (!/^provenance:/m.test(rawFm)) additions.push("provenance: research");
|
|
1751
|
-
if (additions.length === 0) return rawFm;
|
|
1752
|
-
return rawFm.trimEnd() + "\n" + additions.join("\n") + "\n";
|
|
1753
|
-
}
|
|
1754
|
-
function removeOrphanTagsLines(body) {
|
|
1755
|
-
return body.split("\n").filter((line) => !/^tags:\s*\[/.test(line.trim())).join("\n");
|
|
1756
|
-
}
|
|
1757
|
-
async function runFrontmatterFix(input) {
|
|
1758
|
-
const scan = await scanVault(input.vault);
|
|
1759
|
-
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
1760
|
-
const fixed = [];
|
|
1761
|
-
const skipped = [];
|
|
1762
|
-
let unchanged = 0;
|
|
1763
|
-
for (const page of scan.data.typedKnowledge) {
|
|
1764
|
-
const text = await readPage(page);
|
|
1765
|
-
const split = splitFrontmatter(text);
|
|
1766
|
-
if (!split.ok) {
|
|
1767
|
-
skipped.push(page.relPath);
|
|
1768
|
-
continue;
|
|
1769
|
-
}
|
|
1770
|
-
const { rawFrontmatter, body } = split.data;
|
|
1771
|
-
const newFm = fixFrontmatter(rawFrontmatter);
|
|
1772
|
-
const newBody = removeOrphanTagsLines(body);
|
|
1773
|
-
const newText = `---
|
|
1774
|
-
${newFm}
|
|
1775
|
-
---
|
|
1776
|
-
${newBody}`;
|
|
1777
|
-
if (newText === text) {
|
|
1778
|
-
unchanged++;
|
|
1779
|
-
continue;
|
|
1780
|
-
}
|
|
1781
|
-
if (!input.dryRun) {
|
|
1782
|
-
const w = await safeWritePage(page.absPath, newText);
|
|
1783
|
-
if (!w.ok) {
|
|
1784
|
-
skipped.push(page.relPath);
|
|
1785
|
-
continue;
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
fixed.push(page.relPath);
|
|
1789
|
-
}
|
|
1790
|
-
const exitCode = fixed.length > 0 ? ExitCode.MIGRATION_APPLIED : ExitCode.OK;
|
|
1791
|
-
const hintLines = [`scanned: ${fixed.length + skipped.length + unchanged}`];
|
|
1792
|
-
if (fixed.length > 0) hintLines.push(`fixed: ${fixed.length}`);
|
|
1793
|
-
if (skipped.length > 0) hintLines.push(`skipped (parse error): ${skipped.length}`);
|
|
1794
|
-
if (unchanged > 0) hintLines.push(`unchanged: ${unchanged}`);
|
|
1795
|
-
if (input.dryRun && fixed.length > 0) hintLines.push("(dry run \u2014 no files written)");
|
|
1796
|
-
if (!input.dryRun && fixed.length > 0) {
|
|
1797
|
-
appendLastOp(input.vault, {
|
|
1798
|
-
operation: "frontmatter-fix",
|
|
1799
|
-
summary: `normalized frontmatter on ${fixed.length} page(s)`,
|
|
1800
|
-
files: fixed,
|
|
1801
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
return {
|
|
1805
|
-
exitCode,
|
|
1806
|
-
result: ok({
|
|
1807
|
-
scanned: fixed.length + skipped.length + unchanged,
|
|
1808
|
-
fixed,
|
|
1809
|
-
skipped,
|
|
1810
|
-
unchanged,
|
|
1811
|
-
humanHint: hintLines.join("\n")
|
|
1812
|
-
})
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
1772
|
// src/commands/update.ts
|
|
1817
1773
|
import { execSync } from "child_process";
|
|
1818
1774
|
import { join as join11 } from "path";
|
|
@@ -1827,19 +1783,23 @@ function resolveGlobalSkillsRoot() {
|
|
|
1827
1783
|
return null;
|
|
1828
1784
|
}
|
|
1829
1785
|
}
|
|
1830
|
-
async function refreshInstalledSkills(target) {
|
|
1786
|
+
async function refreshInstalledSkills(home, target) {
|
|
1831
1787
|
const skillsRoot = resolveGlobalSkillsRoot();
|
|
1832
1788
|
if (!skillsRoot) {
|
|
1833
|
-
return { warnings: ["could not locate global skillwiki installation for skill refresh"], refreshed: false };
|
|
1789
|
+
return { warnings: ["could not locate global skillwiki installation for skill refresh"], refreshed: false, deferred_to_plugin: false };
|
|
1834
1790
|
}
|
|
1835
1791
|
try {
|
|
1836
|
-
const result = await runInstall({ skillsRoot, target, dryRun: false, symlink: false });
|
|
1792
|
+
const result = await runInstall({ skillsRoot, target, dryRun: false, symlink: false, home, force: false });
|
|
1837
1793
|
if (result.result.ok) {
|
|
1838
|
-
return {
|
|
1794
|
+
return {
|
|
1795
|
+
warnings: result.result.data.version_warnings,
|
|
1796
|
+
refreshed: !result.result.data.deferred_to_plugin,
|
|
1797
|
+
deferred_to_plugin: result.result.data.deferred_to_plugin
|
|
1798
|
+
};
|
|
1839
1799
|
}
|
|
1840
|
-
return { warnings: [`skill refresh failed: ${result.result.error}`], refreshed: false };
|
|
1800
|
+
return { warnings: [`skill refresh failed: ${result.result.error}`], refreshed: false, deferred_to_plugin: false };
|
|
1841
1801
|
} catch (e) {
|
|
1842
|
-
return { warnings: [`skill refresh error: ${String(e)}`], refreshed: false };
|
|
1802
|
+
return { warnings: [`skill refresh error: ${String(e)}`], refreshed: false, deferred_to_plugin: false };
|
|
1843
1803
|
}
|
|
1844
1804
|
}
|
|
1845
1805
|
async function runUpdate(input) {
|
|
@@ -1875,6 +1835,7 @@ async function runUpdate(input) {
|
|
|
1875
1835
|
wasAlreadyLatest: true,
|
|
1876
1836
|
version_warnings: [],
|
|
1877
1837
|
skills_refreshed: false,
|
|
1838
|
+
deferred_to_plugin: false,
|
|
1878
1839
|
humanHint: `Already on npm@${tag}: v${currentVersion}`
|
|
1879
1840
|
})
|
|
1880
1841
|
};
|
|
@@ -1891,12 +1852,13 @@ async function runUpdate(input) {
|
|
|
1891
1852
|
};
|
|
1892
1853
|
}
|
|
1893
1854
|
writeCache(input.home, { ...cache, updateAppliedAt: Date.now() });
|
|
1894
|
-
const installResult = await refreshInstalledSkills(target);
|
|
1855
|
+
const installResult = await refreshInstalledSkills(input.home, target);
|
|
1895
1856
|
const version_warnings = installResult.warnings;
|
|
1896
1857
|
const skills_refreshed = installResult.refreshed;
|
|
1858
|
+
const deferred_to_plugin = installResult.deferred_to_plugin;
|
|
1897
1859
|
const hintLines = [
|
|
1898
1860
|
`Updated skillwiki ${currentVersion} \u2192 ${latest} via npm@${tag}`,
|
|
1899
|
-
`skills refreshed: ${skills_refreshed}`
|
|
1861
|
+
deferred_to_plugin ? `skills deferred to plugin channel (skillwiki@llm-wiki)` : `skills refreshed: ${skills_refreshed}`
|
|
1900
1862
|
];
|
|
1901
1863
|
if (version_warnings.length > 0) {
|
|
1902
1864
|
hintLines.push(`version warnings: ${version_warnings.length}`);
|
|
@@ -1910,6 +1872,7 @@ async function runUpdate(input) {
|
|
|
1910
1872
|
wasAlreadyLatest: false,
|
|
1911
1873
|
version_warnings,
|
|
1912
1874
|
skills_refreshed,
|
|
1875
|
+
deferred_to_plugin,
|
|
1913
1876
|
humanHint: hintLines.join("\n")
|
|
1914
1877
|
})
|
|
1915
1878
|
};
|
|
@@ -2388,7 +2351,10 @@ async function runSessionBrief(input) {
|
|
|
2388
2351
|
const transcripts = await loadTranscriptInfo(scan.data.raw);
|
|
2389
2352
|
const workItems = await loadWorkItems(scan.data.workItems);
|
|
2390
2353
|
const digests = await loadTrendDigests(scan.data.typedKnowledge);
|
|
2391
|
-
const healthWarnings =
|
|
2354
|
+
const healthWarnings = [
|
|
2355
|
+
...await loadHealthWarnings(input.vault),
|
|
2356
|
+
...satelliteHealthWarnings(await loadSatelliteHealth(input.vault))
|
|
2357
|
+
];
|
|
2392
2358
|
const memoryTopics = project ? await loadMemoryTopics(input.vault, project) : [];
|
|
2393
2359
|
const latestLogs = newest(transcripts.filter((t) => t.kind === "session-log"), 3);
|
|
2394
2360
|
const unclaimedCaptures = newest(transcripts.filter((t) => {
|
|
@@ -2598,6 +2564,31 @@ function appendTextSection(lines, title, items, empty) {
|
|
|
2598
2564
|
}
|
|
2599
2565
|
lines.push("");
|
|
2600
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
|
+
}
|
|
2601
2592
|
async function loadHealthWarnings(vault) {
|
|
2602
2593
|
const text = await readIfExists(join15(vault, ".skillwiki", "health.json"));
|
|
2603
2594
|
if (!text) return [];
|
|
@@ -4326,16 +4317,333 @@ written: ${outPath}`
|
|
|
4326
4317
|
};
|
|
4327
4318
|
}
|
|
4328
4319
|
|
|
4329
|
-
// src/
|
|
4330
|
-
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";
|
|
4331
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";
|
|
4332
4640
|
async function postCommit(vault, exitCode) {
|
|
4333
4641
|
if (exitCode !== 0) return;
|
|
4334
4642
|
const home = process.env.HOME ?? "";
|
|
4335
4643
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
4336
4644
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
4337
4645
|
if (autoCommit === "false") return;
|
|
4338
|
-
if (!
|
|
4646
|
+
if (!existsSync11(join24(vault, ".git"))) return;
|
|
4339
4647
|
const lastOps = readLastOp(vault);
|
|
4340
4648
|
if (lastOps.length === 0) return;
|
|
4341
4649
|
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
@@ -4381,7 +4689,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
4381
4689
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
4382
4690
|
});
|
|
4383
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) => {
|
|
4384
|
-
const out = opts.out ??
|
|
4692
|
+
const out = opts.out ?? join25(vault, ".skillwiki", "graph.json");
|
|
4385
4693
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
4386
4694
|
});
|
|
4387
4695
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -4407,9 +4715,9 @@ program.command("orphans [vault]").description("find pages not referenced by any
|
|
|
4407
4715
|
wiki: opts.wiki
|
|
4408
4716
|
})));
|
|
4409
4717
|
program.command("audit <file>").description("audit citation markers and source provenance for a vault page").action(async (file) => emit(await runAudit({ file })));
|
|
4410
|
-
program.command("install").description("install skillwiki SKILL.md files into ~/.claude/skills/").option("--target <dir>", "target install directory", `${process.env.HOME ?? ""}/.claude/skills/`).option("--dry-run", "preview only", false).option("--skills-root <dir>", "source skills directory (defaults to packaged)").option("--symlink", "create symlinks instead of copies (dev mode \u2014 edits to source are immediately visible)", false).action(async (opts) => {
|
|
4718
|
+
program.command("install").description("install skillwiki SKILL.md files into ~/.claude/skills/").option("--target <dir>", "target install directory", `${process.env.HOME ?? ""}/.claude/skills/`).option("--dry-run", "preview only", false).option("--skills-root <dir>", "source skills directory (defaults to packaged)").option("--symlink", "create symlinks instead of copies (dev mode \u2014 edits to source are immediately visible)", false).option("--force", "install CLI copies even when the skillwiki@llm-wiki plugin channel is active", false).action(async (opts) => {
|
|
4411
4719
|
const skillsRoot = opts.skillsRoot ?? new URL("../skills/", import.meta.url).pathname;
|
|
4412
|
-
emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun, symlink: !!opts.symlink }));
|
|
4720
|
+
emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun, symlink: !!opts.symlink, home: process.env.HOME ?? "", force: !!opts.force }));
|
|
4413
4721
|
});
|
|
4414
4722
|
program.command("path").description("show the resolved vault path").option("--vault <dir>", "explicit vault override (runtime)").option("--target <dir>", "explicit target override (init-time)").option("--wiki <name>", "wiki profile name").option("--init-time", "use init-time chain instead of runtime", false).option("--explain", "include resolution chain in output", false).action(async (opts) => {
|
|
4415
4723
|
const initTime = !!opts.initTime;
|
|
@@ -4830,6 +5138,20 @@ fleetCmd.command("context [vault]").description("render compact Runtime Host Con
|
|
|
4830
5138
|
user: process.env.USER
|
|
4831
5139
|
}));
|
|
4832
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
|
+
});
|
|
4833
5155
|
program.command("mcp").description("start stdio Model Context Protocol server (read-only vault tools)").action(async () => {
|
|
4834
5156
|
await runSkillwikiMcpStdio();
|
|
4835
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": {
|