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.
Files changed (40) hide show
  1. package/dist/{chunk-TDBUJUYH.js → chunk-25OXNFDV.js} +42 -18
  2. package/dist/{chunk-6OQLBNKR.js → chunk-ABRYBO4T.js} +29 -4
  3. package/dist/{chunk-OJJBZDPR.js → chunk-APKDTEVV.js} +178 -210
  4. package/dist/{chunk-5IBLXGLY.js → chunk-CKQPUHM2.js} +2 -2
  5. package/dist/chunk-DOHWNPAZ.js +60 -0
  6. package/dist/{chunk-GQWCTJTU.js → chunk-EE7W3GLA.js} +1 -1
  7. package/dist/{chunk-KWEYA3OE.js → chunk-FCA3RRNF.js} +1 -1
  8. package/dist/{chunk-MRPJ7X7W.js → chunk-IR2DI4Y5.js} +3 -3
  9. package/dist/chunk-K3M6OSUW.js +77 -0
  10. package/dist/{chunk-I6IRJCUJ.js → chunk-KJG2CSYD.js} +2 -2
  11. package/dist/chunk-LOVLZNLX.js +111 -0
  12. package/dist/{chunk-HBJLFPQP.js → chunk-Q6EZF37H.js} +8 -8
  13. package/dist/{chunk-JQV5KNPT.js → chunk-RR6VDWSI.js} +3 -3
  14. package/dist/{chunk-PYPPG7RF.js → chunk-SKOLE6US.js} +1 -1
  15. package/dist/{chunk-X5GL2FCU.js → chunk-TKX6PSWZ.js} +1 -1
  16. package/dist/cli.js +77 -33
  17. package/dist/{index-projection-2K672CBO.js → index-projection-UNZG5UJZ.js} +5 -5
  18. package/dist/{journal-QU5IDXQF.js → journal-5IW3TZ5F.js} +3 -3
  19. package/dist/{managed-write-preflight-GRSHQYKC.js → managed-write-preflight-L7IRQ76C.js} +7 -7
  20. package/dist/mcp-auth-env-X5MI5IX6.js +25 -0
  21. package/dist/{migrate-PIB4QA54.js → migrate-RO4BJP5T.js} +6 -5
  22. package/dist/mirror-H5BTBAON.js +151 -0
  23. package/dist/runtime-VMFYW2K3.js +20 -0
  24. package/dist/skillwiki-mcp.js +14 -12
  25. package/dist/sources-AOYP6ROO.js +10 -0
  26. package/dist/{watcher-MHG46J3R.js → watcher-MGHQD3UI.js} +20 -110
  27. package/package.json +1 -1
  28. package/skills/.claude-plugin/plugin.json +1 -1
  29. package/skills/.codex-plugin/plugin.json +1 -1
  30. package/skills/.cursor-plugin/plugin.json +1 -1
  31. package/skills/package.json +1 -1
  32. package/skills/skills/using-skillwiki/SKILL.md +1 -1
  33. package/skills/skills/wiki-init/SKILL.md +3 -0
  34. package/skills/using-skillwiki/SKILL.md +1 -1
  35. package/skills/wiki-init/SKILL.md +3 -0
  36. package/templates/launchd/com.karlchow.skillwiki-mirror-sse.plist +20 -0
  37. package/dist/chunk-I2J3IJB7.js +0 -54
  38. package/dist/runtime-GLMI5OCT.js +0 -15
  39. package/dist/sources-D2MGP246.js +0 -10
  40. package/dist/{chunk-UIEQIJXP.js → chunk-7POLZOFY.js} +42 -42
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ atomicWriteText
4
+ } from "./chunk-EE7W3GLA.js";
5
+ import {
6
+ PRODUCTION_MIRROR_LABEL,
3
7
  PRODUCTION_WATCHER_LABEL,
4
8
  evidencePath,
5
9
  expandUserPath,
@@ -7,25 +11,22 @@ import {
7
11
  nestedOrEqual,
8
12
  resolveClipOutboxConfig,
9
13
  validateRuntime
10
- } from "./chunk-6OQLBNKR.js";
14
+ } from "./chunk-ABRYBO4T.js";
11
15
  import {
12
16
  parseDotenvFile
13
- } from "./chunk-PYPPG7RF.js";
14
- import {
15
- atomicWriteText
16
- } from "./chunk-GQWCTJTU.js";
17
+ } from "./chunk-SKOLE6US.js";
17
18
 
