skillwiki 0.10.111 → 0.10.113
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-I6IRJCUJ.js → chunk-3TKNAOJH.js} +2 -2
- package/dist/chunk-5D5HFQ76.js +111 -0
- package/dist/{chunk-OJJBZDPR.js → chunk-5RHR4FEK.js} +183 -211
- package/dist/{chunk-GQWCTJTU.js → chunk-6VUK2FTX.js} +1 -1
- package/dist/{chunk-JQV5KNPT.js → chunk-CBT35FAW.js} +3 -3
- package/dist/{chunk-HBJLFPQP.js → chunk-FQERKMD7.js} +458 -18
- package/dist/{chunk-PYPPG7RF.js → chunk-IJKJGQKL.js} +1 -1
- package/dist/{chunk-MRPJ7X7W.js → chunk-IJWZDQDH.js} +3 -3
- package/dist/{chunk-X5GL2FCU.js → chunk-JL2SCLXT.js} +1 -1
- package/dist/chunk-K3M6OSUW.js +77 -0
- package/dist/{chunk-5IBLXGLY.js → chunk-K3YZESND.js} +2 -2
- package/dist/chunk-OGEAFXON.js +60 -0
- package/dist/{chunk-6OQLBNKR.js → chunk-OWWWO2XS.js} +29 -4
- package/dist/{chunk-KWEYA3OE.js → chunk-SEY4N4CT.js} +1 -1
- package/dist/{chunk-UIEQIJXP.js → chunk-VNSZCK5V.js} +47 -44
- package/dist/{chunk-TDBUJUYH.js → chunk-XJ5VEIH7.js} +42 -18
- package/dist/cli.js +586 -122
- package/dist/{index-projection-2K672CBO.js → index-projection-RQSOEDXM.js} +5 -5
- package/dist/{journal-QU5IDXQF.js → journal-V5XHBEI5.js} +3 -3
- package/dist/{managed-write-preflight-GRSHQYKC.js → managed-write-preflight-2BMHZWVJ.js} +7 -7
- package/dist/mcp-auth-env-QONKW4EO.js +25 -0
- package/dist/{migrate-PIB4QA54.js → migrate-SRRCXI2K.js} +6 -5
- package/dist/mirror-H5BTBAON.js +151 -0
- package/dist/runtime-LQZIG3XN.js +20 -0
- package/dist/skillwiki-mcp.js +14 -12
- package/dist/sources-E6KHDRDE.js +10 -0
- package/dist/{watcher-MHG46J3R.js → watcher-VJ3TOU6J.js} +20 -110
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/.cursor-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/skills/wiki-ingest/SKILL.md +47 -0
- package/skills/skills/wiki-init/SKILL.md +3 -0
- package/skills/skills/wiki-lint/SKILL.md +7 -0
- package/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/wiki-ingest/SKILL.md +47 -0
- package/skills/wiki-init/SKILL.md +3 -0
- package/skills/wiki-lint/SKILL.md +7 -0
- package/templates/launchd/com.karlchow.skillwiki-mirror-sse.plist +20 -0
- package/dist/chunk-I2J3IJB7.js +0 -54
- package/dist/runtime-GLMI5OCT.js +0 -15
- package/dist/sources-D2MGP246.js +0 -10
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
runFetchGuard,
|
|
4
|
-
runFetchGuardSync
|
|
5
|
-
} from "./chunk-I2J3IJB7.js";
|
|
6
2
|
import {
|
|
7
3
|
buildDeleteIntent,
|
|
8
4
|
normalizeVaultRelPath,
|
|
@@ -16,9 +12,6 @@ import {
|
|
|
16
12
|
DEFAULT_MCP_URL,
|
|
17
13
|
DEFAULT_NO_DECISION_STREAK,
|
|
18
14
|
GateError,
|
|
19
|
-
MCP_HOST_ID_ENV,
|
|
20
|
-
MCP_TOKEN_ENV,
|
|
21
|
-
MCP_URL_ENV,
|
|
22
15
|
PUSH_NOT_IN_PROFILE,
|
|
23
16
|
SATELLITE_STALE_MS,
|
|
24
17
|
buildVectorIndex,
|
|
@@ -31,17 +24,13 @@ import {
|
|
|
31
24
|
guardProtectedVaultWrite,
|
|
32
25
|
isEventLedgerPath,
|
|
33
26
|
isFailedRunStatus,
|
|
34
|
-
mcpAuthFromEnv,
|
|
35
27
|
measureDirtyVolume,
|
|
36
28
|
memoryAuthorityTiersRank,
|
|
37
|
-
mergeMcpAuthIntoEnv,
|
|
38
|
-
nonEmptyEnvValue,
|
|
39
29
|
pruneVectorIndex,
|
|
40
30
|
readCliPackageJson,
|
|
41
31
|
readSatelliteLatestRunAt,
|
|
42
32
|
readSatelliteLatestRunFromText,
|
|
43
33
|
readVaultSyncConfig,
|
|
44
|
-
redactMcpSecret,
|
|
45
34
|
reindexPageInVectorIndex,
|
|
46
35
|
renderIndexUpsert,
|
|
47
36
|
renderProjectIndex,
|
|
@@ -75,7 +64,7 @@ import {
|
|
|
75
64
|
upsertIndexEntry,
|
|
76
65
|
vectorIndexStatus,
|
|
77
66
|
withMcpClient
|
|
78
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-5RHR4FEK.js";
|
|
79
68
|
import {
|
|
80
69
|
applySourceDisposition,
|
|
81
70
|
decodeSourceActionApproval,
|
|
@@ -83,7 +72,7 @@ import {
|
|
|
83
72
|
inventorySources,
|
|
84
73
|
planSourceDisposition,
|
|
85
74
|
runSourcesPending
|
|
86
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-CBT35FAW.js";
|
|
87
76
|
import {
|
|
88
77
|
normalizeDistTag,
|
|
89
78
|
readCache,
|
|
@@ -96,7 +85,7 @@ import {
|
|
|
96
85
|
UNMANAGED_START,
|
|
97
86
|
renderRootIndex,
|
|
98
87
|
writeRootIndexProjection
|
|
99
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-IJWZDQDH.js";
|
|
100
89
|
import {
|
|
101
90
|
FLEET_REL_PATH,
|
|
102
91
|
REDACTED_MALFORMED_REFERENCE,
|
|
@@ -124,6 +113,7 @@ import {
|
|
|
124
113
|
gitStrict,
|
|
125
114
|
hasActiveGitSequencer,
|
|
126
115
|
hasUnmergedPaths,
|
|
116
|
+
isAdvisoryLintWarning,
|
|
127
117
|
isValidRemoteDeleteCap,
|
|
128
118
|
isWorktreeClean,
|
|
129
119
|
listJournalOpIds,
|
|
@@ -184,7 +174,7 @@ import {
|
|
|
184
174
|
stageVaultContentChanges,
|
|
185
175
|
supersedeStaleReviewRequiredJournals,
|
|
186
176
|
taxonomyCommentForPage
|
|
187
|
-
} from "./chunk-
|
|
177
|
+
} from "./chunk-FQERKMD7.js";
|
|
188
178
|
import {
|
|
189
179
|
authorizeRawOperation,
|
|
190
180
|
buildSourceReferenceIndex,
|
|
@@ -201,11 +191,11 @@ import {
|
|
|
201
191
|
resolveExistingRegularFileInsideVault,
|
|
202
192
|
validateLogEvent,
|
|
203
193
|
writeLogEvent
|
|
204
|
-
} from "./chunk-
|
|
194
|
+
} from "./chunk-K3YZESND.js";
|
|
205
195
|
import {
|
|
206
196
|
assertTargetInsideVault,
|
|
207
197
|
prepareTypedPage
|
|
208
|
-
} from "./chunk-
|
|
198
|
+
} from "./chunk-3TKNAOJH.js";
|
|
209
199
|
import {
|
|
210
200
|
extractFrontmatter,
|
|
211
201
|
mapWithConcurrency,
|
|
@@ -217,25 +207,38 @@ import {
|
|
|
217
207
|
scanVault,
|
|
218
208
|
splitFrontmatter,
|
|
219
209
|
vaultIoConcurrency
|
|
220
|
-
} from "./chunk-
|
|
210
|
+
} from "./chunk-JL2SCLXT.js";
|
|
211
|
+
import {
|
|
212
|
+
MCP_HOST_ID_ENV,
|
|
213
|
+
MCP_TOKEN_ENV,
|
|
214
|
+
MCP_URL_ENV,
|
|
215
|
+
mcpAuthFromEnv,
|
|
216
|
+
mergeMcpAuthIntoEnv,
|
|
217
|
+
nonEmptyEnvValue,
|
|
218
|
+
redactMcpSecret
|
|
219
|
+
} from "./chunk-OGEAFXON.js";
|
|
221
220
|
import {
|
|
222
221
|
CLIP_MIGRATION_HINT,
|
|
223
222
|
refreshInstalledClipPlist
|
|
224
|
-
} from "./chunk-
|
|
223
|
+
} from "./chunk-XJ5VEIH7.js";
|
|
224
|
+
import {
|
|
225
|
+
atomicWriteText
|
|
226
|
+
} from "./chunk-6VUK2FTX.js";
|
|
225
227
|
import {
|
|
226
228
|
CLIP_NOT_CONFIGURED,
|
|
227
229
|
loadClipOutboxConfig
|
|
228
|
-
} from "./chunk-
|
|
230
|
+
} from "./chunk-OWWWO2XS.js";
|
|
231
|
+
import {
|
|
232
|
+
runFetchGuard,
|
|
233
|
+
runFetchGuardSync
|
|
234
|
+
} from "./chunk-5D5HFQ76.js";
|
|
229
235
|
import {
|
|
230
236
|
parseDotenvFile,
|
|
231
237
|
parseDotenvText,
|
|
232
238
|
profileKey,
|
|
233
239
|
resolveSkillwikiHome,
|
|
234
240
|
writeDotenv
|
|
235
|
-
} from "./chunk-
|
|
236
|
-
import {
|
|
237
|
-
atomicWriteText
|
|
238
|
-
} from "./chunk-GQWCTJTU.js";
|
|
241
|
+
} from "./chunk-IJKJGQKL.js";
|
|
239
242
|
import {
|
|
240
243
|
ExitCode,
|
|
241
244
|
MetaSchema,
|
|
@@ -245,10 +248,10 @@ import {
|
|
|
245
248
|
err,
|
|
246
249
|
ok,
|
|
247
250
|
resolveSessionKind
|
|
248
|
-
} from "./chunk-
|
|
251
|
+
} from "./chunk-VNSZCK5V.js";
|
|
249
252
|
|
|
250
253
|
// src/cli.ts
|
|
251
|
-
import { join as
|
|
254
|
+
import { join as join45 } from "path";
|
|
252
255
|
import { Command } from "commander";
|
|
253
256
|
|
|
254
257
|
// src/utils/output.ts
|
|
@@ -1599,21 +1602,21 @@ function rollbackPath(vault, path, stage2, stage3) {
|
|
|
1599
1602
|
function hasMarkers(path, text) {
|
|
1600
1603
|
const lines = text.split(/\r?\n/);
|
|
1601
1604
|
let open2 = false;
|
|
1602
|
-
let
|
|
1605
|
+
let sep6 = false;
|
|
1603
1606
|
for (const line of lines) {
|
|
1604
1607
|
if (line.startsWith("<<<<<<< ")) {
|
|
1605
1608
|
open2 = true;
|
|
1606
|
-
|
|
1609
|
+
sep6 = false;
|
|
1607
1610
|
continue;
|
|
1608
1611
|
}
|
|
1609
1612
|
if (line === "=======" && open2) {
|
|
1610
|
-
|
|
1613
|
+
sep6 = true;
|
|
1611
1614
|
continue;
|
|
1612
1615
|
}
|
|
1613
|
-
if (line.startsWith(">>>>>>> ") && open2 &&
|
|
1616
|
+
if (line.startsWith(">>>>>>> ") && open2 && sep6) return true;
|
|
1614
1617
|
if (line.startsWith(">>>>>>> ")) {
|
|
1615
1618
|
open2 = false;
|
|
1616
|
-
|
|
1619
|
+
sep6 = false;
|
|
1617
1620
|
}
|
|
1618
1621
|
}
|
|
1619
1622
|
return false;
|
|
@@ -3476,7 +3479,11 @@ async function runHealth(input) {
|
|
|
3476
3479
|
checks: doctor.result.data.checks.map((check) => ({ id: check.id, status: check.status, detail: check.detail }))
|
|
3477
3480
|
};
|
|
3478
3481
|
const lintComponent = {
|
|
3479
|
-
status: statusFromCounts({
|
|
3482
|
+
status: statusFromCounts({
|
|
3483
|
+
error: lint.result.data.summary.errors,
|
|
3484
|
+
warnings: lint.result.data.buckets.filter((bucket) => bucket.severity === "warning" && !isAdvisoryLintWarning(bucket.kind)).reduce((n, bucket) => n + bucket.count, 0),
|
|
3485
|
+
info: lint.result.data.summary.info
|
|
3486
|
+
}),
|
|
3480
3487
|
blocking: true,
|
|
3481
3488
|
summary: lint.result.data.summary,
|
|
3482
3489
|
buckets: lint.result.data.buckets,
|
|
@@ -3583,7 +3590,7 @@ async function runClipOutboxStatus(home) {
|
|
|
3583
3590
|
result: ok({ enabled: false, humanHint: CLIP_NOT_CONFIGURED })
|
|
3584
3591
|
};
|
|
3585
3592
|
}
|
|
3586
|
-
const { readJournal: readJournal2 } = await import("./journal-
|
|
3593
|
+
const { readJournal: readJournal2 } = await import("./journal-V5XHBEI5.js");
|
|
3587
3594
|
let queue;
|
|
3588
3595
|
try {
|
|
3589
3596
|
queue = (await readJournal2(resolved.config.stateRoot)).map(({ captureId, state, reason, attempts }) => ({ captureId, state, reason, attempts }));
|
|
@@ -3601,7 +3608,7 @@ async function runClipOutboxStatus(home) {
|
|
|
3601
3608
|
}
|
|
3602
3609
|
async function runClipOutboxInstall(input) {
|
|
3603
3610
|
try {
|
|
3604
|
-
const { migrateClipOutbox } = await import("./migrate-
|
|
3611
|
+
const { migrateClipOutbox } = await import("./migrate-SRRCXI2K.js");
|
|
3605
3612
|
return { exitCode: ExitCode.OK, result: ok(await migrateClipOutbox(input)) };
|
|
3606
3613
|
} catch (e) {
|
|
3607
3614
|
return { exitCode: ExitCode.PREFLIGHT_FAILED, result: err("PREFLIGHT_FAILED", { message: e instanceof Error ? e.message : "clip migration failed" }) };
|
|
@@ -3614,10 +3621,10 @@ async function runClipOutboxWatch(home) {
|
|
|
3614
3621
|
let client;
|
|
3615
3622
|
try {
|
|
3616
3623
|
const { S3Client: S3Client3 } = await import("@aws-sdk/client-s3");
|
|
3617
|
-
const { loadClipCredentials, validateRuntime } = await import("./runtime-
|
|
3624
|
+
const { loadClipCredentials, validateRuntime } = await import("./runtime-LQZIG3XN.js");
|
|
3618
3625
|
const { createS3CreateOnlyPut, parseDestination } = await import("./upload-HTE62TOV.js");
|
|
3619
3626
|
const { postReadyHint } = await import("./ready-S7F53Q2D.js");
|
|
3620
|
-
const { startClipWorker } = await import("./watcher-
|
|
3627
|
+
const { startClipWorker } = await import("./watcher-VJ3TOU6J.js");
|
|
3621
3628
|
const cfg = resolved.config;
|
|
3622
3629
|
const credentials = await loadClipCredentials(home, cfg);
|
|
3623
3630
|
await validateRuntime(home, cfg, credentials.s3.endpoint);
|
|
@@ -3653,6 +3660,47 @@ async function runClipOutboxWatch(home) {
|
|
|
3653
3660
|
client?.destroy();
|
|
3654
3661
|
}
|
|
3655
3662
|
}
|
|
3663
|
+
async function runClipOutboxMirrorWatch(home) {
|
|
3664
|
+
const resolved = await loadClipOutboxConfig(home);
|
|
3665
|
+
if (!resolved.ok || !resolved.enabled) return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { message: resolved.ok ? CLIP_NOT_CONFIGURED : resolved.message }) };
|
|
3666
|
+
let client;
|
|
3667
|
+
const abort = new AbortController();
|
|
3668
|
+
try {
|
|
3669
|
+
const { S3Client: S3Client3 } = await import("@aws-sdk/client-s3");
|
|
3670
|
+
const { loadClipCredentials, validateRuntime } = await import("./runtime-LQZIG3XN.js");
|
|
3671
|
+
const { createS3GetObject, eventsUrlFromMcp, parseDestinationForMirror, startMirrorListener } = await import("./mirror-H5BTBAON.js");
|
|
3672
|
+
const { resolveMcpAuthEnv } = await import("./mcp-auth-env-QONKW4EO.js");
|
|
3673
|
+
const cfg = resolved.config;
|
|
3674
|
+
const credentials = await loadClipCredentials(home, cfg);
|
|
3675
|
+
await validateRuntime(home, cfg, credentials.s3.endpoint);
|
|
3676
|
+
const auth = await resolveMcpAuthEnv({ home });
|
|
3677
|
+
if (!auth.url || !auth.token) throw new Error("MCP bearer required for inbound SSE");
|
|
3678
|
+
const destination = parseDestinationForMirror(cfg.destination);
|
|
3679
|
+
client = new S3Client3({ endpoint: credentials.s3.endpoint, region: credentials.s3.region, credentials: credentials.s3, forcePathStyle: true, maxAttempts: 1 });
|
|
3680
|
+
const done = new Promise((resolve10) => {
|
|
3681
|
+
abort.signal.addEventListener("abort", () => resolve10(), { once: true });
|
|
3682
|
+
});
|
|
3683
|
+
process.once("SIGTERM", () => abort.abort());
|
|
3684
|
+
process.once("SIGINT", () => abort.abort());
|
|
3685
|
+
await Promise.race([
|
|
3686
|
+
startMirrorListener({
|
|
3687
|
+
home,
|
|
3688
|
+
config: cfg,
|
|
3689
|
+
eventsUrl: eventsUrlFromMcp(auth.url),
|
|
3690
|
+
mcpToken: auth.token,
|
|
3691
|
+
getObject: createS3GetObject({ client, bucket: destination.bucket, prefix: destination.prefix, maxBytes: cfg.maxBundleBytes }),
|
|
3692
|
+
signal: abort.signal
|
|
3693
|
+
}),
|
|
3694
|
+
done
|
|
3695
|
+
]);
|
|
3696
|
+
return { exitCode: ExitCode.OK, result: ok({ humanHint: "Inbound SSE listener stopped." }) };
|
|
3697
|
+
} catch {
|
|
3698
|
+
return { exitCode: ExitCode.PREFLIGHT_FAILED, result: err("PREFLIGHT_FAILED", { message: "inbound SSE listener startup failed; check MCP bearer, clip credentials, frozen mirror and wiki-push isolation" }) };
|
|
3699
|
+
} finally {
|
|
3700
|
+
abort.abort();
|
|
3701
|
+
client?.destroy();
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3656
3704
|
|
|
3657
3705
|
// src/commands/connect.ts
|
|
3658
3706
|
import { chmod, mkdir as mkdir5, readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
@@ -4132,7 +4180,7 @@ ${fmRewritten}
|
|
|
4132
4180
|
}
|
|
4133
4181
|
let indexUpdated = false;
|
|
4134
4182
|
if (!isRaw) {
|
|
4135
|
-
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-
|
|
4183
|
+
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-RQSOEDXM.js");
|
|
4136
4184
|
const before = await readFile7(join19(input.vault, "index.md"), "utf8").catch(() => "");
|
|
4137
4185
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
4138
4186
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
@@ -4253,10 +4301,10 @@ async function runRemove(input) {
|
|
|
4253
4301
|
const tombstonePath = await writeDeleteIntent(input.vault, intent);
|
|
4254
4302
|
await unlink2(join20(input.vault, relPath));
|
|
4255
4303
|
if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
|
|
4256
|
-
const { readFile:
|
|
4304
|
+
const { readFile: readFile24 } = await import("fs/promises");
|
|
4257
4305
|
const { join: pathJoin } = await import("path");
|
|
4258
|
-
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-
|
|
4259
|
-
const before = await
|
|
4306
|
+
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-RQSOEDXM.js");
|
|
4307
|
+
const before = await readFile24(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
|
|
4260
4308
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
4261
4309
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
4262
4310
|
const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
|
|
@@ -6839,8 +6887,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
|
|
|
6839
6887
|
if (!strategy.usesJournal || !phaseAtLeast(currentPhase, "page")) {
|
|
6840
6888
|
if (strategy.publisherKind === "project-page") {
|
|
6841
6889
|
try {
|
|
6842
|
-
const { mkdir:
|
|
6843
|
-
await
|
|
6890
|
+
const { mkdir: mkdir14 } = await import("fs/promises");
|
|
6891
|
+
await mkdir14(dirname7(prepared.targetPath), { recursive: true });
|
|
6844
6892
|
} catch (error) {
|
|
6845
6893
|
return {
|
|
6846
6894
|
ok: false,
|
|
@@ -7647,7 +7695,7 @@ function buildRawContent(sourceUrl, ingested, sha2562, body) {
|
|
|
7647
7695
|
];
|
|
7648
7696
|
return lines.join("\n");
|
|
7649
7697
|
}
|
|
7650
|
-
function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance) {
|
|
7698
|
+
function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance, html) {
|
|
7651
7699
|
const aliases = [];
|
|
7652
7700
|
const sourcesYaml = ` - ${rawRelPath}`;
|
|
7653
7701
|
const tagsYaml = tags.length > 0 ? tags.map((t) => ` - ${t}`).join("\n") : " []";
|
|
@@ -7670,6 +7718,10 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
|
|
|
7670
7718
|
if (provenance) {
|
|
7671
7719
|
fmLines.push(`provenance: ${provenance}`);
|
|
7672
7720
|
}
|
|
7721
|
+
fmLines.push("output_standard: v2");
|
|
7722
|
+
if (html) {
|
|
7723
|
+
fmLines.push("html_render: true");
|
|
7724
|
+
}
|
|
7673
7725
|
fmLines.push("---");
|
|
7674
7726
|
fmLines.push("");
|
|
7675
7727
|
const body = [
|
|
@@ -7753,7 +7805,7 @@ async function acquireRawCaptureLock(path) {
|
|
|
7753
7805
|
if (error.code !== "EEXIST") {
|
|
7754
7806
|
return err("WRITE_FAILED", { path: lockPath, phase: "raw-lock", message: String(error) });
|
|
7755
7807
|
}
|
|
7756
|
-
await new Promise((
|
|
7808
|
+
await new Promise((resolve10) => setTimeout(resolve10, 10));
|
|
7757
7809
|
}
|
|
7758
7810
|
}
|
|
7759
7811
|
return err("WRITE_FAILED", { path: lockPath, phase: "raw-lock", message: "raw capture lock held" });
|
|
@@ -7892,7 +7944,8 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
7892
7944
|
input.type,
|
|
7893
7945
|
tags,
|
|
7894
7946
|
rawRelPath,
|
|
7895
|
-
input.provenance
|
|
7947
|
+
input.provenance,
|
|
7948
|
+
input.html
|
|
7896
7949
|
);
|
|
7897
7950
|
if (!input.dryRun) {
|
|
7898
7951
|
try {
|
|
@@ -7983,7 +8036,8 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
7983
8036
|
input.type,
|
|
7984
8037
|
tags,
|
|
7985
8038
|
rawRelPath,
|
|
7986
|
-
input.provenance
|
|
8039
|
+
input.provenance,
|
|
8040
|
+
input.html
|
|
7987
8041
|
);
|
|
7988
8042
|
const refreshedPublication = preparePagePublicationFromContent({
|
|
7989
8043
|
vault: input.vault,
|
|
@@ -8386,14 +8440,412 @@ async function runTagReconcile(input) {
|
|
|
8386
8440
|
};
|
|
8387
8441
|
}
|
|
8388
8442
|
|
|
8443
|
+
// src/commands/page-render-html.ts
|
|
8444
|
+
import { lstat, mkdir as mkdir11, readFile as readFile17, realpath } from "fs/promises";
|
|
8445
|
+
import { basename, dirname as dirname9, join as join33, relative as relative2, resolve as resolve5, sep as sep2 } from "path";
|
|
8446
|
+
function escapeHtml(text) {
|
|
8447
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
8448
|
+
}
|
|
8449
|
+
var SAFE_HREF_RE = /^(https?:\/\/|mailto:|#)/i;
|
|
8450
|
+
function escapeAttr(text) {
|
|
8451
|
+
return escapeHtml(text).replace(/'/g, "'");
|
|
8452
|
+
}
|
|
8453
|
+
function slugifyHeading(text, used) {
|
|
8454
|
+
const base = text.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "section";
|
|
8455
|
+
const n = used.get(base) ?? 0;
|
|
8456
|
+
used.set(base, n + 1);
|
|
8457
|
+
return n === 0 ? base : `${base}-${n + 1}`;
|
|
8458
|
+
}
|
|
8459
|
+
function renderInlineMarkdown(raw) {
|
|
8460
|
+
const re = /(`[^`]+`)|(\[\[(?:[^\]]+)\]\])|(!\[[^\]]*\]\([^)]+\))|(\[[^\]]+\]\([^)]+\))|(\*\*[^*]+\*\*)|(\*[^*]+\*)/g;
|
|
8461
|
+
const parts = [];
|
|
8462
|
+
let last = 0;
|
|
8463
|
+
let match;
|
|
8464
|
+
while ((match = re.exec(raw)) !== null) {
|
|
8465
|
+
parts.push(escapeHtml(raw.slice(last, match.index)));
|
|
8466
|
+
const token = match[0];
|
|
8467
|
+
if (token.startsWith("`")) {
|
|
8468
|
+
parts.push(`<code>${escapeHtml(token.slice(1, -1))}</code>`);
|
|
8469
|
+
} else if (token.startsWith("[[")) {
|
|
8470
|
+
const inner = token.slice(2, -2);
|
|
8471
|
+
const pipe = inner.indexOf("|");
|
|
8472
|
+
const label = pipe === -1 ? inner : inner.slice(pipe + 1);
|
|
8473
|
+
parts.push(escapeHtml(label.trim()));
|
|
8474
|
+
} else if (token.startsWith("![")) {
|
|
8475
|
+
const alt = token.match(/^!\[([^\]]*)\]/)?.[1] ?? "";
|
|
8476
|
+
parts.push(escapeHtml(alt));
|
|
8477
|
+
} else if (token.startsWith("[")) {
|
|
8478
|
+
const linked = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
8479
|
+
if (linked && SAFE_HREF_RE.test(linked[2])) {
|
|
8480
|
+
parts.push(`<a href="${escapeAttr(linked[2])}">${escapeHtml(linked[1])}</a>`);
|
|
8481
|
+
} else if (linked) {
|
|
8482
|
+
parts.push(escapeHtml(linked[1]));
|
|
8483
|
+
} else {
|
|
8484
|
+
parts.push(escapeHtml(token));
|
|
8485
|
+
}
|
|
8486
|
+
} else if (token.startsWith("**")) {
|
|
8487
|
+
parts.push(`<strong>${escapeHtml(token.slice(2, -2))}</strong>`);
|
|
8488
|
+
} else {
|
|
8489
|
+
parts.push(`<em>${escapeHtml(token.slice(1, -1))}</em>`);
|
|
8490
|
+
}
|
|
8491
|
+
last = match.index + token.length;
|
|
8492
|
+
}
|
|
8493
|
+
parts.push(escapeHtml(raw.slice(last)));
|
|
8494
|
+
return parts.join("");
|
|
8495
|
+
}
|
|
8496
|
+
function parseTableRow(line) {
|
|
8497
|
+
const trimmed = line.trim();
|
|
8498
|
+
const inner = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
|
|
8499
|
+
const withoutEnd = inner.endsWith("|") ? inner.slice(0, -1) : inner;
|
|
8500
|
+
return withoutEnd.split("|").map((cell) => cell.trim());
|
|
8501
|
+
}
|
|
8502
|
+
function isTableSeparator(line) {
|
|
8503
|
+
return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
|
|
8504
|
+
}
|
|
8505
|
+
function parseMarkdownBlocks(body) {
|
|
8506
|
+
const lines = body.split(/\r?\n/);
|
|
8507
|
+
const blocks = [];
|
|
8508
|
+
let i = 0;
|
|
8509
|
+
while (i < lines.length) {
|
|
8510
|
+
const line = lines[i];
|
|
8511
|
+
const fence = line.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
|
|
8512
|
+
if (fence) {
|
|
8513
|
+
const marker = fence[2];
|
|
8514
|
+
const language = fence[3].trim();
|
|
8515
|
+
const content = [];
|
|
8516
|
+
i++;
|
|
8517
|
+
while (i < lines.length) {
|
|
8518
|
+
const close = lines[i].match(/^(\s*)(`{3,}|~{3,})\s*$/);
|
|
8519
|
+
if (close && close[2][0] === marker[0] && close[2].length >= marker.length) break;
|
|
8520
|
+
content.push(lines[i]);
|
|
8521
|
+
i++;
|
|
8522
|
+
}
|
|
8523
|
+
blocks.push({ kind: "code", language, text: content.join("\n") });
|
|
8524
|
+
i++;
|
|
8525
|
+
continue;
|
|
8526
|
+
}
|
|
8527
|
+
const heading = line.match(/^(#{1,6})\s+(\S.*)$/);
|
|
8528
|
+
if (heading) {
|
|
8529
|
+
blocks.push({ kind: "heading", level: heading[1].length, text: heading[2].trim() });
|
|
8530
|
+
i++;
|
|
8531
|
+
continue;
|
|
8532
|
+
}
|
|
8533
|
+
if (/^\s*\|.*\|\s*$/.test(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
|
|
8534
|
+
const rows = [parseTableRow(line)];
|
|
8535
|
+
i += 2;
|
|
8536
|
+
while (i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i]) && !isTableSeparator(lines[i])) {
|
|
8537
|
+
rows.push(parseTableRow(lines[i]));
|
|
8538
|
+
i++;
|
|
8539
|
+
}
|
|
8540
|
+
blocks.push({ kind: "table", rows });
|
|
8541
|
+
continue;
|
|
8542
|
+
}
|
|
8543
|
+
const ordered = line.match(/^\s*\d+\.\s+(.*)$/);
|
|
8544
|
+
const unordered = line.match(/^\s*[-*+]\s+(.*)$/);
|
|
8545
|
+
if (ordered || unordered) {
|
|
8546
|
+
const isOrdered = Boolean(ordered);
|
|
8547
|
+
const itemRe = isOrdered ? /^\s*\d+\.\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/;
|
|
8548
|
+
const items = [];
|
|
8549
|
+
while (i < lines.length) {
|
|
8550
|
+
const item = lines[i].match(itemRe);
|
|
8551
|
+
if (!item) break;
|
|
8552
|
+
const chunks = [item[1]];
|
|
8553
|
+
i++;
|
|
8554
|
+
while (i < lines.length && /^\s+\S/.test(lines[i]) && !itemRe.test(lines[i]) && !/^(#{1,6})\s+/.test(lines[i])) {
|
|
8555
|
+
chunks.push(lines[i].trim());
|
|
8556
|
+
i++;
|
|
8557
|
+
}
|
|
8558
|
+
items.push(chunks.join(" "));
|
|
8559
|
+
}
|
|
8560
|
+
blocks.push({ kind: "list", ordered: isOrdered, items });
|
|
8561
|
+
continue;
|
|
8562
|
+
}
|
|
8563
|
+
if (!line.trim()) {
|
|
8564
|
+
i++;
|
|
8565
|
+
continue;
|
|
8566
|
+
}
|
|
8567
|
+
const para = [line.trim()];
|
|
8568
|
+
i++;
|
|
8569
|
+
while (i < lines.length) {
|
|
8570
|
+
const next = lines[i];
|
|
8571
|
+
if (!next.trim()) break;
|
|
8572
|
+
if (/^(#{1,6})\s+/.test(next) || /^(\s*)(`{3,}|~{3,})/.test(next) || /^\s*\|.*\|\s*$/.test(next) || /^\s*\d+\.\s+/.test(next) || /^\s*[-*+]\s+/.test(next)) {
|
|
8573
|
+
break;
|
|
8574
|
+
}
|
|
8575
|
+
para.push(next.trim());
|
|
8576
|
+
i++;
|
|
8577
|
+
}
|
|
8578
|
+
blocks.push({ kind: "paragraph", text: para.join(" ") });
|
|
8579
|
+
}
|
|
8580
|
+
return blocks;
|
|
8581
|
+
}
|
|
8582
|
+
function renderBlock(block) {
|
|
8583
|
+
switch (block.kind) {
|
|
8584
|
+
case "heading": {
|
|
8585
|
+
const tag = `h${Math.min(block.level, 6)}`;
|
|
8586
|
+
return `<${tag}>${renderInlineMarkdown(block.text)}</${tag}>`;
|
|
8587
|
+
}
|
|
8588
|
+
case "paragraph":
|
|
8589
|
+
return `<p>${renderInlineMarkdown(block.text)}</p>`;
|
|
8590
|
+
case "list": {
|
|
8591
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
8592
|
+
const items = block.items.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("");
|
|
8593
|
+
return `<${tag}>${items}</${tag}>`;
|
|
8594
|
+
}
|
|
8595
|
+
case "table": {
|
|
8596
|
+
if (block.rows.length === 0) return "";
|
|
8597
|
+
const [header, ...body] = block.rows;
|
|
8598
|
+
const thead = `<thead><tr>${header.map((cell) => `<th>${renderInlineMarkdown(cell)}</th>`).join("")}</tr></thead>`;
|
|
8599
|
+
const tbody = body.length > 0 ? `<tbody>${body.map((row) => `<tr>${row.map((cell) => `<td>${renderInlineMarkdown(cell)}</td>`).join("")}</tr>`).join("")}</tbody>` : "";
|
|
8600
|
+
return `<table>${thead}${tbody}</table>`;
|
|
8601
|
+
}
|
|
8602
|
+
case "code": {
|
|
8603
|
+
const cls = block.language.toLowerCase() === "mermaid" ? ' class="mermaid"' : "";
|
|
8604
|
+
return `<pre${cls}><code>${escapeHtml(block.text)}</code></pre>`;
|
|
8605
|
+
}
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8608
|
+
function groupSections(blocks) {
|
|
8609
|
+
const used = /* @__PURE__ */ new Map();
|
|
8610
|
+
let title = "Page";
|
|
8611
|
+
const intro = [];
|
|
8612
|
+
const sections = [];
|
|
8613
|
+
let current = null;
|
|
8614
|
+
for (const block of blocks) {
|
|
8615
|
+
if (block.kind === "heading" && block.level === 1 && title === "Page") {
|
|
8616
|
+
title = block.text;
|
|
8617
|
+
continue;
|
|
8618
|
+
}
|
|
8619
|
+
if (block.kind === "heading" && block.level === 2) {
|
|
8620
|
+
current = { id: slugifyHeading(block.text, used), title: block.text, blocks: [] };
|
|
8621
|
+
sections.push(current);
|
|
8622
|
+
continue;
|
|
8623
|
+
}
|
|
8624
|
+
if (current) current.blocks.push(block);
|
|
8625
|
+
else intro.push(block);
|
|
8626
|
+
}
|
|
8627
|
+
return { title, intro, sections };
|
|
8628
|
+
}
|
|
8629
|
+
function renderPageHtml(title, body) {
|
|
8630
|
+
const parsed = parseMarkdownBlocks(body);
|
|
8631
|
+
const grouped = groupSections(parsed);
|
|
8632
|
+
const pageTitle = grouped.title !== "Page" ? grouped.title : title;
|
|
8633
|
+
const sections = grouped.sections.length > 0 ? grouped.sections : [{ id: "content", title: pageTitle, blocks: grouped.intro }];
|
|
8634
|
+
const introHtml = grouped.sections.length > 0 && grouped.intro.length > 0 ? grouped.intro.map(renderBlock).join("\n") : "";
|
|
8635
|
+
const toc = sections.map((section) => `<li><a href="#${escapeAttr(section.id)}">${escapeHtml(section.title)}</a></li>`).join("");
|
|
8636
|
+
const tabs = sections.map(
|
|
8637
|
+
(section, index) => `<button type="button" role="tab" aria-selected="${index === 0 ? "true" : "false"}" aria-controls="${escapeAttr(section.id)}" data-tab="${escapeAttr(section.id)}">${escapeHtml(section.title)}</button>`
|
|
8638
|
+
).join("");
|
|
8639
|
+
const panels = sections.map((section, index) => {
|
|
8640
|
+
const inner = section.blocks.map(renderBlock).join("\n");
|
|
8641
|
+
return `<section id="${escapeAttr(section.id)}" class="tab-panel" role="tabpanel"${index === 0 ? "" : " hidden"}><details open><summary>${escapeHtml(section.title)}</summary>
|
|
8642
|
+
${inner}
|
|
8643
|
+
</details></section>`;
|
|
8644
|
+
}).join("\n");
|
|
8645
|
+
return `<!DOCTYPE html>
|
|
8646
|
+
<html lang="en">
|
|
8647
|
+
<head>
|
|
8648
|
+
<meta charset="utf-8">
|
|
8649
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8650
|
+
<title>${escapeHtml(pageTitle)}</title>
|
|
8651
|
+
<style>
|
|
8652
|
+
:root { color-scheme: light dark; --bg: #f7f5f0; --fg: #1b1b1b; --accent: #355c7d; --line: #cfc8bc; }
|
|
8653
|
+
body { margin: 0 auto; max-width: 52rem; padding: 1.5rem; font: 16px/1.5 system-ui, sans-serif; background: var(--bg); color: var(--fg); }
|
|
8654
|
+
nav.toc { border: 1px solid var(--line); padding: 0.75rem 1rem; margin-bottom: 1rem; }
|
|
8655
|
+
nav.toc ol { margin: 0; padding-left: 1.25rem; }
|
|
8656
|
+
.tabs { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 1rem 0; }
|
|
8657
|
+
.tabs button { border: 1px solid var(--line); background: transparent; color: inherit; padding: 0.35rem 0.7rem; cursor: pointer; }
|
|
8658
|
+
.tabs button[aria-selected="true"] { background: var(--accent); color: #fff; }
|
|
8659
|
+
.tab-panel { border: 1px solid var(--line); padding: 0.75rem; }
|
|
8660
|
+
details { margin: 0; }
|
|
8661
|
+
summary { cursor: pointer; font-weight: 600; }
|
|
8662
|
+
pre { overflow: auto; padding: 0.75rem; background: #111; color: #f5f5f5; }
|
|
8663
|
+
pre.mermaid { white-space: pre-wrap; }
|
|
8664
|
+
table { border-collapse: collapse; width: 100%; }
|
|
8665
|
+
th, td { border: 1px solid var(--line); padding: 0.35rem 0.5rem; text-align: left; }
|
|
8666
|
+
a { color: var(--accent); }
|
|
8667
|
+
</style>
|
|
8668
|
+
</head>
|
|
8669
|
+
<body>
|
|
8670
|
+
<nav class="toc" id="toc"><strong>Contents</strong><ol>${toc}</ol></nav>
|
|
8671
|
+
${introHtml}
|
|
8672
|
+
<div class="tabs" role="tablist">${tabs}</div>
|
|
8673
|
+
${panels}
|
|
8674
|
+
<script>
|
|
8675
|
+
(function () {
|
|
8676
|
+
var tabs = document.querySelectorAll('[role="tab"]');
|
|
8677
|
+
var panels = document.querySelectorAll('.tab-panel');
|
|
8678
|
+
function activate(id) {
|
|
8679
|
+
if (!id) return;
|
|
8680
|
+
var found = false;
|
|
8681
|
+
panels.forEach(function (panel) { if (panel.id === id) found = true; });
|
|
8682
|
+
if (!found) return;
|
|
8683
|
+
tabs.forEach(function (tab) {
|
|
8684
|
+
tab.setAttribute("aria-selected", tab.getAttribute("data-tab") === id ? "true" : "false");
|
|
8685
|
+
});
|
|
8686
|
+
panels.forEach(function (panel) { panel.hidden = panel.id !== id; });
|
|
8687
|
+
}
|
|
8688
|
+
tabs.forEach(function (tab) {
|
|
8689
|
+
tab.addEventListener("click", function () {
|
|
8690
|
+
activate(tab.getAttribute("data-tab"));
|
|
8691
|
+
});
|
|
8692
|
+
});
|
|
8693
|
+
document.querySelectorAll("nav.toc a[href^='#']").forEach(function (link) {
|
|
8694
|
+
link.addEventListener("click", function () {
|
|
8695
|
+
activate((link.getAttribute("href") || "").replace(/^#/, ""));
|
|
8696
|
+
});
|
|
8697
|
+
});
|
|
8698
|
+
function fromHash() {
|
|
8699
|
+
activate((location.hash || "").replace(/^#/, ""));
|
|
8700
|
+
}
|
|
8701
|
+
window.addEventListener("hashchange", fromHash);
|
|
8702
|
+
fromHash();
|
|
8703
|
+
})();
|
|
8704
|
+
</script>
|
|
8705
|
+
</body>
|
|
8706
|
+
</html>
|
|
8707
|
+
`;
|
|
8708
|
+
}
|
|
8709
|
+
function isInsideCanonical(root, candidate) {
|
|
8710
|
+
const rel = relative2(root, candidate).split(sep2).join("/");
|
|
8711
|
+
return rel === "" || rel !== ".." && !rel.startsWith("../");
|
|
8712
|
+
}
|
|
8713
|
+
function outInsideVaultError(vault, out, message) {
|
|
8714
|
+
return err("HTML_RENDER_OUT_INSIDE_VAULT", { vault, out, message });
|
|
8715
|
+
}
|
|
8716
|
+
async function canonicalizeWithMissingSuffix(abs) {
|
|
8717
|
+
try {
|
|
8718
|
+
await lstat(abs);
|
|
8719
|
+
return await realpath(abs);
|
|
8720
|
+
} catch (error) {
|
|
8721
|
+
if (error.code !== "ENOENT") throw error;
|
|
8722
|
+
}
|
|
8723
|
+
const missing = [];
|
|
8724
|
+
let current = abs;
|
|
8725
|
+
while (true) {
|
|
8726
|
+
missing.unshift(basename(current));
|
|
8727
|
+
const parent = dirname9(current);
|
|
8728
|
+
try {
|
|
8729
|
+
await lstat(parent);
|
|
8730
|
+
return join33(await realpath(parent), ...missing);
|
|
8731
|
+
} catch (error) {
|
|
8732
|
+
if (error.code !== "ENOENT") throw error;
|
|
8733
|
+
if (parent === current) return join33(parent, ...missing);
|
|
8734
|
+
current = parent;
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8737
|
+
}
|
|
8738
|
+
async function resolveOutOutsideVault(vault, out) {
|
|
8739
|
+
let vaultReal;
|
|
8740
|
+
try {
|
|
8741
|
+
vaultReal = await realpath(vault);
|
|
8742
|
+
} catch (error) {
|
|
8743
|
+
return err("VAULT_PATH_INVALID", { message: "vault realpath failed", cause: String(error) });
|
|
8744
|
+
}
|
|
8745
|
+
const abs = resolve5(out);
|
|
8746
|
+
try {
|
|
8747
|
+
const info = await lstat(abs);
|
|
8748
|
+
if (info.isSymbolicLink()) {
|
|
8749
|
+
return outInsideVaultError(vault, out, "--out must be a regular path outside the vault");
|
|
8750
|
+
}
|
|
8751
|
+
} catch (error) {
|
|
8752
|
+
if (error.code !== "ENOENT") {
|
|
8753
|
+
return err("WRITE_FAILED", { path: abs, message: String(error) });
|
|
8754
|
+
}
|
|
8755
|
+
}
|
|
8756
|
+
let canonical;
|
|
8757
|
+
try {
|
|
8758
|
+
canonical = await canonicalizeWithMissingSuffix(abs);
|
|
8759
|
+
} catch (error) {
|
|
8760
|
+
return err("WRITE_FAILED", { path: abs, message: String(error) });
|
|
8761
|
+
}
|
|
8762
|
+
if (isInsideCanonical(vaultReal, canonical)) {
|
|
8763
|
+
return outInsideVaultError(vault, out, "--out must be outside the vault");
|
|
8764
|
+
}
|
|
8765
|
+
return ok(canonical);
|
|
8766
|
+
}
|
|
8767
|
+
async function runPageRenderHtml(input) {
|
|
8768
|
+
if (!input.out || input.out.trim().length === 0) {
|
|
8769
|
+
return {
|
|
8770
|
+
exitCode: ExitCode.USAGE,
|
|
8771
|
+
result: err("USAGE", { message: "--out is required" })
|
|
8772
|
+
};
|
|
8773
|
+
}
|
|
8774
|
+
const pagePath = await resolveExistingRegularFileInsideVault(input.vault, input.page);
|
|
8775
|
+
if (!pagePath.ok) {
|
|
8776
|
+
return {
|
|
8777
|
+
exitCode: pagePath.error === "FILE_NOT_FOUND" ? ExitCode.FILE_NOT_FOUND : ExitCode.VAULT_PATH_INVALID,
|
|
8778
|
+
result: pagePath
|
|
8779
|
+
};
|
|
8780
|
+
}
|
|
8781
|
+
const outPath = await resolveOutOutsideVault(input.vault, input.out);
|
|
8782
|
+
if (!outPath.ok) {
|
|
8783
|
+
const exitCode = outPath.error === "HTML_RENDER_OUT_INSIDE_VAULT" ? ExitCode.SCHEME_REJECTED : outPath.error === "WRITE_FAILED" ? ExitCode.WRITE_FAILED : ExitCode.VAULT_PATH_INVALID;
|
|
8784
|
+
return { exitCode, result: outPath };
|
|
8785
|
+
}
|
|
8786
|
+
let text;
|
|
8787
|
+
try {
|
|
8788
|
+
text = await readFile17(pagePath.data, "utf8");
|
|
8789
|
+
} catch {
|
|
8790
|
+
return {
|
|
8791
|
+
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
8792
|
+
result: err("FILE_NOT_FOUND", { path: input.page })
|
|
8793
|
+
};
|
|
8794
|
+
}
|
|
8795
|
+
const fm = extractFrontmatter(text);
|
|
8796
|
+
if (!fm.ok) {
|
|
8797
|
+
return {
|
|
8798
|
+
exitCode: ExitCode.INVALID_FRONTMATTER,
|
|
8799
|
+
result: fm
|
|
8800
|
+
};
|
|
8801
|
+
}
|
|
8802
|
+
if (fm.data.html_render !== true) {
|
|
8803
|
+
return {
|
|
8804
|
+
exitCode: ExitCode.HTML_RENDER_NOT_OPTED_IN,
|
|
8805
|
+
result: err("HTML_RENDER_NOT_OPTED_IN", {
|
|
8806
|
+
page: input.page,
|
|
8807
|
+
message: "page frontmatter must set html_render: true"
|
|
8808
|
+
})
|
|
8809
|
+
};
|
|
8810
|
+
}
|
|
8811
|
+
const split = splitFrontmatter(text);
|
|
8812
|
+
if (!split.ok) {
|
|
8813
|
+
return { exitCode: ExitCode.MISSING_CLOSING_DELIMITER, result: split };
|
|
8814
|
+
}
|
|
8815
|
+
const title = typeof fm.data.title === "string" ? fm.data.title : input.page;
|
|
8816
|
+
const html = renderPageHtml(title, split.data.body);
|
|
8817
|
+
const outAbs = outPath.data;
|
|
8818
|
+
try {
|
|
8819
|
+
await mkdir11(dirname9(outAbs), { recursive: true });
|
|
8820
|
+
} catch (error) {
|
|
8821
|
+
return {
|
|
8822
|
+
exitCode: ExitCode.WRITE_FAILED,
|
|
8823
|
+
result: err("WRITE_FAILED", { path: outAbs, message: String(error) })
|
|
8824
|
+
};
|
|
8825
|
+
}
|
|
8826
|
+
const written = await atomicWriteText(outAbs, html);
|
|
8827
|
+
if (!written.ok) {
|
|
8828
|
+
return { exitCode: ExitCode.WRITE_FAILED, result: written };
|
|
8829
|
+
}
|
|
8830
|
+
return {
|
|
8831
|
+
exitCode: ExitCode.OK,
|
|
8832
|
+
result: ok({
|
|
8833
|
+
page: input.page,
|
|
8834
|
+
out: outAbs,
|
|
8835
|
+
bytes: Buffer.byteLength(html, "utf8"),
|
|
8836
|
+
humanHint: `Wrote ${outAbs} (${Buffer.byteLength(html, "utf8")} bytes)`
|
|
8837
|
+
})
|
|
8838
|
+
};
|
|
8839
|
+
}
|
|
8840
|
+
|
|
8389
8841
|
// src/commands/project-page-publish.ts
|
|
8390
8842
|
import { realpathSync as realpathSync4 } from "fs";
|
|
8391
|
-
import { mkdir as
|
|
8392
|
-
import { dirname as
|
|
8843
|
+
import { mkdir as mkdir12, readFile as readFile18 } from "fs/promises";
|
|
8844
|
+
import { dirname as dirname11, join as join34, resolve as resolve7 } from "path";
|
|
8393
8845
|
|
|
8394
8846
|
// src/utils/architecture-page.ts
|
|
8395
8847
|
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
8396
|
-
import { dirname as
|
|
8848
|
+
import { dirname as dirname10, posix as posix2, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
8397
8849
|
var PROJECT_SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
8398
8850
|
var ARCH_TARGET_RE = /^projects\/([a-z0-9][a-z0-9-]*)\/architecture\/([a-z0-9][a-z0-9._-]*\.md)$/;
|
|
8399
8851
|
function invalidFrontmatter(target, issues) {
|
|
@@ -8439,15 +8891,15 @@ function assertArchitectureTargetInsideVault(vault, target, project) {
|
|
|
8439
8891
|
} catch {
|
|
8440
8892
|
return err("VAULT_PATH_INVALID", { target, message: "vault realpath failed" });
|
|
8441
8893
|
}
|
|
8442
|
-
const absolutePath =
|
|
8443
|
-
const parent =
|
|
8894
|
+
const absolutePath = resolve6(vaultReal, target);
|
|
8895
|
+
const parent = dirname10(absolutePath);
|
|
8444
8896
|
let parentReal;
|
|
8445
8897
|
try {
|
|
8446
8898
|
parentReal = realpathSync3(parent);
|
|
8447
8899
|
} catch {
|
|
8448
8900
|
return err("VAULT_PATH_INVALID", { target, message: "target parent realpath failed" });
|
|
8449
8901
|
}
|
|
8450
|
-
const parentRelative =
|
|
8902
|
+
const parentRelative = relative3(vaultReal, parentReal).split(sep3).join("/");
|
|
8451
8903
|
if (parentRelative === ".." || parentRelative.startsWith("../")) {
|
|
8452
8904
|
return err("VAULT_PATH_INVALID", { target, message: "target parent escapes vault" });
|
|
8453
8905
|
}
|
|
@@ -8561,7 +9013,7 @@ function validateLogNote(logNote) {
|
|
|
8561
9013
|
async function prepareProjectPagePublication(input) {
|
|
8562
9014
|
let content;
|
|
8563
9015
|
try {
|
|
8564
|
-
content = await
|
|
9016
|
+
content = await readFile18(input.draftPath, "utf8");
|
|
8565
9017
|
} catch (error) {
|
|
8566
9018
|
return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
|
|
8567
9019
|
}
|
|
@@ -8576,7 +9028,7 @@ async function prepareProjectPagePublication(input) {
|
|
|
8576
9028
|
}
|
|
8577
9029
|
const target = assertArchitectureTargetInsideVault(input.vault, input.target, input.project);
|
|
8578
9030
|
if (!target.ok) return target;
|
|
8579
|
-
if (
|
|
9031
|
+
if (resolve7(input.draftPath) === target.data.absolutePath || target.data.existingRealPath !== void 0 && draftRealPath === target.data.existingRealPath) {
|
|
8580
9032
|
return err("VAULT_PATH_INVALID", { message: "draft must not alias the final target" });
|
|
8581
9033
|
}
|
|
8582
9034
|
const isNew = target.data.existingRealPath === void 0;
|
|
@@ -8694,9 +9146,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
|
|
|
8694
9146
|
async updateIndicesLocked(prepared, vault, state, observe) {
|
|
8695
9147
|
const rendered = await renderProjectIndex(vault, prepared.page.project, { today: prepared.date });
|
|
8696
9148
|
if (!rendered.ok) return rendered;
|
|
8697
|
-
const knowledgePath =
|
|
9149
|
+
const knowledgePath = join34(vault, rendered.data.index_path);
|
|
8698
9150
|
try {
|
|
8699
|
-
await
|
|
9151
|
+
await mkdir12(dirname11(knowledgePath), { recursive: true });
|
|
8700
9152
|
} catch (error) {
|
|
8701
9153
|
return err("WRITE_FAILED", { message: String(error) });
|
|
8702
9154
|
}
|
|
@@ -8705,7 +9157,7 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
|
|
|
8705
9157
|
state.projectIndexUpdated = knowledgeWrite.data.changed;
|
|
8706
9158
|
if (state.projectIndexUpdated) state.changed.add(rendered.data.index_path);
|
|
8707
9159
|
try {
|
|
8708
|
-
const visibleIndex = await
|
|
9160
|
+
const visibleIndex = await readFile18(knowledgePath, "utf8");
|
|
8709
9161
|
if (visibleIndex !== rendered.data.text) {
|
|
8710
9162
|
return err("WRITE_FAILED", { message: "project knowledge.md verification failed" });
|
|
8711
9163
|
}
|
|
@@ -8725,9 +9177,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
|
|
|
8725
9177
|
}
|
|
8726
9178
|
let projectIndexChanged = true;
|
|
8727
9179
|
if (projectIndex.ok) {
|
|
8728
|
-
const indexPath =
|
|
9180
|
+
const indexPath = join34(vault, projectIndex.data.index_path);
|
|
8729
9181
|
try {
|
|
8730
|
-
const existing = await
|
|
9182
|
+
const existing = await readFile18(indexPath, "utf8");
|
|
8731
9183
|
projectIndexChanged = existing !== projectIndex.data.text || pageChanged;
|
|
8732
9184
|
} catch {
|
|
8733
9185
|
projectIndexChanged = true;
|
|
@@ -8887,7 +9339,7 @@ import { existsSync as existsSync11, writeFileSync as writeFileSync6, mkdirSync
|
|
|
8887
9339
|
import { createHash as createHash6 } from "crypto";
|
|
8888
9340
|
import { execSync as execSync3, spawn } from "child_process";
|
|
8889
9341
|
import { platform as platform2 } from "os";
|
|
8890
|
-
import { join as
|
|
9342
|
+
import { join as join35, resolve as resolvePath } from "path";
|
|
8891
9343
|
var MAINTENANCE_SCHEMA_VERSION = 1;
|
|
8892
9344
|
var MAINTENANCE_COMMAND = "snapshot-maintenance journal clear-stale";
|
|
8893
9345
|
var REPAIR_COMMAND = "snapshot-maintenance projection-conflict repair";
|
|
@@ -8952,7 +9404,7 @@ async function runSnapshotMaintenanceDryRun(input) {
|
|
|
8952
9404
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE"));
|
|
8953
9405
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
|
|
8954
9406
|
}
|
|
8955
|
-
if (!existsSync11(requested) || !existsSync11(
|
|
9407
|
+
if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
|
|
8956
9408
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO"));
|
|
8957
9409
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
|
|
8958
9410
|
}
|
|
@@ -9032,14 +9484,14 @@ async function resolveLiveVault(input) {
|
|
|
9032
9484
|
return resolved.ok ? canonicalize(resolved.data.path) : void 0;
|
|
9033
9485
|
}
|
|
9034
9486
|
function snapshotMaintenanceAuditLogPath(home) {
|
|
9035
|
-
const stateDir = platform2() === "darwin" ?
|
|
9036
|
-
return
|
|
9487
|
+
const stateDir = platform2() === "darwin" ? join35(home, "Library", "Application Support", "vault-sync") : join35(home, ".local", "state", "vault-sync");
|
|
9488
|
+
return join35(stateDir, "snapshot-maintenance-audit.jsonl");
|
|
9037
9489
|
}
|
|
9038
9490
|
function defaultAuditSink(home) {
|
|
9039
9491
|
return (event) => {
|
|
9040
9492
|
try {
|
|
9041
9493
|
const logPath = snapshotMaintenanceAuditLogPath(home);
|
|
9042
|
-
mkdirSync4(
|
|
9494
|
+
mkdirSync4(join35(logPath, ".."), { recursive: true });
|
|
9043
9495
|
appendFileSync(logPath, JSON.stringify(event) + "\n", { encoding: "utf8" });
|
|
9044
9496
|
} catch {
|
|
9045
9497
|
}
|
|
@@ -9187,9 +9639,9 @@ function nonMergeSequencerActive(repo) {
|
|
|
9187
9639
|
const gitDir = git(repo, ["rev-parse", "--absolute-git-dir"]);
|
|
9188
9640
|
if (!gitDir) return false;
|
|
9189
9641
|
for (const m of ["CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
|
|
9190
|
-
if (existsSync11(
|
|
9642
|
+
if (existsSync11(join35(gitDir, m))) return true;
|
|
9191
9643
|
}
|
|
9192
|
-
return existsSync11(
|
|
9644
|
+
return existsSync11(join35(gitDir, "rebase-merge")) || existsSync11(join35(gitDir, "rebase-apply"));
|
|
9193
9645
|
}
|
|
9194
9646
|
function extraUnmergedPaths(unmerged) {
|
|
9195
9647
|
const allow = new Set(PROJECTION_CONFLICT_ALLOWLIST);
|
|
@@ -9228,7 +9680,7 @@ async function authorizeRepairContext(input) {
|
|
|
9228
9680
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE", void 0, REPAIR_COMMAND));
|
|
9229
9681
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
|
|
9230
9682
|
}
|
|
9231
|
-
if (!existsSync11(requested) || !existsSync11(
|
|
9683
|
+
if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
|
|
9232
9684
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO", void 0, REPAIR_COMMAND));
|
|
9233
9685
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
|
|
9234
9686
|
}
|
|
@@ -9329,8 +9781,8 @@ async function performProjectionRepair(input, plan, audit, now) {
|
|
|
9329
9781
|
return { exitCode: ExitCode.WRITE_FAILED, result: rendered };
|
|
9330
9782
|
}
|
|
9331
9783
|
try {
|
|
9332
|
-
writeFileSync6(
|
|
9333
|
-
writeFileSync6(
|
|
9784
|
+
writeFileSync6(join35(plan.snapshot_worktree, "index.md"), rendered.data.index);
|
|
9785
|
+
writeFileSync6(join35(plan.snapshot_worktree, "log.md"), rendered.data.log);
|
|
9334
9786
|
gitStrict(plan.snapshot_worktree, ["add", "--", "index.md", "log.md"]);
|
|
9335
9787
|
gitStrict(plan.snapshot_worktree, [
|
|
9336
9788
|
"commit",
|
|
@@ -9438,7 +9890,7 @@ function releaseSnapshotFlock(handle) {
|
|
|
9438
9890
|
|
|
9439
9891
|
// src/commands/backup.ts
|
|
9440
9892
|
import { statSync as statSync3, readdirSync as readdirSync2, readFileSync as readFileSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
9441
|
-
import { join as
|
|
9893
|
+
import { join as join36, relative as relative4, dirname as dirname12 } from "path";
|
|
9442
9894
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
9443
9895
|
|
|
9444
9896
|
// src/utils/s3-client.ts
|
|
@@ -9462,11 +9914,11 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
9462
9914
|
function* walkMarkdown(dir, base) {
|
|
9463
9915
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
9464
9916
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
9465
|
-
const full =
|
|
9917
|
+
const full = join36(dir, entry.name);
|
|
9466
9918
|
if (entry.isDirectory()) {
|
|
9467
9919
|
yield* walkMarkdown(full, base);
|
|
9468
9920
|
} else if (entry.name.endsWith(".md")) {
|
|
9469
|
-
yield
|
|
9921
|
+
yield relative4(base, full).replace(/\\/g, "/");
|
|
9470
9922
|
}
|
|
9471
9923
|
}
|
|
9472
9924
|
}
|
|
@@ -9485,7 +9937,7 @@ async function runBackupSync(input) {
|
|
|
9485
9937
|
let failed = 0;
|
|
9486
9938
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
9487
9939
|
for (const relPath of files) {
|
|
9488
|
-
const absPath =
|
|
9940
|
+
const absPath = join36(input.vault, relPath);
|
|
9489
9941
|
const localStat = statSync3(absPath);
|
|
9490
9942
|
let needsUpload = true;
|
|
9491
9943
|
try {
|
|
@@ -9561,7 +10013,7 @@ async function runBackupRestore(input) {
|
|
|
9561
10013
|
const objects = list.Contents ?? [];
|
|
9562
10014
|
for (const obj of objects) {
|
|
9563
10015
|
if (!obj.Key) continue;
|
|
9564
|
-
const localPath =
|
|
10016
|
+
const localPath = join36(target, obj.Key);
|
|
9565
10017
|
try {
|
|
9566
10018
|
const localStat = statSync3(localPath);
|
|
9567
10019
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -9574,7 +10026,7 @@ async function runBackupRestore(input) {
|
|
|
9574
10026
|
const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
|
|
9575
10027
|
const body = await resp.Body?.transformToByteArray();
|
|
9576
10028
|
if (body) {
|
|
9577
|
-
mkdirSync5(
|
|
10029
|
+
mkdirSync5(dirname12(localPath), { recursive: true });
|
|
9578
10030
|
writeFileSync7(localPath, Buffer.from(body));
|
|
9579
10031
|
downloaded++;
|
|
9580
10032
|
}
|
|
@@ -9608,8 +10060,8 @@ async function runBackupRestore(input) {
|
|
|
9608
10060
|
|
|
9609
10061
|
// src/commands/status.ts
|
|
9610
10062
|
import { existsSync as existsSync12, statSync as statSync4 } from "fs";
|
|
9611
|
-
import { readFile as
|
|
9612
|
-
import { join as
|
|
10063
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
10064
|
+
import { join as join37 } from "path";
|
|
9613
10065
|
var FUSE_NO_MIRROR_NOTE = "FUSE vault with no read mirror - status scan may be slow";
|
|
9614
10066
|
async function runStatus(input) {
|
|
9615
10067
|
if (!existsSync12(input.vault)) {
|
|
@@ -9648,7 +10100,7 @@ async function runStatus(input) {
|
|
|
9648
10100
|
const compound = scan.data.compound.length;
|
|
9649
10101
|
let schemaVersion = "v1";
|
|
9650
10102
|
try {
|
|
9651
|
-
const schemaContent = await
|
|
10103
|
+
const schemaContent = await readFile19(join37(scanRoot, "SCHEMA.md"), "utf8");
|
|
9652
10104
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
9653
10105
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
9654
10106
|
} catch {
|
|
@@ -9716,25 +10168,25 @@ async function runStatus(input) {
|
|
|
9716
10168
|
|
|
9717
10169
|
// src/commands/copy-status.ts
|
|
9718
10170
|
import { existsSync as existsSync13, realpathSync as realpathSync5 } from "fs";
|
|
9719
|
-
import { isAbsolute as isAbsolute2, join as
|
|
10171
|
+
import { isAbsolute as isAbsolute2, join as join39, relative as relative6, resolve as resolve9 } from "path";
|
|
9720
10172
|
|
|
9721
10173
|
// src/utils/fetch-projection.ts
|
|
9722
|
-
import { isAbsolute, join as
|
|
10174
|
+
import { isAbsolute, join as join38, resolve as resolve8 } from "path";
|
|
9723
10175
|
function inspectConfiguredFetchProjection(home) {
|
|
9724
10176
|
if (!home) return { configured: false };
|
|
9725
|
-
const config = readSkillWikiConfig(
|
|
10177
|
+
const config = readSkillWikiConfig(join38(home, ".skillwiki", ".env"));
|
|
9726
10178
|
const configured = config["vault_sync.fetch_projection"];
|
|
9727
10179
|
if (!configured || configured === "none") return { configured: false };
|
|
9728
10180
|
if (!isAbsolute(configured)) {
|
|
9729
10181
|
return { configured: true, invalidDetail: "configured fetch projection path must be absolute" };
|
|
9730
10182
|
}
|
|
9731
|
-
return { configured: true, path:
|
|
10183
|
+
return { configured: true, path: resolve8(configured) };
|
|
9732
10184
|
}
|
|
9733
10185
|
|
|
9734
10186
|
// src/utils/live-drift.ts
|
|
9735
10187
|
import { createHash as createHash7 } from "crypto";
|
|
9736
|
-
import { readFile as
|
|
9737
|
-
import { relative as
|
|
10188
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
10189
|
+
import { relative as relative5, sep as sep4 } from "path";
|
|
9738
10190
|
|
|
9739
10191
|
// src/utils/git-promotion.ts
|
|
9740
10192
|
function normalizedRelativePath(path) {
|
|
@@ -9775,10 +10227,10 @@ async function measureAuthoritativeLiveDrift(liveVault, projection) {
|
|
|
9775
10227
|
let content = 0;
|
|
9776
10228
|
const livePromotable = /* @__PURE__ */ new Set();
|
|
9777
10229
|
for (const page of scan.data.allMarkdown) {
|
|
9778
|
-
const rel =
|
|
10230
|
+
const rel = relative5(liveVault, page.absPath).split(sep4).join("/");
|
|
9779
10231
|
if (!isGitPromotablePath(rel)) continue;
|
|
9780
10232
|
livePromotable.add(rel);
|
|
9781
|
-
const bytes = await
|
|
10233
|
+
const bytes = await readFile20(page.absPath);
|
|
9782
10234
|
if (projectionBlobs.get(rel) !== gitBlobOid(bytes)) content += 1;
|
|
9783
10235
|
}
|
|
9784
10236
|
for (const rel of projectionBlobs.keys()) {
|
|
@@ -9953,11 +10405,11 @@ function comparablePath(path) {
|
|
|
9953
10405
|
try {
|
|
9954
10406
|
return realpathSync5(path);
|
|
9955
10407
|
} catch {
|
|
9956
|
-
return
|
|
10408
|
+
return resolve9(path);
|
|
9957
10409
|
}
|
|
9958
10410
|
}
|
|
9959
10411
|
function pathsAreNested(left, right) {
|
|
9960
|
-
const rel =
|
|
10412
|
+
const rel = relative6(comparablePath(left), comparablePath(right));
|
|
9961
10413
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
9962
10414
|
}
|
|
9963
10415
|
function defaultCopyStatusDeps(input) {
|
|
@@ -9973,7 +10425,7 @@ function defaultCopyStatusDeps(input) {
|
|
|
9973
10425
|
const projectionWarning = selection.configured ? `configured fetch projection ignored for Git status; using live vault${projectionProblem ? ` (${projectionProblem})` : ""}` : void 0;
|
|
9974
10426
|
const invalidProjectionDetail = projectionProblem ?? "configured fetch projection is not a git repository";
|
|
9975
10427
|
const gitRootProblem = () => {
|
|
9976
|
-
if (!existsSync13(
|
|
10428
|
+
if (!existsSync13(join39(gitVault, ".git"))) {
|
|
9977
10429
|
return "vault is not a git repository";
|
|
9978
10430
|
}
|
|
9979
10431
|
return void 0;
|
|
@@ -10053,7 +10505,7 @@ function defaultCopyStatusDeps(input) {
|
|
|
10053
10505
|
detail: projectionProblem ?? "live drift requires a configured fetch projection"
|
|
10054
10506
|
};
|
|
10055
10507
|
}
|
|
10056
|
-
if (!existsSync13(
|
|
10508
|
+
if (!existsSync13(join39(configuredProjection, ".git"))) {
|
|
10057
10509
|
return {
|
|
10058
10510
|
unknown: true,
|
|
10059
10511
|
reason_code: "fetch_projection_invalid",
|
|
@@ -10073,8 +10525,8 @@ async function runCopyStatusCommand(input) {
|
|
|
10073
10525
|
}
|
|
10074
10526
|
|
|
10075
10527
|
// src/commands/seed.ts
|
|
10076
|
-
import { mkdir as
|
|
10077
|
-
import { join as
|
|
10528
|
+
import { mkdir as mkdir13, writeFile as writeFile7, stat as stat5 } from "fs/promises";
|
|
10529
|
+
import { join as join40 } from "path";
|
|
10078
10530
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10079
10531
|
var EXAMPLE_PAGES = {
|
|
10080
10532
|
"entities/example-project.md": `---
|
|
@@ -10143,29 +10595,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
10143
10595
|
`;
|
|
10144
10596
|
async function runSeed(input) {
|
|
10145
10597
|
try {
|
|
10146
|
-
await stat5(
|
|
10598
|
+
await stat5(join40(input.vault, "SCHEMA.md"));
|
|
10147
10599
|
} catch {
|
|
10148
10600
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
10149
10601
|
}
|
|
10150
10602
|
const created = [];
|
|
10151
10603
|
const skipped = [];
|
|
10152
10604
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
10153
|
-
const absPath =
|
|
10605
|
+
const absPath = join40(input.vault, relPath);
|
|
10154
10606
|
try {
|
|
10155
10607
|
await stat5(absPath);
|
|
10156
10608
|
skipped.push(relPath);
|
|
10157
10609
|
} catch {
|
|
10158
|
-
await
|
|
10610
|
+
await mkdir13(join40(absPath, ".."), { recursive: true });
|
|
10159
10611
|
await writeFile7(absPath, content, "utf8");
|
|
10160
10612
|
created.push(relPath);
|
|
10161
10613
|
}
|
|
10162
10614
|
}
|
|
10163
|
-
const rawPath =
|
|
10615
|
+
const rawPath = join40(input.vault, "raw", "articles", "example-source.md");
|
|
10164
10616
|
try {
|
|
10165
10617
|
await stat5(rawPath);
|
|
10166
10618
|
skipped.push("raw/articles/example-source.md");
|
|
10167
10619
|
} catch {
|
|
10168
|
-
await
|
|
10620
|
+
await mkdir13(join40(rawPath, ".."), { recursive: true });
|
|
10169
10621
|
await writeFile7(rawPath, EXAMPLE_RAW, "utf8");
|
|
10170
10622
|
created.push("raw/articles/example-source.md");
|
|
10171
10623
|
}
|
|
@@ -10188,9 +10640,9 @@ async function runSeed(input) {
|
|
|
10188
10640
|
}
|
|
10189
10641
|
|
|
10190
10642
|
// src/commands/canvas.ts
|
|
10191
|
-
import { readFile as
|
|
10643
|
+
import { readFile as readFile21, writeFile as writeFile8 } from "fs/promises";
|
|
10192
10644
|
import { existsSync as existsSync14 } from "fs";
|
|
10193
|
-
import { join as
|
|
10645
|
+
import { join as join41 } from "path";
|
|
10194
10646
|
var NODE_WIDTH = 240;
|
|
10195
10647
|
var NODE_HEIGHT = 60;
|
|
10196
10648
|
var COLUMN_SPACING = 400;
|
|
@@ -10268,7 +10720,7 @@ function buildCanvasEdges(adjacency) {
|
|
|
10268
10720
|
return edges;
|
|
10269
10721
|
}
|
|
10270
10722
|
async function runCanvasGenerate(input) {
|
|
10271
|
-
const graphPath = input.graphPath ??
|
|
10723
|
+
const graphPath = input.graphPath ?? join41(input.vault, ".skillwiki", "graph.json");
|
|
10272
10724
|
if (!existsSync14(graphPath)) {
|
|
10273
10725
|
return {
|
|
10274
10726
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -10280,7 +10732,7 @@ async function runCanvasGenerate(input) {
|
|
|
10280
10732
|
}
|
|
10281
10733
|
let raw;
|
|
10282
10734
|
try {
|
|
10283
|
-
raw = await
|
|
10735
|
+
raw = await readFile21(graphPath, "utf8");
|
|
10284
10736
|
} catch (e) {
|
|
10285
10737
|
return {
|
|
10286
10738
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -10306,7 +10758,7 @@ async function runCanvasGenerate(input) {
|
|
|
10306
10758
|
const nodes = buildCanvasNodes(paths);
|
|
10307
10759
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
10308
10760
|
const canvas = { nodes, edges };
|
|
10309
|
-
const outPath =
|
|
10761
|
+
const outPath = join41(input.vault, "vault-graph.canvas");
|
|
10310
10762
|
try {
|
|
10311
10763
|
await writeFile8(outPath, JSON.stringify(canvas, null, 2));
|
|
10312
10764
|
} catch (e) {
|
|
@@ -10451,11 +10903,11 @@ async function runSourceDisposition(input) {
|
|
|
10451
10903
|
|
|
10452
10904
|
// src/utils/source-disposal.ts
|
|
10453
10905
|
import { createHash as createHash8 } from "crypto";
|
|
10454
|
-
import { readFile as
|
|
10906
|
+
import { readFile as readFile23, unlink as unlink5 } from "fs/promises";
|
|
10455
10907
|
|
|
10456
10908
|
// src/utils/raw-asset-reference-index.ts
|
|
10457
|
-
import { readdir as readdir6, readFile as
|
|
10458
|
-
import { join as
|
|
10909
|
+
import { readdir as readdir6, readFile as readFile22 } from "fs/promises";
|
|
10910
|
+
import { join as join42, relative as relative7, sep as sep5 } from "path";
|
|
10459
10911
|
var PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "pdf", "mp3", "wav", "m4a", "mp4", "webm"]);
|
|
10460
10912
|
async function walkFiles(root, dir = root) {
|
|
10461
10913
|
let entries;
|
|
@@ -10466,9 +10918,9 @@ async function walkFiles(root, dir = root) {
|
|
|
10466
10918
|
}
|
|
10467
10919
|
const out = [];
|
|
10468
10920
|
for (const entry of entries) {
|
|
10469
|
-
const path =
|
|
10921
|
+
const path = join42(dir, entry.name);
|
|
10470
10922
|
if (entry.isDirectory()) out.push(...await walkFiles(root, path));
|
|
10471
|
-
else if (entry.isFile()) out.push(
|
|
10923
|
+
else if (entry.isFile()) out.push(relative7(root, path).split(sep5).join("/"));
|
|
10472
10924
|
}
|
|
10473
10925
|
return out;
|
|
10474
10926
|
}
|
|
@@ -10482,7 +10934,7 @@ function addInbound(map, target, source) {
|
|
|
10482
10934
|
map.set(target, refs);
|
|
10483
10935
|
}
|
|
10484
10936
|
async function buildRawAssetReferenceIndex(vault) {
|
|
10485
|
-
const assetRoot =
|
|
10937
|
+
const assetRoot = join42(vault, "raw", "assets");
|
|
10486
10938
|
const relativeAssets = await walkFiles(assetRoot);
|
|
10487
10939
|
const assets = relativeAssets.map((path) => `raw/assets/${path}`).sort((a, b) => a.localeCompare(b));
|
|
10488
10940
|
const assetSet = new Set(assets);
|
|
@@ -10498,7 +10950,7 @@ async function buildRawAssetReferenceIndex(vault) {
|
|
|
10498
10950
|
for (const page of scan.data.allMarkdown) {
|
|
10499
10951
|
let text;
|
|
10500
10952
|
try {
|
|
10501
|
-
text = await
|
|
10953
|
+
text = await readFile22(page.absPath, "utf8");
|
|
10502
10954
|
} catch {
|
|
10503
10955
|
continue;
|
|
10504
10956
|
}
|
|
@@ -10558,7 +11010,7 @@ async function planSourceDisposal(input) {
|
|
|
10558
11010
|
if (sensitive.length > 0) return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
|
|
10559
11011
|
const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
|
|
10560
11012
|
if (!target.ok) return err("RAW_DISPOSAL_TARGET_INVALID", { path: input.rawPath, cause: target });
|
|
10561
|
-
const bytes = await
|
|
11013
|
+
const bytes = await readFile23(target.data);
|
|
10562
11014
|
const size = bytes.byteLength;
|
|
10563
11015
|
let typedReferences = [];
|
|
10564
11016
|
let otherReferences = [];
|
|
@@ -10682,7 +11134,7 @@ async function runSourceDisposal(input) {
|
|
|
10682
11134
|
import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
|
|
10683
11135
|
import { execSync as nodeExecSync } from "child_process";
|
|
10684
11136
|
import { hostname as nodeHostname, platform as nodePlatform } from "os";
|
|
10685
|
-
import { join as
|
|
11137
|
+
import { join as join43 } from "path";
|
|
10686
11138
|
var SSH_TIMEOUT_MS = 15e3;
|
|
10687
11139
|
var TIMER_UNIT = "agent-memory-trends.timer";
|
|
10688
11140
|
var SERVICE_UNIT = "agent-memory-trends.service";
|
|
@@ -10912,7 +11364,7 @@ async function runFleetHealth(input) {
|
|
|
10912
11364
|
const home = resolveSkillwikiHome(input.home, env);
|
|
10913
11365
|
const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
|
|
10914
11366
|
const vault = input.vault ?? env.WIKI_PATH;
|
|
10915
|
-
const file = input.file ?? (vault ?
|
|
11367
|
+
const file = input.file ?? (vault ? join43(vault, FLEET_REL_PATH) : void 0);
|
|
10916
11368
|
if (!file) {
|
|
10917
11369
|
return {
|
|
10918
11370
|
exitCode: ExitCode.NO_VAULT_CONFIGURED,
|
|
@@ -10997,7 +11449,7 @@ async function runFleetHealth(input) {
|
|
|
10997
11449
|
|
|
10998
11450
|
// src/utils/auto-commit.ts
|
|
10999
11451
|
import { existsSync as existsSync16 } from "fs";
|
|
11000
|
-
import { isAbsolute as isAbsolute3, join as
|
|
11452
|
+
import { isAbsolute as isAbsolute3, join as join44, normalize } from "path";
|
|
11001
11453
|
function isValidLastOpPath(file) {
|
|
11002
11454
|
if (!file || typeof file !== "string") return false;
|
|
11003
11455
|
const trimmed = file.trim();
|
|
@@ -11018,7 +11470,7 @@ async function postCommit(vault, exitCode) {
|
|
|
11018
11470
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
11019
11471
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
11020
11472
|
if (autoCommit === "false") return;
|
|
11021
|
-
if (!existsSync16(
|
|
11473
|
+
if (!existsSync16(join44(vault, ".git"))) return;
|
|
11022
11474
|
const lastOps = readLastOp(vault);
|
|
11023
11475
|
if (lastOps.length === 0) return;
|
|
11024
11476
|
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
@@ -11066,7 +11518,7 @@ function preflight(error) {
|
|
|
11066
11518
|
}
|
|
11067
11519
|
async function runMcpAuthIssueHost(input) {
|
|
11068
11520
|
const isTty = input.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
11069
|
-
const
|
|
11521
|
+
const readFile24 = input.readFile ?? readFileSync17;
|
|
11070
11522
|
const writeFile9 = input.writeFile ?? writeFileSync8;
|
|
11071
11523
|
const stderrWrite = input.stderrWrite ?? ((s) => {
|
|
11072
11524
|
process.stderr.write(s);
|
|
@@ -11081,7 +11533,7 @@ async function runMcpAuthIssueHost(input) {
|
|
|
11081
11533
|
if (write && !isTty) return preflight("NO_TTY");
|
|
11082
11534
|
let yamlText = "";
|
|
11083
11535
|
try {
|
|
11084
|
-
yamlText =
|
|
11536
|
+
yamlText = readFile24(mapPath, "utf8");
|
|
11085
11537
|
} catch (error) {
|
|
11086
11538
|
if (!isEnoent(error)) throw error;
|
|
11087
11539
|
}
|
|
@@ -11199,8 +11651,8 @@ async function emit(r, vault, opts) {
|
|
|
11199
11651
|
printJson(r.result);
|
|
11200
11652
|
}
|
|
11201
11653
|
if (vault && opts?.postCommit !== false) await postCommit(vault, r.exitCode);
|
|
11202
|
-
await new Promise((
|
|
11203
|
-
process.stdout.write("", (error) => error ? reject(error) :
|
|
11654
|
+
await new Promise((resolve10, reject) => {
|
|
11655
|
+
process.stdout.write("", (error) => error ? reject(error) : resolve10());
|
|
11204
11656
|
});
|
|
11205
11657
|
process.exit(r.exitCode);
|
|
11206
11658
|
}
|
|
@@ -11263,7 +11715,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
|
|
|
11263
11715
|
if (dirty) {
|
|
11264
11716
|
return emit(dirty, void 0, { postCommit: false });
|
|
11265
11717
|
}
|
|
11266
|
-
const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-
|
|
11718
|
+
const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-2BMHZWVJ.js");
|
|
11267
11719
|
const run = await runManagedWriteTransaction2({
|
|
11268
11720
|
vault,
|
|
11269
11721
|
command,
|
|
@@ -11293,7 +11745,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
11293
11745
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
11294
11746
|
});
|
|
11295
11747
|
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) => {
|
|
11296
|
-
const out = opts.out ??
|
|
11748
|
+
const out = opts.out ?? join45(vault, ".skillwiki", "graph.json");
|
|
11297
11749
|
return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
|
|
11298
11750
|
});
|
|
11299
11751
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -11637,6 +12089,15 @@ pageCmd.command("publish <draft> [vault]").description("preview or publish an un
|
|
|
11637
12089
|
{ postCommit: false }
|
|
11638
12090
|
);
|
|
11639
12091
|
});
|
|
12092
|
+
pageCmd.command("render-html <page> [vault]").description("render an opted-in typed page to a self-contained HTML file").requiredOption("--out <file>", "output HTML path (must be outside the vault)").option("--wiki <name>", "wiki profile name").action(async (page, vault, opts) => {
|
|
12093
|
+
const resolved = await resolveVaultArg(vault, opts.wiki);
|
|
12094
|
+
if (!resolved.ok) return emit({ exitCode: resolved.exitCode, result: resolved.payload });
|
|
12095
|
+
return emit(await runPageRenderHtml({
|
|
12096
|
+
vault: resolved.vault,
|
|
12097
|
+
page,
|
|
12098
|
+
out: opts.out
|
|
12099
|
+
}), resolved.vault, { postCommit: false });
|
|
12100
|
+
});
|
|
11640
12101
|
var projectPageCmd = program.command("project-page").description("publish project-local architecture pages");
|
|
11641
12102
|
projectPageCmd.command("publish <draft> [vault]").description("preview or publish a Layer-3 architecture page under projects/{slug}/architecture/").requiredOption("--project <slug>", "project slug").requiredOption("--target <path>", "vault-relative architecture target").option("--log-note <text>", "single-line publication log note").option("--write", "publish SCHEMA, page, project knowledge index, and log", false).option("--approve <token>", "required with --write; token from a prior dry-run").option("--wiki <name>", "wiki profile name").action(async (draft, vault, opts) => {
|
|
11642
12103
|
const resolved = await resolveVaultArg(vault, opts.wiki);
|
|
@@ -11910,7 +12371,8 @@ program.command("doctor").description("diagnose skillwiki setup issues").option(
|
|
|
11910
12371
|
var clipOutboxCmd = program.command("clip-outbox").description("dedicated file-level web-clip outbox");
|
|
11911
12372
|
clipOutboxCmd.command("status").description("show whether the clip-outbox channel is configured").action(async () => emit(await runClipOutboxStatus(resolveSkillwikiHome())));
|
|
11912
12373
|
clipOutboxCmd.command("watch").description("run the enabled dedicated outbox worker using macOS FSEvents").action(async () => emit(await runClipOutboxWatch(resolveSkillwikiHome())));
|
|
11913
|
-
clipOutboxCmd.command("
|
|
12374
|
+
clipOutboxCmd.command("mirror-watch").description("copy admitted raw/articles and raw/assets from storage into the frozen mirror using MCP SSE").action(async () => emit(await runClipOutboxMirrorWatch(resolveSkillwikiHome())));
|
|
12375
|
+
clipOutboxCmd.command("install-or-migrate").description("refresh the packaged launchd plist without enabling the channel").option("--load", "attended clip watcher bootstrap; requires existing enabled=true", false).option("--load-mirror", "attended inbound SSE bootstrap; requires existing enabled=true; never auto-loaded on upgrade", false).option("--conditional-create-proven", "attest the deployed SeaweedFS If-None-Match:* race test passed", false).action(async (opts) => emit(await runClipOutboxInstall({ home: resolveSkillwikiHome(), load: opts.load, loadMirror: opts.loadMirror, conditionalCreateProven: opts.conditionalCreateProven })));
|
|
11914
12376
|
program.command("connect").description(CONNECT_DESCRIPTION).option("--from-file <path>", "chat-attached env file (not Drive / \u4E91\u76D8)").option("--from-stdin", "read env file from stdin (no echo)").option("--dry-run", "validate without writing ~/.skillwiki/.env").option("--force", "overwrite a different existing token").action(async (opts) => emit(await runConnect({
|
|
11915
12377
|
home: resolveSkillwikiHome(),
|
|
11916
12378
|
fromFile: opts.fromFile,
|
|
@@ -12409,7 +12871,7 @@ memoryCmd.command("import [vault]").description("preview or apply local memory i
|
|
|
12409
12871
|
maxBytes: opts.maxBytes
|
|
12410
12872
|
}), v.vault, { postCommit: false });
|
|
12411
12873
|
});
|
|
12412
|
-
program.command("ingest <source>").description("ingest a source URL or local file into the vault").requiredOption("--vault <path>", "vault root directory").requiredOption("--type <type>", "typed-knowledge type (entity|concept|comparison|query)").requiredOption("--title <title>", "page title").option("--tags <csv>", "comma-separated tags").option("--provenance <provenance>", "provenance (research|project)").option("--dry-run", "preview without writing files", false).action(async (source, opts) => {
|
|
12874
|
+
program.command("ingest <source>").description("ingest a source URL or local file into the vault").requiredOption("--vault <path>", "vault root directory").requiredOption("--type <type>", "typed-knowledge type (entity|concept|comparison|query)").requiredOption("--title <title>", "page title").option("--tags <csv>", "comma-separated tags").option("--provenance <provenance>", "provenance (research|project)").option("--html", "stamp html_render: true on the typed stub", false).option("--dry-run", "preview without writing files", false).action(async (source, opts) => {
|
|
12413
12875
|
const tags = typeof opts.tags === "string" ? opts.tags.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
|
|
12414
12876
|
if (opts.dryRun) {
|
|
12415
12877
|
emit(await runIngest({
|
|
@@ -12419,6 +12881,7 @@ program.command("ingest <source>").description("ingest a source URL or local fil
|
|
|
12419
12881
|
title: opts.title,
|
|
12420
12882
|
tags,
|
|
12421
12883
|
provenance: opts.provenance,
|
|
12884
|
+
html: !!opts.html,
|
|
12422
12885
|
dryRun: true
|
|
12423
12886
|
}), opts.vault);
|
|
12424
12887
|
return;
|
|
@@ -12433,6 +12896,7 @@ program.command("ingest <source>").description("ingest a source URL or local fil
|
|
|
12433
12896
|
title: opts.title,
|
|
12434
12897
|
tags,
|
|
12435
12898
|
provenance: opts.provenance,
|
|
12899
|
+
html: !!opts.html,
|
|
12436
12900
|
dryRun: false
|
|
12437
12901
|
})
|
|
12438
12902
|
);
|