@mulmoclaude/core 2.0.1 → 2.0.2
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/{atomic-C_7YpMiM.cjs → atomic-DhUk8uiM.cjs} +2 -2
- package/dist/{atomic-C_7YpMiM.cjs.map → atomic-DhUk8uiM.cjs.map} +1 -1
- package/dist/collection/registry/server/index.cjs +21 -26
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +18 -23
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/index.cjs +2 -2
- package/dist/collection/server/index.js +2 -2
- package/dist/collection-watchers/index.cjs +3 -3
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +3 -3
- package/dist/{discovery-DSLgRQhN.cjs → discovery-B_sHig7n.cjs} +5 -5
- package/dist/{discovery-DSLgRQhN.cjs.map → discovery-B_sHig7n.cjs.map} +1 -1
- package/dist/{discovery-BSedPW8A.js → discovery-BvnXyUel.js} +3 -3
- package/dist/{discovery-BSedPW8A.js.map → discovery-BvnXyUel.js.map} +1 -1
- package/dist/dist-CsgSfWwR.cjs.map +1 -1
- package/dist/dist-D8zokgGo.js.map +1 -1
- package/dist/feeds/paths.cjs +1 -1
- package/dist/feeds/server/index.cjs +5 -5
- package/dist/feeds/server/index.cjs.map +1 -1
- package/dist/feeds/server/index.js +4 -4
- package/dist/feeds/server/index.js.map +1 -1
- package/dist/file-change/index.cjs +1 -1
- package/dist/file-change/index.cjs.map +1 -1
- package/dist/files/index.cjs +3 -3
- package/dist/files/index.cjs.map +1 -1
- package/dist/google/index.cjs +7 -6
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.js +5 -4
- package/dist/google/index.js.map +1 -1
- package/dist/notifier/index.cjs +1 -1
- package/dist/notifier/index.js +1 -1
- package/dist/{notifier-CKFVVwDP.cjs → notifier-DZJj0sOh.cjs} +2 -2
- package/dist/{notifier-CKFVVwDP.cjs.map → notifier-DZJj0sOh.cjs.map} +1 -1
- package/dist/{notifier-CQD8NljP.js → notifier-DhnwR82U.js} +2 -2
- package/dist/{notifier-CQD8NljP.js.map → notifier-DhnwR82U.js.map} +1 -1
- package/dist/plugin-vue/i18n.cjs.map +1 -1
- package/dist/plugin-vue/index.cjs +39 -10
- package/dist/plugin-vue/index.cjs.map +1 -1
- package/dist/plugin-vue/index.js +39 -10
- package/dist/plugin-vue/index.js.map +1 -1
- package/dist/{relPath-CgGi4-Nv.cjs → relPath-CTAjGdCL.cjs} +2 -2
- package/dist/{relPath-CgGi4-Nv.cjs.map → relPath-CTAjGdCL.cjs.map} +1 -1
- package/dist/remote-host/server/index.cjs +3 -2
- package/dist/remote-host/server/index.cjs.map +1 -1
- package/dist/remote-host/server/index.js +3 -2
- package/dist/remote-host/server/index.js.map +1 -1
- package/dist/remote-host-CtQjagPt.cjs.map +1 -1
- package/dist/{rolldown-runtime-D6vf50IK.cjs → rolldown-runtime-VH7oDXx4.cjs} +1 -1
- package/dist/scheduler/index.cjs +6 -4
- package/dist/scheduler/index.cjs.map +1 -1
- package/dist/scheduler/index.js +5 -3
- package/dist/scheduler/index.js.map +1 -1
- package/dist/{server-B78f_bnY.js → server-DJt6USBQ.js} +11 -7
- package/dist/{server-B78f_bnY.js.map → server-DJt6USBQ.js.map} +1 -1
- package/dist/{server-By4oSql2.cjs → server-DoyGZMPS.cjs} +14 -10
- package/dist/{server-By4oSql2.cjs.map → server-DoyGZMPS.cjs.map} +1 -1
- package/dist/skill-bridge/index.cjs +1 -1
- package/dist/skill-bridge/index.cjs.map +1 -1
- package/dist/utils/fetch.cjs +1 -1
- package/dist/utils/fetch.cjs.map +1 -1
- package/dist/utils/fetch.js +1 -1
- package/dist/utils/fetch.js.map +1 -1
- package/dist/whisper/client.cjs.map +1 -1
- package/dist/whisper/client.js.map +1 -1
- package/dist/whisper/index.cjs +1 -1
- package/dist/whisper/index.cjs.map +1 -1
- package/dist/wiki/index.cjs +2 -1
- package/dist/wiki/index.cjs.map +1 -1
- package/dist/wiki/index.js +2 -1
- package/dist/wiki/index.js.map +1 -1
- package/dist/wiki/paths.cjs +1 -1
- package/dist/wiki/server/index.cjs +5 -3
- package/dist/wiki/server/index.cjs.map +1 -1
- package/dist/wiki/server/index.js +4 -2
- package/dist/wiki/server/index.js.map +1 -1
- package/dist/workspace-setup/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
2
2
|
let node_fs = require("node:fs");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
@@ -99,4 +99,4 @@ Object.defineProperty(exports, "writeFileAtomicSync", {
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
//# sourceMappingURL=atomic-
|
|
102
|
+
//# sourceMappingURL=atomic-DhUk8uiM.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-
|
|
1
|
+
{"version":3,"file":"atomic-DhUk8uiM.cjs","names":[],"sources":["../src/files/atomic.ts"],"sourcesContent":["// rename(2) is atomic on POSIX; Node's Windows fallback (copy+unlink) is still safer than truncating in place.\n// Readers always see either the old file or the new — never a half-written one.\n//\n// Single source of truth for atomic file writes across the whole monorepo\n// (host, core, plugins). Previously copy-pasted four times, so a Windows retry\n// fix landed in only one (#2399, precedent #2222).\n\nimport { mkdirSync, promises, renameSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\n\nexport interface WriteAtomicOptions {\n mode?: number;\n /** Give the staging file a random suffix so concurrent writers to the same\n * destination can't collide at the OS layer.\n *\n * Defaults to `true`, and opting out is almost always wrong. A shared\n * `${filePath}.tmp` means two writers of one file race: the second write\n * overwrites the first's staging file, or one rename/unlink pulls it out\n * from under the other, surfacing as `ENOENT … rename '<file>.tmp'`. That is\n * not theoretical — it fired in production on session meta, where ten\n * distinct callers (`setClaudeSessionId`, `backfillOrigin`,\n * `incrementUserQueryCount`, …) all write the same `<sessionId>.json`\n * (#2222).\n *\n * Pass `false` only when you specifically need the staging path to be\n * predictable (e.g. a single-writer token file, or a test that pre-creates\n * it to force a write failure). */\n uniqueTmp?: boolean;\n}\n\n// Unique staging names are the safe default: the cost is a random suffix, the\n// cost of the alternative is a lost update under concurrency (#2222).\nconst DEFAULT_UNIQUE_TMP = true;\n\nfunction tmpPathFor(filePath: string, uniqueTmp: boolean | undefined): string {\n return (uniqueTmp ?? DEFAULT_UNIQUE_TMP) ? `${filePath}.${randomBytes(6).toString(\"hex\")}.tmp` : `${filePath}.tmp`;\n}\n\n// On Windows, AV / Search Indexer / Defender briefly hold handles and rename trips EPERM/EBUSY/EACCES. Retry loop is\n// gated to Windows because POSIX EPERM means a real perm problem (read-only fs, sticky, cross-device) — retrying\n// just adds latency before the inevitable throw.\nconst IS_WINDOWS = process.platform === \"win32\";\nconst RENAME_RETRY_DELAYS_MS = [30, 100, 300] as const;\n\nfunction hasErrnoCode(err: unknown): err is { code: string } {\n return typeof err === \"object\" && err !== null && \"code\" in err && typeof err.code === \"string\";\n}\n\n// `isWindows` is a parameter (defaulting to the real platform) so the safety-critical decision is testable on any OS.\nexport function isTransientRenameError(err: unknown, isWindows: boolean = IS_WINDOWS): boolean {\n if (!isWindows || !hasErrnoCode(err)) return false;\n return err.code === \"EPERM\" || err.code === \"EBUSY\" || err.code === \"EACCES\";\n}\n\n// Injectable so a test can drive the retry path (fail-then-succeed rename, no-op sleep, isWindows=true) on any OS.\nexport interface RenameRetryDeps {\n rename: (fromPath: string, toPath: string) => Promise<void>;\n sleep: (millis: number) => Promise<void>;\n isWindows: boolean;\n}\n\nconst defaultRenameRetryDeps: RenameRetryDeps = {\n rename: (fromPath, toPath) => promises.rename(fromPath, toPath),\n sleep: (millis) => new Promise((resolve) => setTimeout(resolve, millis)),\n isWindows: IS_WINDOWS,\n};\n\nexport async function renameWithWindowsRetry(fromPath: string, toPath: string, deps: RenameRetryDeps = defaultRenameRetryDeps): Promise<void> {\n for (const delayMs of RENAME_RETRY_DELAYS_MS) {\n try {\n await deps.rename(fromPath, toPath);\n return;\n } catch (err) {\n if (!isTransientRenameError(err, deps.isWindows)) throw err;\n await deps.sleep(delayMs);\n }\n }\n // Final attempt — let any error propagate.\n await deps.rename(fromPath, toPath);\n}\n\n// Atomics.wait parks the thread instead of busy-spinning. Only on the Windows-rename retry path, total ≤ ~430ms.\nconst SYNC_SLEEP_BUF = new Int32Array(new SharedArrayBuffer(4));\nfunction sleepSync(millis: number): void {\n Atomics.wait(SYNC_SLEEP_BUF, 0, 0, millis);\n}\n\n// Deliberate async/sync twin of RenameRetryDeps: `sleep` blocks the thread instead of returning a Promise.\nexport interface RenameRetryDepsSync {\n rename: (fromPath: string, toPath: string) => void;\n sleep: (millis: number) => void;\n isWindows: boolean;\n}\n\nconst defaultRenameRetryDepsSync: RenameRetryDepsSync = {\n rename: (fromPath, toPath) => renameSync(fromPath, toPath),\n sleep: sleepSync,\n isWindows: IS_WINDOWS,\n};\n\nexport function renameSyncWithWindowsRetry(fromPath: string, toPath: string, deps: RenameRetryDepsSync = defaultRenameRetryDepsSync): void {\n for (const delayMs of RENAME_RETRY_DELAYS_MS) {\n try {\n deps.rename(fromPath, toPath);\n return;\n } catch (err) {\n if (!isTransientRenameError(err, deps.isWindows)) throw err;\n deps.sleep(delayMs);\n }\n }\n deps.rename(fromPath, toPath);\n}\n\n// Forcing utf-8 on a Uint8Array would re-encode the bytes — wrong for PNGs and other binary blobs.\nfunction writeOptionsFor(content: string | Uint8Array, mode: number | undefined): { encoding?: \"utf-8\" | undefined; mode?: number | undefined } {\n return typeof content === \"string\" ? { encoding: \"utf-8\", mode } : { mode };\n}\n\nexport async function writeFileAtomic(filePath: string, content: string | Uint8Array, opts: WriteAtomicOptions = {}): Promise<void> {\n const tmp = tmpPathFor(filePath, opts.uniqueTmp);\n await promises.mkdir(path.dirname(filePath), { recursive: true });\n try {\n await promises.writeFile(tmp, content, writeOptionsFor(content, opts.mode));\n await renameWithWindowsRetry(tmp, filePath);\n } catch (err) {\n await promises.unlink(tmp).catch(() => {});\n throw err;\n }\n}\n\nexport function writeFileAtomicSync(filePath: string, content: string | Uint8Array, opts: WriteAtomicOptions = {}): void {\n const tmp = tmpPathFor(filePath, opts.uniqueTmp);\n mkdirSync(path.dirname(filePath), { recursive: true });\n try {\n writeFileSync(tmp, content, writeOptionsFor(content, opts.mode));\n renameSyncWithWindowsRetry(tmp, filePath);\n } catch (err) {\n try {\n unlinkSync(tmp);\n } catch {\n // best-effort cleanup\n }\n throw err;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_rolldown_runtime = require("../../../rolldown-runtime-
|
|
2
|
+
const require_rolldown_runtime = require("../../../rolldown-runtime-VH7oDXx4.cjs");
|
|
3
3
|
const require_dist = require("../../../dist-CsgSfWwR.cjs");
|
|
4
|
-
const require_atomic = require("../../../atomic-
|
|
5
|
-
const require_relPath = require("../../../relPath-
|
|
4
|
+
const require_atomic = require("../../../atomic-DhUk8uiM.cjs");
|
|
5
|
+
const require_relPath = require("../../../relPath-CTAjGdCL.cjs");
|
|
6
6
|
const require_utils_fetch = require("../../../utils/fetch.cjs");
|
|
7
|
-
const require_discovery = require("../../../discovery-
|
|
7
|
+
const require_discovery = require("../../../discovery-B_sHig7n.cjs");
|
|
8
8
|
const require_templatePath = require("../../../templatePath-27vUZowm.cjs");
|
|
9
|
-
const require_server = require("../../../server-
|
|
9
|
+
const require_server = require("../../../server-DoyGZMPS.cjs");
|
|
10
10
|
const require_skill_bridge_index = require("../../../skill-bridge/index.cjs");
|
|
11
11
|
const require_types = require("../../../types-CNqkLT4M.cjs");
|
|
12
12
|
let node_fs = require("node:fs");
|
|
@@ -145,7 +145,8 @@ async function loadFromNetwork(descriptor) {
|
|
|
145
145
|
status: STATUS_BAD_GATEWAY$2,
|
|
146
146
|
error: `registry responded ${res.status}`
|
|
147
147
|
};
|
|
148
|
-
const
|
|
148
|
+
const json = await res.json().catch(() => null);
|
|
149
|
+
const parsed = require_types.parseRegistryIndex(json, descriptor.name);
|
|
149
150
|
if (!parsed.ok) {
|
|
150
151
|
require_discovery.log.warn("collections-registry", "index invalid", {
|
|
151
152
|
registry: descriptor.name,
|
|
@@ -626,7 +627,8 @@ function bridgeAllowlistFiles(bundle) {
|
|
|
626
627
|
* mirror files left over from a previous install so we can prune them after
|
|
627
628
|
* writing the new ones. */
|
|
628
629
|
async function listFilesRecursive(root, prefix = "") {
|
|
629
|
-
const
|
|
630
|
+
const dir = prefix ? node_path.default.join(root, ...prefix.split("/")) : root;
|
|
631
|
+
const entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }).catch(() => []);
|
|
630
632
|
const out = [];
|
|
631
633
|
for (const entry of entries) {
|
|
632
634
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
@@ -935,35 +937,28 @@ var DOUBLE_QUOTE_ESCAPES = {
|
|
|
935
937
|
t: " "
|
|
936
938
|
};
|
|
937
939
|
var unescapeDoubleQuote = (next) => DOUBLE_QUOTE_ESCAPES[next] ?? next;
|
|
938
|
-
function
|
|
940
|
+
function scanQuotedScalar(value, quote, readEscape) {
|
|
939
941
|
const out = [];
|
|
940
942
|
for (let i = 1; i < value.length; i += 1) {
|
|
941
|
-
const
|
|
942
|
-
if (
|
|
943
|
-
out.push(
|
|
943
|
+
const escaped = readEscape(value, i);
|
|
944
|
+
if (escaped !== null) {
|
|
945
|
+
out.push(escaped);
|
|
944
946
|
i += 1;
|
|
945
947
|
continue;
|
|
946
948
|
}
|
|
947
|
-
|
|
949
|
+
const char = value.charAt(i);
|
|
950
|
+
if (char === quote) return isOnlyTrailingComment(value.slice(i + 1)) ? out.join("") : null;
|
|
948
951
|
out.push(char);
|
|
949
952
|
}
|
|
950
953
|
return null;
|
|
951
954
|
}
|
|
955
|
+
var readDoubleQuoteEscape = (value, index) => value.charAt(index) === "\\" && index + 1 < value.length ? unescapeDoubleQuote(value.charAt(index + 1)) : null;
|
|
956
|
+
var readSingleQuoteEscape = (value, index) => value.charAt(index) === "'" && value[index + 1] === "'" ? "'" : null;
|
|
957
|
+
function parseDoubleQuoted(value) {
|
|
958
|
+
return scanQuotedScalar(value, "\"", readDoubleQuoteEscape);
|
|
959
|
+
}
|
|
952
960
|
function parseSingleQuoted(value) {
|
|
953
|
-
|
|
954
|
-
for (let i = 1; i < value.length; i += 1) {
|
|
955
|
-
const char = value.charAt(i);
|
|
956
|
-
if (char === "'") {
|
|
957
|
-
if (value[i + 1] === "'") {
|
|
958
|
-
out.push("'");
|
|
959
|
-
i += 1;
|
|
960
|
-
continue;
|
|
961
|
-
}
|
|
962
|
-
return isOnlyTrailingComment(value.slice(i + 1)) ? out.join("") : null;
|
|
963
|
-
}
|
|
964
|
-
out.push(char);
|
|
965
|
-
}
|
|
966
|
-
return null;
|
|
961
|
+
return scanQuotedScalar(value, "'", readSingleQuoteEscape);
|
|
967
962
|
}
|
|
968
963
|
function stripPlainComment(value) {
|
|
969
964
|
for (let i = 0; i < value.length; i += 1) if (value[i] === "#" && (i === 0 || isYamlSpace(value[i - 1]))) return value.slice(0, i);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/collection/registry/server/registriesConfig.ts","../../../../src/collection/registry/server/client.ts","../../../../src/collection/registry/server/collectionFiles.ts","../../../../src/collection/registry/server/importCollection.ts","../../../../src/collection/registry/server/importWriter.ts","../../../../src/collection/registry/server/exportCollection.ts","../../../../src/collection/registry/server/skillDescription.ts","../../../../src/collection/registry/server/performExport.ts","../../../../src/collection/registry/server/index.ts"],"sourcesContent":["// User-supplied list of extra collection registries (`config/collections-registries.json`).\n// The official receptron/mulmoclaude-collections registry is always loaded; this\n// file adds more on top so a community / org / private registry shows up in the\n// same Discover catalog.\n//\n// Shape on disk:\n// [\n// { \"name\": \"myorg\", \"indexUrl\": \"https://...\", \"rawBaseUrl\": \"https://...\" }\n// ]\n//\n// Validation rules (a bad entry is dropped + logged; the rest still load):\n// - `name` matches `[A-Za-z0-9][A-Za-z0-9_-]{0,31}` and is NOT the reserved\n// `official` (which is always synthesized by client.ts)\n// - Both URLs are HTTPS, parseable, and have no userinfo/credentials\n// - Names are unique (later duplicates are dropped, first wins)\n//\n// Pure read + parse — no network. Caller invalidates by re-reading the file.\n\nimport { readFileSync } from \"node:fs\";\n\nimport { isRecord } from \"../guards.js\";\nimport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"../types.js\";\nimport { collectionsRegistriesConfigPath, log } from \"../../server/host.js\";\n\nexport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry };\n\nconst NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;\n\nfunction isValidHttpsUrl(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0) return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol !== \"https:\") return false;\n if (url.username !== \"\" || url.password !== \"\") return false;\n return true;\n}\n\nfunction parseEntry(value: unknown, index: number): RegistryConfigEntry | string {\n if (!isRecord(value)) return `entry[${index}] is not an object`;\n const { name, indexUrl, rawBaseUrl } = value;\n if (typeof name !== \"string\" || !NAME_RE.test(name)) {\n return `entry[${index}].name must match ${NAME_RE.source}`;\n }\n if (name === OFFICIAL_REGISTRY_NAME) {\n return `entry[${index}].name \"${OFFICIAL_REGISTRY_NAME}\" is reserved`;\n }\n if (!isValidHttpsUrl(indexUrl)) return `entry[${index}].indexUrl must be a valid HTTPS URL (no credentials)`;\n if (!isValidHttpsUrl(rawBaseUrl)) return `entry[${index}].rawBaseUrl must be a valid HTTPS URL (no credentials)`;\n // `rawBaseUrl` gets joined as `${rawBaseUrl}/<path>` downstream — a query or\n // fragment would land in the middle of the composed URL and break every\n // collection-file fetch on that registry (CodeRabbit review on #1837). The\n // index URL is fetched directly so a query is fine there; only rawBase\n // needs the constraint.\n if (rawBaseUrl.includes(\"?\") || rawBaseUrl.includes(\"#\")) {\n return `entry[${index}].rawBaseUrl must not contain a query (?) or fragment (#) — it's joined as a path prefix`;\n }\n // Strip trailing slashes so `${rawBaseUrl}/${path}` joins cleanly regardless\n // of what the user wrote. Plain-loop trim instead of a regex — the linter\n // flags any unanchored-quantifier regex as ReDoS-suspect even when it's\n // safe, and the loop is just as clear at this size.\n let normalizedRawBase = rawBaseUrl;\n while (normalizedRawBase.endsWith(\"/\")) normalizedRawBase = normalizedRawBase.slice(0, -1);\n return { name, indexUrl, rawBaseUrl: normalizedRawBase };\n}\n\n/** Parse the JSON text of `config/collections-registries.json`. Bad entries\n * drop with a warning; valid entries are returned in source order. Exported\n * for unit testing — the I/O wrapper `loadRegistriesConfig` below reads the\n * workspace file and delegates here. */\nexport function parseRegistriesConfig(raw: unknown): RegistryConfigEntry[] {\n if (!Array.isArray(raw)) {\n log.warn(\"collections-registry\", \"registries config is not an array — ignoring\");\n return [];\n }\n const seen = new Set<string>();\n const out: RegistryConfigEntry[] = [];\n for (let i = 0; i < raw.length; i++) {\n const parsed = parseEntry(raw[i], i);\n if (typeof parsed === \"string\") {\n log.warn(\"collections-registry\", \"registry config entry rejected\", { reason: parsed });\n continue;\n }\n if (seen.has(parsed.name)) {\n log.warn(\"collections-registry\", \"registry config duplicate name dropped\", { name: parsed.name });\n continue;\n }\n seen.add(parsed.name);\n out.push(parsed);\n }\n return out;\n}\n\n// Missing file ⇒ null (the most common case — most users only want the official\n// registry). Other read errors propagate, matching the host's prior behavior.\nfunction readConfigTextOrNull(filePath: string): string | null {\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/** Read `config/collections-registries.json` from the configured workspace.\n * Missing file ⇒ empty list. Malformed JSON or rejected entries log but never\n * throw. */\nexport function loadRegistriesConfig(): RegistryConfigEntry[] {\n const text = readConfigTextOrNull(collectionsRegistriesConfigPath());\n if (text === null) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n log.warn(\"collections-registry\", \"registries config is not valid JSON — ignoring\", {\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n return parseRegistriesConfig(parsed);\n}\n","// Server-side client for the curated collection registries' published index.json\n// files. The official registry (receptron/mulmoclaude-collections via GitHub Pages)\n// is always loaded; users can append more via `config/collections-registries.json`.\n// Each index is fetched over HTTPS with a timeout, validated against the index\n// contract, and memo-cached per-registry so the Discover tab doesn't hammer the\n// upstreams. On a transient upstream failure we serve that registry's last good\n// index rather than failing the whole Discover view.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { log } from \"../../server/host.js\";\nimport { parseRegistryIndex, type RegistryEntry, type RegistryIndex } from \"../registryIndex.js\";\nimport { loadRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"./registriesConfig.js\";\n\nconst DEFAULT_OFFICIAL_INDEX_URL = \"https://receptron.github.io/mulmoclaude-collections/index.json\";\nconst DEFAULT_OFFICIAL_RAW_BASE = \"https://raw.githubusercontent.com/receptron/mulmoclaude-collections/main\";\nexport const CACHE_TTL_MS = 5 * 60 * ONE_SECOND_MS;\n// During an outage (cache past TTL + failing upstream) don't re-hit the network\n// more than once per this window — serve stale immediately in between, so a down\n// upstream can't add its full timeout to every request.\nexport const STALE_RETRY_BACKOFF_MS = 60 * ONE_SECOND_MS;\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\n\n/** Per-registry fetch outcome. `ok: false` is reported per-registry — the\n * merged Discover response still succeeds when at least one registry is\n * reachable; failed registries just contribute zero entries. */\nexport type FetchIndexResult = { ok: true; index: RegistryIndex; stale: boolean } | { ok: false; status: number; error: string };\n\ninterface CacheEntry {\n index: RegistryIndex;\n atMs: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\nconst lastFailureMs = new Map<string, number>();\n\n/** Resolved descriptor for one registry the host should fetch — name + URLs.\n * `official` is always synthesized; further entries come from\n * `config/collections-registries.json`. Currently structurally identical to\n * `RegistryConfigEntry`; aliased so we can add server-only fields (e.g.\n * auth headers) without touching the on-disk config schema. */\nexport type RegistryDescriptor = RegistryConfigEntry;\n\nfunction officialDescriptor(): RegistryDescriptor {\n return {\n name: OFFICIAL_REGISTRY_NAME,\n indexUrl: process.env.COLLECTIONS_REGISTRY_URL ?? DEFAULT_OFFICIAL_INDEX_URL,\n rawBaseUrl: process.env.COLLECTIONS_REGISTRY_RAW_BASE ?? DEFAULT_OFFICIAL_RAW_BASE,\n };\n}\n\n/** The full ordered registry list: official first, then user-configured ones.\n * Re-reads the config file on every call so a Discover refresh picks up edits\n * without a server restart (the config is small + read-rarely). */\nexport function listRegistries(): RegistryDescriptor[] {\n return [officialDescriptor(), ...loadRegistriesConfig()];\n}\n\n/** Look up one registry descriptor by name. Used by the preview / import path,\n * which has the registry label from the entry and needs the rawBase. */\nexport function findRegistry(name: string): RegistryDescriptor | null {\n return listRegistries().find((reg) => reg.name === name) ?? null;\n}\n\nasync function loadFromNetwork(descriptor: RegistryDescriptor): Promise<FetchIndexResult> {\n let res: Response;\n try {\n res = await fetchWithTimeout(descriptor.indexUrl, { timeoutMs: FETCH_TIMEOUT_MS, headers: { accept: \"application/json\" } });\n } catch (err) {\n log.warn(\"collections-registry\", \"index fetch failed\", { registry: descriptor.name, url: descriptor.indexUrl, error: errorMessage(err) });\n return { ok: false, status: STATUS_UNAVAILABLE, error: \"registry unreachable\" };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry responded ${res.status}` };\n const json: unknown = await res.json().catch(() => null);\n const parsed = parseRegistryIndex(json, descriptor.name);\n if (!parsed.ok) {\n log.warn(\"collections-registry\", \"index invalid\", { registry: descriptor.name, url: descriptor.indexUrl, error: parsed.error });\n return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry index invalid: ${parsed.error}` };\n }\n return { ok: true, index: parsed.index, stale: false };\n}\n\nexport type IndexLoader = (descriptor: RegistryDescriptor) => Promise<FetchIndexResult>;\n\n/** Cache key incorporates BOTH URLs so editing `indexUrl` or `rawBaseUrl` for\n * an existing registry name invalidates the cached index automatically — same\n * refresh cycle as if the user had renamed it (CodeRabbit review on #1837).\n * Without this, the Discover catalog could keep serving the old upstream's\n * entries while preview / import resolved the new rawBase, drifting until TTL\n * expiry. Tab + key-content guarantees no collision between e.g. swapped\n * name/url pairs. */\nfunction descriptorCacheKey(descriptor: RegistryDescriptor): string {\n return `${descriptor.name}\\t${descriptor.indexUrl}\\t${descriptor.rawBaseUrl}`;\n}\n\n/** Fetch one registry's index. Same cache + stale-on-failure semantics as the\n * original single-registry implementation — just keyed by descriptor identity\n * (name + both URLs) so multiple registries don't fight over one slot AND a\n * reconfigured registry doesn't serve a stale index from the prior URL. */\nexport async function fetchRegistryIndex(\n descriptor: RegistryDescriptor,\n opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } = {},\n): Promise<FetchIndexResult> {\n const nowMs = opts.nowMs ?? Date.now();\n const loader = opts.loader ?? loadFromNetwork;\n const key = descriptorCacheKey(descriptor);\n const cached = cache.get(key);\n if (!opts.force && cached && nowMs - cached.atMs < CACHE_TTL_MS) {\n return { ok: true, index: cached.index, stale: false };\n }\n const failedAt = lastFailureMs.get(key);\n if (!opts.force && cached && failedAt !== undefined && nowMs - failedAt < STALE_RETRY_BACKOFF_MS) {\n return { ok: true, index: cached.index, stale: true };\n }\n const fresh = await loader(descriptor);\n if (fresh.ok) {\n cache.set(key, { index: fresh.index, atMs: nowMs });\n lastFailureMs.delete(key);\n return { ok: true, index: fresh.index, stale: false };\n }\n lastFailureMs.set(key, nowMs);\n if (cached) return { ok: true, index: cached.index, stale: true };\n return fresh;\n}\n\n/** One registry's contribution to the merged Discover view. `failed` registries\n * surface in the response so the UI can show a per-registry error badge while\n * still rendering the entries from the registries that did work. */\nexport interface MergedRegistryResult {\n name: string;\n status: \"ok\" | \"stale\" | \"failed\";\n generatedAt: string | null;\n error: string | null;\n entries: RegistryEntry[];\n}\n\n/** Fetch every configured registry in parallel and return per-registry\n * outcomes. Callers (the Discover route) concatenate `entries` from each.\n * Failure of any single registry doesn't abort the others — that's the point\n * of supporting multiple registries. */\nexport async function fetchAllRegistries(opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } = {}): Promise<MergedRegistryResult[]> {\n const descriptors = listRegistries();\n return await Promise.all(\n descriptors.map(async (descriptor): Promise<MergedRegistryResult> => {\n const result = await fetchRegistryIndex(descriptor, opts);\n if (!result.ok) {\n return { name: descriptor.name, status: \"failed\", generatedAt: null, error: result.error, entries: [] };\n }\n return {\n name: descriptor.name,\n status: result.stale ? \"stale\" : \"ok\",\n generatedAt: result.index.generatedAt,\n error: null,\n entries: result.index.collections,\n };\n }),\n );\n}\n\n/** Test seam: reset the module cache + failure backoff state (all registries). */\nexport function resetRegistryCache(): void {\n cache.clear();\n lastFailureMs.clear();\n}\n","// Fetch individual files of a registry collection (schema.json, meta.json, …)\n// from the registry repo's raw content. The collection's repo-relative dir comes\n// from a trusted index entry (never raw user input), so the host can only fetch\n// files of collections that actually appear in the published index.\n//\n// The index itself is served from GitHub Pages (see client.ts); per-collection\n// files are served from raw.githubusercontent. With multi-registry support the\n// rawBase is no longer a single module-level value — it comes from the entry's\n// source registry descriptor, so user-added registries can live anywhere.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries, findRegistry } from \"./client.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\nconst STATUS_NOT_FOUND = 404;\n\n/** Compose the raw URL for `<dirPath>/<relFile>` under a given registry's\n * rawBase. Empty and traversal (`.`/`..`) segments are dropped so the URL can\n * never escape the base, even if an upstream check is bypassed (the index\n * parser already rejects such identifiers — this is defense-in-depth). The\n * rawBase is trailing-slash-normalized: `parseRegistriesConfig` already trims\n * user-config trailing slashes, but the official descriptor and any test\n * bypass parse — repeating the trim here keeps the join `${base}/${path}`\n * from producing `//` even when the caller bypassed config validation\n * (CodeRabbit review on #1837). */\nexport function collectionFileUrl(rawBase: string, dirPath: string, relFile: string): string {\n let base = rawBase;\n while (base.endsWith(\"/\")) base = base.slice(0, -1);\n const segments = dirPath.split(\"/\").filter((segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\");\n return `${base}/${segments.join(\"/\")}/${relFile}`;\n}\n\nexport type FileResult = { ok: true; text: string } | { ok: false; status: number; error: string };\n\n/** Fetch one file out of a registry collection. `rawBase` comes from the\n * entry's source registry (`findRegistry(entry.registryName).rawBaseUrl`),\n * not a module-level setting — that's what makes additional user-configured\n * registries reachable. */\nexport async function fetchCollectionFile(rawBase: string, dirPath: string, relFile: string): Promise<FileResult> {\n const url = collectionFileUrl(rawBase, dirPath, relFile);\n let res: Response;\n try {\n res = await fetchWithTimeout(url, { timeoutMs: FETCH_TIMEOUT_MS });\n } catch (err) {\n return { ok: false, status: STATUS_UNAVAILABLE, error: `fetch failed: ${errorMessage(err)}` };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `${relFile} responded ${res.status}` };\n return { ok: true, text: await res.text() };\n}\n\nexport type JsonObjectResult = { ok: true; value: Record<string, unknown> } | { ok: false; error: string };\n\nexport function parseJsonObject(text: string, label: string): JsonObjectResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return { ok: false, error: `${label} is not valid JSON` };\n }\n if (!isRecord(parsed)) return { ok: false, error: `${label} is not an object` };\n return { ok: true, value: parsed };\n}\n\nasync function fetchJsonObject(\n rawBase: string,\n dirPath: string,\n relFile: string,\n label: string,\n): Promise<{ ok: true; value: Record<string, unknown> } | { ok: false; status: number; error: string }> {\n const file = await fetchCollectionFile(rawBase, dirPath, relFile);\n if (!file.ok) return { ok: false, status: file.status, error: `${label}: ${file.error}` };\n const parsed = parseJsonObject(file.text, label);\n if (!parsed.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: parsed.error };\n return { ok: true, value: parsed.value };\n}\n\n/** Resolve an entry's rawBase from its `registryName`. A missing match (the\n * user removed the registry from config while a cached index still references\n * it) returns null — the caller surfaces it as a 404 rather than crashing. */\nexport function rawBaseForEntry(entry: Pick<RegistryEntry, \"registryName\">): string | null {\n const registry = findRegistry(entry.registryName);\n return registry?.rawBaseUrl ?? null;\n}\n\nexport type PreviewResult =\n { ok: true; entry: RegistryEntry; schema: Record<string, unknown>; meta: Record<string, unknown> } | { ok: false; status: number; error: string };\n\n/** Resolve an entry by author+slug across every cached registry's entries.\n * When `registry` is passed we constrain to that registry — needed because\n * multiple registries can publish the same author/slug, and the UI should\n * follow the card it just clicked. */\nfunction findEntryInMergedView(\n merged: { name: string; entries: RegistryEntry[] }[],\n author: string,\n slug: string,\n registry: string | null,\n): RegistryEntry | null {\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n const match = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (match) return match;\n }\n return null;\n}\n\n/** Preview a registry collection: confirm it's in some registry's published\n * index, then fetch + parse its schema.json + meta.json so the Discover tab\n * can show fields/views before import. With multi-registry support the\n * `registry` arg disambiguates same-name collections from different sources. */\nexport async function previewCollection(author: string, slug: string, registry: string | null = null): Promise<PreviewResult> {\n const merged = await fetchAllRegistries();\n const entry = findEntryInMergedView(merged, author, slug, registry);\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const rawBase = rawBaseForEntry(entry);\n if (!rawBase) return { ok: false, status: STATUS_NOT_FOUND, error: `registry \"${entry.registryName}\" is no longer configured` };\n const schema = await fetchJsonObject(rawBase, entry.path, \"schema.json\", \"schema.json\");\n if (!schema.ok) return schema;\n const meta = await fetchJsonObject(rawBase, entry.path, \"meta.json\", \"meta.json\");\n if (!meta.ok) return meta;\n return { ok: true, entry, schema: schema.value, meta: meta.value };\n}\n","// Import-side fetch + transform helpers for a registry collection. The writer\n// (writes into .claude/skills/, materializes seed, records provenance) builds on\n// these; kept separate so the pure, security-critical parts are unit-tested.\n//\n// Files to fetch come from the collection's manifest.json (published by the\n// registry's build-index). Every manifest path is re-checked for safety here —\n// the host must never write outside the target skill dir even if the manifest is\n// malformed/poisoned.\n\nimport { isRecord } from \"../guards.js\";\nimport { fetchCollectionFile, parseJsonObject, rawBaseForEntry } from \"./collectionFiles.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst MANIFEST_FILE = \"manifest.json\";\nconst STATUS_BAD_GATEWAY = 502;\n\n/** A manifest entry must be a relative path that stays inside the collection dir:\n * no absolute paths, no backslashes, no empty / `.` / `..` segments. */\nexport function isSafeBundlePath(rel: unknown): rel is string {\n if (typeof rel !== \"string\" || rel.length === 0) return false;\n if (rel.startsWith(\"/\") || rel.includes(\"\\\\\")) return false;\n return !rel.split(\"/\").some((segment) => segment === \"\" || segment === \".\" || segment === \"..\");\n}\n\nexport type ManifestResult = { ok: true; files: string[] } | { ok: false; error: string };\n\nexport function parseManifest(value: unknown): ManifestResult {\n if (!isRecord(value) || !Array.isArray(value.files)) return { ok: false, error: \"manifest is missing a files[] array\" };\n const unsafe = value.files.find((file) => !isSafeBundlePath(file));\n if (unsafe !== undefined) return { ok: false, error: `manifest contains an unsafe path: ${String(unsafe)}` };\n return { ok: true, files: value.files.filter(isSafeBundlePath) };\n}\n\n/** `data/collections/<localSlug>/items` — the host owns dataPath, never the\n * registry's authored value, so imported collections can't collide on disk. */\nexport function normalizedDataPath(localSlug: string): string {\n return `data/collections/${localSlug}/items`;\n}\n\nexport function withNormalizedDataPath(schema: Record<string, unknown>, localSlug: string): Record<string, unknown> {\n return { ...schema, dataPath: normalizedDataPath(localSlug) };\n}\n\nexport type ManifestFetch = { ok: true; files: string[] } | { ok: false; status: number; error: string };\n\nexport async function fetchManifest(entry: RegistryEntry): Promise<ManifestFetch> {\n const rawBase = rawBaseForEntry(entry);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const file = await fetchCollectionFile(rawBase, entry.path, MANIFEST_FILE);\n if (!file.ok) return { ok: false, status: file.status, error: `manifest.json: ${file.error}` };\n const obj = parseJsonObject(file.text, \"manifest.json\");\n if (!obj.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: obj.error };\n const manifest = parseManifest(obj.value);\n if (!manifest.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: manifest.error };\n return { ok: true, files: manifest.files };\n}\n\nexport type BundleFetch = { ok: true; files: Map<string, string> } | { ok: false; status: number; error: string };\n\n/** Fetch every manifest file. Paths are already safety-checked by parseManifest. */\nexport async function fetchBundle(entry: RegistryEntry, fileList: readonly string[]): Promise<BundleFetch> {\n const rawBase = rawBaseForEntry(entry);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const files = new Map<string, string>();\n for (const rel of fileList) {\n const file = await fetchCollectionFile(rawBase, entry.path, rel);\n if (!file.ok) return { ok: false, status: file.status, error: `${rel}: ${file.error}` };\n files.set(rel, file.text);\n }\n return { ok: true, files };\n}\n","// Import a registry collection into the active workspace. The writer fetches\n// the bundle, re-validates the schema with the host's own gates (R7 — the\n// index is not a trust boundary), writes the bundle into `data/skills/<localSlug>/`\n// with a host-owned dataPath (R3), materializes any seed records into that\n// dataPath when it's empty, and records provenance in `.origin.json` for\n// update detection (R5/R8). After the data-skills swap, the bundle's\n// allowlisted files (SKILL.md, schema.json, templates/<safe>) are mirrored\n// 1:1 into `.claude/skills/<localSlug>/` via the same skill-bridge rules an\n// agent-authored skill would go through — so an authored and an imported\n// collection live in EXACTLY the same place on disk, and the user can edit\n// either one identically.\n//\n// `.origin.json` lives ONLY in `data/skills/<slug>/.origin.json` and is NOT\n// mirrored — the skill-bridge allowlist deliberately excludes host\n// bookkeeping. Its presence is what distinguishes \"imported\" from\n// \"user-authored\" in every downstream tool.\n//\n// `writeImportedCollection` takes the already-fetched bundle + an explicit\n// workspaceRoot/clock so it is unit-testable against a temp workspace with\n// no network; `performImport` is the thin glue that fetches and calls it.\n\nimport { mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { acceptParsedSchema, CollectionSchemaZ, isSafeActionTemplatePath, safeRecordId } from \"../../server/index.js\";\nimport type { CollectionSchema } from \"../../index.js\";\nimport { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from \"../../../skill-bridge/index.js\";\n\nimport { log } from \"../../server/host.js\";\nimport { errorMessage } from \"../../server/util.js\";\nimport { writeFileAtomic } from \"../../../files/atomic.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries } from \"./client.js\";\nimport { fetchBundle, fetchManifest, normalizedDataPath } from \"./importCollection.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst ORIGIN_FILE = \".origin.json\";\nconst SEED_PREFIX = \"seed/items/\";\nconst SCHEMA_FILE = \"schema.json\";\nconst SKILL_FILE = \"SKILL.md\";\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_CONFLICT = 409;\nconst STATUS_UNPROCESSABLE = 422;\n\nexport interface ImportOrigin {\n registry: string;\n author: string;\n slug: string;\n version: string;\n contentSha: string;\n importedAt: string;\n}\n\nexport type ImportResult =\n { ok: true; localSlug: string; updated: boolean; seedWritten: number; seedSkipped: boolean } | { ok: false; status: number; error: string };\n\nasync function statType(target: string): Promise<\"dir\" | \"other\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"other\";\n } catch (err) {\n // EACCES etc. are path-shape conflicts mkdir would later throw on → \"other\".\n // A file ancestor surfaces as ENOTDIR on POSIX but ENOENT on Windows (which\n // reports the whole path as missing), so ENOENT can't be trusted as \"absent\"\n // until we've confirmed no existing ancestor is a non-directory file.\n if (!(isRecord(err) && err.code === \"ENOENT\")) return \"other\";\n return (await hasNonDirAncestor(target)) ? \"other\" : \"absent\";\n }\n}\n\n// Walk up from `target` to the nearest ancestor that exists: a non-directory\n// file there means `target` can never be mkdir'd (a path-shape conflict → 409),\n// whichever errno the platform's `stat(target)` chose. An all-absent-or-directory\n// chain up to the filesystem root is a genuine \"absent\".\nasync function hasNonDirAncestor(target: string): Promise<boolean> {\n let current = path.dirname(target);\n while (current !== path.dirname(current)) {\n try {\n return !(await stat(current)).isDirectory();\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") {\n current = path.dirname(current);\n continue;\n }\n return true;\n }\n }\n return false;\n}\n\nasync function isEmptyOrAbsentDir(target: string): Promise<boolean> {\n try {\n return (await readdir(target)).length === 0;\n } catch {\n return true;\n }\n}\n\nfunction originMatches(origin: unknown, registry: string, author: string, slug: string): boolean {\n return isRecord(origin) && origin.registry === registry && origin.author === author && origin.slug === slug;\n}\n\nasync function readOrigin(targetDir: string): Promise<unknown> {\n try {\n return JSON.parse(await readFile(path.join(targetDir, ORIGIN_FILE), \"utf-8\"));\n } catch {\n return null;\n }\n}\n\ntype TargetResolution = { targetDir: string; localSlug: string; updated: boolean } | { conflict: string };\n\n// Only bounds the fresh-slug search (a safety cap, far above any realistic\n// number of same-named collections — the first free slug is normally found in\n// 1–2 iterations). An EXISTING install is found via the directory scan below,\n// not this loop, so updates are never missed regardless of how high the rename\n// suffix is.\nconst MAX_SLUG_ATTEMPTS = 10000;\n\nconst renameCandidate = (slug: string, attempt: number): string => (attempt === 0 ? slug : `${slug}-${attempt + 1}`);\n\n// True when `name` is the registry slug or a `<slug>-<n>` rename of it.\nfunction isRenameOf(name: string, slug: string): boolean {\n if (name === slug) return true;\n if (!name.startsWith(`${slug}-`)) return false;\n const suffix = name.slice(slug.length + 1);\n return suffix.length > 0 && /^\\d+$/.test(suffix);\n}\n\n// Find an existing install of this registry collection at ANY rename suffix\n// by scanning `data/skills/` — independent of any candidate bound, so a\n// re-import always updates the existing install rather than duplicating it\n// (even if an earlier slug freed up). An authored skill without `.origin.json`\n// is invisible to this scan and so never collides with an update.\nasync function findMatchingInstall(skillsDir: string, registry: string, entry: RegistryEntry): Promise<string | null> {\n const names = await readdir(skillsDir).catch((): string[] => []);\n for (const name of names) {\n if (!isRenameOf(name, entry.slug)) continue;\n const dir = path.join(skillsDir, name);\n if ((await statType(dir)) === \"dir\" && originMatches(await readOrigin(dir), registry, entry.author, entry.slug)) return name;\n }\n return null;\n}\n\n// Pick the local install slug (rename-on-collision, R8). First reuse an\n// existing matching install (update). Otherwise install fresh at the first\n// free slug — the registry slug, else `<slug>-2`, `-3`, … — never clobbering\n// a user's own same-named collection.\n//\n// A slug is \"free\" only when BOTH `data/skills/<slug>/` AND\n// `.claude/skills/<slug>/` are absent. The mirror check matters because\n// `mirrorToClaudeSkills` overwrites the mirror dir's allowlisted files and\n// prunes anything else — if the user has a manually-installed Claude skill\n// at `.claude/skills/<slug>/` with no corresponding `data/skills/<slug>/`\n// (e.g. installed by the Claude CLI directly, or a legacy pre-refactor\n// import we never migrated), picking that slug would silently overwrite\n// their SKILL.md/schema.json and prune the rest of their files (CodeRabbit\n// review on #1839).\nasync function resolveTarget(workspaceRoot: string, registry: string, entry: RegistryEntry): Promise<TargetResolution> {\n const skillsDir = path.dirname(dataSkillDir(workspaceRoot, entry.slug));\n const existing = await findMatchingInstall(skillsDir, registry, entry);\n if (existing) return { targetDir: dataSkillDir(workspaceRoot, existing), localSlug: existing, updated: true };\n for (let attempt = 0; attempt < MAX_SLUG_ATTEMPTS; attempt += 1) {\n const localSlug = renameCandidate(entry.slug, attempt);\n const sourceAbsent = (await statType(dataSkillDir(workspaceRoot, localSlug))) === \"absent\";\n const mirrorAbsent = (await statType(claudeSkillDir(workspaceRoot, localSlug))) === \"absent\";\n if (sourceAbsent && mirrorAbsent) {\n return { targetDir: dataSkillDir(workspaceRoot, localSlug), localSlug, updated: false };\n }\n }\n return { conflict: `couldn't find an available slug for '${entry.slug}'` };\n}\n\ntype SchemaResolution = { schema: CollectionSchema } | { error: string };\n\nfunction validateAndNormalize(bundle: Map<string, string>, localSlug: string, workspaceRoot: string): SchemaResolution {\n // SKILL.md is required: `mirrorToClaudeSkills` calls\n // `mirrorSkillWrite({slug, relSegments: [SKILL_FILE]})` unconditionally\n // after the data-side swap, which throws if SKILL.md is missing. Caught\n // outside, that throw is logged but the data-side write still succeeds and\n // the function returns `ok: true` for an unusable import. Reject up front\n // so a missing SKILL.md surfaces as a clean 422 instead (CodeRabbit review\n // on #1839).\n if (bundle.get(SKILL_FILE) === undefined) return { error: \"bundle is missing SKILL.md\" };\n const schemaText = bundle.get(SCHEMA_FILE);\n if (schemaText === undefined) return { error: \"bundle is missing schema.json\" };\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(schemaText);\n } catch {\n return { error: \"schema.json is not valid JSON\" };\n }\n const parsed = CollectionSchemaZ.safeParse(parsedJson);\n if (!parsed.success) return { error: `schema.json failed validation: ${parsed.error.issues[0]?.message ?? \"invalid\"}` };\n // A `dataSource` collection can't be imported: its records are the rows of\n // a machine-local data file the bundle doesn't (and can't) carry — and the\n // R3 dataPath stamp below would collide with the exactly-one-of rule.\n if (parsed.data.dataSource !== undefined) {\n return { error: \"schema.json declares `dataSource` — external-data collections are machine-local and cannot be imported from a registry\" };\n }\n const schema: CollectionSchema = { ...parsed.data, dataPath: normalizedDataPath(localSlug) };\n const acceptance = acceptParsedSchema(schema, { source: \"project\", workspaceRoot, slug: localSlug });\n if (!acceptance.ok) return { error: `schema.json rejected: ${acceptance.reason}` };\n return { schema };\n}\n\nasync function writeBundleFiles(targetDir: string, bundle: Map<string, string>, schema: CollectionSchema): Promise<void> {\n for (const [rel, content] of bundle) {\n if (rel.startsWith(SEED_PREFIX)) continue; // seed goes to dataPath, not the skill dir\n const dest = path.join(targetDir, ...rel.split(\"/\"));\n if (dest !== targetDir && !dest.startsWith(targetDir + path.sep)) continue; // belt-and-suspenders (paths pre-validated)\n await mkdir(path.dirname(dest), { recursive: true });\n await writeFile(dest, rel === SCHEMA_FILE ? `${JSON.stringify(schema, null, 2)}\\n` : content, \"utf-8\");\n }\n}\n\nasync function materializeSeed(dataDir: string, bundle: Map<string, string>): Promise<{ written: number; skipped: boolean }> {\n const seedEntries = [...bundle].filter(([rel]) => rel.startsWith(SEED_PREFIX));\n if (seedEntries.length === 0) return { written: 0, skipped: false };\n if (!(await isEmptyOrAbsentDir(dataDir))) return { written: 0, skipped: true };\n await mkdir(dataDir, { recursive: true });\n let written = 0;\n for (const [rel, content] of seedEntries) {\n const fileName = rel.slice(SEED_PREFIX.length);\n if (fileName.includes(\"/\") || safeRecordId(fileName.replace(/\\.json$/, \"\")) === null) {\n log.warn(\"collections-registry\", \"skipped unsafe seed record\", { rel });\n continue;\n }\n await writeFile(path.join(dataDir, fileName), content, \"utf-8\");\n written += 1;\n }\n return { written, skipped: false };\n}\n\n/** Compute the set of bundle-relative paths that should land in `.claude/skills/<slug>/`.\n * Matches the bridge allowlist exactly — SKILL.md, schema.json,\n * `templates/<safe>` — so anything else (seed, meta.json, views, README,\n * assets) stays source-side. */\nfunction bridgeAllowlistFiles(bundle: Map<string, string>): Set<string> {\n const wanted = new Set<string>([SKILL_FILE, SCHEMA_FILE]);\n for (const rel of bundle.keys()) {\n if (rel === SKILL_FILE || rel === SCHEMA_FILE) continue;\n if (rel.startsWith(SEED_PREFIX)) continue;\n if (!isSafeActionTemplatePath(rel)) continue;\n wanted.add(rel);\n }\n return wanted;\n}\n\n/** Recursively list every file under `root`, returned as forward-slash paths\n * relative to `root`. Empty list when `root` doesn't exist. Used to spot\n * mirror files left over from a previous install so we can prune them after\n * writing the new ones. */\nasync function listFilesRecursive(root: string, prefix = \"\"): Promise<string[]> {\n const dir = prefix ? path.join(root, ...prefix.split(\"/\")) : root;\n const entries: Dirent[] = await readdir(dir, { withFileTypes: true }).catch((): Dirent[] => []);\n const out: string[] = [];\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isFile()) out.push(rel);\n else if (entry.isDirectory()) out.push(...(await listFilesRecursive(root, rel)));\n }\n return out;\n}\n\n// Mirror the just-written `data/skills/<slug>/` into `.claude/skills/<slug>/`\n// using the shared bridge package — exactly the set of files an\n// agent-authored skill would mirror, no more (SKILL.md, schema.json,\n// templates/<safe>). `.origin.json` is host bookkeeping and stays only on the\n// data side.\n//\n// **Write-then-prune ordering** (Codex review on #1839): write the new\n// bundle's allowlisted files FIRST (each is a per-file tmp+rename, atomic),\n// THEN prune mirror files that aren't in the new bundle. A transient mirror\n// failure during the writes leaves the previous mirror's files in place —\n// agent discovery keeps working with the prior state instead of finding an\n// empty `.claude/skills/<slug>/`. The prune step is best-effort: a failure\n// there leaves harmless stale leftovers but doesn't break the new install.\n// Compare with the old `mirrorSkillDelete`-first ordering, which could\n// silently leave an empty mirror if a subsequent write threw.\nasync function mirrorToClaudeSkills(workspaceRoot: string, localSlug: string, bundle: Map<string, string>): Promise<void> {\n const wanted = bridgeAllowlistFiles(bundle);\n // 1. Write the new set first (overwrites existing via tmp+rename).\n for (const rel of wanted) {\n mirrorSkillWrite(workspaceRoot, { slug: localSlug, relSegments: rel.split(\"/\") });\n }\n // 2. Prune files that exist in the mirror but aren't in the new bundle.\n const mirrorRoot = claudeSkillDir(workspaceRoot, localSlug);\n const existing = await listFilesRecursive(mirrorRoot);\n for (const rel of existing) {\n if (wanted.has(rel)) continue;\n await rm(path.join(mirrorRoot, ...rel.split(\"/\")), { force: true }).catch(() => undefined);\n }\n}\n\nexport async function writeImportedCollection(params: {\n registry: string;\n entry: RegistryEntry;\n bundle: Map<string, string>;\n workspaceRoot: string;\n nowIso: string;\n}): Promise<ImportResult> {\n const { registry, entry, bundle, workspaceRoot, nowIso } = params;\n const target = await resolveTarget(workspaceRoot, registry, entry);\n if (\"conflict\" in target) return { ok: false, status: STATUS_CONFLICT, error: target.conflict };\n\n const { localSlug } = target;\n\n // Pre-flight the data dir before schema validation: a non-directory at the dataPath\n // (or an ancestor that's a file → ENOTDIR) would otherwise surface as a generic 500\n // on mkdir. statType maps ENOTDIR/other to a deterministic 409 path-shape conflict.\n const dataDir = path.join(workspaceRoot, ...normalizedDataPath(localSlug).split(\"/\"));\n if ((await statType(dataDir)) === \"other\") {\n return { ok: false, status: STATUS_CONFLICT, error: `data path for slug '${localSlug}' exists and is not a directory` };\n }\n\n const validated = validateAndNormalize(bundle, localSlug, workspaceRoot);\n if (\"error\" in validated) return { ok: false, status: STATUS_UNPROCESSABLE, error: validated.error };\n\n // Build the replacement fully in a hidden sibling staging dir under\n // `data/skills/` (bundle + origin), so the prior install is untouched until\n // everything is durably written. Leftover staging/backup dirs from a\n // crashed import are cleaned first, keeping retries possible.\n const skillsParent = path.dirname(target.targetDir);\n await mkdir(skillsParent, { recursive: true }); // first-import case — data/skills/ may not exist\n const staging = path.join(skillsParent, `.importing-${localSlug}`);\n const backup = path.join(skillsParent, `.backup-${localSlug}`);\n await rm(staging, { recursive: true, force: true });\n await rm(backup, { recursive: true, force: true });\n await mkdir(staging, { recursive: true });\n await writeBundleFiles(staging, bundle, validated.schema);\n const origin: ImportOrigin = { registry, author: entry.author, slug: entry.slug, version: entry.version, contentSha: entry.contentSha, importedAt: nowIso };\n await writeFileAtomic(path.join(staging, ORIGIN_FILE), `${JSON.stringify(origin, null, 2)}\\n`);\n\n // Swap with rollback: move the old install aside (rename), move the new in\n // (rename), then discard the old. If the swap fails, restore the old so we\n // never end up with no installed collection. Records live in dataPath (a\n // separate dir) and are untouched.\n if (target.updated) await rename(target.targetDir, backup);\n try {\n await rename(staging, target.targetDir);\n } catch (err) {\n if (target.updated) await rename(backup, target.targetDir).catch(() => undefined);\n throw err;\n }\n await rm(backup, { recursive: true, force: true });\n\n // Replicate the new `data/skills/<slug>/` set into `.claude/skills/<slug>/`\n // via the shared bridge package — same allowlist + tmp+rename semantics the\n // hook uses for agent-authored writes. Write-then-prune ordering inside\n // mirrorToClaudeSkills means a transient failure here can't leave the mirror\n // empty (Codex review on #1839); the worst case is a half-written mirror\n // where the new install's SKILL.md/schema.json may not have updated, but the\n // prior install remains accessible to the agent until the next mirror\n // attempt. Logged-not-thrown matches the hook's posture for agent writes.\n try {\n await mirrorToClaudeSkills(workspaceRoot, localSlug, bundle);\n } catch (err) {\n log.warn(\"collections-registry\", \"mirror to .claude/skills/ failed (data/skills write succeeded; prior mirror left intact)\", {\n localSlug,\n error: errorMessage(err),\n });\n }\n\n const seed = await materializeSeed(dataDir, bundle);\n return { ok: true, localSlug, updated: target.updated, seedWritten: seed.written, seedSkipped: seed.skipped };\n}\n\nexport async function performImport(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<ImportResult> {\n const merged = await fetchAllRegistries();\n let entry: RegistryEntry | undefined;\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n entry = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (entry) break;\n }\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const manifest = await fetchManifest(entry);\n if (!manifest.ok) return { ok: false, status: manifest.status, error: manifest.error };\n const bundle = await fetchBundle(entry, manifest.files);\n if (!bundle.ok) return { ok: false, status: bundle.status, error: bundle.error };\n try {\n return await writeImportedCollection({\n registry: entry.registryName,\n entry,\n bundle: bundle.files,\n workspaceRoot,\n nowIso: new Date().toISOString(),\n });\n } catch (err) {\n log.warn(\"collections-registry\", \"import write failed\", { author, slug, registry: entry.registryName, error: errorMessage(err) });\n return { ok: false, status: 500, error: `import failed: ${errorMessage(err)}` };\n }\n}\n\n// Exported for downstream code that wants the path conventions without\n// importing skill-bridge directly.\nexport { claudeSkillDir, dataSkillDir, hasNonDirAncestor };\n","// Export a workspace collection into the curated registry's contribution layout\n// (collections/<author>/<slug>/ + meta.json + optional seed/) under\n// data/registry-export/, so the user can open a PR to the registry. The producer\n// side of the registry loop.\n//\n// writeCollectionExport takes explicit dirs + a workspace root so it is unit-testable\n// against temp dirs with no discovery/network; performExport (separate file) is the\n// thin glue that resolves the collection from the live workspace.\n\nimport { cp, mkdir, readdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { log } from \"../../server/host.js\";\nimport { toPosixRelPath } from \"../../../files/relPath.js\";\n\nexport const EXPORT_BASE = \"data/registry-export\";\nconst BUNDLE_FILES = [\"SKILL.md\", \"schema.json\"] as const;\nconst OPTIONAL_FILES = [\"screenshot.png\"] as const;\nconst BUNDLE_DIRS = [\"views\", \"templates\"] as const;\nconst AUTHOR_RE = /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/;\nconst SLUG_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;\nconst STATUS_BAD_REQUEST = 400;\n// Block obvious credentials from being published in seed data; PII is only warned.\nconst SECRET_PATTERNS = [\n /AKIA[0-9A-Z]{16}/,\n /gh[pousr]_[A-Za-z0-9]{20,}/,\n /-----BEGIN [A-Z ]*PRIVATE KEY-----/,\n /sk-[A-Za-z0-9]{20,}/,\n /xox[baprs]-[A-Za-z0-9-]{10,}/,\n];\nconst hasSecret = (text: string): boolean => SECRET_PATTERNS.some((pattern) => pattern.test(text));\n// Bounded + non-overlapping (no ReDoS): local@label.tld with single quantifier runs.\nconst EMAIL_RE = /[\\w.%+-]{1,64}@[\\w-]{1,255}\\.[a-z]{2,24}/i;\n\nexport interface ExportMeta {\n author: string;\n slug: string;\n version: string;\n title: string;\n description: string;\n tags: string[];\n license: string;\n}\n\nexport type ExportResult =\n | { ok: true; outputPath: string; fileCount: number; seedCount: number; seedSkipped: number; warnings: string[] }\n | { ok: false; status: number; error: string };\n\nasync function pathKind(target: string): Promise<\"dir\" | \"file\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"file\";\n } catch {\n return \"absent\";\n }\n}\n\nasync function copyBundle(skillDir: string, outDir: string): Promise<number> {\n let count = 0;\n for (const file of [...BUNDLE_FILES, ...OPTIONAL_FILES]) {\n const src = path.join(skillDir, file);\n if ((await pathKind(src)) !== \"file\") continue;\n await cp(src, path.join(outDir, file));\n count += 1;\n }\n for (const dir of BUNDLE_DIRS) {\n const src = path.join(skillDir, dir);\n if ((await pathKind(src)) !== \"dir\") continue;\n await cp(src, path.join(outDir, dir), { recursive: true });\n count += (await readdir(src)).filter((name) => !name.startsWith(\".\")).length;\n }\n return count;\n}\n\nasync function exportSeed(dataDir: string, outDir: string): Promise<{ count: number; skipped: number; warnings: string[] }> {\n let names: string[];\n try {\n names = (await readdir(dataDir)).filter((name) => name.endsWith(\".json\"));\n } catch {\n return { count: 0, skipped: 0, warnings: [] };\n }\n if (names.length === 0) return { count: 0, skipped: 0, warnings: [] };\n const seedDir = path.join(outDir, \"seed\", \"items\");\n await mkdir(seedDir, { recursive: true });\n const warnings: string[] = [];\n let count = 0;\n let skipped = 0;\n for (const name of names) {\n const base = path.basename(name);\n const text = await readFile(path.join(dataDir, base), \"utf-8\");\n if (hasSecret(text)) {\n warnings.push(`skipped ${base}: contains a possible credential`);\n skipped += 1;\n continue;\n }\n if (EMAIL_RE.test(text)) warnings.push(`${base}: contains a possible email/PII (kept — your responsibility)`);\n await writeFile(path.join(seedDir, base), text, \"utf-8\");\n count += 1;\n }\n return { count, skipped, warnings };\n}\n\n// Resolve `segments` under `root` and return the absolute path only when it stays\n// inside `root` — a containment barrier for the user-derived author/slug and the\n// collection dirs, on top of the AUTHOR_RE/SLUG_RE format checks. `..` or an\n// absolute escape resolves outside `root` and yields null (rejected upstream).\nfunction resolveWithin(root: string, ...segments: string[]): string | null {\n const resolvedRoot = path.resolve(root);\n const target = path.resolve(resolvedRoot, ...segments);\n return target === resolvedRoot || target.startsWith(resolvedRoot + path.sep) ? target : null;\n}\n\n// Required bundle files (SKILL.md, schema.json) must be present — exporting an\n// incomplete bundle would pass here but fail downstream registry curation.\n// Returns the first missing required file, or null when all are present.\nasync function findMissingRequired(skillDir: string): Promise<string | null> {\n for (const file of BUNDLE_FILES) {\n if ((await pathKind(path.join(skillDir, file))) !== \"file\") return file;\n }\n return null;\n}\n\nexport async function writeCollectionExport(params: {\n workspaceRoot: string;\n skillDir: string;\n dataDir: string;\n meta: ExportMeta;\n includeSeed: boolean;\n}): Promise<ExportResult> {\n const { workspaceRoot, skillDir, dataDir, meta, includeSeed } = params;\n if (!AUTHOR_RE.test(meta.author)) return { ok: false, status: STATUS_BAD_REQUEST, error: `author '${meta.author}' is not a valid GitHub login` };\n if (!SLUG_RE.test(meta.slug)) return { ok: false, status: STATUS_BAD_REQUEST, error: `slug '${meta.slug}' is invalid` };\n\n const wsRoot = path.resolve(workspaceRoot);\n const exportRoot = path.join(wsRoot, ...EXPORT_BASE.split(\"/\"));\n const outDir = resolveWithin(exportRoot, meta.author, meta.slug);\n const safeSkillDir = resolveWithin(wsRoot, skillDir);\n const safeDataDir = resolveWithin(wsRoot, dataDir);\n if (!outDir || !safeSkillDir || !safeDataDir) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: \"resolved path escapes the workspace\" };\n }\n\n const missing = await findMissingRequired(safeSkillDir);\n if (missing) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: `required bundle file missing: ${missing}` };\n }\n\n await rm(outDir, { recursive: true, force: true });\n await mkdir(outDir, { recursive: true });\n\n const bundleCount = await copyBundle(safeSkillDir, outDir);\n const seed = includeSeed ? await exportSeed(safeDataDir, outDir) : { count: 0, skipped: 0, warnings: [] };\n const metaOut = { ...meta, ...(seed.count > 0 ? { dataConsent: true } : {}) };\n await writeFile(path.join(outDir, \"meta.json\"), `${JSON.stringify(metaOut, null, 2)}\\n`, \"utf-8\");\n log.info(\"collections-registry\", \"exported collection\", { slug: meta.slug, author: meta.author, seedCount: seed.count });\n const outRel = toPosixRelPath(path.relative(wsRoot, outDir));\n return { ok: true, outputPath: outRel, fileCount: bundleCount + 1, seedCount: seed.count, seedSkipped: seed.skipped, warnings: seed.warnings };\n}\n","// `description:` reader for a SKILL.md YAML frontmatter envelope.\n//\n// The host (MulmoClaude) parses SKILL.md frontmatter with js-yaml, but\n// @mulmoclaude/core deliberately carries no YAML dependency and export only needs\n// the single `description` scalar for the registry meta.json (best-effort — it\n// defaults to \"\" when absent). Rather than pull in a YAML parser we scan the\n// envelope for the first `description:` line and resolve the common YAML scalar\n// forms so exported descriptions match what the host produced:\n// - double-quoted (\"…\") → unescaped, trailing inline comment ignored\n// - single-quoted ('…') → '' → ', trailing inline comment ignored\n// - plain (unquoted) → trailing \" #comment\" stripped (YAML comment rule)\n// Block scalars (`description: |`) are NOT expanded — they yield \"\" (skill\n// descriptions are single-line in practice).\n\nconst FENCE = \"---\";\nconst KEY = \"description:\";\nconst BLOCK_SCALAR_INDICATORS = new Set([\"|\", \">\", \"|-\", \">-\", \"|+\", \">+\"]);\n\nconst isYamlSpace = (char: string | undefined): boolean => char === \" \" || char === \"\\t\";\n\n// After a closing quote, YAML allows only whitespace and an optional \"#\" comment.\n// Anything else (e.g. `\"foo\" bar`) is malformed — js-yaml would throw, so we\n// reject (→ null → \"\") rather than silently truncate.\nconst isOnlyTrailingComment = (rest: string): boolean => {\n const trimmed = rest.trimStart();\n return trimmed === \"\" || trimmed.startsWith(\"#\");\n};\n\n// Escapes a description realistically carries inside a double-quoted scalar;\n// any other `\\x` collapses to the literal `x`.\nconst DOUBLE_QUOTE_ESCAPES: Record<string, string> = { n: \"\\n\", t: \"\\t\" };\nconst unescapeDoubleQuote = (next: string): string => DOUBLE_QUOTE_ESCAPES[next] ?? next;\n\n// Double-quoted scalar: walk to the closing unescaped quote. Returns null for a\n// malformed scalar (no closing quote, or trailing non-comment text) so the caller\n// degrades to \"\" — matching the host's js-yaml behavior.\nfunction parseDoubleQuoted(value: string): string | null {\n const out: string[] = [];\n // `charAt` (not `value[i]`) so the scanner reads a plain `string`: every\n // index below is in range, and an out-of-range read would be \"\" either way.\n for (let i = 1; i < value.length; i += 1) {\n const char = value.charAt(i);\n if (char === \"\\\\\" && i + 1 < value.length) {\n out.push(unescapeDoubleQuote(value.charAt(i + 1)));\n i += 1;\n continue;\n }\n if (char === '\"') return isOnlyTrailingComment(value.slice(i + 1)) ? out.join(\"\") : null;\n out.push(char);\n }\n return null; // unterminated\n}\n\n// Single-quoted scalar: the only escape is a doubled quote (''). Same\n// malformed-rejection contract as parseDoubleQuoted.\nfunction parseSingleQuoted(value: string): string | null {\n const out: string[] = [];\n for (let i = 1; i < value.length; i += 1) {\n const char = value.charAt(i);\n if (char === \"'\") {\n if (value[i + 1] === \"'\") {\n out.push(\"'\");\n i += 1;\n continue;\n }\n return isOnlyTrailingComment(value.slice(i + 1)) ? out.join(\"\") : null;\n }\n out.push(char);\n }\n return null; // unterminated\n}\n\n// Plain scalar: a \"#\" preceded by whitespace (or at the start) begins a YAML\n// comment; \"#\" not preceded by whitespace (e.g. \"C#\") is literal.\nfunction stripPlainComment(value: string): string {\n for (let i = 0; i < value.length; i += 1) {\n if (value[i] === \"#\" && (i === 0 || isYamlSpace(value[i - 1]))) return value.slice(0, i);\n }\n return value;\n}\n\n/** Extract the frontmatter `description` from raw SKILL.md text. Returns \"\" when\n * there's no `---` envelope, no `description:` key, or the value is a block\n * scalar indicator. */\nexport function parseSkillDescription(raw: string): string {\n const lines = raw.split(/\\r?\\n/);\n if (lines[0]?.trim() !== FENCE) return \"\";\n for (const line of lines.slice(1)) {\n if (line.trim() === FENCE) return \"\"; // end of envelope, key not found\n if (!line.startsWith(KEY)) continue;\n return resolveScalar(line.slice(KEY.length).trim());\n }\n return \"\";\n}\n\n/** Resolve the raw text after `description:` to its scalar value. */\nfunction resolveScalar(value: string): string {\n if (value === \"\" || BLOCK_SCALAR_INDICATORS.has(value)) return \"\";\n if (value.startsWith('\"')) return parseDoubleQuoted(value) ?? \"\";\n if (value.startsWith(\"'\")) return parseSingleQuoted(value) ?? \"\";\n return stripPlainComment(value).trim();\n}\n","// Thin glue for the collection export: resolve the collection from the live workspace\n// (its skill dir + data dir + schema), derive meta from SKILL.md front-matter / any\n// existing meta.json, then hand off to the testable writeCollectionExport. Kept\n// separate so the file-writing core stays import-light + unit-testable.\n\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { loadCollection } from \"../../server/index.js\";\nimport { isRecord } from \"../guards.js\";\nimport { parseSkillDescription } from \"./skillDescription.js\";\nimport { writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\n\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_UNPROCESSABLE = 422;\n\nasync function readJsonObject(file: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed: unknown = JSON.parse(await readFile(file, \"utf-8\"));\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nexport async function performExport(\n slug: string,\n opts: { author: string; license: string; includeSeed: boolean },\n workspaceRoot: string,\n): Promise<ExportResult> {\n const collection = await loadCollection(slug);\n if (!collection) return { ok: false, status: STATUS_NOT_FOUND, error: `collection not found: ${slug}` };\n // Mirrors the import-side refusal: a `dataSource` collection's records are\n // a machine-local data file a bundle can't carry.\n if (collection.schema.dataSource !== undefined) {\n return { ok: false, status: STATUS_UNPROCESSABLE, error: `collection '${slug}' is backed by an external dataSource and cannot be exported to a registry` };\n }\n const skillMd = await readFile(path.join(collection.skillDir, \"SKILL.md\"), \"utf-8\").catch(() => \"\");\n const existingMeta = await readJsonObject(path.join(collection.skillDir, \"meta.json\"));\n const meta: ExportMeta = {\n author: opts.author,\n slug,\n version: typeof existingMeta?.version === \"string\" ? existingMeta.version : \"1.0.0\",\n title: collection.schema.title,\n description: parseSkillDescription(skillMd),\n tags: [],\n license: opts.license,\n };\n return writeCollectionExport({ workspaceRoot, skillDir: collection.skillDir, dataDir: collection.dataDir, meta, includeSeed: opts.includeSeed });\n}\n","// @mulmoclaude/core/collection/registry/server — node-only collection registry\n// engine (Discover catalog fetch/cache, import writer, export writer). Shares the\n// `configureCollectionHost` binding with @mulmoclaude/core/collection/server, so a\n// host wires the workspace + logger + paths ONCE and both engines work. The\n// isomorphic contract types live on @mulmoclaude/core/collection/registry.\n\nimport { fetchAllRegistries } from \"./client.js\";\nimport { performImport } from \"./importWriter.js\";\nimport type { RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\nexport {\n CACHE_TTL_MS,\n STALE_RETRY_BACKOFF_MS,\n fetchAllRegistries,\n fetchRegistryIndex,\n findRegistry,\n listRegistries,\n resetRegistryCache,\n type FetchIndexResult,\n type IndexLoader,\n type MergedRegistryResult,\n type RegistryDescriptor,\n} from \"./client.js\";\nexport {\n collectionFileUrl,\n fetchCollectionFile,\n parseJsonObject,\n previewCollection,\n rawBaseForEntry,\n type FileResult,\n type JsonObjectResult,\n type PreviewResult,\n} from \"./collectionFiles.js\";\nexport {\n fetchBundle,\n fetchManifest,\n isSafeBundlePath,\n normalizedDataPath,\n parseManifest,\n withNormalizedDataPath,\n type BundleFetch,\n type ManifestFetch,\n type ManifestResult,\n} from \"./importCollection.js\";\nexport { claudeSkillDir, dataSkillDir, performImport, writeImportedCollection, type ImportOrigin, type ImportResult } from \"./importWriter.js\";\nexport { EXPORT_BASE, writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\nexport { performExport } from \"./performExport.js\";\nexport { parseSkillDescription } from \"./skillDescription.js\";\nexport { loadRegistriesConfig, parseRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"./registriesConfig.js\";\nexport type { RegistryEntry, RegistryIndex, ParseResult } from \"../registryIndex.js\";\nexport { parseRegistryIndex } from \"../registryIndex.js\";\nexport type { RegistrySummary, RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\n/** Build the merged Discover catalog response (`GET …collectionsRegistry.list`).\n * Wraps `fetchAllRegistries` with the per-registry summary + stale-flag shaping\n * every host needs, so a downstream host doesn't re-implement the mapping. */\nexport async function listRegistry(): Promise<RegistryListResponse> {\n const merged = await fetchAllRegistries();\n return {\n registries: merged.map((reg) => ({\n name: reg.name,\n status: reg.status,\n generatedAt: reg.generatedAt,\n error: reg.error,\n entryCount: reg.entries.length,\n })),\n stale: merged.some((reg) => reg.status === \"stale\"),\n collections: merged.flatMap((reg) => reg.entries),\n };\n}\n\nexport type RegistryImportOutcome = { ok: true; response: RegistryImportResponse } | { ok: false; status: number; error: string };\n\n/** Import a registry collection and return the host-facing response shape\n * (`POST …collectionsRegistry.import`). On failure carries the HTTP status so\n * the host route can pass it straight through. */\nexport async function importRegistry(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<RegistryImportOutcome> {\n const result = await performImport(author, slug, workspaceRoot, registry);\n if (!result.ok) return { ok: false, status: result.status, error: result.error };\n return {\n ok: true,\n response: { localSlug: result.localSlug, updated: result.updated, seedWritten: result.seedWritten, seedSkipped: result.seedSkipped },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/collection/registry/server/registriesConfig.ts","../../../../src/collection/registry/server/client.ts","../../../../src/collection/registry/server/collectionFiles.ts","../../../../src/collection/registry/server/importCollection.ts","../../../../src/collection/registry/server/importWriter.ts","../../../../src/collection/registry/server/exportCollection.ts","../../../../src/collection/registry/server/skillDescription.ts","../../../../src/collection/registry/server/performExport.ts","../../../../src/collection/registry/server/index.ts"],"sourcesContent":["// User-supplied list of extra collection registries (`config/collections-registries.json`).\n// The official receptron/mulmoclaude-collections registry is always loaded; this\n// file adds more on top so a community / org / private registry shows up in the\n// same Discover catalog.\n//\n// Shape on disk:\n// [\n// { \"name\": \"myorg\", \"indexUrl\": \"https://...\", \"rawBaseUrl\": \"https://...\" }\n// ]\n//\n// Validation rules (a bad entry is dropped + logged; the rest still load):\n// - `name` matches `[A-Za-z0-9][A-Za-z0-9_-]{0,31}` and is NOT the reserved\n// `official` (which is always synthesized by client.ts)\n// - Both URLs are HTTPS, parseable, and have no userinfo/credentials\n// - Names are unique (later duplicates are dropped, first wins)\n//\n// Pure read + parse — no network. Caller invalidates by re-reading the file.\n\nimport { readFileSync } from \"node:fs\";\n\nimport { isRecord } from \"../guards.js\";\nimport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"../types.js\";\nimport { collectionsRegistriesConfigPath, log } from \"../../server/host.js\";\n\nexport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry };\n\nconst NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;\n\nfunction isValidHttpsUrl(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0) return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol !== \"https:\") return false;\n if (url.username !== \"\" || url.password !== \"\") return false;\n return true;\n}\n\nfunction parseEntry(value: unknown, index: number): RegistryConfigEntry | string {\n if (!isRecord(value)) return `entry[${index}] is not an object`;\n const { name, indexUrl, rawBaseUrl } = value;\n if (typeof name !== \"string\" || !NAME_RE.test(name)) {\n return `entry[${index}].name must match ${NAME_RE.source}`;\n }\n if (name === OFFICIAL_REGISTRY_NAME) {\n return `entry[${index}].name \"${OFFICIAL_REGISTRY_NAME}\" is reserved`;\n }\n if (!isValidHttpsUrl(indexUrl)) return `entry[${index}].indexUrl must be a valid HTTPS URL (no credentials)`;\n if (!isValidHttpsUrl(rawBaseUrl)) return `entry[${index}].rawBaseUrl must be a valid HTTPS URL (no credentials)`;\n // `rawBaseUrl` gets joined as `${rawBaseUrl}/<path>` downstream — a query or\n // fragment would land in the middle of the composed URL and break every\n // collection-file fetch on that registry (CodeRabbit review on #1837). The\n // index URL is fetched directly so a query is fine there; only rawBase\n // needs the constraint.\n if (rawBaseUrl.includes(\"?\") || rawBaseUrl.includes(\"#\")) {\n return `entry[${index}].rawBaseUrl must not contain a query (?) or fragment (#) — it's joined as a path prefix`;\n }\n // Strip trailing slashes so `${rawBaseUrl}/${path}` joins cleanly regardless\n // of what the user wrote. Plain-loop trim instead of a regex — the linter\n // flags any unanchored-quantifier regex as ReDoS-suspect even when it's\n // safe, and the loop is just as clear at this size.\n let normalizedRawBase = rawBaseUrl;\n while (normalizedRawBase.endsWith(\"/\")) normalizedRawBase = normalizedRawBase.slice(0, -1);\n return { name, indexUrl, rawBaseUrl: normalizedRawBase };\n}\n\n/** Parse the JSON text of `config/collections-registries.json`. Bad entries\n * drop with a warning; valid entries are returned in source order. Exported\n * for unit testing — the I/O wrapper `loadRegistriesConfig` below reads the\n * workspace file and delegates here. */\nexport function parseRegistriesConfig(raw: unknown): RegistryConfigEntry[] {\n if (!Array.isArray(raw)) {\n log.warn(\"collections-registry\", \"registries config is not an array — ignoring\");\n return [];\n }\n const seen = new Set<string>();\n const out: RegistryConfigEntry[] = [];\n for (let i = 0; i < raw.length; i++) {\n const parsed = parseEntry(raw[i], i);\n if (typeof parsed === \"string\") {\n log.warn(\"collections-registry\", \"registry config entry rejected\", { reason: parsed });\n continue;\n }\n if (seen.has(parsed.name)) {\n log.warn(\"collections-registry\", \"registry config duplicate name dropped\", { name: parsed.name });\n continue;\n }\n seen.add(parsed.name);\n out.push(parsed);\n }\n return out;\n}\n\n// Missing file ⇒ null (the most common case — most users only want the official\n// registry). Other read errors propagate, matching the host's prior behavior.\nfunction readConfigTextOrNull(filePath: string): string | null {\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/** Read `config/collections-registries.json` from the configured workspace.\n * Missing file ⇒ empty list. Malformed JSON or rejected entries log but never\n * throw. */\nexport function loadRegistriesConfig(): RegistryConfigEntry[] {\n const text = readConfigTextOrNull(collectionsRegistriesConfigPath());\n if (text === null) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n log.warn(\"collections-registry\", \"registries config is not valid JSON — ignoring\", {\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n return parseRegistriesConfig(parsed);\n}\n","// Server-side client for the curated collection registries' published index.json\n// files. The official registry (receptron/mulmoclaude-collections via GitHub Pages)\n// is always loaded; users can append more via `config/collections-registries.json`.\n// Each index is fetched over HTTPS with a timeout, validated against the index\n// contract, and memo-cached per-registry so the Discover tab doesn't hammer the\n// upstreams. On a transient upstream failure we serve that registry's last good\n// index rather than failing the whole Discover view.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { log } from \"../../server/host.js\";\nimport { parseRegistryIndex, type RegistryEntry, type RegistryIndex } from \"../registryIndex.js\";\nimport { loadRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"./registriesConfig.js\";\n\nconst DEFAULT_OFFICIAL_INDEX_URL = \"https://receptron.github.io/mulmoclaude-collections/index.json\";\nconst DEFAULT_OFFICIAL_RAW_BASE = \"https://raw.githubusercontent.com/receptron/mulmoclaude-collections/main\";\nexport const CACHE_TTL_MS = 5 * 60 * ONE_SECOND_MS;\n// During an outage (cache past TTL + failing upstream) don't re-hit the network\n// more than once per this window — serve stale immediately in between, so a down\n// upstream can't add its full timeout to every request.\nexport const STALE_RETRY_BACKOFF_MS = 60 * ONE_SECOND_MS;\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\n\n/** Per-registry fetch outcome. `ok: false` is reported per-registry — the\n * merged Discover response still succeeds when at least one registry is\n * reachable; failed registries just contribute zero entries. */\nexport type FetchIndexResult = { ok: true; index: RegistryIndex; stale: boolean } | { ok: false; status: number; error: string };\n\ninterface CacheEntry {\n index: RegistryIndex;\n atMs: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\nconst lastFailureMs = new Map<string, number>();\n\n/** Resolved descriptor for one registry the host should fetch — name + URLs.\n * `official` is always synthesized; further entries come from\n * `config/collections-registries.json`. Currently structurally identical to\n * `RegistryConfigEntry`; aliased so we can add server-only fields (e.g.\n * auth headers) without touching the on-disk config schema. */\nexport type RegistryDescriptor = RegistryConfigEntry;\n\nfunction officialDescriptor(): RegistryDescriptor {\n return {\n name: OFFICIAL_REGISTRY_NAME,\n indexUrl: process.env.COLLECTIONS_REGISTRY_URL ?? DEFAULT_OFFICIAL_INDEX_URL,\n rawBaseUrl: process.env.COLLECTIONS_REGISTRY_RAW_BASE ?? DEFAULT_OFFICIAL_RAW_BASE,\n };\n}\n\n/** The full ordered registry list: official first, then user-configured ones.\n * Re-reads the config file on every call so a Discover refresh picks up edits\n * without a server restart (the config is small + read-rarely). */\nexport function listRegistries(): RegistryDescriptor[] {\n return [officialDescriptor(), ...loadRegistriesConfig()];\n}\n\n/** Look up one registry descriptor by name. Used by the preview / import path,\n * which has the registry label from the entry and needs the rawBase. */\nexport function findRegistry(name: string): RegistryDescriptor | null {\n return listRegistries().find((reg) => reg.name === name) ?? null;\n}\n\nasync function loadFromNetwork(descriptor: RegistryDescriptor): Promise<FetchIndexResult> {\n let res: Response;\n try {\n res = await fetchWithTimeout(descriptor.indexUrl, { timeoutMs: FETCH_TIMEOUT_MS, headers: { accept: \"application/json\" } });\n } catch (err) {\n log.warn(\"collections-registry\", \"index fetch failed\", { registry: descriptor.name, url: descriptor.indexUrl, error: errorMessage(err) });\n return { ok: false, status: STATUS_UNAVAILABLE, error: \"registry unreachable\" };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry responded ${res.status}` };\n const json: unknown = await res.json().catch(() => null);\n const parsed = parseRegistryIndex(json, descriptor.name);\n if (!parsed.ok) {\n log.warn(\"collections-registry\", \"index invalid\", { registry: descriptor.name, url: descriptor.indexUrl, error: parsed.error });\n return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry index invalid: ${parsed.error}` };\n }\n return { ok: true, index: parsed.index, stale: false };\n}\n\nexport type IndexLoader = (descriptor: RegistryDescriptor) => Promise<FetchIndexResult>;\n\n/** Cache key incorporates BOTH URLs so editing `indexUrl` or `rawBaseUrl` for\n * an existing registry name invalidates the cached index automatically — same\n * refresh cycle as if the user had renamed it (CodeRabbit review on #1837).\n * Without this, the Discover catalog could keep serving the old upstream's\n * entries while preview / import resolved the new rawBase, drifting until TTL\n * expiry. Tab + key-content guarantees no collision between e.g. swapped\n * name/url pairs. */\nfunction descriptorCacheKey(descriptor: RegistryDescriptor): string {\n return `${descriptor.name}\\t${descriptor.indexUrl}\\t${descriptor.rawBaseUrl}`;\n}\n\n/** Fetch one registry's index. Same cache + stale-on-failure semantics as the\n * original single-registry implementation — just keyed by descriptor identity\n * (name + both URLs) so multiple registries don't fight over one slot AND a\n * reconfigured registry doesn't serve a stale index from the prior URL. */\nexport async function fetchRegistryIndex(\n descriptor: RegistryDescriptor,\n opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } = {},\n): Promise<FetchIndexResult> {\n const nowMs = opts.nowMs ?? Date.now();\n const loader = opts.loader ?? loadFromNetwork;\n const key = descriptorCacheKey(descriptor);\n const cached = cache.get(key);\n if (!opts.force && cached && nowMs - cached.atMs < CACHE_TTL_MS) {\n return { ok: true, index: cached.index, stale: false };\n }\n const failedAt = lastFailureMs.get(key);\n if (!opts.force && cached && failedAt !== undefined && nowMs - failedAt < STALE_RETRY_BACKOFF_MS) {\n return { ok: true, index: cached.index, stale: true };\n }\n const fresh = await loader(descriptor);\n if (fresh.ok) {\n cache.set(key, { index: fresh.index, atMs: nowMs });\n lastFailureMs.delete(key);\n return { ok: true, index: fresh.index, stale: false };\n }\n lastFailureMs.set(key, nowMs);\n if (cached) return { ok: true, index: cached.index, stale: true };\n return fresh;\n}\n\n/** One registry's contribution to the merged Discover view. `failed` registries\n * surface in the response so the UI can show a per-registry error badge while\n * still rendering the entries from the registries that did work. */\nexport interface MergedRegistryResult {\n name: string;\n status: \"ok\" | \"stale\" | \"failed\";\n generatedAt: string | null;\n error: string | null;\n entries: RegistryEntry[];\n}\n\n/** Fetch every configured registry in parallel and return per-registry\n * outcomes. Callers (the Discover route) concatenate `entries` from each.\n * Failure of any single registry doesn't abort the others — that's the point\n * of supporting multiple registries. */\nexport async function fetchAllRegistries(opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } = {}): Promise<MergedRegistryResult[]> {\n const descriptors = listRegistries();\n return await Promise.all(\n descriptors.map(async (descriptor): Promise<MergedRegistryResult> => {\n const result = await fetchRegistryIndex(descriptor, opts);\n if (!result.ok) {\n return { name: descriptor.name, status: \"failed\", generatedAt: null, error: result.error, entries: [] };\n }\n return {\n name: descriptor.name,\n status: result.stale ? \"stale\" : \"ok\",\n generatedAt: result.index.generatedAt,\n error: null,\n entries: result.index.collections,\n };\n }),\n );\n}\n\n/** Test seam: reset the module cache + failure backoff state (all registries). */\nexport function resetRegistryCache(): void {\n cache.clear();\n lastFailureMs.clear();\n}\n","// Fetch individual files of a registry collection (schema.json, meta.json, …)\n// from the registry repo's raw content. The collection's repo-relative dir comes\n// from a trusted index entry (never raw user input), so the host can only fetch\n// files of collections that actually appear in the published index.\n//\n// The index itself is served from GitHub Pages (see client.ts); per-collection\n// files are served from raw.githubusercontent. With multi-registry support the\n// rawBase is no longer a single module-level value — it comes from the entry's\n// source registry descriptor, so user-added registries can live anywhere.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries, findRegistry } from \"./client.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\nconst STATUS_NOT_FOUND = 404;\n\n/** Compose the raw URL for `<dirPath>/<relFile>` under a given registry's\n * rawBase. Empty and traversal (`.`/`..`) segments are dropped so the URL can\n * never escape the base, even if an upstream check is bypassed (the index\n * parser already rejects such identifiers — this is defense-in-depth). The\n * rawBase is trailing-slash-normalized: `parseRegistriesConfig` already trims\n * user-config trailing slashes, but the official descriptor and any test\n * bypass parse — repeating the trim here keeps the join `${base}/${path}`\n * from producing `//` even when the caller bypassed config validation\n * (CodeRabbit review on #1837). */\nexport function collectionFileUrl(rawBase: string, dirPath: string, relFile: string): string {\n let base = rawBase;\n while (base.endsWith(\"/\")) base = base.slice(0, -1);\n const segments = dirPath.split(\"/\").filter((segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\");\n return `${base}/${segments.join(\"/\")}/${relFile}`;\n}\n\nexport type FileResult = { ok: true; text: string } | { ok: false; status: number; error: string };\n\n/** Fetch one file out of a registry collection. `rawBase` comes from the\n * entry's source registry (`findRegistry(entry.registryName).rawBaseUrl`),\n * not a module-level setting — that's what makes additional user-configured\n * registries reachable. */\nexport async function fetchCollectionFile(rawBase: string, dirPath: string, relFile: string): Promise<FileResult> {\n const url = collectionFileUrl(rawBase, dirPath, relFile);\n let res: Response;\n try {\n res = await fetchWithTimeout(url, { timeoutMs: FETCH_TIMEOUT_MS });\n } catch (err) {\n return { ok: false, status: STATUS_UNAVAILABLE, error: `fetch failed: ${errorMessage(err)}` };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `${relFile} responded ${res.status}` };\n return { ok: true, text: await res.text() };\n}\n\nexport type JsonObjectResult = { ok: true; value: Record<string, unknown> } | { ok: false; error: string };\n\nexport function parseJsonObject(text: string, label: string): JsonObjectResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return { ok: false, error: `${label} is not valid JSON` };\n }\n if (!isRecord(parsed)) return { ok: false, error: `${label} is not an object` };\n return { ok: true, value: parsed };\n}\n\nasync function fetchJsonObject(\n rawBase: string,\n dirPath: string,\n relFile: string,\n label: string,\n): Promise<{ ok: true; value: Record<string, unknown> } | { ok: false; status: number; error: string }> {\n const file = await fetchCollectionFile(rawBase, dirPath, relFile);\n if (!file.ok) return { ok: false, status: file.status, error: `${label}: ${file.error}` };\n const parsed = parseJsonObject(file.text, label);\n if (!parsed.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: parsed.error };\n return { ok: true, value: parsed.value };\n}\n\n/** Resolve an entry's rawBase from its `registryName`. A missing match (the\n * user removed the registry from config while a cached index still references\n * it) returns null — the caller surfaces it as a 404 rather than crashing. */\nexport function rawBaseForEntry(entry: Pick<RegistryEntry, \"registryName\">): string | null {\n const registry = findRegistry(entry.registryName);\n return registry?.rawBaseUrl ?? null;\n}\n\nexport type PreviewResult =\n { ok: true; entry: RegistryEntry; schema: Record<string, unknown>; meta: Record<string, unknown> } | { ok: false; status: number; error: string };\n\n/** Resolve an entry by author+slug across every cached registry's entries.\n * When `registry` is passed we constrain to that registry — needed because\n * multiple registries can publish the same author/slug, and the UI should\n * follow the card it just clicked. */\nfunction findEntryInMergedView(\n merged: { name: string; entries: RegistryEntry[] }[],\n author: string,\n slug: string,\n registry: string | null,\n): RegistryEntry | null {\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n const match = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (match) return match;\n }\n return null;\n}\n\n/** Preview a registry collection: confirm it's in some registry's published\n * index, then fetch + parse its schema.json + meta.json so the Discover tab\n * can show fields/views before import. With multi-registry support the\n * `registry` arg disambiguates same-name collections from different sources. */\nexport async function previewCollection(author: string, slug: string, registry: string | null = null): Promise<PreviewResult> {\n const merged = await fetchAllRegistries();\n const entry = findEntryInMergedView(merged, author, slug, registry);\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const rawBase = rawBaseForEntry(entry);\n if (!rawBase) return { ok: false, status: STATUS_NOT_FOUND, error: `registry \"${entry.registryName}\" is no longer configured` };\n const schema = await fetchJsonObject(rawBase, entry.path, \"schema.json\", \"schema.json\");\n if (!schema.ok) return schema;\n const meta = await fetchJsonObject(rawBase, entry.path, \"meta.json\", \"meta.json\");\n if (!meta.ok) return meta;\n return { ok: true, entry, schema: schema.value, meta: meta.value };\n}\n","// Import-side fetch + transform helpers for a registry collection. The writer\n// (writes into .claude/skills/, materializes seed, records provenance) builds on\n// these; kept separate so the pure, security-critical parts are unit-tested.\n//\n// Files to fetch come from the collection's manifest.json (published by the\n// registry's build-index). Every manifest path is re-checked for safety here —\n// the host must never write outside the target skill dir even if the manifest is\n// malformed/poisoned.\n\nimport { isRecord } from \"../guards.js\";\nimport { fetchCollectionFile, parseJsonObject, rawBaseForEntry } from \"./collectionFiles.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst MANIFEST_FILE = \"manifest.json\";\nconst STATUS_BAD_GATEWAY = 502;\n\n/** A manifest entry must be a relative path that stays inside the collection dir:\n * no absolute paths, no backslashes, no empty / `.` / `..` segments. */\nexport function isSafeBundlePath(rel: unknown): rel is string {\n if (typeof rel !== \"string\" || rel.length === 0) return false;\n if (rel.startsWith(\"/\") || rel.includes(\"\\\\\")) return false;\n return !rel.split(\"/\").some((segment) => segment === \"\" || segment === \".\" || segment === \"..\");\n}\n\nexport type ManifestResult = { ok: true; files: string[] } | { ok: false; error: string };\n\nexport function parseManifest(value: unknown): ManifestResult {\n if (!isRecord(value) || !Array.isArray(value.files)) return { ok: false, error: \"manifest is missing a files[] array\" };\n const unsafe = value.files.find((file) => !isSafeBundlePath(file));\n if (unsafe !== undefined) return { ok: false, error: `manifest contains an unsafe path: ${String(unsafe)}` };\n return { ok: true, files: value.files.filter(isSafeBundlePath) };\n}\n\n/** `data/collections/<localSlug>/items` — the host owns dataPath, never the\n * registry's authored value, so imported collections can't collide on disk. */\nexport function normalizedDataPath(localSlug: string): string {\n return `data/collections/${localSlug}/items`;\n}\n\nexport function withNormalizedDataPath(schema: Record<string, unknown>, localSlug: string): Record<string, unknown> {\n return { ...schema, dataPath: normalizedDataPath(localSlug) };\n}\n\nexport type ManifestFetch = { ok: true; files: string[] } | { ok: false; status: number; error: string };\n\nexport async function fetchManifest(entry: RegistryEntry): Promise<ManifestFetch> {\n const rawBase = rawBaseForEntry(entry);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const file = await fetchCollectionFile(rawBase, entry.path, MANIFEST_FILE);\n if (!file.ok) return { ok: false, status: file.status, error: `manifest.json: ${file.error}` };\n const obj = parseJsonObject(file.text, \"manifest.json\");\n if (!obj.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: obj.error };\n const manifest = parseManifest(obj.value);\n if (!manifest.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: manifest.error };\n return { ok: true, files: manifest.files };\n}\n\nexport type BundleFetch = { ok: true; files: Map<string, string> } | { ok: false; status: number; error: string };\n\n/** Fetch every manifest file. Paths are already safety-checked by parseManifest. */\nexport async function fetchBundle(entry: RegistryEntry, fileList: readonly string[]): Promise<BundleFetch> {\n const rawBase = rawBaseForEntry(entry);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const files = new Map<string, string>();\n for (const rel of fileList) {\n const file = await fetchCollectionFile(rawBase, entry.path, rel);\n if (!file.ok) return { ok: false, status: file.status, error: `${rel}: ${file.error}` };\n files.set(rel, file.text);\n }\n return { ok: true, files };\n}\n","// Import a registry collection into the active workspace. The writer fetches\n// the bundle, re-validates the schema with the host's own gates (R7 — the\n// index is not a trust boundary), writes the bundle into `data/skills/<localSlug>/`\n// with a host-owned dataPath (R3), materializes any seed records into that\n// dataPath when it's empty, and records provenance in `.origin.json` for\n// update detection (R5/R8). After the data-skills swap, the bundle's\n// allowlisted files (SKILL.md, schema.json, templates/<safe>) are mirrored\n// 1:1 into `.claude/skills/<localSlug>/` via the same skill-bridge rules an\n// agent-authored skill would go through — so an authored and an imported\n// collection live in EXACTLY the same place on disk, and the user can edit\n// either one identically.\n//\n// `.origin.json` lives ONLY in `data/skills/<slug>/.origin.json` and is NOT\n// mirrored — the skill-bridge allowlist deliberately excludes host\n// bookkeeping. Its presence is what distinguishes \"imported\" from\n// \"user-authored\" in every downstream tool.\n//\n// `writeImportedCollection` takes the already-fetched bundle + an explicit\n// workspaceRoot/clock so it is unit-testable against a temp workspace with\n// no network; `performImport` is the thin glue that fetches and calls it.\n\nimport { mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { acceptParsedSchema, CollectionSchemaZ, isSafeActionTemplatePath, safeRecordId } from \"../../server/index.js\";\nimport type { CollectionSchema } from \"../../index.js\";\nimport { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from \"../../../skill-bridge/index.js\";\n\nimport { log } from \"../../server/host.js\";\nimport { errorMessage } from \"../../server/util.js\";\nimport { writeFileAtomic } from \"../../../files/atomic.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries } from \"./client.js\";\nimport { fetchBundle, fetchManifest, normalizedDataPath } from \"./importCollection.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst ORIGIN_FILE = \".origin.json\";\nconst SEED_PREFIX = \"seed/items/\";\nconst SCHEMA_FILE = \"schema.json\";\nconst SKILL_FILE = \"SKILL.md\";\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_CONFLICT = 409;\nconst STATUS_UNPROCESSABLE = 422;\n\nexport interface ImportOrigin {\n registry: string;\n author: string;\n slug: string;\n version: string;\n contentSha: string;\n importedAt: string;\n}\n\nexport type ImportResult =\n { ok: true; localSlug: string; updated: boolean; seedWritten: number; seedSkipped: boolean } | { ok: false; status: number; error: string };\n\nasync function statType(target: string): Promise<\"dir\" | \"other\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"other\";\n } catch (err) {\n // EACCES etc. are path-shape conflicts mkdir would later throw on → \"other\".\n // A file ancestor surfaces as ENOTDIR on POSIX but ENOENT on Windows (which\n // reports the whole path as missing), so ENOENT can't be trusted as \"absent\"\n // until we've confirmed no existing ancestor is a non-directory file.\n if (!(isRecord(err) && err.code === \"ENOENT\")) return \"other\";\n return (await hasNonDirAncestor(target)) ? \"other\" : \"absent\";\n }\n}\n\n// Walk up from `target` to the nearest ancestor that exists: a non-directory\n// file there means `target` can never be mkdir'd (a path-shape conflict → 409),\n// whichever errno the platform's `stat(target)` chose. An all-absent-or-directory\n// chain up to the filesystem root is a genuine \"absent\".\nasync function hasNonDirAncestor(target: string): Promise<boolean> {\n let current = path.dirname(target);\n while (current !== path.dirname(current)) {\n try {\n return !(await stat(current)).isDirectory();\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") {\n current = path.dirname(current);\n continue;\n }\n return true;\n }\n }\n return false;\n}\n\nasync function isEmptyOrAbsentDir(target: string): Promise<boolean> {\n try {\n return (await readdir(target)).length === 0;\n } catch {\n return true;\n }\n}\n\nfunction originMatches(origin: unknown, registry: string, author: string, slug: string): boolean {\n return isRecord(origin) && origin.registry === registry && origin.author === author && origin.slug === slug;\n}\n\nasync function readOrigin(targetDir: string): Promise<unknown> {\n try {\n return JSON.parse(await readFile(path.join(targetDir, ORIGIN_FILE), \"utf-8\"));\n } catch {\n return null;\n }\n}\n\ntype TargetResolution = { targetDir: string; localSlug: string; updated: boolean } | { conflict: string };\n\n// Only bounds the fresh-slug search (a safety cap, far above any realistic\n// number of same-named collections — the first free slug is normally found in\n// 1–2 iterations). An EXISTING install is found via the directory scan below,\n// not this loop, so updates are never missed regardless of how high the rename\n// suffix is.\nconst MAX_SLUG_ATTEMPTS = 10000;\n\nconst renameCandidate = (slug: string, attempt: number): string => (attempt === 0 ? slug : `${slug}-${attempt + 1}`);\n\n// True when `name` is the registry slug or a `<slug>-<n>` rename of it.\nfunction isRenameOf(name: string, slug: string): boolean {\n if (name === slug) return true;\n if (!name.startsWith(`${slug}-`)) return false;\n const suffix = name.slice(slug.length + 1);\n return suffix.length > 0 && /^\\d+$/.test(suffix);\n}\n\n// Find an existing install of this registry collection at ANY rename suffix\n// by scanning `data/skills/` — independent of any candidate bound, so a\n// re-import always updates the existing install rather than duplicating it\n// (even if an earlier slug freed up). An authored skill without `.origin.json`\n// is invisible to this scan and so never collides with an update.\nasync function findMatchingInstall(skillsDir: string, registry: string, entry: RegistryEntry): Promise<string | null> {\n const names = await readdir(skillsDir).catch((): string[] => []);\n for (const name of names) {\n if (!isRenameOf(name, entry.slug)) continue;\n const dir = path.join(skillsDir, name);\n if ((await statType(dir)) === \"dir\" && originMatches(await readOrigin(dir), registry, entry.author, entry.slug)) return name;\n }\n return null;\n}\n\n// Pick the local install slug (rename-on-collision, R8). First reuse an\n// existing matching install (update). Otherwise install fresh at the first\n// free slug — the registry slug, else `<slug>-2`, `-3`, … — never clobbering\n// a user's own same-named collection.\n//\n// A slug is \"free\" only when BOTH `data/skills/<slug>/` AND\n// `.claude/skills/<slug>/` are absent. The mirror check matters because\n// `mirrorToClaudeSkills` overwrites the mirror dir's allowlisted files and\n// prunes anything else — if the user has a manually-installed Claude skill\n// at `.claude/skills/<slug>/` with no corresponding `data/skills/<slug>/`\n// (e.g. installed by the Claude CLI directly, or a legacy pre-refactor\n// import we never migrated), picking that slug would silently overwrite\n// their SKILL.md/schema.json and prune the rest of their files (CodeRabbit\n// review on #1839).\nasync function resolveTarget(workspaceRoot: string, registry: string, entry: RegistryEntry): Promise<TargetResolution> {\n const skillsDir = path.dirname(dataSkillDir(workspaceRoot, entry.slug));\n const existing = await findMatchingInstall(skillsDir, registry, entry);\n if (existing) return { targetDir: dataSkillDir(workspaceRoot, existing), localSlug: existing, updated: true };\n for (let attempt = 0; attempt < MAX_SLUG_ATTEMPTS; attempt += 1) {\n const localSlug = renameCandidate(entry.slug, attempt);\n const sourceAbsent = (await statType(dataSkillDir(workspaceRoot, localSlug))) === \"absent\";\n const mirrorAbsent = (await statType(claudeSkillDir(workspaceRoot, localSlug))) === \"absent\";\n if (sourceAbsent && mirrorAbsent) {\n return { targetDir: dataSkillDir(workspaceRoot, localSlug), localSlug, updated: false };\n }\n }\n return { conflict: `couldn't find an available slug for '${entry.slug}'` };\n}\n\ntype SchemaResolution = { schema: CollectionSchema } | { error: string };\n\nfunction validateAndNormalize(bundle: Map<string, string>, localSlug: string, workspaceRoot: string): SchemaResolution {\n // SKILL.md is required: `mirrorToClaudeSkills` calls\n // `mirrorSkillWrite({slug, relSegments: [SKILL_FILE]})` unconditionally\n // after the data-side swap, which throws if SKILL.md is missing. Caught\n // outside, that throw is logged but the data-side write still succeeds and\n // the function returns `ok: true` for an unusable import. Reject up front\n // so a missing SKILL.md surfaces as a clean 422 instead (CodeRabbit review\n // on #1839).\n if (bundle.get(SKILL_FILE) === undefined) return { error: \"bundle is missing SKILL.md\" };\n const schemaText = bundle.get(SCHEMA_FILE);\n if (schemaText === undefined) return { error: \"bundle is missing schema.json\" };\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(schemaText);\n } catch {\n return { error: \"schema.json is not valid JSON\" };\n }\n const parsed = CollectionSchemaZ.safeParse(parsedJson);\n if (!parsed.success) return { error: `schema.json failed validation: ${parsed.error.issues[0]?.message ?? \"invalid\"}` };\n // A `dataSource` collection can't be imported: its records are the rows of\n // a machine-local data file the bundle doesn't (and can't) carry — and the\n // R3 dataPath stamp below would collide with the exactly-one-of rule.\n if (parsed.data.dataSource !== undefined) {\n return { error: \"schema.json declares `dataSource` — external-data collections are machine-local and cannot be imported from a registry\" };\n }\n const schema: CollectionSchema = { ...parsed.data, dataPath: normalizedDataPath(localSlug) };\n const acceptance = acceptParsedSchema(schema, { source: \"project\", workspaceRoot, slug: localSlug });\n if (!acceptance.ok) return { error: `schema.json rejected: ${acceptance.reason}` };\n return { schema };\n}\n\nasync function writeBundleFiles(targetDir: string, bundle: Map<string, string>, schema: CollectionSchema): Promise<void> {\n for (const [rel, content] of bundle) {\n if (rel.startsWith(SEED_PREFIX)) continue; // seed goes to dataPath, not the skill dir\n const dest = path.join(targetDir, ...rel.split(\"/\"));\n if (dest !== targetDir && !dest.startsWith(targetDir + path.sep)) continue; // belt-and-suspenders (paths pre-validated)\n await mkdir(path.dirname(dest), { recursive: true });\n await writeFile(dest, rel === SCHEMA_FILE ? `${JSON.stringify(schema, null, 2)}\\n` : content, \"utf-8\");\n }\n}\n\nasync function materializeSeed(dataDir: string, bundle: Map<string, string>): Promise<{ written: number; skipped: boolean }> {\n const seedEntries = [...bundle].filter(([rel]) => rel.startsWith(SEED_PREFIX));\n if (seedEntries.length === 0) return { written: 0, skipped: false };\n if (!(await isEmptyOrAbsentDir(dataDir))) return { written: 0, skipped: true };\n await mkdir(dataDir, { recursive: true });\n let written = 0;\n for (const [rel, content] of seedEntries) {\n const fileName = rel.slice(SEED_PREFIX.length);\n if (fileName.includes(\"/\") || safeRecordId(fileName.replace(/\\.json$/, \"\")) === null) {\n log.warn(\"collections-registry\", \"skipped unsafe seed record\", { rel });\n continue;\n }\n await writeFile(path.join(dataDir, fileName), content, \"utf-8\");\n written += 1;\n }\n return { written, skipped: false };\n}\n\n/** Compute the set of bundle-relative paths that should land in `.claude/skills/<slug>/`.\n * Matches the bridge allowlist exactly — SKILL.md, schema.json,\n * `templates/<safe>` — so anything else (seed, meta.json, views, README,\n * assets) stays source-side. */\nfunction bridgeAllowlistFiles(bundle: Map<string, string>): Set<string> {\n const wanted = new Set<string>([SKILL_FILE, SCHEMA_FILE]);\n for (const rel of bundle.keys()) {\n if (rel === SKILL_FILE || rel === SCHEMA_FILE) continue;\n if (rel.startsWith(SEED_PREFIX)) continue;\n if (!isSafeActionTemplatePath(rel)) continue;\n wanted.add(rel);\n }\n return wanted;\n}\n\n/** Recursively list every file under `root`, returned as forward-slash paths\n * relative to `root`. Empty list when `root` doesn't exist. Used to spot\n * mirror files left over from a previous install so we can prune them after\n * writing the new ones. */\nasync function listFilesRecursive(root: string, prefix = \"\"): Promise<string[]> {\n const dir = prefix ? path.join(root, ...prefix.split(\"/\")) : root;\n const entries: Dirent[] = await readdir(dir, { withFileTypes: true }).catch((): Dirent[] => []);\n const out: string[] = [];\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isFile()) out.push(rel);\n else if (entry.isDirectory()) out.push(...(await listFilesRecursive(root, rel)));\n }\n return out;\n}\n\n// Mirror the just-written `data/skills/<slug>/` into `.claude/skills/<slug>/`\n// using the shared bridge package — exactly the set of files an\n// agent-authored skill would mirror, no more (SKILL.md, schema.json,\n// templates/<safe>). `.origin.json` is host bookkeeping and stays only on the\n// data side.\n//\n// **Write-then-prune ordering** (Codex review on #1839): write the new\n// bundle's allowlisted files FIRST (each is a per-file tmp+rename, atomic),\n// THEN prune mirror files that aren't in the new bundle. A transient mirror\n// failure during the writes leaves the previous mirror's files in place —\n// agent discovery keeps working with the prior state instead of finding an\n// empty `.claude/skills/<slug>/`. The prune step is best-effort: a failure\n// there leaves harmless stale leftovers but doesn't break the new install.\n// Compare with the old `mirrorSkillDelete`-first ordering, which could\n// silently leave an empty mirror if a subsequent write threw.\nasync function mirrorToClaudeSkills(workspaceRoot: string, localSlug: string, bundle: Map<string, string>): Promise<void> {\n const wanted = bridgeAllowlistFiles(bundle);\n // 1. Write the new set first (overwrites existing via tmp+rename).\n for (const rel of wanted) {\n mirrorSkillWrite(workspaceRoot, { slug: localSlug, relSegments: rel.split(\"/\") });\n }\n // 2. Prune files that exist in the mirror but aren't in the new bundle.\n const mirrorRoot = claudeSkillDir(workspaceRoot, localSlug);\n const existing = await listFilesRecursive(mirrorRoot);\n for (const rel of existing) {\n if (wanted.has(rel)) continue;\n await rm(path.join(mirrorRoot, ...rel.split(\"/\")), { force: true }).catch(() => undefined);\n }\n}\n\nexport async function writeImportedCollection(params: {\n registry: string;\n entry: RegistryEntry;\n bundle: Map<string, string>;\n workspaceRoot: string;\n nowIso: string;\n}): Promise<ImportResult> {\n const { registry, entry, bundle, workspaceRoot, nowIso } = params;\n const target = await resolveTarget(workspaceRoot, registry, entry);\n if (\"conflict\" in target) return { ok: false, status: STATUS_CONFLICT, error: target.conflict };\n\n const { localSlug } = target;\n\n // Pre-flight the data dir before schema validation: a non-directory at the dataPath\n // (or an ancestor that's a file → ENOTDIR) would otherwise surface as a generic 500\n // on mkdir. statType maps ENOTDIR/other to a deterministic 409 path-shape conflict.\n const dataDir = path.join(workspaceRoot, ...normalizedDataPath(localSlug).split(\"/\"));\n if ((await statType(dataDir)) === \"other\") {\n return { ok: false, status: STATUS_CONFLICT, error: `data path for slug '${localSlug}' exists and is not a directory` };\n }\n\n const validated = validateAndNormalize(bundle, localSlug, workspaceRoot);\n if (\"error\" in validated) return { ok: false, status: STATUS_UNPROCESSABLE, error: validated.error };\n\n // Build the replacement fully in a hidden sibling staging dir under\n // `data/skills/` (bundle + origin), so the prior install is untouched until\n // everything is durably written. Leftover staging/backup dirs from a\n // crashed import are cleaned first, keeping retries possible.\n const skillsParent = path.dirname(target.targetDir);\n await mkdir(skillsParent, { recursive: true }); // first-import case — data/skills/ may not exist\n const staging = path.join(skillsParent, `.importing-${localSlug}`);\n const backup = path.join(skillsParent, `.backup-${localSlug}`);\n await rm(staging, { recursive: true, force: true });\n await rm(backup, { recursive: true, force: true });\n await mkdir(staging, { recursive: true });\n await writeBundleFiles(staging, bundle, validated.schema);\n const origin: ImportOrigin = { registry, author: entry.author, slug: entry.slug, version: entry.version, contentSha: entry.contentSha, importedAt: nowIso };\n await writeFileAtomic(path.join(staging, ORIGIN_FILE), `${JSON.stringify(origin, null, 2)}\\n`);\n\n // Swap with rollback: move the old install aside (rename), move the new in\n // (rename), then discard the old. If the swap fails, restore the old so we\n // never end up with no installed collection. Records live in dataPath (a\n // separate dir) and are untouched.\n if (target.updated) await rename(target.targetDir, backup);\n try {\n await rename(staging, target.targetDir);\n } catch (err) {\n if (target.updated) await rename(backup, target.targetDir).catch(() => undefined);\n throw err;\n }\n await rm(backup, { recursive: true, force: true });\n\n // Replicate the new `data/skills/<slug>/` set into `.claude/skills/<slug>/`\n // via the shared bridge package — same allowlist + tmp+rename semantics the\n // hook uses for agent-authored writes. Write-then-prune ordering inside\n // mirrorToClaudeSkills means a transient failure here can't leave the mirror\n // empty (Codex review on #1839); the worst case is a half-written mirror\n // where the new install's SKILL.md/schema.json may not have updated, but the\n // prior install remains accessible to the agent until the next mirror\n // attempt. Logged-not-thrown matches the hook's posture for agent writes.\n try {\n await mirrorToClaudeSkills(workspaceRoot, localSlug, bundle);\n } catch (err) {\n log.warn(\"collections-registry\", \"mirror to .claude/skills/ failed (data/skills write succeeded; prior mirror left intact)\", {\n localSlug,\n error: errorMessage(err),\n });\n }\n\n const seed = await materializeSeed(dataDir, bundle);\n return { ok: true, localSlug, updated: target.updated, seedWritten: seed.written, seedSkipped: seed.skipped };\n}\n\nexport async function performImport(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<ImportResult> {\n const merged = await fetchAllRegistries();\n let entry: RegistryEntry | undefined;\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n entry = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (entry) break;\n }\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const manifest = await fetchManifest(entry);\n if (!manifest.ok) return { ok: false, status: manifest.status, error: manifest.error };\n const bundle = await fetchBundle(entry, manifest.files);\n if (!bundle.ok) return { ok: false, status: bundle.status, error: bundle.error };\n try {\n return await writeImportedCollection({\n registry: entry.registryName,\n entry,\n bundle: bundle.files,\n workspaceRoot,\n nowIso: new Date().toISOString(),\n });\n } catch (err) {\n log.warn(\"collections-registry\", \"import write failed\", { author, slug, registry: entry.registryName, error: errorMessage(err) });\n return { ok: false, status: 500, error: `import failed: ${errorMessage(err)}` };\n }\n}\n\n// Exported for downstream code that wants the path conventions without\n// importing skill-bridge directly.\nexport { claudeSkillDir, dataSkillDir, hasNonDirAncestor };\n","// Export a workspace collection into the curated registry's contribution layout\n// (collections/<author>/<slug>/ + meta.json + optional seed/) under\n// data/registry-export/, so the user can open a PR to the registry. The producer\n// side of the registry loop.\n//\n// writeCollectionExport takes explicit dirs + a workspace root so it is unit-testable\n// against temp dirs with no discovery/network; performExport (separate file) is the\n// thin glue that resolves the collection from the live workspace.\n\nimport { cp, mkdir, readdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { log } from \"../../server/host.js\";\nimport { toPosixRelPath } from \"../../../files/relPath.js\";\n\nexport const EXPORT_BASE = \"data/registry-export\";\nconst BUNDLE_FILES = [\"SKILL.md\", \"schema.json\"] as const;\nconst OPTIONAL_FILES = [\"screenshot.png\"] as const;\nconst BUNDLE_DIRS = [\"views\", \"templates\"] as const;\nconst AUTHOR_RE = /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/;\nconst SLUG_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;\nconst STATUS_BAD_REQUEST = 400;\n// Block obvious credentials from being published in seed data; PII is only warned.\nconst SECRET_PATTERNS = [\n /AKIA[0-9A-Z]{16}/,\n /gh[pousr]_[A-Za-z0-9]{20,}/,\n /-----BEGIN [A-Z ]*PRIVATE KEY-----/,\n /sk-[A-Za-z0-9]{20,}/,\n /xox[baprs]-[A-Za-z0-9-]{10,}/,\n];\nconst hasSecret = (text: string): boolean => SECRET_PATTERNS.some((pattern) => pattern.test(text));\n// Bounded + non-overlapping (no ReDoS): local@label.tld with single quantifier runs.\nconst EMAIL_RE = /[\\w.%+-]{1,64}@[\\w-]{1,255}\\.[a-z]{2,24}/i;\n\nexport interface ExportMeta {\n author: string;\n slug: string;\n version: string;\n title: string;\n description: string;\n tags: string[];\n license: string;\n}\n\nexport type ExportResult =\n | { ok: true; outputPath: string; fileCount: number; seedCount: number; seedSkipped: number; warnings: string[] }\n | { ok: false; status: number; error: string };\n\nasync function pathKind(target: string): Promise<\"dir\" | \"file\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"file\";\n } catch {\n return \"absent\";\n }\n}\n\nasync function copyBundle(skillDir: string, outDir: string): Promise<number> {\n let count = 0;\n for (const file of [...BUNDLE_FILES, ...OPTIONAL_FILES]) {\n const src = path.join(skillDir, file);\n if ((await pathKind(src)) !== \"file\") continue;\n await cp(src, path.join(outDir, file));\n count += 1;\n }\n for (const dir of BUNDLE_DIRS) {\n const src = path.join(skillDir, dir);\n if ((await pathKind(src)) !== \"dir\") continue;\n await cp(src, path.join(outDir, dir), { recursive: true });\n count += (await readdir(src)).filter((name) => !name.startsWith(\".\")).length;\n }\n return count;\n}\n\nasync function exportSeed(dataDir: string, outDir: string): Promise<{ count: number; skipped: number; warnings: string[] }> {\n let names: string[];\n try {\n names = (await readdir(dataDir)).filter((name) => name.endsWith(\".json\"));\n } catch {\n return { count: 0, skipped: 0, warnings: [] };\n }\n if (names.length === 0) return { count: 0, skipped: 0, warnings: [] };\n const seedDir = path.join(outDir, \"seed\", \"items\");\n await mkdir(seedDir, { recursive: true });\n const warnings: string[] = [];\n let count = 0;\n let skipped = 0;\n for (const name of names) {\n const base = path.basename(name);\n const text = await readFile(path.join(dataDir, base), \"utf-8\");\n if (hasSecret(text)) {\n warnings.push(`skipped ${base}: contains a possible credential`);\n skipped += 1;\n continue;\n }\n if (EMAIL_RE.test(text)) warnings.push(`${base}: contains a possible email/PII (kept — your responsibility)`);\n await writeFile(path.join(seedDir, base), text, \"utf-8\");\n count += 1;\n }\n return { count, skipped, warnings };\n}\n\n// Resolve `segments` under `root` and return the absolute path only when it stays\n// inside `root` — a containment barrier for the user-derived author/slug and the\n// collection dirs, on top of the AUTHOR_RE/SLUG_RE format checks. `..` or an\n// absolute escape resolves outside `root` and yields null (rejected upstream).\nfunction resolveWithin(root: string, ...segments: string[]): string | null {\n const resolvedRoot = path.resolve(root);\n const target = path.resolve(resolvedRoot, ...segments);\n return target === resolvedRoot || target.startsWith(resolvedRoot + path.sep) ? target : null;\n}\n\n// Required bundle files (SKILL.md, schema.json) must be present — exporting an\n// incomplete bundle would pass here but fail downstream registry curation.\n// Returns the first missing required file, or null when all are present.\nasync function findMissingRequired(skillDir: string): Promise<string | null> {\n for (const file of BUNDLE_FILES) {\n if ((await pathKind(path.join(skillDir, file))) !== \"file\") return file;\n }\n return null;\n}\n\nexport async function writeCollectionExport(params: {\n workspaceRoot: string;\n skillDir: string;\n dataDir: string;\n meta: ExportMeta;\n includeSeed: boolean;\n}): Promise<ExportResult> {\n const { workspaceRoot, skillDir, dataDir, meta, includeSeed } = params;\n if (!AUTHOR_RE.test(meta.author)) return { ok: false, status: STATUS_BAD_REQUEST, error: `author '${meta.author}' is not a valid GitHub login` };\n if (!SLUG_RE.test(meta.slug)) return { ok: false, status: STATUS_BAD_REQUEST, error: `slug '${meta.slug}' is invalid` };\n\n const wsRoot = path.resolve(workspaceRoot);\n const exportRoot = path.join(wsRoot, ...EXPORT_BASE.split(\"/\"));\n const outDir = resolveWithin(exportRoot, meta.author, meta.slug);\n const safeSkillDir = resolveWithin(wsRoot, skillDir);\n const safeDataDir = resolveWithin(wsRoot, dataDir);\n if (!outDir || !safeSkillDir || !safeDataDir) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: \"resolved path escapes the workspace\" };\n }\n\n const missing = await findMissingRequired(safeSkillDir);\n if (missing) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: `required bundle file missing: ${missing}` };\n }\n\n await rm(outDir, { recursive: true, force: true });\n await mkdir(outDir, { recursive: true });\n\n const bundleCount = await copyBundle(safeSkillDir, outDir);\n const seed = includeSeed ? await exportSeed(safeDataDir, outDir) : { count: 0, skipped: 0, warnings: [] };\n const metaOut = { ...meta, ...(seed.count > 0 ? { dataConsent: true } : {}) };\n await writeFile(path.join(outDir, \"meta.json\"), `${JSON.stringify(metaOut, null, 2)}\\n`, \"utf-8\");\n log.info(\"collections-registry\", \"exported collection\", { slug: meta.slug, author: meta.author, seedCount: seed.count });\n const outRel = toPosixRelPath(path.relative(wsRoot, outDir));\n return { ok: true, outputPath: outRel, fileCount: bundleCount + 1, seedCount: seed.count, seedSkipped: seed.skipped, warnings: seed.warnings };\n}\n","// `description:` reader for a SKILL.md YAML frontmatter envelope.\n//\n// The host (MulmoClaude) parses SKILL.md frontmatter with js-yaml, but\n// @mulmoclaude/core deliberately carries no YAML dependency and export only needs\n// the single `description` scalar for the registry meta.json (best-effort — it\n// defaults to \"\" when absent). Rather than pull in a YAML parser we scan the\n// envelope for the first `description:` line and resolve the common YAML scalar\n// forms so exported descriptions match what the host produced:\n// - double-quoted (\"…\") → unescaped, trailing inline comment ignored\n// - single-quoted ('…') → '' → ', trailing inline comment ignored\n// - plain (unquoted) → trailing \" #comment\" stripped (YAML comment rule)\n// Block scalars (`description: |`) are NOT expanded — they yield \"\" (skill\n// descriptions are single-line in practice).\n\nconst FENCE = \"---\";\nconst KEY = \"description:\";\nconst BLOCK_SCALAR_INDICATORS = new Set([\"|\", \">\", \"|-\", \">-\", \"|+\", \">+\"]);\n\nconst isYamlSpace = (char: string | undefined): boolean => char === \" \" || char === \"\\t\";\n\n// After a closing quote, YAML allows only whitespace and an optional \"#\" comment.\n// Anything else (e.g. `\"foo\" bar`) is malformed — js-yaml would throw, so we\n// reject (→ null → \"\") rather than silently truncate.\nconst isOnlyTrailingComment = (rest: string): boolean => {\n const trimmed = rest.trimStart();\n return trimmed === \"\" || trimmed.startsWith(\"#\");\n};\n\n// Escapes a description realistically carries inside a double-quoted scalar;\n// any other `\\x` collapses to the literal `x`.\nconst DOUBLE_QUOTE_ESCAPES: Record<string, string> = { n: \"\\n\", t: \"\\t\" };\nconst unescapeDoubleQuote = (next: string): string => DOUBLE_QUOTE_ESCAPES[next] ?? next;\n\n// The text a two-character escape pair at `index` stands for, or null when the\n// pair isn't an escape — which makes `index` an ordinary character (possibly the\n// closing quote). Both YAML quoted forms differ only in this one rule.\ntype EscapeReader = (value: string, index: number) => string | null;\n\n// Walk a quoted scalar from just past its opening quote to the closing one.\n// Returns null for a malformed scalar (no closing quote, or trailing non-comment\n// text) so the caller degrades to \"\" — matching the host's js-yaml behavior.\nfunction scanQuotedScalar(value: string, quote: string, readEscape: EscapeReader): string | null {\n const out: string[] = [];\n // `charAt` (not `value[i]`) so the scanner reads a plain `string`: every\n // index below is in range, and an out-of-range read would be \"\" either way.\n for (let i = 1; i < value.length; i += 1) {\n const escaped = readEscape(value, i);\n if (escaped !== null) {\n out.push(escaped);\n i += 1;\n continue;\n }\n const char = value.charAt(i);\n if (char === quote) return isOnlyTrailingComment(value.slice(i + 1)) ? out.join(\"\") : null;\n out.push(char);\n }\n return null; // unterminated\n}\n\n// A trailing backslash has nothing to escape, so it stays a literal backslash.\nconst readDoubleQuoteEscape: EscapeReader = (value, index) =>\n value.charAt(index) === \"\\\\\" && index + 1 < value.length ? unescapeDoubleQuote(value.charAt(index + 1)) : null;\n\n// The only escape in a single-quoted scalar is a doubled quote ('').\nconst readSingleQuoteEscape: EscapeReader = (value, index) => (value.charAt(index) === \"'\" && value[index + 1] === \"'\" ? \"'\" : null);\n\nfunction parseDoubleQuoted(value: string): string | null {\n return scanQuotedScalar(value, '\"', readDoubleQuoteEscape);\n}\n\nfunction parseSingleQuoted(value: string): string | null {\n return scanQuotedScalar(value, \"'\", readSingleQuoteEscape);\n}\n\n// Plain scalar: a \"#\" preceded by whitespace (or at the start) begins a YAML\n// comment; \"#\" not preceded by whitespace (e.g. \"C#\") is literal.\nfunction stripPlainComment(value: string): string {\n for (let i = 0; i < value.length; i += 1) {\n if (value[i] === \"#\" && (i === 0 || isYamlSpace(value[i - 1]))) return value.slice(0, i);\n }\n return value;\n}\n\n/** Extract the frontmatter `description` from raw SKILL.md text. Returns \"\" when\n * there's no `---` envelope, no `description:` key, or the value is a block\n * scalar indicator. */\nexport function parseSkillDescription(raw: string): string {\n const lines = raw.split(/\\r?\\n/);\n if (lines[0]?.trim() !== FENCE) return \"\";\n for (const line of lines.slice(1)) {\n if (line.trim() === FENCE) return \"\"; // end of envelope, key not found\n if (!line.startsWith(KEY)) continue;\n return resolveScalar(line.slice(KEY.length).trim());\n }\n return \"\";\n}\n\n/** Resolve the raw text after `description:` to its scalar value. */\nfunction resolveScalar(value: string): string {\n if (value === \"\" || BLOCK_SCALAR_INDICATORS.has(value)) return \"\";\n if (value.startsWith('\"')) return parseDoubleQuoted(value) ?? \"\";\n if (value.startsWith(\"'\")) return parseSingleQuoted(value) ?? \"\";\n return stripPlainComment(value).trim();\n}\n","// Thin glue for the collection export: resolve the collection from the live workspace\n// (its skill dir + data dir + schema), derive meta from SKILL.md front-matter / any\n// existing meta.json, then hand off to the testable writeCollectionExport. Kept\n// separate so the file-writing core stays import-light + unit-testable.\n\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { loadCollection } from \"../../server/index.js\";\nimport { isRecord } from \"../guards.js\";\nimport { parseSkillDescription } from \"./skillDescription.js\";\nimport { writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\n\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_UNPROCESSABLE = 422;\n\nasync function readJsonObject(file: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed: unknown = JSON.parse(await readFile(file, \"utf-8\"));\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nexport async function performExport(\n slug: string,\n opts: { author: string; license: string; includeSeed: boolean },\n workspaceRoot: string,\n): Promise<ExportResult> {\n const collection = await loadCollection(slug);\n if (!collection) return { ok: false, status: STATUS_NOT_FOUND, error: `collection not found: ${slug}` };\n // Mirrors the import-side refusal: a `dataSource` collection's records are\n // a machine-local data file a bundle can't carry.\n if (collection.schema.dataSource !== undefined) {\n return { ok: false, status: STATUS_UNPROCESSABLE, error: `collection '${slug}' is backed by an external dataSource and cannot be exported to a registry` };\n }\n const skillMd = await readFile(path.join(collection.skillDir, \"SKILL.md\"), \"utf-8\").catch(() => \"\");\n const existingMeta = await readJsonObject(path.join(collection.skillDir, \"meta.json\"));\n const meta: ExportMeta = {\n author: opts.author,\n slug,\n version: typeof existingMeta?.version === \"string\" ? existingMeta.version : \"1.0.0\",\n title: collection.schema.title,\n description: parseSkillDescription(skillMd),\n tags: [],\n license: opts.license,\n };\n return writeCollectionExport({ workspaceRoot, skillDir: collection.skillDir, dataDir: collection.dataDir, meta, includeSeed: opts.includeSeed });\n}\n","// @mulmoclaude/core/collection/registry/server — node-only collection registry\n// engine (Discover catalog fetch/cache, import writer, export writer). Shares the\n// `configureCollectionHost` binding with @mulmoclaude/core/collection/server, so a\n// host wires the workspace + logger + paths ONCE and both engines work. The\n// isomorphic contract types live on @mulmoclaude/core/collection/registry.\n\nimport { fetchAllRegistries } from \"./client.js\";\nimport { performImport } from \"./importWriter.js\";\nimport type { RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\nexport {\n CACHE_TTL_MS,\n STALE_RETRY_BACKOFF_MS,\n fetchAllRegistries,\n fetchRegistryIndex,\n findRegistry,\n listRegistries,\n resetRegistryCache,\n type FetchIndexResult,\n type IndexLoader,\n type MergedRegistryResult,\n type RegistryDescriptor,\n} from \"./client.js\";\nexport {\n collectionFileUrl,\n fetchCollectionFile,\n parseJsonObject,\n previewCollection,\n rawBaseForEntry,\n type FileResult,\n type JsonObjectResult,\n type PreviewResult,\n} from \"./collectionFiles.js\";\nexport {\n fetchBundle,\n fetchManifest,\n isSafeBundlePath,\n normalizedDataPath,\n parseManifest,\n withNormalizedDataPath,\n type BundleFetch,\n type ManifestFetch,\n type ManifestResult,\n} from \"./importCollection.js\";\nexport { claudeSkillDir, dataSkillDir, performImport, writeImportedCollection, type ImportOrigin, type ImportResult } from \"./importWriter.js\";\nexport { EXPORT_BASE, writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\nexport { performExport } from \"./performExport.js\";\nexport { parseSkillDescription } from \"./skillDescription.js\";\nexport { loadRegistriesConfig, parseRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"./registriesConfig.js\";\nexport type { RegistryEntry, RegistryIndex, ParseResult } from \"../registryIndex.js\";\nexport { parseRegistryIndex } from \"../registryIndex.js\";\nexport type { RegistrySummary, RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\n/** Build the merged Discover catalog response (`GET …collectionsRegistry.list`).\n * Wraps `fetchAllRegistries` with the per-registry summary + stale-flag shaping\n * every host needs, so a downstream host doesn't re-implement the mapping. */\nexport async function listRegistry(): Promise<RegistryListResponse> {\n const merged = await fetchAllRegistries();\n return {\n registries: merged.map((reg) => ({\n name: reg.name,\n status: reg.status,\n generatedAt: reg.generatedAt,\n error: reg.error,\n entryCount: reg.entries.length,\n })),\n stale: merged.some((reg) => reg.status === \"stale\"),\n collections: merged.flatMap((reg) => reg.entries),\n };\n}\n\nexport type RegistryImportOutcome = { ok: true; response: RegistryImportResponse } | { ok: false; status: number; error: string };\n\n/** Import a registry collection and return the host-facing response shape\n * (`POST …collectionsRegistry.import`). On failure carries the HTTP status so\n * the host route can pass it straight through. */\nexport async function importRegistry(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<RegistryImportOutcome> {\n const result = await performImport(author, slug, workspaceRoot, registry);\n if (!result.ok) return { ok: false, status: result.status, error: result.error };\n return {\n ok: true,\n response: { localSlug: result.localSlug, updated: result.updated, seedWritten: result.seedWritten, seedSkipped: result.seedSkipped },\n };\n}\n"],"mappings":"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"}
|