@fro.bot/systematic 1.2.1 → 1.3.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +15 -24
- package/dist/index-ymsavt2y.js +404 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +149 -216
- package/dist/lib/agents.d.ts +12 -0
- package/dist/lib/bootstrap.d.ts +5 -0
- package/dist/lib/commands.d.ts +12 -0
- package/dist/lib/config-handler.d.ts +17 -0
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/converter.d.ts +12 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/skill-loader.d.ts +14 -0
- package/dist/lib/skill-tool.d.ts +6 -0
- package/dist/lib/skills.d.ts +13 -0
- package/dist/lib/walk-dir.d.ts +12 -0
- package/package.json +5 -3
- package/dist/index-hkk4125w.js +0 -324
package/dist/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
convertContent,
|
|
5
5
|
findAgentsInDir,
|
|
6
6
|
findCommandsInDir,
|
|
7
|
-
findSkillsInDir
|
|
8
|
-
|
|
7
|
+
findSkillsInDir,
|
|
8
|
+
getConfigPaths
|
|
9
|
+
} from "./index-ymsavt2y.js";
|
|
9
10
|
|
|
10
11
|
// src/cli.ts
|
|
11
12
|
import fs from "fs";
|
|
@@ -37,12 +38,6 @@ Examples:
|
|
|
37
38
|
systematic convert skill ./skills/my-skill/SKILL.md
|
|
38
39
|
systematic config show
|
|
39
40
|
`;
|
|
40
|
-
function getUserConfigDir() {
|
|
41
|
-
return path.join(process.env.HOME || process.env.USERPROFILE || ".", ".config/opencode");
|
|
42
|
-
}
|
|
43
|
-
function getProjectConfigDir() {
|
|
44
|
-
return path.join(process.cwd(), ".opencode");
|
|
45
|
-
}
|
|
46
41
|
function listItems(type) {
|
|
47
42
|
const packageRoot = path.resolve(import.meta.dirname, "..");
|
|
48
43
|
const bundledDir = packageRoot;
|
|
@@ -65,7 +60,7 @@ function listItems(type) {
|
|
|
65
60
|
console.error(`Unknown type: ${type}. Use: skills, agents, commands`);
|
|
66
61
|
process.exit(1);
|
|
67
62
|
}
|
|
68
|
-
const items = finder(path.join(bundledDir, subdir)
|
|
63
|
+
const items = finder(path.join(bundledDir, subdir));
|
|
69
64
|
if (items.length === 0) {
|
|
70
65
|
console.log(`No ${type} found.`);
|
|
71
66
|
return;
|
|
@@ -73,7 +68,7 @@ function listItems(type) {
|
|
|
73
68
|
console.log(`Available ${type}:
|
|
74
69
|
`);
|
|
75
70
|
for (const item of items.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
76
|
-
console.log(` ${item.name}
|
|
71
|
+
console.log(` ${item.name}`);
|
|
77
72
|
}
|
|
78
73
|
}
|
|
79
74
|
function runConvert(type, filePath, modeArg) {
|
|
@@ -102,31 +97,27 @@ function runConvert(type, filePath, modeArg) {
|
|
|
102
97
|
console.log(converted);
|
|
103
98
|
}
|
|
104
99
|
function configShow() {
|
|
105
|
-
const
|
|
106
|
-
const projectDir = getProjectConfigDir();
|
|
100
|
+
const paths = getConfigPaths(process.cwd());
|
|
107
101
|
console.log(`Configuration locations:
|
|
108
102
|
`);
|
|
109
|
-
console.log(` User config: ${
|
|
110
|
-
console.log(` Project config: ${
|
|
111
|
-
|
|
112
|
-
if (fs.existsSync(projectConfig)) {
|
|
103
|
+
console.log(` User config: ${paths.userConfig}`);
|
|
104
|
+
console.log(` Project config: ${paths.projectConfig}`);
|
|
105
|
+
if (fs.existsSync(paths.projectConfig)) {
|
|
113
106
|
console.log(`
|
|
114
107
|
Project configuration:`);
|
|
115
|
-
console.log(fs.readFileSync(projectConfig, "utf-8"));
|
|
108
|
+
console.log(fs.readFileSync(paths.projectConfig, "utf-8"));
|
|
116
109
|
}
|
|
117
|
-
|
|
118
|
-
if (fs.existsSync(userConfig)) {
|
|
110
|
+
if (fs.existsSync(paths.userConfig)) {
|
|
119
111
|
console.log(`
|
|
120
112
|
User configuration:`);
|
|
121
|
-
console.log(fs.readFileSync(userConfig, "utf-8"));
|
|
113
|
+
console.log(fs.readFileSync(paths.userConfig, "utf-8"));
|
|
122
114
|
}
|
|
123
115
|
}
|
|
124
116
|
function configPath() {
|
|
125
|
-
const
|
|
126
|
-
const projectDir = getProjectConfigDir();
|
|
117
|
+
const paths = getConfigPaths(process.cwd());
|
|
127
118
|
console.log("Config file paths:");
|
|
128
|
-
console.log(` User: ${
|
|
129
|
-
console.log(` Project: ${
|
|
119
|
+
console.log(` User: ${paths.userConfig}`);
|
|
120
|
+
console.log(` Project: ${paths.projectConfig}`);
|
|
130
121
|
}
|
|
131
122
|
var args = process.argv.slice(2);
|
|
132
123
|
var command = args[0];
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/lib/config.ts
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
7
|
+
var DEFAULT_CONFIG = {
|
|
8
|
+
disabled_skills: [],
|
|
9
|
+
disabled_agents: [],
|
|
10
|
+
disabled_commands: [],
|
|
11
|
+
bootstrap: {
|
|
12
|
+
enabled: true
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function loadJsoncFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(filePath))
|
|
18
|
+
return null;
|
|
19
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
20
|
+
return parseJsonc(content);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function mergeArraysUnique(arr1, arr2) {
|
|
26
|
+
const set = new Set;
|
|
27
|
+
if (arr1)
|
|
28
|
+
for (const item of arr1)
|
|
29
|
+
set.add(item);
|
|
30
|
+
if (arr2)
|
|
31
|
+
for (const item of arr2)
|
|
32
|
+
set.add(item);
|
|
33
|
+
return Array.from(set);
|
|
34
|
+
}
|
|
35
|
+
function loadConfig(projectDir) {
|
|
36
|
+
const homeDir = os.homedir();
|
|
37
|
+
const userConfigPath = path.join(homeDir, ".config/opencode/systematic.json");
|
|
38
|
+
const projectConfigPath = path.join(projectDir, ".opencode/systematic.json");
|
|
39
|
+
const userConfig = loadJsoncFile(userConfigPath);
|
|
40
|
+
const projectConfig = loadJsoncFile(projectConfigPath);
|
|
41
|
+
const result = {
|
|
42
|
+
disabled_skills: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_skills, userConfig?.disabled_skills), projectConfig?.disabled_skills),
|
|
43
|
+
disabled_agents: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents),
|
|
44
|
+
disabled_commands: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands),
|
|
45
|
+
bootstrap: {
|
|
46
|
+
...DEFAULT_CONFIG.bootstrap,
|
|
47
|
+
...userConfig?.bootstrap,
|
|
48
|
+
...projectConfig?.bootstrap
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function getConfigPaths(projectDir) {
|
|
54
|
+
const homeDir = os.homedir();
|
|
55
|
+
return {
|
|
56
|
+
userConfig: path.join(homeDir, ".config/opencode/systematic.json"),
|
|
57
|
+
projectConfig: path.join(projectDir, ".opencode/systematic.json"),
|
|
58
|
+
userDir: path.join(homeDir, ".config/opencode/systematic"),
|
|
59
|
+
projectDir: path.join(projectDir, ".opencode/systematic")
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/lib/frontmatter.ts
|
|
64
|
+
import yaml from "js-yaml";
|
|
65
|
+
function parseFrontmatter(content) {
|
|
66
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
|
|
67
|
+
const match = content.match(frontmatterRegex);
|
|
68
|
+
if (!match) {
|
|
69
|
+
return {
|
|
70
|
+
data: {},
|
|
71
|
+
body: content,
|
|
72
|
+
hadFrontmatter: false,
|
|
73
|
+
parseError: false
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const yamlContent = match[1];
|
|
77
|
+
const body = match[2];
|
|
78
|
+
try {
|
|
79
|
+
const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
80
|
+
const data = parsed ?? {};
|
|
81
|
+
return { data, body, hadFrontmatter: true, parseError: false };
|
|
82
|
+
} catch {
|
|
83
|
+
return { data: {}, body, hadFrontmatter: true, parseError: true };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function formatFrontmatter(data) {
|
|
87
|
+
if (Object.keys(data).length === 0) {
|
|
88
|
+
return ["---", "---"].join(`
|
|
89
|
+
`);
|
|
90
|
+
}
|
|
91
|
+
const yamlContent = yaml.dump(data, {
|
|
92
|
+
schema: yaml.JSON_SCHEMA,
|
|
93
|
+
lineWidth: -1,
|
|
94
|
+
noRefs: true
|
|
95
|
+
}).trimEnd();
|
|
96
|
+
return ["---", yamlContent, "---"].join(`
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
function stripFrontmatter(content) {
|
|
100
|
+
const { body, hadFrontmatter } = parseFrontmatter(content);
|
|
101
|
+
return hadFrontmatter ? body.trim() : content.trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/lib/walk-dir.ts
|
|
105
|
+
import fs2 from "fs";
|
|
106
|
+
import path2 from "path";
|
|
107
|
+
function walkDir(rootDir, options = {}) {
|
|
108
|
+
const { maxDepth = 3, filter } = options;
|
|
109
|
+
const results = [];
|
|
110
|
+
if (!fs2.existsSync(rootDir))
|
|
111
|
+
return results;
|
|
112
|
+
function recurse(currentDir, depth, category) {
|
|
113
|
+
if (depth > maxDepth)
|
|
114
|
+
return;
|
|
115
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
118
|
+
const walkEntry = {
|
|
119
|
+
path: fullPath,
|
|
120
|
+
name: entry.name,
|
|
121
|
+
isDirectory: entry.isDirectory(),
|
|
122
|
+
depth,
|
|
123
|
+
category
|
|
124
|
+
};
|
|
125
|
+
if (!filter || filter(walkEntry)) {
|
|
126
|
+
results.push(walkEntry);
|
|
127
|
+
}
|
|
128
|
+
if (entry.isDirectory()) {
|
|
129
|
+
recurse(fullPath, depth + 1, entry.name);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
recurse(rootDir, 0);
|
|
134
|
+
return results;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/lib/agents.ts
|
|
138
|
+
function findAgentsInDir(dir, maxDepth = 2) {
|
|
139
|
+
const entries = walkDir(dir, {
|
|
140
|
+
maxDepth,
|
|
141
|
+
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
142
|
+
});
|
|
143
|
+
return entries.map((entry) => ({
|
|
144
|
+
name: entry.name.replace(/\.md$/, ""),
|
|
145
|
+
file: entry.path,
|
|
146
|
+
category: entry.category
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
function extractAgentFrontmatter(content) {
|
|
150
|
+
const { data, parseError } = parseFrontmatter(content);
|
|
151
|
+
return {
|
|
152
|
+
name: !parseError && typeof data.name === "string" ? data.name : "",
|
|
153
|
+
description: !parseError && typeof data.description === "string" ? data.description : "",
|
|
154
|
+
prompt: stripFrontmatter(content)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/lib/commands.ts
|
|
159
|
+
function findCommandsInDir(dir, maxDepth = 2) {
|
|
160
|
+
const entries = walkDir(dir, {
|
|
161
|
+
maxDepth,
|
|
162
|
+
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
163
|
+
});
|
|
164
|
+
return entries.map((entry) => {
|
|
165
|
+
const baseName = entry.name.replace(/\.md$/, "");
|
|
166
|
+
const commandName = entry.category ? `/${entry.category}:${baseName}` : `/${baseName}`;
|
|
167
|
+
return {
|
|
168
|
+
name: commandName,
|
|
169
|
+
file: entry.path,
|
|
170
|
+
category: entry.category
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function extractCommandFrontmatter(content) {
|
|
175
|
+
const { data, parseError } = parseFrontmatter(content);
|
|
176
|
+
const argumentHintRaw = !parseError && typeof data["argument-hint"] === "string" ? data["argument-hint"] : "";
|
|
177
|
+
return {
|
|
178
|
+
name: !parseError && typeof data.name === "string" ? data.name : "",
|
|
179
|
+
description: !parseError && typeof data.description === "string" ? data.description : "",
|
|
180
|
+
argumentHint: argumentHintRaw.replace(/^["']|["']$/g, "")
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/lib/converter.ts
|
|
185
|
+
import fs3 from "fs";
|
|
186
|
+
var cache = new Map;
|
|
187
|
+
var TOOL_MAPPINGS = [
|
|
188
|
+
[/\bTask\s+tool\b/gi, "delegate_task tool"],
|
|
189
|
+
[/\bTask\s+([\w-]+)\s*\(/g, "delegate_task $1("],
|
|
190
|
+
[/\bTask\s*\(/g, "delegate_task("],
|
|
191
|
+
[/\bTask\b(?=\s+to\s+\w)/g, "delegate_task"],
|
|
192
|
+
[/\bTodoWrite\b/g, "todowrite"],
|
|
193
|
+
[/\bAskUserQuestion\b/g, "question"],
|
|
194
|
+
[/\bWebSearch\b/g, "google_search"],
|
|
195
|
+
[/\bRead\b(?=\s+tool|\s+to\s+|\()/g, "read"],
|
|
196
|
+
[/\bWrite\b(?=\s+tool|\s+to\s+|\()/g, "write"],
|
|
197
|
+
[/\bEdit\b(?=\s+tool|\s+to\s+|\()/g, "edit"],
|
|
198
|
+
[/\bBash\b(?=\s+tool|\s+to\s+|\()/g, "bash"],
|
|
199
|
+
[/\bGrep\b(?=\s+tool|\s+to\s+|\()/g, "grep"],
|
|
200
|
+
[/\bGlob\b(?=\s+tool|\s+to\s+|\()/g, "glob"],
|
|
201
|
+
[/\bWebFetch\b/g, "webfetch"],
|
|
202
|
+
[/\bSkill\b(?=\s+tool)/g, "skill"]
|
|
203
|
+
];
|
|
204
|
+
var PATH_REPLACEMENTS = [
|
|
205
|
+
[/\.claude\/skills\//g, ".opencode/skills/"],
|
|
206
|
+
[/\.claude\/commands\//g, ".opencode/commands/"],
|
|
207
|
+
[/\.claude\/agents\//g, ".opencode/agents/"],
|
|
208
|
+
[/~\/\.claude\//g, "~/.config/opencode/"],
|
|
209
|
+
[/CLAUDE\.md/g, "AGENTS.md"],
|
|
210
|
+
[/\/compound-engineering:/g, "/systematic:"],
|
|
211
|
+
[/compound-engineering:/g, "systematic:"]
|
|
212
|
+
];
|
|
213
|
+
var CC_ONLY_SKILL_FIELDS = [
|
|
214
|
+
"model",
|
|
215
|
+
"allowed-tools",
|
|
216
|
+
"allowedTools",
|
|
217
|
+
"disable-model-invocation",
|
|
218
|
+
"disableModelInvocation",
|
|
219
|
+
"user-invocable",
|
|
220
|
+
"userInvocable",
|
|
221
|
+
"context",
|
|
222
|
+
"agent"
|
|
223
|
+
];
|
|
224
|
+
var CC_ONLY_COMMAND_FIELDS = ["argument-hint", "argumentHint"];
|
|
225
|
+
function inferTemperature(name, description) {
|
|
226
|
+
const sample = `${name} ${description ?? ""}`.toLowerCase();
|
|
227
|
+
if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
|
|
228
|
+
return 0.1;
|
|
229
|
+
}
|
|
230
|
+
if (/(plan|planning|architecture|strategist|analysis|research)/.test(sample)) {
|
|
231
|
+
return 0.2;
|
|
232
|
+
}
|
|
233
|
+
if (/(doc|readme|changelog|editor|writer)/.test(sample)) {
|
|
234
|
+
return 0.3;
|
|
235
|
+
}
|
|
236
|
+
if (/(brainstorm|creative|ideate|design|concept)/.test(sample)) {
|
|
237
|
+
return 0.6;
|
|
238
|
+
}
|
|
239
|
+
return 0.3;
|
|
240
|
+
}
|
|
241
|
+
function transformBody(body) {
|
|
242
|
+
let result = body;
|
|
243
|
+
for (const [pattern, replacement] of TOOL_MAPPINGS) {
|
|
244
|
+
result = result.replace(pattern, replacement);
|
|
245
|
+
}
|
|
246
|
+
for (const [pattern, replacement] of PATH_REPLACEMENTS) {
|
|
247
|
+
result = result.replace(pattern, replacement);
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function removeFields(data, fieldsToRemove) {
|
|
252
|
+
const result = {};
|
|
253
|
+
for (const [key, value] of Object.entries(data)) {
|
|
254
|
+
if (!fieldsToRemove.includes(key)) {
|
|
255
|
+
result[key] = value;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
function transformSkillFrontmatter(data) {
|
|
261
|
+
return removeFields(data, CC_ONLY_SKILL_FIELDS);
|
|
262
|
+
}
|
|
263
|
+
function transformCommandFrontmatter(data) {
|
|
264
|
+
const cleaned = removeFields(data, CC_ONLY_COMMAND_FIELDS);
|
|
265
|
+
if (typeof cleaned.model === "string" && cleaned.model !== "inherit") {
|
|
266
|
+
cleaned.model = normalizeModel(cleaned.model);
|
|
267
|
+
} else if (cleaned.model === "inherit") {
|
|
268
|
+
delete cleaned.model;
|
|
269
|
+
}
|
|
270
|
+
return cleaned;
|
|
271
|
+
}
|
|
272
|
+
function normalizeModel(model) {
|
|
273
|
+
if (model.includes("/"))
|
|
274
|
+
return model;
|
|
275
|
+
if (model === "inherit")
|
|
276
|
+
return model;
|
|
277
|
+
if (/^claude-/.test(model))
|
|
278
|
+
return `anthropic/${model}`;
|
|
279
|
+
if (/^(gpt-|o1-|o3-)/.test(model))
|
|
280
|
+
return `openai/${model}`;
|
|
281
|
+
if (/^gemini-/.test(model))
|
|
282
|
+
return `google/${model}`;
|
|
283
|
+
return `anthropic/${model}`;
|
|
284
|
+
}
|
|
285
|
+
function transformAgentFrontmatter(data, agentMode) {
|
|
286
|
+
const name = typeof data.name === "string" ? data.name : "";
|
|
287
|
+
const description = typeof data.description === "string" ? data.description : "";
|
|
288
|
+
const newData = {
|
|
289
|
+
description: description || `${name} agent`,
|
|
290
|
+
mode: agentMode
|
|
291
|
+
};
|
|
292
|
+
if (typeof data.model === "string" && data.model !== "inherit") {
|
|
293
|
+
newData.model = normalizeModel(data.model);
|
|
294
|
+
}
|
|
295
|
+
if (typeof data.temperature === "number") {
|
|
296
|
+
newData.temperature = data.temperature;
|
|
297
|
+
} else {
|
|
298
|
+
newData.temperature = inferTemperature(name, description);
|
|
299
|
+
}
|
|
300
|
+
return newData;
|
|
301
|
+
}
|
|
302
|
+
function convertContent(content, type, options = {}) {
|
|
303
|
+
if (content === "")
|
|
304
|
+
return "";
|
|
305
|
+
const { data, body, hadFrontmatter } = parseFrontmatter(content);
|
|
306
|
+
if (!hadFrontmatter) {
|
|
307
|
+
return options.skipBodyTransform ? content : transformBody(content);
|
|
308
|
+
}
|
|
309
|
+
const shouldTransformBody = !options.skipBodyTransform;
|
|
310
|
+
const transformedBody = shouldTransformBody ? transformBody(body) : body;
|
|
311
|
+
if (type === "agent") {
|
|
312
|
+
const agentMode = options.agentMode ?? "subagent";
|
|
313
|
+
const transformedData = transformAgentFrontmatter(data, agentMode);
|
|
314
|
+
return `${formatFrontmatter(transformedData)}
|
|
315
|
+
${transformedBody}`;
|
|
316
|
+
}
|
|
317
|
+
if (type === "skill") {
|
|
318
|
+
const transformedData = transformSkillFrontmatter(data);
|
|
319
|
+
return `${formatFrontmatter(transformedData)}
|
|
320
|
+
${transformedBody}`;
|
|
321
|
+
}
|
|
322
|
+
if (type === "command") {
|
|
323
|
+
const transformedData = transformCommandFrontmatter(data);
|
|
324
|
+
return `${formatFrontmatter(transformedData)}
|
|
325
|
+
${transformedBody}`;
|
|
326
|
+
}
|
|
327
|
+
return content;
|
|
328
|
+
}
|
|
329
|
+
function convertFileWithCache(filePath, type, options = {}) {
|
|
330
|
+
const fd = fs3.openSync(filePath, "r");
|
|
331
|
+
try {
|
|
332
|
+
const stats = fs3.fstatSync(fd);
|
|
333
|
+
const cacheKey = `${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}:${options.skipBodyTransform ?? false}`;
|
|
334
|
+
const cached = cache.get(cacheKey);
|
|
335
|
+
if (cached != null && cached.mtimeMs === stats.mtimeMs) {
|
|
336
|
+
return cached.converted;
|
|
337
|
+
}
|
|
338
|
+
const content = fs3.readFileSync(fd, "utf8");
|
|
339
|
+
const converted = convertContent(content, type, options);
|
|
340
|
+
cache.set(cacheKey, { mtimeMs: stats.mtimeMs, converted });
|
|
341
|
+
return converted;
|
|
342
|
+
} finally {
|
|
343
|
+
fs3.closeSync(fd);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/lib/skills.ts
|
|
348
|
+
import fs4 from "fs";
|
|
349
|
+
import path3 from "path";
|
|
350
|
+
function extractFrontmatter(filePath) {
|
|
351
|
+
try {
|
|
352
|
+
const content = fs4.readFileSync(filePath, "utf8");
|
|
353
|
+
const { data, parseError } = parseFrontmatter(content);
|
|
354
|
+
if (parseError) {
|
|
355
|
+
return { name: "", description: "" };
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
name: typeof data.name === "string" ? data.name : "",
|
|
359
|
+
description: typeof data.description === "string" ? data.description : ""
|
|
360
|
+
};
|
|
361
|
+
} catch {
|
|
362
|
+
return { name: "", description: "" };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function findSkillsInDir(dir, maxDepth = 3) {
|
|
366
|
+
const skills = [];
|
|
367
|
+
const entries = walkDir(dir, {
|
|
368
|
+
maxDepth,
|
|
369
|
+
filter: (e) => e.isDirectory
|
|
370
|
+
});
|
|
371
|
+
for (const entry of entries) {
|
|
372
|
+
const skillFile = path3.join(entry.path, "SKILL.md");
|
|
373
|
+
if (fs4.existsSync(skillFile)) {
|
|
374
|
+
const { name, description } = extractFrontmatter(skillFile);
|
|
375
|
+
skills.push({
|
|
376
|
+
path: entry.path,
|
|
377
|
+
skillFile,
|
|
378
|
+
name: name || entry.name,
|
|
379
|
+
description: description || ""
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return skills;
|
|
384
|
+
}
|
|
385
|
+
function formatSkillsXml(skills) {
|
|
386
|
+
if (skills.length === 0)
|
|
387
|
+
return "";
|
|
388
|
+
const skillsXml = skills.map((skill) => {
|
|
389
|
+
const lines = [
|
|
390
|
+
" <skill>",
|
|
391
|
+
` <name>systematic:${skill.name}</name>`,
|
|
392
|
+
` <description>${skill.description}</description>`
|
|
393
|
+
];
|
|
394
|
+
lines.push(" </skill>");
|
|
395
|
+
return lines.join(`
|
|
396
|
+
`);
|
|
397
|
+
}).join(`
|
|
398
|
+
`);
|
|
399
|
+
return `<available_skills>
|
|
400
|
+
${skillsXml}
|
|
401
|
+
</available_skills>`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export { stripFrontmatter, loadConfig, getConfigPaths, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, convertContent, convertFileWithCache, findSkillsInDir, formatSkillsXml };
|
package/dist/index.d.ts
ADDED