@fakhrulraharjo/opencode-skill-creator 0.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/README.md +343 -0
- package/bin/opencode-skill-creator.js +338 -0
- package/dist/build-manifest.json +6 -0
- package/dist/package.json +62 -0
- package/dist/skill/SKILL.md +481 -0
- package/dist/skill/agents/analyzer.md +274 -0
- package/dist/skill/agents/comparator.md +202 -0
- package/dist/skill/agents/grader.md +223 -0
- package/dist/skill/references/schemas.md +430 -0
- package/dist/skill/templates/eval-review.html +146 -0
- package/dist/skill-creator.js +15318 -0
- package/dist/templates/viewer.html +1325 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# opencode-skill-creator
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-skill-creator)
|
|
4
|
+
|
|
5
|
+
A **skill + plugin** for [OpenCode](https://opencode.ai) that helps you create, test, and optimize other OpenCode skills.
|
|
6
|
+
|
|
7
|
+
This is a faithful adaptation of Anthropic's official [skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) for Claude Code, fully rewritten to work with OpenCode's extensibility mechanisms. The Python scripts from the original have been ported to TypeScript and packaged as an OpenCode plugin with custom tools.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Package: https://www.npmjs.com/package/opencode-skill-creator
|
|
12
|
+
|
|
13
|
+
### Pick your option
|
|
14
|
+
|
|
15
|
+
| If you are... | Use this |
|
|
16
|
+
|---|---|
|
|
17
|
+
| New to OpenCode / non-developer | **Option A (Recommended)** |
|
|
18
|
+
| Already using other plugins | **Option B** |
|
|
19
|
+
| Setting up all projects on your computer | **Option C (Global config)** |
|
|
20
|
+
| Setting up only one project | **Option D (Project config)** |
|
|
21
|
+
| Cannot use npm / offline environment | **Option E (Manual install)** |
|
|
22
|
+
|
|
23
|
+
### Option A (Recommended): easiest setup for most users
|
|
24
|
+
|
|
25
|
+
Run one command (global install, recommended):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx opencode-skill-creator install --global
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Optional checks:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx opencode-skill-creator --version
|
|
35
|
+
npx opencode-skill-creator --help
|
|
36
|
+
npx opencode-skill-creator --about
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
What this command does:
|
|
40
|
+
|
|
41
|
+
1. Creates/updates `~/.config/opencode/opencode.json`
|
|
42
|
+
2. Adds `"opencode-skill-creator"` to the `plugin` array
|
|
43
|
+
3. Leaves your existing plugins untouched
|
|
44
|
+
|
|
45
|
+
Then:
|
|
46
|
+
|
|
47
|
+
4. Restart OpenCode
|
|
48
|
+
5. Ask OpenCode: `Create a skill that helps with Docker compose files`
|
|
49
|
+
|
|
50
|
+
That's it.
|
|
51
|
+
|
|
52
|
+
Manual equivalent for the same result:
|
|
53
|
+
|
|
54
|
+
1. Open (or create) `~/.config/opencode/opencode.json`
|
|
55
|
+
2. Paste this:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"plugin": ["opencode-skill-creator"]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
3. Restart OpenCode.
|
|
64
|
+
|
|
65
|
+
If you want project-only install instead, use:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx opencode-skill-creator install --project
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Option B: you already have plugins
|
|
72
|
+
|
|
73
|
+
If your file already has plugins, append this package to the list:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"plugin": [
|
|
78
|
+
"your-existing-plugin",
|
|
79
|
+
"opencode-skill-creator"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Do not remove your existing plugins.
|
|
85
|
+
|
|
86
|
+
### Option C: global config (works in all projects)
|
|
87
|
+
|
|
88
|
+
Use global config when you want this plugin available everywhere.
|
|
89
|
+
|
|
90
|
+
Command version:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx opencode-skill-creator install --global
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
1. Open (or create) `~/.config/opencode/opencode.json`
|
|
97
|
+
2. Add:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"plugin": ["opencode-skill-creator"]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
3. Restart OpenCode.
|
|
106
|
+
|
|
107
|
+
### Option D: project config (only one project)
|
|
108
|
+
|
|
109
|
+
Use project config when you want this plugin only for one repo.
|
|
110
|
+
|
|
111
|
+
Command version:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx opencode-skill-creator install --project
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
1. Open (or create) `opencode.json` in that project root
|
|
118
|
+
2. Add:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"plugin": ["opencode-skill-creator"]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
3. Restart OpenCode in that project.
|
|
127
|
+
|
|
128
|
+
### Option E: manual install (no npm)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git clone https://github.com/antongulin/opencode-skill-creator.git
|
|
132
|
+
cd opencode-skill-creator
|
|
133
|
+
|
|
134
|
+
# Install the skill (global)
|
|
135
|
+
cp -r opencode-skill-creator/ ~/.config/opencode/skills/opencode-skill-creator/
|
|
136
|
+
|
|
137
|
+
# Install the plugin (global)
|
|
138
|
+
cp -r plugin/ ~/.config/opencode/plugins/skill-creator/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Then create `~/.config/opencode/package.json` if needed:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"dependencies": {
|
|
146
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### What happens after install
|
|
152
|
+
|
|
153
|
+
After you add `opencode-skill-creator` and restart OpenCode:
|
|
154
|
+
|
|
155
|
+
1. OpenCode installs the plugin from npm automatically.
|
|
156
|
+
2. The npm package loads compiled JavaScript from `dist/skill-creator.js`.
|
|
157
|
+
3. On first plugin startup, it auto-copies skill files to `~/.config/opencode/skills/opencode-skill-creator/`.
|
|
158
|
+
4. Restart OpenCode after changing config because plugin config is loaded at startup.
|
|
159
|
+
|
|
160
|
+
### Verify install
|
|
161
|
+
|
|
162
|
+
Check that the skill file exists:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
ls ~/.config/opencode/skills/opencode-skill-creator/SKILL.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Then ask OpenCode:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
Use opencode-skill-creator to create a skill that helps with API documentation.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You should see it use the opencode-skill-creator workflow/tools.
|
|
175
|
+
|
|
176
|
+
### Migration from the old `skill-creator` folder
|
|
177
|
+
|
|
178
|
+
Earlier versions installed the bundled skill as the generic `skill-creator` skill. That could conflict with other plugins, including Superpowers, that also provide a skill with the same name.
|
|
179
|
+
|
|
180
|
+
Current versions install the bundled skill as `opencode-skill-creator` instead. On startup, if the plugin finds an old plugin-owned folder at `~/.config/opencode/skills/skill-creator/`, it moves that folder to an inactive backup such as:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
~/.config/opencode/skills/skill-creator.opencode-skill-creator-backup-YYYYMMDDTHHMMSS/
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The backup preserves user files and renames `SKILL.md` to `SKILL.md.backup` so OpenCode will not keep loading the old generic skill. If the old `skill-creator` folder does not contain the plugin's `.opencode-skill-creator-version` marker, the plugin leaves it untouched because it may belong to another plugin or a manually installed skill.
|
|
187
|
+
|
|
188
|
+
### Troubleshooting
|
|
189
|
+
|
|
190
|
+
- `I don't have opencode.json or opencode.jsonc`: create one in project root (or use global config path).
|
|
191
|
+
- `Nothing changed after edit`: fully restart OpenCode.
|
|
192
|
+
- `I already had plugins`: keep them; just add `opencode-skill-creator` to the same array.
|
|
193
|
+
- `I want a clean reinstall`: delete `~/.config/opencode/skills/opencode-skill-creator/` and restart OpenCode.
|
|
194
|
+
- `I still see another skill-creator skill`: if `~/.config/opencode/skills/skill-creator/` has no `.opencode-skill-creator-version` marker, it is not managed by this plugin and must be reviewed separately.
|
|
195
|
+
- `npx command failed`: run `npx opencode-skill-creator --help` and then use `install` or `install --global`.
|
|
196
|
+
|
|
197
|
+
### Compatibility fixes
|
|
198
|
+
|
|
199
|
+
- The installer supports existing `opencode.jsonc` files and preserves JSONC comments when adding the plugin entry.
|
|
200
|
+
- The npm package loads compiled JavaScript from `dist/skill-creator.js` so OpenCode does not need to strip TypeScript under `node_modules`.
|
|
201
|
+
- The plugin resolves bundled assets with `import.meta.url`, which works in OpenCode Desktop runtimes where `import.meta.path` is unavailable.
|
|
202
|
+
- Trigger evaluations run with an explicit OpenCode agent, defaulting to `build`, to avoid false 0% scores from delegating default agents.
|
|
203
|
+
- Release-safety tests now verify the compiled plugin entrypoint imports correctly and that `dist/` was built from the current TypeScript sources.
|
|
204
|
+
|
|
205
|
+
### For LLMs / automation (compact)
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{ "plugin": ["opencode-skill-creator"] }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## What it does
|
|
212
|
+
|
|
213
|
+
When loaded, this skill guides OpenCode through the full skill development lifecycle:
|
|
214
|
+
|
|
215
|
+
1. **Analyze** the user's request and determine what kind of skill to build
|
|
216
|
+
2. **Create** a well-structured skill with proper frontmatter, SKILL.md, and supporting files
|
|
217
|
+
3. **Generate** an eval set of test queries (should-trigger and should-not-trigger)
|
|
218
|
+
4. **Evaluate** the skill's description by testing whether it triggers correctly
|
|
219
|
+
5. **Optimize** the description through iterative improvement loops
|
|
220
|
+
6. **Benchmark** skill performance with variance analysis
|
|
221
|
+
7. **Install** the skill to the project or global OpenCode skills directory
|
|
222
|
+
|
|
223
|
+
## Plugin tools
|
|
224
|
+
|
|
225
|
+
The plugin registers these custom tools that OpenCode can call:
|
|
226
|
+
|
|
227
|
+
| Tool | Purpose |
|
|
228
|
+
|------|---------|
|
|
229
|
+
| `skill_validate` | Validate SKILL.md structure and frontmatter |
|
|
230
|
+
| `skill_parse` | Parse SKILL.md and extract name/description |
|
|
231
|
+
| `skill_eval` | Test trigger accuracy for eval queries |
|
|
232
|
+
| `skill_improve_description` | LLM-powered description improvement |
|
|
233
|
+
| `skill_optimize_loop` | Full eval->improve optimization loop |
|
|
234
|
+
| `skill_aggregate_benchmark` | Aggregate grading results into statistics |
|
|
235
|
+
| `skill_generate_report` | Generate HTML optimization report |
|
|
236
|
+
| `skill_serve_review` | Start the eval review viewer (HTTP server) |
|
|
237
|
+
| `skill_stop_review` | Stop a running review server |
|
|
238
|
+
| `skill_export_static_review` | Generate standalone HTML review file |
|
|
239
|
+
| `skill_add_gold_standard` | Save an exemplary skill response for future guidance |
|
|
240
|
+
| `skill_list_gold_standards` | List saved gold-standard examples for a skill |
|
|
241
|
+
| `skill_remove_gold_standard` | Remove a saved gold-standard example |
|
|
242
|
+
| `skill_get_gold_advice` | Get guidance from saved gold-standard examples |
|
|
243
|
+
|
|
244
|
+
### Review workflow guard (strict by default)
|
|
245
|
+
|
|
246
|
+
The review launch tools now enforce paired comparison data by default:
|
|
247
|
+
|
|
248
|
+
- `skill_serve_review` and `skill_export_static_review` require each `eval-*` directory to include:
|
|
249
|
+
- `with_skill`
|
|
250
|
+
- baseline (`without_skill` or `old_skill`)
|
|
251
|
+
- If pairs are missing, the tools fail fast with a clear list of missing items.
|
|
252
|
+
- Override only when intentionally reviewing partial data by passing `allowPartial: true`.
|
|
253
|
+
- If `benchmarkPath` is omitted, the tools auto-generate `benchmark.json` and `benchmark.md` in the workspace.
|
|
254
|
+
|
|
255
|
+
### Skill draft staging (recommended)
|
|
256
|
+
|
|
257
|
+
When creating new skills, use a staging path in the system temp directory outside your current repository:
|
|
258
|
+
|
|
259
|
+
- Unix/macOS draft skill path: `/tmp/opencode-skills/<skill-name>/` (or `$TMPDIR/opencode-skills/<skill-name>/`)
|
|
260
|
+
- Unix/macOS eval workspace path: `/tmp/opencode-skills/<skill-name>-workspace/`
|
|
261
|
+
- Windows draft/eval paths: `%TEMP%\\opencode-skills\\<skill-name>\\` and `%TEMP%\\opencode-skills\\<skill-name>-workspace\\`
|
|
262
|
+
- Install only the final validated skill to:
|
|
263
|
+
- project: `.opencode/skills/<skill-name>/`
|
|
264
|
+
- global: `~/.config/opencode/skills/<skill-name>/`
|
|
265
|
+
|
|
266
|
+
This keeps plugin/source repositories clean while preserving the full eval loop.
|
|
267
|
+
|
|
268
|
+
## Usage
|
|
269
|
+
|
|
270
|
+
Once installed, OpenCode will automatically detect the skill when you ask it to create or improve a skill. For example:
|
|
271
|
+
|
|
272
|
+
- "Create a skill that helps with Docker compose files"
|
|
273
|
+
- "Build me a skill for generating API documentation"
|
|
274
|
+
- "Help me make a skill that assists with database migrations"
|
|
275
|
+
- "Optimize the description of my existing skill"
|
|
276
|
+
|
|
277
|
+
OpenCode will load the opencode-skill-creator instructions and use the plugin tools to walk through the full workflow.
|
|
278
|
+
|
|
279
|
+
## Architecture
|
|
280
|
+
|
|
281
|
+
This project has two components:
|
|
282
|
+
|
|
283
|
+
| Component | What it is |
|
|
284
|
+
|-----------|-----------|
|
|
285
|
+
| **Skill** | Markdown instructions (SKILL.md + agents + templates) that tell the agent how to create, evaluate, and improve skills |
|
|
286
|
+
| **Plugin** | TypeScript module that registers custom tools for validation, eval, benchmarking, and review |
|
|
287
|
+
|
|
288
|
+
The skill provides the workflow knowledge; the plugin provides the executable tools the agent calls during that workflow.
|
|
289
|
+
|
|
290
|
+
On first startup, the plugin automatically copies the bundled skill files to `~/.config/opencode/skills/opencode-skill-creator/`. If you need to reinstall the skill (e.g., after an update), delete that directory and restart OpenCode.
|
|
291
|
+
|
|
292
|
+
## Project structure
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
opencode-skill-creator/
|
|
296
|
+
├── README.md
|
|
297
|
+
├── LICENSE # Apache 2.0
|
|
298
|
+
├── opencode-skill-creator/ # The SKILL
|
|
299
|
+
│ ├── SKILL.md # Main skill instructions
|
|
300
|
+
│ ├── agents/
|
|
301
|
+
│ │ ├── grader.md # Assertion evaluation
|
|
302
|
+
│ │ ├── analyzer.md # Benchmark analysis
|
|
303
|
+
│ │ └── comparator.md # Blind A/B comparison
|
|
304
|
+
│ ├── references/
|
|
305
|
+
│ │ └── schemas.md # JSON schema definitions
|
|
306
|
+
│ └── templates/
|
|
307
|
+
│ └── eval-review.html # Eval set review/edit UI
|
|
308
|
+
└── plugin/ # The PLUGIN (npm: opencode-skill-creator)
|
|
309
|
+
├── package.json # npm package metadata
|
|
310
|
+
├── skill-creator.ts # Entry point — registers all tools
|
|
311
|
+
├── skill/ # Bundled copy of skill (auto-installed)
|
|
312
|
+
├── lib/
|
|
313
|
+
│ ├── utils.ts # SKILL.md frontmatter parsing
|
|
314
|
+
│ ├── validate.ts # Skill structure validation
|
|
315
|
+
│ ├── run-eval.ts # Trigger evaluation via opencode run
|
|
316
|
+
│ ├── improve-description.ts # LLM-powered description improvement
|
|
317
|
+
│ ├── run-loop.ts # Eval→improve optimization loop
|
|
318
|
+
│ ├── aggregate.ts # Benchmark aggregation
|
|
319
|
+
│ ├── report.ts # HTML report generation
|
|
320
|
+
│ └── review-server.ts # Eval review HTTP server
|
|
321
|
+
└── templates/
|
|
322
|
+
└── viewer.html # Eval review viewer UI
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Differences from the Anthropic original
|
|
326
|
+
|
|
327
|
+
| Area | Anthropic (Claude Code) | This repo (OpenCode) |
|
|
328
|
+
|------|------------------------|---------------------|
|
|
329
|
+
| CLI invocation | `claude -p "prompt"` | `opencode run "prompt"` |
|
|
330
|
+
| Skill location | `.claude/commands/` | `.opencode/skills/` |
|
|
331
|
+
| Automation scripts | Python (`scripts/*.py`) | TypeScript plugin (`plugin/lib/*.ts`) |
|
|
332
|
+
| Script execution | `python -m scripts.run_loop` | `skill_optimize_loop` tool call |
|
|
333
|
+
| Eval viewer | `python generate_review.py` | `skill_serve_review` tool call |
|
|
334
|
+
| Benchmarking | `python aggregate_benchmark.py` | `skill_aggregate_benchmark` tool call |
|
|
335
|
+
| Dependencies | Python 3.11+, pyyaml | Bun (via OpenCode), @opencode-ai/plugin |
|
|
336
|
+
| Packaging | `.skill` zip files | npm package + skill directory |
|
|
337
|
+
| Subagents | Built-in subagent concept | Task tool with `general`/`explore` types |
|
|
338
|
+
|
|
339
|
+
## License
|
|
340
|
+
|
|
341
|
+
Apache License 2.0 — see [LICENSE](LICENSE) for details.
|
|
342
|
+
|
|
343
|
+
Based on [anthropics/skills](https://github.com/anthropics/skills) by Anthropic.
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs"
|
|
4
|
+
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser"
|
|
5
|
+
import { homedir } from "os"
|
|
6
|
+
import { join, dirname } from "path"
|
|
7
|
+
|
|
8
|
+
const PKG_PATH = new URL("../package.json", import.meta.url)
|
|
9
|
+
const PACKAGE_NAME = "@fakhrulraharjo/opencode-skill-creator"
|
|
10
|
+
const PLUGIN_LOAD_ERROR_PREFIX = `Failed to load plugin ${PACKAGE_NAME}:`
|
|
11
|
+
|
|
12
|
+
function getVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(readFileSync(PKG_PATH, "utf-8"))
|
|
15
|
+
return pkg.version || "unknown"
|
|
16
|
+
} catch {
|
|
17
|
+
return "unknown"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function printHelp() {
|
|
22
|
+
console.log(`${PACKAGE_NAME} installer
|
|
23
|
+
|
|
24
|
+
Links:
|
|
25
|
+
npm: https://www.npmjs.com/package/${PACKAGE_NAME}
|
|
26
|
+
repo: https://github.com/fakhrulraharjo/opencode-skill-creator
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
npx ${PACKAGE_NAME} install [--global|--project]
|
|
30
|
+
npx ${PACKAGE_NAME} [--global|--project]
|
|
31
|
+
npx ${PACKAGE_NAME} --version
|
|
32
|
+
npx ${PACKAGE_NAME} --about
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--global Update ~/.config/opencode/opencode.jsonc if present, otherwise opencode.json (default)
|
|
36
|
+
--project Update ./opencode.jsonc if present, otherwise opencode.json in current directory
|
|
37
|
+
-v, --version Show installer version
|
|
38
|
+
--about Show package links
|
|
39
|
+
-h, --help Show help
|
|
40
|
+
`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printAbout() {
|
|
44
|
+
console.log(`${PACKAGE_NAME} ${getVersion()}
|
|
45
|
+
npm: https://www.npmjs.com/package/${PACKAGE_NAME}
|
|
46
|
+
repo: https://github.com/fakhrulraharjo/opencode-skill-creator`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseArgs(argv) {
|
|
50
|
+
const input = argv.slice(2)
|
|
51
|
+
const args = new Set(input)
|
|
52
|
+
|
|
53
|
+
if (args.has("-h") || args.has("--help")) {
|
|
54
|
+
return {
|
|
55
|
+
help: true,
|
|
56
|
+
about: false,
|
|
57
|
+
version: false,
|
|
58
|
+
global: false,
|
|
59
|
+
command: "install",
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (args.has("-v") || args.has("--version")) {
|
|
64
|
+
return {
|
|
65
|
+
help: false,
|
|
66
|
+
about: false,
|
|
67
|
+
version: true,
|
|
68
|
+
global: false,
|
|
69
|
+
command: "install",
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (args.has("--about")) {
|
|
74
|
+
return {
|
|
75
|
+
help: false,
|
|
76
|
+
about: true,
|
|
77
|
+
version: false,
|
|
78
|
+
global: false,
|
|
79
|
+
command: "install",
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const hasProject = args.has("--project")
|
|
84
|
+
const hasGlobal = args.has("--global")
|
|
85
|
+
|
|
86
|
+
if (hasProject && hasGlobal) {
|
|
87
|
+
throw new Error("Use either --project or --global, not both.")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const command = input.find((arg) => !arg.startsWith("-")) || "install"
|
|
91
|
+
if (command !== "install") {
|
|
92
|
+
throw new Error(`Unknown command: ${command}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
help: false,
|
|
97
|
+
about: false,
|
|
98
|
+
version: false,
|
|
99
|
+
global: !hasProject,
|
|
100
|
+
command,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getConfigPath(globalInstall) {
|
|
105
|
+
const configDir = globalInstall
|
|
106
|
+
? join(homedir(), ".config", "opencode")
|
|
107
|
+
: process.cwd()
|
|
108
|
+
const jsoncPath = join(configDir, "opencode.jsonc")
|
|
109
|
+
|
|
110
|
+
if (existsSync(jsoncPath)) {
|
|
111
|
+
return jsoncPath
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return join(configDir, "opencode.json")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function clearStaleOpenCodePackageCache() {
|
|
118
|
+
const currentVersion = getVersion()
|
|
119
|
+
if (currentVersion === "unknown") return { cleared: false, error: null }
|
|
120
|
+
|
|
121
|
+
const cacheDir = process.env.XDG_CACHE_HOME || join(homedir(), ".cache")
|
|
122
|
+
const packageCacheRoot = join(
|
|
123
|
+
cacheDir,
|
|
124
|
+
"opencode",
|
|
125
|
+
"packages",
|
|
126
|
+
`${PACKAGE_NAME}@latest`
|
|
127
|
+
)
|
|
128
|
+
const cachedPackageJson = join(
|
|
129
|
+
packageCacheRoot,
|
|
130
|
+
"node_modules",
|
|
131
|
+
...PACKAGE_NAME.split("/"),
|
|
132
|
+
"package.json"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if (!existsSync(cachedPackageJson)) return { cleared: false, error: null }
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const cachedPackage = JSON.parse(readFileSync(cachedPackageJson, "utf-8"))
|
|
139
|
+
if (cachedPackage.version === currentVersion) {
|
|
140
|
+
return { cleared: false, error: null }
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// Broken cache entries should be removed so OpenCode can recreate them.
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
rmSync(packageCacheRoot, { recursive: true, force: true })
|
|
148
|
+
return { cleared: true, error: null }
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return { cleared: false, error }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getDesktopGlobalDataPath() {
|
|
155
|
+
if (process.platform === "darwin") {
|
|
156
|
+
return join(
|
|
157
|
+
homedir(),
|
|
158
|
+
"Library",
|
|
159
|
+
"Application Support",
|
|
160
|
+
"ai.opencode.desktop",
|
|
161
|
+
"opencode.global.dat"
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (process.platform === "win32") {
|
|
166
|
+
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming")
|
|
167
|
+
return join(appData, "ai.opencode.desktop", "opencode.global.dat")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const configDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
|
|
171
|
+
return join(configDir, "ai.opencode.desktop", "opencode.global.dat")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function clearPluginFaultNotifications() {
|
|
175
|
+
const dataPath = getDesktopGlobalDataPath()
|
|
176
|
+
if (!existsSync(dataPath)) return 0
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const data = JSON.parse(readFileSync(dataPath, "utf-8"))
|
|
180
|
+
if (typeof data.notification !== "string") return 0
|
|
181
|
+
|
|
182
|
+
const notifications = JSON.parse(data.notification)
|
|
183
|
+
if (!Array.isArray(notifications.list)) return 0
|
|
184
|
+
|
|
185
|
+
const originalCount = notifications.list.length
|
|
186
|
+
notifications.list = notifications.list.filter((notification) => {
|
|
187
|
+
if (notification?.type !== "error") return true
|
|
188
|
+
|
|
189
|
+
const message = notification?.error?.data?.message
|
|
190
|
+
return typeof message !== "string" || !message.startsWith(PLUGIN_LOAD_ERROR_PREFIX)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const removedCount = originalCount - notifications.list.length
|
|
194
|
+
if (removedCount === 0) return 0
|
|
195
|
+
|
|
196
|
+
data.notification = JSON.stringify(notifications)
|
|
197
|
+
writeFileSync(dataPath, `${JSON.stringify(data, null, "\t")}\n`, "utf-8")
|
|
198
|
+
return removedCount
|
|
199
|
+
} catch {
|
|
200
|
+
// Desktop state cleanup is best-effort; installation should not fail here.
|
|
201
|
+
return 0
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function loadConfig(path) {
|
|
206
|
+
if (!existsSync(path)) {
|
|
207
|
+
return {
|
|
208
|
+
raw: "{\n}\n",
|
|
209
|
+
config: {},
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const raw = readFileSync(path, "utf-8")
|
|
214
|
+
if (!raw.trim()) {
|
|
215
|
+
return {
|
|
216
|
+
raw: "{\n}\n",
|
|
217
|
+
config: {},
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const errors = []
|
|
222
|
+
const config = parse(raw, errors, {
|
|
223
|
+
allowTrailingComma: true,
|
|
224
|
+
disallowComments: false,
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
if (errors.length) {
|
|
228
|
+
const message = errors
|
|
229
|
+
.map((error) => printParseErrorCode(error.error))
|
|
230
|
+
.join(", ")
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Could not parse JSONC in ${path}: ${message}. Please fix the file, then re-run this installer.`
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
237
|
+
throw new Error(`Expected top-level config in ${path} to be an object.`)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { raw, config }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function saveConfig(path, raw, config) {
|
|
244
|
+
const formattingOptions = {
|
|
245
|
+
insertSpaces: true,
|
|
246
|
+
tabSize: 2,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const edits = Array.isArray(config.plugin)
|
|
250
|
+
? modify(raw, ["plugin", -1], PACKAGE_NAME, {
|
|
251
|
+
formattingOptions,
|
|
252
|
+
})
|
|
253
|
+
: modify(raw, ["plugin"], [PACKAGE_NAME], {
|
|
254
|
+
formattingOptions,
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
258
|
+
writeFileSync(path, applyEdits(raw, edits), "utf-8")
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function ensurePlugin(config) {
|
|
262
|
+
if (typeof config.plugin === "undefined") {
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!Array.isArray(config.plugin)) {
|
|
267
|
+
throw new Error('Expected "plugin" to be an array in opencode config')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return !config.plugin.includes(PACKAGE_NAME)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function main() {
|
|
274
|
+
const { help, about, version, global } = parseArgs(process.argv)
|
|
275
|
+
if (help) {
|
|
276
|
+
printHelp()
|
|
277
|
+
process.exit(0)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (about) {
|
|
281
|
+
printAbout()
|
|
282
|
+
process.exit(0)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (version) {
|
|
286
|
+
console.log(getVersion())
|
|
287
|
+
process.exit(0)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const configPath = getConfigPath(global)
|
|
291
|
+
const { raw, config } = loadConfig(configPath)
|
|
292
|
+
const changed = ensurePlugin(config)
|
|
293
|
+
if (changed) {
|
|
294
|
+
saveConfig(configPath, raw, config)
|
|
295
|
+
console.log(`Updated ${configPath}`)
|
|
296
|
+
console.log(`Added "${PACKAGE_NAME}" to the "plugin" array.`)
|
|
297
|
+
} else {
|
|
298
|
+
console.log(`No changes needed for ${configPath}`)
|
|
299
|
+
console.log(`"${PACKAGE_NAME}" is already in the "plugin" array.`)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (global) {
|
|
303
|
+
const cacheCleanup = clearStaleOpenCodePackageCache()
|
|
304
|
+
if (cacheCleanup.cleared) {
|
|
305
|
+
console.log(`Cleared stale OpenCode package cache for ${PACKAGE_NAME}.`)
|
|
306
|
+
} else if (cacheCleanup.error) {
|
|
307
|
+
console.warn(
|
|
308
|
+
`Could not clear stale OpenCode package cache: ${cacheCleanup.error.message}`
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (global) {
|
|
314
|
+
const removedNotifications = clearPluginFaultNotifications()
|
|
315
|
+
if (removedNotifications > 0) {
|
|
316
|
+
const noun = removedNotifications === 1 ? "notification" : "notifications"
|
|
317
|
+
console.log(
|
|
318
|
+
`Removed ${removedNotifications} stale ${PACKAGE_NAME} plugin fault ${noun}.`
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log("\nNext steps:")
|
|
324
|
+
console.log("1) Restart OpenCode")
|
|
325
|
+
console.log("2) Ask: Create a skill that helps with API documentation")
|
|
326
|
+
console.log(
|
|
327
|
+
"\nOn first startup, the plugin auto-installs skill files to ~/.config/opencode/skills/opencode-skill-creator/"
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
main()
|
|
333
|
+
} catch (error) {
|
|
334
|
+
printHelp()
|
|
335
|
+
console.error()
|
|
336
|
+
console.error(error.message)
|
|
337
|
+
process.exit(1)
|
|
338
|
+
}
|