@agimon-ai/doompi-skill 0.0.1-alpha.54 → 0.0.1-alpha.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/extensions/mcp.cjs +53 -0
  2. package/dist/extensions/mcp.cjs.map +1 -0
  3. package/dist/extensions/mcp.d.cts +5 -0
  4. package/dist/extensions/mcp.d.cts.map +1 -0
  5. package/dist/extensions/mcp.d.mts +5 -0
  6. package/dist/extensions/mcp.d.mts.map +1 -0
  7. package/dist/extensions/mcp.mjs +48 -0
  8. package/dist/extensions/mcp.mjs.map +1 -0
  9. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/skillServer.cjs +4 -2
  10. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/skillServer.cjs.map +1 -1
  11. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/skillServer.mjs +3 -1
  12. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/skillServer.mjs.map +1 -1
  13. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs +6 -2
  14. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs.map +1 -1
  15. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs +6 -2
  16. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs.map +1 -1
  17. package/dist/src/services/mcpSkills/index.cjs +40 -0
  18. package/dist/src/services/mcpSkills/index.cjs.map +1 -0
  19. package/dist/src/services/mcpSkills/index.mjs +39 -0
  20. package/dist/src/services/mcpSkills/index.mjs.map +1 -0
  21. package/dist/src/services/serverInventory/index.cjs +42 -6
  22. package/dist/src/services/serverInventory/index.cjs.map +1 -1
  23. package/dist/src/services/serverInventory/index.mjs +42 -6
  24. package/dist/src/services/serverInventory/index.mjs.map +1 -1
  25. package/package.json +17 -9