18
19
  // src/clip-outbox/migrate.ts
19
20
  import { access, mkdir, readFile } from "fs/promises";
20
21
  import { constants } from "fs";
21
22
  import { basename, dirname, isAbsolute, join } from "path";
22
23
  import { execFileSync } from "child_process";
23
- var CLIP_MIGRATION_HINT = "Clip channel requires attended setup: run skillwiki clip-outbox install-or-migrate, then skillwiki doctor. Installation/update never sets clip_outbox.enabled=true or loads launchd. Prove deployed SeaweedFS If-None-Match:* create-only before operator enablement.";
24
+ var CLIP_MIGRATION_HINT = "Clip channel requires attended setup: run skillwiki clip-outbox install-or-migrate, then skillwiki doctor. Installation/update never sets clip_outbox.enabled=true or loads launchd. Prove deployed SeaweedFS If-None-Match:* create-only before --load. Inbound SSE uses a separate --load-mirror and never watches ~/wiki as clip input.";
24
25
  var xml = (s) => s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
25
- async function packagedPlist() {
26
+ async function packagedPlist(label = PRODUCTION_WATCHER_LABEL) {
26
27
  for (const rel of ["../templates/launchd/", "../../templates/launchd/"]) {
27
28
  try {
28
- return await readFile(new URL(`${rel}${PRODUCTION_WATCHER_LABEL}.plist`, import.meta.url), "utf8");
29
+ return await readFile(new URL(`${rel}${label}.plist`, import.meta.url), "utf8");
29
30
  } catch {
30
31
  }
31
32
  }
@@ -48,6 +49,7 @@ async function migrateClipOutbox(input) {
48
49
  const resolved = resolveClipOutboxConfig(map, input.home);
49
50
  if (!resolved.ok) throw new Error(resolved.message);
50
51
  if (input.load && (!resolved.enabled || !input.conditionalCreateProven)) throw new Error("--load requires existing clip_outbox.enabled=true and --conditional-create-proven after the operator's deployed create-only race test");
52
+ if (input.loadMirror && !resolved.enabled) throw new Error("--load-mirror requires existing clip_outbox.enabled=true; upgrades never auto-load the inbound SSE listener");
51
53
  if ((input.platform ?? process.platform) !== "darwin") throw new Error("clip launchd migration requires macOS");
52
54
  const rawRoot = map["clip_outbox.root"] ?? "~/Obsidian/clip-outbox";
53
55
  const root = resolved.enabled ? resolved.config.root : expandUserPath(rawRoot, input.home);
@@ -58,9 +60,16 @@ async function migrateClipOutbox(input) {
58
60
  if (!isAbsolute(binary) || !isAbsolute(input.nodePath ?? process.execPath)) throw new Error("absolute executable paths required");
59
61
  const logRoot = join(input.home, "Library", "Logs", "SkillWiki");
60
62
  const plistPath = join(input.home, "Library", "LaunchAgents", `${PRODUCTION_WATCHER_LABEL}.plist`);
61
- const template = await packagedPlist();
62
- let plist = template;
63
- for (const [key, value] of Object.entries({ NODE_BINARY: input.nodePath ?? process.execPath, CLI_BINARY: binary, OUTBOX_ROOT: root, HOME: input.home, LOG_ROOT: logRoot })) plist = plist.replaceAll(`@${key}@`, xml(value));
63
+ const mirrorPlistPath = join(input.home, "Library", "LaunchAgents", `${PRODUCTION_MIRROR_LABEL}.plist`);
64
+ const replacements = { NODE_BINARY: input.nodePath ?? process.execPath, CLI_BINARY: binary, OUTBOX_ROOT: root, HOME: input.home, LOG_ROOT: logRoot };
65
+ const render = async (label) => {
66
+ let plist = await packagedPlist(label);
67
+ for (const [key, value] of Object.entries(replacements)) plist = plist.replaceAll(`@${key}@`, xml(value));
68
+ if (label === PRODUCTION_MIRROR_LABEL && plist.includes("<key>WatchPaths</key>")) throw new Error("inbound SSE plist must not watch the frozen mirror");
69
+ return plist;
70
+ };
71
+ const watcherPlist = await render(PRODUCTION_WATCHER_LABEL);
72
+ const mirrorPlist = await render(PRODUCTION_MIRROR_LABEL);
64
73
  if (input.load) {
65
74
  if (!resolved.enabled) throw new Error("--load requires existing clip_outbox.enabled=true and --conditional-create-proven after the operator's deployed create-only race test");
66
75
  const creds = await loadClipCredentials(input.home, resolved.config);
@@ -71,22 +80,37 @@ async function migrateClipOutbox(input) {
71
80
  }
72
81
  await mkdir(dirname(plistPath), { recursive: true });
73
82
  await mkdir(logRoot, { recursive: true });
74
- const result = await atomicWriteText(plistPath, plist);
83
+ const result = await atomicWriteText(plistPath, watcherPlist);
75
84
  if (!result.ok) throw new Error("cannot refresh clip plist");
76
- if (input.load) {
85
+ const mirrorResult = await atomicWriteText(mirrorPlistPath, mirrorPlist);
86
+ if (!mirrorResult.ok) throw new Error("cannot refresh inbound SSE plist");
87
+ const bootstrap = (label, path) => {
77
88
  if (typeof process.getuid !== "function") throw new Error("clip launchd load requires macOS");
78
89
  const domain = `gui/${process.getuid()}`;
79
90
  const launch = input.launch ?? ((args) => {
80
91
  execFileSync("launchctl", args, { stdio: "ignore", timeout: 5e3 });
81
92
  });
82
93
  try {
83
- launch(["bootout", `${domain}/${PRODUCTION_WATCHER_LABEL}`]);
94
+ launch(["bootout", `${domain}/${label}`]);
84
95
  } catch {
85
96
  }
86
- launch(["enable", `${domain}/${PRODUCTION_WATCHER_LABEL}`]);
87
- launch(["bootstrap", domain, plistPath]);
88
- }
89
- return { plistPath, loaded: !!input.load, enabled: resolved.enabled, humanHint: `${result.data.changed ? "Refreshed" : "Current"} clip launchd plist: ${plistPath}. ${input.load ? "Operator-gated launchd bootstrap requested." : CLIP_MIGRATION_HINT}` };
97
+ launch(["enable", `${domain}/${label}`]);
98
+ launch(["bootstrap", domain, path]);
99
+ };
100
+ if (input.load) bootstrap(PRODUCTION_WATCHER_LABEL, plistPath);
101
+ if (input.loadMirror) bootstrap(PRODUCTION_MIRROR_LABEL, mirrorPlistPath);
102
+ const loadedBits = [
103
+ input.load ? "Operator-gated clip watcher bootstrap requested." : void 0,
104
+ input.loadMirror ? "Operator-gated inbound SSE bootstrap requested." : void 0
105
+ ].filter(Boolean).join(" ");
106
+ return {
107
+ plistPath,
108
+ mirrorPlistPath,
109
+ loaded: !!input.load,
110
+ mirrorLoaded: !!input.loadMirror,
111
+ enabled: resolved.enabled,
112
+ humanHint: `${result.data.changed ? "Refreshed" : "Current"} clip launchd plist: ${plistPath}. ${mirrorResult.data.changed ? "Refreshed" : "Current"} inbound SSE plist: ${mirrorPlistPath}. ${loadedBits || CLIP_MIGRATION_HINT}`
113
+ };
90
114
  }
91
115
  async function refreshInstalledClipPlist(home, opts) {
92
116
  const plistPath = join(home, "Library", "LaunchAgents", `${PRODUCTION_WATCHER_LABEL}.plist`);
@@ -1,17 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ defaultLookup
4
+ } from "./chunk-LOVLZNLX.js";
2
5
  import {
3
6
  parseDotenvFile,
4
7
  resolveSkillwikiHome
5
- } from "./chunk-PYPPG7RF.js";
8
+ } from "./chunk-SKOLE6US.js";
6
9
  import {
7
10
  CLIP_OUTBOX_DEFAULTS,
11
+ ipv4InCidr,
8
12
  isClipOutboxKey,
9
13
  parseClipOutboxKeyValue
10
- } from "./chunk-UIEQIJXP.js";
14
+ } from "./chunk-7POLZOFY.js";
11
15
 
12
16
  // src/clip-outbox/runtime.ts
13
17
  import { existsSync as existsSync2 } from "fs";
14
18
  import { lstat, readFile } from "fs/promises";
19
+ import { isIP } from "net";
15
20
  import { join as join2 } from "path";
16
21
  import { execFileSync } from "child_process";
17
22
  import { z } from "zod";
@@ -22,6 +27,7 @@ import { homedir } from "os";
22
27
  import { isAbsolute, join, resolve, sep } from "path";
23
28
  var CLIP_NOT_CONFIGURED = "clip channel not configured";
24
29
  var PRODUCTION_WATCHER_LABEL = CLIP_OUTBOX_DEFAULTS.watcher_label;
30
+ var PRODUCTION_MIRROR_LABEL = "com.karlchow.skillwiki-mirror-sse";
25
31
  var RETIRED_WATCHER_LABELS = [
26
32
  "com.karlchow.wiki-dev-outbox",
27
33
  "com.karlchow.wiki-dev-sse",
@@ -188,6 +194,23 @@ async function loadClipOutboxConfig(home) {
188
194
 
189
195
  // src/clip-outbox/runtime.ts
190
196
  var s3Schema = z.object({ endpoint: z.string().url(), region: z.string().default("us-east-1"), accessKeyId: z.string().min(1), secretAccessKey: z.string().min(1), sessionToken: z.string().optional() }).strict();
197
+ function isRfc1918OrLoopback(address) {
198
+ const version = isIP(address);
199
+ if (version === 4) {
200
+ return ipv4InCidr(address, "10.0.0.0", 8) || ipv4InCidr(address, "172.16.0.0", 12) || ipv4InCidr(address, "192.168.0.0", 16) || ipv4InCidr(address, "127.0.0.0", 8);
201
+ }
202
+ return version === 6 && address.toLowerCase() === "::1";
203
+ }
204
+ async function assertClipS3Endpoint(endpoint, lookup = defaultLookup) {
205
+ const url = new URL(endpoint);
206
+ if (url.username || url.password || url.search || url.hash) throw new Error("invalid clip S3 endpoint");
207
+ if (url.protocol === "https:") return;
208
+ if (url.protocol !== "http:") throw new Error("invalid clip S3 endpoint");
209
+ const addresses = isIP(url.hostname) ? [url.hostname] : await lookup(url.hostname);
210
+ if (addresses.length === 0 || !addresses.every(isRfc1918OrLoopback)) {
211
+ throw new Error("http clip S3 endpoint must resolve only to RFC1918 or loopback");
212
+ }
213
+ }
191
214
  async function loadClipCredentials(home, config, env = process.env) {
192
215
  let providers = {};
193
216
  const path = join2(home, ".skillwiki", "clip-outbox-credentials.json");
@@ -201,8 +224,7 @@ async function loadClipCredentials(home, config, env = process.env) {
201
224
  try {
202
225
  const rawS3 = env[config.s3CredentialRef] ?? providers[config.s3CredentialRef];
203
226
  const s3 = s3Schema.parse(typeof rawS3 === "string" ? JSON.parse(rawS3) : rawS3);
204
- const endpoint = new URL(s3.endpoint);
205
- if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error();
227
+ await assertClipS3Endpoint(s3.endpoint);
206
228
  const hostConfig = await parseDotenvFile(join2(home, ".skillwiki", ".env"));
207
229
  const readyToken = env[config.readyCredentialRef] ?? providers[config.readyCredentialRef];
208
230
  if (typeof readyToken !== "string" || !readyToken || config.readyCredentialRef === "SKILLWIKI_MCP_TOKEN" || readyToken === env.SKILLWIKI_MCP_TOKEN || readyToken === hostConfig.SKILLWIKI_MCP_TOKEN || config.readyCredentialRef === config.s3CredentialRef) throw new Error();
@@ -238,11 +260,14 @@ async function validateRuntime(home, config, endpoint) {
238
260
  export {
239
261
  CLIP_NOT_CONFIGURED,
240
262
  PRODUCTION_WATCHER_LABEL,
263
+ PRODUCTION_MIRROR_LABEL,
241
264
  RETIRED_WATCHER_LABELS,
242
265
  expandUserPath,
243
266
  nestedOrEqual,
244
267
  resolveClipOutboxConfig,
245
268
  loadClipOutboxConfig,
269
+ isRfc1918OrLoopback,
270
+ assertClipS3Endpoint,
246
271
  loadClipCredentials,
247
272
  evidencePath,
248
273
  conditionalCreateProven,