@fro.bot/systematic 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index-3h7kpmfa.js +1480 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/index-5wn35nny.js +0 -675
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fro.bot/systematic",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Structured engineering workflows for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://fro.bot/systematic",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
26
|
"clean": "rimraf dist",
|
|
27
|
-
"build": "bun run clean && bun build src/index.ts src/cli.ts --outdir dist --target bun --splitting --
|
|
27
|
+
"build": "bun run clean && bun build src/index.ts src/cli.ts --outdir dist --target bun --splitting --external @opencode-ai/plugin --external js-yaml && tsc --emitDeclarationOnly",
|
|
28
28
|
"dev": "bun --watch src/index.ts",
|
|
29
29
|
"test": "bun test tests/unit",
|
|
30
30
|
"test:integration": "bun test tests/integration",
|
package/dist/index-5wn35nny.js
DELETED
|
@@ -1,675 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/lib/config.ts
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import os from "os";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import jsonc 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 jsonc.parse(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 paths = getConfigPaths(projectDir);
|
|
37
|
-
const userConfig = loadJsoncFile(paths.userConfig);
|
|
38
|
-
const projectConfig = loadJsoncFile(paths.projectConfig);
|
|
39
|
-
const customConfig = paths.customConfig ? loadJsoncFile(paths.customConfig) : null;
|
|
40
|
-
const result = {
|
|
41
|
-
disabled_skills: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_skills, userConfig?.disabled_skills), projectConfig?.disabled_skills), customConfig?.disabled_skills),
|
|
42
|
-
disabled_agents: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents), customConfig?.disabled_agents),
|
|
43
|
-
disabled_commands: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands), customConfig?.disabled_commands),
|
|
44
|
-
bootstrap: {
|
|
45
|
-
...DEFAULT_CONFIG.bootstrap,
|
|
46
|
-
...userConfig?.bootstrap,
|
|
47
|
-
...projectConfig?.bootstrap,
|
|
48
|
-
...customConfig?.bootstrap
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
function getConfigPaths(projectDir) {
|
|
54
|
-
const homeDir = os.homedir();
|
|
55
|
-
const customConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
56
|
-
const result = {
|
|
57
|
-
userConfig: path.join(homeDir, ".config/opencode/systematic.json"),
|
|
58
|
-
projectConfig: path.join(projectDir, ".opencode/systematic.json"),
|
|
59
|
-
userDir: path.join(homeDir, ".config/opencode/systematic"),
|
|
60
|
-
projectDir: path.join(projectDir, ".opencode/systematic"),
|
|
61
|
-
...customConfigDir && {
|
|
62
|
-
customConfig: path.join(customConfigDir, "systematic.json"),
|
|
63
|
-
customDir: path.join(customConfigDir, "systematic")
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/lib/frontmatter.ts
|
|
70
|
-
import yaml from "js-yaml";
|
|
71
|
-
function parseFrontmatter(content) {
|
|
72
|
-
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
|
|
73
|
-
const match = content.match(frontmatterRegex);
|
|
74
|
-
if (!match) {
|
|
75
|
-
return {
|
|
76
|
-
data: {},
|
|
77
|
-
body: content,
|
|
78
|
-
hadFrontmatter: false,
|
|
79
|
-
parseError: false
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const yamlContent = match[1];
|
|
83
|
-
const body = match[2];
|
|
84
|
-
try {
|
|
85
|
-
const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
86
|
-
const data = parsed ?? {};
|
|
87
|
-
return { data, body, hadFrontmatter: true, parseError: false };
|
|
88
|
-
} catch {
|
|
89
|
-
return { data: {}, body, hadFrontmatter: true, parseError: true };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
function formatFrontmatter(data) {
|
|
93
|
-
if (Object.keys(data).length === 0) {
|
|
94
|
-
return ["---", "---"].join(`
|
|
95
|
-
`);
|
|
96
|
-
}
|
|
97
|
-
const yamlContent = yaml.dump(data, {
|
|
98
|
-
schema: yaml.JSON_SCHEMA,
|
|
99
|
-
lineWidth: -1,
|
|
100
|
-
noRefs: true
|
|
101
|
-
}).trimEnd();
|
|
102
|
-
return ["---", yamlContent, "---"].join(`
|
|
103
|
-
`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// src/lib/validation.ts
|
|
107
|
-
function isRecord(value) {
|
|
108
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
109
|
-
}
|
|
110
|
-
function isPermissionSetting(value) {
|
|
111
|
-
return value === "ask" || value === "allow" || value === "deny";
|
|
112
|
-
}
|
|
113
|
-
function isToolsMap(value) {
|
|
114
|
-
if (!isRecord(value))
|
|
115
|
-
return false;
|
|
116
|
-
return Object.values(value).every((entry) => typeof entry === "boolean");
|
|
117
|
-
}
|
|
118
|
-
function isAgentMode(value) {
|
|
119
|
-
return value === "subagent" || value === "primary" || value === "all";
|
|
120
|
-
}
|
|
121
|
-
function extractSimplePermission(data, key) {
|
|
122
|
-
if (!(key in data))
|
|
123
|
-
return;
|
|
124
|
-
const value = data[key];
|
|
125
|
-
return isPermissionSetting(value) ? value : null;
|
|
126
|
-
}
|
|
127
|
-
function extractBashPermission(data) {
|
|
128
|
-
if (!("bash" in data))
|
|
129
|
-
return;
|
|
130
|
-
const bash = data.bash;
|
|
131
|
-
if (isPermissionSetting(bash))
|
|
132
|
-
return bash;
|
|
133
|
-
if (isRecord(bash)) {
|
|
134
|
-
const entries = Object.entries(bash);
|
|
135
|
-
if (entries.every(([, setting]) => isPermissionSetting(setting))) {
|
|
136
|
-
return Object.fromEntries(entries);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
|
|
142
|
-
const permission = {};
|
|
143
|
-
if (edit)
|
|
144
|
-
permission.edit = edit;
|
|
145
|
-
if (bash)
|
|
146
|
-
permission.bash = bash;
|
|
147
|
-
if (webfetch)
|
|
148
|
-
permission.webfetch = webfetch;
|
|
149
|
-
if (doom_loop)
|
|
150
|
-
permission.doom_loop = doom_loop;
|
|
151
|
-
if (external_directory)
|
|
152
|
-
permission.external_directory = external_directory;
|
|
153
|
-
if (task)
|
|
154
|
-
permission.task = task;
|
|
155
|
-
if (skill)
|
|
156
|
-
permission.skill = skill;
|
|
157
|
-
return Object.keys(permission).length > 0 ? permission : undefined;
|
|
158
|
-
}
|
|
159
|
-
function normalizePermission(value) {
|
|
160
|
-
if (!isRecord(value))
|
|
161
|
-
return;
|
|
162
|
-
const bash = extractBashPermission(value);
|
|
163
|
-
if (bash === null)
|
|
164
|
-
return;
|
|
165
|
-
const edit = extractSimplePermission(value, "edit");
|
|
166
|
-
if (edit === null)
|
|
167
|
-
return;
|
|
168
|
-
const webfetch = extractSimplePermission(value, "webfetch");
|
|
169
|
-
if (webfetch === null)
|
|
170
|
-
return;
|
|
171
|
-
const doom_loop = extractSimplePermission(value, "doom_loop");
|
|
172
|
-
if (doom_loop === null)
|
|
173
|
-
return;
|
|
174
|
-
const external_directory = extractSimplePermission(value, "external_directory");
|
|
175
|
-
if (external_directory === null)
|
|
176
|
-
return;
|
|
177
|
-
const task = extractSimplePermission(value, "task");
|
|
178
|
-
if (task === null)
|
|
179
|
-
return;
|
|
180
|
-
const skill = extractSimplePermission(value, "skill");
|
|
181
|
-
if (skill === null)
|
|
182
|
-
return;
|
|
183
|
-
return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
|
|
184
|
-
}
|
|
185
|
-
function extractString(data, key, fallback = "") {
|
|
186
|
-
const value = data[key];
|
|
187
|
-
return typeof value === "string" ? value : fallback;
|
|
188
|
-
}
|
|
189
|
-
function extractNonEmptyString(data, key) {
|
|
190
|
-
const value = data[key];
|
|
191
|
-
if (typeof value !== "string")
|
|
192
|
-
return;
|
|
193
|
-
const trimmed = value.trim();
|
|
194
|
-
return trimmed !== "" ? trimmed : undefined;
|
|
195
|
-
}
|
|
196
|
-
function extractNumber(data, key) {
|
|
197
|
-
const value = data[key];
|
|
198
|
-
return typeof value === "number" ? value : undefined;
|
|
199
|
-
}
|
|
200
|
-
function extractBoolean(data, key) {
|
|
201
|
-
const value = data[key];
|
|
202
|
-
if (typeof value === "boolean")
|
|
203
|
-
return value;
|
|
204
|
-
if (typeof value === "string") {
|
|
205
|
-
const normalized = value.trim().toLowerCase();
|
|
206
|
-
if (normalized === "true")
|
|
207
|
-
return true;
|
|
208
|
-
if (normalized === "false")
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// src/lib/walk-dir.ts
|
|
215
|
-
import fs2 from "fs";
|
|
216
|
-
import path2 from "path";
|
|
217
|
-
function walkDir(rootDir, options = {}) {
|
|
218
|
-
const { maxDepth = 3, filter } = options;
|
|
219
|
-
const results = [];
|
|
220
|
-
if (!fs2.existsSync(rootDir))
|
|
221
|
-
return results;
|
|
222
|
-
function recurse(currentDir, depth, category) {
|
|
223
|
-
if (depth > maxDepth)
|
|
224
|
-
return;
|
|
225
|
-
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
226
|
-
for (const entry of entries) {
|
|
227
|
-
const fullPath = path2.join(currentDir, entry.name);
|
|
228
|
-
const walkEntry = {
|
|
229
|
-
path: fullPath,
|
|
230
|
-
name: entry.name,
|
|
231
|
-
isDirectory: entry.isDirectory(),
|
|
232
|
-
depth,
|
|
233
|
-
category
|
|
234
|
-
};
|
|
235
|
-
if (!filter || filter(walkEntry)) {
|
|
236
|
-
results.push(walkEntry);
|
|
237
|
-
}
|
|
238
|
-
if (entry.isDirectory()) {
|
|
239
|
-
recurse(fullPath, depth + 1, entry.name);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
recurse(rootDir, 0);
|
|
244
|
-
return results;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// src/lib/agents.ts
|
|
248
|
-
function findAgentsInDir(dir, maxDepth = 2) {
|
|
249
|
-
const entries = walkDir(dir, {
|
|
250
|
-
maxDepth,
|
|
251
|
-
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
252
|
-
});
|
|
253
|
-
return entries.map((entry) => ({
|
|
254
|
-
name: entry.name.replace(/\.md$/, ""),
|
|
255
|
-
file: entry.path,
|
|
256
|
-
category: entry.category
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
function extractAgentFrontmatter(content) {
|
|
260
|
-
const { data, parseError, body } = parseFrontmatter(content);
|
|
261
|
-
if (parseError) {
|
|
262
|
-
return { name: "", description: "", prompt: body.trim() };
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
name: extractString(data, "name"),
|
|
266
|
-
description: extractString(data, "description"),
|
|
267
|
-
prompt: body.trim(),
|
|
268
|
-
model: extractNonEmptyString(data, "model"),
|
|
269
|
-
temperature: extractNumber(data, "temperature"),
|
|
270
|
-
top_p: extractNumber(data, "top_p"),
|
|
271
|
-
tools: isToolsMap(data.tools) ? data.tools : undefined,
|
|
272
|
-
disable: extractBoolean(data, "disable"),
|
|
273
|
-
mode: isAgentMode(data.mode) ? data.mode : undefined,
|
|
274
|
-
color: extractNonEmptyString(data, "color"),
|
|
275
|
-
steps: extractNumber(data, "steps"),
|
|
276
|
-
hidden: extractBoolean(data, "hidden") ?? undefined,
|
|
277
|
-
permission: normalizePermission(data.permission)
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// src/lib/commands.ts
|
|
282
|
-
function findCommandsInDir(dir, maxDepth = 2) {
|
|
283
|
-
const entries = walkDir(dir, {
|
|
284
|
-
maxDepth,
|
|
285
|
-
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
286
|
-
});
|
|
287
|
-
return entries.map((entry) => {
|
|
288
|
-
const baseName = entry.name.replace(/\.md$/, "");
|
|
289
|
-
const commandName = entry.category ? `/${entry.category}:${baseName}` : `/${baseName}`;
|
|
290
|
-
return {
|
|
291
|
-
name: commandName,
|
|
292
|
-
file: entry.path,
|
|
293
|
-
category: entry.category
|
|
294
|
-
};
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
function extractCommandFrontmatter(content) {
|
|
298
|
-
const { data, parseError } = parseFrontmatter(content);
|
|
299
|
-
if (parseError) {
|
|
300
|
-
return {
|
|
301
|
-
name: "",
|
|
302
|
-
description: "",
|
|
303
|
-
argumentHint: "",
|
|
304
|
-
agent: undefined,
|
|
305
|
-
model: undefined,
|
|
306
|
-
subtask: undefined
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
const argumentHintRaw = extractString(data, "argument-hint");
|
|
310
|
-
return {
|
|
311
|
-
name: extractString(data, "name"),
|
|
312
|
-
description: extractString(data, "description"),
|
|
313
|
-
argumentHint: argumentHintRaw.replace(/^["']|["']$/g, ""),
|
|
314
|
-
agent: extractNonEmptyString(data, "agent"),
|
|
315
|
-
model: extractNonEmptyString(data, "model"),
|
|
316
|
-
subtask: extractBoolean(data, "subtask")
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// src/lib/converter.ts
|
|
321
|
-
import fs3 from "fs";
|
|
322
|
-
var CONVERTER_VERSION = 2;
|
|
323
|
-
var cache = new Map;
|
|
324
|
-
var TOOL_MAPPINGS = [
|
|
325
|
-
[/\bTask\s+tool\b/gi, "task tool"],
|
|
326
|
-
[/\bTask\s+([\w-]+)\s*:/g, "task $1:"],
|
|
327
|
-
[/\bTask\s+([\w-]+)\s*\(/g, "task $1("],
|
|
328
|
-
[/\bTask\s*\(/g, "task("],
|
|
329
|
-
[/\bTask\b(?=\s+to\s+\w)/g, "task"],
|
|
330
|
-
[/\bTodoWrite\b/g, "todowrite"],
|
|
331
|
-
[/\bAskUserQuestion\b/g, "question"],
|
|
332
|
-
[/\bWebSearch\b/g, "google_search"],
|
|
333
|
-
[/\bRead\b(?=\s+tool|\s+to\s+|\()/g, "read"],
|
|
334
|
-
[/\bWrite\b(?=\s+tool|\s+to\s+|\()/g, "write"],
|
|
335
|
-
[/\bEdit\b(?=\s+tool|\s+to\s+|\()/g, "edit"],
|
|
336
|
-
[/\bBash\b(?=\s+tool|\s+to\s+|\()/g, "bash"],
|
|
337
|
-
[/\bGrep\b(?=\s+tool|\s+to\s+|\()/g, "grep"],
|
|
338
|
-
[/\bGlob\b(?=\s+tool|\s+to\s+|\()/g, "glob"],
|
|
339
|
-
[/\bWebFetch\b/g, "webfetch"],
|
|
340
|
-
[/\bSkill\b(?=\s+tool|\s*\()/g, "skill"]
|
|
341
|
-
];
|
|
342
|
-
var PATH_REPLACEMENTS = [
|
|
343
|
-
[/\.claude\/skills\//g, ".opencode/skills/"],
|
|
344
|
-
[/\.claude\/commands\//g, ".opencode/commands/"],
|
|
345
|
-
[/\.claude\/agents\//g, ".opencode/agents/"],
|
|
346
|
-
[/~\/\.claude\//g, "~/.config/opencode/"],
|
|
347
|
-
[/CLAUDE\.md/g, "AGENTS.md"],
|
|
348
|
-
[/\/compound-engineering:/g, "/systematic:"],
|
|
349
|
-
[/compound-engineering:/g, "systematic:"]
|
|
350
|
-
];
|
|
351
|
-
var TOOL_NAME_MAP = {
|
|
352
|
-
task: "task",
|
|
353
|
-
todowrite: "todowrite",
|
|
354
|
-
askuserquestion: "question",
|
|
355
|
-
websearch: "google_search",
|
|
356
|
-
webfetch: "webfetch",
|
|
357
|
-
skill: "skill",
|
|
358
|
-
read: "read",
|
|
359
|
-
write: "write",
|
|
360
|
-
edit: "edit",
|
|
361
|
-
bash: "bash",
|
|
362
|
-
grep: "grep",
|
|
363
|
-
glob: "glob"
|
|
364
|
-
};
|
|
365
|
-
var PERMISSION_MODE_MAP = {
|
|
366
|
-
full: {
|
|
367
|
-
edit: "allow",
|
|
368
|
-
bash: "allow",
|
|
369
|
-
webfetch: "allow"
|
|
370
|
-
},
|
|
371
|
-
default: {
|
|
372
|
-
edit: "ask",
|
|
373
|
-
bash: "ask",
|
|
374
|
-
webfetch: "ask"
|
|
375
|
-
},
|
|
376
|
-
plan: {
|
|
377
|
-
edit: "deny",
|
|
378
|
-
bash: "deny",
|
|
379
|
-
webfetch: "ask"
|
|
380
|
-
},
|
|
381
|
-
bypassPermissions: {
|
|
382
|
-
edit: "allow",
|
|
383
|
-
bash: "allow",
|
|
384
|
-
webfetch: "allow"
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
function inferTemperature(name, description) {
|
|
388
|
-
const sample = `${name} ${description ?? ""}`.toLowerCase();
|
|
389
|
-
if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
|
|
390
|
-
return 0.1;
|
|
391
|
-
}
|
|
392
|
-
if (/(plan|planning|architecture|strategist|analysis|research)/.test(sample)) {
|
|
393
|
-
return 0.2;
|
|
394
|
-
}
|
|
395
|
-
if (/(doc|readme|changelog|editor|writer)/.test(sample)) {
|
|
396
|
-
return 0.3;
|
|
397
|
-
}
|
|
398
|
-
if (/(brainstorm|creative|ideate|design|concept)/.test(sample)) {
|
|
399
|
-
return 0.6;
|
|
400
|
-
}
|
|
401
|
-
return 0.3;
|
|
402
|
-
}
|
|
403
|
-
var CODE_BLOCK_PATTERN = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
404
|
-
function transformBody(body) {
|
|
405
|
-
const codeBlocks = [];
|
|
406
|
-
let placeholderIndex = 0;
|
|
407
|
-
const withPlaceholders = body.replace(CODE_BLOCK_PATTERN, (match) => {
|
|
408
|
-
codeBlocks.push(match);
|
|
409
|
-
return `__CODE_BLOCK_${placeholderIndex++}__`;
|
|
410
|
-
});
|
|
411
|
-
let result = withPlaceholders;
|
|
412
|
-
for (const [pattern, replacement] of TOOL_MAPPINGS) {
|
|
413
|
-
result = result.replace(pattern, replacement);
|
|
414
|
-
}
|
|
415
|
-
for (const [pattern, replacement] of PATH_REPLACEMENTS) {
|
|
416
|
-
result = result.replace(pattern, replacement);
|
|
417
|
-
}
|
|
418
|
-
for (let i = 0;i < codeBlocks.length; i++) {
|
|
419
|
-
result = result.replace(`__CODE_BLOCK_${i}__`, codeBlocks[i]);
|
|
420
|
-
}
|
|
421
|
-
return result;
|
|
422
|
-
}
|
|
423
|
-
function normalizeModel(model) {
|
|
424
|
-
if (model.includes("/"))
|
|
425
|
-
return model;
|
|
426
|
-
if (model === "inherit")
|
|
427
|
-
return model;
|
|
428
|
-
if (/^claude-/.test(model))
|
|
429
|
-
return `anthropic/${model}`;
|
|
430
|
-
if (/^(gpt-|o1-|o3-)/.test(model))
|
|
431
|
-
return `openai/${model}`;
|
|
432
|
-
if (/^gemini-/.test(model))
|
|
433
|
-
return `google/${model}`;
|
|
434
|
-
return `anthropic/${model}`;
|
|
435
|
-
}
|
|
436
|
-
function canonicalizeToolName(name) {
|
|
437
|
-
const lower = name.trim().toLowerCase();
|
|
438
|
-
return TOOL_NAME_MAP[lower] ?? lower;
|
|
439
|
-
}
|
|
440
|
-
function isValidSteps(value) {
|
|
441
|
-
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
|
|
442
|
-
}
|
|
443
|
-
function mapStepsField(data) {
|
|
444
|
-
if (data.steps !== undefined) {
|
|
445
|
-
if (isValidSteps(data.steps)) {
|
|
446
|
-
delete data.maxTurns;
|
|
447
|
-
delete data.maxSteps;
|
|
448
|
-
}
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
const candidates = [];
|
|
452
|
-
if (isValidSteps(data.maxTurns))
|
|
453
|
-
candidates.push(data.maxTurns);
|
|
454
|
-
if (isValidSteps(data.maxSteps))
|
|
455
|
-
candidates.push(data.maxSteps);
|
|
456
|
-
if (candidates.length > 0) {
|
|
457
|
-
data.steps = Math.min(...candidates);
|
|
458
|
-
delete data.maxTurns;
|
|
459
|
-
delete data.maxSteps;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
function mapToolsField(data) {
|
|
463
|
-
if (data.tools !== undefined && !Array.isArray(data.tools)) {
|
|
464
|
-
if (isToolsMap(data.tools)) {
|
|
465
|
-
mergeDisallowedTools(data);
|
|
466
|
-
}
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (Array.isArray(data.tools)) {
|
|
470
|
-
const toolsMap = {};
|
|
471
|
-
for (const tool of data.tools) {
|
|
472
|
-
if (typeof tool === "string") {
|
|
473
|
-
toolsMap[canonicalizeToolName(tool)] = true;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (Object.keys(toolsMap).length > 0) {
|
|
477
|
-
data.tools = toolsMap;
|
|
478
|
-
} else {
|
|
479
|
-
delete data.tools;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
mergeDisallowedTools(data);
|
|
483
|
-
}
|
|
484
|
-
function mergeDisallowedTools(data) {
|
|
485
|
-
if (!Array.isArray(data.disallowedTools))
|
|
486
|
-
return;
|
|
487
|
-
const existing = isToolsMap(data.tools) ? data.tools : {};
|
|
488
|
-
for (const tool of data.disallowedTools) {
|
|
489
|
-
if (typeof tool === "string") {
|
|
490
|
-
existing[canonicalizeToolName(tool)] = false;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
if (Object.keys(existing).length > 0) {
|
|
494
|
-
data.tools = existing;
|
|
495
|
-
}
|
|
496
|
-
delete data.disallowedTools;
|
|
497
|
-
}
|
|
498
|
-
function mapPermissionMode(data) {
|
|
499
|
-
if (data.permission !== undefined) {
|
|
500
|
-
const normalized = normalizePermission(data.permission);
|
|
501
|
-
if (normalized) {
|
|
502
|
-
data.permission = normalized;
|
|
503
|
-
delete data.permissionMode;
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (typeof data.permissionMode !== "string")
|
|
508
|
-
return;
|
|
509
|
-
const mapped = PERMISSION_MODE_MAP[data.permissionMode];
|
|
510
|
-
data.permission = mapped ?? { edit: "ask", bash: "ask", webfetch: "ask" };
|
|
511
|
-
delete data.permissionMode;
|
|
512
|
-
}
|
|
513
|
-
function mapHiddenField(data) {
|
|
514
|
-
if (data["disable-model-invocation"] === true || data.disableModelInvocation === true) {
|
|
515
|
-
data.hidden = true;
|
|
516
|
-
delete data["disable-model-invocation"];
|
|
517
|
-
delete data.disableModelInvocation;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
function normalizeModelField(data) {
|
|
521
|
-
if (typeof data.model === "string" && data.model !== "inherit") {
|
|
522
|
-
data.model = normalizeModel(data.model);
|
|
523
|
-
} else if (data.model === "inherit") {
|
|
524
|
-
delete data.model;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
function transformAgentFrontmatter(data, agentMode) {
|
|
528
|
-
const result = { ...data };
|
|
529
|
-
result.mode = isAgentMode(data.mode) ? data.mode : agentMode;
|
|
530
|
-
const name = typeof data.name === "string" ? data.name : "";
|
|
531
|
-
const description = typeof data.description === "string" ? data.description : "";
|
|
532
|
-
if (description) {
|
|
533
|
-
result.description = description;
|
|
534
|
-
} else if (name) {
|
|
535
|
-
result.description = `${name} agent`;
|
|
536
|
-
}
|
|
537
|
-
normalizeModelField(result);
|
|
538
|
-
result.temperature = typeof data.temperature === "number" ? data.temperature : inferTemperature(name, description);
|
|
539
|
-
mapStepsField(result);
|
|
540
|
-
mapToolsField(result);
|
|
541
|
-
mapPermissionMode(result);
|
|
542
|
-
mapHiddenField(result);
|
|
543
|
-
return result;
|
|
544
|
-
}
|
|
545
|
-
function transformSkillFrontmatter(data) {
|
|
546
|
-
const result = { ...data };
|
|
547
|
-
normalizeModelField(result);
|
|
548
|
-
if (result.context === "fork") {
|
|
549
|
-
result.subtask = true;
|
|
550
|
-
}
|
|
551
|
-
return result;
|
|
552
|
-
}
|
|
553
|
-
function transformCommandFrontmatter(data) {
|
|
554
|
-
const result = { ...data };
|
|
555
|
-
normalizeModelField(result);
|
|
556
|
-
return result;
|
|
557
|
-
}
|
|
558
|
-
function convertContent(content, type, options = {}) {
|
|
559
|
-
if (content === "")
|
|
560
|
-
return "";
|
|
561
|
-
const { data, body, hadFrontmatter, parseError } = parseFrontmatter(content);
|
|
562
|
-
if (!hadFrontmatter) {
|
|
563
|
-
return options.skipBodyTransform ? content : transformBody(content);
|
|
564
|
-
}
|
|
565
|
-
if (parseError) {
|
|
566
|
-
return options.skipBodyTransform ? content : transformBody(content);
|
|
567
|
-
}
|
|
568
|
-
const shouldTransformBody = !options.skipBodyTransform;
|
|
569
|
-
const transformedBody = shouldTransformBody ? transformBody(body) : body;
|
|
570
|
-
if (type === "agent") {
|
|
571
|
-
const agentMode = options.agentMode ?? "subagent";
|
|
572
|
-
const transformedData = transformAgentFrontmatter(data, agentMode);
|
|
573
|
-
return `${formatFrontmatter(transformedData)}
|
|
574
|
-
${transformedBody}`;
|
|
575
|
-
}
|
|
576
|
-
if (type === "skill") {
|
|
577
|
-
const transformedData = transformSkillFrontmatter(data);
|
|
578
|
-
return `${formatFrontmatter(transformedData)}
|
|
579
|
-
${transformedBody}`;
|
|
580
|
-
}
|
|
581
|
-
if (type === "command") {
|
|
582
|
-
const transformedData = transformCommandFrontmatter(data);
|
|
583
|
-
return `${formatFrontmatter(transformedData)}
|
|
584
|
-
${transformedBody}`;
|
|
585
|
-
}
|
|
586
|
-
return content;
|
|
587
|
-
}
|
|
588
|
-
function convertFileWithCache(filePath, type, options = {}) {
|
|
589
|
-
const fd = fs3.openSync(filePath, "r");
|
|
590
|
-
try {
|
|
591
|
-
const stats = fs3.fstatSync(fd);
|
|
592
|
-
const cacheKey = `${CONVERTER_VERSION}:${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}:${options.skipBodyTransform ?? false}`;
|
|
593
|
-
const cached = cache.get(cacheKey);
|
|
594
|
-
if (cached != null && cached.mtimeMs === stats.mtimeMs) {
|
|
595
|
-
return cached.converted;
|
|
596
|
-
}
|
|
597
|
-
const content = fs3.readFileSync(fd, "utf8");
|
|
598
|
-
const converted = convertContent(content, type, options);
|
|
599
|
-
cache.set(cacheKey, { mtimeMs: stats.mtimeMs, converted });
|
|
600
|
-
return converted;
|
|
601
|
-
} finally {
|
|
602
|
-
fs3.closeSync(fd);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// src/lib/skills.ts
|
|
607
|
-
import fs4 from "fs";
|
|
608
|
-
import path3 from "path";
|
|
609
|
-
function extractFrontmatter(filePath) {
|
|
610
|
-
try {
|
|
611
|
-
const content = fs4.readFileSync(filePath, "utf8");
|
|
612
|
-
const { data, parseError } = parseFrontmatter(content);
|
|
613
|
-
if (parseError) {
|
|
614
|
-
return { name: "", description: "" };
|
|
615
|
-
}
|
|
616
|
-
const metadataRaw = data.metadata;
|
|
617
|
-
let metadata;
|
|
618
|
-
if (isRecord(metadataRaw)) {
|
|
619
|
-
const entries = Object.entries(metadataRaw);
|
|
620
|
-
if (entries.every(([, v]) => typeof v === "string")) {
|
|
621
|
-
metadata = Object.fromEntries(entries);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
|
|
625
|
-
const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
|
|
626
|
-
return {
|
|
627
|
-
name: extractString(data, "name"),
|
|
628
|
-
description: extractString(data, "description"),
|
|
629
|
-
license: extractNonEmptyString(data, "license"),
|
|
630
|
-
compatibility: extractNonEmptyString(data, "compatibility"),
|
|
631
|
-
metadata,
|
|
632
|
-
disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
|
|
633
|
-
userInvocable: extractBoolean(data, "user-invocable"),
|
|
634
|
-
subtask: data.context === "fork" ? true : undefined,
|
|
635
|
-
agent: extractNonEmptyString(data, "agent"),
|
|
636
|
-
model: extractNonEmptyString(data, "model"),
|
|
637
|
-
argumentHint: argumentHint !== "" ? argumentHint : undefined,
|
|
638
|
-
allowedTools: extractNonEmptyString(data, "allowed-tools")
|
|
639
|
-
};
|
|
640
|
-
} catch {
|
|
641
|
-
return { name: "", description: "" };
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
function findSkillsInDir(dir, maxDepth = 3) {
|
|
645
|
-
const skills = [];
|
|
646
|
-
const entries = walkDir(dir, {
|
|
647
|
-
maxDepth,
|
|
648
|
-
filter: (e) => e.isDirectory
|
|
649
|
-
});
|
|
650
|
-
for (const entry of entries) {
|
|
651
|
-
const skillFile = path3.join(entry.path, "SKILL.md");
|
|
652
|
-
if (fs4.existsSync(skillFile)) {
|
|
653
|
-
const frontmatter = extractFrontmatter(skillFile);
|
|
654
|
-
skills.push({
|
|
655
|
-
path: entry.path,
|
|
656
|
-
skillFile,
|
|
657
|
-
name: frontmatter.name || entry.name,
|
|
658
|
-
description: frontmatter.description || "",
|
|
659
|
-
license: frontmatter.license,
|
|
660
|
-
compatibility: frontmatter.compatibility,
|
|
661
|
-
metadata: frontmatter.metadata,
|
|
662
|
-
disableModelInvocation: frontmatter.disableModelInvocation,
|
|
663
|
-
userInvocable: frontmatter.userInvocable,
|
|
664
|
-
subtask: frontmatter.subtask,
|
|
665
|
-
agent: frontmatter.agent,
|
|
666
|
-
model: frontmatter.model,
|
|
667
|
-
argumentHint: frontmatter.argumentHint,
|
|
668
|
-
allowedTools: frontmatter.allowedTools
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
return skills;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
export { parseFrontmatter, loadConfig, getConfigPaths, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, convertContent, convertFileWithCache, findSkillsInDir };
|