@@ -0,0 +1,53 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ let _agimon_ai_doompi_core_mcp_facet = require("@agimon-ai/doompi-core/mcp-facet");
6
+ let _agimon_ai_doompi_core_extension_file = require("@agimon-ai/doompi-core/extension-file");
7
+ let _agimon_ai_doompi_core_server_facet = require("@agimon-ai/doompi-core/server-facet");
8
+ //#region src/services/mcpSkills/index.ts
9
+ const MCP_SKILLS_SERVICE = "doom/discovered-mcp-skills";
10
+ function bindMcpSkills(context) {
11
+ const provider = context.services.get(MCP_SKILLS_SERVICE);
12
+ if (!provider) throw new Error("Discovered MCP skills are unavailable.");
13
+ return provider.list(context);
14
+ }
15
+ //#endregion
16
+ //#region src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts
17
+ var discovered_mcp_default = (0, _agimon_ai_doompi_core_extension_file.defineRoutedContribution)(bindMcpSkills, { cardinality: "many" });
18
+ //#endregion
19
+ //#region src/extensions/workspaces/sessions/(backend)/skill/doompi-author-skill.mcp.ts
20
+ var doompi_author_skill_mcp_default = (0, _agimon_ai_doompi_core_mcp_facet.defineMcpSkill)({
21
+ name: "doompi-author-skill",
22
+ description: "Author and distribute DoomPi agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a DoomPi package.",
23
+ read: () => (0, _agimon_ai_doompi_core_server_facet.readPackageResource)(require("url").pathToFileURL(__filename).href, "src/prompts/doompi-author-skill/SKILL.md")
24
+ });
25
+ //#endregion
26
+ //#region src/extensions/workspaces/sessions/(backend)/skill/doompi-use-skill.mcp.ts
27
+ var doompi_use_skill_mcp_default = (0, _agimon_ai_doompi_core_mcp_facet.defineMcpSkill)({
28
+ name: "doompi-use-skill",
29
+ description: "Use DoomPi's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.",
30
+ read: () => (0, _agimon_ai_doompi_core_server_facet.readPackageResource)(require("url").pathToFileURL(__filename).href, "src/prompts/doompi-use-skill/SKILL.md")
31
+ });
32
+ //#endregion
33
+ //#region generated/mcp.ts
34
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
35
+ const many = (value, context) => {
36
+ const resolved = typeof value === "function" ? value(context) : value;
37
+ return Array.isArray(resolved) ? resolved : [resolved];
38
+ };
39
+ const mcp = (0, _agimon_ai_doompi_core_mcp_facet.defineMcpPlugin)({
40
+ name: "@agimon-ai/doompi-skill",
41
+ session: (context) => ({ get skills() {
42
+ return [
43
+ ...many(discovered_mcp_default, context),
44
+ at(doompi_author_skill_mcp_default, context),
45
+ at(doompi_use_skill_mcp_default, context)
46
+ ];
47
+ } })
48
+ });
49
+ //#endregion
50
+ exports.default = mcp;
51
+ exports.mcp = mcp;
52
+
53
+ //# sourceMappingURL=mcp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.cjs","names":["defineRoutedContribution","defineMcpSkill","readPackageResource","defineMcpSkill","readPackageResource","defineMcpPlugin","skillDiscovered","skillDoompiAuthorSkill","skillDoompiUseSkill"],"sources":["../../src/services/mcpSkills/index.ts","../../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/doompi-author-skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/doompi-use-skill.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { defineRoutedContribution } from '@agimon-ai/doompi-core/extension-file';\n\nimport { bindMcpSkills } from '../../../../../services/mcpSkills';\n\nexport default defineRoutedContribution(bindMcpSkills, { cardinality: 'many' });\n","import { defineMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport { readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nexport default defineMcpSkill({\n name: 'doompi-author-skill',\n description:\n 'Author and distribute DoomPi agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a DoomPi package.',\n read: () => readPackageResource(import.meta.url, 'src/prompts/doompi-author-skill/SKILL.md'),\n});\n","import { defineMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport { readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nexport default defineMcpSkill({\n name: 'doompi-use-skill',\n description:\n \"Use DoomPi's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.\",\n read: () => readPackageResource(import.meta.url, 'src/prompts/doompi-use-skill/SKILL.md'),\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport skillDiscovered from '../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp';\nimport skillDoompiAuthorSkill from '../src/extensions/workspaces/sessions/(backend)/skill/doompi-author-skill.mcp';\nimport skillDoompiUseSkill from '../src/extensions/workspaces/sessions/(backend)/skill/doompi-use-skill.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-skill',\n session: (context) => ({\n get skills(): DoomMcpSessionPlugin['skills'] { return [...many(skillDiscovered, context), at(skillDoompiAuthorSkill, context), at(skillDoompiUseSkill, context)]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":";;;;;;;;AAOA,MAAa,qBAAqB;AAuClC,SAAgB,cAAc,SAAwD;CACpF,MAAM,WAAW,QAAQ,SAAS,IAAsB,kBAAkB;CAC1E,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,wCAAwC;CACvE,OAAO,SAAS,KAAK,OAAO;AAC9B;;;AC9CA,IAAA,0BAAA,GAAeA,sCAAAA,yBAAAA,CAAyB,eAAe,EAAE,aAAa,OAAO,CAAC;;;ACD9E,IAAA,mCAAA,GAAeC,iCAAAA,eAAAA,CAAe;CAC5B,MAAM;CACN,aACE;CACF,aAAA,GAAYC,oCAAAA,oBAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,MAAqC,0CAA0C;AAC7F,CAAC;;;ACLD,IAAA,gCAAA,GAAeC,iCAAAA,eAAAA,CAAe;CAC5B,MAAM;CACN,aACE;CACF,aAAA,GAAYC,oCAAAA,oBAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,MAAqC,uCAAuC;AAC1F,CAAC;;;ACAD,MAAM,MAAY,OAA0B,YAC1C,OAAO,UAAU,aAAc,MAAwB,OAAO,IAAI;AAEpE,MAAM,QAAc,OAA6C,YAA6B;CAC5F,MAAM,WAAW,OAAO,UAAU,aAAc,MAA4B,OAAO,IAAI;CACvF,OAAO,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAa;AAC5D;AAEA,MAAa,OAAA,GAAMC,iCAAAA,gBAAAA,CAAgB;CACjC,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,SAAyC;EAAE,OAAO;GAAC,GAAG,KAAKC,wBAAiB,OAAO;GAAG,GAAGC,iCAAwB,OAAO;GAAG,GAAGC,8BAAqB,OAAO;EAAC;CAAG,EACpK;AACF,CAAC"}
@@ -0,0 +1,5 @@
1
+ //#region generated/mcp.d.ts
2
+ export declare const mcp: import("@agimon-ai/doompi-core/mcp-facet").DoomMcpPluginDefinition;
3
+ //#endregion
4
+ export { mcp as default };
5
+ //# sourceMappingURL=mcp.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.cts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";qBAgBa,gDAAG"}
@@ -0,0 +1,5 @@
1
+ //#region generated/mcp.d.ts
2
+ export declare const mcp: import("@agimon-ai/doompi-core/mcp-facet").DoomMcpPluginDefinition;
3
+ //#endregion
4
+ export { mcp as default };
5
+ //# sourceMappingURL=mcp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.mts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";qBAgBa,gDAAG"}
@@ -0,0 +1,48 @@
1
+ import { defineMcpPlugin, defineMcpSkill } from "@agimon-ai/doompi-core/mcp-facet";
2
+ import { defineRoutedContribution } from "@agimon-ai/doompi-core/extension-file";
3
+ import { readPackageResource } from "@agimon-ai/doompi-core/server-facet";
4
+ //#region src/services/mcpSkills/index.ts
5
+ const MCP_SKILLS_SERVICE = "doom/discovered-mcp-skills";
6
+ function bindMcpSkills(context) {
7
+ const provider = context.services.get(MCP_SKILLS_SERVICE);
8
+ if (!provider) throw new Error("Discovered MCP skills are unavailable.");
9
+ return provider.list(context);
10
+ }
11
+ //#endregion
12
+ //#region src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts
13
+ var discovered_mcp_default = defineRoutedContribution(bindMcpSkills, { cardinality: "many" });
14
+ //#endregion
15
+ //#region src/extensions/workspaces/sessions/(backend)/skill/doompi-author-skill.mcp.ts
16
+ var doompi_author_skill_mcp_default = defineMcpSkill({
17
+ name: "doompi-author-skill",
18
+ description: "Author and distribute DoomPi agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a DoomPi package.",
19
+ read: () => readPackageResource(import.meta.url, "src/prompts/doompi-author-skill/SKILL.md")
20
+ });
21
+ //#endregion
22
+ //#region src/extensions/workspaces/sessions/(backend)/skill/doompi-use-skill.mcp.ts
23
+ var doompi_use_skill_mcp_default = defineMcpSkill({
24
+ name: "doompi-use-skill",
25
+ description: "Use DoomPi's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.",
26
+ read: () => readPackageResource(import.meta.url, "src/prompts/doompi-use-skill/SKILL.md")
27
+ });
28
+ //#endregion
29
+ //#region generated/mcp.ts
30
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
31
+ const many = (value, context) => {
32
+ const resolved = typeof value === "function" ? value(context) : value;
33
+ return Array.isArray(resolved) ? resolved : [resolved];
34
+ };
35
+ const mcp = defineMcpPlugin({
36
+ name: "@agimon-ai/doompi-skill",
37
+ session: (context) => ({ get skills() {
38
+ return [
39
+ ...many(discovered_mcp_default, context),
40
+ at(doompi_author_skill_mcp_default, context),
41
+ at(doompi_use_skill_mcp_default, context)
42
+ ];
43
+ } })
44
+ });
45
+ //#endregion
46
+ export { mcp as default, mcp };
47
+
48
+ //# sourceMappingURL=mcp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.mjs","names":["skillDiscovered","skillDoompiAuthorSkill","skillDoompiUseSkill"],"sources":["../../src/services/mcpSkills/index.ts","../../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/doompi-author-skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/doompi-use-skill.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { defineRoutedContribution } from '@agimon-ai/doompi-core/extension-file';\n\nimport { bindMcpSkills } from '../../../../../services/mcpSkills';\n\nexport default defineRoutedContribution(bindMcpSkills, { cardinality: 'many' });\n","import { defineMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport { readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nexport default defineMcpSkill({\n name: 'doompi-author-skill',\n description:\n 'Author and distribute DoomPi agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a DoomPi package.',\n read: () => readPackageResource(import.meta.url, 'src/prompts/doompi-author-skill/SKILL.md'),\n});\n","import { defineMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport { readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nexport default defineMcpSkill({\n name: 'doompi-use-skill',\n description:\n \"Use DoomPi's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.\",\n read: () => readPackageResource(import.meta.url, 'src/prompts/doompi-use-skill/SKILL.md'),\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport skillDiscovered from '../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp';\nimport skillDoompiAuthorSkill from '../src/extensions/workspaces/sessions/(backend)/skill/doompi-author-skill.mcp';\nimport skillDoompiUseSkill from '../src/extensions/workspaces/sessions/(backend)/skill/doompi-use-skill.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-skill',\n session: (context) => ({\n get skills(): DoomMcpSessionPlugin['skills'] { return [...many(skillDiscovered, context), at(skillDoompiAuthorSkill, context), at(skillDoompiUseSkill, context)]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":";;;;AAOA,MAAa,qBAAqB;AAuClC,SAAgB,cAAc,SAAwD;CACpF,MAAM,WAAW,QAAQ,SAAS,IAAsB,kBAAkB;CAC1E,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,wCAAwC;CACvE,OAAO,SAAS,KAAK,OAAO;AAC9B;;;AC9CA,IAAA,yBAAe,yBAAyB,eAAe,EAAE,aAAa,OAAO,CAAC;;;ACD9E,IAAA,kCAAe,eAAe;CAC5B,MAAM;CACN,aACE;CACF,YAAY,oBAAoB,YAAY,KAAK,0CAA0C;AAC7F,CAAC;;;ACLD,IAAA,+BAAe,eAAe;CAC5B,MAAM;CACN,aACE;CACF,YAAY,oBAAoB,YAAY,KAAK,uCAAuC;AAC1F,CAAC;;;ACAD,MAAM,MAAY,OAA0B,YAC1C,OAAO,UAAU,aAAc,MAAwB,OAAO,IAAI;AAEpE,MAAM,QAAc,OAA6C,YAA6B;CAC5F,MAAM,WAAW,OAAO,UAAU,aAAc,MAA4B,OAAO,IAAI;CACvF,OAAO,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAa;AAC5D;AAEA,MAAa,MAAM,gBAAgB;CACjC,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,SAAyC;EAAE,OAAO;GAAC,GAAG,KAAKA,wBAAiB,OAAO;GAAG,GAAGC,iCAAwB,OAAO;GAAG,GAAGC,8BAAqB,OAAO;EAAC;CAAG,EACpK;AACF,CAAC"}
@@ -1,11 +1,13 @@
1
- const require_index = require("../../../../../services/serverInventory/index.cjs");
1
+ const require_index = require("../../../../../services/mcpSkills/index.cjs");
2
+ const require_index$1 = require("../../../../../services/serverInventory/index.cjs");
2
3
  const require_skillCommands = require("./skillCommands.cjs");
