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 +11 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
178
|
-
|
|
179
|
-
text
|
|
180
|
-
|
|
181
|
-
|
|
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,
|
package/dist/index.cjs.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.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":"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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 (!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
|
|
174
|
-
|
|
175
|
-
text
|
|
176
|
-
|
|
177
|
-
|
|
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