@zenobius/opencode-skillful 1.0.0 β 1.1.0-next.3
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 +605 -48
- package/dist/index.js +1914 -487
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
An interpretation of the [Anthropic Agent Skills Specification](https://github.com/anthropics/skills) for OpenCode, providing lazy-loaded skill discovery and injection.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Differentiators include:
|
|
6
6
|
|
|
7
7
|
- Conversationally the agent uses `skill_find words, words words` to discover skills
|
|
8
8
|
- The agent uses `skill_use fully_resolved_skill_name` and,
|
|
@@ -68,20 +68,19 @@ skill_find "testing -performance"
|
|
|
68
68
|
|
|
69
69
|
## Features
|
|
70
70
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
71
|
+
- Discover SKILL.md files from multiple locations
|
|
72
|
+
- Lazy loading - skills only inject when explicitly requested
|
|
73
|
+
- Path prefix matching for organized skill browsing
|
|
74
|
+
- Natural query syntax with negation and quoted phrases
|
|
75
|
+
- Skill ranking by relevance (name matches weighted higher)
|
|
76
|
+
- Silent message insertion (noReply pattern)
|
|
77
77
|
|
|
78
78
|
## Skill Discovery Paths
|
|
79
79
|
|
|
80
80
|
Skills are discovered from these locations (in priority order, last wins on duplicates):
|
|
81
81
|
|
|
82
|
-
1. `~/.opencode/skills/` -
|
|
83
|
-
2.
|
|
84
|
-
3. `.opencode/skills/` - Project-local skills (highest priority)
|
|
82
|
+
1. `~/.config/opencode/skills/` - Standard XDG config location or (`%APPDATA%/.opencode/skills` for windows users)
|
|
83
|
+
2. `.opencode/skills/` - Project-local skills (highest priority)
|
|
85
84
|
|
|
86
85
|
## Usage in OpenCode
|
|
87
86
|
|
|
@@ -89,69 +88,598 @@ Skills are discovered from these locations (in priority order, last wins on dupl
|
|
|
89
88
|
|
|
90
89
|
```
|
|
91
90
|
# List all available skills
|
|
92
|
-
skill_find
|
|
93
|
-
query="*"
|
|
91
|
+
skill_find query="*"
|
|
94
92
|
|
|
95
93
|
# Search by keyword
|
|
96
|
-
skill_find
|
|
97
|
-
query="git commit"
|
|
98
|
-
|
|
99
|
-
# Path prefix matching
|
|
100
|
-
skill_find
|
|
101
|
-
query="experts/data-ai"
|
|
94
|
+
skill_find query="git commit"
|
|
102
95
|
|
|
103
96
|
# Exclude terms
|
|
104
|
-
skill_find
|
|
105
|
-
query="testing -performance"
|
|
97
|
+
skill_find query="testing -performance"
|
|
106
98
|
```
|
|
107
99
|
|
|
108
100
|
### Loading Skills
|
|
109
101
|
|
|
102
|
+
Skills must be loaded by their fully-qualified identifier (generated from the directory path).
|
|
103
|
+
|
|
104
|
+
The skill identifier is created by converting the directory path to a slug: directory separators and hyphens become underscores.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
skills/
|
|
108
|
+
experts/
|
|
109
|
+
ai/
|
|
110
|
+
agentic-engineer/ # Identifier: experts_ai_agentic_engineer
|
|
111
|
+
superpowers/
|
|
112
|
+
writing/
|
|
113
|
+
code-review/ # Identifier: superpowers_writing_code_review
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
When loading skills, use the full identifier:
|
|
117
|
+
|
|
110
118
|
```
|
|
111
|
-
# Load a
|
|
112
|
-
skill_use
|
|
113
|
-
skill_names=["writing-git-commits"]
|
|
119
|
+
# Load a skill by its identifier
|
|
120
|
+
skill_use skill_names=["experts_ai_agentic_engineer"]
|
|
114
121
|
|
|
115
122
|
# Load multiple skills
|
|
116
|
-
skill_use
|
|
117
|
-
skill_names=["writing-git-commits", "code-review"]
|
|
123
|
+
skill_use skill_names=["experts_ai_agentic_engineer", "superpowers_writing_code_review"]
|
|
118
124
|
```
|
|
119
125
|
|
|
120
126
|
### Reading Skill Resources
|
|
121
127
|
|
|
122
128
|
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
|
|
130
|
+
# Read a reference document
|
|
131
|
+
|
|
132
|
+
skill_resource skill_name="writing-git-commits" relative_path="references/style-guide.md"
|
|
133
|
+
|
|
134
|
+
# Read a template
|
|
135
|
+
|
|
136
|
+
skill_resource skill_name="brand-guidelines" relative_path="assets/logo-usage.html"
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Normally you won't need to use `skill_resource` directly, since the llm will do it for you.
|
|
141
|
+
|
|
142
|
+
When skill_use is called, it will be wrapped with instructions to the llm on
|
|
143
|
+
how to load references, assets, and scripts from the skill.
|
|
144
|
+
|
|
145
|
+
But when writing your skills, there's nothing stopping you from using `skill_resource` to be explicit.
|
|
146
|
+
|
|
147
|
+
(Just be aware that exotic file types might need special tooling to handle them properly.)
|
|
148
|
+
|
|
149
|
+
### Executing Skill Scripts
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
# Execute a script without arguments
|
|
154
|
+
|
|
155
|
+
skill_exec skill_name="build-utils" relative_path="scripts/generate-changelog.sh"
|
|
156
|
+
|
|
157
|
+
# Execute a script with arguments
|
|
158
|
+
|
|
159
|
+
skill_exec skill_name="code-generator" relative_path="scripts/gen.py" args=["--format", "json", "--output", "schema.json"]
|
|
160
|
+
|
|
127
161
|
```
|
|
128
162
|
|
|
129
163
|
## Plugin Tools
|
|
130
164
|
|
|
131
|
-
|
|
165
|
+
The plugin provides four core tools implemented in `src/tools/`:
|
|
166
|
+
|
|
167
|
+
### `skill_find` (SkillFinder.ts)
|
|
168
|
+
|
|
169
|
+
Search for skills using natural query syntax with intelligent ranking by relevance.
|
|
132
170
|
|
|
133
|
-
|
|
171
|
+
**Parameters:**
|
|
134
172
|
|
|
135
173
|
- `query`: Search string supporting:
|
|
136
174
|
- `*` or empty: List all skills
|
|
137
|
-
- Path prefixes: `experts`, `superpowers/writing`
|
|
138
|
-
- Keywords: `git commit`
|
|
139
|
-
- Negation: `-term`
|
|
140
|
-
- Quoted phrases: `"exact match"`
|
|
175
|
+
- Path prefixes: `experts`, `superpowers/writing` (prefix matching)
|
|
176
|
+
- Keywords: `git commit` (multiple terms use AND logic)
|
|
177
|
+
- Negation: `-term` (exclude results)
|
|
178
|
+
- Quoted phrases: `"exact match"` (phrase matching)
|
|
179
|
+
|
|
180
|
+
**Returns:**
|
|
181
|
+
|
|
182
|
+
- List of matching skills with:
|
|
183
|
+
- `skill_name`: Skill identifier (e.g., `experts_writing_git_commits`)
|
|
184
|
+
- `skill_shortname`: Directory name of the skill (e.g., `writing-git-commits`)
|
|
185
|
+
- `description`: Human-readable description
|
|
186
|
+
- If config.debug is enabled:
|
|
187
|
+
- Debug information: discovered count, parsed results, rejections, duplicates, errors
|
|
188
|
+
|
|
189
|
+
**Example Response:**
|
|
190
|
+
|
|
191
|
+
```xml
|
|
192
|
+
<SkillSearchResults query="git commit">
|
|
193
|
+
<Skills>
|
|
194
|
+
<Skill skill_name="experts_writing_git_commits" skill_shortname="writing-git-commits">
|
|
195
|
+
Guidelines for writing effective git commit messages
|
|
196
|
+
</Skill>
|
|
197
|
+
</Skills>
|
|
198
|
+
<Summary>
|
|
199
|
+
<Total>42</Total>
|
|
200
|
+
<Matches>1</Matches>
|
|
201
|
+
<Feedback>Found 1 skill matching your query</Feedback>
|
|
202
|
+
</Summary>
|
|
203
|
+
</SkillSearchResults>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `skill_use` (SkillUser.ts)
|
|
207
|
+
|
|
208
|
+
Load one or more skills into the chat context with full resource metadata.
|
|
209
|
+
|
|
210
|
+
**Parameters:**
|
|
211
|
+
|
|
212
|
+
- `skill_names`: Array of skill identifiers to load (e.g., `experts_ai_agentic_engineer`)
|
|
213
|
+
|
|
214
|
+
**Features:**
|
|
215
|
+
|
|
216
|
+
- Injects skill metadata and content including:
|
|
217
|
+
- Skill name and description
|
|
218
|
+
- Resource inventory:
|
|
219
|
+
- `references`: Documentation and reference files
|
|
220
|
+
- `assets`: Binary files, templates, images
|
|
221
|
+
- `scripts`: Executable scripts with paths and MIME types
|
|
222
|
+
- Full skill content formatted as Markdown for easy reading
|
|
223
|
+
- Base directory context for relative path resolution
|
|
224
|
+
|
|
225
|
+
**Behavior:** Silently injects skills as user messages (persists in conversation history)
|
|
226
|
+
|
|
227
|
+
**Example Response:**
|
|
141
228
|
|
|
142
|
-
|
|
229
|
+
```json
|
|
230
|
+
{ "loaded": ["experts/writing-git-commits"], "notFound": [] }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `skill_resource` (SkillResourceReader.ts)
|
|
143
234
|
|
|
144
|
-
|
|
235
|
+
Read a specific resource file from a skill's directory and inject silently into the chat.
|
|
145
236
|
|
|
146
|
-
|
|
237
|
+
**When to Use:**
|
|
147
238
|
|
|
148
|
-
|
|
239
|
+
- Load specific reference documents or templates from a skill
|
|
240
|
+
- Access supporting files without loading the entire skill
|
|
241
|
+
- Retrieve examples, guides, or configuration templates
|
|
242
|
+
- Most commonly used after loading a skill with `skill_use` to access its resources
|
|
149
243
|
|
|
150
|
-
|
|
244
|
+
**Parameters:**
|
|
151
245
|
|
|
152
|
-
- `skill_name`: The skill containing the resource
|
|
246
|
+
- `skill_name`: The skill containing the resource (by toolName/FQDN or short name)
|
|
153
247
|
- `relative_path`: Path to the resource relative to the skill directory
|
|
154
248
|
|
|
249
|
+
**Returns:**
|
|
250
|
+
|
|
251
|
+
- MIME type of the resource
|
|
252
|
+
- Success confirmation with skill name, resource path, and type
|
|
253
|
+
|
|
254
|
+
**Behavior:**
|
|
255
|
+
|
|
256
|
+
- Silently injects resource content without triggering AI response (noReply pattern)
|
|
257
|
+
- Resolves relative paths within skill directory (e.g., `references/guide.md`, `assets/template.html`, `scripts/setup.sh`)
|
|
258
|
+
- Supports any text or binary file type
|
|
259
|
+
|
|
260
|
+
**Example Response:**
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
Load Skill Resource
|
|
264
|
+
|
|
265
|
+
skill: experts/writing-git-commits
|
|
266
|
+
resource: templates/commit-template.md
|
|
267
|
+
type: text/markdown
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### `skill_exec` (SkillScriptExec.ts)
|
|
271
|
+
|
|
272
|
+
Execute scripts from skill resources with optional arguments.
|
|
273
|
+
|
|
274
|
+
**Parameters:**
|
|
275
|
+
|
|
276
|
+
- `skill_name`: The skill containing the script (by toolName/FQDN or short name)
|
|
277
|
+
- `relative_path`: Path to the script file relative to the skill directory
|
|
278
|
+
- `args`: Optional array of string arguments to pass to the script
|
|
279
|
+
|
|
280
|
+
**Returns:**
|
|
281
|
+
|
|
282
|
+
- Exit code of the script execution
|
|
283
|
+
- Standard output (stdout)
|
|
284
|
+
- Standard error (stderr)
|
|
285
|
+
- Formatted text representation
|
|
286
|
+
|
|
287
|
+
**Behavior:**
|
|
288
|
+
|
|
289
|
+
- Locates and executes scripts within skill directories
|
|
290
|
+
- Passes arguments to the script
|
|
291
|
+
- Silently injects execution results
|
|
292
|
+
- Includes proper error handling and reporting
|
|
293
|
+
|
|
294
|
+
**Example Response:**
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
Executed script from skill "build-utils": scripts/generate-changelog.sh
|
|
298
|
+
|
|
299
|
+
Exit Code: 0
|
|
300
|
+
STDOUT: Changelog generated successfully
|
|
301
|
+
STDERR: (none)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Error Handling
|
|
305
|
+
|
|
306
|
+
### skill_find Errors
|
|
307
|
+
|
|
308
|
+
When a search query doesn't match any skills, the response includes feedback:
|
|
309
|
+
|
|
310
|
+
```xml
|
|
311
|
+
<SkillSearchResults query="nonexistent">
|
|
312
|
+
<Skills></Skills>
|
|
313
|
+
<Summary>
|
|
314
|
+
<Total>42</Total>
|
|
315
|
+
<Matches>0</Matches>
|
|
316
|
+
<Feedback>No skills found matching your query</Feedback>
|
|
317
|
+
</Summary>
|
|
318
|
+
</SkillSearchResults>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### skill_use Errors
|
|
322
|
+
|
|
323
|
+
When loading skills, if a skill name is not found, it's returned in the `notFound` array:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{ "loaded": ["writing-git-commits"], "notFound": ["nonexistent-skill"] }
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The loaded skills are still injected into the conversation, allowing you to use available skills even if some are not found.
|
|
330
|
+
|
|
331
|
+
### skill_resource Errors
|
|
332
|
+
|
|
333
|
+
Resource loading failures occur when:
|
|
334
|
+
|
|
335
|
+
- The skill doesn't exist
|
|
336
|
+
- The resource path doesn't exist within the skill
|
|
337
|
+
- The file cannot be read due to permissions
|
|
338
|
+
|
|
339
|
+
The tool returns an error message indicating which skill or resource path was problematic.
|
|
340
|
+
|
|
341
|
+
### skill_exec Errors
|
|
342
|
+
|
|
343
|
+
Script execution includes exit codes and error output:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
Executed script from skill "build-utils": scripts/generate-changelog.sh
|
|
347
|
+
|
|
348
|
+
Exit Code: 1
|
|
349
|
+
STDOUT: (partial output)
|
|
350
|
+
STDERR: Permission denied: scripts/generate-changelog.sh
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Non-zero exit codes indicate script failures. Always check STDERR and the exit code when troubleshooting.
|
|
354
|
+
|
|
355
|
+
## Configuration
|
|
356
|
+
|
|
357
|
+
The plugin reads configuration from the OpenCode config file (`~/.config/opencode/config.json`):
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"plugins": ["@zenobius/opencode-skillful"],
|
|
362
|
+
"skillful": {
|
|
363
|
+
"debug": false,
|
|
364
|
+
"basePaths": ["~/.config/opencode/skills", ".opencode/skills"]
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Configuration Options
|
|
370
|
+
|
|
371
|
+
- **debug** (boolean, default: `false`): Enable debug output showing skill discovery stats
|
|
372
|
+
- When enabled, `skill_find` includes discovered, parsed, rejected, and duplicate counts
|
|
373
|
+
- Useful for diagnosing skill loading issues
|
|
374
|
+
- **basePaths** (array, default: standard locations): Custom skill search directories
|
|
375
|
+
- Paths are searched in order; later paths override earlier ones for duplicate skill names
|
|
376
|
+
- Use project-local `.opencode/skills/` for project-specific skills
|
|
377
|
+
|
|
378
|
+
## Architecture
|
|
379
|
+
|
|
380
|
+
### System Design Overview
|
|
381
|
+
|
|
382
|
+
The plugin uses a **layered, modular architecture** with clear separation of concerns and two-phase initialization.
|
|
383
|
+
|
|
384
|
+
#### Design Principles
|
|
385
|
+
|
|
386
|
+
- **Async Coordination**: ReadyStateMachine ensures tools don't execute before registry initialization completes
|
|
387
|
+
- **Security-First Resource Access**: All resources are pre-indexed at parse time; no path traversal possible
|
|
388
|
+
- **Factory Pattern**: Centralized API creation for easy testing and configuration management
|
|
389
|
+
- **Lazy Loading**: Skills only injected when explicitly requested, minimal memory overhead
|
|
390
|
+
|
|
391
|
+
### System Layers
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
395
|
+
β Plugin Entry Point (index.ts) β
|
|
396
|
+
β - Defines 3 core tools: skill_find, skill_use, β
|
|
397
|
+
β skill_resource β
|
|
398
|
+
β - Initializes API factory and config β
|
|
399
|
+
β - Manages message injection (XML serialization) β
|
|
400
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
401
|
+
β
|
|
402
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
403
|
+
β API Factory Layer (api.ts, config.ts) β
|
|
404
|
+
β - createApi(): initializes logger, registry, tools β
|
|
405
|
+
β - getPluginConfig(): resolves base paths with proper β
|
|
406
|
+
β precedence (project-local overrides global) β
|
|
407
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
408
|
+
β
|
|
409
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
410
|
+
β Services Layer (src/services/) β
|
|
411
|
+
β - SkillRegistry: discovery, parsing, resource β
|
|
412
|
+
β mapping with ReadyStateMachine coordination β
|
|
413
|
+
β - SkillSearcher: query parsing + intelligent rankingβ
|
|
414
|
+
β - SkillResourceResolver: safe path-based retrieval β
|
|
415
|
+
β - SkillFs (via lib): filesystem abstraction (mockable)
|
|
416
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
417
|
+
β
|
|
418
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
419
|
+
β Core Libraries (src/lib/) β
|
|
420
|
+
β - ReadyStateMachine: async initialization sequencing β
|
|
421
|
+
β - SkillFs: abstract filesystem operations β
|
|
422
|
+
β - Identifiers: path β tool name conversions β
|
|
423
|
+
β - OpenCodeChat: message injection abstraction β
|
|
424
|
+
β - xml.ts: JSON β XML serialization β
|
|
425
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Initialization Flow (Why This Matters)
|
|
429
|
+
|
|
430
|
+
The plugin uses a **two-phase initialization pattern** to coordinate async discovery with tool execution:
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
PHASE 1: SYNCHRONOUS CREATION
|
|
434
|
+
ββ Plugin loads configuration
|
|
435
|
+
ββ createApi() called:
|
|
436
|
+
β ββ Creates logger
|
|
437
|
+
β ββ Creates SkillRegistry (factory, NOT initialized yet)
|
|
438
|
+
β ββ Returns registry + tool creators
|
|
439
|
+
ββ index.ts registers tools immediately
|
|
440
|
+
|
|
441
|
+
PHASE 2: ASYNCHRONOUS DISCOVERY (Background)
|
|
442
|
+
ββ registry.initialise() called
|
|
443
|
+
β ββ Sets ready state to "loading"
|
|
444
|
+
β ββ Scans all base paths for SKILL.md files
|
|
445
|
+
β ββ Parses each file (YAML frontmatter + resources)
|
|
446
|
+
β ββ Pre-indexes all resources (scripts/assets/references)
|
|
447
|
+
β ββ Sets ready state to "ready"
|
|
448
|
+
β
|
|
449
|
+
ββ WHY PRE-INDEXING: Prevents path traversal attacks.
|
|
450
|
+
Tools can only retrieve pre-registered resources,
|
|
451
|
+
never arbitrary filesystem paths.
|
|
452
|
+
|
|
453
|
+
PHASE 3: TOOL EXECUTION (User Requested)
|
|
454
|
+
ββ User calls: skill_find("git commits")
|
|
455
|
+
ββ Tool executes:
|
|
456
|
+
β ββ await registry.controller.ready.whenReady()
|
|
457
|
+
β β (blocks until Phase 2 completes)
|
|
458
|
+
β ββ Search registry
|
|
459
|
+
β ββ Return results
|
|
460
|
+
ββ WHY THIS PATTERN: Multiple tools can call whenReady()
|
|
461
|
+
at different times without race conditions.
|
|
462
|
+
Simple Promise would resolve once; this allows
|
|
463
|
+
concurrent waiters.
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Data Flow Diagram: skill_find Query
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
470
|
+
β User Query: skill_find("git commit") β
|
|
471
|
+
ββββββββββββββ¬βββββββββββββββββββββββββββββ
|
|
472
|
+
β
|
|
473
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
474
|
+
β SkillFinder Tool (tools/SkillFinder.ts) β
|
|
475
|
+
β - Validates query string β
|
|
476
|
+
β - Awaits: controller.ready.whenReady() β
|
|
477
|
+
ββββββββββββββ¬βββββββββββββββββββββββββββββ
|
|
478
|
+
β (continues when registry ready)
|
|
479
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
480
|
+
β SkillSearcher.search() β
|
|
481
|
+
β - Parse query via search-string library β
|
|
482
|
+
β ("git commit" β include: [git,commit])β
|
|
483
|
+
β - Filter ALL include terms must match β
|
|
484
|
+
β (name, description, or toolName) β
|
|
485
|
+
β - Exclude results matching exclude β
|
|
486
|
+
β terms β
|
|
487
|
+
β - Rank results: β
|
|
488
|
+
β * nameMatches Γ 3 (strong signal) β
|
|
489
|
+
β * descMatches Γ 1 (weak signal) β
|
|
490
|
+
β * exact match bonus +10 β
|
|
491
|
+
ββββββββββββββ¬βββββββββββββββββββββββββββββ
|
|
492
|
+
β
|
|
493
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
494
|
+
β Results with Feedback β
|
|
495
|
+
β - Matched skills array β
|
|
496
|
+
β - Sorted by relevance score β
|
|
497
|
+
β - User-friendly feedback message β
|
|
498
|
+
β "Searching for: **git commit** | β
|
|
499
|
+
β Found 3 matches" β
|
|
500
|
+
ββββββββββββββ¬βββββββββββββββββββββββββββββ
|
|
501
|
+
β
|
|
502
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
503
|
+
β Format & Return (index.ts) β
|
|
504
|
+
β - Convert to XML via jsonToXml() β
|
|
505
|
+
β - Inject into message context β
|
|
506
|
+
β - Return to user β
|
|
507
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Data Flow Diagram: skill_use Loading
|
|
511
|
+
|
|
512
|
+
```
|
|
513
|
+
ββββββββββββββββββββββββββββββββββββ
|
|
514
|
+
β User: skill_use("git-commits") β
|
|
515
|
+
ββββββββββββββ¬ββββββββββββββββββββββ
|
|
516
|
+
β
|
|
517
|
+
ββββββββββββββββββββββββββββββββββββ
|
|
518
|
+
β SkillUser Tool (tools/SkillUser) β
|
|
519
|
+
β - Await ready state β
|
|
520
|
+
β - Validate skill names β
|
|
521
|
+
ββββββββββββββ¬ββββββββββββββββββββββ
|
|
522
|
+
β
|
|
523
|
+
ββββββββββββββββββββββββββββββββββββ
|
|
524
|
+
β Registry.controller.get(key) β
|
|
525
|
+
β - Look up skill in Map β
|
|
526
|
+
β - Return Skill object with: β
|
|
527
|
+
β * Name, description β
|
|
528
|
+
β * Content (markdown) β
|
|
529
|
+
β * Resource maps (indexed) β
|
|
530
|
+
ββββββββββββββ¬ββββββββββββββββββββββ
|
|
531
|
+
β
|
|
532
|
+
ββββββββββββββββββββββββββββββββββββ
|
|
533
|
+
β Format Skill for Injection β
|
|
534
|
+
β - Skill metadata β
|
|
535
|
+
β - Resource inventory (with MIME) β
|
|
536
|
+
β - Full content as markdown β
|
|
537
|
+
β - Base directory context β
|
|
538
|
+
ββββββββββββββ¬ββββββββββββββββββββββ
|
|
539
|
+
β
|
|
540
|
+
ββββββββββββββββββββββββββββββββββββ
|
|
541
|
+
β Silent Injection Pattern β
|
|
542
|
+
β - Inject as user message β
|
|
543
|
+
β - Persists in conversation β
|
|
544
|
+
β - Returns success summary β
|
|
545
|
+
β { loaded: [...], notFound: []}β
|
|
546
|
+
ββββββββββββββββββββββββββββββββββββ
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Data Flow Diagram: skill_resource Access
|
|
550
|
+
|
|
551
|
+
```
|
|
552
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
553
|
+
β User: skill_resource("git-commits", β
|
|
554
|
+
β "scripts/commit.sh") β
|
|
555
|
+
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
|
|
556
|
+
β
|
|
557
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
558
|
+
β SkillResourceReader Tool β
|
|
559
|
+
β (tools/SkillResourceReader.ts) β
|
|
560
|
+
β - Validate skill_name β
|
|
561
|
+
β - Parse path: "scripts/commit.sh" β
|
|
562
|
+
β ββ type = "scripts" β
|
|
563
|
+
β ββ relative_path = "commit.sh" β
|
|
564
|
+
β - Assert type is valid (scripts|assets|refs)β
|
|
565
|
+
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
|
|
566
|
+
β
|
|
567
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
568
|
+
β SkillResourceResolver β
|
|
569
|
+
β - Fetch skill object from registry β
|
|
570
|
+
β - Look up in skill.scripts Map β
|
|
571
|
+
β (pre-indexed at parse time) β
|
|
572
|
+
β - Retrieve: { absolutePath, mimeType } β
|
|
573
|
+
β β
|
|
574
|
+
β β
SECURITY: Only pre-indexed paths exist β
|
|
575
|
+
β No way to request ../../../etc/passwd β
|
|
576
|
+
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
|
|
577
|
+
β
|
|
578
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
579
|
+
β File I/O (SkillFs abstraction) β
|
|
580
|
+
β - Read file content from absolutePath β
|
|
581
|
+
β - Detect MIME type (e.g., text/plain) β
|
|
582
|
+
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
|
|
583
|
+
β
|
|
584
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
585
|
+
β Return Injection Object β
|
|
586
|
+
β { β
|
|
587
|
+
β skill_name, β
|
|
588
|
+
β resource_path, β
|
|
589
|
+
β resource_mimetype, β
|
|
590
|
+
β content β
|
|
591
|
+
β } β
|
|
592
|
+
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
|
|
593
|
+
β
|
|
594
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
595
|
+
β Silent Injection β
|
|
596
|
+
β - Inject content into message β
|
|
597
|
+
β - User sees resource inline β
|
|
598
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Key Design Decisions and Their Rationale
|
|
602
|
+
|
|
603
|
+
| Decision | Why | Impact |
|
|
604
|
+
| ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------ |
|
|
605
|
+
| **ReadyStateMachine** | Ensures tools don't race with async registry setup | Tools are guaranteed registry is ready before execution |
|
|
606
|
+
| **Pre-indexed Resources** | Prevents path traversal attacks | Security: only pre-registered paths retrievable |
|
|
607
|
+
| **Factory Pattern (api.ts)** | Centralizes initialization | Easy to test, swap implementations, mock components |
|
|
608
|
+
| **Removed SkillProvider** | Eliminated unnecessary abstraction | Simpler code, direct registry access, easier debugging |
|
|
609
|
+
| **XML Serialization** | Human-readable message injection | Results display nicely formatted in chat context |
|
|
610
|
+
| **Two-phase Init** | Async discovery doesn't block tool registration | Tools available immediately, discovery happens in background |
|
|
611
|
+
|
|
612
|
+
### Services Layer Breakdown
|
|
613
|
+
|
|
614
|
+
#### SkillRegistry (`src/services/SkillRegistry.ts`)
|
|
615
|
+
|
|
616
|
+
- **Role**: Central skill catalog and discovery engine
|
|
617
|
+
- **Responsibilities**:
|
|
618
|
+
1. Scan multiple base paths for SKILL.md files (recursive)
|
|
619
|
+
2. Parse each file's YAML frontmatter (validates schema)
|
|
620
|
+
3. Index all resources (scripts/assets/references) for safe retrieval
|
|
621
|
+
4. Register skills in controller.skills Map by toolName
|
|
622
|
+
5. Coordinate initialization via ReadyStateMachine
|
|
623
|
+
- **Key Methods**:
|
|
624
|
+
- `initialise()`: Async discovery and parsing pipeline
|
|
625
|
+
- `register()`: Parse and store skill files
|
|
626
|
+
- `parseSkill()`: Extract metadata and resource paths
|
|
627
|
+
- **Error Handling**: Malformed skills logged but don't halt discovery
|
|
628
|
+
|
|
629
|
+
#### SkillSearcher (`src/services/SkillSearcher.ts`)
|
|
630
|
+
|
|
631
|
+
- **Role**: Natural language query interpretation and ranking
|
|
632
|
+
- **Responsibilities**:
|
|
633
|
+
1. Parse queries using search-string library (Gmail syntax)
|
|
634
|
+
2. Filter: ALL include terms must match (AND logic)
|
|
635
|
+
3. Exclude: Remove results matching exclude terms
|
|
636
|
+
4. Rank by relevance (name matches 3Γ, description 1Γ, exact +10)
|
|
637
|
+
5. Generate user-friendly feedback
|
|
638
|
+
- **Scoring Formula**: `(nameMatches Γ 3) + (descMatches Γ 1) + exactBonus`
|
|
639
|
+
- **Edge Cases**: Empty queries or "\*" list all skills
|
|
640
|
+
|
|
641
|
+
### Tools Layer Overview
|
|
642
|
+
|
|
643
|
+
#### SkillFinder (`src/tools/SkillFinder.ts`)
|
|
644
|
+
|
|
645
|
+
- Wraps SkillSearcher with ready-state synchronization
|
|
646
|
+
- Transforms results to SkillFinder schema
|
|
647
|
+
- Returns matched skills + summary metadata
|
|
648
|
+
|
|
649
|
+
#### SkillUser (`src/tools/SkillUser.ts`)
|
|
650
|
+
|
|
651
|
+
- Loads skills into chat context
|
|
652
|
+
- Injects as user message (persists in conversation)
|
|
653
|
+
- Returns { loaded: [...], notFound: [...] }
|
|
654
|
+
|
|
655
|
+
#### SkillResourceReader (`src/tools/SkillResourceReader.ts`)
|
|
656
|
+
|
|
657
|
+
- Safe resource path parsing and validation
|
|
658
|
+
- Pre-indexed path lookup (prevents traversal)
|
|
659
|
+
- Returns injection object with MIME type and content
|
|
660
|
+
|
|
661
|
+
### Configuration and Path Resolution
|
|
662
|
+
|
|
663
|
+
```
|
|
664
|
+
Configuration Priority (Last Wins):
|
|
665
|
+
1. Global: ~/.opencode/skills/ (lowest)
|
|
666
|
+
2. Project: ./.opencode/skills/ (highest)
|
|
667
|
+
|
|
668
|
+
Why This Order:
|
|
669
|
+
- Users install global skills once
|
|
670
|
+
- Projects override with local versions
|
|
671
|
+
- Same skill name in both β project version wins
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
## Contributing
|
|
675
|
+
|
|
676
|
+
Contributions are welcome! When adding features, follow these principles:
|
|
677
|
+
|
|
678
|
+
- **Explain the WHY, not the WHAT**: Code comments should document design decisions, not mechanics
|
|
679
|
+
- **Keep modules focused**: Each file has one primary responsibility
|
|
680
|
+
- **Test async behavior**: Ready state coordination is critical
|
|
681
|
+
- **Document algorithms**: Ranking, parsing, and search logic should have detailed comments
|
|
682
|
+
|
|
155
683
|
## Creating Skills
|
|
156
684
|
|
|
157
685
|
Skills follow the [Anthropic Agent Skills Specification](https://github.com/anthropics/skills). Each skill is a directory containing a `SKILL.md` file:
|
|
@@ -164,18 +692,28 @@ skills/
|
|
|
164
692
|
template.md
|
|
165
693
|
```
|
|
166
694
|
|
|
695
|
+
### Skill Structure
|
|
696
|
+
|
|
697
|
+
```
|
|
698
|
+
my-skill/
|
|
699
|
+
SKILL.md # Required: Skill metadata and instructions
|
|
700
|
+
references/ # Optional: Documentation and guides
|
|
701
|
+
guide.md
|
|
702
|
+
examples.md
|
|
703
|
+
assets/ # Optional: Binary files and templates
|
|
704
|
+
template.html
|
|
705
|
+
logo.png
|
|
706
|
+
scripts/ # Optional: Executable scripts
|
|
707
|
+
setup.sh
|
|
708
|
+
generate.py
|
|
709
|
+
```
|
|
710
|
+
|
|
167
711
|
### SKILL.md Format
|
|
168
712
|
|
|
169
713
|
```yaml
|
|
170
714
|
---
|
|
171
715
|
name: my-skill
|
|
172
716
|
description: A brief description of what this skill does (min 20 chars)
|
|
173
|
-
license: MIT
|
|
174
|
-
allowed-tools:
|
|
175
|
-
- bash
|
|
176
|
-
- read
|
|
177
|
-
metadata:
|
|
178
|
-
author: Your Name
|
|
179
717
|
---
|
|
180
718
|
# My Skill
|
|
181
719
|
|
|
@@ -185,9 +723,28 @@ Instructions for the AI agent when this skill is loaded...
|
|
|
185
723
|
**Requirements:**
|
|
186
724
|
|
|
187
725
|
- `name` must match the directory name (lowercase, alphanumeric, hyphens only)
|
|
188
|
-
- `description` must be at least 20 characters
|
|
726
|
+
- `description` must be at least 20 characters and should be concise (under 500 characters recommended)
|
|
727
|
+
- Focus on when to use the skill, not what it does
|
|
728
|
+
- Include specific triggering conditions and symptoms
|
|
729
|
+
- Use third person for system prompt injection
|
|
730
|
+
- Example: "Use when designing REST APIs to establish consistent patterns and improve developer experience"
|
|
189
731
|
- Directory name and frontmatter `name` must match exactly
|
|
190
732
|
|
|
733
|
+
### Resource Types
|
|
734
|
+
|
|
735
|
+
Skill resources are automatically discovered and categorized:
|
|
736
|
+
|
|
737
|
+
- **References** (`references/` directory): Documentation, guides, and reference materials
|
|
738
|
+
- Typically Markdown, plaintext, or HTML files
|
|
739
|
+
- Accessed via `skill_resource` for reading documentation
|
|
740
|
+
- **Assets** (`assets/` directory): Templates, images, and binary files
|
|
741
|
+
- Can include HTML templates, images, configuration files
|
|
742
|
+
- Useful for providing templates and examples to the AI
|
|
743
|
+
- **Scripts** (`scripts/` directory): Executable scripts that perform actions
|
|
744
|
+
- Shell scripts (.sh), Python scripts (.py), or other executables
|
|
745
|
+
- Executed via `skill_exec` with optional arguments
|
|
746
|
+
- Useful for automation, code generation, or complex operations
|
|
747
|
+
|
|
191
748
|
## Considerations
|
|
192
749
|
|
|
193
750
|
- Skills are discovered at plugin initialization (requires restart to reload)
|