3
4
  let _agimon_ai_doompi_core_server_facet = require("@agimon-ai/doompi-core/server-facet");
4
5
  //#region src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts
5
6
  async function createSkillServer(agent, signal) {
6
- const { inventory, catalog, groups } = await require_index.discoverServerSkills(agent.context, signal);
7
+ const { inventory, catalog, groups, mcpGroups } = await require_index$1.discoverServerSkills(agent.context, signal);
7
8
  return {
8
9
  commands: require_skillCommands.createSkillCommands(inventory, catalog, groups),
10
+ services: [require_index.mountMcpSkills(mcpGroups, signal)],
9
11
  resources: [{
10
12
  name: "doompi/skills",
11
13
  kind: "skill",
@@ -1 +1 @@
1
- {"version":3,"file":"skillServer.cjs","names":["discoverServerSkills","createSkillCommands","readPackageResource"],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts"],"sourcesContent":["import type { DoomHeadlessHostService } from '@agimon-ai/doompi-core/headless';\nimport { type DoomServerSessionPlugin, readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups } = await discoverServerSkills(agent.context, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n resources: [\n { name: 'doompi/skills', kind: 'skill', read: () => catalog },\n ...['doompi-author-skill', 'doompi-use-skill'].map((name) => ({\n name,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, `src/prompts/${name}/SKILL.md`),\n })),\n ],\n };\n}\n"],"mappings":";;;;AAKA,eAAsB,kBACpB,OACA,QACkC;CAClC,MAAM,EAAE,WAAW,SAAS,WAAW,MAAMA,cAAAA,qBAAqB,MAAM,SAAS,MAAM;CACvF,OAAO;EACL,UAAUC,sBAAAA,oBAAoB,WAAW,SAAS,MAAM;EACxD,WAAW,CACT;GAAE,MAAM;GAAiB,MAAM;GAAS,YAAY;EAAQ,GAC5D,GAAG,CAAC,uBAAuB,kBAAkB,CAAC,CAAC,KAAK,UAAU;GAC5D;GACA,MAAM;GACN,aAAA,GAAYC,oCAAAA,oBAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,MAAqC,eAAe,KAAK,UAAU;EACjF,EAAE,CACJ;CACF;AACF"}
1
+ {"version":3,"file":"skillServer.cjs","names":["discoverServerSkills","createSkillCommands","mountMcpSkills","readPackageResource"],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts"],"sourcesContent":["import type { DoomHeadlessHostService } from '@agimon-ai/doompi-core/headless';\nimport { type DoomServerSessionPlugin, readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nimport { mountMcpSkills } from '../../../../../services/mcpSkills';\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups, mcpGroups } = await discoverServerSkills(agent.context, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n services: [mountMcpSkills(mcpGroups, signal)],\n resources: [\n { name: 'doompi/skills', kind: 'skill', read: () => catalog },\n ...['doompi-author-skill', 'doompi-use-skill'].map((name) => ({\n name,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, `src/prompts/${name}/SKILL.md`),\n })),\n ],\n };\n}\n"],"mappings":";;;;;AAMA,eAAsB,kBACpB,OACA,QACkC;CAClC,MAAM,EAAE,WAAW,SAAS,QAAQ,cAAc,MAAMA,gBAAAA,qBAAqB,MAAM,SAAS,MAAM;CAClG,OAAO;EACL,UAAUC,sBAAAA,oBAAoB,WAAW,SAAS,MAAM;EACxD,UAAU,CAACC,cAAAA,eAAe,WAAW,MAAM,CAAC;EAC5C,WAAW,CACT;GAAE,MAAM;GAAiB,MAAM;GAAS,YAAY;EAAQ,GAC5D,GAAG,CAAC,uBAAuB,kBAAkB,CAAC,CAAC,KAAK,UAAU;GAC5D;GACA,MAAM;GACN,aAAA,GAAYC,oCAAAA,oBAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,MAAqC,eAAe,KAAK,UAAU;EACjF,EAAE,CACJ;CACF;AACF"}
@@ -1,11 +1,13 @@
1
+ import { mountMcpSkills } from "../../../../../services/mcpSkills/index.mjs";
1
2
  import { discoverServerSkills } from "../../../../../services/serverInventory/index.mjs";
2
3
  import { createSkillCommands } from "./skillCommands.mjs";
3
4
  import { readPackageResource } from "@agimon-ai/doompi-core/server-facet";
4
5
  //#region src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts
5
6
  async function createSkillServer(agent, signal) {
6
- const { inventory, catalog, groups } = await discoverServerSkills(agent.context, signal);
7
+ const { inventory, catalog, groups, mcpGroups } = await discoverServerSkills(agent.context, signal);
7
8
  return {
8
9
  commands: createSkillCommands(inventory, catalog, groups),
10
+ services: [mountMcpSkills(mcpGroups, signal)],
9
11
  resources: [{
10
12
  name: "doompi/skills",
11
13
  kind: "skill",
@@ -1 +1 @@
1
- {"version":3,"file":"skillServer.mjs","names":[],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts"],"sourcesContent":["import type { DoomHeadlessHostService } from '@agimon-ai/doompi-core/headless';\nimport { type DoomServerSessionPlugin, readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups } = await discoverServerSkills(agent.context, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n resources: [\n { name: 'doompi/skills', kind: 'skill', read: () => catalog },\n ...['doompi-author-skill', 'doompi-use-skill'].map((name) => ({\n name,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, `src/prompts/${name}/SKILL.md`),\n })),\n ],\n };\n}\n"],"mappings":";;;;AAKA,eAAsB,kBACpB,OACA,QACkC;CAClC,MAAM,EAAE,WAAW,SAAS,WAAW,MAAM,qBAAqB,MAAM,SAAS,MAAM;CACvF,OAAO;EACL,UAAU,oBAAoB,WAAW,SAAS,MAAM;EACxD,WAAW,CACT;GAAE,MAAM;GAAiB,MAAM;GAAS,YAAY;EAAQ,GAC5D,GAAG,CAAC,uBAAuB,kBAAkB,CAAC,CAAC,KAAK,UAAU;GAC5D;GACA,MAAM;GACN,YAAY,oBAAoB,YAAY,KAAK,eAAe,KAAK,UAAU;EACjF,EAAE,CACJ;CACF;AACF"}
1
+ {"version":3,"file":"skillServer.mjs","names":[],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts"],"sourcesContent":["import type { DoomHeadlessHostService } from '@agimon-ai/doompi-core/headless';\nimport { type DoomServerSessionPlugin, readPackageResource } from '@agimon-ai/doompi-core/server-facet';\n\nimport { mountMcpSkills } from '../../../../../services/mcpSkills';\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups, mcpGroups } = await discoverServerSkills(agent.context, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n services: [mountMcpSkills(mcpGroups, signal)],\n resources: [\n { name: 'doompi/skills', kind: 'skill', read: () => catalog },\n ...['doompi-author-skill', 'doompi-use-skill'].map((name) => ({\n name,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, `src/prompts/${name}/SKILL.md`),\n })),\n ],\n };\n}\n"],"mappings":";;;;;AAMA,eAAsB,kBACpB,OACA,QACkC;CAClC,MAAM,EAAE,WAAW,SAAS,QAAQ,cAAc,MAAM,qBAAqB,MAAM,SAAS,MAAM;CAClG,OAAO;EACL,UAAU,oBAAoB,WAAW,SAAS,MAAM;EACxD,UAAU,CAAC,eAAe,WAAW,MAAM,CAAC;EAC5C,WAAW,CACT;GAAE,MAAM;GAAiB,MAAM;GAAS,YAAY;EAAQ,GAC5D,GAAG,CAAC,uBAAuB,kBAAkB,CAAC,CAAC,KAAK,UAAU;GAC5D;GACA,MAAM;GACN,YAAY,oBAAoB,YAAY,KAAK,eAAe,KAAK,UAAU;EACjF,EAAE,CACJ;CACF;AACF"}
@@ -1,10 +1,14 @@
1
1
  const require_skillServer = require("./_lib/skillServer.cjs");
2
2
  //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
3
3
  var root_server_default = (0, require("@agimon-ai/doompi-core/extension-file").defineRoot)(async ({ agent, signal }) => {
4
- return { value: agent ? await require_skillServer.createSkillServer(agent, signal) : {
4
+ const runtime = agent ? await require_skillServer.createSkillServer(agent, signal) : {
5
5
  commands: [],
6
6
  resources: []
7
- } };
7
+ };
8
+ return {
9
+ value: runtime,
10
+ services: runtime.services
11
+ };
8
12
  });
9
13
  //#endregion
10
14
  exports.default = root_server_default;
@@ -1 +1 @@
1
- {"version":3,"file":"root.server.cjs","names":["defineRoot","createSkillServer"],"sources":["../../../../../../src/extensions/workspaces/sessions/(backend)/root.server.ts"],"sourcesContent":["import { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/doompi-core/server-facet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, signal)\n : { commands: [], resources: [] };\n return { value: runtime };\n});\n"],"mappings":";;AAKA,IAAA,uBAAA,kDAAeA,CAAAA,CAAAA,WAAAA,CAAW,OAAO,EAAE,OAAO,aAAsC;CAI9E,OAAO,EAAE,OAHgC,QACrC,MAAMC,oBAAAA,kBAAkB,OAAO,MAAM,IACrC;EAAE,UAAU,CAAC;EAAG,WAAW,CAAC;CAAE,EACV;AAC1B,CAAC"}
1
+ {"version":3,"file":"root.server.cjs","names":["defineRoot","createSkillServer"],"sources":["../../../../../../src/extensions/workspaces/sessions/(backend)/root.server.ts"],"sourcesContent":["import { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/doompi-core/server-facet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, signal)\n : { commands: [], resources: [] };\n return { value: runtime, services: runtime.services };\n});\n"],"mappings":";;AAKA,IAAA,uBAAA,kDAAeA,CAAAA,CAAAA,WAAAA,CAAW,OAAO,EAAE,OAAO,aAAsC;CAC9E,MAAM,UAAmC,QACrC,MAAMC,oBAAAA,kBAAkB,OAAO,MAAM,IACrC;EAAE,UAAU,CAAC;EAAG,WAAW,CAAC;CAAE;CAClC,OAAO;EAAE,OAAO;EAAS,UAAU,QAAQ;CAAS;AACtD,CAAC"}
@@ -2,10 +2,14 @@ import { createSkillServer } from "./_lib/skillServer.mjs";
2
2
  import { defineRoot } from "@agimon-ai/doompi-core/extension-file";
3
3
  //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
4
4
  var root_server_default = defineRoot(async ({ agent, signal }) => {
5
- return { value: agent ? await createSkillServer(agent, signal) : {
5
+ const runtime = agent ? await createSkillServer(agent, signal) : {
6
6
  commands: [],
7
7
  resources: []
8
- } };
8
+ };
9
+ return {
10
+ value: runtime,
11
+ services: runtime.services
12
+ };
9
13
  });
10
14
  //#endregion
11
15
  export { root_server_default as default };
@@ -1 +1 @@
1
- {"version":3,"file":"root.server.mjs","names":[],"sources":["../../../../../../src/extensions/workspaces/sessions/(backend)/root.server.ts"],"sourcesContent":["import { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/doompi-core/server-facet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, signal)\n : { commands: [], resources: [] };\n return { value: runtime };\n});\n"],"mappings":";;;AAKA,IAAA,sBAAe,WAAW,OAAO,EAAE,OAAO,aAAsC;CAI9E,OAAO,EAAE,OAHgC,QACrC,MAAM,kBAAkB,OAAO,MAAM,IACrC;EAAE,UAAU,CAAC;EAAG,WAAW,CAAC;CAAE,EACV;AAC1B,CAAC"}
1
+ {"version":3,"file":"root.server.mjs","names":[],"sources":["../../../../../../src/extensions/workspaces/sessions/(backend)/root.server.ts"],"sourcesContent":["import { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/doompi-core/server-facet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, signal)\n : { commands: [], resources: [] };\n return { value: runtime, services: runtime.services };\n});\n"],"mappings":";;;AAKA,IAAA,sBAAe,WAAW,OAAO,EAAE,OAAO,aAAsC;CAC9E,MAAM,UAAmC,QACrC,MAAM,kBAAkB,OAAO,MAAM,IACrC;EAAE,UAAU,CAAC;EAAG,WAAW,CAAC;CAAE;CAClC,OAAO;EAAE,OAAO;EAAS,UAAU,QAAQ;CAAS;AACtD,CAAC"}
@@ -0,0 +1,40 @@
1
+ let node_fs_promises = require("node:fs/promises");
2
+ //#region src/services/mcpSkills/index.ts
3
+ const MCP_SKILLS_SERVICE = "doom/discovered-mcp-skills";
4
+ /** Explicit remote provider, never a projection of the local resource registry. */
5
+ function mountMcpSkills(groups, signal) {
6
+ return (context) => {
7
+ context.provide(MCP_SKILLS_SERVICE, { list(remote) {
8
+ signal.throwIfAborted();
9
+ remote.signal.throwIfAborted();
10
+ const domains = remote.selection.read().domains;
11
+ const skills = /* @__PURE__ */ new Map();
12
+ for (const group of groups) {
13
+ if (group.domain !== void 0 && !domains.includes(group.domain)) continue;
14
+ for (const skill of group.skills) {
15
+ if (skills.has(skill.name)) continue;
16
+ skills.set(skill.name, {
17
+ name: skill.name,
18
+ description: skill.description,
19
+ async read() {
20
+ const readSignal = AbortSignal.any([signal, remote.signal]);
21
+ readSignal.throwIfAborted();
22
+ const text = await (0, node_fs_promises.readFile)(skill.filePath, {
23
+ encoding: "utf8",
24
+ signal: readSignal
25
+ });
26
+ readSignal.throwIfAborted();
27
+ return text;
28
+ }
29
+ });
30
+ }
31
+ }
32
+ return [...skills.values()];
33
+ } });
34
+ };
35
+ }
36
+ //#endregion
37
+ exports.MCP_SKILLS_SERVICE = MCP_SKILLS_SERVICE;
38
+ exports.mountMcpSkills = mountMcpSkills;
39
+
40
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["readFile"],"sources":["../../../../src/services/mcpSkills/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n"],"mappings":";;AAOA,MAAa,qBAAqB;;AAOlC,SAAgB,eAAe,QAAqC,QAAqB;CACvF,QAAQ,YAA2B;EACjC,QAAQ,QAAQ,oBAAoB,EAClC,KAAK,QAAQ;GACX,OAAO,eAAe;GACtB,OAAO,OAAO,eAAe;GAC7B,MAAM,UAAU,OAAO,UAAU,KAAK,CAAC,CAAC;GACxC,MAAM,yBAAS,IAAI,IAA0B;GAC7C,KAAK,MAAM,SAAS,QAAQ;IAC1B,IAAI,MAAM,WAAW,KAAA,KAAa,CAAC,QAAQ,SAAS,MAAM,MAAM,GAAG;IACnE,KAAK,MAAM,SAAS,MAAM,QAAQ;KAEhC,IAAI,OAAO,IAAI,MAAM,IAAI,GAAG;KAC5B,OAAO,IAAI,MAAM,MAAM;MACrB,MAAM,MAAM;MACZ,aAAa,MAAM;MACnB,MAAM,OAAO;OACX,MAAM,aAAa,YAAY,IAAI,CAAC,QAAQ,OAAO,MAAM,CAAC;OAC1D,WAAW,eAAe;OAC1B,MAAM,OAAO,OAAA,GAAMA,iBAAAA,SAAAA,CAAS,MAAM,UAAU;QAAE,UAAU;QAAQ,QAAQ;OAAW,CAAC;OACpF,WAAW,eAAe;OAC1B,OAAO;MACT;KACF,CAAC;IACH;GACF;GACA,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC;EAC5B,EACF,CAA4B;CAC9B;AACF"}
@@ -0,0 +1,39 @@
1
+ import { readFile } from "node:fs/promises";
2
+ //#region src/services/mcpSkills/index.ts
3
+ const MCP_SKILLS_SERVICE = "doom/discovered-mcp-skills";
4
+ /** Explicit remote provider, never a projection of the local resource registry. */
5
+ function mountMcpSkills(groups, signal) {
6
+ return (context) => {
7
+ context.provide(MCP_SKILLS_SERVICE, { list(remote) {
8
+ signal.throwIfAborted();
9
+ remote.signal.throwIfAborted();
10
+ const domains = remote.selection.read().domains;
11
+ const skills = /* @__PURE__ */ new Map();
12
+ for (const group of groups) {
13
+ if (group.domain !== void 0 && !domains.includes(group.domain)) continue;
14
+ for (const skill of group.skills) {
15
+ if (skills.has(skill.name)) continue;
16
+ skills.set(skill.name, {
17
+ name: skill.name,
18
+ description: skill.description,
19
+ async read() {
20
+ const readSignal = AbortSignal.any([signal, remote.signal]);
21
+ readSignal.throwIfAborted();
22
+ const text = await readFile(skill.filePath, {
23
+ encoding: "utf8",
24
+ signal: readSignal
25
+ });
26
+ readSignal.throwIfAborted();
27
+ return text;
28
+ }
29
+ });
30
+ }
31
+ }
32
+ return [...skills.values()];
33
+ } });
34
+ };
35
+ }
36
+ //#endregion
37
+ export { MCP_SKILLS_SERVICE, mountMcpSkills };
38
+
39
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/mcpSkills/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/doompi-core/mcp-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n"],"mappings":";;AAOA,MAAa,qBAAqB;;AAOlC,SAAgB,eAAe,QAAqC,QAAqB;CACvF,QAAQ,YAA2B;EACjC,QAAQ,QAAQ,oBAAoB,EAClC,KAAK,QAAQ;GACX,OAAO,eAAe;GACtB,OAAO,OAAO,eAAe;GAC7B,MAAM,UAAU,OAAO,UAAU,KAAK,CAAC,CAAC;GACxC,MAAM,yBAAS,IAAI,IAA0B;GAC7C,KAAK,MAAM,SAAS,QAAQ;IAC1B,IAAI,MAAM,WAAW,KAAA,KAAa,CAAC,QAAQ,SAAS,MAAM,MAAM,GAAG;IACnE,KAAK,MAAM,SAAS,MAAM,QAAQ;KAEhC,IAAI,OAAO,IAAI,MAAM,IAAI,GAAG;KAC5B,OAAO,IAAI,MAAM,MAAM;MACrB,MAAM,MAAM;MACZ,aAAa,MAAM;MACnB,MAAM,OAAO;OACX,MAAM,aAAa,YAAY,IAAI,CAAC,QAAQ,OAAO,MAAM,CAAC;OAC1D,WAAW,eAAe;OAC1B,MAAM,OAAO,MAAM,SAAS,MAAM,UAAU;QAAE,UAAU;QAAQ,QAAQ;OAAW,CAAC;OACpF,WAAW,eAAe;OAC1B,OAAO;MACT;KACF,CAAC;IACH;GACF;GACA,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC;EAC5B,EACF,CAA4B;CAC9B;AACF"}
@@ -5,11 +5,33 @@ node_path = require_runtime.__toESM(node_path, 1);
5
5
  let _agimon_ai_doompi_config_domains = require("@agimon-ai/doompi-config/domains");
6
6
  let _agimon_ai_doompi_domain_plugins = require("@agimon-ai/doompi-domain/plugins");
7
7
  let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
8
+ let node_fs_promises = require("node:fs/promises");
8
9
  let node_os = require("node:os");
9
10
  node_os = require_runtime.__toESM(node_os, 1);
10
11
  let _agimon_ai_doompi_config_harnessState = require("@agimon-ai/doompi-config/harnessState");
11
12
  let _agimon_ai_doompi_domain_resources = require("@agimon-ai/doompi-domain/resources");
12
13
  //#region src/services/serverInventory/index.ts
14
+ const LOCAL_ONLY_MCP_PACKAGES = /* @__PURE__ */ new Set([
15
+ "@agimon-ai/doompi-author",
16
+ "@agimon-ai/doompi-git",
17
+ "@agimon-ai/doompi-goal",
18
+ "@agimon-ai/doompi-help",
19
+ "@agimon-ai/doompi-loop",
20
+ "@agimon-ai/doompi-user-feedback",
21
+ "@agimon-ai/doompi-voice",
22
+ "@agimon-ai/doompi-workflow"
23
+ ]);
24
+ async function isMcpSkillPlugin(entry) {
25
+ if (entry.source?.type === "npm") return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);
26
+ try {
27
+ const manifest = JSON.parse(await (0, node_fs_promises.readFile)(node_path.default.join(entry.directory, "package.json"), "utf8"));
28
+ if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) return false;
29
+ const name = manifest.name;
30
+ return typeof name === "string" && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);
31
+ } catch (error) {
32
+ return error.code === "ENOENT";
33
+ }
34
+ }
13
35
  /**
14
36
  * Whether a discovered skill came from one of these paths.
15
37
  *
@@ -51,16 +73,21 @@ async function discoverServerSkills(execution, signal) {
51
73
  signal.throwIfAborted();
52
74
  try {
53
75
  const plugins = await (0, _agimon_ai_doompi_domain_plugins.materializePluginEntries)((0, _agimon_ai_doompi_config_domains.resolvePluginEntries)(execution.repoRoot, [domain], [], home));
76
+ const mcpPluginRoots = (await Promise.all(plugins.map(async (plugin) => await isMcpSkillPlugin(plugin) ? node_path.default.join(plugin.directory, "skills") : void 0))).filter((root) => root !== void 0);
54
77
  const collected = await (0, _agimon_ai_doompi_domain_resources.collectResources)(execution.repoRoot, plugins, {
55
78
  agents: false,
56
79
  mcp: false,
57
80
  sharedSkills: false
58
81
  });
59
82
  try {
60
- if (collected.skillDirectories.length > 0) sources.push({
61
- domain,
62
- paths: [...collected.skillDirectories]
63
- });
83
+ if (collected.skillDirectories.length > 0) {
84
+ const paths = [...collected.skillDirectories];
85
+ sources.push({
86
+ domain,
87
+ paths,
88
+ mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots))
89
+ });
90
+ }
64
91
  } finally {
65
92
  await collected.cleanup();
66
93
  }
@@ -74,10 +101,14 @@ async function discoverServerSkills(execution, signal) {
74
101
  const sharedPaths = shared.map((skill) => skill.path);
75
102
  for (const domain of domains) if ((0, _agimon_ai_doompi_config_domains.resolveSharedSkills)(execution.repoRoot, [domain], home)) sources.push({
76
103
  domain,
77
- paths: sharedPaths
104
+ paths: sharedPaths,
105
+ mcpPaths: sharedPaths
78
106
  });
79
107
  }
80
- sources.push({ paths: [repoSkills] });
108
+ sources.push({
109
+ paths: [repoSkills],
110
+ mcpPaths: [repoSkills]
111
+ });
81
112
  signal.throwIfAborted();
82
113
  const unique = [.../* @__PURE__ */ new Set([...sources.flatMap((source) => source.paths), ...state.skillDirectories ?? []])];
