@fro.bot/systematic 1.0.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 +158 -0
- package/agents/research/framework-docs-researcher.md +19 -0
- package/agents/review/architecture-strategist.md +23 -0
- package/agents/review/code-simplicity-reviewer.md +30 -0
- package/agents/review/pattern-recognition-specialist.md +24 -0
- package/agents/review/performance-oracle.md +25 -0
- package/agents/review/security-sentinel.md +25 -0
- package/commands/agent-native-audit.md +277 -0
- package/commands/create-agent-skill.md +8 -0
- package/commands/deepen-plan.md +546 -0
- package/commands/lfg.md +19 -0
- package/commands/workflows/brainstorm.md +115 -0
- package/commands/workflows/compound.md +202 -0
- package/commands/workflows/plan.md +551 -0
- package/commands/workflows/review.md +514 -0
- package/commands/workflows/work.md +363 -0
- package/dist/cli.js +360 -0
- package/dist/index-v8dhd5s2.js +194 -0
- package/dist/index.js +297 -0
- package/package.json +69 -0
- package/skills/agent-browser/SKILL.md +223 -0
- package/skills/agent-native-architecture/SKILL.md +435 -0
- package/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
- package/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
- package/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
- package/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
- package/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
- package/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
- package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
- package/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
- package/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
- package/skills/agent-native-architecture/references/product-implications.md +443 -0
- package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
- package/skills/agent-native-architecture/references/self-modification.md +269 -0
- package/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
- package/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
- package/skills/brainstorming/SKILL.md +190 -0
- package/skills/compound-docs/SKILL.md +510 -0
- package/skills/compound-docs/assets/critical-pattern-template.md +34 -0
- package/skills/compound-docs/assets/resolution-template.md +93 -0
- package/skills/compound-docs/references/yaml-schema.md +65 -0
- package/skills/compound-docs/schema.yaml +176 -0
- package/skills/create-agent-skills/SKILL.md +299 -0
- package/skills/create-agent-skills/references/api-security.md +226 -0
- package/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
- package/skills/create-agent-skills/references/best-practices.md +404 -0
- package/skills/create-agent-skills/references/common-patterns.md +595 -0
- package/skills/create-agent-skills/references/core-principles.md +437 -0
- package/skills/create-agent-skills/references/executable-code.md +175 -0
- package/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
- package/skills/create-agent-skills/references/official-spec.md +185 -0
- package/skills/create-agent-skills/references/recommended-structure.md +168 -0
- package/skills/create-agent-skills/references/skill-structure.md +372 -0
- package/skills/create-agent-skills/references/using-scripts.md +113 -0
- package/skills/create-agent-skills/references/using-templates.md +112 -0
- package/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
- package/skills/create-agent-skills/templates/router-skill.md +73 -0
- package/skills/create-agent-skills/templates/simple-skill.md +33 -0
- package/skills/create-agent-skills/workflows/add-reference.md +96 -0
- package/skills/create-agent-skills/workflows/add-script.md +93 -0
- package/skills/create-agent-skills/workflows/add-template.md +74 -0
- package/skills/create-agent-skills/workflows/add-workflow.md +120 -0
- package/skills/create-agent-skills/workflows/audit-skill.md +138 -0
- package/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
- package/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
- package/skills/create-agent-skills/workflows/get-guidance.md +121 -0
- package/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
- package/skills/create-agent-skills/workflows/verify-skill.md +204 -0
- package/skills/file-todos/SKILL.md +251 -0
- package/skills/file-todos/assets/todo-template.md +155 -0
- package/skills/git-worktree/SKILL.md +302 -0
- package/skills/git-worktree/scripts/worktree-manager.sh +345 -0
- package/skills/using-systematic/SKILL.md +94 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
findAgentsInDir,
|
|
4
|
+
findCommandsInDir,
|
|
5
|
+
findSkillsInDir
|
|
6
|
+
} from "./index-v8dhd5s2.js";
|
|
7
|
+
|
|
8
|
+
// src/cli.ts
|
|
9
|
+
import fs2 from "node:fs";
|
|
10
|
+
import path2 from "node:path";
|
|
11
|
+
|
|
12
|
+
// src/lib/converter.ts
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
function parseFrontmatter(raw) {
|
|
16
|
+
const lines = raw.split(/\r?\n/);
|
|
17
|
+
if (lines.length === 0 || lines[0].trim() !== "---") {
|
|
18
|
+
return { data: {}, body: raw };
|
|
19
|
+
}
|
|
20
|
+
let endIndex = -1;
|
|
21
|
+
for (let i = 1;i < lines.length; i++) {
|
|
22
|
+
if (lines[i].trim() === "---") {
|
|
23
|
+
endIndex = i;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (endIndex === -1) {
|
|
28
|
+
return { data: {}, body: raw };
|
|
29
|
+
}
|
|
30
|
+
const yamlLines = lines.slice(1, endIndex);
|
|
31
|
+
const body = lines.slice(endIndex + 1).join(`
|
|
32
|
+
`);
|
|
33
|
+
const data = {};
|
|
34
|
+
for (const line of yamlLines) {
|
|
35
|
+
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
36
|
+
if (match) {
|
|
37
|
+
const [, key, value] = match;
|
|
38
|
+
if (value === "true")
|
|
39
|
+
data[key] = true;
|
|
40
|
+
else if (value === "false")
|
|
41
|
+
data[key] = false;
|
|
42
|
+
else if (/^\d+(\.\d+)?$/.test(value))
|
|
43
|
+
data[key] = parseFloat(value);
|
|
44
|
+
else
|
|
45
|
+
data[key] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { data, body };
|
|
49
|
+
}
|
|
50
|
+
function formatFrontmatter(data, body) {
|
|
51
|
+
const lines = [];
|
|
52
|
+
if (data.description)
|
|
53
|
+
lines.push(`description: ${data.description}`);
|
|
54
|
+
if (data.mode)
|
|
55
|
+
lines.push(`mode: ${data.mode}`);
|
|
56
|
+
if (data.model)
|
|
57
|
+
lines.push(`model: ${data.model}`);
|
|
58
|
+
if (data.temperature !== undefined)
|
|
59
|
+
lines.push(`temperature: ${data.temperature}`);
|
|
60
|
+
if (lines.length === 0)
|
|
61
|
+
return body;
|
|
62
|
+
return ["---", ...lines, "---", "", body].join(`
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
function inferTemperature(name, description) {
|
|
66
|
+
const sample = `${name} ${description || ""}`.toLowerCase();
|
|
67
|
+
if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
|
|
68
|
+
return 0.1;
|
|
69
|
+
}
|
|
70
|
+
if (/(plan|planning|architecture|strategist|analysis|research)/.test(sample)) {
|
|
71
|
+
return 0.2;
|
|
72
|
+
}
|
|
73
|
+
if (/(doc|readme|changelog|editor|writer)/.test(sample)) {
|
|
74
|
+
return 0.3;
|
|
75
|
+
}
|
|
76
|
+
if (/(brainstorm|creative|ideate|design|concept)/.test(sample)) {
|
|
77
|
+
return 0.6;
|
|
78
|
+
}
|
|
79
|
+
return 0.3;
|
|
80
|
+
}
|
|
81
|
+
function normalizeModel(model) {
|
|
82
|
+
if (model.includes("/"))
|
|
83
|
+
return model;
|
|
84
|
+
if (/^claude-/.test(model))
|
|
85
|
+
return `anthropic/${model}`;
|
|
86
|
+
if (/^(gpt-|o1-|o3-)/.test(model))
|
|
87
|
+
return `openai/${model}`;
|
|
88
|
+
if (/^gemini-/.test(model))
|
|
89
|
+
return `google/${model}`;
|
|
90
|
+
return `anthropic/${model}`;
|
|
91
|
+
}
|
|
92
|
+
function convertAgent(sourcePath, options = {}) {
|
|
93
|
+
const content = fs.readFileSync(sourcePath, "utf-8");
|
|
94
|
+
const name = path.basename(sourcePath, ".md");
|
|
95
|
+
const { data, body } = parseFrontmatter(content);
|
|
96
|
+
const existingDescription = data.description;
|
|
97
|
+
const existingModel = data.model;
|
|
98
|
+
let description = existingDescription;
|
|
99
|
+
if (!description) {
|
|
100
|
+
const firstLine = body.split(`
|
|
101
|
+
`).find((l) => l.trim() && !l.startsWith("#"));
|
|
102
|
+
description = firstLine?.slice(0, 100) || `${name} agent`;
|
|
103
|
+
}
|
|
104
|
+
const frontmatter = {
|
|
105
|
+
description,
|
|
106
|
+
mode: "subagent",
|
|
107
|
+
temperature: inferTemperature(name, description)
|
|
108
|
+
};
|
|
109
|
+
if (existingModel && existingModel !== "inherit") {
|
|
110
|
+
frontmatter.model = normalizeModel(existingModel);
|
|
111
|
+
}
|
|
112
|
+
const converted = formatFrontmatter(frontmatter, body.trim());
|
|
113
|
+
const outputPath = options.output || sourcePath;
|
|
114
|
+
if (!options.dryRun) {
|
|
115
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
116
|
+
fs.writeFileSync(outputPath, converted);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
type: "agent",
|
|
120
|
+
sourcePath,
|
|
121
|
+
outputPath,
|
|
122
|
+
converted: true,
|
|
123
|
+
files: [path.basename(outputPath)]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function convertCommand(sourcePath, options = {}) {
|
|
127
|
+
const content = fs.readFileSync(sourcePath, "utf-8");
|
|
128
|
+
const outputPath = options.output || sourcePath;
|
|
129
|
+
if (!options.dryRun) {
|
|
130
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
131
|
+
fs.writeFileSync(outputPath, content);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
type: "command",
|
|
135
|
+
sourcePath,
|
|
136
|
+
outputPath,
|
|
137
|
+
converted: true,
|
|
138
|
+
files: [path.basename(outputPath)]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function convertSkill(sourcePath, options = {}) {
|
|
142
|
+
const stats = fs.statSync(sourcePath);
|
|
143
|
+
if (!stats.isDirectory()) {
|
|
144
|
+
throw new Error(`Skill source must be a directory: ${sourcePath}`);
|
|
145
|
+
}
|
|
146
|
+
const skillName = path.basename(sourcePath);
|
|
147
|
+
const outputPath = options.output || sourcePath;
|
|
148
|
+
const files = [];
|
|
149
|
+
function copyDir(src, dest) {
|
|
150
|
+
if (!options.dryRun) {
|
|
151
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
154
|
+
const srcPath = path.join(src, entry.name);
|
|
155
|
+
const destPath = path.join(dest, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
copyDir(srcPath, destPath);
|
|
158
|
+
} else {
|
|
159
|
+
files.push(path.relative(outputPath, destPath));
|
|
160
|
+
if (!options.dryRun) {
|
|
161
|
+
fs.copyFileSync(srcPath, destPath);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
copyDir(sourcePath, outputPath);
|
|
167
|
+
return {
|
|
168
|
+
type: "skill",
|
|
169
|
+
sourcePath,
|
|
170
|
+
outputPath,
|
|
171
|
+
converted: true,
|
|
172
|
+
files
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function convert(type, sourcePath, options = {}) {
|
|
176
|
+
switch (type) {
|
|
177
|
+
case "agent":
|
|
178
|
+
return convertAgent(sourcePath, options);
|
|
179
|
+
case "command":
|
|
180
|
+
return convertCommand(sourcePath, options);
|
|
181
|
+
case "skill":
|
|
182
|
+
return convertSkill(sourcePath, options);
|
|
183
|
+
default:
|
|
184
|
+
throw new Error(`Unknown type: ${type}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/cli.ts
|
|
189
|
+
var VERSION = "0.1.0";
|
|
190
|
+
var HELP = `
|
|
191
|
+
systematic - OpenCode plugin for systematic engineering workflows
|
|
192
|
+
|
|
193
|
+
Usage:
|
|
194
|
+
systematic <command> [options]
|
|
195
|
+
|
|
196
|
+
Commands:
|
|
197
|
+
list [type] List available skills, agents, or commands
|
|
198
|
+
convert <type> <source> [--output <path>] [--dry-run]
|
|
199
|
+
Convert Claude Code content to OpenCode format
|
|
200
|
+
Types: skill, agent, command
|
|
201
|
+
config [subcommand] Configuration management
|
|
202
|
+
show Show configuration
|
|
203
|
+
path Print config file locations
|
|
204
|
+
|
|
205
|
+
Options:
|
|
206
|
+
--output, -o Output path for convert command
|
|
207
|
+
--dry-run Preview conversion without writing files
|
|
208
|
+
-h, --help Show this help message
|
|
209
|
+
-v, --version Show version
|
|
210
|
+
|
|
211
|
+
Examples:
|
|
212
|
+
systematic list skills
|
|
213
|
+
systematic convert skill /path/to/cep/skills/agent-browser -o ./skills/agent-browser
|
|
214
|
+
systematic convert agent /path/to/agent.md --dry-run
|
|
215
|
+
`;
|
|
216
|
+
function getUserConfigDir() {
|
|
217
|
+
return path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".config/opencode");
|
|
218
|
+
}
|
|
219
|
+
function getProjectConfigDir() {
|
|
220
|
+
return path2.join(process.cwd(), ".opencode");
|
|
221
|
+
}
|
|
222
|
+
function listItems(type) {
|
|
223
|
+
const packageRoot = path2.resolve(import.meta.dirname, "..");
|
|
224
|
+
const bundledDir = packageRoot;
|
|
225
|
+
let finder;
|
|
226
|
+
let subdir;
|
|
227
|
+
switch (type) {
|
|
228
|
+
case "skills":
|
|
229
|
+
finder = findSkillsInDir;
|
|
230
|
+
subdir = "skills";
|
|
231
|
+
break;
|
|
232
|
+
case "agents":
|
|
233
|
+
finder = findAgentsInDir;
|
|
234
|
+
subdir = "agents";
|
|
235
|
+
break;
|
|
236
|
+
case "commands":
|
|
237
|
+
finder = findCommandsInDir;
|
|
238
|
+
subdir = "commands";
|
|
239
|
+
break;
|
|
240
|
+
default:
|
|
241
|
+
console.error(`Unknown type: ${type}. Use: skills, agents, commands`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
const items = finder(path2.join(bundledDir, subdir), "bundled");
|
|
245
|
+
if (items.length === 0) {
|
|
246
|
+
console.log(`No ${type} found.`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
console.log(`Available ${type}:
|
|
250
|
+
`);
|
|
251
|
+
for (const item of items.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
252
|
+
console.log(` ${item.name} (${item.sourceType})`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function configShow() {
|
|
256
|
+
const userDir = getUserConfigDir();
|
|
257
|
+
const projectDir = getProjectConfigDir();
|
|
258
|
+
console.log(`Configuration locations:
|
|
259
|
+
`);
|
|
260
|
+
console.log(` User config: ${path2.join(userDir, "systematic.json")}`);
|
|
261
|
+
console.log(` Project config: ${path2.join(projectDir, "systematic.json")}`);
|
|
262
|
+
const projectConfig = path2.join(projectDir, "systematic.json");
|
|
263
|
+
if (fs2.existsSync(projectConfig)) {
|
|
264
|
+
console.log(`
|
|
265
|
+
Project configuration:`);
|
|
266
|
+
console.log(fs2.readFileSync(projectConfig, "utf-8"));
|
|
267
|
+
}
|
|
268
|
+
const userConfig = path2.join(userDir, "systematic.json");
|
|
269
|
+
if (fs2.existsSync(userConfig)) {
|
|
270
|
+
console.log(`
|
|
271
|
+
User configuration:`);
|
|
272
|
+
console.log(fs2.readFileSync(userConfig, "utf-8"));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function configPath() {
|
|
276
|
+
const userDir = getUserConfigDir();
|
|
277
|
+
const projectDir = getProjectConfigDir();
|
|
278
|
+
console.log("Config file paths:");
|
|
279
|
+
console.log(` User: ${path2.join(userDir, "systematic.json")}`);
|
|
280
|
+
console.log(` Project: ${path2.join(projectDir, "systematic.json")}`);
|
|
281
|
+
}
|
|
282
|
+
function runConvert(args) {
|
|
283
|
+
const typeArg = args[1];
|
|
284
|
+
const sourceArg = args[2];
|
|
285
|
+
if (!typeArg || !sourceArg) {
|
|
286
|
+
console.error("Usage: systematic convert <type> <source> [--output <path>] [--dry-run]");
|
|
287
|
+
console.error("Types: skill, agent, command");
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
const validTypes = ["skill", "agent", "command"];
|
|
291
|
+
if (!validTypes.includes(typeArg)) {
|
|
292
|
+
console.error(`Invalid type: ${typeArg}. Must be one of: ${validTypes.join(", ")}`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
const sourcePath = path2.resolve(sourceArg);
|
|
296
|
+
if (!fs2.existsSync(sourcePath)) {
|
|
297
|
+
console.error(`Source not found: ${sourcePath}`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
const outputIndex = args.findIndex((a) => a === "--output" || a === "-o");
|
|
301
|
+
const outputPath = outputIndex !== -1 ? path2.resolve(args[outputIndex + 1]) : undefined;
|
|
302
|
+
const dryRun = args.includes("--dry-run");
|
|
303
|
+
try {
|
|
304
|
+
const result = convert(typeArg, sourcePath, { output: outputPath, dryRun });
|
|
305
|
+
if (dryRun) {
|
|
306
|
+
console.log(`[DRY RUN] Would convert ${result.type}:`);
|
|
307
|
+
} else {
|
|
308
|
+
console.log(`Converted ${result.type}:`);
|
|
309
|
+
}
|
|
310
|
+
console.log(` Source: ${result.sourcePath}`);
|
|
311
|
+
console.log(` Output: ${result.outputPath}`);
|
|
312
|
+
console.log(" Files:");
|
|
313
|
+
for (const file of result.files) {
|
|
314
|
+
console.log(` - ${file}`);
|
|
315
|
+
}
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.error(`Conversion failed: ${err.message}`);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
var args = process.argv.slice(2);
|
|
322
|
+
var command = args[0];
|
|
323
|
+
switch (command) {
|
|
324
|
+
case "list":
|
|
325
|
+
listItems(args[1] || "skills");
|
|
326
|
+
break;
|
|
327
|
+
case "convert":
|
|
328
|
+
runConvert(args);
|
|
329
|
+
break;
|
|
330
|
+
case "config":
|
|
331
|
+
switch (args[1]) {
|
|
332
|
+
case "show":
|
|
333
|
+
case undefined:
|
|
334
|
+
configShow();
|
|
335
|
+
break;
|
|
336
|
+
case "path":
|
|
337
|
+
configPath();
|
|
338
|
+
break;
|
|
339
|
+
default:
|
|
340
|
+
console.error(`Unknown config subcommand: ${args[1]}`);
|
|
341
|
+
console.log("Available: show, path");
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
case "version":
|
|
346
|
+
case "--version":
|
|
347
|
+
case "-v":
|
|
348
|
+
console.log(`systematic v${VERSION}`);
|
|
349
|
+
break;
|
|
350
|
+
case "help":
|
|
351
|
+
case "--help":
|
|
352
|
+
case "-h":
|
|
353
|
+
case undefined:
|
|
354
|
+
console.log(HELP);
|
|
355
|
+
break;
|
|
356
|
+
default:
|
|
357
|
+
console.error(`Unknown command: ${command}`);
|
|
358
|
+
console.log(HELP);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// src/lib/skills-core.ts
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
function extractFrontmatter(filePath) {
|
|
5
|
+
try {
|
|
6
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
7
|
+
const lines = content.split(`
|
|
8
|
+
`);
|
|
9
|
+
let inFrontmatter = false;
|
|
10
|
+
let name = "";
|
|
11
|
+
let description = "";
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
if (line.trim() === "---") {
|
|
14
|
+
if (inFrontmatter)
|
|
15
|
+
break;
|
|
16
|
+
inFrontmatter = true;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (inFrontmatter) {
|
|
20
|
+
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
21
|
+
if (match) {
|
|
22
|
+
const [, key, value] = match;
|
|
23
|
+
if (key === "name")
|
|
24
|
+
name = value.trim();
|
|
25
|
+
if (key === "description")
|
|
26
|
+
description = value.trim();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { name, description };
|
|
31
|
+
} catch {
|
|
32
|
+
return { name: "", description: "" };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function stripFrontmatter(content) {
|
|
36
|
+
const lines = content.split(`
|
|
37
|
+
`);
|
|
38
|
+
let inFrontmatter = false;
|
|
39
|
+
let frontmatterEnded = false;
|
|
40
|
+
const contentLines = [];
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
if (line.trim() === "---") {
|
|
43
|
+
if (inFrontmatter) {
|
|
44
|
+
frontmatterEnded = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
inFrontmatter = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (frontmatterEnded || !inFrontmatter) {
|
|
51
|
+
contentLines.push(line);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return contentLines.join(`
|
|
55
|
+
`).trim();
|
|
56
|
+
}
|
|
57
|
+
function findSkillsInDir(dir, sourceType, maxDepth = 3) {
|
|
58
|
+
const skills = [];
|
|
59
|
+
if (!fs.existsSync(dir))
|
|
60
|
+
return skills;
|
|
61
|
+
function recurse(currentDir, depth) {
|
|
62
|
+
if (depth > maxDepth)
|
|
63
|
+
return;
|
|
64
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
const skillFile = path.join(fullPath, "SKILL.md");
|
|
69
|
+
if (fs.existsSync(skillFile)) {
|
|
70
|
+
const { name, description } = extractFrontmatter(skillFile);
|
|
71
|
+
skills.push({
|
|
72
|
+
path: fullPath,
|
|
73
|
+
skillFile,
|
|
74
|
+
name: name || entry.name,
|
|
75
|
+
description: description || "",
|
|
76
|
+
sourceType
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
recurse(fullPath, depth + 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
recurse(dir, 0);
|
|
84
|
+
return skills;
|
|
85
|
+
}
|
|
86
|
+
function findAgentsInDir(dir, sourceType, maxDepth = 2) {
|
|
87
|
+
const agents = [];
|
|
88
|
+
if (!fs.existsSync(dir))
|
|
89
|
+
return agents;
|
|
90
|
+
function recurse(currentDir, depth, category) {
|
|
91
|
+
if (depth > maxDepth)
|
|
92
|
+
return;
|
|
93
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
recurse(fullPath, depth + 1, entry.name);
|
|
98
|
+
} else if (entry.name.endsWith(".md")) {
|
|
99
|
+
agents.push({
|
|
100
|
+
name: entry.name.replace(/\.md$/, ""),
|
|
101
|
+
file: fullPath,
|
|
102
|
+
sourceType,
|
|
103
|
+
category
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
recurse(dir, 0);
|
|
109
|
+
return agents;
|
|
110
|
+
}
|
|
111
|
+
function findCommandsInDir(dir, sourceType, maxDepth = 2) {
|
|
112
|
+
const commands = [];
|
|
113
|
+
if (!fs.existsSync(dir))
|
|
114
|
+
return commands;
|
|
115
|
+
function recurse(currentDir, depth, category) {
|
|
116
|
+
if (depth > maxDepth)
|
|
117
|
+
return;
|
|
118
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
recurse(fullPath, depth + 1, entry.name);
|
|
123
|
+
} else if (entry.name.endsWith(".md")) {
|
|
124
|
+
const baseName = entry.name.replace(/\.md$/, "");
|
|
125
|
+
const commandName = category ? `/${category}:${baseName}` : `/${baseName}`;
|
|
126
|
+
commands.push({
|
|
127
|
+
name: commandName,
|
|
128
|
+
file: fullPath,
|
|
129
|
+
sourceType,
|
|
130
|
+
category
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
recurse(dir, 0);
|
|
136
|
+
return commands;
|
|
137
|
+
}
|
|
138
|
+
function extractAgentFrontmatter(content) {
|
|
139
|
+
const lines = content.split(`
|
|
140
|
+
`);
|
|
141
|
+
let inFrontmatter = false;
|
|
142
|
+
let name = "";
|
|
143
|
+
let description = "";
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
if (line.trim() === "---") {
|
|
146
|
+
if (inFrontmatter)
|
|
147
|
+
break;
|
|
148
|
+
inFrontmatter = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (inFrontmatter) {
|
|
152
|
+
const match = line.match(/^(\w+(?:-\w+)*):\s*(.*)$/);
|
|
153
|
+
if (match) {
|
|
154
|
+
const [, key, value] = match;
|
|
155
|
+
if (key === "name")
|
|
156
|
+
name = value.trim();
|
|
157
|
+
if (key === "description")
|
|
158
|
+
description = value.trim();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { name, description, prompt: stripFrontmatter(content) };
|
|
163
|
+
}
|
|
164
|
+
function extractCommandFrontmatter(content) {
|
|
165
|
+
const lines = content.split(`
|
|
166
|
+
`);
|
|
167
|
+
let inFrontmatter = false;
|
|
168
|
+
let name = "";
|
|
169
|
+
let description = "";
|
|
170
|
+
let argumentHint = "";
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
if (line.trim() === "---") {
|
|
173
|
+
if (inFrontmatter)
|
|
174
|
+
break;
|
|
175
|
+
inFrontmatter = true;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (inFrontmatter) {
|
|
179
|
+
const match = line.match(/^(\w+(?:-\w+)*):\s*(.*)$/);
|
|
180
|
+
if (match) {
|
|
181
|
+
const [, key, value] = match;
|
|
182
|
+
if (key === "name")
|
|
183
|
+
name = value.trim();
|
|
184
|
+
if (key === "description")
|
|
185
|
+
description = value.trim();
|
|
186
|
+
if (key === "argument-hint")
|
|
187
|
+
argumentHint = value.trim().replace(/^["']|["']$/g, "");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { name, description, argumentHint };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { stripFrontmatter, findSkillsInDir, findAgentsInDir, findCommandsInDir, extractAgentFrontmatter, extractCommandFrontmatter };
|