opencode-dynamic-skills 1.0.1 → 1.1.0
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/README.md +13 -2
- package/README.zh-CN.md +13 -2
- package/dist/api.d.ts +17 -58
- package/dist/commands/SlashCommand.d.ts +0 -2
- package/dist/index.js +232 -79
- package/dist/lib/SkillLinks.d.ts +7 -0
- package/dist/lib/formatLoadedSkill.d.ts +6 -0
- package/dist/tools/Skill.d.ts +3 -0
- package/dist/tools/Skill.test.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,9 +21,14 @@ This project is a reboot of the archived `opencode-skillful` codebase.
|
|
|
21
21
|
This project is derived from and adapted from:
|
|
22
22
|
|
|
23
23
|
- https://github.com/zenobi-us/opencode-skillful
|
|
24
|
+
- https://github.com/code-yeongyu/oh-my-openagent
|
|
25
|
+
- Anthropic Agent Skills specification and skills ecosystem: https://github.com/anthropics/skills
|
|
24
26
|
|
|
25
27
|
The current codebase restructures and extends that work for a fresh restart.
|
|
26
28
|
|
|
29
|
+
This project also references the slash-command and skill-loading ideas explored by oh-my-openagent.
|
|
30
|
+
Thanks to the maintainers and contributors of these projects for the public implementation ideas and prior art.
|
|
31
|
+
|
|
27
32
|
## Goals
|
|
28
33
|
|
|
29
34
|
- Keep skills **lazy-loaded**, instead of injecting all skills into every session
|
|
@@ -37,11 +42,16 @@ The current codebase restructures and extends that work for a fresh restart.
|
|
|
37
42
|
|
|
38
43
|
The plugin provides:
|
|
39
44
|
|
|
45
|
+
- `skill`
|
|
40
46
|
- `skill_find`
|
|
41
47
|
- `skill_recommend`
|
|
42
48
|
- `skill_use`
|
|
43
49
|
- `skill_resource`
|
|
44
50
|
|
|
51
|
+
The default design keeps the initial tool context small.
|
|
52
|
+
It does **not** inject the full skill catalog or every skill description up front.
|
|
53
|
+
Discovery is deferred to runtime through `skill_find`, `skill_recommend`, direct slash interception, or explicit `skill(name="...")` calls.
|
|
54
|
+
|
|
45
55
|
### Slash skill command
|
|
46
56
|
|
|
47
57
|
Default form:
|
|
@@ -68,7 +78,8 @@ Optional aliases are also supported:
|
|
|
68
78
|
/git-release draft a release checklist
|
|
69
79
|
```
|
|
70
80
|
|
|
71
|
-
Aliases are
|
|
81
|
+
Aliases are enabled by default in the pure oh-my-openagent-style flow, so `/<skill-name>` can be intercepted after submit.
|
|
82
|
+
This does not mean OpenCode will show dynamic picker suggestions for those aliases.
|
|
72
83
|
|
|
73
84
|
### Skill-root-relative resources
|
|
74
85
|
|
|
@@ -112,7 +123,7 @@ Example:
|
|
|
112
123
|
"gpt-4": "json"
|
|
113
124
|
},
|
|
114
125
|
"slashCommandName": "skill",
|
|
115
|
-
"enableSkillAliases":
|
|
126
|
+
"enableSkillAliases": true
|
|
116
127
|
}
|
|
117
128
|
```
|
|
118
129
|
|
package/README.zh-CN.md
CHANGED
|
@@ -21,9 +21,14 @@
|
|
|
21
21
|
本项目来自并改造于:
|
|
22
22
|
|
|
23
23
|
- https://github.com/zenobi-us/opencode-skillful
|
|
24
|
+
- https://github.com/code-yeongyu/oh-my-openagent
|
|
25
|
+
- Anthropic Agent Skills 规范与 skills 生态:https://github.com/anthropics/skills
|
|
24
26
|
|
|
25
27
|
当前版本在此基础上做了重新组织和重启开发。
|
|
26
28
|
|
|
29
|
+
其中 slash 风格 skill 调用与 skill 装载思路,明确参考了 oh-my-openagent 的公开实现。
|
|
30
|
+
感谢这些项目的维护者与贡献者。
|
|
31
|
+
|
|
27
32
|
## 目标
|
|
28
33
|
|
|
29
34
|
- 保持 **按需加载**,不把所有 skill 预注入到每个会话
|
|
@@ -37,11 +42,16 @@
|
|
|
37
42
|
|
|
38
43
|
插件提供:
|
|
39
44
|
|
|
45
|
+
- `skill`
|
|
40
46
|
- `skill_find`
|
|
41
47
|
- `skill_recommend`
|
|
42
48
|
- `skill_use`
|
|
43
49
|
- `skill_resource`
|
|
44
50
|
|
|
51
|
+
默认设计会尽量保持初始工具上下文很小。
|
|
52
|
+
它**不会**在一开始把全部 skill 目录和每个 skill 的 description 都注入进去。
|
|
53
|
+
真正的发现与装载会延后到运行时,通过 `skill_find`、`skill_recommend`、直接 slash 拦截或显式 `skill(name="...")` 调用完成。
|
|
54
|
+
|
|
45
55
|
### Slash 技能命令
|
|
46
56
|
|
|
47
57
|
默认形式:
|
|
@@ -68,7 +78,8 @@
|
|
|
68
78
|
/git-release 帮我起草 release checklist
|
|
69
79
|
```
|
|
70
80
|
|
|
71
|
-
|
|
81
|
+
在纯 oh-my-openagent 风格下,alias 默认开启,因此提交 `/<skill-name>` 后插件会尝试接管。
|
|
82
|
+
但这不代表 OpenCode 会在输入阶段为这些 alias 提供动态候选列表。
|
|
72
83
|
|
|
73
84
|
### Skill 根目录相对资源
|
|
74
85
|
|
|
@@ -112,7 +123,7 @@ templates/pr.md
|
|
|
112
123
|
"gpt-4": "json"
|
|
113
124
|
},
|
|
114
125
|
"slashCommandName": "skill",
|
|
115
|
-
"enableSkillAliases":
|
|
126
|
+
"enableSkillAliases": true
|
|
116
127
|
}
|
|
117
128
|
```
|
|
118
129
|
|
package/dist/api.d.ts
CHANGED
|
@@ -34,63 +34,22 @@
|
|
|
34
34
|
* // Note: registry is created but NOT yet initialized
|
|
35
35
|
* // Must be done by caller: await registry.initialise()
|
|
36
36
|
*/
|
|
37
|
+
import { createLogger } from './services/logger';
|
|
38
|
+
import { createSkillRegistry } from './services/SkillRegistry';
|
|
39
|
+
import { createSkillFinder } from './tools/SkillFinder';
|
|
40
|
+
import { createSkillRecommender } from './tools/SkillRecommender';
|
|
41
|
+
import { createSkillResourceReader } from './tools/SkillResourceReader';
|
|
42
|
+
import { createSkillTool } from './tools/Skill';
|
|
43
|
+
import { createSkillLoader } from './tools/SkillUser';
|
|
37
44
|
import type { PluginConfig } from './types';
|
|
38
|
-
export
|
|
39
|
-
registry:
|
|
40
|
-
logger:
|
|
45
|
+
export type SkillsApi = {
|
|
46
|
+
registry: Awaited<ReturnType<typeof createSkillRegistry>>;
|
|
47
|
+
logger: ReturnType<typeof createLogger>;
|
|
41
48
|
config: PluginConfig;
|
|
42
|
-
findSkills:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}[];
|
|
50
|
-
summary: {
|
|
51
|
-
total: number;
|
|
52
|
-
matches: number;
|
|
53
|
-
feedback: string;
|
|
54
|
-
};
|
|
55
|
-
debug: import("./types").SkillRegistryDebugInfo | undefined;
|
|
56
|
-
}>;
|
|
57
|
-
recommendSkills: (args: {
|
|
58
|
-
task: string;
|
|
59
|
-
limit?: number;
|
|
60
|
-
}) => Promise<{
|
|
61
|
-
mode: "recommend";
|
|
62
|
-
query: string;
|
|
63
|
-
skills: {
|
|
64
|
-
name: string;
|
|
65
|
-
description: string;
|
|
66
|
-
}[];
|
|
67
|
-
recommendations: {
|
|
68
|
-
name: string;
|
|
69
|
-
description: string;
|
|
70
|
-
score: number;
|
|
71
|
-
reason: string;
|
|
72
|
-
}[];
|
|
73
|
-
guidance: string;
|
|
74
|
-
summary: {
|
|
75
|
-
total: number;
|
|
76
|
-
matches: number;
|
|
77
|
-
feedback: string;
|
|
78
|
-
};
|
|
79
|
-
debug: import("./types").SkillRegistryDebugInfo | undefined;
|
|
80
|
-
}>;
|
|
81
|
-
readResource: (args: {
|
|
82
|
-
skill_name: string;
|
|
83
|
-
relative_path: string;
|
|
84
|
-
}) => Promise<{
|
|
85
|
-
injection: {
|
|
86
|
-
skill_name: string;
|
|
87
|
-
resource_path: string;
|
|
88
|
-
resource_mimetype: string;
|
|
89
|
-
content: string;
|
|
90
|
-
};
|
|
91
|
-
}>;
|
|
92
|
-
loadSkill: (skillNames: string[]) => Promise<{
|
|
93
|
-
loaded: import("./types").Skill[];
|
|
94
|
-
notFound: string[];
|
|
95
|
-
}>;
|
|
96
|
-
}>;
|
|
49
|
+
findSkills: ReturnType<typeof createSkillFinder>;
|
|
50
|
+
recommendSkills: ReturnType<typeof createSkillRecommender>;
|
|
51
|
+
readResource: ReturnType<typeof createSkillResourceReader>;
|
|
52
|
+
loadSkill: ReturnType<typeof createSkillLoader>;
|
|
53
|
+
skillTool: ReturnType<typeof createSkillTool>;
|
|
54
|
+
};
|
|
55
|
+
export declare const createApi: (config: PluginConfig) => Promise<SkillsApi>;
|
|
@@ -10,7 +10,6 @@ export declare function parseSlashCommand(text: string, slashCommandName: string
|
|
|
10
10
|
export declare function findSkillBySelector(registry: SkillRegistry, selector: string): Skill | null;
|
|
11
11
|
export declare function renderSlashSkillPrompt(args: {
|
|
12
12
|
invocationName: string;
|
|
13
|
-
renderedSkill: string;
|
|
14
13
|
skill: Skill;
|
|
15
14
|
userPrompt: string;
|
|
16
15
|
}): string;
|
|
@@ -19,7 +18,6 @@ export declare function rewriteRecommendSlashCommandText(text: string): Promise<
|
|
|
19
18
|
export declare function rewriteSlashCommandText(args: {
|
|
20
19
|
text: string;
|
|
21
20
|
registry: SkillRegistry;
|
|
22
|
-
renderSkill: (skill: Skill) => string;
|
|
23
21
|
slashCommandName: string;
|
|
24
22
|
enableSkillAliases: boolean;
|
|
25
23
|
}): Promise<string | null>;
|
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
|
-
|
|
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(
|
|
17869
|
+
const content = await readSkillFile(resolvedResource.absolutePath);
|
|
17857
17870
|
return {
|
|
17858
|
-
absolute_path:
|
|
17871
|
+
absolute_path: resolvedResource.absolutePath,
|
|
17859
17872
|
content,
|
|
17860
|
-
mimeType:
|
|
17873
|
+
mimeType: resolvedResource.mimeType
|
|
17861
17874
|
};
|
|
17862
17875
|
} catch (error45) {
|
|
17863
|
-
throw new Error(`Failed to read resource at ${
|
|
17876
|
+
throw new Error(`Failed to read resource at ${resolvedResource.absolutePath}: ${error45 instanceof Error ? error45.message : String(error45)}`);
|
|
17864
17877
|
}
|
|
17865
17878
|
};
|
|
17866
17879
|
}
|
|
@@ -17886,7 +17899,130 @@ function createSkillResourceReader(provider) {
|
|
|
17886
17899
|
};
|
|
17887
17900
|
}
|
|
17888
17901
|
|
|
17902
|
+
// src/lib/SkillLinks.ts
|
|
17903
|
+
import path2 from "path";
|
|
17904
|
+
var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/i;
|
|
17905
|
+
function normalizeLinkedSkillPath(target) {
|
|
17906
|
+
const trimmedTarget = target.trim();
|
|
17907
|
+
if (trimmedTarget.length === 0 || trimmedTarget.startsWith("#") || trimmedTarget.startsWith("/") || SCHEME_PATTERN.test(trimmedTarget)) {
|
|
17908
|
+
return null;
|
|
17909
|
+
}
|
|
17910
|
+
const [pathWithoutFragment] = trimmedTarget.split("#", 1);
|
|
17911
|
+
const [pathWithoutQuery] = pathWithoutFragment.split("?", 1);
|
|
17912
|
+
const normalizedPath = path2.posix.normalize(pathWithoutQuery.replace(/\\/g, "/")).replace(/^\.\//, "");
|
|
17913
|
+
if (normalizedPath.length === 0 || normalizedPath === "." || normalizedPath.startsWith("../") || normalizedPath.includes("/../")) {
|
|
17914
|
+
return null;
|
|
17915
|
+
}
|
|
17916
|
+
return normalizedPath;
|
|
17917
|
+
}
|
|
17918
|
+
function extractSkillLinks(content) {
|
|
17919
|
+
const links = new Map;
|
|
17920
|
+
for (const match of content.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)) {
|
|
17921
|
+
const label = match[1]?.trim();
|
|
17922
|
+
const originalPath = match[2]?.trim();
|
|
17923
|
+
if (!label || !originalPath) {
|
|
17924
|
+
continue;
|
|
17925
|
+
}
|
|
17926
|
+
const resourcePath = normalizeLinkedSkillPath(originalPath);
|
|
17927
|
+
if (!resourcePath) {
|
|
17928
|
+
continue;
|
|
17929
|
+
}
|
|
17930
|
+
links.set(`${label}:${resourcePath}`, {
|
|
17931
|
+
label,
|
|
17932
|
+
originalPath,
|
|
17933
|
+
resourcePath
|
|
17934
|
+
});
|
|
17935
|
+
}
|
|
17936
|
+
for (const match of content.matchAll(/(^|[^a-zA-Z0-9])@([a-zA-Z0-9_.\-/]+(?:\/[a-zA-Z0-9_.\-/]+)*)/g)) {
|
|
17937
|
+
const originalPath = match[2]?.trim();
|
|
17938
|
+
if (!originalPath) {
|
|
17939
|
+
continue;
|
|
17940
|
+
}
|
|
17941
|
+
const resourcePath = normalizeLinkedSkillPath(originalPath);
|
|
17942
|
+
if (!resourcePath) {
|
|
17943
|
+
continue;
|
|
17944
|
+
}
|
|
17945
|
+
links.set(`@:${resourcePath}`, {
|
|
17946
|
+
label: `@${resourcePath}`,
|
|
17947
|
+
originalPath: `@${originalPath}`,
|
|
17948
|
+
resourcePath
|
|
17949
|
+
});
|
|
17950
|
+
}
|
|
17951
|
+
return Array.from(links.values());
|
|
17952
|
+
}
|
|
17953
|
+
|
|
17954
|
+
// src/lib/formatLoadedSkill.ts
|
|
17955
|
+
function formatLoadedSkill(args) {
|
|
17956
|
+
const linkedResources = extractSkillLinks(args.skill.content);
|
|
17957
|
+
const output = [
|
|
17958
|
+
`## Skill: ${args.skill.name}`,
|
|
17959
|
+
"",
|
|
17960
|
+
`**Skill identifier**: ${args.skill.toolName}`,
|
|
17961
|
+
`**Base directory**: ${args.skill.fullPath}`
|
|
17962
|
+
];
|
|
17963
|
+
if (args.invocationName) {
|
|
17964
|
+
output.push(`**Invocation**: /${args.invocationName}`);
|
|
17965
|
+
}
|
|
17966
|
+
if (args.userMessage?.trim()) {
|
|
17967
|
+
output.push(`**User request**: ${args.userMessage.trim()}`);
|
|
17968
|
+
}
|
|
17969
|
+
output.push("", "Relative file references in this skill resolve from the skill root directory.");
|
|
17970
|
+
output.push("Use skill_resource with the exact root-relative path when you need a linked file.");
|
|
17971
|
+
if (linkedResources.length > 0) {
|
|
17972
|
+
output.push("", "### Linked files", "");
|
|
17973
|
+
for (const link of linkedResources) {
|
|
17974
|
+
output.push(`- ${link.originalPath} -> ${link.resourcePath}`);
|
|
17975
|
+
}
|
|
17976
|
+
}
|
|
17977
|
+
output.push("", args.skill.content);
|
|
17978
|
+
return output.join(`
|
|
17979
|
+
`);
|
|
17980
|
+
}
|
|
17981
|
+
|
|
17982
|
+
// src/tools/Skill.ts
|
|
17983
|
+
function normalizeSkillSelector(selector) {
|
|
17984
|
+
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
17985
|
+
}
|
|
17986
|
+
function findSkill(registry2, selector) {
|
|
17987
|
+
const directMatch = registry2.controller.get(selector);
|
|
17988
|
+
if (directMatch) {
|
|
17989
|
+
return directMatch;
|
|
17990
|
+
}
|
|
17991
|
+
const normalizedSelector = normalizeSkillSelector(selector);
|
|
17992
|
+
for (const skill of registry2.controller.skills) {
|
|
17993
|
+
if (normalizeSkillSelector(skill.name) === normalizedSelector || normalizeSkillSelector(skill.toolName) === normalizedSelector) {
|
|
17994
|
+
return skill;
|
|
17995
|
+
}
|
|
17996
|
+
}
|
|
17997
|
+
return null;
|
|
17998
|
+
}
|
|
17999
|
+
function createSkillTool(registry2) {
|
|
18000
|
+
return tool({
|
|
18001
|
+
description: "Load a dynamic skill by exact name or slash alias without preloading the entire skill catalog. Use skill_find or skill_recommend first if you do not know the skill name.",
|
|
18002
|
+
args: {
|
|
18003
|
+
name: tool.schema.string().describe("The skill name. Use without the leading slash, for example bmad-help."),
|
|
18004
|
+
user_message: tool.schema.string().optional().describe("Optional user request or arguments to apply with the skill.")
|
|
18005
|
+
},
|
|
18006
|
+
async execute(args) {
|
|
18007
|
+
await registry2.controller.ready.whenReady();
|
|
18008
|
+
const skill = findSkill(registry2, args.name.replace(/^\//, ""));
|
|
18009
|
+
if (!skill) {
|
|
18010
|
+
const available = registry2.controller.skills.map((entry) => entry.name).join(", ");
|
|
18011
|
+
throw new Error(`Skill "${args.name}" not found. Available: ${available || "none"}`);
|
|
18012
|
+
}
|
|
18013
|
+
return formatLoadedSkill({
|
|
18014
|
+
skill,
|
|
18015
|
+
invocationName: args.name.replace(/^\//, ""),
|
|
18016
|
+
userMessage: args.user_message
|
|
18017
|
+
});
|
|
18018
|
+
}
|
|
18019
|
+
});
|
|
18020
|
+
}
|
|
18021
|
+
|
|
17889
18022
|
// src/tools/SkillUser.ts
|
|
18023
|
+
function normalizeSkillSelector2(selector) {
|
|
18024
|
+
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
18025
|
+
}
|
|
17890
18026
|
function createSkillLoader(provider) {
|
|
17891
18027
|
const registry2 = provider.controller;
|
|
17892
18028
|
async function loadSkills(skillNames) {
|
|
@@ -17894,7 +18030,8 @@ function createSkillLoader(provider) {
|
|
|
17894
18030
|
const loaded = [];
|
|
17895
18031
|
const notFound = [];
|
|
17896
18032
|
for (const name of skillNames) {
|
|
17897
|
-
const
|
|
18033
|
+
const normalizedName = normalizeSkillSelector2(name);
|
|
18034
|
+
const skill = registry2.get(name) ?? registry2.skills.find((candidate) => normalizeSkillSelector2(candidate.name) === normalizedName || normalizeSkillSelector2(candidate.toolName) === normalizedName);
|
|
17898
18035
|
if (skill) {
|
|
17899
18036
|
loaded.push(skill);
|
|
17900
18037
|
} else {
|
|
@@ -17920,7 +18057,8 @@ var createApi = async (config2) => {
|
|
|
17920
18057
|
findSkills: createSkillFinder(registry2),
|
|
17921
18058
|
recommendSkills: createSkillRecommender(registry2),
|
|
17922
18059
|
readResource: createSkillResourceReader(registry2),
|
|
17923
|
-
loadSkill: createSkillLoader(registry2)
|
|
18060
|
+
loadSkill: createSkillLoader(registry2),
|
|
18061
|
+
skillTool: createSkillTool(registry2)
|
|
17924
18062
|
};
|
|
17925
18063
|
};
|
|
17926
18064
|
|
|
@@ -18386,10 +18524,10 @@ async function loadConfig({
|
|
|
18386
18524
|
var defaultConfigDir = resolve(process3.cwd(), "config");
|
|
18387
18525
|
var defaultGeneratedDir = resolve(process3.cwd(), "src/generated");
|
|
18388
18526
|
function getProjectRoot(filePath, options2 = {}) {
|
|
18389
|
-
let
|
|
18390
|
-
while (
|
|
18391
|
-
|
|
18392
|
-
const finalPath = resolve2(
|
|
18527
|
+
let path3 = process2.cwd();
|
|
18528
|
+
while (path3.includes("storage"))
|
|
18529
|
+
path3 = resolve2(path3, "..");
|
|
18530
|
+
const finalPath = resolve2(path3, filePath || "");
|
|
18393
18531
|
if (options2?.relative)
|
|
18394
18532
|
return relative3(process2.cwd(), finalPath);
|
|
18395
18533
|
return finalPath;
|
|
@@ -19836,10 +19974,10 @@ function applyEnvVarsToConfig(name, config3, verbose = false) {
|
|
|
19836
19974
|
return config3;
|
|
19837
19975
|
const envPrefix = name.toUpperCase().replace(/-/g, "_");
|
|
19838
19976
|
const result = { ...config3 };
|
|
19839
|
-
function processObject(obj,
|
|
19977
|
+
function processObject(obj, path3 = []) {
|
|
19840
19978
|
const result2 = { ...obj };
|
|
19841
19979
|
for (const [key, value] of Object.entries(obj)) {
|
|
19842
|
-
const envPath = [...
|
|
19980
|
+
const envPath = [...path3, key];
|
|
19843
19981
|
const formatKey = (k) => k.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
19844
19982
|
const envKey = `${envPrefix}_${envPath.map(formatKey).join("_")}`;
|
|
19845
19983
|
const oldEnvKey = `${envPrefix}_${envPath.map((p) => p.toUpperCase()).join("_")}`;
|
|
@@ -20021,10 +20159,10 @@ async function loadConfig3({
|
|
|
20021
20159
|
var defaultConfigDir2 = resolve3(process6.cwd(), "config");
|
|
20022
20160
|
var defaultGeneratedDir2 = resolve3(process6.cwd(), "src/generated");
|
|
20023
20161
|
function getProjectRoot2(filePath, options2 = {}) {
|
|
20024
|
-
let
|
|
20025
|
-
while (
|
|
20026
|
-
|
|
20027
|
-
const finalPath = resolve4(
|
|
20162
|
+
let path3 = process7.cwd();
|
|
20163
|
+
while (path3.includes("storage"))
|
|
20164
|
+
path3 = resolve4(path3, "..");
|
|
20165
|
+
const finalPath = resolve4(path3, filePath || "");
|
|
20028
20166
|
if (options2?.relative)
|
|
20029
20167
|
return relative2(process7.cwd(), finalPath);
|
|
20030
20168
|
return finalPath;
|
|
@@ -21461,10 +21599,10 @@ class EnvVarError extends BunfigError {
|
|
|
21461
21599
|
|
|
21462
21600
|
class FileSystemError extends BunfigError {
|
|
21463
21601
|
code = "FILE_SYSTEM_ERROR";
|
|
21464
|
-
constructor(operation,
|
|
21465
|
-
super(`File system ${operation} failed for "${
|
|
21602
|
+
constructor(operation, path3, cause) {
|
|
21603
|
+
super(`File system ${operation} failed for "${path3}": ${cause.message}`, {
|
|
21466
21604
|
operation,
|
|
21467
|
-
path:
|
|
21605
|
+
path: path3,
|
|
21468
21606
|
originalError: cause.name,
|
|
21469
21607
|
originalMessage: cause.message
|
|
21470
21608
|
});
|
|
@@ -21537,8 +21675,8 @@ var ErrorFactory = {
|
|
|
21537
21675
|
envVar(envKey, envValue, expectedType, configName) {
|
|
21538
21676
|
return new EnvVarError(envKey, envValue, expectedType, configName);
|
|
21539
21677
|
},
|
|
21540
|
-
fileSystem(operation,
|
|
21541
|
-
return new FileSystemError(operation,
|
|
21678
|
+
fileSystem(operation, path3, cause) {
|
|
21679
|
+
return new FileSystemError(operation, path3, cause);
|
|
21542
21680
|
},
|
|
21543
21681
|
typeGeneration(configDir, outputPath, cause) {
|
|
21544
21682
|
return new TypeGenerationError(configDir, outputPath, cause);
|
|
@@ -21674,9 +21812,9 @@ class EnvProcessor {
|
|
|
21674
21812
|
}
|
|
21675
21813
|
return key.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
21676
21814
|
}
|
|
21677
|
-
processObject(obj,
|
|
21815
|
+
processObject(obj, path3, envPrefix, options2) {
|
|
21678
21816
|
for (const [key, value] of Object.entries(obj)) {
|
|
21679
|
-
const envPath = [...
|
|
21817
|
+
const envPath = [...path3, key];
|
|
21680
21818
|
const formattedKeys = envPath.map((k) => this.formatEnvKey(k, options2.useCamelCase));
|
|
21681
21819
|
const envKey = `${envPrefix}_${formattedKeys.join("_")}`;
|
|
21682
21820
|
const oldEnvKey = options2.useBackwardCompatibility ? `${envPrefix}_${envPath.map((p) => p.toUpperCase()).join("_")}` : null;
|
|
@@ -21761,9 +21899,9 @@ class EnvProcessor {
|
|
|
21761
21899
|
return this.formatAsText(envVars, configName);
|
|
21762
21900
|
}
|
|
21763
21901
|
}
|
|
21764
|
-
extractEnvVarInfo(obj,
|
|
21902
|
+
extractEnvVarInfo(obj, path3, prefix, envVars) {
|
|
21765
21903
|
for (const [key, value] of Object.entries(obj)) {
|
|
21766
|
-
const envPath = [...
|
|
21904
|
+
const envPath = [...path3, key];
|
|
21767
21905
|
const envKey = `${prefix}_${envPath.map((k) => this.formatEnvKey(k, true)).join("_")}`;
|
|
21768
21906
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
21769
21907
|
this.extractEnvVarInfo(value, envPath, prefix, envVars);
|
|
@@ -22178,15 +22316,15 @@ class ConfigFileLoader {
|
|
|
22178
22316
|
}
|
|
22179
22317
|
async preloadConfigurations(configPaths, options2 = {}) {
|
|
22180
22318
|
const preloaded = new Map;
|
|
22181
|
-
await Promise.allSettled(configPaths.map(async (
|
|
22319
|
+
await Promise.allSettled(configPaths.map(async (path3) => {
|
|
22182
22320
|
try {
|
|
22183
|
-
const result = await this.loadFromPath(
|
|
22321
|
+
const result = await this.loadFromPath(path3, {}, options2);
|
|
22184
22322
|
if (result) {
|
|
22185
|
-
preloaded.set(
|
|
22323
|
+
preloaded.set(path3, result.config);
|
|
22186
22324
|
}
|
|
22187
22325
|
} catch (error45) {
|
|
22188
22326
|
if (options2.verbose) {
|
|
22189
|
-
console.warn(`Failed to preload ${
|
|
22327
|
+
console.warn(`Failed to preload ${path3}:`, error45);
|
|
22190
22328
|
}
|
|
22191
22329
|
}
|
|
22192
22330
|
}));
|
|
@@ -22265,13 +22403,13 @@ class ConfigValidator {
|
|
|
22265
22403
|
warnings
|
|
22266
22404
|
};
|
|
22267
22405
|
}
|
|
22268
|
-
validateObjectAgainstSchema(value, schema,
|
|
22406
|
+
validateObjectAgainstSchema(value, schema, path3, errors3, warnings, options2) {
|
|
22269
22407
|
if (options2.validateTypes && schema.type) {
|
|
22270
22408
|
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
22271
22409
|
const expectedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
22272
22410
|
if (!expectedTypes.includes(actualType)) {
|
|
22273
22411
|
errors3.push({
|
|
22274
|
-
path:
|
|
22412
|
+
path: path3,
|
|
22275
22413
|
message: `Expected type ${expectedTypes.join(" or ")}, got ${actualType}`,
|
|
22276
22414
|
expected: expectedTypes.join(" or "),
|
|
22277
22415
|
actual: actualType,
|
|
@@ -22283,7 +22421,7 @@ class ConfigValidator {
|
|
|
22283
22421
|
}
|
|
22284
22422
|
if (schema.enum && !schema.enum.includes(value)) {
|
|
22285
22423
|
errors3.push({
|
|
22286
|
-
path:
|
|
22424
|
+
path: path3,
|
|
22287
22425
|
message: `Value must be one of: ${schema.enum.join(", ")}`,
|
|
22288
22426
|
expected: schema.enum.join(", "),
|
|
22289
22427
|
actual: value,
|
|
@@ -22295,7 +22433,7 @@ class ConfigValidator {
|
|
|
22295
22433
|
if (typeof value === "string") {
|
|
22296
22434
|
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
22297
22435
|
errors3.push({
|
|
22298
|
-
path:
|
|
22436
|
+
path: path3,
|
|
22299
22437
|
message: `String length must be at least ${schema.minLength}`,
|
|
22300
22438
|
expected: `>= ${schema.minLength}`,
|
|
22301
22439
|
actual: value.length,
|
|
@@ -22304,7 +22442,7 @@ class ConfigValidator {
|
|
|
22304
22442
|
}
|
|
22305
22443
|
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
22306
22444
|
errors3.push({
|
|
22307
|
-
path:
|
|
22445
|
+
path: path3,
|
|
22308
22446
|
message: `String length must not exceed ${schema.maxLength}`,
|
|
22309
22447
|
expected: `<= ${schema.maxLength}`,
|
|
22310
22448
|
actual: value.length,
|
|
@@ -22315,7 +22453,7 @@ class ConfigValidator {
|
|
|
22315
22453
|
const regex = new RegExp(schema.pattern);
|
|
22316
22454
|
if (!regex.test(value)) {
|
|
22317
22455
|
errors3.push({
|
|
22318
|
-
path:
|
|
22456
|
+
path: path3,
|
|
22319
22457
|
message: `String does not match pattern ${schema.pattern}`,
|
|
22320
22458
|
expected: schema.pattern,
|
|
22321
22459
|
actual: value,
|
|
@@ -22327,7 +22465,7 @@ class ConfigValidator {
|
|
|
22327
22465
|
if (typeof value === "number") {
|
|
22328
22466
|
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
22329
22467
|
errors3.push({
|
|
22330
|
-
path:
|
|
22468
|
+
path: path3,
|
|
22331
22469
|
message: `Value must be at least ${schema.minimum}`,
|
|
22332
22470
|
expected: `>= ${schema.minimum}`,
|
|
22333
22471
|
actual: value,
|
|
@@ -22336,7 +22474,7 @@ class ConfigValidator {
|
|
|
22336
22474
|
}
|
|
22337
22475
|
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
22338
22476
|
errors3.push({
|
|
22339
|
-
path:
|
|
22477
|
+
path: path3,
|
|
22340
22478
|
message: `Value must not exceed ${schema.maximum}`,
|
|
22341
22479
|
expected: `<= ${schema.maximum}`,
|
|
22342
22480
|
actual: value,
|
|
@@ -22346,7 +22484,7 @@ class ConfigValidator {
|
|
|
22346
22484
|
}
|
|
22347
22485
|
if (Array.isArray(value) && schema.items) {
|
|
22348
22486
|
for (let i = 0;i < value.length; i++) {
|
|
22349
|
-
const itemPath =
|
|
22487
|
+
const itemPath = path3 ? `${path3}[${i}]` : `[${i}]`;
|
|
22350
22488
|
this.validateObjectAgainstSchema(value[i], schema.items, itemPath, errors3, warnings, options2);
|
|
22351
22489
|
if (options2.stopOnFirstError && errors3.length > 0)
|
|
22352
22490
|
return;
|
|
@@ -22358,7 +22496,7 @@ class ConfigValidator {
|
|
|
22358
22496
|
for (const requiredProp of schema.required) {
|
|
22359
22497
|
if (!(requiredProp in obj)) {
|
|
22360
22498
|
errors3.push({
|
|
22361
|
-
path:
|
|
22499
|
+
path: path3 ? `${path3}.${requiredProp}` : requiredProp,
|
|
22362
22500
|
message: `Missing required property '${requiredProp}'`,
|
|
22363
22501
|
expected: "required",
|
|
22364
22502
|
rule: "required"
|
|
@@ -22371,7 +22509,7 @@ class ConfigValidator {
|
|
|
22371
22509
|
if (schema.properties) {
|
|
22372
22510
|
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
22373
22511
|
if (propName in obj) {
|
|
22374
|
-
const propPath =
|
|
22512
|
+
const propPath = path3 ? `${path3}.${propName}` : propName;
|
|
22375
22513
|
this.validateObjectAgainstSchema(obj[propName], propSchema, propPath, errors3, warnings, options2);
|
|
22376
22514
|
if (options2.stopOnFirstError && errors3.length > 0)
|
|
22377
22515
|
return;
|
|
@@ -22383,7 +22521,7 @@ class ConfigValidator {
|
|
|
22383
22521
|
for (const propName of Object.keys(obj)) {
|
|
22384
22522
|
if (!allowedProps.has(propName)) {
|
|
22385
22523
|
warnings.push({
|
|
22386
|
-
path:
|
|
22524
|
+
path: path3 ? `${path3}.${propName}` : propName,
|
|
22387
22525
|
message: `Additional property '${propName}' is not allowed`,
|
|
22388
22526
|
rule: "additionalProperties"
|
|
22389
22527
|
});
|
|
@@ -22417,12 +22555,12 @@ class ConfigValidator {
|
|
|
22417
22555
|
warnings
|
|
22418
22556
|
};
|
|
22419
22557
|
}
|
|
22420
|
-
validateWithRule(value, rule,
|
|
22558
|
+
validateWithRule(value, rule, path3) {
|
|
22421
22559
|
const errors3 = [];
|
|
22422
22560
|
if (rule.required && (value === undefined || value === null)) {
|
|
22423
22561
|
errors3.push({
|
|
22424
|
-
path:
|
|
22425
|
-
message: rule.message || `Property '${
|
|
22562
|
+
path: path3,
|
|
22563
|
+
message: rule.message || `Property '${path3}' is required`,
|
|
22426
22564
|
expected: "required",
|
|
22427
22565
|
rule: "required"
|
|
22428
22566
|
});
|
|
@@ -22435,7 +22573,7 @@ class ConfigValidator {
|
|
|
22435
22573
|
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
22436
22574
|
if (actualType !== rule.type) {
|
|
22437
22575
|
errors3.push({
|
|
22438
|
-
path:
|
|
22576
|
+
path: path3,
|
|
22439
22577
|
message: rule.message || `Expected type ${rule.type}, got ${actualType}`,
|
|
22440
22578
|
expected: rule.type,
|
|
22441
22579
|
actual: actualType,
|
|
@@ -22447,7 +22585,7 @@ class ConfigValidator {
|
|
|
22447
22585
|
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
22448
22586
|
if (length < rule.min) {
|
|
22449
22587
|
errors3.push({
|
|
22450
|
-
path:
|
|
22588
|
+
path: path3,
|
|
22451
22589
|
message: rule.message || `Value must be at least ${rule.min}`,
|
|
22452
22590
|
expected: `>= ${rule.min}`,
|
|
22453
22591
|
actual: length,
|
|
@@ -22459,7 +22597,7 @@ class ConfigValidator {
|
|
|
22459
22597
|
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
22460
22598
|
if (length > rule.max) {
|
|
22461
22599
|
errors3.push({
|
|
22462
|
-
path:
|
|
22600
|
+
path: path3,
|
|
22463
22601
|
message: rule.message || `Value must not exceed ${rule.max}`,
|
|
22464
22602
|
expected: `<= ${rule.max}`,
|
|
22465
22603
|
actual: length,
|
|
@@ -22470,7 +22608,7 @@ class ConfigValidator {
|
|
|
22470
22608
|
if (rule.pattern && typeof value === "string") {
|
|
22471
22609
|
if (!rule.pattern.test(value)) {
|
|
22472
22610
|
errors3.push({
|
|
22473
|
-
path:
|
|
22611
|
+
path: path3,
|
|
22474
22612
|
message: rule.message || `Value does not match pattern ${rule.pattern}`,
|
|
22475
22613
|
expected: rule.pattern.toString(),
|
|
22476
22614
|
actual: value,
|
|
@@ -22480,7 +22618,7 @@ class ConfigValidator {
|
|
|
22480
22618
|
}
|
|
22481
22619
|
if (rule.enum && !rule.enum.includes(value)) {
|
|
22482
22620
|
errors3.push({
|
|
22483
|
-
path:
|
|
22621
|
+
path: path3,
|
|
22484
22622
|
message: rule.message || `Value must be one of: ${rule.enum.join(", ")}`,
|
|
22485
22623
|
expected: rule.enum.join(", "),
|
|
22486
22624
|
actual: value,
|
|
@@ -22491,7 +22629,7 @@ class ConfigValidator {
|
|
|
22491
22629
|
const customError = rule.validator(value);
|
|
22492
22630
|
if (customError) {
|
|
22493
22631
|
errors3.push({
|
|
22494
|
-
path:
|
|
22632
|
+
path: path3,
|
|
22495
22633
|
message: rule.message || customError,
|
|
22496
22634
|
rule: "custom"
|
|
22497
22635
|
});
|
|
@@ -22499,10 +22637,10 @@ class ConfigValidator {
|
|
|
22499
22637
|
}
|
|
22500
22638
|
return errors3;
|
|
22501
22639
|
}
|
|
22502
|
-
getValueByPath(obj,
|
|
22503
|
-
if (!
|
|
22640
|
+
getValueByPath(obj, path3) {
|
|
22641
|
+
if (!path3)
|
|
22504
22642
|
return obj;
|
|
22505
|
-
const keys =
|
|
22643
|
+
const keys = path3.split(".");
|
|
22506
22644
|
let current = obj;
|
|
22507
22645
|
for (const key of keys) {
|
|
22508
22646
|
if (current && typeof current === "object" && key in current) {
|
|
@@ -22928,10 +23066,10 @@ async function loadConfig5(options2) {
|
|
|
22928
23066
|
function applyEnvVarsToConfig2(name, config4, verbose = false) {
|
|
22929
23067
|
const _envProcessor = new EnvProcessor;
|
|
22930
23068
|
const envPrefix = name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
22931
|
-
function processConfigLevel(obj,
|
|
23069
|
+
function processConfigLevel(obj, path3 = []) {
|
|
22932
23070
|
const result = { ...obj };
|
|
22933
23071
|
for (const [key, value] of Object.entries(obj)) {
|
|
22934
|
-
const currentPath = [...
|
|
23072
|
+
const currentPath = [...path3, key];
|
|
22935
23073
|
const envKeys = [
|
|
22936
23074
|
`${envPrefix}_${currentPath.join("_").toUpperCase()}`,
|
|
22937
23075
|
`${envPrefix}_${currentPath.map((k) => k.toUpperCase()).join("")}`,
|
|
@@ -22997,14 +23135,14 @@ function getOpenCodeConfigPaths() {
|
|
|
22997
23135
|
paths.push(join4(home, ".opencode"));
|
|
22998
23136
|
return paths;
|
|
22999
23137
|
}
|
|
23000
|
-
function expandTildePath(
|
|
23001
|
-
if (
|
|
23138
|
+
function expandTildePath(path3) {
|
|
23139
|
+
if (path3 === "~") {
|
|
23002
23140
|
return homedir3();
|
|
23003
23141
|
}
|
|
23004
|
-
if (
|
|
23005
|
-
return join4(homedir3(),
|
|
23142
|
+
if (path3.startsWith("~/")) {
|
|
23143
|
+
return join4(homedir3(), path3.slice(2));
|
|
23006
23144
|
}
|
|
23007
|
-
return
|
|
23145
|
+
return path3;
|
|
23008
23146
|
}
|
|
23009
23147
|
var createPathKey = (absolutePath) => {
|
|
23010
23148
|
const normalizedPath = normalize(absolutePath);
|
|
@@ -23077,7 +23215,7 @@ var options2 = {
|
|
|
23077
23215
|
promptRenderer: "xml",
|
|
23078
23216
|
modelRenderers: {},
|
|
23079
23217
|
slashCommandName: "skill",
|
|
23080
|
-
enableSkillAliases:
|
|
23218
|
+
enableSkillAliases: true
|
|
23081
23219
|
}
|
|
23082
23220
|
};
|
|
23083
23221
|
async function getPluginConfig(ctx) {
|
|
@@ -23093,7 +23231,7 @@ async function getPluginConfig(ctx) {
|
|
|
23093
23231
|
// src/commands/SlashCommand.ts
|
|
23094
23232
|
var SLASH_COMMAND_SENTINEL = "<!-- opencode-dynamic-skills:slash-expanded -->";
|
|
23095
23233
|
var RECOMMEND_COMMAND_SENTINEL = "<!-- opencode-dynamic-skills:skill-recommend-expanded -->";
|
|
23096
|
-
function
|
|
23234
|
+
function normalizeSkillSelector3(selector) {
|
|
23097
23235
|
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
23098
23236
|
}
|
|
23099
23237
|
function parseSlashCommand(text, slashCommandName) {
|
|
@@ -23130,30 +23268,26 @@ function findSkillBySelector(registry2, selector) {
|
|
|
23130
23268
|
if (directMatch) {
|
|
23131
23269
|
return directMatch;
|
|
23132
23270
|
}
|
|
23133
|
-
const normalizedSelector =
|
|
23271
|
+
const normalizedSelector = normalizeSkillSelector3(selector);
|
|
23134
23272
|
for (const skill of registry2.controller.skills) {
|
|
23135
|
-
if (
|
|
23273
|
+
if (normalizeSkillSelector3(skill.toolName) === normalizedSelector) {
|
|
23136
23274
|
return skill;
|
|
23137
23275
|
}
|
|
23138
|
-
if (
|
|
23276
|
+
if (normalizeSkillSelector3(skill.name) === normalizedSelector) {
|
|
23139
23277
|
return skill;
|
|
23140
23278
|
}
|
|
23141
23279
|
}
|
|
23142
23280
|
return null;
|
|
23143
23281
|
}
|
|
23144
23282
|
function renderSlashSkillPrompt(args) {
|
|
23145
|
-
const userPrompt = args.userPrompt || "Apply this skill to the current request.";
|
|
23146
23283
|
return [
|
|
23147
23284
|
SLASH_COMMAND_SENTINEL,
|
|
23148
|
-
|
|
23149
|
-
|
|
23150
|
-
|
|
23151
|
-
|
|
23152
|
-
|
|
23153
|
-
|
|
23154
|
-
`User request: ${userPrompt}`,
|
|
23155
|
-
"",
|
|
23156
|
-
args.renderedSkill
|
|
23285
|
+
formatLoadedSkill({
|
|
23286
|
+
invocationName: args.invocationName,
|
|
23287
|
+
skill: args.skill,
|
|
23288
|
+
userMessage: args.userPrompt || "Apply this skill to the current request."
|
|
23289
|
+
}),
|
|
23290
|
+
""
|
|
23157
23291
|
].join(`
|
|
23158
23292
|
`);
|
|
23159
23293
|
}
|
|
@@ -23201,7 +23335,6 @@ async function rewriteSlashCommandText(args) {
|
|
|
23201
23335
|
}
|
|
23202
23336
|
return renderSlashSkillPrompt({
|
|
23203
23337
|
invocationName: parsedCommand.invocationName,
|
|
23204
|
-
renderedSkill: args.renderSkill(skill),
|
|
23205
23338
|
skill,
|
|
23206
23339
|
userPrompt: parsedCommand.userPrompt
|
|
23207
23340
|
});
|
|
@@ -23212,6 +23345,15 @@ var createJsonPromptRenderer = () => {
|
|
|
23212
23345
|
const renderer = {
|
|
23213
23346
|
format: "json",
|
|
23214
23347
|
render(args) {
|
|
23348
|
+
if (args.type === "Skill") {
|
|
23349
|
+
return JSON.stringify({
|
|
23350
|
+
Skill: {
|
|
23351
|
+
...args.data,
|
|
23352
|
+
linkedResources: extractSkillLinks(args.data.content),
|
|
23353
|
+
skillRootInstruction: "Resolve linked files relative to the skill root and use skill_resource with the exact root-relative path."
|
|
23354
|
+
}
|
|
23355
|
+
}, null, 2);
|
|
23356
|
+
}
|
|
23215
23357
|
return JSON.stringify({ [args.type]: args.data }, null, 2);
|
|
23216
23358
|
}
|
|
23217
23359
|
};
|
|
@@ -23269,6 +23411,8 @@ var createXmlPromptRenderer = () => {
|
|
|
23269
23411
|
const prepareSkill = (skill) => {
|
|
23270
23412
|
return {
|
|
23271
23413
|
...skill,
|
|
23414
|
+
linkedResources: extractSkillLinks(skill.content),
|
|
23415
|
+
skillRootInstruction: "Resolve linked files relative to the skill root and use skill_resource with the exact root-relative path.",
|
|
23272
23416
|
references: resourceMapToArray(skill.references),
|
|
23273
23417
|
scripts: resourceMapToArray(skill.scripts),
|
|
23274
23418
|
assets: resourceMapToArray(skill.assets)
|
|
@@ -23490,6 +23634,7 @@ var createMdPromptRenderer = () => {
|
|
|
23490
23634
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
23491
23635
|
};
|
|
23492
23636
|
const renderSkill = (skill) => {
|
|
23637
|
+
const linkedResources = extractSkillLinks(skill.content);
|
|
23493
23638
|
return dedent_default`
|
|
23494
23639
|
# ${skill.name}
|
|
23495
23640
|
|
|
@@ -23497,6 +23642,14 @@ var createMdPromptRenderer = () => {
|
|
|
23497
23642
|
> ${skill.fullPath}
|
|
23498
23643
|
|
|
23499
23644
|
Relative file references in this skill resolve from the skill root directory.
|
|
23645
|
+
Always use skill_resource with the exact root-relative path for linked files.
|
|
23646
|
+
|
|
23647
|
+
${linkedResources.length > 0 ? dedent_default`
|
|
23648
|
+
## Linked files detected in skill content
|
|
23649
|
+
|
|
23650
|
+
${linkedResources.map((link) => `- [${link.label}](${link.originalPath}) \u2192 use skill_resource with ${link.resourcePath}`).join(`
|
|
23651
|
+
`)}
|
|
23652
|
+
` : ""}
|
|
23500
23653
|
|
|
23501
23654
|
${skill.content}
|
|
23502
23655
|
|
|
@@ -23645,7 +23798,7 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23645
23798
|
providerId: input.model?.providerID,
|
|
23646
23799
|
config: config3
|
|
23647
23800
|
});
|
|
23648
|
-
|
|
23801
|
+
promptRenderer.getFormatter(format);
|
|
23649
23802
|
for (const part of output.parts) {
|
|
23650
23803
|
if (part.type !== "text") {
|
|
23651
23804
|
continue;
|
|
@@ -23658,7 +23811,6 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23658
23811
|
const rewrittenText = await rewriteSlashCommandText({
|
|
23659
23812
|
text: part.text,
|
|
23660
23813
|
registry: api2.registry,
|
|
23661
|
-
renderSkill: (skill) => renderSkill({ data: skill, type: "Skill" }),
|
|
23662
23814
|
slashCommandName: config3.slashCommandName,
|
|
23663
23815
|
enableSkillAliases: config3.enableSkillAliases
|
|
23664
23816
|
});
|
|
@@ -23680,6 +23832,7 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23680
23832
|
}
|
|
23681
23833
|
},
|
|
23682
23834
|
tool: {
|
|
23835
|
+
skill: api2.skillTool,
|
|
23683
23836
|
skill_use: tool({
|
|
23684
23837
|
description: "Load one or more skills into the chat. Provide an array of skill names to load them as user messages.",
|
|
23685
23838
|
args: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-dynamic-skills",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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": {
|