opencode-dynamic-skills 1.0.1 → 1.0.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.js CHANGED
@@ -17839,6 +17839,18 @@ function createResourceCandidates(type, relativePath) {
17839
17839
  function normalizeSkillIdentifier(identifier) {
17840
17840
  return identifier.trim().toLowerCase().replace(/[/-]/g, "_");
17841
17841
  }
17842
+ function resolveFileWithinSkillRoot(skill, relativePath) {
17843
+ const normalizedPath = normalizeSkillResourcePath(relativePath);
17844
+ const absolutePath = path.resolve(skill.fullPath, normalizedPath);
17845
+ const relativeFromSkillRoot = path.relative(skill.fullPath, absolutePath);
17846
+ if (relativeFromSkillRoot.startsWith("..") || path.isAbsolute(relativeFromSkillRoot) || !doesPathExist(absolutePath)) {
17847
+ return null;
17848
+ }
17849
+ return {
17850
+ absolutePath,
17851
+ mimeType: detectMimeType(absolutePath)
17852
+ };
17853
+ }
17842
17854
  function createSkillResourceResolver(provider) {
17843
17855
  return async (args) => {
17844
17856
  const skillIdentifier = normalizeSkillIdentifier(args.skill_name);
@@ -17849,18 +17861,19 @@ function createSkillResourceResolver(provider) {
17849
17861
  const resourceMap = getSkillResources(skill);
17850
17862
  const candidatePaths = createResourceCandidates(args.type, args.relative_path);
17851
17863
  const resourceEntry = candidatePaths.map((candidatePath) => resourceMap.get(candidatePath)).find((candidate) => candidate !== undefined);
17852
- if (!resourceEntry) {
17864
+ const resolvedResource = resourceEntry ?? candidatePaths.map((candidatePath) => resolveFileWithinSkillRoot(skill, candidatePath)).find((candidate) => candidate !== null);
17865
+ if (!resolvedResource) {
17853
17866
  throw new Error(`Resource not found: Skill "${args.skill_name}" does not have a resource at path "${args.relative_path}"`);
17854
17867
  }
17855
17868
  try {
17856
- const content = await readSkillFile(resourceEntry.absolutePath);
17869
+ const content = await readSkillFile(resolvedResource.absolutePath);
17857
17870
  return {
17858
- absolute_path: resourceEntry.absolutePath,
17871
+ absolute_path: resolvedResource.absolutePath,
17859
17872
  content,
17860
- mimeType: resourceEntry.mimeType
17873
+ mimeType: resolvedResource.mimeType
17861
17874
  };
17862
17875
  } catch (error45) {
17863
- throw new Error(`Failed to read resource at ${resourceEntry.absolutePath}: ${error45 instanceof Error ? error45.message : String(error45)}`);
17876
+ throw new Error(`Failed to read resource at ${resolvedResource.absolutePath}: ${error45 instanceof Error ? error45.message : String(error45)}`);
17864
17877
  }
17865
17878
  };
17866
17879
  }
@@ -23207,11 +23220,57 @@ async function rewriteSlashCommandText(args) {
23207
23220
  });
23208
23221
  }
23209
23222
 
23223
+ // src/lib/SkillLinks.ts
23224
+ import path2 from "path";
23225
+ var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/i;
23226
+ function normalizeLinkedSkillPath(target) {
23227
+ const trimmedTarget = target.trim();
23228
+ if (trimmedTarget.length === 0 || trimmedTarget.startsWith("#") || trimmedTarget.startsWith("/") || SCHEME_PATTERN.test(trimmedTarget)) {
23229
+ return null;
23230
+ }
23231
+ const [pathWithoutFragment] = trimmedTarget.split("#", 1);
23232
+ const [pathWithoutQuery] = pathWithoutFragment.split("?", 1);
23233
+ const normalizedPath = path2.posix.normalize(pathWithoutQuery.replace(/\\/g, "/")).replace(/^\.\//, "");
23234
+ if (normalizedPath.length === 0 || normalizedPath === "." || normalizedPath.startsWith("../") || normalizedPath.includes("/../")) {
23235
+ return null;
23236
+ }
23237
+ return normalizedPath;
23238
+ }
23239
+ function extractSkillLinks(content) {
23240
+ const links = new Map;
23241
+ for (const match of content.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)) {
23242
+ const label = match[1]?.trim();
23243
+ const originalPath = match[2]?.trim();
23244
+ if (!label || !originalPath) {
23245
+ continue;
23246
+ }
23247
+ const resourcePath = normalizeLinkedSkillPath(originalPath);
23248
+ if (!resourcePath) {
23249
+ continue;
23250
+ }
23251
+ links.set(`${label}:${resourcePath}`, {
23252
+ label,
23253
+ originalPath,
23254
+ resourcePath
23255
+ });
23256
+ }
23257
+ return Array.from(links.values());
23258
+ }
23259
+
23210
23260
  // src/lib/renderers/JsonPromptRenderer.ts
23211
23261
  var createJsonPromptRenderer = () => {
23212
23262
  const renderer = {
23213
23263
  format: "json",
23214
23264
  render(args) {
23265
+ if (args.type === "Skill") {
23266
+ return JSON.stringify({
23267
+ Skill: {
23268
+ ...args.data,
23269
+ linkedResources: extractSkillLinks(args.data.content),
23270
+ skillRootInstruction: "Resolve linked files relative to the skill root and use skill_resource with the exact root-relative path."
23271
+ }
23272
+ }, null, 2);
23273
+ }
23215
23274
  return JSON.stringify({ [args.type]: args.data }, null, 2);
23216
23275
  }
23217
23276
  };
@@ -23269,6 +23328,8 @@ var createXmlPromptRenderer = () => {
23269
23328
  const prepareSkill = (skill) => {
23270
23329
  return {
23271
23330
  ...skill,
23331
+ linkedResources: extractSkillLinks(skill.content),
23332
+ skillRootInstruction: "Resolve linked files relative to the skill root and use skill_resource with the exact root-relative path.",
23272
23333
  references: resourceMapToArray(skill.references),
23273
23334
  scripts: resourceMapToArray(skill.scripts),
23274
23335
  assets: resourceMapToArray(skill.assets)
@@ -23490,6 +23551,7 @@ var createMdPromptRenderer = () => {
23490
23551
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
23491
23552
  };
23492
23553
  const renderSkill = (skill) => {
23554
+ const linkedResources = extractSkillLinks(skill.content);
23493
23555
  return dedent_default`
23494
23556
  # ${skill.name}
23495
23557
 
@@ -23497,6 +23559,14 @@ var createMdPromptRenderer = () => {
23497
23559
  > ${skill.fullPath}
23498
23560
 
23499
23561
  Relative file references in this skill resolve from the skill root directory.
23562
+ Always use skill_resource with the exact root-relative path for linked files.
23563
+
23564
+ ${linkedResources.length > 0 ? dedent_default`
23565
+ ## Linked files detected in skill content
23566
+
23567
+ ${linkedResources.map((link) => `- [${link.label}](${link.originalPath}) \u2192 use skill_resource with ${link.resourcePath}`).join(`
23568
+ `)}
23569
+ ` : ""}
23500
23570
 
23501
23571
  ${skill.content}
23502
23572
 
@@ -0,0 +1,7 @@
1
+ export type SkillLink = {
2
+ label: string;
3
+ originalPath: string;
4
+ resourcePath: string;
5
+ };
6
+ export declare function normalizeLinkedSkillPath(target: string): string | null;
7
+ export declare function extractSkillLinks(content: string): SkillLink[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-dynamic-skills",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "OpenCode plugin for dynamic skill loading, slash skill commands, and skill-root-relative resources",
5
5
  "homepage": "https://github.com/Wu-H-Y/opencode-dynamic-skills#readme",
6
6
  "bugs": {