@agimon-ai/doompi-skill 0.0.1-alpha.10
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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/adapters/deferredSkills.cjs +12 -0
- package/dist/adapters/deferredSkills.cjs.map +1 -0
- package/dist/adapters/deferredSkills.mjs +12 -0
- package/dist/adapters/deferredSkills.mjs.map +1 -0
- package/dist/adapters/helpSkills.cjs +2 -0
- package/dist/adapters/helpSkills.cjs.map +1 -0
- package/dist/adapters/helpSkills.mjs +2 -0
- package/dist/adapters/helpSkills.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.d.cts +7 -0
- package/dist/adapters/pi/extension.d.cts.map +1 -0
- package/dist/adapters/pi/extension.d.mts +7 -0
- package/dist/adapters/pi/extension.d.mts.map +1 -0
- package/dist/adapters/pi/extension.mjs +2 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/adapters/pi/skillReadiness.cjs +4 -0
- package/dist/adapters/pi/skillReadiness.cjs.map +1 -0
- package/dist/adapters/pi/skillReadiness.mjs +4 -0
- package/dist/adapters/pi/skillReadiness.mjs.map +1 -0
- package/dist/adapters/skillCatalog.cjs +2 -0
- package/dist/adapters/skillCatalog.cjs.map +1 -0
- package/dist/adapters/skillCatalog.d.cts +58 -0
- package/dist/adapters/skillCatalog.d.cts.map +1 -0
- package/dist/adapters/skillCatalog.d.mts +58 -0
- package/dist/adapters/skillCatalog.d.mts.map +1 -0
- package/dist/adapters/skillCatalog.mjs +2 -0
- package/dist/adapters/skillCatalog.mjs.map +1 -0
- package/dist/catalog.cjs +1 -0
- package/dist/catalog.d.cts +2 -0
- package/dist/catalog.d.mts +2 -0
- package/dist/catalog.mjs +1 -0
- package/dist/commands/skillsCommand.cjs +2 -0
- package/dist/commands/skillsCommand.cjs.map +1 -0
- package/dist/commands/skillsCommand.mjs +2 -0
- package/dist/commands/skillsCommand.mjs.map +1 -0
- package/dist/extensions/pi.cjs +1 -0
- package/dist/extensions/pi.d.cts +2 -0
- package/dist/extensions/pi.d.mts +2 -0
- package/dist/extensions/pi.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +1 -0
- package/dist/services/skillText.cjs +2 -0
- package/dist/services/skillText.cjs.map +1 -0
- package/dist/services/skillText.d.cts +13 -0
- package/dist/services/skillText.d.cts.map +1 -0
- package/dist/services/skillText.d.mts +13 -0
- package/dist/services/skillText.d.mts.map +1 -0
- package/dist/services/skillText.mjs +2 -0
- package/dist/services/skillText.mjs.map +1 -0
- package/dist/tui/skillsOverlay.cjs +4 -0
- package/dist/tui/skillsOverlay.cjs.map +1 -0
- package/dist/tui/skillsOverlay.d.cts +18 -0
- package/dist/tui/skillsOverlay.d.cts.map +1 -0
- package/dist/tui/skillsOverlay.d.mts +18 -0
- package/dist/tui/skillsOverlay.d.mts.map +1 -0
- package/dist/tui/skillsOverlay.mjs +4 -0
- package/dist/tui/skillsOverlay.mjs.map +1 -0
- package/dist/types/skills.cjs +2 -0
- package/dist/types/skills.cjs.map +1 -0
- package/dist/types/skills.d.cts +32 -0
- package/dist/types/skills.d.cts.map +1 -0
- package/dist/types/skills.d.mts +32 -0
- package/dist/types/skills.d.mts.map +1 -0
- package/dist/types/skills.mjs +2 -0
- package/dist/types/skills.mjs.map +1 -0
- package/llms.txt +15 -0
- package/package.json +100 -0
- package/src/prompts/doompi-author-skill/SKILL.md +33 -0
- package/src/prompts/doompi-use-skill/SKILL.md +18 -0
package/dist/catalog.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/skillCatalog.cjs");exports.buildSkillCatalog=e.buildSkillCatalog,exports.counter=e.counter;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { SkillCatalog, SkillCatalogOptions, SkillEntry, SkillGroup, SkillOwnerNode, buildSkillCatalog, counter } from "./adapters/skillCatalog.cjs";
|
|
2
|
+
export { type SkillCatalog, type SkillCatalogOptions, type SkillEntry, type SkillGroup, type SkillOwnerNode, buildSkillCatalog, counter };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { SkillCatalog, SkillCatalogOptions, SkillEntry, SkillGroup, SkillOwnerNode, buildSkillCatalog, counter } from "./adapters/skillCatalog.mjs";
|
|
2
|
+
export { type SkillCatalog, type SkillCatalogOptions, type SkillEntry, type SkillGroup, type SkillOwnerNode, buildSkillCatalog, counter };
|
package/dist/catalog.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{buildSkillCatalog as e,counter as t}from"./adapters/skillCatalog.mjs";export{e as buildSkillCatalog,t as counter};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../types/skills.cjs"),t=require("../services/skillText.cjs");let n=require("@agimon-ai/doompi-config/piContext");function r(r,i){r.registerCommand(e.SKILLS_COMMAND,{description:`Browse the skills this session loaded, and what other domains would add`,handler:async(e,r)=>{if(!r.hasUI||r.mode!==`tui`){r.ui.notify(`/skills requires interactive mode`,`error`);return}let a=(0,n.requireDoomConfigContext)(i.cordisContext()).harness,[o,s]=await Promise.all([i.repositoryRoot(r),i.inventory(r)]),c=await i.buildCatalog({repoRoot:o,activeSkillDirectories:a.skillDirectories,extensionSources:i.extensionSources(),helpSkills:s.helpSkills,helpDiagnostics:[...s.diagnostics]}),l=await i.openOverlay(r,{catalog:c,repoRoot:o});l&&(r.ui.setEditorText(t.skillInvocation(l.skill.name)),r.ui.notify(`Loaded ${l.skill.name} into the prompt. Press enter to run it.`,`info`))}})}exports.registerSkillsCommand=r;
|
|
2
|
+
//# sourceMappingURL=skillsCommand.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsCommand.cjs","names":["SKILLS_COMMAND","requireDoomConfigContext","skillInvocation"],"sources":["../../src/commands/skillsCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { skillInvocation } from '../services/skillText.ts';\nimport type { SkillCatalog, SkillCatalogOptions } from '../adapters/skillCatalog.ts';\nimport type { SkillsOverlayResult } from '../tui/skillsOverlay.ts';\nimport { SKILLS_COMMAND } from '../types/skills.ts';\n\nconst INFO = 'info';\nconst ERROR = 'error';\n\nexport interface SkillsCommandDependencies {\n readonly cordisContext: () => Context;\n /** Skill directories every loaded extension has contributed this session. */\n readonly extensionSources: () => readonly { source: string; directories: readonly string[] }[];\n /** Merged view of normal, deferred and package Help skills. */\n readonly inventory: (ctx: ExtensionContext) => Promise<{\n readonly helpSkills: SkillCatalogOptions['helpSkills'];\n readonly diagnostics: readonly string[];\n }>;\n readonly repositoryRoot: (ctx: ExtensionContext) => Promise<string>;\n readonly buildCatalog: (request: SkillCatalogOptions) => Promise<SkillCatalog>;\n readonly openOverlay: (\n ctx: ExtensionContext,\n options: { catalog: SkillCatalog; repoRoot: string },\n ) => Promise<SkillsOverlayResult | undefined>;\n}\n\nexport function registerSkillsCommand(pi: ExtensionAPI, dependencies: SkillsCommandDependencies): void {\n pi.registerCommand(SKILLS_COMMAND, {\n description: 'Browse the skills this session loaded, and what other domains would add',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI || ctx.mode !== 'tui') {\n ctx.ui.notify('/skills requires interactive mode', ERROR);\n return;\n }\n\n const state = requireDoomConfigContext(dependencies.cordisContext()).harness;\n const [repoRoot, inventory] = await Promise.all([dependencies.repositoryRoot(ctx), dependencies.inventory(ctx)]);\n const catalog = await dependencies.buildCatalog({\n repoRoot,\n activeSkillDirectories: state.skillDirectories,\n extensionSources: dependencies.extensionSources(),\n helpSkills: inventory.helpSkills,\n helpDiagnostics: [...inventory.diagnostics],\n });\n\n const result = await dependencies.openOverlay(ctx, { catalog, repoRoot });\n if (!result) return;\n ctx.ui.setEditorText(skillInvocation(result.skill.name));\n ctx.ui.notify(`Loaded ${result.skill.name} into the prompt. Press enter to run it.`, INFO);\n },\n });\n}\n"],"mappings":"kIA4BA,SAAgB,EAAsB,EAAkB,EAA+C,CACrG,EAAG,gBAAgBA,EAAAA,eAAgB,CACjC,YAAa,0EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,OAAS,EAAI,OAAS,MAAO,CACpC,EAAI,GAAG,OAAO,oCAAqC,OAAK,EACxD,MACF,CAEA,IAAM,GAAA,EAAQC,EAAAA,yBAAAA,CAAyB,EAAa,cAAc,CAAC,CAAC,CAAC,QAC/D,CAAC,EAAU,GAAa,MAAM,QAAQ,IAAI,CAAC,EAAa,eAAe,CAAG,EAAG,EAAa,UAAU,CAAG,CAAC,CAAC,EACzG,EAAU,MAAM,EAAa,aAAa,CAC9C,WACA,uBAAwB,EAAM,iBAC9B,iBAAkB,EAAa,iBAAiB,EAChD,WAAY,EAAU,WACtB,gBAAiB,CAAC,GAAG,EAAU,WAAW,CAC5C,CAAC,EAEK,EAAS,MAAM,EAAa,YAAY,EAAK,CAAE,UAAS,UAAS,CAAC,EACnE,IACL,EAAI,GAAG,cAAcC,EAAAA,gBAAgB,EAAO,MAAM,IAAI,CAAC,EACvD,EAAI,GAAG,OAAO,UAAU,EAAO,MAAM,KAAK,0CAA2C,MAAI,EAC3F,CACF,CAAC,CACH"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{SKILLS_COMMAND as e}from"../types/skills.mjs";import{skillInvocation as t}from"../services/skillText.mjs";import{requireDoomConfigContext as n}from"@agimon-ai/doompi-config/piContext";function r(r,i){r.registerCommand(e,{description:`Browse the skills this session loaded, and what other domains would add`,handler:async(e,r)=>{if(!r.hasUI||r.mode!==`tui`){r.ui.notify(`/skills requires interactive mode`,`error`);return}let a=n(i.cordisContext()).harness,[o,s]=await Promise.all([i.repositoryRoot(r),i.inventory(r)]),c=await i.buildCatalog({repoRoot:o,activeSkillDirectories:a.skillDirectories,extensionSources:i.extensionSources(),helpSkills:s.helpSkills,helpDiagnostics:[...s.diagnostics]}),l=await i.openOverlay(r,{catalog:c,repoRoot:o});l&&(r.ui.setEditorText(t(l.skill.name)),r.ui.notify(`Loaded ${l.skill.name} into the prompt. Press enter to run it.`,`info`))}})}export{r as registerSkillsCommand};
|
|
2
|
+
//# sourceMappingURL=skillsCommand.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsCommand.mjs","names":[],"sources":["../../src/commands/skillsCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { skillInvocation } from '../services/skillText.ts';\nimport type { SkillCatalog, SkillCatalogOptions } from '../adapters/skillCatalog.ts';\nimport type { SkillsOverlayResult } from '../tui/skillsOverlay.ts';\nimport { SKILLS_COMMAND } from '../types/skills.ts';\n\nconst INFO = 'info';\nconst ERROR = 'error';\n\nexport interface SkillsCommandDependencies {\n readonly cordisContext: () => Context;\n /** Skill directories every loaded extension has contributed this session. */\n readonly extensionSources: () => readonly { source: string; directories: readonly string[] }[];\n /** Merged view of normal, deferred and package Help skills. */\n readonly inventory: (ctx: ExtensionContext) => Promise<{\n readonly helpSkills: SkillCatalogOptions['helpSkills'];\n readonly diagnostics: readonly string[];\n }>;\n readonly repositoryRoot: (ctx: ExtensionContext) => Promise<string>;\n readonly buildCatalog: (request: SkillCatalogOptions) => Promise<SkillCatalog>;\n readonly openOverlay: (\n ctx: ExtensionContext,\n options: { catalog: SkillCatalog; repoRoot: string },\n ) => Promise<SkillsOverlayResult | undefined>;\n}\n\nexport function registerSkillsCommand(pi: ExtensionAPI, dependencies: SkillsCommandDependencies): void {\n pi.registerCommand(SKILLS_COMMAND, {\n description: 'Browse the skills this session loaded, and what other domains would add',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI || ctx.mode !== 'tui') {\n ctx.ui.notify('/skills requires interactive mode', ERROR);\n return;\n }\n\n const state = requireDoomConfigContext(dependencies.cordisContext()).harness;\n const [repoRoot, inventory] = await Promise.all([dependencies.repositoryRoot(ctx), dependencies.inventory(ctx)]);\n const catalog = await dependencies.buildCatalog({\n repoRoot,\n activeSkillDirectories: state.skillDirectories,\n extensionSources: dependencies.extensionSources(),\n helpSkills: inventory.helpSkills,\n helpDiagnostics: [...inventory.diagnostics],\n });\n\n const result = await dependencies.openOverlay(ctx, { catalog, repoRoot });\n if (!result) return;\n ctx.ui.setEditorText(skillInvocation(result.skill.name));\n ctx.ui.notify(`Loaded ${result.skill.name} into the prompt. Press enter to run it.`, INFO);\n },\n });\n}\n"],"mappings":"+LA4BA,SAAgB,EAAsB,EAAkB,EAA+C,CACrG,EAAG,gBAAgB,EAAgB,CACjC,YAAa,0EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,OAAS,EAAI,OAAS,MAAO,CACpC,EAAI,GAAG,OAAO,oCAAqC,OAAK,EACxD,MACF,CAEA,IAAM,EAAQ,EAAyB,EAAa,cAAc,CAAC,CAAC,CAAC,QAC/D,CAAC,EAAU,GAAa,MAAM,QAAQ,IAAI,CAAC,EAAa,eAAe,CAAG,EAAG,EAAa,UAAU,CAAG,CAAC,CAAC,EACzG,EAAU,MAAM,EAAa,aAAa,CAC9C,WACA,uBAAwB,EAAM,iBAC9B,iBAAkB,EAAa,iBAAiB,EAChD,WAAY,EAAU,WACtB,gBAAiB,CAAC,GAAG,EAAU,WAAW,CAC5C,CAAC,EAEK,EAAS,MAAM,EAAa,YAAY,EAAK,CAAE,UAAS,UAAS,CAAC,EACnE,IACL,EAAI,GAAG,cAAc,EAAgB,EAAO,MAAM,IAAI,CAAC,EACvD,EAAI,GAAG,OAAO,UAAU,EAAO,MAAM,KAAK,0CAA2C,MAAI,EAC3F,CACF,CAAC,CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../adapters/pi/extension.cjs");exports.default=e.default,exports.skillsExtension=e.default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"../adapters/pi/extension.mjs";export{e as default,e as skillsExtension};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types/skills.cjs"),t=require("./services/skillText.cjs"),n=require("./adapters/pi/extension.cjs"),r=require("./tui/skillsOverlay.cjs");exports.LEADER_SOURCE=e.LEADER_SOURCE,exports.SKILLS_COMMAND=e.SKILLS_COMMAND,exports.SKILLS_LEADER_CONTRIBUTION=e.SKILLS_LEADER_CONTRIBUTION,exports.SKILL_COMMAND_PREFIX=e.SKILL_COMMAND_PREFIX,exports.SKILL_INVOCATION_PREFIX=e.SKILL_INVOCATION_PREFIX,exports.openSkillsOverlay=r.openSkillsOverlay,exports.skillInvocation=t.skillInvocation,exports.skillsExtension=n.default;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import skillsExtension from "./adapters/pi/extension.cjs";
|
|
2
|
+
import { skillInvocation } from "./services/skillText.cjs";
|
|
3
|
+
import { SkillsOverlayResult, openSkillsOverlay } from "./tui/skillsOverlay.cjs";
|
|
4
|
+
import { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from "./types/skills.cjs";
|
|
5
|
+
export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, type SkillsOverlayResult, openSkillsOverlay, skillInvocation, skillsExtension };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import skillsExtension from "./adapters/pi/extension.mjs";
|
|
2
|
+
import { skillInvocation } from "./services/skillText.mjs";
|
|
3
|
+
import { SkillsOverlayResult, openSkillsOverlay } from "./tui/skillsOverlay.mjs";
|
|
4
|
+
import { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from "./types/skills.mjs";
|
|
5
|
+
export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, type SkillsOverlayResult, openSkillsOverlay, skillInvocation, skillsExtension };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LEADER_SOURCE as e,SKILLS_COMMAND as t,SKILLS_LEADER_CONTRIBUTION as n,SKILL_COMMAND_PREFIX as r,SKILL_INVOCATION_PREFIX as i}from"./types/skills.mjs";import{skillInvocation as a}from"./services/skillText.mjs";import o from"./adapters/pi/extension.mjs";import{openSkillsOverlay as s}from"./tui/skillsOverlay.mjs";export{e as LEADER_SOURCE,t as SKILLS_COMMAND,n as SKILLS_LEADER_CONTRIBUTION,r as SKILL_COMMAND_PREFIX,i as SKILL_INVOCATION_PREFIX,s as openSkillsOverlay,a as skillInvocation,o as skillsExtension};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillText.cjs","names":["SKILL_INVOCATION_PREFIX"],"sources":["../../src/services/skillText.ts"],"sourcesContent":["import { SKILL_INVOCATION_PREFIX } from '../types/skills.ts';\n\n/**\n * Prefills the draft rather than submitting it.\n *\n * `/skill:<name>` is expanded by Pi inside `prompt()`, which only the editor's\n * own submission path reaches: `sendUserMessage` sets `expandPromptTemplates`\n * false and would deliver the literal text. Leaving the command in the editor\n * keeps the expansion and costs one keystroke.\n */\nexport function skillInvocation(name: string): string {\n return `${SKILL_INVOCATION_PREFIX}${name} `;\n}\n"],"mappings":"uCAUA,SAAgB,EAAgB,EAAsB,CACpD,MAAO,GAAGA,EAAAA,0BAA0B,EAAK,EAC3C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/services/skillText.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Prefills the draft rather than submitting it.
|
|
4
|
+
*
|
|
5
|
+
* `/skill:<name>` is expanded by Pi inside `prompt()`, which only the editor's
|
|
6
|
+
* own submission path reaches: `sendUserMessage` sets `expandPromptTemplates`
|
|
7
|
+
* false and would deliver the literal text. Leaving the command in the editor
|
|
8
|
+
* keeps the expansion and costs one keystroke.
|
|
9
|
+
*/
|
|
10
|
+
declare function skillInvocation(name: string): string;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { skillInvocation };
|
|
13
|
+
//# sourceMappingURL=skillText.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillText.d.cts","names":[],"sources":["../../src/services/skillText.ts"],"mappings":";;;;;;;;;iBAUgB,gBAAgB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/services/skillText.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Prefills the draft rather than submitting it.
|
|
4
|
+
*
|
|
5
|
+
* `/skill:<name>` is expanded by Pi inside `prompt()`, which only the editor's
|
|
6
|
+
* own submission path reaches: `sendUserMessage` sets `expandPromptTemplates`
|
|
7
|
+
* false and would deliver the literal text. Leaving the command in the editor
|
|
8
|
+
* keeps the expansion and costs one keystroke.
|
|
9
|
+
*/
|
|
10
|
+
declare function skillInvocation(name: string): string;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { skillInvocation };
|
|
13
|
+
//# sourceMappingURL=skillText.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillText.d.mts","names":[],"sources":["../../src/services/skillText.ts"],"mappings":";;;;;;;;;iBAUgB,gBAAgB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillText.mjs","names":[],"sources":["../../src/services/skillText.ts"],"sourcesContent":["import { SKILL_INVOCATION_PREFIX } from '../types/skills.ts';\n\n/**\n * Prefills the draft rather than submitting it.\n *\n * `/skill:<name>` is expanded by Pi inside `prompt()`, which only the editor's\n * own submission path reaches: `sendUserMessage` sets `expandPromptTemplates`\n * false and would deliver the literal text. Leaving the command in the editor\n * keeps the expansion and costs one keystroke.\n */\nexport function skillInvocation(name: string): string {\n return `${SKILL_INVOCATION_PREFIX}${name} `;\n}\n"],"mappings":"8DAUA,SAAgB,EAAgB,EAAsB,CACpD,MAAO,GAAG,IAA0B,EAAK,EAC3C"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("@agimon-ai/doompi-ui/components/doomOverlay"),i=require("@agimon-ai/doompi-ui/rendering"),a=require("@earendil-works/pi-tui");const o=`CATALOG`,s=[[`↑↓`,`select`],[`enter`,`use skill`],[`tab`,`detail pane`],[`/`,`filter`],[`esc`,`close`]],c=[[`pgup/pgdn`,`scroll`],[`tab`,`tree`],[`esc`,`close`]],l=[[`type`,`to filter`],[`enter`,`keep`],[`esc`,`clear`]];function u(e,t){return(0,i.fitStyledLine)(e,t,`…`)}function d(e,t){if(t<=0)return[];let n=[],r=``;for(let i of e.split(/\s+/).filter(Boolean))r.length===0?r=i:(0,a.visibleWidth)(r)+1+(0,a.visibleWidth)(i)<=t?r+=` ${i}`:(n.push(r),r=i);return r.length>0&&n.push(r),n}function f(e){let t=e.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);return(t?t[1]??``:e).replace(/^\s+/,``)}function p(e,t){let n=t.toLowerCase();return e.name.toLowerCase().includes(n)||e.description.toLowerCase().includes(n)}var m=class extends r.DoomOverlay{options;done;readFile;bodyCache=new Map;selected=0;focus=`tree`;filtering=!1;query=``;bodyOffset=0;constructor(e,n,r,i){super(e,n),this.options=r,this.done=i,this.readFile=r.readFile??(e=>t.default.readFileSync(e,`utf8`))}rows(){let e=this.query.trim(),t=[];for(let n of this.options.catalog.groups){let r=e?n.owners.map(t=>({...t,skills:t.skills.filter(t=>p(t,e))})).filter(e=>e.skills.length>0):n.owners;if(r.length===0)continue;let i=r.reduce((e,t)=>e+t.skills.length,0);t.push({kind:`group`,depth:0,label:n.label,tag:String(i)});for(let e of r){t.push({kind:`owner`,depth:1,label:e.owner,tag:String(e.skills.length)});for(let n of e.skills)t.push({kind:`skill`,depth:2,label:n.name,tag:``,skill:n})}}return t}selectable(e){let t=[];for(let[n,r]of e.entries())r.skill&&t.push(n);return t}currentRow(){let e=this.rows(),t=this.selectable(e);if(t.length!==0)return e[t[Math.min(this.selected,t.length-1)]]}move(e){let t=this.selectable(this.rows()).length;t!==0&&(this.selected=Math.max(0,Math.min(t-1,this.selected+e)),this.bodyOffset=0,this.invalidate())}bodyLines(e,t){let n=this.bodyCache.get(e.filePath);if(!n){let t;try{t=f(this.readFile(e.filePath))}catch{t=`Could not read SKILL.md.`}n=t.split(/\r?\n/),this.bodyCache.set(e.filePath,n)}return n.flatMap(e=>e.trim()===``?[``]:d(e,t))}handleInput(e){if(this.filtering){this.handleFilterInput(e);return}if((0,a.matchesKey)(e,`escape`)){if(this.query){this.query=``,this.selected=0,this.invalidate();return}this.done(void 0);return}if(e===` `||(0,a.matchesKey)(e,`tab`)){this.focus=this.focus===`tree`?`body`:`tree`,this.invalidate();return}if((0,a.matchesKey)(e,`pageUp`)){this.bodyOffset=Math.max(0,this.bodyOffset-10),this.invalidate();return}if((0,a.matchesKey)(e,`pageDown`)){this.bodyOffset+=10,this.invalidate();return}if(this.focus!==`body`){if(e===`/`){this.filtering=!0,this.invalidate();return}if((0,a.matchesKey)(e,`up`)||(0,a.matchesKey)(e,`k`)){this.move(-1);return}if((0,a.matchesKey)(e,`down`)||(0,a.matchesKey)(e,`j`)){this.move(1);return}if(e===`\r`||e===`
|
|
2
|
+
`||(0,a.matchesKey)(e,`enter`)){let e=this.currentRow()?.skill;e&&this.done({kind:`invoke`,skill:e})}}}handleFilterInput(e){if((0,a.matchesKey)(e,`escape`)){this.filtering=!1,this.query=``,this.selected=0,this.invalidate();return}if(e===`\r`||e===`
|
|
3
|
+
`){this.filtering=!1,this.invalidate();return}e===``?this.query=this.query.slice(0,-1):e.length===1&&e>=` `&&(this.query+=e),this.selected=0,this.invalidate()}getChrome(){let{skillCount:e,promptTokens:t,bodyTokens:n}=this.options.catalog,a=this.options.catalog.groups.filter(e=>e.owners.length>0).length,o=this.filtering?l:this.focus===`body`?c:s;return{title:`SKILLS`,accent:r.DOOM_OVERLAY_ACCENT,breadcrumb:`SPC › e / extension › s / skills`,headerRight:`${e} skills · ${a} sources · ${(0,i.formatTokens)(t)} always-on · ${(0,i.formatTokens)(n)} full`,footer:this.hintLine(o)}}badge(e,t,n=`text`){return this.theme.bg(t,this.theme.fg(n,` ${e} `))}hintLine(e){return e.map(([e,t])=>`${this.badge(e,`selectedBg`)}${this.theme.fg(`dim`,` ${t}`)}`).join(this.theme.fg(`dim`,` `))}renderBody(e,t){if(e<60)return this.renderStacked(e,t);let n=Math.max(20,Math.floor((e-1)*.3333333333333333)),r=Math.max(1,e-n-1),i=Math.max(1,n-1),o=Math.max(1,r-1),s=this.treeLines(i,t),c=this.detailLines(o,t),l=this.theme.fg(`borderMuted`,`│`);return Array.from({length:t},(t,n)=>{let r=`${u(s[n]??``,i)} ${l} ${u(c[n]??``,o)}`;return(0,a.truncateToWidth)(r,e,`…`)})}renderStacked(e,t){let n=Math.max(1,Math.floor((t-1)/2)),r=Math.max(0,t-n-1),i=this.treeLines(e,n),a=this.detailLines(e,r);return[...i.slice(0,n),...Array.from({length:Math.max(0,n-i.length)},()=>``),this.theme.fg(`borderMuted`,`─`.repeat(e)),...a.slice(0,r)]}treeLines(e,t){let n=this.filtering||this.query?`${o} /${this.query}`:o,r=[this.theme.bold(this.theme.fg(`accent`,n)),``],i=this.rows();if(i.length===0)return r.push(this.theme.fg(`dim`,`No skills discovered.`)),r;let s=Math.max(1,t-r.length),c=this.selectable(i),l=c[Math.min(this.selected,c.length-1)]??-1,d=Math.max(0,Math.min(l-s+1,Math.max(0,i.length-s)));for(let[t,n]of i.slice(d,d+s).entries()){let i=d+t===l,o=i?this.theme.fg(`accent`,`›`):` `,s=` `.repeat(n.depth*2),c=n.kind===`group`?`accent`:n.kind===`owner`?`dim`:`text`,f=this.theme.fg(c,`${s}${n.label}`);(n.kind===`group`||i)&&(f=this.theme.bold(f));let p=n.tag?(0,a.truncateToWidth)(n.tag,Math.floor(e*.4),`…`):``,m=p?(0,a.visibleWidth)(p)+1:0,h=u(`${o} ${f}`,Math.max(1,e-m)),g=p?`${h} ${this.theme.fg(`dim`,p)}`:h;r.push(i?this.theme.bg(`selectedBg`,u(g,e)):g)}return r}field(e,t,n,r){return d(t,Math.max(1,r-13)).map((t,r)=>`${this.theme.fg(`dim`,u(r===0?e:``,13))}${this.theme.fg(n,t)}`)}detailLines(e,t){let r=this.currentRow()?.skill;if(!r)return[``,``,this.theme.fg(`dim`,`Select a skill to read it.`)];let i=n.default.relative(this.options.repoRoot,r.filePath)||r.filePath,a=[this.theme.bold(this.theme.fg(`accent`,r.name)),``,...this.field(`description`,r.description,`text`,e),...this.field(`source`,`${r.group} › ${r.owner}`,`success`,e),...this.field(`path`,i,`text`,e),...this.field(`invocable`,r.modelInvocable?`model · /skill:${r.name}`:`/skill:${r.name} only`,`success`,e)],o=this.bodyLines(r,e),s=Math.max(0,t-a.length-2);this.bodyOffset=Math.min(this.bodyOffset,Math.max(0,o.length-s));let c=o.slice(this.bodyOffset,this.bodyOffset+s),l=o.length>s?`lines ${this.bodyOffset+1}-${this.bodyOffset+c.length} / ${o.length}`:`SKILL.md`,u=this.focus===`body`?this.theme.fg(`accent`,` ◀ focused`):``;a.push(``,`${this.theme.fg(`dim`,l)}${u}`);for(let e of c)a.push(this.theme.fg(`dim`,e));return a}};async function h(e,t){return e.ui.custom((e,n,r,i)=>new m(e,n,t,i),r.DOOM_FULLSCREEN_UI_OPTIONS)}exports.SkillsOverlayComponent=m,exports.openSkillsOverlay=h,exports.skillBody=f;
|
|
4
|
+
//# sourceMappingURL=skillsOverlay.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsOverlay.cjs","names":["fitStyledLine","visibleWidth","DoomOverlay","fs","matchesKey","DOOM_OVERLAY_ACCENT","formatTokens","truncateToWidth","path","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/skillsOverlay.ts"],"sourcesContent":["/**\n * Skills: the `SPC e s` overlay.\n *\n * A tree of the skills this session loaded on the left, the selected skill's\n * frontmatter and body on the right. Everything listed is loaded, so the\n * surface answers \"what do I have, and what does it cost\" rather than doubling\n * as a domain browser.\n *\n * The tree is flat and always open: groups and owners are headings, only skills\n * take the selection. Rendering is pure: the catalogue is resolved once by the\n * caller and passed in, so `render(width)` returns lines with no side effects\n * and the whole surface can be asserted as text without a live terminal.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport { fitStyledLine, formatTokens } from '@agimon-ai/doompi-ui/rendering';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { SkillCatalog, SkillEntry } from '../adapters/skillCatalog.ts';\n\ntype ThemeColor = Parameters<Theme['fg']>[0];\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst MIN_WIDTH = 60;\n/** A third for the tree, two thirds for the skill being read. */\nconst TREE_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 20;\nconst LABEL_COLUMN = 13;\n/** Ceiling on the columns a row's right-hand tag may take from its label. */\nconst TAG_SHARE = 0.4;\nconst INDENT = 2;\nconst ELLIPSIS = '…';\nconst SELECTION_MARKER = '›';\nconst RULE = '─';\nconst KEY_TAB = '\\t';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_BACKSPACE = '\\x7f';\nconst FILTER_KEY = '/';\nconst BODY_PAGE = 10;\n\nconst TITLE = 'SKILLS';\nconst BREADCRUMB = 'SPC › e / extension › s / skills';\nconst HEADING = 'CATALOG';\nconst EMPTY_TREE = 'No skills discovered.';\nconst NO_SELECTION = 'Select a skill to read it.';\nconst UNREADABLE = 'Could not read SKILL.md.';\n\nconst TREE_HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'select'],\n ['enter', 'use skill'],\n ['tab', 'detail pane'],\n ['/', 'filter'],\n ['esc', 'close'],\n];\nconst BODY_HINTS: readonly (readonly [string, string])[] = [\n ['pgup/pgdn', 'scroll'],\n ['tab', 'tree'],\n ['esc', 'close'],\n];\nconst FILTER_HINTS: readonly (readonly [string, string])[] = [\n ['type', 'to filter'],\n ['enter', 'keep'],\n ['esc', 'clear'],\n];\n\nexport type SkillsOverlayResult = { kind: 'invoke'; skill: SkillEntry } | undefined;\n\nexport interface SkillsOverlayOptions {\n catalog: SkillCatalog;\n /** Repo root, so paths render relative rather than as absolute noise. */\n repoRoot: string;\n readFile?: (filePath: string) => string;\n}\n\ntype RowKind = 'group' | 'owner' | 'skill';\n\ninterface TreeRow {\n kind: RowKind;\n depth: number;\n label: string;\n tag: string;\n /** Only a skill row carries one; group and owner rows are headings. */\n skill?: SkillEntry;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (visibleWidth(current) + 1 + visibleWidth(word) <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/** Drops YAML frontmatter so the preview starts at the prose, as Pi does. */\nexport function skillBody(content: string): string {\n const match = content.match(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n return (match ? (match[1] ?? '') : content).replace(/^\\s+/, '');\n}\n\nfunction matches(skill: SkillEntry, query: string): boolean {\n const needle = query.toLowerCase();\n return skill.name.toLowerCase().includes(needle) || skill.description.toLowerCase().includes(needle);\n}\n\nexport class SkillsOverlayComponent extends DoomOverlay {\n private readonly options: SkillsOverlayOptions;\n private readonly done: (result: SkillsOverlayResult) => void;\n private readonly readFile: (filePath: string) => string;\n private readonly bodyCache = new Map<string, string[]>();\n\n /** Index into the skill rows, not into every row: headings are not selectable. */\n private selected = 0;\n private focus: 'tree' | 'body' = 'tree';\n private filtering = false;\n private query = '';\n private bodyOffset = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n options: SkillsOverlayOptions,\n done: (result: SkillsOverlayResult) => void,\n ) {\n super(tui, theme);\n this.options = options;\n this.done = done;\n this.readFile = options.readFile ?? ((filePath) => fs.readFileSync(filePath, 'utf8'));\n }\n\n /** Every row, honouring the active filter. The tree does not fold. */\n private rows(): TreeRow[] {\n const query = this.query.trim();\n const rows: TreeRow[] = [];\n for (const group of this.options.catalog.groups) {\n const owners = query\n ? group.owners\n .map((owner) => ({ ...owner, skills: owner.skills.filter((skill) => matches(skill, query)) }))\n .filter((owner) => owner.skills.length > 0)\n : group.owners;\n if (owners.length === 0) continue;\n const count = owners.reduce((total, owner) => total + owner.skills.length, 0);\n rows.push({ kind: 'group', depth: 0, label: group.label, tag: String(count) });\n for (const owner of owners) {\n rows.push({ kind: 'owner', depth: 1, label: owner.owner, tag: String(owner.skills.length) });\n for (const skill of owner.skills) {\n rows.push({ kind: 'skill', depth: 2, label: skill.name, tag: '', skill });\n }\n }\n }\n return rows;\n }\n\n /**\n * Where the skill rows sit in `rows()`.\n *\n * The selection indexes this list rather than `rows()`, which is what keeps\n * headings unselectable without the key handler having to hunt past them, and\n * what puts the opening selection on a skill rather than on the first group.\n */\n private selectable(rows: TreeRow[]): number[] {\n const indexes: number[] = [];\n for (const [index, row] of rows.entries()) if (row.skill) indexes.push(index);\n return indexes;\n }\n\n private currentRow(): TreeRow | undefined {\n const rows = this.rows();\n const indexes = this.selectable(rows);\n if (indexes.length === 0) return undefined;\n return rows[indexes[Math.min(this.selected, indexes.length - 1)] as number];\n }\n\n private move(delta: number): void {\n const count = this.selectable(this.rows()).length;\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.bodyOffset = 0;\n this.invalidate();\n }\n\n private bodyLines(skill: SkillEntry, width: number): string[] {\n let raw = this.bodyCache.get(skill.filePath);\n if (!raw) {\n let source: string;\n try {\n source = skillBody(this.readFile(skill.filePath));\n } catch {\n source = UNREADABLE;\n }\n raw = source.split(/\\r?\\n/);\n this.bodyCache.set(skill.filePath, raw);\n }\n // Wrapped at render width rather than at read time, so a resize reflows the\n // same cached file instead of re-reading it.\n return raw.flatMap((line) => (line.trim() === '' ? [''] : wrap(line, width)));\n }\n\n handleInput(data: string): void {\n if (this.filtering) {\n this.handleFilterInput(data);\n return;\n }\n\n if (matchesKey(data, 'escape')) {\n // The filter clears before the overlay closes: escaping straight out of a\n // narrowed tree loses the search with no way to see what it hid.\n if (this.query) {\n this.query = '';\n this.selected = 0;\n this.invalidate();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB || matchesKey(data, 'tab')) {\n this.focus = this.focus === 'tree' ? 'body' : 'tree';\n this.invalidate();\n return;\n }\n if (matchesKey(data, 'pageUp')) {\n this.bodyOffset = Math.max(0, this.bodyOffset - BODY_PAGE);\n this.invalidate();\n return;\n }\n if (matchesKey(data, 'pageDown')) {\n this.bodyOffset += BODY_PAGE;\n this.invalidate();\n return;\n }\n if (this.focus === 'body') return;\n\n if (data === FILTER_KEY) {\n this.filtering = true;\n this.invalidate();\n return;\n }\n if (matchesKey(data, 'up') || matchesKey(data, 'k')) {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || matchesKey(data, 'j')) {\n this.move(1);\n return;\n }\n\n if (data === KEY_ENTER || data === KEY_NEWLINE || matchesKey(data, 'enter')) {\n const skill = this.currentRow()?.skill;\n if (skill) this.done({ kind: 'invoke', skill });\n }\n }\n\n private handleFilterInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.filtering = false;\n this.query = '';\n this.selected = 0;\n this.invalidate();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.filtering = false;\n this.invalidate();\n return;\n }\n if (data === KEY_BACKSPACE) this.query = this.query.slice(0, -1);\n // Control sequences are dropped rather than appended: an arrow key would\n // otherwise land in the query as a raw escape sequence.\n else if (data.length === 1 && data >= ' ') this.query += data;\n this.selected = 0;\n this.invalidate();\n }\n\n protected getChrome(): DoomOverlayChrome {\n const { skillCount, promptTokens, bodyTokens } = this.options.catalog;\n const sources = this.options.catalog.groups.filter((group) => group.owners.length > 0).length;\n const hints = this.filtering ? FILTER_HINTS : this.focus === 'body' ? BODY_HINTS : TREE_HINTS;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n // Two token figures, because they answer different questions: what the\n // catalogue costs on every request, and what reading all of it would cost.\n headerRight: `${skillCount} skills · ${sources} sources · ${formatTokens(promptTokens)} always-on · ${formatTokens(bodyTokens)} full`,\n footer: this.hintLine(hints),\n };\n }\n\n private badge(text: string, background: ThemeBg, colour: ThemeColor = 'text'): string {\n return this.theme.bg(background, this.theme.fg(colour, ` ${text} `));\n }\n\n /** Footer hints as capped keys: `[ enter ] use skill`, per the mockup. */\n private hintLine(hints: readonly (readonly [string, string])[]): string {\n return hints\n .map(([key, label]) => `${this.badge(key, 'selectedBg')}${this.theme.fg('dim', ` ${label}`)}`)\n .join(this.theme.fg('dim', ' '));\n }\n\n protected renderBody(width: number, height: number): string[] {\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const treeWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * TREE_PANE_RATIO));\n const detailWidth = Math.max(1, width - treeWidth - 1);\n const treeContent = Math.max(1, treeWidth - PANE_GUTTER);\n const detailContent = Math.max(1, detailWidth - PANE_GUTTER);\n\n const left = this.treeLines(treeContent, height);\n const right = this.detailLines(detailContent, height);\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', treeContent)} ${divider} ${fitRow(right[index] ?? '', detailContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n const top = this.treeLines(width, topHeight);\n const bottom = this.detailLines(width, bottomHeight);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', RULE.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n\n private treeLines(width: number, height: number): string[] {\n const heading = this.filtering || this.query ? `${HEADING} ${FILTER_KEY}${this.query}` : HEADING;\n const lines = [this.theme.bold(this.theme.fg('accent', heading)), ''];\n const rows = this.rows();\n if (rows.length === 0) {\n lines.push(this.theme.fg('dim', EMPTY_TREE));\n return lines;\n }\n\n const budget = Math.max(1, height - lines.length);\n const indexes = this.selectable(rows);\n // No skill rows means the filter matched nothing, so nothing is highlighted.\n const selected = indexes[Math.min(this.selected, indexes.length - 1)] ?? -1;\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, rows.length - budget)));\n for (const [offset, row] of rows.slice(start, start + budget).entries()) {\n const isSelected = start + offset === selected;\n const marker = isSelected ? this.theme.fg('accent', SELECTION_MARKER) : ' ';\n const indent = ' '.repeat(row.depth * INDENT);\n // Three tiers rather than two: the group heading leads, the owner beneath\n // it recedes to a caption, and the skills it holds carry the reading\n // weight because they are the only rows that do anything.\n const colour: ThemeColor = row.kind === 'group' ? 'accent' : row.kind === 'owner' ? 'dim' : 'text';\n let label = this.theme.fg(colour, `${indent}${row.label}`);\n if (row.kind === 'group' || isSelected) label = this.theme.bold(label);\n // The tag is capped before it claims columns: an owner in many domains\n // carries a long `+a,b,c` hint that would otherwise leave the name with\n // no room at all and render the row as a bare count.\n const tag = row.tag ? truncateToWidth(row.tag, Math.floor(width * TAG_SHARE), ELLIPSIS) : '';\n const tagWidth = tag ? visibleWidth(tag) + 1 : 0;\n const body = fitRow(`${marker} ${label}`, Math.max(1, width - tagWidth));\n const line = tag ? `${body} ${this.theme.fg('dim', tag)}` : body;\n lines.push(isSelected ? this.theme.bg('selectedBg', fitRow(line, width)) : line);\n }\n return lines;\n }\n\n private field(label: string, value: string, colour: ThemeColor, width: number): string[] {\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n return wrap(value, valueWidth).map(\n (line, index) =>\n `${this.theme.fg('dim', fitRow(index === 0 ? label : '', LABEL_COLUMN))}${this.theme.fg(colour, line)}`,\n );\n }\n\n private detailLines(width: number, height: number): string[] {\n const skill = this.currentRow()?.skill;\n if (!skill) return ['', '', this.theme.fg('dim', NO_SELECTION)];\n\n const relative = path.relative(this.options.repoRoot, skill.filePath) || skill.filePath;\n const lines = [\n this.theme.bold(this.theme.fg('accent', skill.name)),\n '',\n ...this.field('description', skill.description, 'text', width),\n ...this.field('source', `${skill.group} › ${skill.owner}`, 'success', width),\n ...this.field('path', relative, 'text', width),\n ...this.field(\n 'invocable',\n skill.modelInvocable ? `model · /skill:${skill.name}` : `/skill:${skill.name} only`,\n 'success',\n width,\n ),\n ];\n\n const body = this.bodyLines(skill, width);\n const remaining = Math.max(0, height - lines.length - 2);\n // Clamped here rather than in the key handler, because how far the body can\n // scroll depends on the width it has just wrapped to.\n this.bodyOffset = Math.min(this.bodyOffset, Math.max(0, body.length - remaining));\n const shown = body.slice(this.bodyOffset, this.bodyOffset + remaining);\n const position =\n body.length > remaining\n ? `lines ${this.bodyOffset + 1}-${this.bodyOffset + shown.length} / ${body.length}`\n : 'SKILL.md';\n const focus = this.focus === 'body' ? this.theme.fg('accent', ' ◀ focused') : '';\n\n lines.push('', `${this.theme.fg('dim', position)}${focus}`);\n for (const line of shown) lines.push(this.theme.fg('dim', line));\n return lines;\n }\n}\n\nexport async function openSkillsOverlay(\n ctx: ExtensionContext,\n options: SkillsOverlayOptions,\n): Promise<SkillsOverlayResult> {\n return ctx.ui.custom<SkillsOverlayResult>(\n (tui, theme, _keybindings, done) => new SkillsOverlayComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SkillCatalog, SkillEntry } from "../adapters/skillCatalog.cjs";
|
|
2
|
+
import { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import "@agimon-ai/doompi-ui/components/doomOverlay";
|
|
4
|
+
//#region src/tui/skillsOverlay.d.ts
|
|
5
|
+
type SkillsOverlayResult = {
|
|
6
|
+
kind: 'invoke';
|
|
7
|
+
skill: SkillEntry;
|
|
8
|
+
} | undefined;
|
|
9
|
+
interface SkillsOverlayOptions {
|
|
10
|
+
catalog: SkillCatalog;
|
|
11
|
+
/** Repo root, so paths render relative rather than as absolute noise. */
|
|
12
|
+
repoRoot: string;
|
|
13
|
+
readFile?: (filePath: string) => string;
|
|
14
|
+
}
|
|
15
|
+
declare function openSkillsOverlay(ctx: ExtensionContext, options: SkillsOverlayOptions): Promise<SkillsOverlayResult>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { SkillsOverlayOptions, SkillsOverlayResult, openSkillsOverlay };
|
|
18
|
+
//# sourceMappingURL=skillsOverlay.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsOverlay.d.cts","names":[],"sources":["../../src/tui/skillsOverlay.ts"],"mappings":";;;;KA4EY;EAAwB;EAAgB,OAAO;;UAE1C;EACf,SAAS;;EAET;EACA,YAAY;;iBA+VQ,kBACpB,KAAK,kBACL,SAAS,uBACR,QAAQ"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SkillCatalog, SkillEntry } from "../adapters/skillCatalog.mjs";
|
|
2
|
+
import { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { DoomOverlay } from "@agimon-ai/doompi-ui/components/doomOverlay";
|
|
4
|
+
//#region src/tui/skillsOverlay.d.ts
|
|
5
|
+
type SkillsOverlayResult = {
|
|
6
|
+
kind: 'invoke';
|
|
7
|
+
skill: SkillEntry;
|
|
8
|
+
} | undefined;
|
|
9
|
+
interface SkillsOverlayOptions {
|
|
10
|
+
catalog: SkillCatalog;
|
|
11
|
+
/** Repo root, so paths render relative rather than as absolute noise. */
|
|
12
|
+
repoRoot: string;
|
|
13
|
+
readFile?: (filePath: string) => string;
|
|
14
|
+
}
|
|
15
|
+
declare function openSkillsOverlay(ctx: ExtensionContext, options: SkillsOverlayOptions): Promise<SkillsOverlayResult>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { SkillsOverlayOptions, SkillsOverlayResult, openSkillsOverlay };
|
|
18
|
+
//# sourceMappingURL=skillsOverlay.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsOverlay.d.mts","names":[],"sources":["../../src/tui/skillsOverlay.ts"],"mappings":";;;;KA4EY;EAAwB;EAAgB,OAAO;;UAE1C;EACf,SAAS;;EAET;EACA,YAAY;;iBA+VQ,kBACpB,KAAK,kBACL,SAAS,uBACR,QAAQ"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{DOOM_FULLSCREEN_UI_OPTIONS as n,DOOM_OVERLAY_ACCENT as r,DoomOverlay as i}from"@agimon-ai/doompi-ui/components/doomOverlay";import{fitStyledLine as a,formatTokens as o}from"@agimon-ai/doompi-ui/rendering";import{matchesKey as s,truncateToWidth as c,visibleWidth as l}from"@earendil-works/pi-tui";const u=`CATALOG`,d=[[`↑↓`,`select`],[`enter`,`use skill`],[`tab`,`detail pane`],[`/`,`filter`],[`esc`,`close`]],f=[[`pgup/pgdn`,`scroll`],[`tab`,`tree`],[`esc`,`close`]],p=[[`type`,`to filter`],[`enter`,`keep`],[`esc`,`clear`]];function m(e,t){return a(e,t,`…`)}function h(e,t){if(t<=0)return[];let n=[],r=``;for(let i of e.split(/\s+/).filter(Boolean))r.length===0?r=i:l(r)+1+l(i)<=t?r+=` ${i}`:(n.push(r),r=i);return r.length>0&&n.push(r),n}function g(e){let t=e.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);return(t?t[1]??``:e).replace(/^\s+/,``)}function _(e,t){let n=t.toLowerCase();return e.name.toLowerCase().includes(n)||e.description.toLowerCase().includes(n)}var v=class extends i{options;done;readFile;bodyCache=new Map;selected=0;focus=`tree`;filtering=!1;query=``;bodyOffset=0;constructor(t,n,r,i){super(t,n),this.options=r,this.done=i,this.readFile=r.readFile??(t=>e.readFileSync(t,`utf8`))}rows(){let e=this.query.trim(),t=[];for(let n of this.options.catalog.groups){let r=e?n.owners.map(t=>({...t,skills:t.skills.filter(t=>_(t,e))})).filter(e=>e.skills.length>0):n.owners;if(r.length===0)continue;let i=r.reduce((e,t)=>e+t.skills.length,0);t.push({kind:`group`,depth:0,label:n.label,tag:String(i)});for(let e of r){t.push({kind:`owner`,depth:1,label:e.owner,tag:String(e.skills.length)});for(let n of e.skills)t.push({kind:`skill`,depth:2,label:n.name,tag:``,skill:n})}}return t}selectable(e){let t=[];for(let[n,r]of e.entries())r.skill&&t.push(n);return t}currentRow(){let e=this.rows(),t=this.selectable(e);if(t.length!==0)return e[t[Math.min(this.selected,t.length-1)]]}move(e){let t=this.selectable(this.rows()).length;t!==0&&(this.selected=Math.max(0,Math.min(t-1,this.selected+e)),this.bodyOffset=0,this.invalidate())}bodyLines(e,t){let n=this.bodyCache.get(e.filePath);if(!n){let t;try{t=g(this.readFile(e.filePath))}catch{t=`Could not read SKILL.md.`}n=t.split(/\r?\n/),this.bodyCache.set(e.filePath,n)}return n.flatMap(e=>e.trim()===``?[``]:h(e,t))}handleInput(e){if(this.filtering){this.handleFilterInput(e);return}if(s(e,`escape`)){if(this.query){this.query=``,this.selected=0,this.invalidate();return}this.done(void 0);return}if(e===` `||s(e,`tab`)){this.focus=this.focus===`tree`?`body`:`tree`,this.invalidate();return}if(s(e,`pageUp`)){this.bodyOffset=Math.max(0,this.bodyOffset-10),this.invalidate();return}if(s(e,`pageDown`)){this.bodyOffset+=10,this.invalidate();return}if(this.focus!==`body`){if(e===`/`){this.filtering=!0,this.invalidate();return}if(s(e,`up`)||s(e,`k`)){this.move(-1);return}if(s(e,`down`)||s(e,`j`)){this.move(1);return}if(e===`\r`||e===`
|
|
2
|
+
`||s(e,`enter`)){let e=this.currentRow()?.skill;e&&this.done({kind:`invoke`,skill:e})}}}handleFilterInput(e){if(s(e,`escape`)){this.filtering=!1,this.query=``,this.selected=0,this.invalidate();return}if(e===`\r`||e===`
|
|
3
|
+
`){this.filtering=!1,this.invalidate();return}e===``?this.query=this.query.slice(0,-1):e.length===1&&e>=` `&&(this.query+=e),this.selected=0,this.invalidate()}getChrome(){let{skillCount:e,promptTokens:t,bodyTokens:n}=this.options.catalog,i=this.options.catalog.groups.filter(e=>e.owners.length>0).length,a=this.filtering?p:this.focus===`body`?f:d;return{title:`SKILLS`,accent:r,breadcrumb:`SPC › e / extension › s / skills`,headerRight:`${e} skills · ${i} sources · ${o(t)} always-on · ${o(n)} full`,footer:this.hintLine(a)}}badge(e,t,n=`text`){return this.theme.bg(t,this.theme.fg(n,` ${e} `))}hintLine(e){return e.map(([e,t])=>`${this.badge(e,`selectedBg`)}${this.theme.fg(`dim`,` ${t}`)}`).join(this.theme.fg(`dim`,` `))}renderBody(e,t){if(e<60)return this.renderStacked(e,t);let n=Math.max(20,Math.floor((e-1)*.3333333333333333)),r=Math.max(1,e-n-1),i=Math.max(1,n-1),a=Math.max(1,r-1),o=this.treeLines(i,t),s=this.detailLines(a,t),l=this.theme.fg(`borderMuted`,`│`);return Array.from({length:t},(t,n)=>{let r=`${m(o[n]??``,i)} ${l} ${m(s[n]??``,a)}`;return c(r,e,`…`)})}renderStacked(e,t){let n=Math.max(1,Math.floor((t-1)/2)),r=Math.max(0,t-n-1),i=this.treeLines(e,n),a=this.detailLines(e,r);return[...i.slice(0,n),...Array.from({length:Math.max(0,n-i.length)},()=>``),this.theme.fg(`borderMuted`,`─`.repeat(e)),...a.slice(0,r)]}treeLines(e,t){let n=this.filtering||this.query?`${u} /${this.query}`:u,r=[this.theme.bold(this.theme.fg(`accent`,n)),``],i=this.rows();if(i.length===0)return r.push(this.theme.fg(`dim`,`No skills discovered.`)),r;let a=Math.max(1,t-r.length),o=this.selectable(i),s=o[Math.min(this.selected,o.length-1)]??-1,d=Math.max(0,Math.min(s-a+1,Math.max(0,i.length-a)));for(let[t,n]of i.slice(d,d+a).entries()){let i=d+t===s,a=i?this.theme.fg(`accent`,`›`):` `,o=` `.repeat(n.depth*2),u=n.kind===`group`?`accent`:n.kind===`owner`?`dim`:`text`,f=this.theme.fg(u,`${o}${n.label}`);(n.kind===`group`||i)&&(f=this.theme.bold(f));let p=n.tag?c(n.tag,Math.floor(e*.4),`…`):``,h=p?l(p)+1:0,g=m(`${a} ${f}`,Math.max(1,e-h)),_=p?`${g} ${this.theme.fg(`dim`,p)}`:g;r.push(i?this.theme.bg(`selectedBg`,m(_,e)):_)}return r}field(e,t,n,r){return h(t,Math.max(1,r-13)).map((t,r)=>`${this.theme.fg(`dim`,m(r===0?e:``,13))}${this.theme.fg(n,t)}`)}detailLines(e,n){let r=this.currentRow()?.skill;if(!r)return[``,``,this.theme.fg(`dim`,`Select a skill to read it.`)];let i=t.relative(this.options.repoRoot,r.filePath)||r.filePath,a=[this.theme.bold(this.theme.fg(`accent`,r.name)),``,...this.field(`description`,r.description,`text`,e),...this.field(`source`,`${r.group} › ${r.owner}`,`success`,e),...this.field(`path`,i,`text`,e),...this.field(`invocable`,r.modelInvocable?`model · /skill:${r.name}`:`/skill:${r.name} only`,`success`,e)],o=this.bodyLines(r,e),s=Math.max(0,n-a.length-2);this.bodyOffset=Math.min(this.bodyOffset,Math.max(0,o.length-s));let c=o.slice(this.bodyOffset,this.bodyOffset+s),l=o.length>s?`lines ${this.bodyOffset+1}-${this.bodyOffset+c.length} / ${o.length}`:`SKILL.md`,u=this.focus===`body`?this.theme.fg(`accent`,` ◀ focused`):``;a.push(``,`${this.theme.fg(`dim`,l)}${u}`);for(let e of c)a.push(this.theme.fg(`dim`,e));return a}};async function y(e,t){return e.ui.custom((e,n,r,i)=>new v(e,n,t,i),n)}export{v as SkillsOverlayComponent,y as openSkillsOverlay,g as skillBody};
|
|
4
|
+
//# sourceMappingURL=skillsOverlay.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsOverlay.mjs","names":[],"sources":["../../src/tui/skillsOverlay.ts"],"sourcesContent":["/**\n * Skills: the `SPC e s` overlay.\n *\n * A tree of the skills this session loaded on the left, the selected skill's\n * frontmatter and body on the right. Everything listed is loaded, so the\n * surface answers \"what do I have, and what does it cost\" rather than doubling\n * as a domain browser.\n *\n * The tree is flat and always open: groups and owners are headings, only skills\n * take the selection. Rendering is pure: the catalogue is resolved once by the\n * caller and passed in, so `render(width)` returns lines with no side effects\n * and the whole surface can be asserted as text without a live terminal.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport { fitStyledLine, formatTokens } from '@agimon-ai/doompi-ui/rendering';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { SkillCatalog, SkillEntry } from '../adapters/skillCatalog.ts';\n\ntype ThemeColor = Parameters<Theme['fg']>[0];\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst MIN_WIDTH = 60;\n/** A third for the tree, two thirds for the skill being read. */\nconst TREE_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 20;\nconst LABEL_COLUMN = 13;\n/** Ceiling on the columns a row's right-hand tag may take from its label. */\nconst TAG_SHARE = 0.4;\nconst INDENT = 2;\nconst ELLIPSIS = '…';\nconst SELECTION_MARKER = '›';\nconst RULE = '─';\nconst KEY_TAB = '\\t';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_BACKSPACE = '\\x7f';\nconst FILTER_KEY = '/';\nconst BODY_PAGE = 10;\n\nconst TITLE = 'SKILLS';\nconst BREADCRUMB = 'SPC › e / extension › s / skills';\nconst HEADING = 'CATALOG';\nconst EMPTY_TREE = 'No skills discovered.';\nconst NO_SELECTION = 'Select a skill to read it.';\nconst UNREADABLE = 'Could not read SKILL.md.';\n\nconst TREE_HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'select'],\n ['enter', 'use skill'],\n ['tab', 'detail pane'],\n ['/', 'filter'],\n ['esc', 'close'],\n];\nconst BODY_HINTS: readonly (readonly [string, string])[] = [\n ['pgup/pgdn', 'scroll'],\n ['tab', 'tree'],\n ['esc', 'close'],\n];\nconst FILTER_HINTS: readonly (readonly [string, string])[] = [\n ['type', 'to filter'],\n ['enter', 'keep'],\n ['esc', 'clear'],\n];\n\nexport type SkillsOverlayResult = { kind: 'invoke'; skill: SkillEntry } | undefined;\n\nexport interface SkillsOverlayOptions {\n catalog: SkillCatalog;\n /** Repo root, so paths render relative rather than as absolute noise. */\n repoRoot: string;\n readFile?: (filePath: string) => string;\n}\n\ntype RowKind = 'group' | 'owner' | 'skill';\n\ninterface TreeRow {\n kind: RowKind;\n depth: number;\n label: string;\n tag: string;\n /** Only a skill row carries one; group and owner rows are headings. */\n skill?: SkillEntry;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (visibleWidth(current) + 1 + visibleWidth(word) <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/** Drops YAML frontmatter so the preview starts at the prose, as Pi does. */\nexport function skillBody(content: string): string {\n const match = content.match(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n return (match ? (match[1] ?? '') : content).replace(/^\\s+/, '');\n}\n\nfunction matches(skill: SkillEntry, query: string): boolean {\n const needle = query.toLowerCase();\n return skill.name.toLowerCase().includes(needle) || skill.description.toLowerCase().includes(needle);\n}\n\nexport class SkillsOverlayComponent extends DoomOverlay {\n private readonly options: SkillsOverlayOptions;\n private readonly done: (result: SkillsOverlayResult) => void;\n private readonly readFile: (filePath: string) => string;\n private readonly bodyCache = new Map<string, string[]>();\n\n /** Index into the skill rows, not into every row: headings are not selectable. */\n private selected = 0;\n private focus: 'tree' | 'body' = 'tree';\n private filtering = false;\n private query = '';\n private bodyOffset = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n options: SkillsOverlayOptions,\n done: (result: SkillsOverlayResult) => void,\n ) {\n super(tui, theme);\n this.options = options;\n this.done = done;\n this.readFile = options.readFile ?? ((filePath) => fs.readFileSync(filePath, 'utf8'));\n }\n\n /** Every row, honouring the active filter. The tree does not fold. */\n private rows(): TreeRow[] {\n const query = this.query.trim();\n const rows: TreeRow[] = [];\n for (const group of this.options.catalog.groups) {\n const owners = query\n ? group.owners\n .map((owner) => ({ ...owner, skills: owner.skills.filter((skill) => matches(skill, query)) }))\n .filter((owner) => owner.skills.length > 0)\n : group.owners;\n if (owners.length === 0) continue;\n const count = owners.reduce((total, owner) => total + owner.skills.length, 0);\n rows.push({ kind: 'group', depth: 0, label: group.label, tag: String(count) });\n for (const owner of owners) {\n rows.push({ kind: 'owner', depth: 1, label: owner.owner, tag: String(owner.skills.length) });\n for (const skill of owner.skills) {\n rows.push({ kind: 'skill', depth: 2, label: skill.name, tag: '', skill });\n }\n }\n }\n return rows;\n }\n\n /**\n * Where the skill rows sit in `rows()`.\n *\n * The selection indexes this list rather than `rows()`, which is what keeps\n * headings unselectable without the key handler having to hunt past them, and\n * what puts the opening selection on a skill rather than on the first group.\n */\n private selectable(rows: TreeRow[]): number[] {\n const indexes: number[] = [];\n for (const [index, row] of rows.entries()) if (row.skill) indexes.push(index);\n return indexes;\n }\n\n private currentRow(): TreeRow | undefined {\n const rows = this.rows();\n const indexes = this.selectable(rows);\n if (indexes.length === 0) return undefined;\n return rows[indexes[Math.min(this.selected, indexes.length - 1)] as number];\n }\n\n private move(delta: number): void {\n const count = this.selectable(this.rows()).length;\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.bodyOffset = 0;\n this.invalidate();\n }\n\n private bodyLines(skill: SkillEntry, width: number): string[] {\n let raw = this.bodyCache.get(skill.filePath);\n if (!raw) {\n let source: string;\n try {\n source = skillBody(this.readFile(skill.filePath));\n } catch {\n source = UNREADABLE;\n }\n raw = source.split(/\\r?\\n/);\n this.bodyCache.set(skill.filePath, raw);\n }\n // Wrapped at render width rather than at read time, so a resize reflows the\n // same cached file instead of re-reading it.\n return raw.flatMap((line) => (line.trim() === '' ? [''] : wrap(line, width)));\n }\n\n handleInput(data: string): void {\n if (this.filtering) {\n this.handleFilterInput(data);\n return;\n }\n\n if (matchesKey(data, 'escape')) {\n // The filter clears before the overlay closes: escaping straight out of a\n // narrowed tree loses the search with no way to see what it hid.\n if (this.query) {\n this.query = '';\n this.selected = 0;\n this.invalidate();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB || matchesKey(data, 'tab')) {\n this.focus = this.focus === 'tree' ? 'body' : 'tree';\n this.invalidate();\n return;\n }\n if (matchesKey(data, 'pageUp')) {\n this.bodyOffset = Math.max(0, this.bodyOffset - BODY_PAGE);\n this.invalidate();\n return;\n }\n if (matchesKey(data, 'pageDown')) {\n this.bodyOffset += BODY_PAGE;\n this.invalidate();\n return;\n }\n if (this.focus === 'body') return;\n\n if (data === FILTER_KEY) {\n this.filtering = true;\n this.invalidate();\n return;\n }\n if (matchesKey(data, 'up') || matchesKey(data, 'k')) {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || matchesKey(data, 'j')) {\n this.move(1);\n return;\n }\n\n if (data === KEY_ENTER || data === KEY_NEWLINE || matchesKey(data, 'enter')) {\n const skill = this.currentRow()?.skill;\n if (skill) this.done({ kind: 'invoke', skill });\n }\n }\n\n private handleFilterInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.filtering = false;\n this.query = '';\n this.selected = 0;\n this.invalidate();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.filtering = false;\n this.invalidate();\n return;\n }\n if (data === KEY_BACKSPACE) this.query = this.query.slice(0, -1);\n // Control sequences are dropped rather than appended: an arrow key would\n // otherwise land in the query as a raw escape sequence.\n else if (data.length === 1 && data >= ' ') this.query += data;\n this.selected = 0;\n this.invalidate();\n }\n\n protected getChrome(): DoomOverlayChrome {\n const { skillCount, promptTokens, bodyTokens } = this.options.catalog;\n const sources = this.options.catalog.groups.filter((group) => group.owners.length > 0).length;\n const hints = this.filtering ? FILTER_HINTS : this.focus === 'body' ? BODY_HINTS : TREE_HINTS;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n // Two token figures, because they answer different questions: what the\n // catalogue costs on every request, and what reading all of it would cost.\n headerRight: `${skillCount} skills · ${sources} sources · ${formatTokens(promptTokens)} always-on · ${formatTokens(bodyTokens)} full`,\n footer: this.hintLine(hints),\n };\n }\n\n private badge(text: string, background: ThemeBg, colour: ThemeColor = 'text'): string {\n return this.theme.bg(background, this.theme.fg(colour, ` ${text} `));\n }\n\n /** Footer hints as capped keys: `[ enter ] use skill`, per the mockup. */\n private hintLine(hints: readonly (readonly [string, string])[]): string {\n return hints\n .map(([key, label]) => `${this.badge(key, 'selectedBg')}${this.theme.fg('dim', ` ${label}`)}`)\n .join(this.theme.fg('dim', ' '));\n }\n\n protected renderBody(width: number, height: number): string[] {\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const treeWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * TREE_PANE_RATIO));\n const detailWidth = Math.max(1, width - treeWidth - 1);\n const treeContent = Math.max(1, treeWidth - PANE_GUTTER);\n const detailContent = Math.max(1, detailWidth - PANE_GUTTER);\n\n const left = this.treeLines(treeContent, height);\n const right = this.detailLines(detailContent, height);\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', treeContent)} ${divider} ${fitRow(right[index] ?? '', detailContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n const top = this.treeLines(width, topHeight);\n const bottom = this.detailLines(width, bottomHeight);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', RULE.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n\n private treeLines(width: number, height: number): string[] {\n const heading = this.filtering || this.query ? `${HEADING} ${FILTER_KEY}${this.query}` : HEADING;\n const lines = [this.theme.bold(this.theme.fg('accent', heading)), ''];\n const rows = this.rows();\n if (rows.length === 0) {\n lines.push(this.theme.fg('dim', EMPTY_TREE));\n return lines;\n }\n\n const budget = Math.max(1, height - lines.length);\n const indexes = this.selectable(rows);\n // No skill rows means the filter matched nothing, so nothing is highlighted.\n const selected = indexes[Math.min(this.selected, indexes.length - 1)] ?? -1;\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, rows.length - budget)));\n for (const [offset, row] of rows.slice(start, start + budget).entries()) {\n const isSelected = start + offset === selected;\n const marker = isSelected ? this.theme.fg('accent', SELECTION_MARKER) : ' ';\n const indent = ' '.repeat(row.depth * INDENT);\n // Three tiers rather than two: the group heading leads, the owner beneath\n // it recedes to a caption, and the skills it holds carry the reading\n // weight because they are the only rows that do anything.\n const colour: ThemeColor = row.kind === 'group' ? 'accent' : row.kind === 'owner' ? 'dim' : 'text';\n let label = this.theme.fg(colour, `${indent}${row.label}`);\n if (row.kind === 'group' || isSelected) label = this.theme.bold(label);\n // The tag is capped before it claims columns: an owner in many domains\n // carries a long `+a,b,c` hint that would otherwise leave the name with\n // no room at all and render the row as a bare count.\n const tag = row.tag ? truncateToWidth(row.tag, Math.floor(width * TAG_SHARE), ELLIPSIS) : '';\n const tagWidth = tag ? visibleWidth(tag) + 1 : 0;\n const body = fitRow(`${marker} ${label}`, Math.max(1, width - tagWidth));\n const line = tag ? `${body} ${this.theme.fg('dim', tag)}` : body;\n lines.push(isSelected ? this.theme.bg('selectedBg', fitRow(line, width)) : line);\n }\n return lines;\n }\n\n private field(label: string, value: string, colour: ThemeColor, width: number): string[] {\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n return wrap(value, valueWidth).map(\n (line, index) =>\n `${this.theme.fg('dim', fitRow(index === 0 ? label : '', LABEL_COLUMN))}${this.theme.fg(colour, line)}`,\n );\n }\n\n private detailLines(width: number, height: number): string[] {\n const skill = this.currentRow()?.skill;\n if (!skill) return ['', '', this.theme.fg('dim', NO_SELECTION)];\n\n const relative = path.relative(this.options.repoRoot, skill.filePath) || skill.filePath;\n const lines = [\n this.theme.bold(this.theme.fg('accent', skill.name)),\n '',\n ...this.field('description', skill.description, 'text', width),\n ...this.field('source', `${skill.group} › ${skill.owner}`, 'success', width),\n ...this.field('path', relative, 'text', width),\n ...this.field(\n 'invocable',\n skill.modelInvocable ? `model · /skill:${skill.name}` : `/skill:${skill.name} only`,\n 'success',\n width,\n ),\n ];\n\n const body = this.bodyLines(skill, width);\n const remaining = Math.max(0, height - lines.length - 2);\n // Clamped here rather than in the key handler, because how far the body can\n // scroll depends on the width it has just wrapped to.\n this.bodyOffset = Math.min(this.bodyOffset, Math.max(0, body.length - remaining));\n const shown = body.slice(this.bodyOffset, this.bodyOffset + remaining);\n const position =\n body.length > remaining\n ? `lines ${this.bodyOffset + 1}-${this.bodyOffset + shown.length} / ${body.length}`\n : 'SKILL.md';\n const focus = this.focus === 'body' ? this.theme.fg('accent', ' ◀ focused') : '';\n\n lines.push('', `${this.theme.fg('dim', position)}${focus}`);\n for (const line of shown) lines.push(this.theme.fg('dim', line));\n return lines;\n }\n}\n\nexport async function openSkillsOverlay(\n ctx: ExtensionContext,\n options: SkillsOverlayOptions,\n): Promise<SkillsOverlayResult> {\n return ctx.ui.custom<SkillsOverlayResult>(\n (tui, theme, _keybindings, done) => new SkillsOverlayComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=`skills`,t=`@agimon-ai/doompi-skill`,n={source:t,bindings:[{id:`skills.browse`,path:[{key:`e`,label:`extension`,order:50},{key:`s`,label:`skills`,detail:`browse catalog`}],command:{name:e}}]};exports.LEADER_SOURCE=t,exports.SKILLS_COMMAND=e,exports.SKILLS_LEADER_CONTRIBUTION=n,exports.SKILL_COMMAND_PREFIX=`skill:`,exports.SKILL_INVOCATION_PREFIX=`/skill:`;
|
|
2
|
+
//# sourceMappingURL=skills.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.cjs","names":[],"sources":["../../src/types/skills.ts"],"sourcesContent":["import type { LeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\nexport const LEADER_SOURCE = '@agimon-ai/doompi-skill';\n\n/**\n * The `SPC e s` binding.\n *\n * The label and order repeat the core extension group exactly; the registry\n * rejects a contribution whose shared prefix disagrees.\n */\nexport const SKILLS_LEADER_CONTRIBUTION = {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'skills.browse',\n path: [\n { key: 'e', label: 'extension', order: 50 },\n { key: 's', label: 'skills', detail: 'browse catalog' },\n ],\n command: { name: SKILLS_COMMAND },\n },\n ],\n} satisfies LeaderContribution;\n"],"mappings":"AAEA,MAAa,EAAiB,SAGjB,EAAgB,0BAQhB,EAA6B,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,gBACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,YAAa,MAAO,EAAG,EAC1C,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,gBAAiB,CACxD,EACA,QAAS,CAAE,KAAM,CAAe,CAClC,CACF,CACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/types/skills.d.ts
|
|
2
|
+
declare const SKILLS_COMMAND = "skills";
|
|
3
|
+
declare const SKILL_INVOCATION_PREFIX = "/skill:";
|
|
4
|
+
declare const SKILL_COMMAND_PREFIX = "skill:";
|
|
5
|
+
declare const LEADER_SOURCE = "@agimon-ai/doompi-skill";
|
|
6
|
+
/**
|
|
7
|
+
* The `SPC e s` binding.
|
|
8
|
+
*
|
|
9
|
+
* The label and order repeat the core extension group exactly; the registry
|
|
10
|
+
* rejects a contribution whose shared prefix disagrees.
|
|
11
|
+
*/
|
|
12
|
+
declare const SKILLS_LEADER_CONTRIBUTION: {
|
|
13
|
+
source: string;
|
|
14
|
+
bindings: {
|
|
15
|
+
id: string;
|
|
16
|
+
path: [{
|
|
17
|
+
key: string;
|
|
18
|
+
label: string;
|
|
19
|
+
order: number;
|
|
20
|
+
}, {
|
|
21
|
+
key: string;
|
|
22
|
+
label: string;
|
|
23
|
+
detail: string;
|
|
24
|
+
}];
|
|
25
|
+
command: {
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX };
|
|
32
|
+
//# sourceMappingURL=skills.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.cts","names":[],"sources":["../../src/types/skills.ts"],"mappings":";cAEa;cACA;cACA;cACA;;;;;;;cAQA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/types/skills.d.ts
|
|
2
|
+
declare const SKILLS_COMMAND = "skills";
|
|
3
|
+
declare const SKILL_INVOCATION_PREFIX = "/skill:";
|
|
4
|
+
declare const SKILL_COMMAND_PREFIX = "skill:";
|
|
5
|
+
declare const LEADER_SOURCE = "@agimon-ai/doompi-skill";
|
|
6
|
+
/**
|
|
7
|
+
* The `SPC e s` binding.
|
|
8
|
+
*
|
|
9
|
+
* The label and order repeat the core extension group exactly; the registry
|
|
10
|
+
* rejects a contribution whose shared prefix disagrees.
|
|
11
|
+
*/
|
|
12
|
+
declare const SKILLS_LEADER_CONTRIBUTION: {
|
|
13
|
+
source: string;
|
|
14
|
+
bindings: {
|
|
15
|
+
id: string;
|
|
16
|
+
path: [{
|
|
17
|
+
key: string;
|
|
18
|
+
label: string;
|
|
19
|
+
order: number;
|
|
20
|
+
}, {
|
|
21
|
+
key: string;
|
|
22
|
+
label: string;
|
|
23
|
+
detail: string;
|
|
24
|
+
}];
|
|
25
|
+
command: {
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX };
|
|
32
|
+
//# sourceMappingURL=skills.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.mts","names":[],"sources":["../../src/types/skills.ts"],"mappings":";cAEa;cACA;cACA;cACA;;;;;;;cAQA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=`skills`,t=`/skill:`,n=`skill:`,r=`@agimon-ai/doompi-skill`,i={source:r,bindings:[{id:`skills.browse`,path:[{key:`e`,label:`extension`,order:50},{key:`s`,label:`skills`,detail:`browse catalog`}],command:{name:e}}]};export{r as LEADER_SOURCE,e as SKILLS_COMMAND,i as SKILLS_LEADER_CONTRIBUTION,n as SKILL_COMMAND_PREFIX,t as SKILL_INVOCATION_PREFIX};
|
|
2
|
+
//# sourceMappingURL=skills.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.mjs","names":[],"sources":["../../src/types/skills.ts"],"sourcesContent":["import type { LeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\nexport const LEADER_SOURCE = '@agimon-ai/doompi-skill';\n\n/**\n * The `SPC e s` binding.\n *\n * The label and order repeat the core extension group exactly; the registry\n * rejects a contribution whose shared prefix disagrees.\n */\nexport const SKILLS_LEADER_CONTRIBUTION = {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'skills.browse',\n path: [\n { key: 'e', label: 'extension', order: 50 },\n { key: 's', label: 'skills', detail: 'browse catalog' },\n ],\n command: { name: SKILLS_COMMAND },\n },\n ],\n} satisfies LeaderContribution;\n"],"mappings":"AAEA,MAAa,EAAiB,SACjB,EAA0B,UAC1B,EAAuB,SACvB,EAAgB,0BAQhB,EAA6B,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,gBACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,YAAa,MAAO,EAAG,EAC1C,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,gBAAiB,CACxD,EACA,QAAS,CAAE,KAAM,CAAe,CAClC,CACF,CACF"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Doom Pi Skill
|
|
2
|
+
|
|
3
|
+
The session skill catalogue, deferred skill discovery, and the `SPC e s` browser.
|
|
4
|
+
|
|
5
|
+
## How to author
|
|
6
|
+
|
|
7
|
+
- [Author DoomPi skills](./src/prompts/doompi-author-skill/SKILL.md): Create, distribute, and register runtime skills or activation-gated Help prompts.
|
|
8
|
+
|
|
9
|
+
## How to use
|
|
10
|
+
|
|
11
|
+
- [Use DoomPi skills](./src/prompts/doompi-use-skill/SKILL.md): Browse the catalog, invoke deferred skills, and diagnose missing or shadowed skills.
|
|
12
|
+
|
|
13
|
+
## Package reference
|
|
14
|
+
|
|
15
|
+
- [Package README](./README.md): The `/skills` command, deferred discovery, and extension-contributed skill directories.
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agimon-ai/doompi-skill",
|
|
3
|
+
"version": "0.0.1-alpha.10",
|
|
4
|
+
"description": "Session skill catalogue, deferred skill discovery, and the skill browser for DoomPi.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent-skills",
|
|
7
|
+
"ai",
|
|
8
|
+
"coding-agent",
|
|
9
|
+
"developer-tools",
|
|
10
|
+
"doompi",
|
|
11
|
+
"pi-coding-agent",
|
|
12
|
+
"pi-extension",
|
|
13
|
+
"pi-package",
|
|
14
|
+
"skill-catalog",
|
|
15
|
+
"skill-discovery"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://agimon.ai",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Vuong Ngo",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"llms.txt",
|
|
23
|
+
"README.md",
|
|
24
|
+
"src/prompts",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"package.json"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"jsnext:main": "./dist/index.mjs",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
|
+
"import": "./dist/index.mjs",
|
|
36
|
+
"require": "./dist/index.cjs"
|
|
37
|
+
},
|
|
38
|
+
"./catalog": {
|
|
39
|
+
"types": "./dist/catalog.d.mts",
|
|
40
|
+
"import": "./dist/catalog.mjs",
|
|
41
|
+
"require": "./dist/catalog.cjs"
|
|
42
|
+
},
|
|
43
|
+
"./extensions/pi": {
|
|
44
|
+
"types": "./dist/extensions/pi.d.mts",
|
|
45
|
+
"import": "./dist/extensions/pi.mjs",
|
|
46
|
+
"require": "./dist/extensions/pi.cjs"
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
55
|
+
"gpt-tokenizer": "4.0.0",
|
|
56
|
+
"@agimon-ai/doompi-config": "0.0.1-alpha.32",
|
|
57
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.32",
|
|
58
|
+
"@agimon-ai/doompi-domain": "0.0.1-alpha.10",
|
|
59
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.32",
|
|
60
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.32"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
64
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
65
|
+
"@earendil-works/pi-tui": "0.84.2",
|
|
66
|
+
"@types/node": "26.2.0",
|
|
67
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
68
|
+
"tsdown": "0.22.14",
|
|
69
|
+
"typescript": "7.0.2",
|
|
70
|
+
"vitest": "4.1.11",
|
|
71
|
+
"@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.30"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
75
|
+
"@earendil-works/pi-tui": "0.84.2"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@earendil-works/pi-coding-agent": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@earendil-works/pi-tui": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=22.19.0"
|
|
87
|
+
},
|
|
88
|
+
"pi": {
|
|
89
|
+
"extensions": [
|
|
90
|
+
"./dist/extensions/pi.mjs"
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"scripts": {
|
|
94
|
+
"build": "tsdown",
|
|
95
|
+
"test": "vitest --run",
|
|
96
|
+
"typecheck": "tsc --noEmit",
|
|
97
|
+
"lint": "oxlint . && oxfmt . --check",
|
|
98
|
+
"fixcode": "oxlint . --fix && oxfmt ."
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doompi-author-skill
|
|
3
|
+
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Author DoomPi skills
|
|
7
|
+
|
|
8
|
+
Create one focused skill per directory. Name the directory with lowercase letters, digits, and hyphens, and give it a required `SKILL.md`:
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
---
|
|
12
|
+
name: repository-review
|
|
13
|
+
description: Review repository changes when the user asks for implementation-risk and regression analysis.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Repository review
|
|
17
|
+
|
|
18
|
+
Put the essential workflow and non-obvious constraints here.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The frontmatter `name` must match the directory. Make the description specific enough for automatic routing. Keep the entrypoint concise, and put substantial conditional details in linked `references/`. Add `scripts/`, `assets/`, or `agents/` only when the skill actually uses them.
|
|
22
|
+
|
|
23
|
+
## Choose the distribution path
|
|
24
|
+
|
|
25
|
+
- For a normal Pi skill that should be discoverable whenever its owning extension is active, ship the skill under a package-owned runtime directory such as `skills/<name>/SKILL.md`. Register that directory through `DOOM_SKILL_SOURCES_SERVICE` inside an owning `cordis.inject(...)`, and return the contribution disposer.
|
|
26
|
+
- For guidance that should appear only while DoomPi Help is active, put it at `src/prompts/<name>/SKILL.md`, publish `src/prompts`, link it from the package's H1-led `llms.txt`, and register a Help descriptor through `DOOM_HELP_SERVICE`.
|
|
27
|
+
- For repository content selected by domains, place the skill in the plugin or shared skill source configured by `domains.yaml`; do not hard-code that directory into the Skill package.
|
|
28
|
+
|
|
29
|
+
Help descriptors carry only `name` and `description`. The Help service resolves the owning package from the contribution's exact package `source` and `moduleUrl`, then wraps its `llms.txt`. Keep every index link inside the published package.
|
|
30
|
+
|
|
31
|
+
## Verification
|
|
32
|
+
|
|
33
|
+
Validate the skill frontmatter, ensure every relative reference resolves, and test the packed package rather than relying on checkout-only files. For Cordis contributions, cover late provider arrival, provider replacement, withdrawal, and package shutdown. Use `/skills` or `SPC e s` in an interactive session to confirm the expected owner and invocation name.
|