skillwiki 0.10.109 → 0.10.111
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-JCDAEZVD.js → chunk-5IBLXGLY.js} +5 -3
- package/dist/chunk-6OQLBNKR.js +250 -0
- package/dist/chunk-GQWCTJTU.js +55 -0
- package/dist/{chunk-FYAKMULW.js → chunk-HBJLFPQP.js} +91 -198
- package/dist/chunk-I2J3IJB7.js +54 -0
- package/dist/{chunk-HMHMCPCE.js → chunk-I6IRJCUJ.js} +12 -57
- package/dist/{chunk-QU2IXZRN.js → chunk-JQV5KNPT.js} +8 -6
- package/dist/chunk-KWEYA3OE.js +60 -0
- package/dist/{chunk-DTTKCCQ4.js → chunk-MRPJ7X7W.js} +5 -3
- package/dist/{chunk-LDEZQXH6.js → chunk-OJJBZDPR.js} +306 -116
- package/dist/chunk-OSL4J7GF.js +32 -0
- package/dist/chunk-PYPPG7RF.js +126 -0
- package/dist/chunk-QESNTTVY.js +50 -0
- package/dist/chunk-TDBUJUYH.js +109 -0
- package/dist/{chunk-GJC2CI3W.js → chunk-UIEQIJXP.js} +108 -336
- package/dist/chunk-X5GL2FCU.js +344 -0
- package/dist/chunk-ZRGZBAVS.js +98 -0
- package/dist/cli.js +352 -297
- package/dist/{index-projection-ESBJOBS7.js → index-projection-2K672CBO.js} +5 -3
- package/dist/journal-QU5IDXQF.js +13 -0
- package/dist/{managed-write-preflight-MAIGWFQJ.js → managed-write-preflight-GRSHQYKC.js} +7 -4
- package/dist/migrate-PIB4QA54.js +21 -0
- package/dist/ready-S7F53Q2D.js +9 -0
- package/dist/runtime-GLMI5OCT.js +15 -0
- package/dist/skillwiki-mcp.js +12 -7
- package/dist/sources-D2MGP246.js +10 -0
- package/dist/upload-HTE62TOV.js +14 -0
- package/dist/watcher-MHG46J3R.js +616 -0
- package/docs/clip-outbox.md +119 -0
- package/package.json +5 -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 +12 -11
- package/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/wiki-init/SKILL.md +12 -11
- package/templates/launchd/com.karlchow.skillwiki-clip-outbox.plist +21 -0
- package/templates/web-clipper/llm-wiki-clippings.json +1 -1
- package/templates/web-clipper/readme.txt +13 -13
- package/dist/sources-QTQSZMST.js +0 -9
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/clip-outbox/ready.ts
|
|
4
|
+
function buildReadyHint(vaultId, files) {
|
|
5
|
+
const assets = files.filter((f) => f.path.startsWith("raw/assets/"));
|
|
6
|
+
const articles = files.filter((f) => f.path.startsWith("raw/articles/"));
|
|
7
|
+
return { vault_id: vaultId, files: [...assets, ...articles] };
|
|
8
|
+
}
|
|
9
|
+
async function postReadyHint(opts) {
|
|
10
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
11
|
+
const body = JSON.stringify(opts.hint);
|
|
12
|
+
if (Buffer.byteLength(body) > 32768) return { ok: false, status: 413 };
|
|
13
|
+
const res = await fetchImpl(opts.url, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${opts.token}`,
|
|
17
|
+
"Content-Type": "application/json"
|
|
18
|
+
},
|
|
19
|
+
body,
|
|
20
|
+
signal: AbortSignal.timeout(1e4),
|
|
21
|
+
redirect: "error"
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
24
|
+
const result = await res.json();
|
|
25
|
+
const paths = result.paths;
|
|
26
|
+
return result.ok === true && Array.isArray(paths) && opts.hint.files.every((f) => paths.includes(f.path)) ? { ok: true } : { ok: false, status: 503 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
buildReadyHint,
|
|
31
|
+
postReadyHint
|
|
32
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isClipOutboxKey,
|
|
4
|
+
isVaultSyncKey
|
|
5
|
+
} from "./chunk-UIEQIJXP.js";
|
|
6
|
+
|
|
7
|
+
// src/utils/home.ts
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
function resolveSkillwikiHome(home, env = process.env) {
|
|
11
|
+
for (const candidate of [home, env.HOME, env.USERPROFILE]) {
|
|
12
|
+
if (candidate?.trim()) return resolve(candidate);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const fallback = homedir();
|
|
16
|
+
if (fallback?.trim()) return resolve(fallback);
|
|
17
|
+
} catch {
|
|
18
|
+
throw new Error("Could not resolve SkillWiki home directory");
|
|
19
|
+
}
|
|
20
|
+
throw new Error("Could not resolve SkillWiki home directory");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/utils/dotenv.ts
|
|
24
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
25
|
+
import { dirname } from "path";
|
|
26
|
+
var CONFIG_KEYS = [
|
|
27
|
+
"WIKI_PATH",
|
|
28
|
+
"WIKI_LANG",
|
|
29
|
+
"SKILLWIKI_HOST_ID",
|
|
30
|
+
"AUTO_COMMIT",
|
|
31
|
+
"BACKUP_ENDPOINT",
|
|
32
|
+
"BACKUP_BUCKET",
|
|
33
|
+
"BACKUP_REGION",
|
|
34
|
+
"BACKUP_ACCESS_KEY_ID",
|
|
35
|
+
"BACKUP_SECRET_ACCESS_KEY"
|
|
36
|
+
];
|
|
37
|
+
var MCP_AUTH_KEYS = ["SKILLWIKI_MCP_TOKEN", "SKILLWIKI_MCP_URL"];
|
|
38
|
+
var _whitelist = /* @__PURE__ */ new Set([...CONFIG_KEYS, ...MCP_AUTH_KEYS]);
|
|
39
|
+
var PROFILE_PATH_RE = /^WIKI_([A-Z][A-Z0-9_]{0,31})_PATH$/;
|
|
40
|
+
var PROFILE_LANG_RE = /^WIKI_([A-Z][A-Z0-9_]{0,31})_LANG$/;
|
|
41
|
+
var PROFILE_DEFAULT_RE = /^WIKI_DEFAULT$/;
|
|
42
|
+
function isValidWikiProfileKey(key) {
|
|
43
|
+
if (key === "WIKI_PATH" || key === "WIKI_LANG") return false;
|
|
44
|
+
return PROFILE_PATH_RE.test(key) || PROFILE_LANG_RE.test(key) || PROFILE_DEFAULT_RE.test(key);
|
|
45
|
+
}
|
|
46
|
+
function profileKey(name, suffix) {
|
|
47
|
+
return `WIKI_${name.toUpperCase().replace(/-/g, "_").replace(/[^A-Z0-9_]/g, "")}_${suffix}`;
|
|
48
|
+
}
|
|
49
|
+
function parseDotenvText(text) {
|
|
50
|
+
const out = {};
|
|
51
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
52
|
+
const line = rawLine.trim();
|
|
53
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
54
|
+
const eq = line.indexOf("=");
|
|
55
|
+
if (eq <= 0) continue;
|
|
56
|
+
const key = line.slice(0, eq).trim();
|
|
57
|
+
const value = line.slice(eq + 1).trim();
|
|
58
|
+
if (!_whitelist.has(key) && !isValidWikiProfileKey(key) && !isVaultSyncKey(key) && !isClipOutboxKey(key)) continue;
|
|
59
|
+
if (value.length === 0) continue;
|
|
60
|
+
out[key] = value;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
async function parseDotenvFile(path) {
|
|
65
|
+
let text;
|
|
66
|
+
try {
|
|
67
|
+
text = await readFile(path, "utf8");
|
|
68
|
+
} catch {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
return parseDotenvText(text);
|
|
72
|
+
}
|
|
73
|
+
async function writeDotenv(filePath, entries, originalContent) {
|
|
74
|
+
const lines = originalContent !== void 0 ? updateLines(originalContent, entries) : freshLines(entries);
|
|
75
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
76
|
+
await writeFile(filePath, lines.join("\n") + "\n", "utf8");
|
|
77
|
+
}
|
|
78
|
+
function freshLines(entries) {
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
81
|
+
if (value !== void 0) out.push(`${key}=${value}`);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
function updateLines(originalContent, entries) {
|
|
86
|
+
let rawLines = originalContent.split(/\r?\n/);
|
|
87
|
+
if (rawLines.length > 0 && rawLines[rawLines.length - 1] === "") {
|
|
88
|
+
rawLines = rawLines.slice(0, -1);
|
|
89
|
+
}
|
|
90
|
+
const keysToWrite = new Set(Object.keys(entries));
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const line of rawLines) {
|
|
93
|
+
const trimmed = line.trim();
|
|
94
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) {
|
|
95
|
+
out.push(line);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const eq = trimmed.indexOf("=");
|
|
99
|
+
if (eq <= 0) {
|
|
100
|
+
out.push(line);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const key = trimmed.slice(0, eq).trim();
|
|
104
|
+
if (keysToWrite.has(key)) {
|
|
105
|
+
out.push(`${key}=${entries[key]}`);
|
|
106
|
+
keysToWrite.delete(key);
|
|
107
|
+
} else {
|
|
108
|
+
out.push(line);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const key of keysToWrite) {
|
|
112
|
+
const value = entries[key];
|
|
113
|
+
if (value !== void 0) out.push(`${key}=${value}`);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
resolveSkillwikiHome,
|
|
120
|
+
CONFIG_KEYS,
|
|
121
|
+
isValidWikiProfileKey,
|
|
122
|
+
profileKey,
|
|
123
|
+
parseDotenvText,
|
|
124
|
+
parseDotenvFile,
|
|
125
|
+
writeDotenv
|
|
126
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/delete-intent.ts
|
|
4
|
+
import { mkdir, writeFile, readdir, readFile } from "fs/promises";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var DELETE_INTENT_SCHEMA = "vault-delete-intent/v1";
|
|
7
|
+
var DELETE_INTENT_DIR = "meta/delete-intents";
|
|
8
|
+
function normalizeVaultRelPath(path) {
|
|
9
|
+
const p = path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
10
|
+
if (!p || p.includes("..") || p.startsWith(".git/")) {
|
|
11
|
+
throw new Error(`invalid vault-relative path: ${path}`);
|
|
12
|
+
}
|
|
13
|
+
return p;
|
|
14
|
+
}
|
|
15
|
+
function pathToIntentFilename(path) {
|
|
16
|
+
const p = normalizeVaultRelPath(path);
|
|
17
|
+
return `${p.replace(/\//g, "__")}.json`;
|
|
18
|
+
}
|
|
19
|
+
function intentHostId() {
|
|
20
|
+
return process.env.SKILLWIKI_HOST_ID ?? process.env.AGENT_HOST_ID ?? "unknown";
|
|
21
|
+
}
|
|
22
|
+
function buildDeleteIntent(input) {
|
|
23
|
+
return {
|
|
24
|
+
schema: DELETE_INTENT_SCHEMA,
|
|
25
|
+
path: normalizeVaultRelPath(input.path),
|
|
26
|
+
action: input.action,
|
|
27
|
+
created: input.created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
28
|
+
host: input.host ?? intentHostId(),
|
|
29
|
+
actor: input.actor,
|
|
30
|
+
reason: input.reason,
|
|
31
|
+
source: input.source,
|
|
32
|
+
expires: input.expires ?? null
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function writeDeleteIntent(vault, intent) {
|
|
36
|
+
const dir = join(vault, DELETE_INTENT_DIR);
|
|
37
|
+
await mkdir(dir, { recursive: true });
|
|
38
|
+
const rel = `${DELETE_INTENT_DIR}/${pathToIntentFilename(intent.path)}`;
|
|
39
|
+
await writeFile(join(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
|
|
40
|
+
return rel;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
DELETE_INTENT_SCHEMA,
|
|
45
|
+
DELETE_INTENT_DIR,
|
|
46
|
+
normalizeVaultRelPath,
|
|
47
|
+
pathToIntentFilename,
|
|
48
|
+
buildDeleteIntent,
|
|
49
|
+
writeDeleteIntent
|
|
50
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PRODUCTION_WATCHER_LABEL,
|
|
4
|
+
evidencePath,
|
|
5
|
+
expandUserPath,
|
|
6
|
+
loadClipCredentials,
|
|
7
|
+
nestedOrEqual,
|
|
8
|
+
resolveClipOutboxConfig,
|
|
9
|
+
validateRuntime
|
|
10
|
+
} from "./chunk-6OQLBNKR.js";
|
|
11
|
+
import {
|
|
12
|
+
parseDotenvFile
|
|
13
|
+
} from "./chunk-PYPPG7RF.js";
|
|
14
|
+
import {
|
|
15
|
+
atomicWriteText
|
|
16
|
+
} from "./chunk-GQWCTJTU.js";
|
|
17
|
+
|
|
18
|
+
// src/clip-outbox/migrate.ts
|
|
19
|
+
import { access, mkdir, readFile } from "fs/promises";
|
|
20
|
+
import { constants } from "fs";
|
|
21
|
+
import { basename, dirname, isAbsolute, join } from "path";
|
|
22
|
+
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 xml = (s) => s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
25
|
+
async function packagedPlist() {
|
|
26
|
+
for (const rel of ["../templates/launchd/", "../../templates/launchd/"]) {
|
|
27
|
+
try {
|
|
28
|
+
return await readFile(new URL(`${rel}${PRODUCTION_WATCHER_LABEL}.plist`, import.meta.url), "utf8");
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error("packaged clip launchd template missing");
|
|
33
|
+
}
|
|
34
|
+
async function stableCliBinary(argvPath = process.argv[1]) {
|
|
35
|
+
const candidates = [argvPath, ...[dirname(process.execPath), ...(process.env.PATH ?? "").split(":")].map((dir) => join(dir, "skillwiki"))];
|
|
36
|
+
for (const candidate of candidates) {
|
|
37
|
+
if (!candidate || !isAbsolute(candidate) || basename(candidate) !== "skillwiki") continue;
|
|
38
|
+
try {
|
|
39
|
+
await access(candidate, constants.X_OK);
|
|
40
|
+
return candidate;
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error("global npm skillwiki binary not found; install/link the CLI before migrating");
|
|
45
|
+
}
|
|
46
|
+
async function migrateClipOutbox(input) {
|
|
47
|
+
const map = await parseDotenvFile(join(input.home, ".skillwiki", ".env"));
|
|
48
|
+
const resolved = resolveClipOutboxConfig(map, input.home);
|
|
49
|
+
if (!resolved.ok) throw new Error(resolved.message);
|
|
50
|
+
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");
|
|
51
|
+
if ((input.platform ?? process.platform) !== "darwin") throw new Error("clip launchd migration requires macOS");
|
|
52
|
+
const rawRoot = map["clip_outbox.root"] ?? "~/Obsidian/clip-outbox";
|
|
53
|
+
const root = resolved.enabled ? resolved.config.root : expandUserPath(rawRoot, input.home);
|
|
54
|
+
if (!isAbsolute(root) || root.includes("\0") || rawRoot.split("/").includes("..")) throw new Error("invalid outbox root");
|
|
55
|
+
const mirror = resolved.enabled ? resolved.config.mirrorRoot : expandUserPath(map["clip_outbox.mirror_root"] ?? join(input.home, "wiki"), input.home);
|
|
56
|
+
if (nestedOrEqual(root, mirror)) throw new Error("outbox must be disjoint from mirror");
|
|
57
|
+
const binary = input.binaryPath ?? await stableCliBinary();
|
|
58
|
+
if (!isAbsolute(binary) || !isAbsolute(input.nodePath ?? process.execPath)) throw new Error("absolute executable paths required");
|
|
59
|
+
const logRoot = join(input.home, "Library", "Logs", "SkillWiki");
|
|
60
|
+
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));
|
|
64
|
+
if (input.load) {
|
|
65
|
+
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
|
+
const creds = await loadClipCredentials(input.home, resolved.config);
|
|
67
|
+
await mkdir(resolved.config.stateRoot, { recursive: true, mode: 448 });
|
|
68
|
+
const proof = await atomicWriteText(evidencePath(resolved.config), JSON.stringify({ verified: true, destination: resolved.config.destination, endpoint: creds.s3.endpoint, testedAt: (/* @__PURE__ */ new Date()).toISOString(), basis: "operator-attested deployed If-None-Match:* race test" }));
|
|
69
|
+
if (!proof.ok) throw new Error("cannot record operator conditional-create attestation");
|
|
70
|
+
await validateRuntime(input.home, resolved.config, creds.s3.endpoint);
|
|
71
|
+
}
|
|
72
|
+
await mkdir(dirname(plistPath), { recursive: true });
|
|
73
|
+
await mkdir(logRoot, { recursive: true });
|
|
74
|
+
const result = await atomicWriteText(plistPath, plist);
|
|
75
|
+
if (!result.ok) throw new Error("cannot refresh clip plist");
|
|
76
|
+
if (input.load) {
|
|
77
|
+
if (typeof process.getuid !== "function") throw new Error("clip launchd load requires macOS");
|
|
78
|
+
const domain = `gui/${process.getuid()}`;
|
|
79
|
+
const launch = input.launch ?? ((args) => {
|
|
80
|
+
execFileSync("launchctl", args, { stdio: "ignore", timeout: 5e3 });
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
launch(["bootout", `${domain}/${PRODUCTION_WATCHER_LABEL}`]);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
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}` };
|
|
90
|
+
}
|
|
91
|
+
async function refreshInstalledClipPlist(home, opts) {
|
|
92
|
+
const plistPath = join(home, "Library", "LaunchAgents", `${PRODUCTION_WATCHER_LABEL}.plist`);
|
|
93
|
+
try {
|
|
94
|
+
await access(plistPath, constants.F_OK);
|
|
95
|
+
} catch {
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
const result = await migrateClipOutbox({ ...opts, home, load: false });
|
|
99
|
+
return result.humanHint;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
CLIP_MIGRATION_HINT,
|
|
104
|
+
xml,
|
|
105
|
+
packagedPlist,
|
|
106
|
+
stableCliBinary,
|
|
107
|
+
migrateClipOutbox,
|
|
108
|
+
refreshInstalledClipPlist
|
|
109
|
+
};
|