83
114
  const inventory = await new require_index.DeferredSkillLoader({
@@ -89,6 +120,10 @@ async function discoverServerSkills(execution, signal) {
89
120
  ...source.domain === void 0 ? {} : { domain: source.domain },
90
121
  skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths))
91
122
  })).filter((group) => group.skills.length > 0);
123
+ const mcpGroups = sources.map((source) => ({
124
+ ...source.domain === void 0 ? {} : { domain: source.domain },
125
+ skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths))
126
+ })).filter((group) => group.skills.length > 0);
92
127
  const activeSkills = inventory.skills.filter((skill) => within(skill.filePath, [...state.skillDirectories ?? [], repoSkills]));
93
128
  return {
94
129
  inventory: {
@@ -96,6 +131,7 @@ async function discoverServerSkills(execution, signal) {
96
131
  diagnostics: [...inventory.diagnostics, ...diagnostics]
97
132
  },
98
133
  groups,
134
+ mcpGroups,
99
135
  catalog: (0, _earendil_works_pi_coding_agent.formatSkillsForPrompt)(activeSkills) || "(no discovered skills)"
100
136
  };
101
137
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["path","readHarnessState","os","listDomainNames","materializePluginEntries","resolvePluginEntries","collectResources","discoverSkillsAsync","resolveSharedSkills","DeferredSkillLoader","formatSkillsForPrompt"],"sources":["../../../../src/services/serverInventory/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { listDomainNames, resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/doompi-core/headless';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/doompi-domain/resources';\nimport { formatSkillsForPrompt, type Skill } from '@earendil-works/pi-coding-agent';\n\nimport { DeferredSkillLoader, type DeferredSkillSnapshot } from '../deferredSkills';\n\n/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.doom/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? os.homedir();\n const repoSkills = path.join(execution.repoRoot, '.doom', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...listDomainNames(execution.repoRoot, home));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) sources.push({ domain, paths: [...collected.skillDirectories] });\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (resolveSharedSkills(execution.repoRoot, [domain], home)) sources.push({ domain, paths: sharedPaths });\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills] });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["readFile","path","readHarnessState","os","listDomainNames","materializePluginEntries","resolvePluginEntries","collectResources","discoverSkillsAsync","resolveSharedSkills","DeferredSkillLoader","formatSkillsForPrompt"],"sources":["../../../../src/services/serverInventory/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n listDomainNames,\n resolvePluginEntries,\n resolveSharedSkills,\n type PluginEntry,\n} from '@agimon-ai/doompi-config/domains';\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/doompi-core/headless';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/doompi-domain/resources';\nimport { formatSkillsForPrompt, type Skill } from '@earendil-works/pi-coding-agent';\n\nimport { DeferredSkillLoader, type DeferredSkillSnapshot } from '../deferredSkills';\n\n/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly mcpGroups: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n readonly mcpPaths: readonly string[];\n}\n\nconst LOCAL_ONLY_MCP_PACKAGES = new Set([\n '@agimon-ai/doompi-author',\n '@agimon-ai/doompi-git',\n '@agimon-ai/doompi-goal',\n '@agimon-ai/doompi-help',\n '@agimon-ai/doompi-loop',\n '@agimon-ai/doompi-user-feedback',\n '@agimon-ai/doompi-voice',\n '@agimon-ai/doompi-workflow',\n]);\n\nasync function isMcpSkillPlugin(entry: PluginEntry): Promise<boolean> {\n if (entry.source?.type === 'npm') return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);\n try {\n const manifest = JSON.parse(await readFile(path.join(entry.directory, 'package.json'), 'utf8')) as unknown;\n if (typeof manifest !== 'object' || manifest === null || Array.isArray(manifest)) return false;\n const name = (manifest as { name?: unknown }).name;\n return typeof name === 'string' && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);\n } catch (error) {\n // A package manifest is optional for catalog plugins, but a present identity\n // that cannot be read or parsed must never make the plugin remotely eligible.\n return (error as NodeJS.ErrnoException).code === 'ENOENT';\n }\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.doom/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? os.homedir();\n const repoSkills = path.join(execution.repoRoot, '.doom', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...listDomainNames(execution.repoRoot, home));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));\n const mcpPluginRoots = (\n await Promise.all(\n plugins.map(async (plugin) =>\n (await isMcpSkillPlugin(plugin)) ? path.join(plugin.directory, 'skills') : undefined,\n ),\n )\n ).filter((root): root is string => root !== undefined);\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) {\n const paths = [...collected.skillDirectories];\n sources.push({\n domain,\n paths,\n // Exact collected paths retain this domain's per-plugin skill subset.\n // A plugin root would also admit skills loaded for another domain.\n mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots)),\n });\n }\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (resolveSharedSkills(execution.repoRoot, [domain], home)) {\n sources.push({ domain, paths: sharedPaths, mcpPaths: sharedPaths });\n }\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills], mcpPaths: [repoSkills] });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const mcpGroups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n mcpGroups,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n"],"mappings":"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"}
@@ -3,10 +3,32 @@ import path from "node:path";
3
3
  import { listDomainNames, resolvePluginEntries, resolveSharedSkills } from "@agimon-ai/doompi-config/domains";
4
4
  import { materializePluginEntries } from "@agimon-ai/doompi-domain/plugins";
5
5
  import { formatSkillsForPrompt } from "@earendil-works/pi-coding-agent";
6
+ import { readFile } from "node:fs/promises";
6
7
  import os from "node:os";
7
8
  import { readHarnessState } from "@agimon-ai/doompi-config/harnessState";
8
9
  import { collectResources, discoverSkillsAsync } from "@agimon-ai/doompi-domain/resources";
9
10
  //#region src/services/serverInventory/index.ts
11
+ const LOCAL_ONLY_MCP_PACKAGES = /* @__PURE__ */ new Set([
12
+ "@agimon-ai/doompi-author",
13
+ "@agimon-ai/doompi-git",
14
+ "@agimon-ai/doompi-goal",
15
+ "@agimon-ai/doompi-help",
16
+ "@agimon-ai/doompi-loop",
17
+ "@agimon-ai/doompi-user-feedback",
18
+ "@agimon-ai/doompi-voice",
19
+ "@agimon-ai/doompi-workflow"
20
+ ]);
21
+ async function isMcpSkillPlugin(entry) {
22
+ if (entry.source?.type === "npm") return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);
23
+ try {
24
+ const manifest = JSON.parse(await readFile(path.join(entry.directory, "package.json"), "utf8"));
25
+ if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) return false;
26
+ const name = manifest.name;
27
+ return typeof name === "string" && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);
28
+ } catch (error) {
29
+ return error.code === "ENOENT";
30
+ }
31
+ }
10
32
  /**
11
33
  * Whether a discovered skill came from one of these paths.
12
34
  *
@@ -48,16 +70,21 @@ async function discoverServerSkills(execution, signal) {
48
70
  signal.throwIfAborted();
49
71
  try {
50
72
  const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));
73
+ const mcpPluginRoots = (await Promise.all(plugins.map(async (plugin) => await isMcpSkillPlugin(plugin) ? path.join(plugin.directory, "skills") : void 0))).filter((root) => root !== void 0);
51
74
  const collected = await collectResources(execution.repoRoot, plugins, {
52
75
  agents: false,
53
76
  mcp: false,
54
77
  sharedSkills: false
55
78
  });
56
79
  try {
57
- if (collected.skillDirectories.length > 0) sources.push({
58
- domain,
59
- paths: [...collected.skillDirectories]
60
- });
80
+ if (collected.skillDirectories.length > 0) {
81
+ const paths = [...collected.skillDirectories];
82
+ sources.push({
83
+ domain,
84
+ paths,
85
+ mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots))
86
+ });
87
+ }
61
88
  } finally {
62
89
  await collected.cleanup();
63
90
  }
@@ -71,10 +98,14 @@ async function discoverServerSkills(execution, signal) {
71
98
  const sharedPaths = shared.map((skill) => skill.path);
72
99
  for (const domain of domains) if (resolveSharedSkills(execution.repoRoot, [domain], home)) sources.push({
73
100
  domain,
74
- paths: sharedPaths
101
+ paths: sharedPaths,
102
+ mcpPaths: sharedPaths
75
103
  });
76
104
  }
77
- sources.push({ paths: [repoSkills] });
105
+ sources.push({
106
+ paths: [repoSkills],
107
+ mcpPaths: [repoSkills]
108
+ });
78
109
  signal.throwIfAborted();
79
110
  const unique = [.../* @__PURE__ */ new Set([...sources.flatMap((source) => source.paths), ...state.skillDirectories ?? []])];
