@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/index.js CHANGED
@@ -1,69 +1,124 @@
1
1
  // @bun
2
2
  import {
3
- convertContent,
4
3
  convertFileWithCache,
5
4
  extractAgentFrontmatter,
6
5
  extractCommandFrontmatter,
7
6
  findAgentsInDir,
8
7
  findCommandsInDir,
9
8
  findSkillsInDir,
9
+ formatSkillsXml,
10
+ loadConfig,
10
11
  stripFrontmatter
11
- } from "./index-hkk4125w.js";
12
+ } from "./index-ymsavt2y.js";
12
13
 
13
14
  // src/index.ts
14
- import fs3 from "fs";
15
- import os2 from "os";
16
- import path3 from "path";
15
+ import fs2 from "fs";
16
+ import path4 from "path";
17
17
  import { fileURLToPath } from "url";
18
18
 
19
- // src/lib/config.ts
19
+ // src/lib/bootstrap.ts
20
20
  import fs from "fs";
21
- import path from "path";
22
21
  import os from "os";
23
- import { parse as parseJsonc } from "jsonc-parser";
24
- var DEFAULT_CONFIG = {
25
- disabled_skills: [],
26
- disabled_agents: [],
27
- disabled_commands: [],
28
- bootstrap: {
29
- enabled: true
22
+ import path from "path";
23
+ function getToolMappingTemplate(bundledSkillsDir) {
24
+ return `**Tool Mapping for OpenCode:**
25
+ When skills reference tools you don't have, substitute OpenCode equivalents:
26
+ - \`TodoWrite\` \u2192 \`update_plan\`
27
+ - \`Task\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)
28
+ - \`Skill\` tool \u2192 OpenCode's native \`skill\` tool
29
+ - \`SystematicSkill\` tool \u2192 \`systematic_skill\` (Systematic plugin skills)
30
+ - \`Read\`, \`Write\`, \`Edit\`, \`Bash\` \u2192 Your native tools
31
+
32
+ **Skills naming:**
33
+ - Bundled skills use the \`systematic:\` prefix (e.g., \`systematic:brainstorming\`)
34
+ - Skills can also be invoked without prefix if unambiguous
35
+
36
+ **Skills usage:**
37
+ - Use \`systematic_skill\` to load Systematic bundled skills
38
+ - Use the native \`skill\` tool for non-Systematic skills
39
+
40
+ **Skills location:**
41
+ Bundled skills are in \`${bundledSkillsDir}/\``;
42
+ }
43
+ function getBootstrapContent(config, deps) {
44
+ const { bundledSkillsDir } = deps;
45
+ if (!config.bootstrap.enabled)
46
+ return null;
47
+ if (config.bootstrap.file) {
48
+ const customPath = config.bootstrap.file.startsWith("~/") ? path.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
49
+ if (fs.existsSync(customPath)) {
50
+ return fs.readFileSync(customPath, "utf8");
51
+ }
30
52
  }
31
- };
32
- function loadJsoncFile(filePath) {
33
- try {
34
- if (!fs.existsSync(filePath))
35
- return null;
36
- const content = fs.readFileSync(filePath, "utf-8");
37
- return parseJsonc(content);
38
- } catch {
53
+ const usingSystematicPath = path.join(bundledSkillsDir, "using-systematic/SKILL.md");
54
+ if (!fs.existsSync(usingSystematicPath))
39
55
  return null;
56
+ const fullContent = fs.readFileSync(usingSystematicPath, "utf8");
57
+ const content = stripFrontmatter(fullContent);
58
+ const toolMapping = getToolMappingTemplate(bundledSkillsDir);
59
+ return `<SYSTEMATIC_WORKFLOWS>
60
+ You have access to structured engineering workflows via the systematic plugin.
61
+
62
+ **IMPORTANT: The using-systematic skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the systematic_skill tool to load "using-systematic" again - that would be redundant.**
63
+
64
+ ${content}
65
+
66
+ ${toolMapping}
67
+ </SYSTEMATIC_WORKFLOWS>`;
68
+ }
69
+
70
+ // src/lib/skill-loader.ts
71
+ import path2 from "path";
72
+ var SKILL_PREFIX = "systematic:";
73
+ var SKILL_DESCRIPTION_PREFIX = "(systematic - Skill) ";
74
+ function formatSkillCommandName(name) {
75
+ if (name.startsWith(SKILL_PREFIX)) {
76
+ return name;
40
77
  }
78
+ return `${SKILL_PREFIX}${name}`;
41
79
  }
42
- function mergeArraysUnique(arr1, arr2) {
43
- const set = new Set;
44
- if (arr1)
45
- arr1.forEach((item) => set.add(item));
46
- if (arr2)
47
- arr2.forEach((item) => set.add(item));
48
- return Array.from(set);
80
+ function formatSkillDescription(description, fallbackName) {
81
+ const desc = description || `${fallbackName} skill`;
82
+ if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
83
+ return desc;
84
+ }
85
+ return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
49
86
  }
50
- function loadConfig(projectDir) {
51
- const homeDir = os.homedir();
52
- const userConfigPath = path.join(homeDir, ".config/opencode/systematic.json");
53
- const projectConfigPath = path.join(projectDir, ".opencode/systematic.json");
54
- const userConfig = loadJsoncFile(userConfigPath);
55
- const projectConfig = loadJsoncFile(projectConfigPath);
56
- const result = {
57
- disabled_skills: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_skills, userConfig?.disabled_skills), projectConfig?.disabled_skills),
58
- disabled_agents: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents),
59
- disabled_commands: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands),
60
- bootstrap: {
61
- ...DEFAULT_CONFIG.bootstrap,
62
- ...userConfig?.bootstrap,
63
- ...projectConfig?.bootstrap
64
- }
65
- };
66
- return result;
87
+ function wrapSkillTemplate(skillPath, body) {
88
+ const skillDir = path2.dirname(skillPath);
89
+ return `<skill-instruction>
90
+ Base directory for this skill: ${skillDir}/
91
+ File references (@path) in this skill are relative to this directory.
92
+
93
+ ${body.trim()}
94
+ </skill-instruction>
95
+
96
+ <user-request>
97
+ $ARGUMENTS
98
+ </user-request>`;
99
+ }
100
+ function extractSkillBody(wrappedTemplate) {
101
+ const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
102
+ return match ? match[1].trim() : wrappedTemplate;
103
+ }
104
+ function loadSkill(skillInfo) {
105
+ try {
106
+ const converted = convertFileWithCache(skillInfo.skillFile, "skill", {
107
+ source: "bundled"
108
+ });
109
+ const body = stripFrontmatter(converted);
110
+ const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body);
111
+ return {
112
+ name: skillInfo.name,
113
+ prefixedName: formatSkillCommandName(skillInfo.name),
114
+ description: formatSkillDescription(skillInfo.description, skillInfo.name),
115
+ path: skillInfo.path,
116
+ skillFile: skillInfo.skillFile,
117
+ wrappedTemplate
118
+ };
119
+ } catch {
120
+ return null;
121
+ }
67
122
  }
