opencode-dynamic-skills 1.0.2 → 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 +194 -111
- 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
|
@@ -17899,7 +17899,130 @@ function createSkillResourceReader(provider) {
|
|
|
17899
17899
|
};
|
|
17900
17900
|
}
|
|
17901
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
|
+
|
|
17902
18022
|
// src/tools/SkillUser.ts
|
|
18023
|
+
function normalizeSkillSelector2(selector) {
|
|
18024
|
+
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
18025
|
+
}
|
|
17903
18026
|
function createSkillLoader(provider) {
|
|
17904
18027
|
const registry2 = provider.controller;
|
|
17905
18028
|
async function loadSkills(skillNames) {
|
|
@@ -17907,7 +18030,8 @@ function createSkillLoader(provider) {
|
|
|
17907
18030
|
const loaded = [];
|
|
17908
18031
|
const notFound = [];
|
|
17909
18032
|
for (const name of skillNames) {
|
|
17910
|
-
const
|
|
18033
|
+
const normalizedName = normalizeSkillSelector2(name);
|
|
18034
|
+
const skill = registry2.get(name) ?? registry2.skills.find((candidate) => normalizeSkillSelector2(candidate.name) === normalizedName || normalizeSkillSelector2(candidate.toolName) === normalizedName);
|
|
17911
18035
|
if (skill) {
|
|
17912
18036
|
loaded.push(skill);
|
|
17913
18037
|
} else {
|
|
@@ -17933,7 +18057,8 @@ var createApi = async (config2) => {
|
|
|
17933
18057
|
findSkills: createSkillFinder(registry2),
|
|
17934
18058
|
recommendSkills: createSkillRecommender(registry2),
|
|
17935
18059
|
readResource: createSkillResourceReader(registry2),
|
|
17936
|
-
loadSkill: createSkillLoader(registry2)
|
|
18060
|
+
loadSkill: createSkillLoader(registry2),
|
|
18061
|
+
skillTool: createSkillTool(registry2)
|
|
17937
18062
|
};
|
|
17938
18063
|
};
|
|
17939
18064
|
|
|
@@ -18399,10 +18524,10 @@ async function loadConfig({
|
|
|
18399
18524
|
var defaultConfigDir = resolve(process3.cwd(), "config");
|
|
18400
18525
|
var defaultGeneratedDir = resolve(process3.cwd(), "src/generated");
|
|
18401
18526
|
function getProjectRoot(filePath, options2 = {}) {
|
|
18402
|
-
let
|
|
18403
|
-
while (
|
|
18404
|
-
|
|
18405
|
-
const finalPath = resolve2(
|
|
18527
|
+
let path3 = process2.cwd();
|
|
18528
|
+
while (path3.includes("storage"))
|
|
18529
|
+
path3 = resolve2(path3, "..");
|
|
18530
|
+
const finalPath = resolve2(path3, filePath || "");
|
|
18406
18531
|
if (options2?.relative)
|
|
18407
18532
|
return relative3(process2.cwd(), finalPath);
|
|
18408
18533
|
return finalPath;
|
|
@@ -19849,10 +19974,10 @@ function applyEnvVarsToConfig(name, config3, verbose = false) {
|
|
|
19849
19974
|
return config3;
|
|
19850
19975
|
const envPrefix = name.toUpperCase().replace(/-/g, "_");
|
|
19851
19976
|
const result = { ...config3 };
|
|
19852
|
-
function processObject(obj,
|
|
19977
|
+
function processObject(obj, path3 = []) {
|
|
19853
19978
|
const result2 = { ...obj };
|
|
19854
19979
|
for (const [key, value] of Object.entries(obj)) {
|
|
19855
|
-
const envPath = [...
|
|
19980
|
+
const envPath = [...path3, key];
|
|
19856
19981
|
const formatKey = (k) => k.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
19857
19982
|
const envKey = `${envPrefix}_${envPath.map(formatKey).join("_")}`;
|
|
19858
19983
|
const oldEnvKey = `${envPrefix}_${envPath.map((p) => p.toUpperCase()).join("_")}`;
|
|
@@ -20034,10 +20159,10 @@ async function loadConfig3({
|
|
|
20034
20159
|
var defaultConfigDir2 = resolve3(process6.cwd(), "config");
|
|
20035
20160
|
var defaultGeneratedDir2 = resolve3(process6.cwd(), "src/generated");
|
|
20036
20161
|
function getProjectRoot2(filePath, options2 = {}) {
|
|
20037
|
-
let
|
|
20038
|
-
while (
|
|
20039
|
-
|
|
20040
|
-
const finalPath = resolve4(
|
|
20162
|
+
let path3 = process7.cwd();
|
|
20163
|
+
while (path3.includes("storage"))
|
|
20164
|
+
path3 = resolve4(path3, "..");
|
|
20165
|
+
const finalPath = resolve4(path3, filePath || "");
|
|
20041
20166
|
if (options2?.relative)
|
|
20042
20167
|
return relative2(process7.cwd(), finalPath);
|
|
20043
20168
|
return finalPath;
|
|
@@ -21474,10 +21599,10 @@ class EnvVarError extends BunfigError {
|
|
|
21474
21599
|
|
|
21475
21600
|
class FileSystemError extends BunfigError {
|
|
21476
21601
|
code = "FILE_SYSTEM_ERROR";
|
|
21477
|
-
constructor(operation,
|
|
21478
|
-
super(`File system ${operation} failed for "${
|
|
21602
|
+
constructor(operation, path3, cause) {
|
|
21603
|
+
super(`File system ${operation} failed for "${path3}": ${cause.message}`, {
|
|
21479
21604
|
operation,
|
|
21480
|
-
path:
|
|
21605
|
+
path: path3,
|
|
21481
21606
|
originalError: cause.name,
|
|
21482
21607
|
originalMessage: cause.message
|
|
21483
21608
|
});
|
|
@@ -21550,8 +21675,8 @@ var ErrorFactory = {
|
|
|
21550
21675
|
envVar(envKey, envValue, expectedType, configName) {
|
|
21551
21676
|
return new EnvVarError(envKey, envValue, expectedType, configName);
|
|
21552
21677
|
},
|
|
21553
|
-
fileSystem(operation,
|
|
21554
|
-
return new FileSystemError(operation,
|
|
21678
|
+
fileSystem(operation, path3, cause) {
|
|
21679
|
+
return new FileSystemError(operation, path3, cause);
|
|
21555
21680
|
},
|
|
21556
21681
|
typeGeneration(configDir, outputPath, cause) {
|
|
21557
21682
|
return new TypeGenerationError(configDir, outputPath, cause);
|
|
@@ -21687,9 +21812,9 @@ class EnvProcessor {
|
|
|
21687
21812
|
}
|
|
21688
21813
|
return key.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
21689
21814
|
}
|
|
21690
|
-
processObject(obj,
|
|
21815
|
+
processObject(obj, path3, envPrefix, options2) {
|
|
21691
21816
|
for (const [key, value] of Object.entries(obj)) {
|
|
21692
|
-
const envPath = [...
|
|
21817
|
+
const envPath = [...path3, key];
|
|
21693
21818
|
const formattedKeys = envPath.map((k) => this.formatEnvKey(k, options2.useCamelCase));
|
|
21694
21819
|
const envKey = `${envPrefix}_${formattedKeys.join("_")}`;
|
|
21695
21820
|
const oldEnvKey = options2.useBackwardCompatibility ? `${envPrefix}_${envPath.map((p) => p.toUpperCase()).join("_")}` : null;
|
|
@@ -21774,9 +21899,9 @@ class EnvProcessor {
|
|
|
21774
21899
|
return this.formatAsText(envVars, configName);
|
|
21775
21900
|
}
|
|
21776
21901
|
}
|
|
21777
|
-
extractEnvVarInfo(obj,
|
|
21902
|
+
extractEnvVarInfo(obj, path3, prefix, envVars) {
|
|
21778
21903
|
for (const [key, value] of Object.entries(obj)) {
|
|
21779
|
-
const envPath = [...
|
|
21904
|
+
const envPath = [...path3, key];
|
|
21780
21905
|
const envKey = `${prefix}_${envPath.map((k) => this.formatEnvKey(k, true)).join("_")}`;
|
|
21781
21906
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
21782
21907
|
this.extractEnvVarInfo(value, envPath, prefix, envVars);
|
|
@@ -22191,15 +22316,15 @@ class ConfigFileLoader {
|
|
|
22191
22316
|
}
|
|
22192
22317
|
async preloadConfigurations(configPaths, options2 = {}) {
|
|
22193
22318
|
const preloaded = new Map;
|
|
22194
|
-
await Promise.allSettled(configPaths.map(async (
|
|
22319
|
+
await Promise.allSettled(configPaths.map(async (path3) => {
|
|
22195
22320
|
try {
|
|
22196
|
-
const result = await this.loadFromPath(
|
|
22321
|
+
const result = await this.loadFromPath(path3, {}, options2);
|
|
22197
22322
|
if (result) {
|
|
22198
|
-
preloaded.set(
|
|
22323
|
+
preloaded.set(path3, result.config);
|
|
22199
22324
|
}
|
|
22200
22325
|
} catch (error45) {
|
|
22201
22326
|
if (options2.verbose) {
|
|
22202
|
-
console.warn(`Failed to preload ${
|
|
22327
|
+
console.warn(`Failed to preload ${path3}:`, error45);
|
|
22203
22328
|
}
|
|
22204
22329
|
}
|
|
22205
22330
|
}));
|
|
@@ -22278,13 +22403,13 @@ class ConfigValidator {
|
|
|
22278
22403
|
warnings
|
|
22279
22404
|
};
|
|
22280
22405
|
}
|
|
22281
|
-
validateObjectAgainstSchema(value, schema,
|
|
22406
|
+
validateObjectAgainstSchema(value, schema, path3, errors3, warnings, options2) {
|
|
22282
22407
|
if (options2.validateTypes && schema.type) {
|
|
22283
22408
|
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
22284
22409
|
const expectedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
22285
22410
|
if (!expectedTypes.includes(actualType)) {
|
|
22286
22411
|
errors3.push({
|
|
22287
|
-
path:
|
|
22412
|
+
path: path3,
|
|
22288
22413
|
message: `Expected type ${expectedTypes.join(" or ")}, got ${actualType}`,
|
|
22289
22414
|
expected: expectedTypes.join(" or "),
|
|
22290
22415
|
actual: actualType,
|
|
@@ -22296,7 +22421,7 @@ class ConfigValidator {
|
|
|
22296
22421
|
}
|
|
22297
22422
|
if (schema.enum && !schema.enum.includes(value)) {
|
|
22298
22423
|
errors3.push({
|
|
22299
|
-
path:
|
|
22424
|
+
path: path3,
|
|
22300
22425
|
message: `Value must be one of: ${schema.enum.join(", ")}`,
|
|
22301
22426
|
expected: schema.enum.join(", "),
|
|
22302
22427
|
actual: value,
|
|
@@ -22308,7 +22433,7 @@ class ConfigValidator {
|
|
|
22308
22433
|
if (typeof value === "string") {
|
|
22309
22434
|
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
22310
22435
|
errors3.push({
|
|
22311
|
-
path:
|
|
22436
|
+
path: path3,
|
|
22312
22437
|
message: `String length must be at least ${schema.minLength}`,
|
|
22313
22438
|
expected: `>= ${schema.minLength}`,
|
|
22314
22439
|
actual: value.length,
|
|
@@ -22317,7 +22442,7 @@ class ConfigValidator {
|
|
|
22317
22442
|
}
|
|
22318
22443
|
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
22319
22444
|
errors3.push({
|
|
22320
|
-
path:
|
|
22445
|
+
path: path3,
|
|
22321
22446
|
message: `String length must not exceed ${schema.maxLength}`,
|
|
22322
22447
|
expected: `<= ${schema.maxLength}`,
|
|
22323
22448
|
actual: value.length,
|
|
@@ -22328,7 +22453,7 @@ class ConfigValidator {
|
|
|
22328
22453
|
const regex = new RegExp(schema.pattern);
|
|
22329
22454
|
if (!regex.test(value)) {
|
|
22330
22455
|
errors3.push({
|
|
22331
|
-
path:
|
|
22456
|
+
path: path3,
|
|
22332
22457
|
message: `String does not match pattern ${schema.pattern}`,
|
|
22333
22458
|
expected: schema.pattern,
|
|
22334
22459
|
actual: value,
|
|
@@ -22340,7 +22465,7 @@ class ConfigValidator {
|
|
|
22340
22465
|
if (typeof value === "number") {
|
|
22341
22466
|
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
22342
22467
|
errors3.push({
|
|
22343
|
-
path:
|
|
22468
|
+
path: path3,
|
|
22344
22469
|
message: `Value must be at least ${schema.minimum}`,
|
|
22345
22470
|
expected: `>= ${schema.minimum}`,
|
|
22346
22471
|
actual: value,
|
|
@@ -22349,7 +22474,7 @@ class ConfigValidator {
|
|
|
22349
22474
|
}
|
|
22350
22475
|
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
22351
22476
|
errors3.push({
|
|
22352
|
-
path:
|
|
22477
|
+
path: path3,
|
|
22353
22478
|
message: `Value must not exceed ${schema.maximum}`,
|
|
22354
22479
|
expected: `<= ${schema.maximum}`,
|
|
22355
22480
|
actual: value,
|
|
@@ -22359,7 +22484,7 @@ class ConfigValidator {
|
|
|
22359
22484
|
}
|
|
22360
22485
|
if (Array.isArray(value) && schema.items) {
|
|
22361
22486
|
for (let i = 0;i < value.length; i++) {
|
|
22362
|
-
const itemPath =
|
|
22487
|
+
const itemPath = path3 ? `${path3}[${i}]` : `[${i}]`;
|
|
22363
22488
|
this.validateObjectAgainstSchema(value[i], schema.items, itemPath, errors3, warnings, options2);
|
|
22364
22489
|
if (options2.stopOnFirstError && errors3.length > 0)
|
|
22365
22490
|
return;
|
|
@@ -22371,7 +22496,7 @@ class ConfigValidator {
|
|
|
22371
22496
|
for (const requiredProp of schema.required) {
|
|
22372
22497
|
if (!(requiredProp in obj)) {
|
|
22373
22498
|
errors3.push({
|
|
22374
|
-
path:
|
|
22499
|
+
path: path3 ? `${path3}.${requiredProp}` : requiredProp,
|
|
22375
22500
|
message: `Missing required property '${requiredProp}'`,
|
|
22376
22501
|
expected: "required",
|
|
22377
22502
|
rule: "required"
|
|
@@ -22384,7 +22509,7 @@ class ConfigValidator {
|
|
|
22384
22509
|
if (schema.properties) {
|
|
22385
22510
|
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
22386
22511
|
if (propName in obj) {
|
|
22387
|
-
const propPath =
|
|
22512
|
+
const propPath = path3 ? `${path3}.${propName}` : propName;
|
|
22388
22513
|
this.validateObjectAgainstSchema(obj[propName], propSchema, propPath, errors3, warnings, options2);
|
|
22389
22514
|
if (options2.stopOnFirstError && errors3.length > 0)
|
|
22390
22515
|
return;
|
|
@@ -22396,7 +22521,7 @@ class ConfigValidator {
|
|
|
22396
22521
|
for (const propName of Object.keys(obj)) {
|
|
22397
22522
|
if (!allowedProps.has(propName)) {
|
|
22398
22523
|
warnings.push({
|
|
22399
|
-
path:
|
|
22524
|
+
path: path3 ? `${path3}.${propName}` : propName,
|
|
22400
22525
|
message: `Additional property '${propName}' is not allowed`,
|
|
22401
22526
|
rule: "additionalProperties"
|
|
22402
22527
|
});
|
|
@@ -22430,12 +22555,12 @@ class ConfigValidator {
|
|
|
22430
22555
|
warnings
|
|
22431
22556
|
};
|
|
22432
22557
|
}
|
|
22433
|
-
validateWithRule(value, rule,
|
|
22558
|
+
validateWithRule(value, rule, path3) {
|
|
22434
22559
|
const errors3 = [];
|
|
22435
22560
|
if (rule.required && (value === undefined || value === null)) {
|
|
22436
22561
|
errors3.push({
|
|
22437
|
-
path:
|
|
22438
|
-
message: rule.message || `Property '${
|
|
22562
|
+
path: path3,
|
|
22563
|
+
message: rule.message || `Property '${path3}' is required`,
|
|
22439
22564
|
expected: "required",
|
|
22440
22565
|
rule: "required"
|
|
22441
22566
|
});
|
|
@@ -22448,7 +22573,7 @@ class ConfigValidator {
|
|
|
22448
22573
|
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
22449
22574
|
if (actualType !== rule.type) {
|
|
22450
22575
|
errors3.push({
|
|
22451
|
-
path:
|
|
22576
|
+
path: path3,
|
|
22452
22577
|
message: rule.message || `Expected type ${rule.type}, got ${actualType}`,
|
|
22453
22578
|
expected: rule.type,
|
|
22454
22579
|
actual: actualType,
|
|
@@ -22460,7 +22585,7 @@ class ConfigValidator {
|
|
|
22460
22585
|
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
22461
22586
|
if (length < rule.min) {
|
|
22462
22587
|
errors3.push({
|
|
22463
|
-
path:
|
|
22588
|
+
path: path3,
|
|
22464
22589
|
message: rule.message || `Value must be at least ${rule.min}`,
|
|
22465
22590
|
expected: `>= ${rule.min}`,
|
|
22466
22591
|
actual: length,
|
|
@@ -22472,7 +22597,7 @@ class ConfigValidator {
|
|
|
22472
22597
|
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
22473
22598
|
if (length > rule.max) {
|
|
22474
22599
|
errors3.push({
|
|
22475
|
-
path:
|
|
22600
|
+
path: path3,
|
|
22476
22601
|
message: rule.message || `Value must not exceed ${rule.max}`,
|
|
22477
22602
|
expected: `<= ${rule.max}`,
|
|
22478
22603
|
actual: length,
|
|
@@ -22483,7 +22608,7 @@ class ConfigValidator {
|
|
|
22483
22608
|
if (rule.pattern && typeof value === "string") {
|
|
22484
22609
|
if (!rule.pattern.test(value)) {
|
|
22485
22610
|
errors3.push({
|
|
22486
|
-
path:
|
|
22611
|
+
path: path3,
|
|
22487
22612
|
message: rule.message || `Value does not match pattern ${rule.pattern}`,
|
|
22488
22613
|
expected: rule.pattern.toString(),
|
|
22489
22614
|
actual: value,
|
|
@@ -22493,7 +22618,7 @@ class ConfigValidator {
|
|
|
22493
22618
|
}
|
|
22494
22619
|
if (rule.enum && !rule.enum.includes(value)) {
|
|
22495
22620
|
errors3.push({
|
|
22496
|
-
path:
|
|
22621
|
+
path: path3,
|
|
22497
22622
|
message: rule.message || `Value must be one of: ${rule.enum.join(", ")}`,
|
|
22498
22623
|
expected: rule.enum.join(", "),
|
|
22499
22624
|
actual: value,
|
|
@@ -22504,7 +22629,7 @@ class ConfigValidator {
|
|
|
22504
22629
|
const customError = rule.validator(value);
|
|
22505
22630
|
if (customError) {
|
|
22506
22631
|
errors3.push({
|
|
22507
|
-
path:
|
|
22632
|
+
path: path3,
|
|
22508
22633
|
message: rule.message || customError,
|
|
22509
22634
|
rule: "custom"
|
|
22510
22635
|
});
|
|
@@ -22512,10 +22637,10 @@ class ConfigValidator {
|
|
|
22512
22637
|
}
|
|
22513
22638
|
return errors3;
|
|
22514
22639
|
}
|
|
22515
|
-
getValueByPath(obj,
|
|
22516
|
-
if (!
|
|
22640
|
+
getValueByPath(obj, path3) {
|
|
22641
|
+
if (!path3)
|
|
22517
22642
|
return obj;
|
|
22518
|
-
const keys =
|
|
22643
|
+
const keys = path3.split(".");
|
|
22519
22644
|
let current = obj;
|
|
22520
22645
|
for (const key of keys) {
|
|
22521
22646
|
if (current && typeof current === "object" && key in current) {
|
|
@@ -22941,10 +23066,10 @@ async function loadConfig5(options2) {
|
|
|
22941
23066
|
function applyEnvVarsToConfig2(name, config4, verbose = false) {
|
|
22942
23067
|
const _envProcessor = new EnvProcessor;
|
|
22943
23068
|
const envPrefix = name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
22944
|
-
function processConfigLevel(obj,
|
|
23069
|
+
function processConfigLevel(obj, path3 = []) {
|
|
22945
23070
|
const result = { ...obj };
|
|
22946
23071
|
for (const [key, value] of Object.entries(obj)) {
|
|
22947
|
-
const currentPath = [...
|
|
23072
|
+
const currentPath = [...path3, key];
|
|
22948
23073
|
const envKeys = [
|
|
22949
23074
|
`${envPrefix}_${currentPath.join("_").toUpperCase()}`,
|
|
22950
23075
|
`${envPrefix}_${currentPath.map((k) => k.toUpperCase()).join("")}`,
|
|
@@ -23010,14 +23135,14 @@ function getOpenCodeConfigPaths() {
|
|
|
23010
23135
|
paths.push(join4(home, ".opencode"));
|
|
23011
23136
|
return paths;
|
|
23012
23137
|
}
|
|
23013
|
-
function expandTildePath(
|
|
23014
|
-
if (
|
|
23138
|
+
function expandTildePath(path3) {
|
|
23139
|
+
if (path3 === "~") {
|
|
23015
23140
|
return homedir3();
|
|
23016
23141
|
}
|
|
23017
|
-
if (
|
|
23018
|
-
return join4(homedir3(),
|
|
23142
|
+
if (path3.startsWith("~/")) {
|
|
23143
|
+
return join4(homedir3(), path3.slice(2));
|
|
23019
23144
|
}
|
|
23020
|
-
return
|
|
23145
|
+
return path3;
|
|
23021
23146
|
}
|
|
23022
23147
|
var createPathKey = (absolutePath) => {
|
|
23023
23148
|
const normalizedPath = normalize(absolutePath);
|
|
@@ -23090,7 +23215,7 @@ var options2 = {
|
|
|
23090
23215
|
promptRenderer: "xml",
|
|
23091
23216
|
modelRenderers: {},
|
|
23092
23217
|
slashCommandName: "skill",
|
|
23093
|
-
enableSkillAliases:
|
|
23218
|
+
enableSkillAliases: true
|
|
23094
23219
|
}
|
|
23095
23220
|
};
|
|
23096
23221
|
async function getPluginConfig(ctx) {
|
|
@@ -23106,7 +23231,7 @@ async function getPluginConfig(ctx) {
|
|
|
23106
23231
|
// src/commands/SlashCommand.ts
|
|
23107
23232
|
var SLASH_COMMAND_SENTINEL = "<!-- opencode-dynamic-skills:slash-expanded -->";
|
|
23108
23233
|
var RECOMMEND_COMMAND_SENTINEL = "<!-- opencode-dynamic-skills:skill-recommend-expanded -->";
|
|
23109
|
-
function
|
|
23234
|
+
function normalizeSkillSelector3(selector) {
|
|
23110
23235
|
return selector.trim().toLowerCase().replace(/[/-]/g, "_");
|
|
23111
23236
|
}
|
|
23112
23237
|
function parseSlashCommand(text, slashCommandName) {
|
|
@@ -23143,30 +23268,26 @@ function findSkillBySelector(registry2, selector) {
|
|
|
23143
23268
|
if (directMatch) {
|
|
23144
23269
|
return directMatch;
|
|
23145
23270
|
}
|
|
23146
|
-
const normalizedSelector =
|
|
23271
|
+
const normalizedSelector = normalizeSkillSelector3(selector);
|
|
23147
23272
|
for (const skill of registry2.controller.skills) {
|
|
23148
|
-
if (
|
|
23273
|
+
if (normalizeSkillSelector3(skill.toolName) === normalizedSelector) {
|
|
23149
23274
|
return skill;
|
|
23150
23275
|
}
|
|
23151
|
-
if (
|
|
23276
|
+
if (normalizeSkillSelector3(skill.name) === normalizedSelector) {
|
|
23152
23277
|
return skill;
|
|
23153
23278
|
}
|
|
23154
23279
|
}
|
|
23155
23280
|
return null;
|
|
23156
23281
|
}
|
|
23157
23282
|
function renderSlashSkillPrompt(args) {
|
|
23158
|
-
const userPrompt = args.userPrompt || "Apply this skill to the current request.";
|
|
23159
23283
|
return [
|
|
23160
23284
|
SLASH_COMMAND_SENTINEL,
|
|
23161
|
-
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
`User request: ${userPrompt}`,
|
|
23168
|
-
"",
|
|
23169
|
-
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
|
+
""
|
|
23170
23291
|
].join(`
|
|
23171
23292
|
`);
|
|
23172
23293
|
}
|
|
@@ -23214,49 +23335,11 @@ async function rewriteSlashCommandText(args) {
|
|
|
23214
23335
|
}
|
|
23215
23336
|
return renderSlashSkillPrompt({
|
|
23216
23337
|
invocationName: parsedCommand.invocationName,
|
|
23217
|
-
renderedSkill: args.renderSkill(skill),
|
|
23218
23338
|
skill,
|
|
23219
23339
|
userPrompt: parsedCommand.userPrompt
|
|
23220
23340
|
});
|
|
23221
23341
|
}
|
|
23222
23342
|
|
|
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
|
-
|
|
23260
23343
|
// src/lib/renderers/JsonPromptRenderer.ts
|
|
23261
23344
|
var createJsonPromptRenderer = () => {
|
|
23262
23345
|
const renderer = {
|
|
@@ -23715,7 +23798,7 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23715
23798
|
providerId: input.model?.providerID,
|
|
23716
23799
|
config: config3
|
|
23717
23800
|
});
|
|
23718
|
-
|
|
23801
|
+
promptRenderer.getFormatter(format);
|
|
23719
23802
|
for (const part of output.parts) {
|
|
23720
23803
|
if (part.type !== "text") {
|
|
23721
23804
|
continue;
|
|
@@ -23728,7 +23811,6 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23728
23811
|
const rewrittenText = await rewriteSlashCommandText({
|
|
23729
23812
|
text: part.text,
|
|
23730
23813
|
registry: api2.registry,
|
|
23731
|
-
renderSkill: (skill) => renderSkill({ data: skill, type: "Skill" }),
|
|
23732
23814
|
slashCommandName: config3.slashCommandName,
|
|
23733
23815
|
enableSkillAliases: config3.enableSkillAliases
|
|
23734
23816
|
});
|
|
@@ -23750,6 +23832,7 @@ var SkillsPlugin = async (ctx) => {
|
|
|
23750
23832
|
}
|
|
23751
23833
|
},
|
|
23752
23834
|
tool: {
|
|
23835
|
+
skill: api2.skillTool,
|
|
23753
23836
|
skill_use: tool({
|
|
23754
23837
|
description: "Load one or more skills into the chat. Provide an array of skill names to load them as user messages.",
|
|
23755
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": {
|