80
111
  const inventory = await new DeferredSkillLoader({
@@ -86,6 +117,10 @@ async function discoverServerSkills(execution, signal) {
86
117
  ...source.domain === void 0 ? {} : { domain: source.domain },
87
118
  skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths))
88
119
  })).filter((group) => group.skills.length > 0);
120
+ const mcpGroups = sources.map((source) => ({
121
+ ...source.domain === void 0 ? {} : { domain: source.domain },
122
+ skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths))
123
+ })).filter((group) => group.skills.length > 0);
89
124
  const activeSkills = inventory.skills.filter((skill) => within(skill.filePath, [...state.skillDirectories ?? [], repoSkills]));
90
125
  return {
91
126
  inventory: {
@@ -93,6 +128,7 @@ async function discoverServerSkills(execution, signal) {
93
128
  diagnostics: [...inventory.diagnostics, ...diagnostics]
94
129
  },
95
130
  groups,
131
+ mcpGroups,
96
132
  catalog: formatSkillsForPrompt(activeSkills) || "(no discovered skills)"
97
133
  };
98
134
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/serverInventory/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { listDomainNames, resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/doompi-core/headless';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/doompi-domain/resources';\nimport { formatSkillsForPrompt, type Skill } from '@earendil-works/pi-coding-agent';\n\nimport { DeferredSkillLoader, type DeferredSkillSnapshot } from '../deferredSkills';\n\n/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.doom/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? os.homedir();\n const repoSkills = path.join(execution.repoRoot, '.doom', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...listDomainNames(execution.repoRoot, home));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) sources.push({ domain, paths: [...collected.skillDirectories] });\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (resolveSharedSkills(execution.repoRoot, [domain], home)) sources.push({ domain, paths: sharedPaths });\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills] });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqCA,SAAS,OAAO,UAAkB,OAAmC;CACnE,MAAM,WAAW,KAAK,QAAQ,QAAQ;CACtC,OAAO,MAAM,MAAM,SAAS;EAC1B,MAAM,OAAO,KAAK,QAAQ,IAAI;EAC9B,OAAO,aAAa,QAAQ,SAAS,WAAW,GAAG,OAAO,KAAK,KAAK;CACtE,CAAC;AACH;;;;;;;;;;;AAWA,eAAsB,qBACpB,WACA,QAC+B;CAC/B,OAAO,eAAe;CACtB,MAAM,QAAQ,iBAAiB,UAAU,WAAW;CACpD,MAAM,OAAO,UAAU,YAAY,QAAQ,GAAG,QAAQ;CACtD,MAAM,aAAa,KAAK,KAAK,UAAU,UAAU,SAAS,QAAQ;CAClE,MAAM,UAAyB,CAAC;CAChC,MAAM,cAAwB,CAAC;CAI/B,MAAM,UAAoB,CAAC;CAC3B,IAAI;EACF,QAAQ,KAAK,GAAG,gBAAgB,UAAU,UAAU,IAAI,CAAC;CAC3D,SAAS,OAAO;EACd,YAAY,KAAK,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;CAC7G;CAKA,KAAK,MAAM,UAAU,SAAS;EAC5B,OAAO,eAAe;EACtB,IAAI;GACF,MAAM,UAAU,MAAM,yBAAyB,qBAAqB,UAAU,UAAU,CAAC,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;GAC3G,MAAM,YAAY,MAAM,iBAAiB,UAAU,UAAU,SAAS;IACpE,QAAQ;IACR,KAAK;IACL,cAAc;GAChB,CAAC;GACD,IAAI;IACF,IAAI,UAAU,iBAAiB,SAAS,GAAG,QAAQ,KAAK;KAAE;KAAQ,OAAO,CAAC,GAAG,UAAU,gBAAgB;IAAE,CAAC;GAC5G,UAAU;IACR,MAAM,UAAU,QAAQ;GAC1B;EACF,SAAS,OAAO;GACd,YAAY,KAAK,WAAW,OAAO,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;EAClG;CACF;CAOA,OAAO,eAAe;CACtB,MAAM,SAAS,MAAM,oBAAoB,UAAU,UAAU,KAAK,KAAK,UAAU,UAAU,WAAW,QAAQ,CAAC;CAC/G,IAAI,OAAO,SAAS,GAAG;EACrB,MAAM,cAAc,OAAO,KAAK,UAAU,MAAM,IAAI;EACpD,KAAK,MAAM,UAAU,SACnB,IAAI,oBAAoB,UAAU,UAAU,CAAC,MAAM,GAAG,IAAI,GAAG,QAAQ,KAAK;GAAE;GAAQ,OAAO;EAAY,CAAC;CAE5G;CAGA,QAAQ,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;CAEpC,OAAO,eAAe;CAGtB,MAAM,SAAS,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,QAAQ,SAAS,WAAW,OAAO,KAAK,GAAG,GAAI,MAAM,oBAAoB,CAAC,CAAE,CAAC,CAAC;CAC7G,MAAM,YAAY,MAAM,IAAI,oBAAoB;EAAE,KAAK,UAAU;EAAK,YAAY;CAAO,CAAC,CAAC,CAAC,MAAM;CAClG,OAAO,eAAe;CAEtB,MAAM,SAAS,QACZ,KAAK,YAAY;EAChB,GAAI,OAAO,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,OAAO,OAAO;EAC/D,QAAQ,UAAU,OAAO,QAAQ,UAAU,OAAO,MAAM,UAAU,OAAO,KAAK,CAAC;CACjF,EAAE,CAAC,CACF,QAAQ,UAAU,MAAM,OAAO,SAAS,CAAC;CAI5C,MAAM,eAAe,UAAU,OAAO,QAAQ,UAC5C,OAAO,MAAM,UAAU,CAAC,GAAI,MAAM,oBAAoB,CAAC,GAAI,UAAU,CAAC,CACxE;CACA,OAAO;EACL,WAAW;GAAE,QAAQ,UAAU;GAAQ,aAAa,CAAC,GAAG,UAAU,aAAa,GAAG,WAAW;EAAE;EAC/F;EACA,SAAS,sBAAsB,YAAY,KAAK;CAClD;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/serverInventory/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n listDomainNames,\n resolvePluginEntries,\n resolveSharedSkills,\n type PluginEntry,\n} from '@agimon-ai/doompi-config/domains';\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/doompi-core/headless';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/doompi-domain/resources';\nimport { formatSkillsForPrompt, type Skill } from '@earendil-works/pi-coding-agent';\n\nimport { DeferredSkillLoader, type DeferredSkillSnapshot } from '../deferredSkills';\n\n/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly mcpGroups: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n readonly mcpPaths: readonly string[];\n}\n\nconst LOCAL_ONLY_MCP_PACKAGES = new Set([\n '@agimon-ai/doompi-author',\n '@agimon-ai/doompi-git',\n '@agimon-ai/doompi-goal',\n '@agimon-ai/doompi-help',\n '@agimon-ai/doompi-loop',\n '@agimon-ai/doompi-user-feedback',\n '@agimon-ai/doompi-voice',\n '@agimon-ai/doompi-workflow',\n]);\n\nasync function isMcpSkillPlugin(entry: PluginEntry): Promise<boolean> {\n if (entry.source?.type === 'npm') return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);\n try {\n const manifest = JSON.parse(await readFile(path.join(entry.directory, 'package.json'), 'utf8')) as unknown;\n if (typeof manifest !== 'object' || manifest === null || Array.isArray(manifest)) return false;\n const name = (manifest as { name?: unknown }).name;\n return typeof name === 'string' && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);\n } catch (error) {\n // A package manifest is optional for catalog plugins, but a present identity\n // that cannot be read or parsed must never make the plugin remotely eligible.\n return (error as NodeJS.ErrnoException).code === 'ENOENT';\n }\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.doom/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? os.homedir();\n const repoSkills = path.join(execution.repoRoot, '.doom', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...listDomainNames(execution.repoRoot, home));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));\n const mcpPluginRoots = (\n await Promise.all(\n plugins.map(async (plugin) =>\n (await isMcpSkillPlugin(plugin)) ? path.join(plugin.directory, 'skills') : undefined,\n ),\n )\n ).filter((root): root is string => root !== undefined);\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) {\n const paths = [...collected.skillDirectories];\n sources.push({\n domain,\n paths,\n // Exact collected paths retain this domain's per-plugin skill subset.\n // A plugin root would also admit skills loaded for another domain.\n mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots)),\n });\n }\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (resolveSharedSkills(execution.repoRoot, [domain], home)) {\n sources.push({ domain, paths: sharedPaths, mcpPaths: sharedPaths });\n }\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills], mcpPaths: [repoSkills] });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const mcpGroups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n mcpGroups,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-skill",
3
- "version": "0.0.1-alpha.54",
3
+ "version": "0.0.1-alpha.55",
4
4
  "description": "Session skill catalogue, deferred skill discovery, and the skill browser for DoomPi.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -70,17 +70,17 @@
70
70
  "access": "public"
71
71
  },
