skillwiki 0.10.111 → 0.10.112
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-TDBUJUYH.js → chunk-25OXNFDV.js} +42 -18
- package/dist/{chunk-6OQLBNKR.js → chunk-ABRYBO4T.js} +29 -4
- package/dist/{chunk-OJJBZDPR.js → chunk-APKDTEVV.js} +178 -210
- package/dist/{chunk-5IBLXGLY.js → chunk-CKQPUHM2.js} +2 -2
- package/dist/chunk-DOHWNPAZ.js +60 -0
- package/dist/{chunk-GQWCTJTU.js → chunk-EE7W3GLA.js} +1 -1
- package/dist/{chunk-KWEYA3OE.js → chunk-FCA3RRNF.js} +1 -1
- package/dist/{chunk-MRPJ7X7W.js → chunk-IR2DI4Y5.js} +3 -3
- package/dist/chunk-K3M6OSUW.js +77 -0
- package/dist/{chunk-I6IRJCUJ.js → chunk-KJG2CSYD.js} +2 -2
- package/dist/chunk-LOVLZNLX.js +111 -0
- package/dist/{chunk-HBJLFPQP.js → chunk-Q6EZF37H.js} +8 -8
- package/dist/{chunk-JQV5KNPT.js → chunk-RR6VDWSI.js} +3 -3
- package/dist/{chunk-PYPPG7RF.js → chunk-SKOLE6US.js} +1 -1
- package/dist/{chunk-X5GL2FCU.js → chunk-TKX6PSWZ.js} +1 -1
- package/dist/cli.js +77 -33
- package/dist/{index-projection-2K672CBO.js → index-projection-UNZG5UJZ.js} +5 -5
- package/dist/{journal-QU5IDXQF.js → journal-5IW3TZ5F.js} +3 -3
- package/dist/{managed-write-preflight-GRSHQYKC.js → managed-write-preflight-L7IRQ76C.js} +7 -7
- package/dist/mcp-auth-env-X5MI5IX6.js +25 -0
- package/dist/{migrate-PIB4QA54.js → migrate-RO4BJP5T.js} +6 -5
- package/dist/mirror-H5BTBAON.js +151 -0
- package/dist/runtime-VMFYW2K3.js +20 -0
- package/dist/skillwiki-mcp.js +14 -12
- package/dist/sources-AOYP6ROO.js +10 -0
- package/dist/{watcher-MHG46J3R.js → watcher-MGHQD3UI.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-init/SKILL.md +3 -0
- package/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/wiki-init/SKILL.md +3 -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/{chunk-UIEQIJXP.js → chunk-7POLZOFY.js} +42 -42
|
@@ -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-IR2DI4Y5.js";
|
|
8
|
+
import "./chunk-KJG2CSYD.js";
|
|
9
|
+
import "./chunk-TKX6PSWZ.js";
|
|
10
|
+
import "./chunk-EE7W3GLA.js";
|
|
11
|
+
import "./chunk-7POLZOFY.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-FCA3RRNF.js";
|
|
7
|
+
import "./chunk-EE7W3GLA.js";
|
|
8
|
+
import "./chunk-7POLZOFY.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-Q6EZF37H.js";
|
|
10
|
+
import "./chunk-CKQPUHM2.js";
|
|
11
|
+
import "./chunk-KJG2CSYD.js";
|
|
12
|
+
import "./chunk-TKX6PSWZ.js";
|
|
13
|
+
import "./chunk-EE7W3GLA.js";
|
|
14
|
+
import "./chunk-SKOLE6US.js";
|
|
15
|
+
import "./chunk-7POLZOFY.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-DOHWNPAZ.js";
|
|
13
|
+
import "./chunk-SKOLE6US.js";
|
|
14
|
+
import "./chunk-7POLZOFY.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-25OXNFDV.js";
|
|
10
|
+
import "./chunk-EE7W3GLA.js";
|
|
11
|
+
import "./chunk-ABRYBO4T.js";
|
|
12
|
+
import "./chunk-LOVLZNLX.js";
|
|
13
|
+
import "./chunk-SKOLE6US.js";
|
|
14
|
+
import "./chunk-7POLZOFY.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-ABRYBO4T.js";
|
|
10
|
+
import "./chunk-LOVLZNLX.js";
|
|
11
|
+
import "./chunk-SKOLE6US.js";
|
|
12
|
+
import "./chunk-7POLZOFY.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-APKDTEVV.js";
|
|
5
|
+
import "./chunk-RR6VDWSI.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-IR2DI4Y5.js";
|
|
8
|
+
import "./chunk-Q6EZF37H.js";
|
|
9
|
+
import "./chunk-CKQPUHM2.js";
|
|
10
|
+
import "./chunk-KJG2CSYD.js";
|
|
11
|
+
import "./chunk-TKX6PSWZ.js";
|
|
12
|
+
import "./chunk-DOHWNPAZ.js";
|
|
13
|
+
import "./chunk-25OXNFDV.js";
|
|
14
|
+
import "./chunk-EE7W3GLA.js";
|
|
15
|
+
import "./chunk-ABRYBO4T.js";
|
|
16
|
+
import "./chunk-LOVLZNLX.js";
|
|
17
|
+
import "./chunk-SKOLE6US.js";
|
|
18
|
+
import "./chunk-7POLZOFY.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-FCA3RRNF.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-EE7W3GLA.js";
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
assertPublicHttpsUrl
|
|
20
|
+
} from "./chunk-LOVLZNLX.js";
|
|
21
|
+
import "./chunk-7POLZOFY.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.112",
|
|
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.112",
|
|
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 |
|
|
@@ -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
|
|
@@ -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 |
|
|
@@ -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
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key><string>com.karlchow.skillwiki-mirror-sse</string>
|
|
6
|
+
<key>ProgramArguments</key>
|
|
7
|
+
<array>
|
|
8
|
+
<string>@NODE_BINARY@</string>
|
|
9
|
+
<string>@CLI_BINARY@</string>
|
|
10
|
+
<string>clip-outbox</string><string>mirror-watch</string>
|
|
11
|
+
</array>
|
|
12
|
+
<key>EnvironmentVariables</key>
|
|
13
|
+
<dict><key>HOME</key><string>@HOME@</string><key>SKILLWIKI_HOME</key><string>@HOME@</string></dict>
|
|
14
|
+
<key>KeepAlive</key><true/>
|
|
15
|
+
<key>RunAtLoad</key><true/>
|
|
16
|
+
<key>ThrottleInterval</key><integer>10</integer>
|
|
17
|
+
<key>StandardOutPath</key><string>@LOG_ROOT@/clip-mirror-sse.log</string>
|
|
18
|
+
<key>StandardErrorPath</key><string>@LOG_ROOT@/clip-mirror-sse-error.log</string>
|
|
19
|
+
</dict>
|
|
20
|
+
</plist>
|