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
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
UNMANAGED_START,
|
|
5
5
|
renderRootIndex,
|
|
6
6
|
writeRootIndexProjection
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-IJWZDQDH.js";
|
|
8
|
+
import "./chunk-3TKNAOJH.js";
|
|
9
|
+
import "./chunk-JL2SCLXT.js";
|
|
10
|
+
import "./chunk-6VUK2FTX.js";
|
|
11
|
+
import "./chunk-VNSZCK5V.js";
|
|
12
12
|
export {
|
|
13
13
|
UNMANAGED_END,
|
|
14
14
|
UNMANAGED_START,
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
readJournal,
|
|
4
4
|
upsertJournal,
|
|
5
5
|
writeJournal
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-SEY4N4CT.js";
|
|
7
|
+
import "./chunk-6VUK2FTX.js";
|
|
8
|
+
import "./chunk-VNSZCK5V.js";
|
|
9
9
|
export {
|
|
10
10
|
readJournal,
|
|
11
11
|
upsertJournal,
|
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
runManagedWritePeerGate,
|
|
7
7
|
runManagedWritePreflight,
|
|
8
8
|
runManagedWriteTransaction
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-FQERKMD7.js";
|
|
10
|
+
import "./chunk-K3YZESND.js";
|
|
11
|
+
import "./chunk-3TKNAOJH.js";
|
|
12
|
+
import "./chunk-JL2SCLXT.js";
|
|
13
|
+
import "./chunk-6VUK2FTX.js";
|
|
14
|
+
import "./chunk-IJKJGQKL.js";
|
|
15
|
+
import "./chunk-VNSZCK5V.js";
|
|
16
16
|
export {
|
|
17
17
|
DEFAULT_MANAGED_WRITE_WAIT_MS,
|
|
18
18
|
MANAGED_WRITE_POLL_INTERVAL_MS,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MCP_HOST_ID_ENV,
|
|
4
|
+
MCP_TOKEN_ENV,
|
|
5
|
+
MCP_URL_ENV,
|
|
6
|
+
isMcpOnlyLeaf,
|
|
7
|
+
mcpAuthFromEnv,
|
|
8
|
+
mergeMcpAuthIntoEnv,
|
|
9
|
+
nonEmptyEnvValue,
|
|
10
|
+
redactMcpSecret,
|
|
11
|
+
resolveMcpAuthEnv
|
|
12
|
+
} from "./chunk-OGEAFXON.js";
|
|
13
|
+
import "./chunk-IJKJGQKL.js";
|
|
14
|
+
import "./chunk-VNSZCK5V.js";
|
|
15
|
+
export {
|
|
16
|
+
MCP_HOST_ID_ENV,
|
|
17
|
+
MCP_TOKEN_ENV,
|
|
18
|
+
MCP_URL_ENV,
|
|
19
|
+
isMcpOnlyLeaf,
|
|
20
|
+
mcpAuthFromEnv,
|
|
21
|
+
mergeMcpAuthIntoEnv,
|
|
22
|
+
nonEmptyEnvValue,
|
|
23
|
+
redactMcpSecret,
|
|
24
|
+
resolveMcpAuthEnv
|
|
25
|
+
};
|
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
refreshInstalledClipPlist,
|
|
7
7
|
stableCliBinary,
|
|
8
8
|
xml
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-XJ5VEIH7.js";
|
|
10
|
+
import "./chunk-6VUK2FTX.js";
|
|
11
|
+
import "./chunk-OWWWO2XS.js";
|
|
12
|
+
import "./chunk-5D5HFQ76.js";
|
|
13
|
+
import "./chunk-IJKJGQKL.js";
|
|
14
|
+
import "./chunk-VNSZCK5V.js";
|
|
14
15
|
export {
|
|
15
16
|
CLIP_MIGRATION_HINT,
|
|
16
17
|
migrateClipOutbox,
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
writeRegularExclusive
|
|
4
|
+
} from "./chunk-K3M6OSUW.js";
|
|
5
|
+
import {
|
|
6
|
+
parseDestination
|
|
7
|
+
} from "./chunk-ZRGZBAVS.js";
|
|
8
|
+
import "./chunk-QESNTTVY.js";
|
|
9
|
+
|
|
10
|
+
// src/clip-outbox/mirror.ts
|
|
11
|
+
import { mkdir, rmdir } from "fs/promises";
|
|
12
|
+
import { dirname, join } from "path";
|
|
13
|
+
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
|
14
|
+
var ADMIT = /^raw\/(articles|assets)\/[A-Za-z0-9._/-]+$/;
|
|
15
|
+
function admitMirrorPath(path) {
|
|
16
|
+
return ADMIT.test(path) && !path.split("/").some((part) => part === ".." || part === "." || !part);
|
|
17
|
+
}
|
|
18
|
+
function orderMirrorPaths(paths) {
|
|
19
|
+
const unique = [...new Set(paths.filter(admitMirrorPath))];
|
|
20
|
+
return [...unique.filter((path) => path.startsWith("raw/assets/")), ...unique.filter((path) => path.startsWith("raw/articles/"))];
|
|
21
|
+
}
|
|
22
|
+
function eventsUrlFromMcp(mcpUrl) {
|
|
23
|
+
const url = new URL(mcpUrl);
|
|
24
|
+
url.pathname = "/events";
|
|
25
|
+
url.search = "";
|
|
26
|
+
url.hash = "";
|
|
27
|
+
return url.toString();
|
|
28
|
+
}
|
|
29
|
+
function wikiFetchPullLockDir(home) {
|
|
30
|
+
return join(home, "Library", "Caches", "vault-sync", "wiki-fetch", "pull.lock.d");
|
|
31
|
+
}
|
|
32
|
+
async function acquireWikiFetchPullLock(home) {
|
|
33
|
+
const lockDir = wikiFetchPullLockDir(home);
|
|
34
|
+
await mkdir(dirname(lockDir), { recursive: true });
|
|
35
|
+
try {
|
|
36
|
+
await mkdir(lockDir);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code === "EEXIST") return null;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
return async () => {
|
|
42
|
+
await rmdir(lockDir).catch(() => void 0);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function parseChangedSseBlock(block) {
|
|
46
|
+
let event = "message";
|
|
47
|
+
let data = "";
|
|
48
|
+
for (const line of block.split(/\r?\n/)) {
|
|
49
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
50
|
+
else if (line.startsWith("data:")) data += line.slice(5).trimStart();
|
|
51
|
+
}
|
|
52
|
+
if (event !== "changed" || !data) return null;
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(data);
|
|
55
|
+
if (parsed.vault_id !== "central" || !Array.isArray(parsed.paths)) return null;
|
|
56
|
+
return { vault_id: "central", paths: parsed.paths.filter((path) => typeof path === "string") };
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function applyMirrorPaths(opts) {
|
|
62
|
+
const ordered = orderMirrorPaths(opts.paths);
|
|
63
|
+
const release = await opts.lock();
|
|
64
|
+
if (!release) return { copied: [], skipped: ordered };
|
|
65
|
+
try {
|
|
66
|
+
const copied = [];
|
|
67
|
+
const skipped = [];
|
|
68
|
+
for (const rel of ordered) {
|
|
69
|
+
const body = await opts.getObject(rel);
|
|
70
|
+
if (!body) {
|
|
71
|
+
skipped.push(rel);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const result = await opts.write(rel, body);
|
|
75
|
+
if (result === "conflict") skipped.push(rel);
|
|
76
|
+
else copied.push(rel);
|
|
77
|
+
}
|
|
78
|
+
return { copied, skipped };
|
|
79
|
+
} finally {
|
|
80
|
+
await release();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function createS3GetObject(opts) {
|
|
84
|
+
const prefix = opts.prefix ?? "";
|
|
85
|
+
const resolveKey = (rel) => [prefix, rel].filter(Boolean).join("/");
|
|
86
|
+
return async (key) => {
|
|
87
|
+
if (!admitMirrorPath(key)) return null;
|
|
88
|
+
try {
|
|
89
|
+
const response = await opts.client.send(new GetObjectCommand({ Bucket: opts.bucket, Key: resolveKey(key) }));
|
|
90
|
+
if (!response.Body) return null;
|
|
91
|
+
const bytes = Buffer.from(await response.Body.transformToByteArray());
|
|
92
|
+
return bytes.length > opts.maxBytes ? null : bytes;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const status = error.$metadata?.httpStatusCode;
|
|
95
|
+
if (status === 404 || error.name === "NoSuchKey") return null;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function startMirrorListener(input) {
|
|
101
|
+
const fetchImpl = input.fetchImpl ?? fetch;
|
|
102
|
+
const write = (rel, body) => writeRegularExclusive(input.config.mirrorRoot, rel, body);
|
|
103
|
+
const lock = input.lock ?? (() => acquireWikiFetchPullLock(input.home));
|
|
104
|
+
let delayMs = 1e3;
|
|
105
|
+
while (!input.signal.aborted) {
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetchImpl(input.eventsUrl, {
|
|
108
|
+
headers: { Authorization: `Bearer ${input.mcpToken}`, Accept: "text/event-stream" },
|
|
109
|
+
signal: input.signal
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok || !response.body) throw new Error("sse unavailable");
|
|
112
|
+
delayMs = 1e3;
|
|
113
|
+
let buffer = "";
|
|
114
|
+
const reader = response.body.getReader();
|
|
115
|
+
const decoder = new TextDecoder();
|
|
116
|
+
while (!input.signal.aborted) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done) break;
|
|
119
|
+
buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
|
|
120
|
+
let split;
|
|
121
|
+
while ((split = buffer.indexOf("\n\n")) >= 0) {
|
|
122
|
+
const block = buffer.slice(0, split);
|
|
123
|
+
buffer = buffer.slice(split + 2);
|
|
124
|
+
const changed = parseChangedSseBlock(block);
|
|
125
|
+
if (!changed) continue;
|
|
126
|
+
await applyMirrorPaths({ paths: changed.paths, getObject: input.getObject, write, lock });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (input.signal.aborted) return;
|
|
131
|
+
if (error.name === "AbortError") return;
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
133
|
+
delayMs = Math.min(delayMs * 2, 3e4);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function parseDestinationForMirror(destination) {
|
|
138
|
+
return parseDestination(destination);
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
acquireWikiFetchPullLock,
|
|
142
|
+
admitMirrorPath,
|
|
143
|
+
applyMirrorPaths,
|
|
144
|
+
createS3GetObject,
|
|
145
|
+
eventsUrlFromMcp,
|
|
146
|
+
orderMirrorPaths,
|
|
147
|
+
parseChangedSseBlock,
|
|
148
|
+
parseDestinationForMirror,
|
|
149
|
+
startMirrorListener,
|
|
150
|
+
wikiFetchPullLockDir
|
|
151
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertClipS3Endpoint,
|
|
4
|
+
conditionalCreateProven,
|
|
5
|
+
evidencePath,
|
|
6
|
+
isRfc1918OrLoopback,
|
|
7
|
+
loadClipCredentials,
|
|
8
|
+
validateRuntime
|
|
9
|
+
} from "./chunk-OWWWO2XS.js";
|
|
10
|
+
import "./chunk-5D5HFQ76.js";
|
|
11
|
+
import "./chunk-IJKJGQKL.js";
|
|
12
|
+
import "./chunk-VNSZCK5V.js";
|
|
13
|
+
export {
|
|
14
|
+
assertClipS3Endpoint,
|
|
15
|
+
conditionalCreateProven,
|
|
16
|
+
evidencePath,
|
|
17
|
+
isRfc1918OrLoopback,
|
|
18
|
+
loadClipCredentials,
|
|
19
|
+
validateRuntime
|
|
20
|
+
};
|
package/dist/skillwiki-mcp.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runSkillwikiMcpStdio
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-5RHR4FEK.js";
|
|
5
|
+
import "./chunk-CBT35FAW.js";
|
|
6
6
|
import "./chunk-7I2TPIV5.js";
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-IJWZDQDH.js";
|
|
8
|
+
import "./chunk-FQERKMD7.js";
|
|
9
|
+
import "./chunk-K3YZESND.js";
|
|
10
|
+
import "./chunk-3TKNAOJH.js";
|
|
11
|
+
import "./chunk-JL2SCLXT.js";
|
|
12
|
+
import "./chunk-OGEAFXON.js";
|
|
13
|
+
import "./chunk-XJ5VEIH7.js";
|
|
14
|
+
import "./chunk-6VUK2FTX.js";
|
|
15
|
+
import "./chunk-OWWWO2XS.js";
|
|
16
|
+
import "./chunk-5D5HFQ76.js";
|
|
17
|
+
import "./chunk-IJKJGQKL.js";
|
|
18
|
+
import "./chunk-VNSZCK5V.js";
|
|
17
19
|
|
|
18
20
|
// src/mcp-entry.ts
|
|
19
21
|
runSkillwikiMcpStdio().catch((error) => {
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
readJournal,
|
|
4
|
+
upsertJournal,
|
|
5
|
+
writeJournal
|
|
6
|
+
} from "./chunk-SEY4N4CT.js";
|
|
5
7
|
import {
|
|
6
8
|
buildReadyHint
|
|
7
9
|
} from "./chunk-OSL4J7GF.js";
|
|
8
10
|
import {
|
|
9
|
-
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-QESNTTVY.js";
|
|
11
|
+
readRegular
|
|
12
|
+
} from "./chunk-K3M6OSUW.js";
|
|
12
13
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import "./chunk-GQWCTJTU.js";
|
|
14
|
+
sha256Hex
|
|
15
|
+
} from "./chunk-ZRGZBAVS.js";
|
|
16
|
+
import "./chunk-QESNTTVY.js";
|
|
17
|
+
import "./chunk-6VUK2FTX.js";
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
assertPublicHttpsUrl
|
|
20
|
+
} from "./chunk-5D5HFQ76.js";
|
|
21
|
+
import "./chunk-VNSZCK5V.js";
|
|
22
22
|
|
|
23
23
|
// src/clip-outbox/watcher.ts
|
|
24
24
|
import { createRequire } from "module";
|
|
25
25
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
26
26
|
import { mkdir as mkdir2, readdir, rmdir, unlink, writeFile } from "fs/promises";
|
|
27
|
-
import { basename, join as join2, resolve as
|
|
27
|
+
import { basename, join as join2, resolve as resolve2 } from "path";
|
|
28
28
|
|
|
29
29
|
// src/clip-outbox/seal.ts
|
|
30
30
|
import { randomBytes } from "crypto";
|
|
31
|
-
import { mkdir, open
|
|
32
|
-
import { dirname, join, relative as
|
|
31
|
+
import { mkdir, open } from "fs/promises";
|
|
32
|
+
import { dirname, join, relative as relative2 } from "path";
|
|
33
33
|
import yaml from "js-yaml";
|
|
34
34
|
|
|
35
35
|
// src/clip-outbox/localize.ts
|
|
@@ -108,60 +108,6 @@ function localAssetRelPath(target) {
|
|
|
108
108
|
return void 0;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
// src/clip-outbox/ssrf.ts
|
|
112
|
-
import { isIP } from "net";
|
|
113
|
-
import { lookup as dnsLookup } from "dns/promises";
|
|
114
|
-
var extraBlockedV4 = [
|
|
115
|
-
["0.0.0.0", 8],
|
|
116
|
-
["100.64.0.0", 10],
|
|
117
|
-
["192.0.2.0", 24],
|
|
118
|
-
["198.18.0.0", 15],
|
|
119
|
-
["198.51.100.0", 24],
|
|
120
|
-
["203.0.113.0", 24],
|
|
121
|
-
["224.0.0.0", 4],
|
|
122
|
-
["240.0.0.0", 4]
|
|
123
|
-
];
|
|
124
|
-
function isBlockedAddress(address) {
|
|
125
|
-
if (isBlockedHost(address)) return true;
|
|
126
|
-
const version = isIP(address);
|
|
127
|
-
if (version === 4) {
|
|
128
|
-
return extraBlockedV4.some(([base, prefix]) => ipv4InCidr(address, base, prefix));
|
|
129
|
-
}
|
|
130
|
-
if (version === 6) {
|
|
131
|
-
const lower = address.toLowerCase();
|
|
132
|
-
return lower === "::" || lower === "::1" || lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe80:");
|
|
133
|
-
}
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
async function assertPublicHttpsUrl(url, lookup = defaultLookup) {
|
|
137
|
-
const guard = runFetchGuardSync({ url });
|
|
138
|
-
if (!guard.result.ok) {
|
|
139
|
-
return { ok: false, reason: guard.result.error };
|
|
140
|
-
}
|
|
141
|
-
let parsed;
|
|
142
|
-
try {
|
|
143
|
-
parsed = new URL(url);
|
|
144
|
-
} catch {
|
|
145
|
-
return { ok: false, reason: "MALFORMED_URL" };
|
|
146
|
-
}
|
|
147
|
-
if (isBlockedAddress(parsed.hostname)) {
|
|
148
|
-
return { ok: false, reason: "HOST_BLOCKED" };
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
const addresses = await lookup(parsed.hostname);
|
|
152
|
-
if (addresses.length === 0) return { ok: false, reason: "DNS_EMPTY" };
|
|
153
|
-
if (addresses.some(isBlockedAddress)) return { ok: false, reason: "HOST_BLOCKED" };
|
|
154
|
-
} catch {
|
|
155
|
-
return { ok: false, reason: "DNS_FAILED" };
|
|
156
|
-
}
|
|
157
|
-
return { ok: true, href: parsed.href };
|
|
158
|
-
}
|
|
159
|
-
async function defaultLookup(hostname) {
|
|
160
|
-
if (isIP(hostname)) return [hostname];
|
|
161
|
-
const v4 = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
162
|
-
return v4.map((row) => row.address);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
111
|
// src/clip-outbox/localize.ts
|
|
166
112
|
var LocalizeError = class extends Error {
|
|
167
113
|
constructor(message, code = "LOCALIZE_HELD") {
|
|
@@ -279,42 +225,6 @@ async function downloadImage(href, config, fetchImpl, lookup) {
|
|
|
279
225
|
throw new LocalizeError("too many redirects");
|
|
280
226
|
}
|
|
281
227
|
|
|
282
|
-
// src/clip-outbox/files.ts
|
|
283
|
-
import { constants } from "fs";
|
|
284
|
-
import { lstat, open, realpath } from "fs/promises";
|
|
285
|
-
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "path";
|
|
286
|
-
async function readRegular(root, path, maxBytes) {
|
|
287
|
-
const rel = relative2(root, path);
|
|
288
|
-
if (!rel || rel.startsWith(`..${sep}`) || rel === ".." || isAbsolute2(rel)) throw new Error("unsafe input path");
|
|
289
|
-
let current = resolve2(root);
|
|
290
|
-
for (const part of ["", ...rel.split(sep)]) {
|
|
291
|
-
current = resolve2(current, part);
|
|
292
|
-
const st = await lstat(current);
|
|
293
|
-
if (st.isSymbolicLink()) throw new Error("symlink input refused");
|
|
294
|
-
if (current !== path && !st.isDirectory()) throw new Error("invalid input parent");
|
|
295
|
-
}
|
|
296
|
-
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
297
|
-
try {
|
|
298
|
-
const before = await handle.stat();
|
|
299
|
-
if (!before.isFile() || before.size > maxBytes) throw new Error("input type or size refused");
|
|
300
|
-
const bytes = Buffer.alloc(before.size + 1);
|
|
301
|
-
let bytesRead = 0;
|
|
302
|
-
while (bytesRead < bytes.length) {
|
|
303
|
-
const chunk = await handle.read(bytes, bytesRead, bytes.length - bytesRead, bytesRead);
|
|
304
|
-
if (chunk.bytesRead === 0) break;
|
|
305
|
-
bytesRead += chunk.bytesRead;
|
|
306
|
-
}
|
|
307
|
-
const after = await handle.stat();
|
|
308
|
-
if (await realpath(path) !== path) throw new Error("input path changed during read");
|
|
309
|
-
const leaf = await lstat(path);
|
|
310
|
-
if (leaf.ino !== before.ino || leaf.dev !== before.dev) throw new Error("input identity changed during read");
|
|
311
|
-
if (bytesRead !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs) throw new Error("input changed during read");
|
|
312
|
-
return bytes.subarray(0, bytesRead);
|
|
313
|
-
} finally {
|
|
314
|
-
await handle.close();
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
228
|
// src/clip-outbox/seal.ts
|
|
319
229
|
var UnstableCapture = class extends Error {
|
|
320
230
|
};
|
|
@@ -325,7 +235,7 @@ function snapshotPath(config, entry, key) {
|
|
|
325
235
|
}
|
|
326
236
|
async function durableFile(root, path, bytes) {
|
|
327
237
|
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
328
|
-
const f = await
|
|
238
|
+
const f = await open(path, "wx", 384);
|
|
329
239
|
try {
|
|
330
240
|
await f.writeFile(bytes);
|
|
331
241
|
await f.sync();
|
|
@@ -334,7 +244,7 @@ async function durableFile(root, path, bytes) {
|
|
|
334
244
|
}
|
|
335
245
|
let dir = dirname(path);
|
|
336
246
|
while (true) {
|
|
337
|
-
const handle = await
|
|
247
|
+
const handle = await open(dir, "r");
|
|
338
248
|
try {
|
|
339
249
|
await handle.sync();
|
|
340
250
|
} finally {
|
|
@@ -383,7 +293,7 @@ async function sealCapture(config, entry, deps = {}) {
|
|
|
383
293
|
return { ...entry, sourceSha256: sha256Hex(article), assetKeys: [...objects.keys()].filter((k) => k !== entry.articleKey), sha256: Object.fromEntries([...objects].map(([k, b]) => [k, sha256Hex(b)])), sizes: Object.fromEntries([...objects].map(([k, b]) => [k, b.length])), receipts: {}, state: "sealed", reason: void 0, attempts: 0, retryAt: void 0, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
384
294
|
}
|
|
385
295
|
function admittedArticle(root, path) {
|
|
386
|
-
const rel =
|
|
296
|
+
const rel = relative2(root, path).replaceAll("\\", "/");
|
|
387
297
|
if (!rel.startsWith("raw/articles/") || !rel.endsWith(".md")) return false;
|
|
388
298
|
return rel.split("/").every((part) => part !== "." && part !== ".." && !part.startsWith(".") && !/[\x00-\x1f]/.test(part) && !part.endsWith("~"));
|
|
389
299
|
}
|
|
@@ -580,7 +490,7 @@ async function startClipWorker(config, deps) {
|
|
|
580
490
|
requestScan();
|
|
581
491
|
return;
|
|
582
492
|
}
|
|
583
|
-
const absolute =
|
|
493
|
+
const absolute = resolve2(path);
|
|
584
494
|
if (admittedArticle(config.root, absolute)) schedule(absolute);
|
|
585
495
|
else if (absolute.startsWith(join2(config.root, "raw/assets") + "/")) {
|
|
586
496
|
for (const entry of entries) if (!entry.sourceSha256 && entry.source) schedule(entry.source);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.113",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.113",
|
|
4
4
|
"description": "Project-aware Karpathy-style knowledge base: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by HTTP MCP wiki_capture.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "karlorz"
|
package/skills/package.json
CHANGED
|
@@ -183,7 +183,7 @@ If the vault has `.WIKI_GIT_FROZEN`:
|
|
|
183
183
|
## Skill Map
|
|
184
184
|
| Skill | When to Invoke |
|
|
185
185
|
|-------|----------------|
|
|
186
|
-
| `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows; `wiki_capture` is not clip ingress. |
|
|
186
|
+
| `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows including the inbound `com.karlchow.skillwiki-mirror-sse` listener, which upgrades never auto-load; `wiki_capture` is not clip ingress. |
|
|
187
187
|
| `wiki-ingest` | Convert URLs, files, or pasted text into typed-knowledge pages |
|
|
188
188
|
| `wiki-query` | Search typed knowledge by default; work-queue / doctor / MCP diagnose questions use `wiki_context` or `--scope work` / `--scope all`; explicitly requested fresh/raw evidence uses the separate pending channel |
|
|
189
189
|
| `wiki-lint` | Vault health and lint checks; use `health` for whole-system reports and `lint --summary` for bounded lint buckets |
|
|
@@ -42,6 +42,53 @@ Run `skillwiki lang` at the start. Generate page-body prose, narrative sections,
|
|
|
42
42
|
5. `skillwiki sources compile published <raw-path> [vault] --pages "<typed/a.md,typed/b.md>" --reason "<why>"` then `--write --approve <token>`
|
|
43
43
|
6. Leave the review `open` unless the human resolves it with `skillwiki sources review <raw-path> --status accepted|needs-fix|dismissed --reason "<why>"`
|
|
44
44
|
Goal, satellite, and headless sessions may list pending / compile status / reviews but must not claim or publish compile turns.
|
|
45
|
+
## Output standard v2
|
|
46
|
+
`skillwiki ingest` stamps `output_standard: v2` on every new typed stub. v2 is an authoring contract. CLI lint reports advisory warnings on marked pages only. Publish, health, and lint exit stay unchanged by these checks.
|
|
47
|
+
|
|
48
|
+
### Rung 1 — STE-80 (default)
|
|
49
|
+
STE is Simplified Technical English.
|
|
50
|
+
- Use at most 20 words for each instruction. Use at most 25 words for each description.
|
|
51
|
+
- One topic per paragraph. Each paragraph <= 6 sentences.
|
|
52
|
+
- One word, one meaning. Prefer simple verbs: use, start, stop, find, check.
|
|
53
|
+
- Keep technical terms (API, cache, Docker). Define each term once at first use.
|
|
54
|
+
- No hedges except in `## Uncertainty`. Never drop facts to shorten.
|
|
55
|
+
- Hedge list: might, may, perhaps, possibly, probably, likely, seems, appears to, arguably, somewhat, could be.
|
|
56
|
+
- Use `## Uncertainty` only for uncertainty the source itself reports. If the source reports none, omit the section or write "The source reports no open uncertainty."
|
|
57
|
+
|
|
58
|
+
### Rung 2 — diagrams (default when content has structure)
|
|
59
|
+
Add one Mermaid diagram when the page has flow, components, timeline, hierarchy, or comparison. Keep a text summary beside the diagram. Directly after the diagram, add `**Least certain:**` and a bullet list of labels or edges.
|
|
60
|
+
|
|
61
|
+
The `**Least certain:**` bullets name diagram labels or edges. State why that part of the diagram is the least certain representation. Use one of these reasons: an interpretive grouping, a simplified step, an omitted condition, or a mapping the source does not state directly.
|
|
62
|
+
|
|
63
|
+
Never add doubt about a fact the source states as settled. Never turn a decision, constraint, or policy into an open question.
|
|
64
|
+
|
|
65
|
+
If a diagram has fewer than 3 labels and edges in total, list all of them. Otherwise list the 3 least-certain labels or edges.
|
|
66
|
+
|
|
67
|
+
A diagram must not present a shortened executable procedure. For safety or recovery flows, keep all required steps or point the node to the full text procedure.
|
|
68
|
+
|
|
69
|
+
### Rung 3 — HTML (opt-in)
|
|
70
|
+
Set `html_render: true` by hand or with `skillwiki ingest --html`. Render outside the vault:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
skillwiki page render-html <page> [vault] --out <file>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
HTML is always opt-in. The renderer writes one self-contained file (TOC, tabs per H2, `<details>`, inline CSS and JS). Mermaid stays as readable source. No network fetch.
|
|
77
|
+
|
|
78
|
+
### Rung 4 — video
|
|
79
|
+
Explainer video is out of scope. Use STE text plus a diagram.
|
|
80
|
+
|
|
81
|
+
### Format table
|
|
82
|
+
|
|
83
|
+
| Content type | Format | Rung | Policy |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| Exact procedure (re-read later) | STE text | 1 | default |
|
|
86
|
+
| Relationships (structure / flow / hierarchy) | Mermaid diagram + text | 2 | default |
|
|
87
|
+
| Comparison / hands-on exploration | HTML interactive page | 3 | opt-in; otherwise STE + table or diagram |
|
|
88
|
+
| Concept from zero | Explainer video | 4 | out of scope; use STE + diagram |
|
|
89
|
+
|
|
90
|
+
Rules: formats stack; HTML is always opt-in; video is outside the current scope.
|
|
91
|
+
|
|
45
92
|
## Provenance defaults
|
|
46
93
|
- Default `provenance: research`.
|
|
47
94
|
- If cwd is inside `projects/{slug}/`, set `provenance: project` and add `provenance_projects: ["[[slug]]"]`.
|
|
@@ -41,6 +41,9 @@ None for the first run.
|
|
|
41
41
|
worker localizes required v1 HTTPS images before freeze, writes
|
|
42
42
|
`![[raw/assets/...]]` embeds, and uploads `raw/assets/<capture-id>/note.md`.
|
|
43
43
|
`wiki_capture` is not clip ingress. Never use `.txt` as the only index.
|
|
44
|
+
The inbound production SSE listener is a separate attended
|
|
45
|
+
`skillwiki clip-outbox install-or-migrate --load-mirror` job; package
|
|
46
|
+
upgrade never auto-loads it and never watches `~/wiki` as clip input.
|
|
44
47
|
8. **Suggest first sources.** Propose 3–5 initial sources (URLs, papers, articles) appropriate to the domain. Prompt the user to provide the first one to ingest, then hand off to wiki-ingest.
|
|
45
48
|
|
|
46
49
|
## Stop conditions
|
|
@@ -19,6 +19,13 @@ Standard four reads.
|
|
|
19
19
|
6. If `log_rotate_needed` is present and the user consents, run `skillwiki log-rotate <vault> --apply`. Otherwise leave alone.
|
|
20
20
|
7. **Post-migration verification**: If the user recently migrated **vault content** (e.g., moved entity/concept pages to another vault), re-run `skillwiki lint <vault> --summary` and verify that broken_wikilinks count decreased. Remaining broken links for migrated content indicate pages still referencing the moved files — these should be cleaned up (remove citations or migrate the referencing pages too). This step is content moves only — not HTTP MCP client/plugin upgrades (those use `skillwiki doctor --check-mcp`).
|
|
21
21
|
8. Append a `log.md` entry summarizing lint counts only when the user asked to record the maintenance result. Do not log routine `health` reports by default.
|
|
22
|
+
## Output standard v2 WARN buckets
|
|
23
|
+
These four buckets apply only to typed pages whose frontmatter has `output_standard: v2`. They are advisory WARN findings. They do not change the lint exit code, publish, health exit, or release CI on content.
|
|
24
|
+
- `ste_sentence_length`: prose sentences > 25 words; ordered-list items and list items under Steps/Procedure headings > 20 words.
|
|
25
|
+
- `ste_hedge_words`: whole-word hedges outside `## Uncertainty`, outside the `**Least certain:**` list, and outside code and blockquotes.
|
|
26
|
+
- `ste_diagram_uncertainty`: a Mermaid block with no `**Least certain:**` bullet list after it.
|
|
27
|
+
- `ste_comparison_visual`: a `type: comparison` page with neither a Markdown table nor a Mermaid block.
|
|
28
|
+
Drill with `skillwiki lint <vault> --only <bucket>`.
|
|
22
29
|
## Stop conditions
|
|
23
30
|
None — lint reports all findings even on per-page errors.
|
|
24
31
|
## Forbidden
|
|
@@ -183,7 +183,7 @@ If the vault has `.WIKI_GIT_FROZEN`:
|
|
|
183
183
|
## Skill Map
|
|
184
184
|
| Skill | When to Invoke |
|
|
185
185
|
|-------|----------------|
|
|
186
|
-
| `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows; `wiki_capture` is not clip ingress. |
|
|
186
|
+
| `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows including the inbound `com.karlchow.skillwiki-mirror-sse` listener, which upgrades never auto-load; `wiki_capture` is not clip ingress. |
|
|
187
187
|
| `wiki-ingest` | Convert URLs, files, or pasted text into typed-knowledge pages |
|
|
188
188
|
| `wiki-query` | Search typed knowledge by default; work-queue / doctor / MCP diagnose questions use `wiki_context` or `--scope work` / `--scope all`; explicitly requested fresh/raw evidence uses the separate pending channel |
|
|
189
189
|
| `wiki-lint` | Vault health and lint checks; use `health` for whole-system reports and `lint --summary` for bounded lint buckets |
|