opencode-swarm-plugin 0.12.20 → 0.12.23

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.
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Skill Initializer - Creates a new skill from template
4
+ *
5
+ * Usage:
6
+ * bun scripts/init-skill.ts <skill-name> [--path <path>] [--global]
7
+ *
8
+ * Examples:
9
+ * bun scripts/init-skill.ts my-skill
10
+ * bun scripts/init-skill.ts my-skill --path .claude/skills
11
+ * bun scripts/init-skill.ts my-skill --global
12
+ */
13
+
14
+ import { mkdir, writeFile } from "fs/promises";
15
+ import { existsSync } from "fs";
16
+ import { join } from "path";
17
+ import { parseArgs } from "util";
18
+
19
+ const SKILL_TEMPLATE = (name: string, title: string) => `---
20
+ name: ${name}
21
+ description: [TODO: Complete description of what this skill does and WHEN to use it. Be specific about scenarios that trigger this skill.]
22
+ tags:
23
+ - [TODO: add tags]
24
+ ---
25
+
26
+ # ${title}
27
+
28
+ ## Overview
29
+
30
+ [TODO: 1-2 sentences explaining what this skill enables]
31
+
32
+ ## When to Use This Skill
33
+
34
+ [TODO: List specific scenarios when this skill should be activated:
35
+ - When working on X type of task
36
+ - When files matching Y pattern are involved
37
+ - When the user asks about Z topic]
38
+
39
+ ## Instructions
40
+
41
+ [TODO: Add actionable instructions for the agent. Use imperative form:
42
+ - "Read the configuration file first"
43
+ - "Check for existing patterns before creating new ones"
44
+ - "Always validate output before completing"]
45
+
46
+ ## Examples
47
+
48
+ ### Example 1: [TODO: Realistic scenario]
49
+
50
+ **User**: "[TODO: Example user request]"
51
+
52
+ **Process**:
53
+ 1. [TODO: Step-by-step process]
54
+ 2. [TODO: Next step]
55
+ 3. [TODO: Final step]
56
+
57
+ ## Resources
58
+
59
+ This skill may include additional resources:
60
+
61
+ ### scripts/
62
+ Executable scripts for automation. Run with \`skills_execute\`.
63
+
64
+ ### references/
65
+ Documentation loaded on-demand. Access with \`skills_read\`.
66
+
67
+ ---
68
+ *Delete any unused sections and this line when skill is complete.*
69
+ `;
70
+
71
+ const EXAMPLE_SCRIPT = (name: string) => `#!/usr/bin/env bash
72
+ # Example helper script for ${name}
73
+ #
74
+ # This is a placeholder. Replace with actual implementation or delete.
75
+ #
76
+ # Usage: skills_execute(skill: "${name}", script: "example.sh")
77
+
78
+ echo "Hello from ${name} skill!"
79
+ echo "Project directory: $1"
80
+
81
+ # TODO: Add actual script logic
82
+ `;
83
+
84
+ const REFERENCE_TEMPLATE = (title: string) => `# Reference Documentation for ${title}
85
+
86
+ ## Overview
87
+
88
+ [TODO: Detailed reference material for this skill]
89
+
90
+ ## API Reference
91
+
92
+ [TODO: If applicable, document APIs, schemas, or interfaces]
93
+
94
+ ## Detailed Workflows
95
+
96
+ [TODO: Complex multi-step workflows that don't fit in SKILL.md]
97
+
98
+ ## Troubleshooting
99
+
100
+ [TODO: Common issues and solutions]
101
+ `;
102
+
103
+ function titleCase(name: string): string {
104
+ return name
105
+ .split("-")
106
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
107
+ .join(" ");
108
+ }
109
+
110
+ async function initSkill(
111
+ name: string,
112
+ basePath: string,
113
+ isGlobal: boolean
114
+ ): Promise<void> {
115
+ // Validate name
116
+ if (!/^[a-z0-9-]+$/.test(name)) {
117
+ console.error("❌ Error: Skill name must be lowercase with hyphens only");
118
+ process.exit(1);
119
+ }
120
+
121
+ if (name.length > 64) {
122
+ console.error("❌ Error: Skill name must be 64 characters or less");
123
+ process.exit(1);
124
+ }
125
+
126
+ // Determine target directory
127
+ let skillDir: string;
128
+ if (isGlobal) {
129
+ const home = process.env.HOME || process.env.USERPROFILE || "~";
130
+ skillDir = join(home, ".config", "opencode", "skills", name);
131
+ } else {
132
+ skillDir = join(basePath, name);
133
+ }
134
+
135
+ // Check if exists
136
+ if (existsSync(skillDir)) {
137
+ console.error(`❌ Error: Skill directory already exists: ${skillDir}`);
138
+ process.exit(1);
139
+ }
140
+
141
+ const title = titleCase(name);
142
+ const createdFiles: string[] = [];
143
+
144
+ try {
145
+ // Create skill directory
146
+ await mkdir(skillDir, { recursive: true });
147
+ console.log(`✅ Created skill directory: ${skillDir}`);
148
+
149
+ // Create SKILL.md
150
+ const skillPath = join(skillDir, "SKILL.md");
151
+ await writeFile(skillPath, SKILL_TEMPLATE(name, title));
152
+ createdFiles.push("SKILL.md");
153
+ console.log("✅ Created SKILL.md");
154
+
155
+ // Create scripts/ directory with example
156
+ const scriptsDir = join(skillDir, "scripts");
157
+ await mkdir(scriptsDir, { recursive: true });
158
+ const scriptPath = join(scriptsDir, "example.sh");
159
+ await writeFile(scriptPath, EXAMPLE_SCRIPT(name), { mode: 0o755 });
160
+ createdFiles.push("scripts/example.sh");
161
+ console.log("✅ Created scripts/example.sh");
162
+
163
+ // Create references/ directory with example
164
+ const refsDir = join(skillDir, "references");
165
+ await mkdir(refsDir, { recursive: true });
166
+ const refPath = join(refsDir, "guide.md");
167
+ await writeFile(refPath, REFERENCE_TEMPLATE(title));
168
+ createdFiles.push("references/guide.md");
169
+ console.log("✅ Created references/guide.md");
170
+
171
+ console.log(`\n✅ Skill '${name}' initialized successfully at ${skillDir}`);
172
+ console.log("\nNext steps:");
173
+ console.log(" 1. Edit SKILL.md to complete TODO placeholders");
174
+ console.log(" 2. Update the description in frontmatter");
175
+ console.log(" 3. Add specific 'When to Use' scenarios");
176
+ console.log(" 4. Delete unused sections and placeholder files");
177
+ console.log(" 5. Test with skills_use to verify it works");
178
+ } catch (error) {
179
+ console.error(
180
+ `❌ Error: ${error instanceof Error ? error.message : String(error)}`
181
+ );
182
+ process.exit(1);
183
+ }
184
+ }
185
+
186
+ // Parse arguments
187
+ const { values, positionals } = parseArgs({
188
+ args: process.argv.slice(2),
189
+ options: {
190
+ path: { type: "string", default: ".opencode/skills" },
191
+ global: { type: "boolean", default: false },
192
+ help: { type: "boolean", short: "h", default: false },
193
+ },
194
+ allowPositionals: true,
195
+ });
196
+
197
+ if (values.help || positionals.length === 0) {
198
+ console.log(`
199
+ Skill Initializer - Creates a new skill from template
200
+
201
+ Usage:
202
+ bun scripts/init-skill.ts <skill-name> [options]
203
+
204
+ Options:
205
+ --path <path> Directory to create skill in (default: .opencode/skills)
206
+ --global Create in global ~/.config/opencode/skills directory
207
+ -h, --help Show this help message
208
+
209
+ Examples:
210
+ bun scripts/init-skill.ts my-skill
211
+ bun scripts/init-skill.ts my-skill --path .claude/skills
212
+ bun scripts/init-skill.ts my-skill --global
213
+
214
+ Skill name requirements:
215
+ - Lowercase letters, digits, and hyphens only
216
+ - Max 64 characters
217
+ `);
218
+ process.exit(values.help ? 0 : 1);
219
+ }
220
+
221
+ const skillName = positionals[0];
222
+ await initSkill(skillName, values.path!, values.global!);
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Skill Validator - Validates skill structure and content
4
+ *
5
+ * Usage:
6
+ * bun scripts/validate-skill.ts <path/to/skill>
7
+ *
8
+ * Examples:
9
+ * bun scripts/validate-skill.ts .opencode/skills/my-skill
10
+ * bun scripts/validate-skill.ts global-skills/debugging
11
+ */
12
+
13
+ import { readFile, readdir } from "fs/promises";
14
+ import { existsSync } from "fs";
15
+ import { join, basename } from "path";
16
+ import { parseFrontmatter } from "../src/skills.js";
17
+
18
+ interface ValidationResult {
19
+ valid: boolean;
20
+ errors: string[];
21
+ warnings: string[];
22
+ info: string[];
23
+ }
24
+
25
+ async function validateSkill(skillPath: string): Promise<ValidationResult> {
26
+ const result: ValidationResult = {
27
+ valid: true,
28
+ errors: [],
29
+ warnings: [],
30
+ info: [],
31
+ };
32
+
33
+ const skillName = basename(skillPath);
34
+
35
+ // Check directory exists
36
+ if (!existsSync(skillPath)) {
37
+ result.errors.push(`Skill directory does not exist: ${skillPath}`);
38
+ result.valid = false;
39
+ return result;
40
+ }
41
+
42
+ // Check SKILL.md exists
43
+ const skillMdPath = join(skillPath, "SKILL.md");
44
+ if (!existsSync(skillMdPath)) {
45
+ result.errors.push("Missing required SKILL.md file");
46
+ result.valid = false;
47
+ return result;
48
+ }
49
+
50
+ // Read and parse SKILL.md
51
+ const content = await readFile(skillMdPath, "utf-8");
52
+
53
+ // Check frontmatter
54
+ if (!content.startsWith("---\n") && !content.startsWith("---\r\n")) {
55
+ result.errors.push("SKILL.md must start with YAML frontmatter (---)");
56
+ result.valid = false;
57
+ return result;
58
+ }
59
+
60
+ const { metadata: frontmatter, body } = parseFrontmatter(content);
61
+ if (Object.keys(frontmatter).length === 0) {
62
+ result.errors.push("Invalid YAML frontmatter format");
63
+ result.valid = false;
64
+ return result;
65
+ }
66
+
67
+ // Validate required fields
68
+ if (!frontmatter.name) {
69
+ result.errors.push("Missing required 'name' field in frontmatter");
70
+ result.valid = false;
71
+ } else if (frontmatter.name !== skillName) {
72
+ result.warnings.push(
73
+ `Frontmatter name '${frontmatter.name}' doesn't match directory name '${skillName}'`,
74
+ );
75
+ }
76
+
77
+ if (!frontmatter.description) {
78
+ result.errors.push("Missing required 'description' field in frontmatter");
79
+ result.valid = false;
80
+ } else {
81
+ const desc = String(frontmatter.description);
82
+ if (desc.includes("[TODO")) {
83
+ result.warnings.push("Description contains TODO placeholder");
84
+ }
85
+ if (desc.length < 20) {
86
+ result.warnings.push("Description is very short (< 20 chars)");
87
+ }
88
+ if (desc.length > 500) {
89
+ result.warnings.push("Description is very long (> 500 chars)");
90
+ }
91
+ }
92
+
93
+ // Check for TODO placeholders in body
94
+ const todoCount = (body.match(/\[TODO/g) || []).length;
95
+ if (todoCount > 0) {
96
+ result.warnings.push(`Found ${todoCount} TODO placeholder(s) in body`);
97
+ }
98
+
99
+ // Check body length (body is already extracted by parseFrontmatter)
100
+ const lineCount = body.split("\n").length;
101
+ if (lineCount > 500) {
102
+ result.warnings.push(
103
+ `SKILL.md body is ${lineCount} lines (recommended < 500)`,
104
+ );
105
+ }
106
+
107
+ // Check for optional directories
108
+ const scriptsDir = join(skillPath, "scripts");
109
+ const refsDir = join(skillPath, "references");
110
+ const assetsDir = join(skillPath, "assets");
111
+
112
+ if (existsSync(scriptsDir)) {
113
+ const scripts = await readdir(scriptsDir);
114
+ result.info.push(`Found ${scripts.length} script(s) in scripts/`);
115
+
116
+ // Check for example placeholders
117
+ if (scripts.includes("example.sh") || scripts.includes("example.py")) {
118
+ result.warnings.push("Contains placeholder example script");
119
+ }
120
+ }
121
+
122
+ if (existsSync(refsDir)) {
123
+ const refs = await readdir(refsDir);
124
+ result.info.push(`Found ${refs.length} reference(s) in references/`);
125
+ }
126
+
127
+ if (existsSync(assetsDir)) {
128
+ const assets = await readdir(assetsDir);
129
+ result.info.push(`Found ${assets.length} asset(s) in assets/`);
130
+ }
131
+
132
+ // Check for unwanted files
133
+ const unwantedFiles = [
134
+ "README.md",
135
+ "CHANGELOG.md",
136
+ "INSTALLATION.md",
137
+ "CONTRIBUTING.md",
138
+ ];
139
+ for (const file of unwantedFiles) {
140
+ if (existsSync(join(skillPath, file))) {
141
+ result.warnings.push(
142
+ `Found ${file} - skills should only contain SKILL.md and resources`,
143
+ );
144
+ }
145
+ }
146
+
147
+ return result;
148
+ }
149
+
150
+ // Main
151
+ const skillPath = process.argv[2];
152
+
153
+ if (!skillPath) {
154
+ console.log(`
155
+ Skill Validator - Validates skill structure and content
156
+
157
+ Usage:
158
+ bun scripts/validate-skill.ts <path/to/skill>
159
+
160
+ Examples:
161
+ bun scripts/validate-skill.ts .opencode/skills/my-skill
162
+ bun scripts/validate-skill.ts global-skills/debugging
163
+ `);
164
+ process.exit(1);
165
+ }
166
+
167
+ console.log(`Validating skill: ${skillPath}\n`);
168
+
169
+ const result = await validateSkill(skillPath);
170
+
171
+ // Print results
172
+ if (result.errors.length > 0) {
173
+ console.log("❌ Errors:");
174
+ for (const error of result.errors) {
175
+ console.log(` - ${error}`);
176
+ }
177
+ console.log();
178
+ }
179
+
180
+ if (result.warnings.length > 0) {
181
+ console.log("⚠️ Warnings:");
182
+ for (const warning of result.warnings) {
183
+ console.log(` - ${warning}`);
184
+ }
185
+ console.log();
186
+ }
187
+
188
+ if (result.info.length > 0) {
189
+ console.log("ℹ️ Info:");
190
+ for (const info of result.info) {
191
+ console.log(` - ${info}`);
192
+ }
193
+ console.log();
194
+ }
195
+
196
+ if (result.valid) {
197
+ console.log("✅ Skill is valid!");
198
+ if (result.warnings.length > 0) {
199
+ console.log(" (but consider addressing warnings above)");
200
+ }
201
+ } else {
202
+ console.log("❌ Skill validation failed");
203
+ process.exit(1);
204
+ }
package/src/agent-mail.ts CHANGED
@@ -27,6 +27,35 @@ const AGENT_MAIL_URL = "http://127.0.0.1:8765";
27
27
  const DEFAULT_TTL_SECONDS = 3600; // 1 hour
28
28
  const MAX_INBOX_LIMIT = 5; // HARD CAP - never exceed this
29
29
 
30
+ /**
31
+ * Default project directory for Agent Mail operations
32
+ *
33
+ * This is set by the plugin init to the actual working directory (from OpenCode).
34
+ * Without this, tools might use the plugin's directory instead of the project's.
35
+ *
36
+ * Set this via setAgentMailProjectDirectory() before using tools.
37
+ */
38
+ let agentMailProjectDirectory: string | null = null;
39
+
40
+ /**
41
+ * Set the default project directory for Agent Mail operations
42
+ *
43
+ * Called during plugin initialization with the actual project directory.
44
+ * This ensures agentmail_init uses the correct project path by default.
45
+ */
46
+ export function setAgentMailProjectDirectory(directory: string): void {
47
+ agentMailProjectDirectory = directory;
48
+ }
49
+
50
+ /**
51
+ * Get the default project directory
52
+ *
53
+ * Returns the configured directory, or falls back to cwd if not set.
54
+ */
55
+ export function getAgentMailProjectDirectory(): string {
56
+ return agentMailProjectDirectory || process.cwd();
57
+ }
58
+
30
59
  // Retry configuration
31
60
  const RETRY_CONFIG = {
32
61
  maxRetries: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_RETRIES || "3"),
@@ -1077,7 +1106,10 @@ export const agentmail_init = tool({
1077
1106
  args: {
1078
1107
  project_path: tool.schema
1079
1108
  .string()
1080
- .describe("Absolute path to the project/repo"),
1109
+ .optional()
1110
+ .describe(
1111
+ "Absolute path to the project/repo (defaults to current working directory)",
1112
+ ),
1081
1113
  agent_name: tool.schema
1082
1114
  .string()
1083
1115
  .optional()
@@ -1088,6 +1120,10 @@ export const agentmail_init = tool({
1088
1120
  .describe("Description of current task"),
1089
1121
  },
1090
1122
  async execute(args, ctx) {
1123
+ // Use provided path or fall back to configured project directory
1124
+ // This prevents using the plugin's directory when working in a different project
1125
+ const projectPath = args.project_path || getAgentMailProjectDirectory();
1126
+
1091
1127
  // Check if Agent Mail is available
1092
1128
  const available = await checkAgentMailAvailable();
1093
1129
  if (!available) {
@@ -1113,12 +1149,12 @@ export const agentmail_init = tool({
1113
1149
  try {
1114
1150
  // 1. Ensure project exists
1115
1151
  const project = await mcpCall<ProjectInfo>("ensure_project", {
1116
- human_key: args.project_path,
1152
+ human_key: projectPath,
1117
1153
  });
1118
1154
 
1119
1155
  // 2. Register agent
1120
1156
  const agent = await mcpCall<AgentInfo>("register_agent", {
1121
- project_key: args.project_path,
1157
+ project_key: projectPath,
1122
1158
  program: "opencode",
1123
1159
  model: "claude-opus-4",
1124
1160
  name: args.agent_name, // undefined = auto-generate
@@ -1127,7 +1163,7 @@ export const agentmail_init = tool({
1127
1163
 
1128
1164
  // 3. Store state using sessionID
1129
1165
  const state: AgentMailState = {
1130
- projectKey: args.project_path,
1166
+ projectKey: projectPath,
1131
1167
  agentName: agent.name,
1132
1168
  reservations: [],
1133
1169
  startedAt: new Date().toISOString(),
package/src/beads.ts CHANGED
@@ -159,6 +159,11 @@ function buildCreateCommand(args: BeadCreateArgs): string[] {
159
159
  parts.push("--parent", args.parent_id);
160
160
  }
161
161
 
162
+ // Custom ID for human-readable bead names (e.g., 'phase-0', 'phase-1.e2e-test')
163
+ if (args.id) {
164
+ parts.push("--id", args.id);
165
+ }
166
+
162
167
  parts.push("--json");
163
168
  return parts;
164
169
  }
@@ -288,12 +293,22 @@ export const beads_create_epic = tool({
288
293
  .string()
289
294
  .optional()
290
295
  .describe("Epic description"),
296
+ epic_id: tool.schema
297
+ .string()
298
+ .optional()
299
+ .describe("Custom ID for the epic (e.g., 'phase-0')"),
291
300
  subtasks: tool.schema
292
301
  .array(
293
302
  tool.schema.object({
294
303
  title: tool.schema.string(),
295
304
  priority: tool.schema.number().min(0).max(3).optional(),
296
305
  files: tool.schema.array(tool.schema.string()).optional(),
306
+ id_suffix: tool.schema
307
+ .string()
308
+ .optional()
309
+ .describe(
310
+ "Custom ID suffix (e.g., 'e2e-test' becomes 'phase-0.e2e-test')",
311
+ ),
297
312
  }),
298
313
  )
299
314
  .describe("Subtasks to create under the epic"),
@@ -309,6 +324,7 @@ export const beads_create_epic = tool({
309
324
  type: "epic",
310
325
  priority: 1,
311
326
  description: validated.epic_description,
327
+ id: validated.epic_id,
312
328
  });
313
329
 
314
330
  const epicResult = await runBdCommand(epicCmd.slice(1)); // Remove 'bd' prefix
@@ -326,11 +342,19 @@ export const beads_create_epic = tool({
326
342
 
327
343
  // 2. Create subtasks
328
344
  for (const subtask of validated.subtasks) {
345
+ // Build subtask ID: if epic has custom ID and subtask has suffix, combine them
346
+ // e.g., epic_id='phase-0', id_suffix='e2e-test' → 'phase-0.e2e-test'
347
+ let subtaskId: string | undefined;
348
+ if (validated.epic_id && subtask.id_suffix) {
349
+ subtaskId = `${validated.epic_id}.${subtask.id_suffix}`;
350
+ }
351
+
329
352
  const subtaskCmd = buildCreateCommand({
330
353
  title: subtask.title,
331
354
  type: "task",
332
355
  priority: subtask.priority ?? 2,
333
356
  parent_id: epic.id,
357
+ id: subtaskId,
334
358
  });
335
359
 
336
360
  const subtaskResult = await runBdCommand(subtaskCmd.slice(1)); // Remove 'bd' prefix
package/src/index.ts CHANGED
@@ -25,12 +25,14 @@ import type { Plugin, PluginInput, Hooks } from "@opencode-ai/plugin";
25
25
  import { beadsTools, setBeadsWorkingDirectory } from "./beads";
26
26
  import {
27
27
  agentMailTools,
28
+ setAgentMailProjectDirectory,
28
29
  type AgentMailState,
29
30
  AGENT_MAIL_URL,
30
31
  } from "./agent-mail";
31
32
  import { structuredTools } from "./structured";
32
33
  import { swarmTools } from "./swarm";
33
34
  import { repoCrawlTools } from "./repo-crawl";
35
+ import { skillsTools, setSkillsProjectDirectory } from "./skills";
34
36
 
35
37
  /**
36
38
  * OpenCode Swarm Plugin
@@ -41,6 +43,7 @@ import { repoCrawlTools } from "./repo-crawl";
41
43
  * - structured:* - Structured output parsing and validation
42
44
  * - swarm:* - Swarm orchestration and task decomposition
43
45
  * - repo-crawl:* - GitHub API tools for repository research
46
+ * - skills:* - Agent skills discovery, activation, and execution
44
47
  *
45
48
  * @param input - Plugin context from OpenCode
46
49
  * @returns Plugin hooks including tools, events, and tool execution hooks
@@ -54,6 +57,15 @@ export const SwarmPlugin: Plugin = async (
54
57
  // This ensures bd runs in the project directory, not ~/.config/opencode
55
58
  setBeadsWorkingDirectory(directory);
56
59
 
60
+ // Set the project directory for skills discovery
61
+ // Skills are discovered from .opencode/skills/, .claude/skills/, or skills/
62
+ setSkillsProjectDirectory(directory);
63
+
64
+ // Set the project directory for Agent Mail
65
+ // This ensures agentmail_init uses the correct project path by default
66
+ // (prevents using plugin directory when working in a different project)
67
+ setAgentMailProjectDirectory(directory);
68
+
57
69
  /** Track active sessions for cleanup */
58
70
  let activeAgentMailState: AgentMailState | null = null;
59
71
 
@@ -116,6 +128,7 @@ export const SwarmPlugin: Plugin = async (
116
128
  ...structuredTools,
117
129
  ...swarmTools,
118
130
  ...repoCrawlTools,
131
+ ...skillsTools,
119
132
  },
120
133
 
121
134
  /**
@@ -239,6 +252,11 @@ export {
239
252
  AgentMailNotInitializedError,
240
253
  FileReservationConflictError,
241
254
  createAgentMailError,
255
+ setAgentMailProjectDirectory,
256
+ getAgentMailProjectDirectory,
257
+ mcpCallWithAutoInit,
258
+ isProjectNotFoundError,
259
+ isAgentNotFoundError,
242
260
  type AgentMailState,
243
261
  } from "./agent-mail";
244
262
 
@@ -305,6 +323,7 @@ export const allTools = {
305
323
  ...structuredTools,
306
324
  ...swarmTools,
307
325
  ...repoCrawlTools,
326
+ ...skillsTools,
308
327
  } as const;
309
328
 
310
329
  /**
@@ -387,3 +406,33 @@ export {
387
406
  * - Graceful rate limit handling
388
407
  */
389
408
  export { repoCrawlTools, RepoCrawlError } from "./repo-crawl";
409
+
410
+ /**
411
+ * Re-export skills module
412
+ *
413
+ * Implements Anthropic's Agent Skills specification for OpenCode.
414
+ *
415
+ * Includes:
416
+ * - skillsTools - All skills tools (list, use, execute, read)
417
+ * - discoverSkills, getSkill, listSkills - Discovery functions
418
+ * - parseFrontmatter - YAML frontmatter parser
419
+ * - getSkillsContextForSwarm - Swarm integration helper
420
+ * - findRelevantSkills - Task-based skill matching
421
+ *
422
+ * Types:
423
+ * - Skill, SkillMetadata, SkillRef - Skill data types
424
+ */
425
+ export {
426
+ skillsTools,
427
+ discoverSkills,
428
+ getSkill,
429
+ listSkills,
430
+ parseFrontmatter,
431
+ setSkillsProjectDirectory,
432
+ invalidateSkillsCache,
433
+ getSkillsContextForSwarm,
434
+ findRelevantSkills,
435
+ type Skill,
436
+ type SkillMetadata,
437
+ type SkillRef,
438
+ } from "./skills";