@mulmoclaude/core 1.0.1 → 1.2.0
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/assets/helps/collection-skills.md +28 -0
- package/dist/artifacts/paths.cjs +68 -0
- package/dist/artifacts/paths.cjs.map +1 -0
- package/dist/artifacts/paths.d.ts +45 -0
- package/dist/artifacts/paths.js +62 -0
- package/dist/artifacts/paths.js.map +1 -0
- package/dist/atomic-C_7YpMiM.cjs +102 -0
- package/dist/atomic-C_7YpMiM.cjs.map +1 -0
- package/dist/atomic-DPpdrJzO.js +89 -0
- package/dist/atomic-DPpdrJzO.js.map +1 -0
- package/dist/collection/core/completion.d.ts +21 -0
- package/dist/collection/core/ids.d.ts +5 -0
- package/dist/collection/core/linkTargets.d.ts +14 -0
- package/dist/collection/core/ownProp.d.ts +9 -0
- package/dist/collection/core/recordKeys.d.ts +14 -0
- package/dist/collection/core/schemaRules.d.ts +167 -0
- package/dist/collection/core/schemaZ.d.ts +489 -9
- package/dist/collection/core/sortValueOf.d.ts +21 -0
- package/dist/collection/core/textSearch.d.ts +12 -0
- package/dist/collection/index.cjs +109 -29
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +4 -0
- package/dist/collection/index.js +98 -29
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +14 -42
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +6 -34
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/discovery.d.ts +7 -0
- package/dist/collection/server/host.d.ts +3 -8
- package/dist/collection/server/index.cjs +10 -9
- package/dist/collection/server/index.d.ts +1 -0
- package/dist/collection/server/index.js +4 -4
- package/dist/collection/server/io.d.ts +0 -88
- package/dist/collection/server/skillAssets.d.ts +90 -0
- package/dist/collection-watchers/config.d.ts +2 -4
- package/dist/collection-watchers/index.cjs +5 -5
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +5 -5
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/{discovery-BYIsLqag.js → discovery-DYlEOa9a.js} +426 -392
- package/dist/discovery-DYlEOa9a.js.map +1 -0
- package/dist/{discovery-Dpz6SWwe.cjs → discovery-UxkvBxV-.cjs} +444 -434
- package/dist/discovery-UxkvBxV-.cjs.map +1 -0
- package/dist/dist-Cwk0e12G.js +50 -0
- package/dist/dist-Cwk0e12G.js.map +1 -0
- package/dist/dist-pWpC-b04.cjs +61 -0
- package/dist/dist-pWpC-b04.cjs.map +1 -0
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/host.d.ts +5 -8
- package/dist/feeds/server/index.cjs +143 -61
- package/dist/feeds/server/index.cjs.map +1 -1
- package/dist/feeds/server/index.js +141 -59
- package/dist/feeds/server/index.js.map +1 -1
- package/dist/files/atomic.d.ts +34 -0
- package/dist/files/index.cjs +47 -0
- package/dist/files/index.cjs.map +1 -0
- package/dist/files/index.d.ts +3 -0
- package/dist/files/index.js +40 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/json.d.ts +4 -0
- package/dist/files/safe.d.ts +9 -0
- package/dist/google/auth.d.ts +1 -1
- package/dist/google/collectionDateTime.d.ts +5 -0
- package/dist/google/collectionSync.d.ts +2 -2
- package/dist/google/fsJson.d.ts +3 -2
- package/dist/google/host.d.ts +3 -6
- package/dist/google/index.cjs +64 -91
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +1 -0
- package/dist/google/index.js +57 -85
- package/dist/google/index.js.map +1 -1
- package/dist/host/hostSlot.d.ts +27 -0
- package/dist/{ids-DWmHjm17.cjs → ids-BJ7rdrDN.cjs} +17 -1
- package/dist/{ids-DWmHjm17.cjs.map → ids-BJ7rdrDN.cjs.map} +1 -1
- package/dist/{ids-D1M1T6KJ.js → ids-Bv6AOW9Z.js} +12 -2
- package/dist/{ids-D1M1T6KJ.js.map → ids-Bv6AOW9Z.js.map} +1 -1
- package/dist/{ingestTypes-DbuQNuK6.cjs → ingestTypes-CkbM-zrO.cjs} +2 -2
- package/dist/{ingestTypes-DbuQNuK6.cjs.map → ingestTypes-CkbM-zrO.cjs.map} +1 -1
- package/dist/{ingestTypes-Ci4eOgv-.js → ingestTypes-DuCiZqye.js} +2 -2
- package/dist/{ingestTypes-Ci4eOgv-.js.map → ingestTypes-DuCiZqye.js.map} +1 -1
- package/dist/notifier/engine.d.ts +2 -4
- package/dist/notifier/index.cjs +1 -1
- package/dist/notifier/index.js +1 -1
- package/dist/{notifier-ChpY0XrY.js → notifier-BdA5qzhe.js} +14 -16
- package/dist/notifier-BdA5qzhe.js.map +1 -0
- package/dist/{notifier-bS8IEeLA.cjs → notifier-tMsAXyXp.cjs} +14 -16
- package/dist/notifier-tMsAXyXp.cjs.map +1 -0
- package/dist/plugin-vue/fileWatch.d.ts +5 -0
- package/dist/plugin-vue/i18n.cjs +45 -0
- package/dist/plugin-vue/i18n.cjs.map +1 -0
- package/dist/plugin-vue/i18n.js +44 -0
- package/dist/plugin-vue/i18n.js.map +1 -0
- package/dist/plugin-vue/index.cjs +98 -0
- package/dist/plugin-vue/index.cjs.map +1 -0
- package/dist/plugin-vue/index.d.ts +5 -0
- package/dist/plugin-vue/index.js +91 -0
- package/dist/plugin-vue/index.js.map +1 -0
- package/dist/plugin-vue/markdownDoc.d.ts +10 -0
- package/dist/plugin-vue/pluginI18n.d.ts +22 -0
- package/dist/plugin-vue/useClipboardCopy.d.ts +6 -0
- package/dist/plugin-vue/useFileWatch.d.ts +8 -0
- package/dist/plugin-vue/useMarkdownDoc.d.ts +3 -0
- package/dist/{promptSafety-Bugq2kqL.js → promptSafety-BNelBKhh.js} +79 -7
- package/dist/promptSafety-BNelBKhh.js.map +1 -0
- package/dist/{promptSafety-DbE6eZmP.cjs → promptSafety-QAtADXUF.cjs} +108 -6
- package/dist/promptSafety-QAtADXUF.cjs.map +1 -0
- package/dist/remote-host/server/hostRunner.d.ts +2 -1
- package/dist/remote-host/server/index.cjs +6 -5
- 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/scheduler/index.cjs +3 -3
- package/dist/scheduler/index.cjs.map +1 -1
- package/dist/scheduler/index.js +2 -2
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/task-manager.d.ts +2 -5
- package/dist/{server-CtCBLYgZ.js → server-CnwgECW-.js} +209 -41
- package/dist/server-CnwgECW-.js.map +1 -0
- package/dist/{server-BjWrcc5T.cjs → server-DFo3zFYB.cjs} +250 -46
- package/dist/server-DFo3zFYB.cjs.map +1 -0
- package/dist/skill-bridge/index.cjs +1 -1
- package/dist/skill-bridge/index.js +1 -1
- package/dist/translation/client.cjs +16 -0
- package/dist/translation/client.cjs.map +1 -1
- package/dist/translation/client.d.ts +6 -0
- package/dist/translation/client.js +16 -1
- package/dist/translation/client.js.map +1 -1
- package/dist/utils/errors.d.ts +2 -1
- package/dist/utils/fetch.cjs +36 -0
- package/dist/utils/fetch.cjs.map +1 -0
- package/dist/{collection/registry/server → utils}/fetch.d.ts +5 -3
- package/dist/utils/fetch.js +34 -0
- package/dist/utils/fetch.js.map +1 -0
- package/dist/utils/index.cjs +11 -3
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.js +9 -1
- package/dist/utils/index.js.map +1 -0
- package/dist/whisper/index.cjs +9 -9
- package/dist/whisper/index.cjs.map +1 -1
- package/dist/whisper/index.js +3 -3
- package/dist/wiki/index.cjs +3 -16
- package/dist/wiki/index.cjs.map +1 -1
- package/dist/wiki/index.js +1 -14
- package/dist/wiki/index.js.map +1 -1
- package/dist/wiki/render.d.ts +2 -4
- package/dist/wiki/server/frontmatter.d.ts +2 -6
- package/dist/wiki/server/index.cjs +4 -40
- package/dist/wiki/server/index.cjs.map +1 -1
- package/dist/wiki/server/index.js +2 -38
- package/dist/wiki/server/index.js.map +1 -1
- package/dist/workspace-setup/index.js +22 -6
- package/dist/workspace-setup/index.js.map +1 -1
- package/package.json +50 -8
- package/dist/collection/server/atomic.d.ts +0 -1
- package/dist/discovery-BYIsLqag.js.map +0 -1
- package/dist/discovery-Dpz6SWwe.cjs.map +0 -1
- package/dist/errors-7P5eMOSX.cjs +0 -30
- package/dist/errors-7P5eMOSX.cjs.map +0 -1
- package/dist/errors-eid6Mes3.js +0 -19
- package/dist/errors-eid6Mes3.js.map +0 -1
- package/dist/google/fetch.d.ts +0 -8
- package/dist/notifier-ChpY0XrY.js.map +0 -1
- package/dist/notifier-bS8IEeLA.cjs.map +0 -1
- package/dist/promptSafety-Bugq2kqL.js.map +0 -1
- package/dist/promptSafety-DbE6eZmP.cjs.map +0 -1
- package/dist/server-BjWrcc5T.cjs.map +0 -1
- package/dist/server-CtCBLYgZ.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region ../common/dist/index.js
|
|
2
|
+
/** Narrow `unknown` to a plain object (not null, not array). */
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
/** Check that a record has a specific key with a string value. */
|
|
7
|
+
function hasStringProp(value, key) {
|
|
8
|
+
return isRecord(value) && typeof value[key] === "string";
|
|
9
|
+
}
|
|
10
|
+
/** Normalise an unknown thrown value into a human-readable string. Isomorphic
|
|
11
|
+
* (host, bridges, plugins, Vue) — this is the single home for the helper that
|
|
12
|
+
* #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`
|
|
13
|
+
* is server-only.
|
|
14
|
+
*
|
|
15
|
+
* A non-Error object with a non-empty string `details` (gRPC convention) or
|
|
16
|
+
* `message` field surfaces that field — `details` wins — instead of the
|
|
17
|
+
* `[object Object]` a bare `String(err)` would print; an empty-string field
|
|
18
|
+
* falls through. `fallback` covers the error-boundary idiom where a thrown
|
|
19
|
+
* non-Error should read as a descriptive message rather than `String(err)`
|
|
20
|
+
* noise; omit it in logging contexts where `String(err)` is fine. */
|
|
21
|
+
function errorMessage(err, fallback) {
|
|
22
|
+
if (err instanceof Error) return err.message;
|
|
23
|
+
if (hasStringProp(err, "details") && err.details) return err.details;
|
|
24
|
+
if (hasStringProp(err, "message") && err.message) return err.message;
|
|
25
|
+
if (fallback !== void 0) return fallback;
|
|
26
|
+
return String(err);
|
|
27
|
+
}
|
|
28
|
+
var HTML_ESCAPES = /* @__PURE__ */ new Map([
|
|
29
|
+
["&", "&"],
|
|
30
|
+
["<", "<"],
|
|
31
|
+
[">", ">"],
|
|
32
|
+
["\"", """],
|
|
33
|
+
["'", "'"]
|
|
34
|
+
]);
|
|
35
|
+
/** HTML-escape text destined for markup or an attribute value — the fixed
|
|
36
|
+
* five-character map, nothing more. Lives here rather than in
|
|
37
|
+
* `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a
|
|
38
|
+
* leaf that core depends on and so cannot import back up; core/wiki
|
|
39
|
+
* re-exports this, keeping its consumers' import path unchanged.
|
|
40
|
+
*
|
|
41
|
+
* Escaping `&` first is what makes a single pass safe — the entities this
|
|
42
|
+
* introduces contain none of the other four characters, so nothing is
|
|
43
|
+
* double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */
|
|
44
|
+
function escapeHtml(value) {
|
|
45
|
+
return value.replace(/[&<>"']/g, (char) => HTML_ESCAPES.get(char) ?? char);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { escapeHtml as n, errorMessage as t };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=dist-Cwk0e12G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dist-Cwk0e12G.js","names":[],"sources":["../../common/dist/index.js"],"sourcesContent":["// General-purpose runtime type guards, shared across the MulmoClaude host,\n// bridges, and plugins. This is a leaf package — pure and dependency-free — so\n// any tier can import it without creating an uphill edge.\n//\n// These originated as `server/utils/types.ts` (#504), which centralised 40+\n// hand-written inline `typeof x === \"object\"` checks. They are promoted here so\n// the same guards stop being re-hand-written in every bridge and plugin too.\n/** Narrow `unknown` to a plain object (not null, not array). */\nexport function isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n/** Narrow `unknown` to any object (not null, arrays allowed).\n * Use `isRecord` when you need to access string keys. */\nexport function isObj(value) {\n return typeof value === \"object\" && value !== null;\n}\n/** Non-empty string after trimming whitespace. */\nexport function isNonEmptyString(value) {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n/** Record whose values are all strings. */\nexport function isStringRecord(value) {\n if (!isRecord(value))\n return false;\n return Object.values(value).every((val) => typeof val === \"string\");\n}\n/** String array (every element is a string). */\nexport function isStringArray(value) {\n return Array.isArray(value) && value.every((val) => typeof val === \"string\");\n}\n/** An array of unknowns. Prefer this over a bare `Array.isArray` in typed code:\n * `Array.isArray(x: unknown)` narrows to `any[]`, silently reintroducing\n * `any`, whereas this keeps the element type `unknown`. */\nexport function isUnknownArray(value) {\n return Array.isArray(value);\n}\n/** Error-like object with a `code` property (e.g. Node.js fs errors). */\nexport function isErrorWithCode(value) {\n return isRecord(value) && typeof value.code === \"string\";\n}\n/** Check that a record has a specific key with a string value. */\nexport function hasStringProp(value, key) {\n return isRecord(value) && typeof value[key] === \"string\";\n}\n/** Check that a record has a specific key with a number value. */\nexport function hasNumberProp(value, key) {\n return isRecord(value) && typeof value[key] === \"number\";\n}\n/** Split a comma-separated env value into trimmed, non-empty entries.\n * `lowercase` folds case for identifiers compared case-insensitively\n * (JIDs, email addresses, hex pubkeys). Absent/empty input → empty list. */\nexport function parseCsvList(raw, opts) {\n return (raw ?? \"\")\n .split(\",\")\n .map((entry) => (opts?.lowercase ? entry.trim().toLowerCase() : entry.trim()))\n .filter(Boolean);\n}\n/** A comma-separated env value as a Set — the canonical allowlist shape,\n * where an empty set is the \"allow all\" sentinel (`set.size === 0`). */\nexport function parseCsvSet(raw, opts) {\n return new Set(parseCsvList(raw, opts));\n}\n/** Normalise an unknown thrown value into a human-readable string. Isomorphic\n * (host, bridges, plugins, Vue) — this is the single home for the helper that\n * #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`\n * is server-only.\n *\n * A non-Error object with a non-empty string `details` (gRPC convention) or\n * `message` field surfaces that field — `details` wins — instead of the\n * `[object Object]` a bare `String(err)` would print; an empty-string field\n * falls through. `fallback` covers the error-boundary idiom where a thrown\n * non-Error should read as a descriptive message rather than `String(err)`\n * noise; omit it in logging contexts where `String(err)` is fine. */\nexport function errorMessage(err, fallback) {\n if (err instanceof Error)\n return err.message;\n if (hasStringProp(err, \"details\") && err.details)\n return err.details;\n if (hasStringProp(err, \"message\") && err.message)\n return err.message;\n if (fallback !== undefined)\n return fallback;\n return String(err);\n}\n/** `Date` → `YYYY-MM-DD` in UTC — for dates that must not shift with the\n * host's local timezone (tool-trace search dirs, API date keys). Isomorphic\n * single source (#2480): the host re-exports it from `server/utils/date.ts`,\n * x-plugin imports it directly. The `@receptron/task-scheduler` copy stays\n * local on purpose — that leaf package is published independently and kept\n * dependency-free. Wall-clock questions use the host's `toLocalIsoDate`. */\nexport function toUtcIsoDate(timestamp) {\n const year = timestamp.getUTCFullYear();\n const month = String(timestamp.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(timestamp.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n// A Map, not an object literal: `{}[char]` reads through the prototype chain,\n// so a future caller widening the regex would silently get `[object Object]`\n// for keys like `constructor`.\nconst HTML_ESCAPES = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n ['\"', \""\"],\n [\"'\", \"'\"],\n]);\n/** HTML-escape text destined for markup or an attribute value — the fixed\n * five-character map, nothing more. Lives here rather than in\n * `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a\n * leaf that core depends on and so cannot import back up; core/wiki\n * re-exports this, keeping its consumers' import path unchanged.\n *\n * Escaping `&` first is what makes a single pass safe — the entities this\n * introduces contain none of the other four characters, so nothing is\n * double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */\nexport function escapeHtml(value) {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES.get(char) ?? char);\n}\nexport { scanEnvOptions, snakeToLowerCamel } from \"./envScan.js\";\n"],"mappings":";;AAQA,SAAgB,SAAS,OAAO;CAC5B,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC9E;;AA+BA,SAAgB,cAAc,OAAO,KAAK;CACtC,OAAO,SAAS,KAAK,KAAK,OAAO,MAAM,SAAS;AACpD;;;;;;;;;;;;AA8BA,SAAgB,aAAa,KAAK,UAAU;CACxC,IAAI,eAAe,OACf,OAAO,IAAI;CACf,IAAI,cAAc,KAAK,SAAS,KAAK,IAAI,SACrC,OAAO,IAAI;CACf,IAAI,cAAc,KAAK,SAAS,KAAK,IAAI,SACrC,OAAO,IAAI;CACf,IAAI,aAAa,KAAA,GACb,OAAO;CACX,OAAO,OAAO,GAAG;AACrB;AAgBA,IAAM,+BAAe,IAAI,IAAI;CACzB,CAAC,KAAK,OAAO;CACb,CAAC,KAAK,MAAM;CACZ,CAAC,KAAK,MAAM;CACZ,CAAC,MAAK,QAAQ;CACd,CAAC,KAAK,OAAO;AACjB,CAAC;;;;;;;;;;AAUD,SAAgB,WAAW,OAAO;CAC9B,OAAO,MAAM,QAAQ,aAAa,SAAS,aAAa,IAAI,IAAI,KAAK,IAAI;AAC7E"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region ../common/dist/index.js
|
|
2
|
+
/** Narrow `unknown` to a plain object (not null, not array). */
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
/** Check that a record has a specific key with a string value. */
|
|
7
|
+
function hasStringProp(value, key) {
|
|
8
|
+
return isRecord(value) && typeof value[key] === "string";
|
|
9
|
+
}
|
|
10
|
+
/** Normalise an unknown thrown value into a human-readable string. Isomorphic
|
|
11
|
+
* (host, bridges, plugins, Vue) — this is the single home for the helper that
|
|
12
|
+
* #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`
|
|
13
|
+
* is server-only.
|
|
14
|
+
*
|
|
15
|
+
* A non-Error object with a non-empty string `details` (gRPC convention) or
|
|
16
|
+
* `message` field surfaces that field — `details` wins — instead of the
|
|
17
|
+
* `[object Object]` a bare `String(err)` would print; an empty-string field
|
|
18
|
+
* falls through. `fallback` covers the error-boundary idiom where a thrown
|
|
19
|
+
* non-Error should read as a descriptive message rather than `String(err)`
|
|
20
|
+
* noise; omit it in logging contexts where `String(err)` is fine. */
|
|
21
|
+
function errorMessage(err, fallback) {
|
|
22
|
+
if (err instanceof Error) return err.message;
|
|
23
|
+
if (hasStringProp(err, "details") && err.details) return err.details;
|
|
24
|
+
if (hasStringProp(err, "message") && err.message) return err.message;
|
|
25
|
+
if (fallback !== void 0) return fallback;
|
|
26
|
+
return String(err);
|
|
27
|
+
}
|
|
28
|
+
var HTML_ESCAPES = /* @__PURE__ */ new Map([
|
|
29
|
+
["&", "&"],
|
|
30
|
+
["<", "<"],
|
|
31
|
+
[">", ">"],
|
|
32
|
+
["\"", """],
|
|
33
|
+
["'", "'"]
|
|
34
|
+
]);
|
|
35
|
+
/** HTML-escape text destined for markup or an attribute value — the fixed
|
|
36
|
+
* five-character map, nothing more. Lives here rather than in
|
|
37
|
+
* `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a
|
|
38
|
+
* leaf that core depends on and so cannot import back up; core/wiki
|
|
39
|
+
* re-exports this, keeping its consumers' import path unchanged.
|
|
40
|
+
*
|
|
41
|
+
* Escaping `&` first is what makes a single pass safe — the entities this
|
|
42
|
+
* introduces contain none of the other four characters, so nothing is
|
|
43
|
+
* double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */
|
|
44
|
+
function escapeHtml(value) {
|
|
45
|
+
return value.replace(/[&<>"']/g, (char) => HTML_ESCAPES.get(char) ?? char);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
Object.defineProperty(exports, "errorMessage", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function() {
|
|
51
|
+
return errorMessage;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(exports, "escapeHtml", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function() {
|
|
57
|
+
return escapeHtml;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=dist-pWpC-b04.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dist-pWpC-b04.cjs","names":[],"sources":["../../common/dist/index.js"],"sourcesContent":["// General-purpose runtime type guards, shared across the MulmoClaude host,\n// bridges, and plugins. This is a leaf package — pure and dependency-free — so\n// any tier can import it without creating an uphill edge.\n//\n// These originated as `server/utils/types.ts` (#504), which centralised 40+\n// hand-written inline `typeof x === \"object\"` checks. They are promoted here so\n// the same guards stop being re-hand-written in every bridge and plugin too.\n/** Narrow `unknown` to a plain object (not null, not array). */\nexport function isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n/** Narrow `unknown` to any object (not null, arrays allowed).\n * Use `isRecord` when you need to access string keys. */\nexport function isObj(value) {\n return typeof value === \"object\" && value !== null;\n}\n/** Non-empty string after trimming whitespace. */\nexport function isNonEmptyString(value) {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n/** Record whose values are all strings. */\nexport function isStringRecord(value) {\n if (!isRecord(value))\n return false;\n return Object.values(value).every((val) => typeof val === \"string\");\n}\n/** String array (every element is a string). */\nexport function isStringArray(value) {\n return Array.isArray(value) && value.every((val) => typeof val === \"string\");\n}\n/** An array of unknowns. Prefer this over a bare `Array.isArray` in typed code:\n * `Array.isArray(x: unknown)` narrows to `any[]`, silently reintroducing\n * `any`, whereas this keeps the element type `unknown`. */\nexport function isUnknownArray(value) {\n return Array.isArray(value);\n}\n/** Error-like object with a `code` property (e.g. Node.js fs errors). */\nexport function isErrorWithCode(value) {\n return isRecord(value) && typeof value.code === \"string\";\n}\n/** Check that a record has a specific key with a string value. */\nexport function hasStringProp(value, key) {\n return isRecord(value) && typeof value[key] === \"string\";\n}\n/** Check that a record has a specific key with a number value. */\nexport function hasNumberProp(value, key) {\n return isRecord(value) && typeof value[key] === \"number\";\n}\n/** Split a comma-separated env value into trimmed, non-empty entries.\n * `lowercase` folds case for identifiers compared case-insensitively\n * (JIDs, email addresses, hex pubkeys). Absent/empty input → empty list. */\nexport function parseCsvList(raw, opts) {\n return (raw ?? \"\")\n .split(\",\")\n .map((entry) => (opts?.lowercase ? entry.trim().toLowerCase() : entry.trim()))\n .filter(Boolean);\n}\n/** A comma-separated env value as a Set — the canonical allowlist shape,\n * where an empty set is the \"allow all\" sentinel (`set.size === 0`). */\nexport function parseCsvSet(raw, opts) {\n return new Set(parseCsvList(raw, opts));\n}\n/** Normalise an unknown thrown value into a human-readable string. Isomorphic\n * (host, bridges, plugins, Vue) — this is the single home for the helper that\n * #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`\n * is server-only.\n *\n * A non-Error object with a non-empty string `details` (gRPC convention) or\n * `message` field surfaces that field — `details` wins — instead of the\n * `[object Object]` a bare `String(err)` would print; an empty-string field\n * falls through. `fallback` covers the error-boundary idiom where a thrown\n * non-Error should read as a descriptive message rather than `String(err)`\n * noise; omit it in logging contexts where `String(err)` is fine. */\nexport function errorMessage(err, fallback) {\n if (err instanceof Error)\n return err.message;\n if (hasStringProp(err, \"details\") && err.details)\n return err.details;\n if (hasStringProp(err, \"message\") && err.message)\n return err.message;\n if (fallback !== undefined)\n return fallback;\n return String(err);\n}\n/** `Date` → `YYYY-MM-DD` in UTC — for dates that must not shift with the\n * host's local timezone (tool-trace search dirs, API date keys). Isomorphic\n * single source (#2480): the host re-exports it from `server/utils/date.ts`,\n * x-plugin imports it directly. The `@receptron/task-scheduler` copy stays\n * local on purpose — that leaf package is published independently and kept\n * dependency-free. Wall-clock questions use the host's `toLocalIsoDate`. */\nexport function toUtcIsoDate(timestamp) {\n const year = timestamp.getUTCFullYear();\n const month = String(timestamp.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(timestamp.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n// A Map, not an object literal: `{}[char]` reads through the prototype chain,\n// so a future caller widening the regex would silently get `[object Object]`\n// for keys like `constructor`.\nconst HTML_ESCAPES = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n ['\"', \""\"],\n [\"'\", \"'\"],\n]);\n/** HTML-escape text destined for markup or an attribute value — the fixed\n * five-character map, nothing more. Lives here rather than in\n * `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a\n * leaf that core depends on and so cannot import back up; core/wiki\n * re-exports this, keeping its consumers' import path unchanged.\n *\n * Escaping `&` first is what makes a single pass safe — the entities this\n * introduces contain none of the other four characters, so nothing is\n * double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */\nexport function escapeHtml(value) {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES.get(char) ?? char);\n}\nexport { scanEnvOptions, snakeToLowerCamel } from \"./envScan.js\";\n"],"mappings":";;AAQA,SAAgB,SAAS,OAAO;CAC5B,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC9E;;AA+BA,SAAgB,cAAc,OAAO,KAAK;CACtC,OAAO,SAAS,KAAK,KAAK,OAAO,MAAM,SAAS;AACpD;;;;;;;;;;;;AA8BA,SAAgB,aAAa,KAAK,UAAU;CACxC,IAAI,eAAe,OACf,OAAO,IAAI;CACf,IAAI,cAAc,KAAK,SAAS,KAAK,IAAI,SACrC,OAAO,IAAI;CACf,IAAI,cAAc,KAAK,SAAS,KAAK,IAAI,SACrC,OAAO,IAAI;CACf,IAAI,aAAa,KAAA,GACb,OAAO;CACX,OAAO,OAAO,GAAG;AACrB;AAgBA,IAAM,+BAAe,IAAI,IAAI;CACzB,CAAC,KAAK,OAAO;CACb,CAAC,KAAK,MAAM;CACZ,CAAC,KAAK,MAAM;CACZ,CAAC,MAAK,QAAQ;CACd,CAAC,KAAK,OAAO;AACjB,CAAC;;;;;;;;;;AAUD,SAAgB,WAAW,OAAO;CAC9B,OAAO,MAAM,QAAQ,aAAa,SAAS,aAAa,IAAI,IAAI,KAAK,IAAI;AAC7E"}
|
package/dist/feeds/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_ids = require("../ids-
|
|
3
|
-
const require_ingestTypes = require("../ingestTypes-
|
|
2
|
+
const require_ids = require("../ids-BJ7rdrDN.cjs");
|
|
3
|
+
const require_ingestTypes = require("../ingestTypes-CkbM-zrO.cjs");
|
|
4
4
|
exports.AGENT_INGEST_KIND = require_ingestTypes.AGENT_INGEST_KIND;
|
|
5
5
|
exports.DEFAULT_FEED_MAX_ITEMS = require_ingestTypes.DEFAULT_FEED_MAX_ITEMS;
|
|
6
6
|
exports.FEED_SCHEDULES = require_ids.FEED_SCHEDULES;
|
package/dist/feeds/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { n as DEFAULT_FEED_MAX_ITEMS, r as isFeedSchedule, t as AGENT_INGEST_KIND } from "../ingestTypes-
|
|
1
|
+
import { c as FEED_SCHEDULES, l as INGEST_KINDS } from "../ids-Bv6AOW9Z.js";
|
|
2
|
+
import { n as DEFAULT_FEED_MAX_ITEMS, r as isFeedSchedule, t as AGENT_INGEST_KIND } from "../ingestTypes-DuCiZqye.js";
|
|
3
3
|
export { AGENT_INGEST_KIND, DEFAULT_FEED_MAX_ITEMS, FEED_SCHEDULES, INGEST_KINDS, isFeedSchedule };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StructuredLogger } from '../../host/hostSlot.js';
|
|
1
2
|
/** Outcome of launching one hidden/visible agent-ingest worker. `chatId` lets
|
|
2
3
|
* the caller register a completion hook so a failed refresh doesn't die
|
|
3
4
|
* silently. */
|
|
@@ -23,13 +24,8 @@ export type AgentWorkerRunner = (args: {
|
|
|
23
24
|
didError: boolean;
|
|
24
25
|
}) => void | Promise<void>;
|
|
25
26
|
}) => Promise<AgentWorkerResult>;
|
|
26
|
-
/**
|
|
27
|
-
export
|
|
28
|
-
error: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
29
|
-
warn: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
30
|
-
info: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
31
|
-
debug: (prefix: string, message: string, data?: Record<string, unknown>) => void;
|
|
32
|
-
}
|
|
27
|
+
/** Public alias of the shared `StructuredLogger` — same shape as `CollectionLogger`. */
|
|
28
|
+
export type FeedsLogger = StructuredLogger;
|
|
33
29
|
export interface FeedsHost {
|
|
34
30
|
/** Absolute workspace root — the default for `refreshDue()` and state paths. */
|
|
35
31
|
workspaceRoot: string;
|
|
@@ -47,5 +43,6 @@ export declare function requireFeedsHost(): FeedsHost;
|
|
|
47
43
|
/** Test-only: clear the configured host. */
|
|
48
44
|
export declare function resetFeedsHostForTesting(): void;
|
|
49
45
|
/** Forwarding logger so engine modules can `import { log }` without each
|
|
50
|
-
* reaching for `requireFeedsHost().log`.
|
|
46
|
+
* reaching for `requireFeedsHost().log`. Non-critical: calls before the host
|
|
47
|
+
* is wired are dropped (not thrown), matching the collection/google seams. */
|
|
51
48
|
export declare const log: FeedsLogger;
|
|
@@ -1,43 +1,37 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("../../rolldown-runtime-D6vf50IK.cjs");
|
|
3
|
-
const require_ids = require("../../ids-
|
|
4
|
-
const require_discovery = require("../../discovery-
|
|
5
|
-
require("../../server-
|
|
6
|
-
const require_ingestTypes = require("../../ingestTypes-
|
|
3
|
+
const require_ids = require("../../ids-BJ7rdrDN.cjs");
|
|
4
|
+
const require_discovery = require("../../discovery-UxkvBxV-.cjs");
|
|
5
|
+
const require_server = require("../../server-DFo3zFYB.cjs");
|
|
6
|
+
const require_ingestTypes = require("../../ingestTypes-CkbM-zrO.cjs");
|
|
7
7
|
const require_feeds_paths = require("../paths.cjs");
|
|
8
|
-
const require_notifier = require("../../notifier-
|
|
8
|
+
const require_notifier = require("../../notifier-tMsAXyXp.cjs");
|
|
9
9
|
let node_path = require("node:path");
|
|
10
10
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
11
|
-
let node_fs_promises = require("node:fs/promises");
|
|
12
11
|
let node_crypto = require("node:crypto");
|
|
12
|
+
let node_fs_promises = require("node:fs/promises");
|
|
13
13
|
let node_dns_promises = require("node:dns/promises");
|
|
14
14
|
let node_net = require("node:net");
|
|
15
15
|
let fast_xml_parser = require("fast-xml-parser");
|
|
16
16
|
let _receptron_task_scheduler = require("@receptron/task-scheduler");
|
|
17
17
|
//#region src/feeds/server/host.ts
|
|
18
|
-
var
|
|
18
|
+
var hostSlot = require_discovery.createHostSlot("@mulmoclaude/core/feeds: configureFeedsHost()");
|
|
19
19
|
/** Wire the feeds engine to a host. Call once at startup, before any refresh. */
|
|
20
20
|
function configureFeedsHost(host) {
|
|
21
|
-
|
|
22
|
-
current = host;
|
|
21
|
+
hostSlot.set(host);
|
|
23
22
|
}
|
|
24
23
|
/** The configured host, or throw if `configureFeedsHost` was never called. */
|
|
25
24
|
function requireFeedsHost() {
|
|
26
|
-
|
|
27
|
-
return current;
|
|
25
|
+
return hostSlot.get();
|
|
28
26
|
}
|
|
29
27
|
/** Test-only: clear the configured host. */
|
|
30
28
|
function resetFeedsHostForTesting() {
|
|
31
|
-
|
|
29
|
+
hostSlot.reset();
|
|
32
30
|
}
|
|
33
31
|
/** Forwarding logger so engine modules can `import { log }` without each
|
|
34
|
-
* reaching for `requireFeedsHost().log`.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
warn: (prefix, msg, data) => requireFeedsHost().log.warn(prefix, msg, data),
|
|
38
|
-
info: (prefix, msg, data) => requireFeedsHost().log.info(prefix, msg, data),
|
|
39
|
-
debug: (prefix, msg, data) => requireFeedsHost().log.debug(prefix, msg, data)
|
|
40
|
-
};
|
|
32
|
+
* reaching for `requireFeedsHost().log`. Non-critical: calls before the host
|
|
33
|
+
* is wired are dropped (not thrown), matching the collection/google seams. */
|
|
34
|
+
var log = require_discovery.createForwardingLogger(() => hostSlot.peek()?.log ?? null);
|
|
41
35
|
//#endregion
|
|
42
36
|
//#region src/feeds/server/registry.ts
|
|
43
37
|
/** Every registered feed, as a discovered collection (carrying its
|
|
@@ -87,69 +81,156 @@ function getRetriever(kind) {
|
|
|
87
81
|
return registry.get(kind);
|
|
88
82
|
}
|
|
89
83
|
//#endregion
|
|
90
|
-
//#region
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
var
|
|
98
|
-
|
|
84
|
+
//#region ../common/dist/ssrf.js
|
|
85
|
+
var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
86
|
+
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
87
|
+
"localhost",
|
|
88
|
+
"ip6-localhost",
|
|
89
|
+
"ip6-loopback"
|
|
90
|
+
]);
|
|
91
|
+
var BLOCKED_HOSTNAME_SUFFIXES = [
|
|
92
|
+
".localhost",
|
|
93
|
+
".local",
|
|
94
|
+
".internal"
|
|
95
|
+
];
|
|
96
|
+
/** IPv4 ranges that must never be fetched, as [firstAddress, prefixLength].
|
|
97
|
+
* The union of the pre-#2459 per-guard tables — removing an entry weakens
|
|
98
|
+
* every guard in the repo at once, so treat edits as security changes. */
|
|
99
|
+
var BLOCKED_IPV4_RANGES = [
|
|
99
100
|
["0.0.0.0", 8],
|
|
100
101
|
["10.0.0.0", 8],
|
|
101
102
|
["100.64.0.0", 10],
|
|
102
103
|
["127.0.0.0", 8],
|
|
103
104
|
["169.254.0.0", 16],
|
|
104
105
|
["172.16.0.0", 12],
|
|
105
|
-
["192.
|
|
106
|
+
["192.0.0.0", 24],
|
|
107
|
+
["192.168.0.0", 16],
|
|
108
|
+
["198.18.0.0", 15],
|
|
109
|
+
["224.0.0.0", 4],
|
|
110
|
+
["240.0.0.0", 4]
|
|
106
111
|
];
|
|
112
|
+
/** Dotted-decimal IPv4 → unsigned 32-bit value; null for anything else
|
|
113
|
+
* (hex / octal octets, whitespace, wrong part count). */
|
|
107
114
|
function ipv4ToInt(address) {
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
|
|
115
|
+
const parts = address.split(".");
|
|
116
|
+
if (parts.length !== 4) return null;
|
|
117
|
+
const octets = parts.map((part) => /^\d{1,3}$/.test(part) ? Number(part) : NaN);
|
|
118
|
+
if (octets.some((octet) => Number.isNaN(octet) || octet > 255)) return null;
|
|
119
|
+
return octets.reduce((value, octet) => value * 256 + octet, 0);
|
|
120
|
+
}
|
|
121
|
+
function isBlockedIpv4Value(value) {
|
|
122
|
+
return BLOCKED_IPV4_RANGES.some(([base, prefix]) => {
|
|
123
|
+
const baseValue = ipv4ToInt(base);
|
|
124
|
+
if (baseValue === null) return false;
|
|
125
|
+
const mask = prefix === 0 ? 0 : -1 << 32 - prefix >>> 0;
|
|
126
|
+
return (value & mask) === (baseValue & mask);
|
|
127
|
+
});
|
|
111
128
|
}
|
|
129
|
+
/** True when this parses as IPv4 AND falls inside a blocked range. Non-IPv4
|
|
130
|
+
* input is false — "not a blocked literal", not "safe"; a caller that must
|
|
131
|
+
* reject non-IP input entirely does so at its own boundary. */
|
|
112
132
|
function isBlockedIpv4(address) {
|
|
113
133
|
const value = ipv4ToInt(address);
|
|
114
|
-
if (value === null) return
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
if (value === null) return false;
|
|
135
|
+
return isBlockedIpv4Value(value);
|
|
136
|
+
}
|
|
137
|
+
/** `URL.hostname` keeps the brackets on an IPv6 literal (`[::1]`); strip them
|
|
138
|
+
* so the address checks see the bare address. */
|
|
139
|
+
function stripIpv6Brackets(hostname) {
|
|
140
|
+
return hostname.replace(/^\[|\]$/g, "");
|
|
141
|
+
}
|
|
142
|
+
/** The 32-bit value inside an IPv4-mapped IPv6 literal, or null when the
|
|
143
|
+
* address is not v4-mapped. The hex spelling matters: WHATWG URL serializes
|
|
144
|
+
* `[::ffff:127.0.0.1]` as `::ffff:7f00:1`, so matching only the dotted form
|
|
145
|
+
* would wave the mapped loopback through any URL-sourced check. */
|
|
146
|
+
function mappedIpv4Value(lower) {
|
|
147
|
+
const dotted = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(lower);
|
|
148
|
+
if (dotted) return ipv4ToInt(dotted[1]);
|
|
149
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(lower);
|
|
150
|
+
if (hex) return Number.parseInt(hex[1], 16) * 65536 + Number.parseInt(hex[2], 16);
|
|
151
|
+
return null;
|
|
120
152
|
}
|
|
153
|
+
/** Blocks loopback/unspecified, fc00::/7 unique-local, fe80::/10 link-local,
|
|
154
|
+
* and IPv4-mapped forms of blocked v4 addresses. Mask-based on the leading
|
|
155
|
+
* group — prefix string matching would miss e.g. fe9a:: (inside fe80::/10). */
|
|
121
156
|
function isBlockedIpv6(address) {
|
|
122
|
-
const lower = address.toLowerCase();
|
|
157
|
+
const lower = stripIpv6Brackets(address.toLowerCase());
|
|
123
158
|
if (lower === "::1" || lower === "::") return true;
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
159
|
+
const mapped = mappedIpv4Value(lower);
|
|
160
|
+
if (mapped !== null) return isBlockedIpv4Value(mapped);
|
|
161
|
+
const [head] = lower.split(":");
|
|
162
|
+
if (head.length === 0) return false;
|
|
163
|
+
const leading = Number.parseInt(head, 16);
|
|
164
|
+
if (Number.isNaN(leading)) return false;
|
|
165
|
+
if ((leading & 65024) === 64512) return true;
|
|
166
|
+
if ((leading & 65472) === 65152) return true;
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/** True when this literal address must never be fetched. Non-IP input is
|
|
170
|
+
* false — see `isBlockedIpv4`. */
|
|
130
171
|
function isBlockedIp(address) {
|
|
172
|
+
return address.includes(":") ? isBlockedIpv6(address) : isBlockedIpv4(address);
|
|
173
|
+
}
|
|
174
|
+
/** Names that are internal by convention (localhost, *.local, *.internal…),
|
|
175
|
+
* rejected without waiting for DNS to prove it. */
|
|
176
|
+
function isBlockedHostname(hostname) {
|
|
177
|
+
const lower = hostname.toLowerCase().replace(/\.$/, "");
|
|
178
|
+
if (BLOCKED_HOSTNAMES.has(lower)) return true;
|
|
179
|
+
return BLOCKED_HOSTNAME_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
180
|
+
}
|
|
181
|
+
function parseUrl(raw) {
|
|
182
|
+
try {
|
|
183
|
+
return new URL(raw);
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Parse and apply every check that doesn't need the network: shape, scheme,
|
|
189
|
+
* obviously-internal hostname, and literal addresses. Returns null on reject.
|
|
190
|
+
* Callers must still vet what non-literal hostnames resolve to. */
|
|
191
|
+
function parseSafeUrlShape(raw) {
|
|
192
|
+
const url = parseUrl(raw);
|
|
193
|
+
if (url === null) return null;
|
|
194
|
+
if (!ALLOWED_PROTOCOLS.has(url.protocol)) return null;
|
|
195
|
+
const hostname = stripIpv6Brackets(url.hostname);
|
|
196
|
+
if (hostname.length === 0) return null;
|
|
197
|
+
if (isBlockedHostname(hostname)) return null;
|
|
198
|
+
if (isBlockedIp(hostname)) return null;
|
|
199
|
+
return url;
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/feeds/server/fetch/httpClient.ts
|
|
203
|
+
var ONE_SECOND_MS = 1e3;
|
|
204
|
+
/** Identifies the bot to site operators. */
|
|
205
|
+
var FEED_USER_AGENT = "MulmoClaude-FeedBot/1.0 (+https://github.com/receptron/mulmoclaude)";
|
|
206
|
+
/** Per-request wall-clock cap so a hung server can't wedge a refresh. */
|
|
207
|
+
var DEFAULT_FEED_TIMEOUT_MS = 30 * ONE_SECOND_MS;
|
|
208
|
+
/** Cap on redirect hops followed (each re-checked for SSRF). */
|
|
209
|
+
var MAX_REDIRECTS = 5;
|
|
210
|
+
/** DNS answers should be IP literals; anything else is blocked outright. */
|
|
211
|
+
function isBlockedResolvedIp(address) {
|
|
131
212
|
const kind = (0, node_net.isIP)(address);
|
|
132
213
|
if (kind === 4) return isBlockedIpv4(address);
|
|
133
214
|
if (kind === 6) return isBlockedIpv6(address);
|
|
134
215
|
return true;
|
|
135
216
|
}
|
|
136
|
-
|
|
137
|
-
* address (SSRF guard). Throws with a clear reason; returns void on pass. */
|
|
138
|
-
async function assertFetchableUrl(rawUrl) {
|
|
139
|
-
if (!/^https?:\/\//i.test(rawUrl)) throw new Error(`refusing non-http(s) URL: ${rawUrl}`);
|
|
140
|
-
const { hostname } = new URL(rawUrl);
|
|
141
|
-
const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
142
|
-
if ((0, node_net.isIP)(host)) {
|
|
143
|
-
if (isBlockedIp(host)) throw new Error(`refusing to fetch a private/loopback address: ${host}`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
let addresses;
|
|
217
|
+
async function resolveAddresses(host) {
|
|
147
218
|
try {
|
|
148
|
-
|
|
219
|
+
return await (0, node_dns_promises.lookup)(host, { all: true });
|
|
149
220
|
} catch (err) {
|
|
150
221
|
throw new Error(`could not resolve host '${host}': ${String(err)}`);
|
|
151
222
|
}
|
|
152
|
-
|
|
223
|
+
}
|
|
224
|
+
/** Reject non-http(s) URLs, internal-by-convention hostnames, and URLs whose
|
|
225
|
+
* host is / resolves to a private/loopback address (SSRF guard). Throws with
|
|
226
|
+
* a clear reason; returns void on pass. */
|
|
227
|
+
async function assertFetchableUrl(rawUrl) {
|
|
228
|
+
if (!/^https?:\/\//i.test(rawUrl)) throw new Error(`refusing non-http(s) URL: ${rawUrl}`);
|
|
229
|
+
const url = parseSafeUrlShape(rawUrl);
|
|
230
|
+
if (url === null) throw new Error(`refusing to fetch a private/loopback/internal address: ${rawUrl}`);
|
|
231
|
+
const host = stripIpv6Brackets(url.hostname);
|
|
232
|
+
if ((0, node_net.isIP)(host)) return;
|
|
233
|
+
const blocked = (await resolveAddresses(host)).find((entry) => isBlockedResolvedIp(entry.address));
|
|
153
234
|
if (blocked) throw new Error(`refusing to fetch '${host}' — resolves to a private/loopback address (${blocked.address})`);
|
|
154
235
|
}
|
|
155
236
|
async function fetchOnce(url, timeoutMs) {
|
|
@@ -308,7 +389,8 @@ function step(current, token) {
|
|
|
308
389
|
if (current === null || current === void 0) return void 0;
|
|
309
390
|
if (token.kind === "key") {
|
|
310
391
|
if (typeof current !== "object" || Array.isArray(current)) return void 0;
|
|
311
|
-
|
|
392
|
+
const record = current;
|
|
393
|
+
return Object.hasOwn(record, token.key) ? record[token.key] : void 0;
|
|
312
394
|
}
|
|
313
395
|
return Array.isArray(current) ? current[token.index] : void 0;
|
|
314
396
|
}
|
|
@@ -497,10 +579,10 @@ async function refreshViaAgent(workspaceRoot, collection, opts) {
|
|
|
497
579
|
if (!ingest || ingest.kind !== "agent") return result(slug, { errors: ["collection has no agent ingest config"] });
|
|
498
580
|
const workerRunner = workerRunnerOrNull();
|
|
499
581
|
if (!workerRunner) return result(slug, { errors: ["agent ingest worker runner not configured"] });
|
|
500
|
-
const template = await
|
|
582
|
+
const template = await require_server.readSkillTemplate(collection.skillDir, ingest.template);
|
|
501
583
|
if (template === null) return result(slug, { errors: [`ingest template '${ingest.template}' could not be read`] });
|
|
502
584
|
const items = await require_discovery.listItems(collection.dataDir, { workspaceRoot });
|
|
503
|
-
const message =
|
|
585
|
+
const message = require_server.buildCollectionActionSeedPrompt(items, collection.schema, template, require_server.promptPathsFor(collection, workspaceRoot));
|
|
504
586
|
let launch;
|
|
505
587
|
try {
|
|
506
588
|
launch = await workerRunner({
|