opencode-plugin-preload-skills 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -167,6 +167,9 @@ var PreloadSkillsPlugin = async (ctx) => {
167
167
  if (loadedSkills.length === 0 || !formattedContent) {
168
168
  return;
169
169
  }
170
+ if (!input.sessionID) {
171
+ return;
172
+ }
170
173
  if (injectedSessions.has(input.sessionID)) {
171
174
  log("debug", "Skills already injected for session", {
172
175
  sessionID: input.sessionID
@@ -174,11 +177,14 @@ var PreloadSkillsPlugin = async (ctx) => {
174
177
  return;
175
178
  }
176
179
  injectedSessions.add(input.sessionID);
177
- const syntheticPart = {
178
- type: "text",
179
- text: formattedContent
180
- };
181
- output.parts.unshift(syntheticPart);
180
+ const firstTextPart = output.parts.find((p) => p.type === "text");
181
+ if (firstTextPart && "text" in firstTextPart) {
182
+ firstTextPart.text = `${formattedContent}
183
+
184
+ ---
185
+
186
+ ${firstTextPart.text}`;
187
+ }
182
188
  log("info", "Injected preloaded skills into session", {
183
189
  sessionID: input.sessionID,
184
190
  skillCount: loadedSkills.length,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/skill-loader.ts","../src/index.ts"],"names":["join","homedir","existsSync","readFileSync"],"mappings":"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","file":"index.cjs","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { ParsedSkill } from \"./types.js\"\n\nconst SKILL_FILENAME = \"SKILL.md\"\n\nconst SKILL_SEARCH_PATHS = [\n (dir: string) => join(dir, \".opencode\", \"skills\"),\n (dir: string) => join(dir, \".claude\", \"skills\"),\n () => join(homedir(), \".config\", \"opencode\", \"skills\"),\n () => join(homedir(), \".claude\", \"skills\"),\n]\n\nfunction findSkillFile(skillName: string, projectDir: string): string | null {\n for (const getPath of SKILL_SEARCH_PATHS) {\n const skillDir = getPath(projectDir)\n const skillPath = join(skillDir, skillName, SKILL_FILENAME)\n\n if (existsSync(skillPath)) {\n return skillPath\n }\n }\n return null\n}\n\nfunction parseFrontmatter(content: string): { name?: string; description?: string } {\n const frontmatterMatch = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/)\n if (!frontmatterMatch?.[1]) {\n return {}\n }\n\n const frontmatter = frontmatterMatch[1]\n const result: { name?: string; description?: string } = {}\n\n const nameMatch = frontmatter.match(/^name:\\s*(.+)$/m)\n if (nameMatch?.[1]) {\n result.name = nameMatch[1].trim()\n }\n\n const descMatch = frontmatter.match(/^description:\\s*(.+)$/m)\n if (descMatch?.[1]) {\n result.description = descMatch[1].trim()\n }\n\n return result\n}\n\nexport function loadSkill(skillName: string, projectDir: string): ParsedSkill | null {\n const filePath = findSkillFile(skillName, projectDir)\n\n if (!filePath) {\n return null\n }\n\n try {\n const content = readFileSync(filePath, \"utf-8\")\n const { name, description } = parseFrontmatter(content)\n\n return {\n name: name ?? skillName,\n description: description ?? \"\",\n content,\n filePath,\n }\n } catch {\n return null\n }\n}\n\nexport function loadSkills(skillNames: string[], projectDir: string): ParsedSkill[] {\n if (!Array.isArray(skillNames)) {\n return []\n }\n\n const skills: ParsedSkill[] = []\n\n for (const name of skillNames) {\n const skill = loadSkill(name, projectDir)\n if (skill) {\n skills.push(skill)\n }\n }\n\n return skills\n}\n\nexport function formatSkillsForInjection(skills: ParsedSkill[]): string {\n if (!Array.isArray(skills) || skills.length === 0) {\n return \"\"\n }\n\n const parts = skills.map(\n (skill) =>\n `<preloaded-skill name=\"${skill.name}\">\\n${skill.content}\\n</preloaded-skill>`\n )\n\n return `<preloaded-skills>\nThe following skills have been automatically loaded for this session:\n\n${parts.join(\"\\n\\n\")}\n</preloaded-skills>`\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { Plugin, PluginInput } from \"@opencode-ai/plugin\"\nimport type { Event, UserMessage, Part } from \"@opencode-ai/sdk\"\nimport type { PreloadSkillsConfig, ParsedSkill } from \"./types.js\"\nimport { loadSkills, formatSkillsForInjection } from \"./skill-loader.js\"\n\nexport type { PreloadSkillsConfig, ParsedSkill }\nexport { loadSkills, formatSkillsForInjection }\n\nconst CONFIG_FILENAME = \"preload-skills.json\"\n\nconst DEFAULT_CONFIG: PreloadSkillsConfig = {\n skills: [],\n persistAfterCompaction: true,\n debug: false,\n}\n\nfunction findConfigFile(projectDir: string): string | null {\n const locations = [\n join(projectDir, \".opencode\", CONFIG_FILENAME),\n join(projectDir, CONFIG_FILENAME),\n join(homedir(), \".config\", \"opencode\", CONFIG_FILENAME),\n ]\n\n for (const path of locations) {\n if (existsSync(path)) {\n return path\n }\n }\n return null\n}\n\nfunction loadConfigFile(projectDir: string): Partial<PreloadSkillsConfig> {\n const configPath = findConfigFile(projectDir)\n if (!configPath) {\n return {}\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\")\n const parsed = JSON.parse(content) as Record<string, unknown>\n \n return {\n skills: Array.isArray(parsed.skills) ? parsed.skills : [],\n persistAfterCompaction: typeof parsed.persistAfterCompaction === \"boolean\" \n ? parsed.persistAfterCompaction \n : undefined,\n debug: typeof parsed.debug === \"boolean\" ? parsed.debug : undefined,\n }\n } catch {\n return {}\n }\n}\n\nexport const PreloadSkillsPlugin: Plugin = async (ctx: PluginInput) => {\n const injectedSessions = new Set<string>()\n\n const fileConfig = loadConfigFile(ctx.directory)\n const config: PreloadSkillsConfig = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n }\n\n const log = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>\n ) => {\n if (level === \"debug\" && !config.debug) return\n\n ctx.client.app.log({\n body: {\n service: \"preload-skills\",\n level,\n message,\n extra,\n },\n })\n }\n\n let loadedSkills: ParsedSkill[] = []\n let formattedContent = \"\"\n\n if (config.skills.length === 0) {\n log(\"warn\", \"No skills configured for preloading. Create .opencode/preload-skills.json\")\n } else {\n loadedSkills = loadSkills(config.skills, ctx.directory)\n formattedContent = formatSkillsForInjection(loadedSkills)\n\n const loadedNames = loadedSkills.map((s) => s.name)\n const missingNames = config.skills.filter((s) => !loadedNames.includes(s))\n\n log(\"info\", `Loaded ${loadedSkills.length} skills for preloading`, {\n loaded: loadedNames,\n missing: missingNames.length > 0 ? missingNames : undefined,\n })\n\n if (missingNames.length > 0) {\n log(\"warn\", \"Some configured skills were not found\", {\n missing: missingNames,\n })\n }\n }\n\n return {\n\n \"chat.message\": async (\n input: {\n sessionID: string\n agent?: string\n model?: { providerID: string; modelID: string }\n messageID?: string\n variant?: string\n },\n output: { message: UserMessage; parts: Part[] }\n ): Promise<void> => {\n if (loadedSkills.length === 0 || !formattedContent) {\n return\n }\n\n if (injectedSessions.has(input.sessionID)) {\n log(\"debug\", \"Skills already injected for session\", {\n sessionID: input.sessionID,\n })\n return\n }\n\n injectedSessions.add(input.sessionID)\n\n const syntheticPart = {\n type: \"text\",\n text: formattedContent,\n } as Part\n\n output.parts.unshift(syntheticPart)\n\n log(\"info\", \"Injected preloaded skills into session\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n skills: loadedSkills.map((s) => s.name),\n })\n },\n\n \"experimental.session.compacting\": async (\n input: { sessionID: string },\n output: { context: string[]; prompt?: string }\n ): Promise<void> => {\n if (!config.persistAfterCompaction || loadedSkills.length === 0) {\n return\n }\n\n output.context.push(\n `## Preloaded Skills\\n\\nThe following skills were auto-loaded at session start and should persist:\\n\\n${formattedContent}`\n )\n\n injectedSessions.delete(input.sessionID)\n\n log(\"info\", \"Added preloaded skills to compaction context\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n })\n },\n\n event: async ({ event }: { event: Event }): Promise<void> => {\n if (\n event.type === \"session.deleted\" &&\n \"sessionID\" in event.properties\n ) {\n const sessionID = event.properties.sessionID as string\n injectedSessions.delete(sessionID)\n log(\"debug\", \"Cleaned up session tracking\", { sessionID })\n }\n },\n }\n}\n\nexport default PreloadSkillsPlugin\n"]}
1
+ {"version":3,"sources":["../src/skill-loader.ts","../src/index.ts"],"names":["join","homedir","existsSync","readFileSync"],"mappings":";;;;;;;;;AAKA,IAAM,cAAA,GAAiB,UAAA;AAEvB,IAAM,kBAAA,GAAqB;AAAA,EACzB,CAAC,GAAA,KAAgBA,SAAA,CAAK,GAAA,EAAK,aAAa,QAAQ,CAAA;AAAA,EAChD,CAAC,GAAA,KAAgBA,SAAA,CAAK,GAAA,EAAK,WAAW,QAAQ,CAAA;AAAA,EAC9C,MAAMA,SAAA,CAAKC,UAAA,EAAQ,EAAG,SAAA,EAAW,YAAY,QAAQ,CAAA;AAAA,EACrD,MAAMD,SAAA,CAAKC,UAAA,EAAQ,EAAG,WAAW,QAAQ;AAC3C,CAAA;AAEA,SAAS,aAAA,CAAc,WAAmB,UAAA,EAAmC;AAC3E,EAAA,KAAA,MAAW,WAAW,kBAAA,EAAoB;AACxC,IAAA,MAAM,QAAA,GAAW,QAAQ,UAAU,CAAA;AACnC,IAAA,MAAM,SAAA,GAAYD,SAAA,CAAK,QAAA,EAAU,SAAA,EAAW,cAAc,CAAA;AAE1D,IAAA,IAAIE,aAAA,CAAW,SAAS,CAAA,EAAG;AACzB,MAAA,OAAO,SAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,iBAAiB,OAAA,EAA0D;AAClF,EAAA,MAAM,gBAAA,GAAmB,OAAA,CAAQ,KAAA,CAAM,0BAA0B,CAAA;AACjE,EAAA,IAAI,CAAC,gBAAA,GAAmB,CAAC,CAAA,EAAG;AAC1B,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,WAAA,GAAc,iBAAiB,CAAC,CAAA;AACtC,EAAA,MAAM,SAAkD,EAAC;AAEzD,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,KAAA,CAAM,iBAAiB,CAAA;AACrD,EAAA,IAAI,SAAA,GAAY,CAAC,CAAA,EAAG;AAClB,IAAA,MAAA,CAAO,IAAA,GAAO,SAAA,CAAU,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EAClC;AAEA,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,KAAA,CAAM,wBAAwB,CAAA;AAC5D,EAAA,IAAI,SAAA,GAAY,CAAC,CAAA,EAAG;AAClB,IAAA,MAAA,CAAO,WAAA,GAAc,SAAA,CAAU,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EACzC;AAEA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,SAAA,CAAU,WAAmB,UAAA,EAAwC;AACnF,EAAA,MAAM,QAAA,GAAW,aAAA,CAAc,SAAA,EAAW,UAAU,CAAA;AAEpD,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAUC,eAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AAC9C,IAAA,MAAM,EAAE,IAAA,EAAM,WAAA,EAAY,GAAI,iBAAiB,OAAO,CAAA;AAEtD,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,IAAQ,SAAA;AAAA,MACd,aAAa,WAAA,IAAe,EAAA;AAAA,MAC5B,OAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEO,SAAS,UAAA,CAAW,YAAsB,UAAA,EAAmC;AAClF,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAU,CAAA,EAAG;AAC9B,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,SAAwB,EAAC;AAE/B,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC7B,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,IAAA,EAAM,UAAU,CAAA;AACxC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,IACnB;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,yBAAyB,MAAA,EAA+B;AACtE,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,IAAK,MAAA,CAAO,WAAW,CAAA,EAAG;AACjD,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAQ,MAAA,CAAO,GAAA;AAAA,IACnB,CAAC,KAAA,KACC,CAAA,uBAAA,EAA0B,KAAA,CAAM,IAAI,CAAA;AAAA,EAAO,MAAM,OAAO;AAAA,kBAAA;AAAA,GAC5D;AAEA,EAAA,OAAO,CAAA;AAAA;;AAAA,EAGP,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC;AAAA,mBAAA,CAAA;AAEpB;;;AC3FA,IAAM,eAAA,GAAkB,qBAAA;AAExB,IAAM,cAAA,GAAsC;AAAA,EAC1C,QAAQ,EAAC;AAAA,EACT,sBAAA,EAAwB,IAAA;AAAA,EACxB,KAAA,EAAO;AACT,CAAA;AAEA,SAAS,eAAe,UAAA,EAAmC;AACzD,EAAA,MAAM,SAAA,GAAY;AAAA,IAChBH,SAAAA,CAAK,UAAA,EAAY,WAAA,EAAa,eAAe,CAAA;AAAA,IAC7CA,SAAAA,CAAK,YAAY,eAAe,CAAA;AAAA,IAChCA,SAAAA,CAAKC,UAAAA,EAAQ,EAAG,SAAA,EAAW,YAAY,eAAe;AAAA,GACxD;AAEA,EAAA,KAAA,MAAW,QAAQ,SAAA,EAAW;AAC5B,IAAA,IAAIC,aAAAA,CAAW,IAAI,CAAA,EAAG;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,eAAe,UAAA,EAAkD;AACxE,EAAA,MAAM,UAAA,GAAa,eAAe,UAAU,CAAA;AAC5C,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAUC,eAAAA,CAAa,UAAA,EAAY,OAAO,CAAA;AAChD,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAEjC,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA,GAAI,MAAA,CAAO,SAAS,EAAC;AAAA,MACxD,wBAAwB,OAAO,MAAA,CAAO,sBAAA,KAA2B,SAAA,GAC7D,OAAO,sBAAA,GACP,KAAA,CAAA;AAAA,MACJ,OAAO,OAAO,MAAA,CAAO,KAAA,KAAU,SAAA,GAAY,OAAO,KAAA,GAAQ,KAAA;AAAA,KAC5D;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AAEO,IAAM,mBAAA,GAA8B,OAAO,GAAA,KAAqB;AACrE,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAY;AAEzC,EAAA,MAAM,UAAA,GAAa,cAAA,CAAe,GAAA,CAAI,SAAS,CAAA;AAC/C,EAAA,MAAM,MAAA,GAA8B;AAAA,IAClC,GAAG,cAAA;AAAA,IACH,GAAG;AAAA,GACL;AAEA,EAAA,MAAM,GAAA,GAAM,CACV,KAAA,EACA,OAAA,EACA,KAAA,KACG;AACH,IAAA,IAAI,KAAA,KAAU,OAAA,IAAW,CAAC,MAAA,CAAO,KAAA,EAAO;AAExC,IAAA,GAAA,CAAI,MAAA,CAAO,IAAI,GAAA,CAAI;AAAA,MACjB,IAAA,EAAM;AAAA,QACJ,OAAA,EAAS,gBAAA;AAAA,QACT,KAAA;AAAA,QACA,OAAA;AAAA,QACA;AAAA;AACF,KACD,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,IAAI,eAA8B,EAAC;AACnC,EAAA,IAAI,gBAAA,GAAmB,EAAA;AAEvB,EAAA,IAAI,MAAA,CAAO,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG;AAC9B,IAAA,GAAA,CAAI,QAAQ,2EAA2E,CAAA;AAAA,EACzF,CAAA,MAAO;AACL,IAAA,YAAA,GAAe,UAAA,CAAW,MAAA,CAAO,MAAA,EAAQ,GAAA,CAAI,SAAS,CAAA;AACtD,IAAA,gBAAA,GAAmB,yBAAyB,YAAY,CAAA;AAExD,IAAA,MAAM,cAAc,YAAA,CAAa,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AAClD,IAAA,MAAM,YAAA,GAAe,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,CAAC,MAAM,CAAC,WAAA,CAAY,QAAA,CAAS,CAAC,CAAC,CAAA;AAEzE,IAAA,GAAA,CAAI,MAAA,EAAQ,CAAA,OAAA,EAAU,YAAA,CAAa,MAAM,CAAA,sBAAA,CAAA,EAA0B;AAAA,MACjE,MAAA,EAAQ,WAAA;AAAA,MACR,OAAA,EAAS,YAAA,CAAa,MAAA,GAAS,CAAA,GAAI,YAAA,GAAe;AAAA,KACnD,CAAA;AAED,IAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,MAAA,GAAA,CAAI,QAAQ,uCAAA,EAAyC;AAAA,QACnD,OAAA,EAAS;AAAA,OACV,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IAEL,cAAA,EAAgB,OACd,KAAA,EAOA,MAAA,KACkB;AAClB,MAAA,IAAI,YAAA,CAAa,MAAA,KAAW,CAAA,IAAK,CAAC,gBAAA,EAAkB;AAClD,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,MAAM,SAAA,EAAW;AACpB,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACzC,QAAA,GAAA,CAAI,SAAS,qCAAA,EAAuC;AAAA,UAClD,WAAW,KAAA,CAAM;AAAA,SAClB,CAAA;AACD,QAAA;AAAA,MACF;AAEA,MAAA,gBAAA,CAAiB,GAAA,CAAI,MAAM,SAAS,CAAA;AAEpC,MAAA,MAAM,aAAA,GAAgB,OAAO,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,MAAM,CAAA;AAChE,MAAA,IAAI,aAAA,IAAiB,UAAU,aAAA,EAAe;AAC5C,QAAA,aAAA,CAAc,IAAA,GAAO,GAAG,gBAAgB;;AAAA;;AAAA,EAAc,cAAc,IAAI,CAAA,CAAA;AAAA,MAC1E;AAEA,MAAA,GAAA,CAAI,QAAQ,wCAAA,EAA0C;AAAA,QACpD,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,YAAY,YAAA,CAAa,MAAA;AAAA,QACzB,QAAQ,YAAA,CAAa,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,IAAI;AAAA,OACvC,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,iCAAA,EAAmC,OACjC,KAAA,EACA,MAAA,KACkB;AAClB,MAAA,IAAI,CAAC,MAAA,CAAO,sBAAA,IAA0B,YAAA,CAAa,WAAW,CAAA,EAAG;AAC/D,QAAA;AAAA,MACF;AAEA,MAAA,MAAA,CAAO,OAAA,CAAQ,IAAA;AAAA,QACb,CAAA;;AAAA;;AAAA,EAAwG,gBAAgB,CAAA;AAAA,OAC1H;AAEA,MAAA,gBAAA,CAAiB,MAAA,CAAO,MAAM,SAAS,CAAA;AAEvC,MAAA,GAAA,CAAI,QAAQ,8CAAA,EAAgD;AAAA,QAC1D,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,YAAY,YAAA,CAAa;AAAA,OAC1B,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,KAAA,EAAO,OAAO,EAAE,KAAA,EAAM,KAAuC;AAC3D,MAAA,IACE,KAAA,CAAM,IAAA,KAAS,iBAAA,IACf,WAAA,IAAe,MAAM,UAAA,EACrB;AACA,QAAA,MAAM,SAAA,GAAY,MAAM,UAAA,CAAW,SAAA;AACnC,QAAA,gBAAA,CAAiB,OAAO,SAAS,CAAA;AACjC,QAAA,GAAA,CAAI,OAAA,EAAS,6BAAA,EAA+B,EAAE,SAAA,EAAW,CAAA;AAAA,MAC3D;AAAA,IACF;AAAA,GACF;AACF;AAEA,IAAO,aAAA,GAAQ","file":"index.cjs","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { ParsedSkill } from \"./types.js\"\n\nconst SKILL_FILENAME = \"SKILL.md\"\n\nconst SKILL_SEARCH_PATHS = [\n (dir: string) => join(dir, \".opencode\", \"skills\"),\n (dir: string) => join(dir, \".claude\", \"skills\"),\n () => join(homedir(), \".config\", \"opencode\", \"skills\"),\n () => join(homedir(), \".claude\", \"skills\"),\n]\n\nfunction findSkillFile(skillName: string, projectDir: string): string | null {\n for (const getPath of SKILL_SEARCH_PATHS) {\n const skillDir = getPath(projectDir)\n const skillPath = join(skillDir, skillName, SKILL_FILENAME)\n\n if (existsSync(skillPath)) {\n return skillPath\n }\n }\n return null\n}\n\nfunction parseFrontmatter(content: string): { name?: string; description?: string } {\n const frontmatterMatch = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/)\n if (!frontmatterMatch?.[1]) {\n return {}\n }\n\n const frontmatter = frontmatterMatch[1]\n const result: { name?: string; description?: string } = {}\n\n const nameMatch = frontmatter.match(/^name:\\s*(.+)$/m)\n if (nameMatch?.[1]) {\n result.name = nameMatch[1].trim()\n }\n\n const descMatch = frontmatter.match(/^description:\\s*(.+)$/m)\n if (descMatch?.[1]) {\n result.description = descMatch[1].trim()\n }\n\n return result\n}\n\nexport function loadSkill(skillName: string, projectDir: string): ParsedSkill | null {\n const filePath = findSkillFile(skillName, projectDir)\n\n if (!filePath) {\n return null\n }\n\n try {\n const content = readFileSync(filePath, \"utf-8\")\n const { name, description } = parseFrontmatter(content)\n\n return {\n name: name ?? skillName,\n description: description ?? \"\",\n content,\n filePath,\n }\n } catch {\n return null\n }\n}\n\nexport function loadSkills(skillNames: string[], projectDir: string): ParsedSkill[] {\n if (!Array.isArray(skillNames)) {\n return []\n }\n\n const skills: ParsedSkill[] = []\n\n for (const name of skillNames) {\n const skill = loadSkill(name, projectDir)\n if (skill) {\n skills.push(skill)\n }\n }\n\n return skills\n}\n\nexport function formatSkillsForInjection(skills: ParsedSkill[]): string {\n if (!Array.isArray(skills) || skills.length === 0) {\n return \"\"\n }\n\n const parts = skills.map(\n (skill) =>\n `<preloaded-skill name=\"${skill.name}\">\\n${skill.content}\\n</preloaded-skill>`\n )\n\n return `<preloaded-skills>\nThe following skills have been automatically loaded for this session:\n\n${parts.join(\"\\n\\n\")}\n</preloaded-skills>`\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { Plugin, PluginInput } from \"@opencode-ai/plugin\"\nimport type { Event, UserMessage, Part } from \"@opencode-ai/sdk\"\nimport type { PreloadSkillsConfig, ParsedSkill } from \"./types.js\"\nimport { loadSkills, formatSkillsForInjection } from \"./skill-loader.js\"\n\nexport type { PreloadSkillsConfig, ParsedSkill }\nexport { loadSkills, formatSkillsForInjection }\n\nconst CONFIG_FILENAME = \"preload-skills.json\"\n\nconst DEFAULT_CONFIG: PreloadSkillsConfig = {\n skills: [],\n persistAfterCompaction: true,\n debug: false,\n}\n\nfunction findConfigFile(projectDir: string): string | null {\n const locations = [\n join(projectDir, \".opencode\", CONFIG_FILENAME),\n join(projectDir, CONFIG_FILENAME),\n join(homedir(), \".config\", \"opencode\", CONFIG_FILENAME),\n ]\n\n for (const path of locations) {\n if (existsSync(path)) {\n return path\n }\n }\n return null\n}\n\nfunction loadConfigFile(projectDir: string): Partial<PreloadSkillsConfig> {\n const configPath = findConfigFile(projectDir)\n if (!configPath) {\n return {}\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\")\n const parsed = JSON.parse(content) as Record<string, unknown>\n \n return {\n skills: Array.isArray(parsed.skills) ? parsed.skills : [],\n persistAfterCompaction: typeof parsed.persistAfterCompaction === \"boolean\" \n ? parsed.persistAfterCompaction \n : undefined,\n debug: typeof parsed.debug === \"boolean\" ? parsed.debug : undefined,\n }\n } catch {\n return {}\n }\n}\n\nexport const PreloadSkillsPlugin: Plugin = async (ctx: PluginInput) => {\n const injectedSessions = new Set<string>()\n\n const fileConfig = loadConfigFile(ctx.directory)\n const config: PreloadSkillsConfig = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n }\n\n const log = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>\n ) => {\n if (level === \"debug\" && !config.debug) return\n\n ctx.client.app.log({\n body: {\n service: \"preload-skills\",\n level,\n message,\n extra,\n },\n })\n }\n\n let loadedSkills: ParsedSkill[] = []\n let formattedContent = \"\"\n\n if (config.skills.length === 0) {\n log(\"warn\", \"No skills configured for preloading. Create .opencode/preload-skills.json\")\n } else {\n loadedSkills = loadSkills(config.skills, ctx.directory)\n formattedContent = formatSkillsForInjection(loadedSkills)\n\n const loadedNames = loadedSkills.map((s) => s.name)\n const missingNames = config.skills.filter((s) => !loadedNames.includes(s))\n\n log(\"info\", `Loaded ${loadedSkills.length} skills for preloading`, {\n loaded: loadedNames,\n missing: missingNames.length > 0 ? missingNames : undefined,\n })\n\n if (missingNames.length > 0) {\n log(\"warn\", \"Some configured skills were not found\", {\n missing: missingNames,\n })\n }\n }\n\n return {\n\n \"chat.message\": async (\n input: {\n sessionID: string\n agent?: string\n model?: { providerID: string; modelID: string }\n messageID?: string\n variant?: string\n },\n output: { message: UserMessage; parts: Part[] }\n ): Promise<void> => {\n if (loadedSkills.length === 0 || !formattedContent) {\n return\n }\n\n if (!input.sessionID) {\n return\n }\n\n if (injectedSessions.has(input.sessionID)) {\n log(\"debug\", \"Skills already injected for session\", {\n sessionID: input.sessionID,\n })\n return\n }\n\n injectedSessions.add(input.sessionID)\n\n const firstTextPart = output.parts.find((p) => p.type === \"text\")\n if (firstTextPart && \"text\" in firstTextPart) {\n firstTextPart.text = `${formattedContent}\\n\\n---\\n\\n${firstTextPart.text}`\n }\n\n log(\"info\", \"Injected preloaded skills into session\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n skills: loadedSkills.map((s) => s.name),\n })\n },\n\n \"experimental.session.compacting\": async (\n input: { sessionID: string },\n output: { context: string[]; prompt?: string }\n ): Promise<void> => {\n if (!config.persistAfterCompaction || loadedSkills.length === 0) {\n return\n }\n\n output.context.push(\n `## Preloaded Skills\\n\\nThe following skills were auto-loaded at session start and should persist:\\n\\n${formattedContent}`\n )\n\n injectedSessions.delete(input.sessionID)\n\n log(\"info\", \"Added preloaded skills to compaction context\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n })\n },\n\n event: async ({ event }: { event: Event }): Promise<void> => {\n if (\n event.type === \"session.deleted\" &&\n \"sessionID\" in event.properties\n ) {\n const sessionID = event.properties.sessionID as string\n injectedSessions.delete(sessionID)\n log(\"debug\", \"Cleaned up session tracking\", { sessionID })\n }\n },\n }\n}\n\nexport default PreloadSkillsPlugin\n"]}
package/dist/index.js CHANGED
@@ -163,6 +163,9 @@ var PreloadSkillsPlugin = async (ctx) => {
163
163
  if (loadedSkills.length === 0 || !formattedContent) {
164
164
  return;
165
165
  }
166
+ if (!input.sessionID) {
167
+ return;
168
+ }
166
169
  if (injectedSessions.has(input.sessionID)) {
167
170
  log("debug", "Skills already injected for session", {
168
171
  sessionID: input.sessionID
@@ -170,11 +173,14 @@ var PreloadSkillsPlugin = async (ctx) => {
170
173
  return;
171
174
  }
172
175
  injectedSessions.add(input.sessionID);
173
- const syntheticPart = {
174
- type: "text",
175
- text: formattedContent
176
- };
177
- output.parts.unshift(syntheticPart);
176
+ const firstTextPart = output.parts.find((p) => p.type === "text");
177
+ if (firstTextPart && "text" in firstTextPart) {
178
+ firstTextPart.text = `${formattedContent}
179
+
180
+ ---
181
+
182
+ ${firstTextPart.text}`;
183
+ }
178
184
  log("info", "Injected preloaded skills into session", {
179
185
  sessionID: input.sessionID,
180
186
  skillCount: loadedSkills.length,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/skill-loader.ts","../src/index.ts"],"names":["join","homedir","existsSync","readFileSync"],"mappings":"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","file":"index.js","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { ParsedSkill } from \"./types.js\"\n\nconst SKILL_FILENAME = \"SKILL.md\"\n\nconst SKILL_SEARCH_PATHS = [\n (dir: string) => join(dir, \".opencode\", \"skills\"),\n (dir: string) => join(dir, \".claude\", \"skills\"),\n () => join(homedir(), \".config\", \"opencode\", \"skills\"),\n () => join(homedir(), \".claude\", \"skills\"),\n]\n\nfunction findSkillFile(skillName: string, projectDir: string): string | null {\n for (const getPath of SKILL_SEARCH_PATHS) {\n const skillDir = getPath(projectDir)\n const skillPath = join(skillDir, skillName, SKILL_FILENAME)\n\n if (existsSync(skillPath)) {\n return skillPath\n }\n }\n return null\n}\n\nfunction parseFrontmatter(content: string): { name?: string; description?: string } {\n const frontmatterMatch = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/)\n if (!frontmatterMatch?.[1]) {\n return {}\n }\n\n const frontmatter = frontmatterMatch[1]\n const result: { name?: string; description?: string } = {}\n\n const nameMatch = frontmatter.match(/^name:\\s*(.+)$/m)\n if (nameMatch?.[1]) {\n result.name = nameMatch[1].trim()\n }\n\n const descMatch = frontmatter.match(/^description:\\s*(.+)$/m)\n if (descMatch?.[1]) {\n result.description = descMatch[1].trim()\n }\n\n return result\n}\n\nexport function loadSkill(skillName: string, projectDir: string): ParsedSkill | null {\n const filePath = findSkillFile(skillName, projectDir)\n\n if (!filePath) {\n return null\n }\n\n try {\n const content = readFileSync(filePath, \"utf-8\")\n const { name, description } = parseFrontmatter(content)\n\n return {\n name: name ?? skillName,\n description: description ?? \"\",\n content,\n filePath,\n }\n } catch {\n return null\n }\n}\n\nexport function loadSkills(skillNames: string[], projectDir: string): ParsedSkill[] {\n if (!Array.isArray(skillNames)) {\n return []\n }\n\n const skills: ParsedSkill[] = []\n\n for (const name of skillNames) {\n const skill = loadSkill(name, projectDir)\n if (skill) {\n skills.push(skill)\n }\n }\n\n return skills\n}\n\nexport function formatSkillsForInjection(skills: ParsedSkill[]): string {\n if (!Array.isArray(skills) || skills.length === 0) {\n return \"\"\n }\n\n const parts = skills.map(\n (skill) =>\n `<preloaded-skill name=\"${skill.name}\">\\n${skill.content}\\n</preloaded-skill>`\n )\n\n return `<preloaded-skills>\nThe following skills have been automatically loaded for this session:\n\n${parts.join(\"\\n\\n\")}\n</preloaded-skills>`\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { Plugin, PluginInput } from \"@opencode-ai/plugin\"\nimport type { Event, UserMessage, Part } from \"@opencode-ai/sdk\"\nimport type { PreloadSkillsConfig, ParsedSkill } from \"./types.js\"\nimport { loadSkills, formatSkillsForInjection } from \"./skill-loader.js\"\n\nexport type { PreloadSkillsConfig, ParsedSkill }\nexport { loadSkills, formatSkillsForInjection }\n\nconst CONFIG_FILENAME = \"preload-skills.json\"\n\nconst DEFAULT_CONFIG: PreloadSkillsConfig = {\n skills: [],\n persistAfterCompaction: true,\n debug: false,\n}\n\nfunction findConfigFile(projectDir: string): string | null {\n const locations = [\n join(projectDir, \".opencode\", CONFIG_FILENAME),\n join(projectDir, CONFIG_FILENAME),\n join(homedir(), \".config\", \"opencode\", CONFIG_FILENAME),\n ]\n\n for (const path of locations) {\n if (existsSync(path)) {\n return path\n }\n }\n return null\n}\n\nfunction loadConfigFile(projectDir: string): Partial<PreloadSkillsConfig> {\n const configPath = findConfigFile(projectDir)\n if (!configPath) {\n return {}\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\")\n const parsed = JSON.parse(content) as Record<string, unknown>\n \n return {\n skills: Array.isArray(parsed.skills) ? parsed.skills : [],\n persistAfterCompaction: typeof parsed.persistAfterCompaction === \"boolean\" \n ? parsed.persistAfterCompaction \n : undefined,\n debug: typeof parsed.debug === \"boolean\" ? parsed.debug : undefined,\n }\n } catch {\n return {}\n }\n}\n\nexport const PreloadSkillsPlugin: Plugin = async (ctx: PluginInput) => {\n const injectedSessions = new Set<string>()\n\n const fileConfig = loadConfigFile(ctx.directory)\n const config: PreloadSkillsConfig = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n }\n\n const log = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>\n ) => {\n if (level === \"debug\" && !config.debug) return\n\n ctx.client.app.log({\n body: {\n service: \"preload-skills\",\n level,\n message,\n extra,\n },\n })\n }\n\n let loadedSkills: ParsedSkill[] = []\n let formattedContent = \"\"\n\n if (config.skills.length === 0) {\n log(\"warn\", \"No skills configured for preloading. Create .opencode/preload-skills.json\")\n } else {\n loadedSkills = loadSkills(config.skills, ctx.directory)\n formattedContent = formatSkillsForInjection(loadedSkills)\n\n const loadedNames = loadedSkills.map((s) => s.name)\n const missingNames = config.skills.filter((s) => !loadedNames.includes(s))\n\n log(\"info\", `Loaded ${loadedSkills.length} skills for preloading`, {\n loaded: loadedNames,\n missing: missingNames.length > 0 ? missingNames : undefined,\n })\n\n if (missingNames.length > 0) {\n log(\"warn\", \"Some configured skills were not found\", {\n missing: missingNames,\n })\n }\n }\n\n return {\n\n \"chat.message\": async (\n input: {\n sessionID: string\n agent?: string\n model?: { providerID: string; modelID: string }\n messageID?: string\n variant?: string\n },\n output: { message: UserMessage; parts: Part[] }\n ): Promise<void> => {\n if (loadedSkills.length === 0 || !formattedContent) {\n return\n }\n\n if (injectedSessions.has(input.sessionID)) {\n log(\"debug\", \"Skills already injected for session\", {\n sessionID: input.sessionID,\n })\n return\n }\n\n injectedSessions.add(input.sessionID)\n\n const syntheticPart = {\n type: \"text\",\n text: formattedContent,\n } as Part\n\n output.parts.unshift(syntheticPart)\n\n log(\"info\", \"Injected preloaded skills into session\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n skills: loadedSkills.map((s) => s.name),\n })\n },\n\n \"experimental.session.compacting\": async (\n input: { sessionID: string },\n output: { context: string[]; prompt?: string }\n ): Promise<void> => {\n if (!config.persistAfterCompaction || loadedSkills.length === 0) {\n return\n }\n\n output.context.push(\n `## Preloaded Skills\\n\\nThe following skills were auto-loaded at session start and should persist:\\n\\n${formattedContent}`\n )\n\n injectedSessions.delete(input.sessionID)\n\n log(\"info\", \"Added preloaded skills to compaction context\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n })\n },\n\n event: async ({ event }: { event: Event }): Promise<void> => {\n if (\n event.type === \"session.deleted\" &&\n \"sessionID\" in event.properties\n ) {\n const sessionID = event.properties.sessionID as string\n injectedSessions.delete(sessionID)\n log(\"debug\", \"Cleaned up session tracking\", { sessionID })\n }\n },\n }\n}\n\nexport default PreloadSkillsPlugin\n"]}
1
+ {"version":3,"sources":["../src/skill-loader.ts","../src/index.ts"],"names":["join","homedir","existsSync","readFileSync"],"mappings":"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","file":"index.js","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { ParsedSkill } from \"./types.js\"\n\nconst SKILL_FILENAME = \"SKILL.md\"\n\nconst SKILL_SEARCH_PATHS = [\n (dir: string) => join(dir, \".opencode\", \"skills\"),\n (dir: string) => join(dir, \".claude\", \"skills\"),\n () => join(homedir(), \".config\", \"opencode\", \"skills\"),\n () => join(homedir(), \".claude\", \"skills\"),\n]\n\nfunction findSkillFile(skillName: string, projectDir: string): string | null {\n for (const getPath of SKILL_SEARCH_PATHS) {\n const skillDir = getPath(projectDir)\n const skillPath = join(skillDir, skillName, SKILL_FILENAME)\n\n if (existsSync(skillPath)) {\n return skillPath\n }\n }\n return null\n}\n\nfunction parseFrontmatter(content: string): { name?: string; description?: string } {\n const frontmatterMatch = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/)\n if (!frontmatterMatch?.[1]) {\n return {}\n }\n\n const frontmatter = frontmatterMatch[1]\n const result: { name?: string; description?: string } = {}\n\n const nameMatch = frontmatter.match(/^name:\\s*(.+)$/m)\n if (nameMatch?.[1]) {\n result.name = nameMatch[1].trim()\n }\n\n const descMatch = frontmatter.match(/^description:\\s*(.+)$/m)\n if (descMatch?.[1]) {\n result.description = descMatch[1].trim()\n }\n\n return result\n}\n\nexport function loadSkill(skillName: string, projectDir: string): ParsedSkill | null {\n const filePath = findSkillFile(skillName, projectDir)\n\n if (!filePath) {\n return null\n }\n\n try {\n const content = readFileSync(filePath, \"utf-8\")\n const { name, description } = parseFrontmatter(content)\n\n return {\n name: name ?? skillName,\n description: description ?? \"\",\n content,\n filePath,\n }\n } catch {\n return null\n }\n}\n\nexport function loadSkills(skillNames: string[], projectDir: string): ParsedSkill[] {\n if (!Array.isArray(skillNames)) {\n return []\n }\n\n const skills: ParsedSkill[] = []\n\n for (const name of skillNames) {\n const skill = loadSkill(name, projectDir)\n if (skill) {\n skills.push(skill)\n }\n }\n\n return skills\n}\n\nexport function formatSkillsForInjection(skills: ParsedSkill[]): string {\n if (!Array.isArray(skills) || skills.length === 0) {\n return \"\"\n }\n\n const parts = skills.map(\n (skill) =>\n `<preloaded-skill name=\"${skill.name}\">\\n${skill.content}\\n</preloaded-skill>`\n )\n\n return `<preloaded-skills>\nThe following skills have been automatically loaded for this session:\n\n${parts.join(\"\\n\\n\")}\n</preloaded-skills>`\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport type { Plugin, PluginInput } from \"@opencode-ai/plugin\"\nimport type { Event, UserMessage, Part } from \"@opencode-ai/sdk\"\nimport type { PreloadSkillsConfig, ParsedSkill } from \"./types.js\"\nimport { loadSkills, formatSkillsForInjection } from \"./skill-loader.js\"\n\nexport type { PreloadSkillsConfig, ParsedSkill }\nexport { loadSkills, formatSkillsForInjection }\n\nconst CONFIG_FILENAME = \"preload-skills.json\"\n\nconst DEFAULT_CONFIG: PreloadSkillsConfig = {\n skills: [],\n persistAfterCompaction: true,\n debug: false,\n}\n\nfunction findConfigFile(projectDir: string): string | null {\n const locations = [\n join(projectDir, \".opencode\", CONFIG_FILENAME),\n join(projectDir, CONFIG_FILENAME),\n join(homedir(), \".config\", \"opencode\", CONFIG_FILENAME),\n ]\n\n for (const path of locations) {\n if (existsSync(path)) {\n return path\n }\n }\n return null\n}\n\nfunction loadConfigFile(projectDir: string): Partial<PreloadSkillsConfig> {\n const configPath = findConfigFile(projectDir)\n if (!configPath) {\n return {}\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\")\n const parsed = JSON.parse(content) as Record<string, unknown>\n \n return {\n skills: Array.isArray(parsed.skills) ? parsed.skills : [],\n persistAfterCompaction: typeof parsed.persistAfterCompaction === \"boolean\" \n ? parsed.persistAfterCompaction \n : undefined,\n debug: typeof parsed.debug === \"boolean\" ? parsed.debug : undefined,\n }\n } catch {\n return {}\n }\n}\n\nexport const PreloadSkillsPlugin: Plugin = async (ctx: PluginInput) => {\n const injectedSessions = new Set<string>()\n\n const fileConfig = loadConfigFile(ctx.directory)\n const config: PreloadSkillsConfig = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n }\n\n const log = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>\n ) => {\n if (level === \"debug\" && !config.debug) return\n\n ctx.client.app.log({\n body: {\n service: \"preload-skills\",\n level,\n message,\n extra,\n },\n })\n }\n\n let loadedSkills: ParsedSkill[] = []\n let formattedContent = \"\"\n\n if (config.skills.length === 0) {\n log(\"warn\", \"No skills configured for preloading. Create .opencode/preload-skills.json\")\n } else {\n loadedSkills = loadSkills(config.skills, ctx.directory)\n formattedContent = formatSkillsForInjection(loadedSkills)\n\n const loadedNames = loadedSkills.map((s) => s.name)\n const missingNames = config.skills.filter((s) => !loadedNames.includes(s))\n\n log(\"info\", `Loaded ${loadedSkills.length} skills for preloading`, {\n loaded: loadedNames,\n missing: missingNames.length > 0 ? missingNames : undefined,\n })\n\n if (missingNames.length > 0) {\n log(\"warn\", \"Some configured skills were not found\", {\n missing: missingNames,\n })\n }\n }\n\n return {\n\n \"chat.message\": async (\n input: {\n sessionID: string\n agent?: string\n model?: { providerID: string; modelID: string }\n messageID?: string\n variant?: string\n },\n output: { message: UserMessage; parts: Part[] }\n ): Promise<void> => {\n if (loadedSkills.length === 0 || !formattedContent) {\n return\n }\n\n if (!input.sessionID) {\n return\n }\n\n if (injectedSessions.has(input.sessionID)) {\n log(\"debug\", \"Skills already injected for session\", {\n sessionID: input.sessionID,\n })\n return\n }\n\n injectedSessions.add(input.sessionID)\n\n const firstTextPart = output.parts.find((p) => p.type === \"text\")\n if (firstTextPart && \"text\" in firstTextPart) {\n firstTextPart.text = `${formattedContent}\\n\\n---\\n\\n${firstTextPart.text}`\n }\n\n log(\"info\", \"Injected preloaded skills into session\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n skills: loadedSkills.map((s) => s.name),\n })\n },\n\n \"experimental.session.compacting\": async (\n input: { sessionID: string },\n output: { context: string[]; prompt?: string }\n ): Promise<void> => {\n if (!config.persistAfterCompaction || loadedSkills.length === 0) {\n return\n }\n\n output.context.push(\n `## Preloaded Skills\\n\\nThe following skills were auto-loaded at session start and should persist:\\n\\n${formattedContent}`\n )\n\n injectedSessions.delete(input.sessionID)\n\n log(\"info\", \"Added preloaded skills to compaction context\", {\n sessionID: input.sessionID,\n skillCount: loadedSkills.length,\n })\n },\n\n event: async ({ event }: { event: Event }): Promise<void> => {\n if (\n event.type === \"session.deleted\" &&\n \"sessionID\" in event.properties\n ) {\n const sessionID = event.properties.sessionID as string\n injectedSessions.delete(sessionID)\n log(\"debug\", \"Cleaned up session tracking\", { sessionID })\n }\n },\n }\n}\n\nexport default PreloadSkillsPlugin\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-preload-skills",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "OpenCode plugin that auto-loads specified skills into agent memory on session start",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",