68
123
 
69
124
  // src/lib/config-handler.ts
@@ -84,7 +139,9 @@ function loadAgentAsConfig(agentInfo) {
84
139
  }
85
140
  function loadCommandAsConfig(commandInfo) {
86
141
  try {
87
- const converted = convertFileWithCache(commandInfo.file, "command", { source: "bundled" });
142
+ const converted = convertFileWithCache(commandInfo.file, "command", {
143
+ source: "bundled"
144
+ });
88
145
  const { name, description } = extractCommandFrontmatter(converted);
89
146
  const cleanName = commandInfo.name.replace(/^\//, "");
90
147
  return {
@@ -95,20 +152,15 @@ function loadCommandAsConfig(commandInfo) {
95
152
  return null;
96
153
  }
97
154
  }
98
- function loadSkillAsCommand(skillInfo) {
99
- try {
100
- const converted = convertFileWithCache(skillInfo.skillFile, "skill", { source: "bundled" });
101
- return {
102
- template: stripFrontmatter(converted),
103
- description: skillInfo.description || `${skillInfo.name} skill`
104
- };
105
- } catch {
106
- return null;
107
- }
155
+ function loadSkillAsCommand(loaded) {
156
+ return {
157
+ template: loaded.wrappedTemplate,
158
+ description: loaded.description
159
+ };
108
160
  }
109
- function collectAgents(dir, sourceType, disabledAgents) {
161
+ function collectAgents(dir, disabledAgents) {
110
162
  const agents = {};
111
- const agentList = findAgentsInDir(dir, sourceType);
163
+ const agentList = findAgentsInDir(dir);
112
164
  for (const agentInfo of agentList) {
113
165
  if (disabledAgents.includes(agentInfo.name))
114
166
  continue;
@@ -119,9 +171,9 @@ function collectAgents(dir, sourceType, disabledAgents) {
119
171
  }
120
172
  return agents;
121
173
  }
122
- function collectCommands(dir, sourceType, disabledCommands) {
174
+ function collectCommands(dir, disabledCommands) {
123
175
  const commands = {};
124
- const commandList = findCommandsInDir(dir, sourceType);
176
+ const commandList = findCommandsInDir(dir);
125
177
  for (const commandInfo of commandList) {
126
178
  const cleanName = commandInfo.name.replace(/^\//, "");
127
179
  if (disabledCommands.includes(cleanName))
@@ -133,15 +185,15 @@ function collectCommands(dir, sourceType, disabledCommands) {
133
185
  }
134
186
  return commands;
135
187
  }
136
- function collectSkillsAsCommands(dir, sourceType, disabledSkills) {
188
+ function collectSkillsAsCommands(dir, disabledSkills) {
137
189
  const commands = {};
138
- const skillList = findSkillsInDir(dir, sourceType, 3);
190
+ const skillList = findSkillsInDir(dir);
139
191
  for (const skillInfo of skillList) {
140
192
  if (disabledSkills.includes(skillInfo.name))
141
193
  continue;
142
- const config = loadSkillAsCommand(skillInfo);
143
- if (config) {
144
- commands[skillInfo.name] = config;
194
+ const loaded = loadSkill(skillInfo);
195
+ if (loaded) {
196
+ commands[loaded.prefixedName] = loadSkillAsCommand(loaded);
145
197
  }
146
198
  }
147
199
  return commands;
@@ -150,9 +202,9 @@ function createConfigHandler(deps) {
150
202
  const { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } = deps;
151
203
  return async (config) => {
152
204
  const systematicConfig = loadConfig(directory);
153
- const bundledAgents = collectAgents(bundledAgentsDir, "bundled", systematicConfig.disabled_agents);
154
- const bundledCommands = collectCommands(bundledCommandsDir, "bundled", systematicConfig.disabled_commands);
155
- const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, "bundled", systematicConfig.disabled_skills);
205
+ const bundledAgents = collectAgents(bundledAgentsDir, systematicConfig.disabled_agents);
206
+ const bundledCommands = collectCommands(bundledCommandsDir, systematicConfig.disabled_commands);
207
+ const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, systematicConfig.disabled_skills);
156
208
  const existingAgents = config.agent ?? {};
157
209
  config.agent = {
158
210
  ...bundledAgents,
@@ -168,92 +220,32 @@ function createConfigHandler(deps) {
168
220
  }
169
221
 
170
222
  // src/lib/skill-tool.ts
171
- import fs2 from "fs";
172
- import path2 from "path";
223
+ import path3 from "path";
173
224
  import { tool } from "@opencode-ai/plugin/tool";
174
- var HOOK_KEY = "systematic_skill_tool_hooked";
175
- var SYSTEMATIC_MARKER = "__systematic_skill_tool__";
176
- var globalStore = globalThis;
177
- function getHookState() {
178
- let state = globalStore[HOOK_KEY];
179
- if (state == null) {
180
- state = {
181
- hookedTool: null,
182
- hookedDescription: null,
183
- initialized: false
184
- };
185
- globalStore[HOOK_KEY] = state;
186
- }
187
- return state;
188
- }
189
- function getHookedTool() {
190
- return getHookState().hookedTool;
191
- }
192
- function formatSkillsXml(skills) {
193
- if (skills.length === 0)
194
- return "";
195
- const skillsXml = skills.map((skill) => {
196
- const lines = [
197
- " <skill>",
198
- ` <name>systematic:${skill.name}</name>`,
199
- ` <description>${skill.description}</description>`
200
- ];
201
- lines.push(" </skill>");
202
- return lines.join(`
203
- `);
204
- }).join(`
205
- `);
206
- return `<available_skills>
207
- ${skillsXml}
208
- </available_skills>`;
209
- }
210
- function mergeDescriptions(baseDescription, hookedDescription, systematicSkillsXml) {
211
- if (hookedDescription == null || hookedDescription.trim() === "") {
212
- return `${baseDescription}
213
-
214
- ${systematicSkillsXml}`;
215
- }
216
- const availableSkillsMatch = hookedDescription.match(/<available_skills>([\s\S]*?)<\/available_skills>/);
217
- if (availableSkillsMatch) {
218
- const existingSkillsContent = availableSkillsMatch[1];
219
- const systematicSkillsContent = systematicSkillsXml.replace("<available_skills>", "").replace("</available_skills>", "").trim();
220
- const mergedContent = `<available_skills>
221
- ${systematicSkillsContent}
222
- ${existingSkillsContent}</available_skills>`;
223
- return hookedDescription.replace(/<available_skills>[\s\S]*?<\/available_skills>/, mergedContent);
224
- }
225
- return `${hookedDescription}
226
-
227
- ${systematicSkillsXml}`;
228
- }
229
- function wrapSkillContent(skillPath, content) {
230
- const skillDir = path2.dirname(skillPath);
231
- const converted = convertContent(content, "skill", { source: "bundled" });
232
- const body = stripFrontmatter(converted);
233
- return `<skill-instruction>
234
- Base directory for this skill: ${skillDir}/
235
- File references (@path) in this skill are relative to this directory.
236
-
237
- ${body.trim()}
238
- </skill-instruction>`;
239
- }
240
225
  function createSkillTool(options) {
241
226
  const { bundledSkillsDir, disabledSkills } = options;
242
227
  const getSystematicSkills = () => {
243
- return findSkillsInDir(bundledSkillsDir, "bundled", 3).filter((s) => !disabledSkills.includes(s.name)).sort((a, b) => a.name.localeCompare(b.name));
228
+ return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).map((skillInfo) => loadSkill(skillInfo)).filter((s) => s !== null).sort((a, b) => a.name.localeCompare(b.name));
244
229
  };
245
230
  const buildDescription = () => {
246
231
  const skills = getSystematicSkills();
247
- const systematicXml = formatSkillsXml(skills);
232
+ const skillInfos = skills.map((s) => ({
233
+ name: s.name,
234
+ description: s.description,
235
+ path: s.path,
236
+ skillFile: s.skillFile
237
+ }));
238
+ const systematicXml = formatSkillsXml(skillInfos);
248
239
  const baseDescription = `Load a skill to get detailed instructions for a specific task.
249
240
 
250
241
  Skills provide specialized knowledge and step-by-step guidance.
251
242
  Use this when a task matches an available skill's description.`;
252
- const hookState = getHookState();
253
- return mergeDescriptions(baseDescription, hookState.hookedDescription, systematicXml);
243
+ return `${baseDescription}
244
+
245
+ ${systematicXml}`;
254
246
  };
255
247
  let cachedDescription = null;
256
- const toolDef = tool({
248
+ return tool({
257
249
  get description() {
258
250
  if (cachedDescription == null) {
259
251
  cachedDescription = buildDescription();
@@ -269,98 +261,39 @@ Use this when a task matches an available skill's description.`;
269
261
  const skills = getSystematicSkills();
270
262
  const matchedSkill = skills.find((s) => s.name === normalizedName);
271
263
  if (matchedSkill) {
272
- try {
273
- const content = fs2.readFileSync(matchedSkill.skillFile, "utf8");
274
- const wrapped = wrapSkillContent(matchedSkill.skillFile, content);
275
- return `## Skill: systematic:${matchedSkill.name}
264
+ const body = extractSkillBody(matchedSkill.wrappedTemplate);
265
+ const dir = path3.dirname(matchedSkill.skillFile);
266
+ return `## Skill: ${matchedSkill.prefixedName}
276
267
 
277
- **Base directory**: ${matchedSkill.path}
268
+ **Base directory**: ${dir}
278
269
 
279
- ${wrapped}`;
280
- } catch (error) {
281
- const errorMessage = error instanceof Error ? error.message : String(error);
282
- throw new Error(`Failed to load skill "${requestedName}": ${errorMessage}`);
283
- }
284
- }
285
- const hookedTool = getHookedTool();
286
- if (hookedTool != null && typeof hookedTool.execute === "function") {
287
- try {
288
- return await hookedTool.execute(args);
289
- } catch {}
270
+ ${body}`;
290
271
  }
291
- const availableSystematic = skills.map((s) => `systematic:${s.name}`);
272
+ const availableSystematic = skills.map((s) => s.prefixedName);
292
273
  throw new Error(`Skill "${requestedName}" not found. Available systematic skills: ${availableSystematic.join(", ")}`);
293
274
  }
294
275
  });
295
- Object.defineProperty(toolDef, SYSTEMATIC_MARKER, {
296
- value: true,
297
- enumerable: false,
298
- writable: false
299
- });
300
- return toolDef;
301
276
  }
302
277
 
303
278
  // src/index.ts
304
- var __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
305
- var packageRoot = path3.resolve(__dirname2, "..");
306
- var bundledSkillsDir = path3.join(packageRoot, "skills");
307
- var bundledAgentsDir = path3.join(packageRoot, "agents");
308
- var bundledCommandsDir = path3.join(packageRoot, "commands");
309
- var packageJsonPath = path3.join(packageRoot, "package.json");
279
+ var __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
280
+ var packageRoot = path4.resolve(__dirname2, "..");
281
+ var bundledSkillsDir = path4.join(packageRoot, "skills");
282
+ var bundledAgentsDir = path4.join(packageRoot, "agents");
283
+ var bundledCommandsDir = path4.join(packageRoot, "commands");
284
+ var packageJsonPath = path4.join(packageRoot, "package.json");
310
285
  var hasLoggedInit = false;
311
286
  var getPackageVersion = () => {
312
287
  try {
313
- if (!fs3.existsSync(packageJsonPath))
288
+ if (!fs2.existsSync(packageJsonPath))
314
289
  return "unknown";
315
- const content = fs3.readFileSync(packageJsonPath, "utf8");
290
+ const content = fs2.readFileSync(packageJsonPath, "utf8");
316
291
  const parsed = JSON.parse(content);
317
292
  return parsed.version ?? "unknown";
318
293
  } catch {
319
294
  return "unknown";
320
295
  }
321
296
  };
322
- var getBootstrapContent = (config) => {
323
- if (!config.bootstrap.enabled)
324
- return null;
325
- if (config.bootstrap.file) {
326
- const customPath = config.bootstrap.file.startsWith("~/") ? path3.join(os2.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
327
- if (fs3.existsSync(customPath)) {
328
- return fs3.readFileSync(customPath, "utf8");
329
- }
330
- }
331
- const usingSystematicPath = path3.join(bundledSkillsDir, "using-systematic/SKILL.md");
332
- if (!fs3.existsSync(usingSystematicPath))
333
- return null;
334
- const fullContent = fs3.readFileSync(usingSystematicPath, "utf8");
335
- const content = stripFrontmatter(fullContent);
336
- const toolMapping = `**Tool Mapping for OpenCode:**
337
- When skills reference tools you don't have, substitute OpenCode equivalents:
338
- - \`TodoWrite\` \u2192 \`update_plan\`
339
- - \`Task\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)
340
- - \`Skill\` tool \u2192 OpenCode's native \`skill\` tool
341
- - \`SystematicSkill\` tool \u2192 \`systematic_skill\` (Systematic plugin skills)
342
- - \`Read\`, \`Write\`, \`Edit\`, \`Bash\` \u2192 Your native tools
343
-
344
- **Skills naming:**
345
- - Bundled skills use the \`systematic:\` prefix (e.g., \`systematic:brainstorming\`)
346
- - Skills can also be invoked without prefix if unambiguous
347
-
348
- **Skills usage:**
349
- - Use \`systematic_skill\` to load Systematic bundled skills
350
- - Use the native \`skill\` tool for non-Systematic skills
351
-
352
- **Skills location:**
353
- Bundled skills are in \`${bundledSkillsDir}/\``;
354
- return `<SYSTEMATIC_WORKFLOWS>
355
- You have access to structured engineering workflows via the systematic plugin.
356
-
357
- **IMPORTANT: The using-systematic skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the systematic_skill tool to load "using-systematic" again - that would be redundant.**
358
-
359
- ${content}
360
-
361
- ${toolMapping}
362
- </SYSTEMATIC_WORKFLOWS>`;
363
- };
364
297
  var SystematicPlugin = async ({ client, directory }) => {
365
298
  const config = loadConfig(directory);
366
299
  const configHandler = createConfigHandler({
@@ -403,7 +336,7 @@ var SystematicPlugin = async ({ client, directory }) => {
403
336
  if (existingSystem.includes("title generator") || existingSystem.includes("generate a title")) {
404
337
  return;
405
338
  }
406
- const content = getBootstrapContent(config);
339
+ const content = getBootstrapContent(config, { bundledSkillsDir });
407
340
  if (content) {
408
341
  if (!output.system) {
409
342
  output.system = [];
@@ -0,0 +1,12 @@
1
+ export interface AgentFrontmatter {
2
+ name: string;
3
+ description: string;
4
+ prompt: string;
5
+ }
6
+ export interface AgentInfo {
7
+ name: string;
8
+ file: string;
9
+ category?: string;
10
+ }
11
+ export declare function findAgentsInDir(dir: string, maxDepth?: number): AgentInfo[];
12
+ export declare function extractAgentFrontmatter(content: string): AgentFrontmatter;
@@ -0,0 +1,5 @@
1
+ import type { SystematicConfig } from './config.js';
2
+ export interface BootstrapDeps {
3
+ bundledSkillsDir: string;
4
+ }
5
+ export declare function getBootstrapContent(config: SystematicConfig, deps: BootstrapDeps): string | null;
@@ -0,0 +1,12 @@
1
+ export interface CommandFrontmatter {
2
+ name: string;
3
+ description: string;
4
+ argumentHint: string;
5
+ }
6
+ export interface CommandInfo {
7
+ name: string;
8
+ file: string;
9
+ category?: string;
10
+ }
11
+ export declare function findCommandsInDir(dir: string, maxDepth?: number): CommandInfo[];
12
+ export declare function extractCommandFrontmatter(content: string): CommandFrontmatter;
@@ -0,0 +1,17 @@
1
+ import type { Config } from '@opencode-ai/sdk';
2
+ export interface ConfigHandlerDeps {
3
+ directory: string;
4
+ bundledSkillsDir: string;
5
+ bundledAgentsDir: string;
6
+ bundledCommandsDir: string;
7
+ }
8
+ /**
9
+ * Create the config hook handler for the Systematic plugin.
10
+ *
11
+ * This follows the pattern used by oh-my-opencode to inject bundled agents,
12
+ * skills (as commands), and commands into OpenCode's configuration.
13
+ *
14
+ * Only bundled content is loaded. User/project overrides are not supported.
15
+ * Existing OpenCode config is preserved and takes precedence.
16
+ */
17
+ export declare function createConfigHandler(deps: ConfigHandlerDeps): (config: Config) => Promise<void>;
@@ -0,0 +1,18 @@
1
+ export interface BootstrapConfig {
2
+ enabled: boolean;
3
+ file?: string;
4
+ }
5
+ export interface SystematicConfig {
6
+ disabled_skills: string[];
7
+ disabled_agents: string[];
8
+ disabled_commands: string[];
9
+ bootstrap: BootstrapConfig;
10
+ }
11
+ export declare const DEFAULT_CONFIG: SystematicConfig;
12
+ export declare function loadConfig(projectDir: string): SystematicConfig;
13
+ export declare function getConfigPaths(projectDir: string): {
14
+ userConfig: string;
15
+ projectConfig: string;
16
+ userDir: string;
17
+ projectDir: string;
18
+ };
@@ -0,0 +1,12 @@
1
+ export type ContentType = 'skill' | 'agent' | 'command';
2
+ export type SourceType = 'bundled' | 'external';
3
+ export type AgentMode = 'primary' | 'subagent';
4
+ export interface ConvertOptions {
5
+ source?: SourceType;
6
+ agentMode?: AgentMode;
7
+ /** Skip body content transformations (tool names, paths, etc.) */
8
+ skipBodyTransform?: boolean;
9
+ }
10
+ export declare function convertContent(content: string, type: ContentType, options?: ConvertOptions): string;
11
+ export declare function convertFileWithCache(filePath: string, type: ContentType, options?: ConvertOptions): string;
12
+ export declare function clearConverterCache(): void;
@@ -0,0 +1,18 @@
1
+ export interface FrontmatterResult<T = Record<string, unknown>> {
2
+ data: T;
3
+ body: string;
4
+ hadFrontmatter: boolean;
5
+ parseError: boolean;
6
+ }
7
+ /**
8
+ * Parses YAML frontmatter from Markdown content.
9
+ *
10
+ * Uses js-yaml with JSON_SCHEMA for security (prevents code execution via YAML tags).
11
+ * Supports all standard YAML keys including hyphenated ones (e.g., 'argument-hint').
12
+ *
13
+ * @param content - Markdown content with optional frontmatter
14
+ * @returns Parsed frontmatter data, body content, and parsing status
15
+ */
16
+ export declare function parseFrontmatter<T = Record<string, unknown>>(content: string): FrontmatterResult<T>;
17
+ export declare function formatFrontmatter(data: Record<string, unknown>): string;
18
+ export declare function stripFrontmatter(content: string): string;
@@ -0,0 +1,14 @@
1
+ import type { SkillInfo } from './skills.js';
2
+ export interface LoadedSkill {
3
+ name: string;
4
+ prefixedName: string;
5
+ description: string;
6
+ path: string;
7
+ skillFile: string;
8
+ wrappedTemplate: string;
9
+ }
10
+ export declare function formatSkillCommandName(name: string): string;
11
+ export declare function formatSkillDescription(description: string, fallbackName: string): string;
12
+ export declare function wrapSkillTemplate(skillPath: string, body: string): string;
13
+ export declare function extractSkillBody(wrappedTemplate: string): string;
14
+ export declare function loadSkill(skillInfo: SkillInfo): LoadedSkill | null;
@@ -0,0 +1,6 @@
1
+ import type { ToolDefinition } from '@opencode-ai/plugin';
2
+ export interface SkillToolOptions {
3
+ bundledSkillsDir: string;
4
+ disabledSkills: string[];
5
+ }
6
+ export declare function createSkillTool(options: SkillToolOptions): ToolDefinition;
@@ -0,0 +1,13 @@
1
+ export interface SkillFrontmatter {
2
+ name: string;
3
+ description: string;
4
+ }
5
+ export interface SkillInfo {
6
+ path: string;
7
+ skillFile: string;
8
+ name: string;
9
+ description: string;
10
+ }
11
+ export declare function extractFrontmatter(filePath: string): SkillFrontmatter;
12
+ export declare function findSkillsInDir(dir: string, maxDepth?: number): SkillInfo[];
13
+ export declare function formatSkillsXml(skills: SkillInfo[]): string;
@@ -0,0 +1,12 @@
1
+ export interface WalkEntry {
2
+ path: string;
3
+ name: string;
4
+ isDirectory: boolean;
5
+ depth: number;
6
+ category?: string;
7
+ }
8
+ export interface WalkOptions {
9
+ maxDepth?: number;
10
+ filter?: (entry: WalkEntry) => boolean;
11
+ }
12
+ export declare function walkDir(rootDir: string, options?: WalkOptions): WalkEntry[];