72
72
  "dependencies": {
73
- "@agimon-ai/doompi-config": "0.0.1-alpha.75",
74
- "@agimon-ai/doompi-core": "0.0.1-alpha.76",
75
- "@agimon-ai/doompi-domain": "0.0.1-alpha.54",
76
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.73",
77
- "@agimon-ai/doompi-ui": "0.0.1-alpha.76",
73
+ "@agimon-ai/doompi-config": "0.0.1-alpha.76",
74
+ "@agimon-ai/doompi-core": "0.0.1-alpha.77",
75
+ "@agimon-ai/doompi-domain": "0.0.1-alpha.55",
76
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.74",
77
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.77",
78
78
  "@deepseek-ai/cordis": "4.0.2",
79
79
  "gpt-tokenizer": "4.0.0"
80
80
  },
81
81
  "devDependencies": {
82
- "@agimon-ai/doompi-build": "0.0.1-alpha.5",
83
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.71",
82
+ "@agimon-ai/doompi-build": "0.0.1-alpha.6",
83
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.72",
84
84
  "@deepseek-ai/cordis": "4.0.2",
85
85
  "@earendil-works/pi-coding-agent": "0.85.1",
86
86
  "@earendil-works/pi-tui": "0.85.1",
@@ -105,6 +105,13 @@
105
105
  "engines": {
106
106
  "node": ">=22.19.0"
107
107
  },
108
+ "doompiMcp": {
109
+ "entry": "./generated/mcp.ts",
110
+ "dist": "./dist/extensions/mcp.mjs",
111
+ "scopes": [
112
+ "session"
113
+ ]
114
+ },
108
115
  "doompiServer": {
109
116
  "entry": "./generated/server.ts",
110
117
  "dist": "./dist/extensions/server.mjs",
@@ -122,7 +129,8 @@
122
129
  ]
123
130
  },
124
131
  "scripts": {
125
- "build": "tsdown",
132
+ "build": "tsdown && pnpm build:mcp",
133
+ "build:mcp": "tsdown --config tsdown.mcp.config.ts",
126
134
  "test": "vitest --run",
127
135
  "typecheck": "tsc --noEmit",
128
136
  "lint": "oxlint . && oxfmt . --check",