opencode-codeops 1.4.0 → 1.6.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/CHANGELOG.md +15 -0
- package/README.md +42 -53
- package/bin/index.mjs +166 -47
- package/bin/install-agents.mjs +8 -11
- package/bin/install-skills.mjs +8 -11
- package/bin/lib/opencode-install.mjs +3 -2
- package/bin/lib/opencode-plugin.mjs +116 -0
- package/install.sh +9 -9
- package/package.json +1 -1
- package/plugin/index.ts +3 -3
- package/skills/setup-codeops/scaffold.md +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to CodeOps are recorded here.
|
|
4
4
|
|
|
5
|
+
## 1.6.0 — 2026-09-19
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- installer: register the plugin automatically on install/update
|
|
10
|
+
- installer: unify install/update into one command
|
|
11
|
+
- installer: add npm-first install, agents installer, and release tooling
|
|
12
|
+
|
|
13
|
+
## 1.5.0 — 2026-09-19
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- installer: unify install/update into one command
|
|
18
|
+
- installer: add npm-first install, agents installer, and release tooling
|
|
19
|
+
|
|
5
20
|
## 1.4.0 — 2026-09-19
|
|
6
21
|
|
|
7
22
|
### Features
|
package/README.md
CHANGED
|
@@ -27,108 +27,97 @@ Turn an idea or existing system into ambiguity-free requirements, grounded speci
|
|
|
27
27
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
filesystem because OpenCode only discovers those from disk.
|
|
33
|
-
|
|
34
|
-
### 1. Add the plugin
|
|
35
|
-
|
|
36
|
-
Add to your `opencode.json`:
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
{
|
|
40
|
-
"$schema": "https://opencode.ai/config.json",
|
|
41
|
-
"plugin": ["opencode-codeops"]
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
OpenCode installs the plugin automatically via Bun on next startup. A global config
|
|
46
|
-
(`~/.config/opencode/opencode.json`) is recommended so CodeOps is active in every project.
|
|
47
|
-
|
|
48
|
-
### 2. Install the skills
|
|
49
|
-
|
|
50
|
-
OpenCode discovers skills only from the filesystem (`.opencode/skills/` or
|
|
51
|
-
`~/.config/opencode/skills/`); it never reads them from a plugin package. The installer is a thin
|
|
52
|
-
`npx` wrapper around this package, so the skills always match the published version:
|
|
30
|
+
One command installs everything CodeOps owns: the skills, the subagents, and the OpenCode plugin
|
|
31
|
+
entry in your config.
|
|
53
32
|
|
|
54
33
|
```bash
|
|
55
34
|
# Global (recommended) — available in every OpenCode project
|
|
56
35
|
curl -fsSL https://cdn.jsdelivr.net/npm/opencode-codeops@latest/install.sh | bash
|
|
57
36
|
|
|
58
|
-
# Project-only —
|
|
37
|
+
# Project-only — files live in ./.opencode and are committed with the repo
|
|
59
38
|
curl -fsSL https://cdn.jsdelivr.net/npm/opencode-codeops@latest/install.sh | bash -s -- --project
|
|
60
39
|
```
|
|
61
40
|
|
|
62
41
|
The same installer runs directly through npm:
|
|
63
42
|
|
|
64
43
|
```bash
|
|
65
|
-
npx -y opencode-codeops@latest install
|
|
44
|
+
npx -y opencode-codeops@latest install
|
|
45
|
+
npx -y opencode-codeops@latest update # alias of install
|
|
66
46
|
```
|
|
67
47
|
|
|
68
|
-
|
|
48
|
+
`install`/`update` writes the skills and subagents onto the filesystem (OpenCode discovers those
|
|
49
|
+
only from disk), then registers the plugin in the OpenCode config by calling OpenCode's own
|
|
50
|
+
`opencode plugin` command, so standards injection and `CODEOPS_PLUGIN_ROOT` are enabled. Restart
|
|
51
|
+
OpenCode after installing for the plugin to load. Pass `--no-plugin` to manage the config yourself.
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
53
|
+
The scope is auto-detected: inside a CodeOps project (a git repo with `.opencode/` or
|
|
54
|
+
`codeops/.codeops.yml`) it installs into `./.opencode`, and registers the plugin in the project
|
|
55
|
+
config; anywhere else it installs globally into `~/.config/opencode` and the global config. Pass
|
|
56
|
+
`--project` or `--global` to force one.
|
|
73
57
|
|
|
74
|
-
|
|
58
|
+
If you prefer to register the plugin manually instead, add it to your `opencode.json`:
|
|
75
59
|
|
|
76
60
|
```json
|
|
77
|
-
{
|
|
61
|
+
{
|
|
62
|
+
"$schema": "https://opencode.ai/config.json",
|
|
63
|
+
"plugin": ["opencode-codeops"]
|
|
64
|
+
}
|
|
78
65
|
```
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
package owns, recorded in `<skills-dir>/.opencode-codeops.json`. Skills you author yourself, or
|
|
82
|
-
install with another tool, are left untouched.
|
|
83
|
-
|
|
84
|
-
Check or remove an install:
|
|
67
|
+
Pin a version with `CODEOPS_VERSION` (an npm dist-tag or exact version; defaults to `latest`):
|
|
85
68
|
|
|
86
69
|
```bash
|
|
87
|
-
|
|
88
|
-
npx -y opencode-codeops@latest uninstall
|
|
70
|
+
CODEOPS_VERSION=1.6.0 curl -fsSL https://cdn.jsdelivr.net/npm/opencode-codeops@latest/install.sh | bash
|
|
89
71
|
```
|
|
90
72
|
|
|
91
|
-
`
|
|
92
|
-
|
|
93
|
-
does not own is skipped with a warning, and `--force` replaces it.
|
|
73
|
+
`install`/`update` pins the plugin in the OpenCode config to the installer's own version, so the
|
|
74
|
+
plugin and the installed files cannot drift apart; run `update` to move both to a new version.
|
|
94
75
|
|
|
95
|
-
|
|
76
|
+
Re-running the installer upgrades an existing install in place. It replaces only the files this
|
|
77
|
+
package owns, recorded in `.opencode-codeops.json`. Files you author yourself, or install with
|
|
78
|
+
another tool, are left untouched.
|
|
96
79
|
|
|
97
|
-
|
|
98
|
-
(global). The `/setup-codeops` skill does this for a project; do it manually with:
|
|
80
|
+
Check or remove an install:
|
|
99
81
|
|
|
100
82
|
```bash
|
|
101
|
-
npx -y opencode-codeops@latest
|
|
83
|
+
npx -y opencode-codeops@latest status
|
|
84
|
+
npx -y opencode-codeops@latest uninstall
|
|
102
85
|
```
|
|
103
86
|
|
|
87
|
+
`status` reports the installed skills and agents versions and the configured plugin entry, so a
|
|
88
|
+
mismatch is visible. Use `--dry-run` to preview an install; a same-named file the package does not
|
|
89
|
+
own is skipped with a warning, and `--force` replaces it. `uninstall` removes the skills and
|
|
90
|
+
subagents but leaves the plugin entry in your config; remove `opencode-codeops` from the `plugin`
|
|
91
|
+
array by hand to fully disable it.
|
|
92
|
+
|
|
104
93
|
### Local development
|
|
105
94
|
|
|
106
|
-
Symlink the plugin into your OpenCode plugin directory and link the
|
|
107
|
-
are picked up without reinstalling
|
|
95
|
+
Symlink the plugin into your OpenCode plugin directory and link the installed files to a checkout,
|
|
96
|
+
so edits are picked up without reinstalling. Pass `--no-plugin` so the checkout is not overwritten
|
|
97
|
+
by a registered npm plugin:
|
|
108
98
|
|
|
109
99
|
```bash
|
|
110
100
|
# Plugin (project or global plugin directory)
|
|
111
101
|
ln -s /path/to/opencode-codeops/plugin/index.ts ~/.config/opencode/plugins/codeops.ts
|
|
112
102
|
|
|
113
103
|
# Skills and agents — link instead of copy
|
|
114
|
-
node /path/to/opencode-codeops/bin/index.mjs install
|
|
115
|
-
node /path/to/opencode-codeops/bin/index.mjs install-agents --link --global
|
|
104
|
+
node /path/to/opencode-codeops/bin/index.mjs install --link --global --no-plugin
|
|
116
105
|
```
|
|
117
106
|
|
|
118
107
|
## Setup
|
|
119
108
|
|
|
120
|
-
After installing the plugin and the
|
|
109
|
+
After installing the plugin and the files, initialize CodeOps in your project:
|
|
121
110
|
|
|
122
111
|
```
|
|
123
112
|
/setup-codeops
|
|
124
113
|
```
|
|
125
114
|
|
|
126
|
-
This creates the `codeops/` layout, scaffolds `codeops/codeops.json` and `codeops/.codeops.yml`, installs the 12 CodeOps subagent files into `.opencode
|
|
115
|
+
This creates the `codeops/` layout, scaffolds `codeops/codeops.json` and `codeops/.codeops.yml`, installs the skills and the 12 CodeOps subagent files into `.opencode/`, and adds a managed section to `AGENTS.md`.
|
|
127
116
|
|
|
128
117
|
Commit the result:
|
|
129
118
|
|
|
130
119
|
```bash
|
|
131
|
-
git add codeops/ .opencode/
|
|
120
|
+
git add codeops/ .opencode/ AGENTS.md
|
|
132
121
|
git commit -m "chore: initialize CodeOps"
|
|
133
122
|
```
|
|
134
123
|
|
|
@@ -140,7 +129,7 @@ On every OpenCode session start and after every compaction, the plugin injects:
|
|
|
140
129
|
|
|
141
130
|
These standards are active without any user action. They do not need to be copied into `AGENTS.md`.
|
|
142
131
|
|
|
143
|
-
The plugin also warns (non-blocking) if any tool attempts to edit `codeops/.codeops.yml` directly — that file is managed exclusively by the `setup-codeops` skill.
|
|
132
|
+
The plugin also warns (non-blocking) if any tool attempts to edit `codeops/.codeops.yml` directly — that file is managed exclusively by the `setup-codeops` skill — and if the installed skills version differs from the plugin version, so a stale install is visible.
|
|
144
133
|
|
|
145
134
|
## Agent model configuration
|
|
146
135
|
|
package/bin/index.mjs
CHANGED
|
@@ -2,92 +2,211 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* The `opencode-codeops` command-line entry point.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
5
|
+
* There is exactly one installation path: a command installs or updates the
|
|
6
|
+
* skills and the subagents together. The two filesystems installers
|
|
7
|
+
* (`install-skills.mjs` and `install-agents.mjs`) are implementation details
|
|
8
|
+
* this module orchestrates; they are never exposed as separate user commands.
|
|
9
|
+
*
|
|
10
|
+
* The scope is auto-detected. Inside a CodeOps project the files go to
|
|
11
|
+
* `.opencode/skills` and `.opencode/agents`; anywhere else they go to the
|
|
12
|
+
* global `~/.config/opencode/` directories. `--project` and `--global` override
|
|
13
|
+
* the detection.
|
|
9
14
|
*
|
|
10
15
|
* Usage:
|
|
11
|
-
* opencode-codeops install
|
|
12
|
-
* opencode-codeops
|
|
13
|
-
* opencode-codeops status [options]
|
|
14
|
-
* opencode-codeops uninstall [options]
|
|
15
|
-
* opencode-codeops help
|
|
16
|
+
* opencode-codeops install [options] Install or upgrade skills and agents
|
|
17
|
+
* opencode-codeops update [options] Alias of install
|
|
18
|
+
* opencode-codeops status [options] Show installed versions
|
|
19
|
+
* opencode-codeops uninstall [options] Remove managed files
|
|
20
|
+
* opencode-codeops help Show this help
|
|
16
21
|
*
|
|
17
22
|
* @module index
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
|
-
import {
|
|
25
|
+
import { execFileSync } from "node:child_process"
|
|
26
|
+
import { existsSync, realpathSync } from "node:fs"
|
|
27
|
+
import { join } from "node:path"
|
|
21
28
|
import { fileURLToPath } from "node:url"
|
|
22
29
|
|
|
30
|
+
import { PLUGIN_NAME, readConfiguredPlugin, registerPlugin } from "./lib/opencode-plugin.mjs"
|
|
31
|
+
|
|
32
|
+
/** Commands this CLI understands. */
|
|
33
|
+
const COMMANDS = new Set(["install", "update", "status", "uninstall", "help"])
|
|
34
|
+
|
|
23
35
|
/** Prints command usage. */
|
|
24
36
|
function printUsage() {
|
|
25
37
|
console.log(`CodeOps installer for OpenCode.
|
|
26
38
|
|
|
27
39
|
Usage:
|
|
28
|
-
opencode-codeops install
|
|
29
|
-
opencode-codeops
|
|
30
|
-
opencode-codeops status [options]
|
|
31
|
-
opencode-codeops uninstall [options]
|
|
32
|
-
opencode-codeops help
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
opencode-codeops install [options] Install or upgrade skills and agents (default)
|
|
41
|
+
opencode-codeops update [options] Alias of install
|
|
42
|
+
opencode-codeops status [options] Show installed skills and agents
|
|
43
|
+
opencode-codeops uninstall [options] Remove managed skills and agents
|
|
44
|
+
opencode-codeops help Show this help
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--project Force the project scope (./.opencode)
|
|
48
|
+
--global Force the global scope (~/.config/opencode)
|
|
49
|
+
--dry-run Show what would happen without writing files
|
|
50
|
+
--force Replace same-named files this package does not own
|
|
51
|
+
--link Symlink to the source instead of copying (development)
|
|
52
|
+
--no-plugin Do not register the plugin in the OpenCode config
|
|
53
|
+
-h, --help Show this help
|
|
54
|
+
|
|
55
|
+
Scope is auto-detected: inside a CodeOps project (a git repo with .opencode/ or
|
|
56
|
+
codeops/.codeops.yml) the files go to .opencode/; otherwise to ~/.config/opencode/.
|
|
57
|
+
|
|
58
|
+
install/update also register the CodeOps plugin in the OpenCode config (unless
|
|
59
|
+
--no-plugin), so standards injection and CODEOPS_PLUGIN_ROOT are enabled. Restart
|
|
60
|
+
OpenCode after installing for the plugin to load.`)
|
|
36
61
|
}
|
|
37
62
|
|
|
38
63
|
/**
|
|
39
|
-
*
|
|
64
|
+
* Finds the git top-level directory for a working directory.
|
|
40
65
|
*
|
|
41
|
-
* @param
|
|
42
|
-
* @returns `
|
|
66
|
+
* @param cwd - Directory to start from
|
|
67
|
+
* @returns The repository root, or `cwd` when it is not a git checkout
|
|
43
68
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
function findProjectRoot(cwd) {
|
|
70
|
+
try {
|
|
71
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
72
|
+
cwd,
|
|
73
|
+
encoding: "utf-8",
|
|
74
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
75
|
+
}).trim()
|
|
76
|
+
} catch {
|
|
77
|
+
return cwd
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolves whether files install into the project or globally.
|
|
83
|
+
*
|
|
84
|
+
* Explicit flags win. Otherwise a git repository is a CodeOps project when it
|
|
85
|
+
* already contains `.opencode/` or `codeops/.codeops.yml`; anything else uses
|
|
86
|
+
* the global scope.
|
|
87
|
+
*
|
|
88
|
+
* @param options - Scope flags from the command line
|
|
89
|
+
* @param options.project - Force the project scope
|
|
90
|
+
* @param options.global - Force the global scope
|
|
91
|
+
* @param cwd - Directory the command ran from
|
|
92
|
+
* @returns `"project"` or `"global"`
|
|
93
|
+
*/
|
|
94
|
+
export function resolveScope(options, cwd) {
|
|
95
|
+
if (options.project) return "project"
|
|
96
|
+
if (options.global) return "global"
|
|
97
|
+
|
|
98
|
+
const root = findProjectRoot(cwd)
|
|
99
|
+
if (existsSync(join(root, ".opencode")) || existsSync(join(root, "codeops", ".codeops.yml"))) {
|
|
100
|
+
return "project"
|
|
48
101
|
}
|
|
49
|
-
return "
|
|
102
|
+
return "global"
|
|
50
103
|
}
|
|
51
104
|
|
|
52
105
|
/**
|
|
53
|
-
* Runs
|
|
106
|
+
* Runs one command against both installers.
|
|
54
107
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
108
|
+
* @param command - `install`, `status`, or `uninstall`
|
|
109
|
+
* @param rest - Options to forward, without the scope flags (re-added below)
|
|
110
|
+
* @param io - Injectable environment (`cwd`, `home`) for tests
|
|
111
|
+
* @returns The combined process exit code
|
|
57
112
|
*/
|
|
58
|
-
async function
|
|
59
|
-
const
|
|
60
|
-
const
|
|
113
|
+
async function runCombined(command, rest, io) {
|
|
114
|
+
const skills = await import("./install-skills.mjs")
|
|
115
|
+
const agents = await import("./install-agents.mjs")
|
|
116
|
+
|
|
117
|
+
const cwd = io.cwd ?? process.cwd()
|
|
118
|
+
const noPlugin = rest.includes("--no-plugin")
|
|
119
|
+
const dryRun = rest.includes("--dry-run")
|
|
120
|
+
const scope = resolveScope(
|
|
121
|
+
{ project: rest.includes("--project"), global: rest.includes("--global") },
|
|
122
|
+
cwd
|
|
123
|
+
)
|
|
124
|
+
const passed = rest.filter(
|
|
125
|
+
(arg) => arg !== "--project" && arg !== "--global" && arg !== "--no-plugin"
|
|
126
|
+
)
|
|
127
|
+
const scoped = [scope === "project" ? "--project" : "--global", ...passed]
|
|
128
|
+
|
|
129
|
+
const skillsCode = skills.main([command, ...scoped], io)
|
|
130
|
+
const agentsCode = agents.main([command, ...scoped], io)
|
|
131
|
+
const code = skillsCode || agentsCode
|
|
132
|
+
|
|
133
|
+
if (command === "install" && code === 0 && !noPlugin && !dryRun) {
|
|
134
|
+
const version = io.version ?? skills.readPackageVersion()
|
|
135
|
+
const result = registerPlugin({ scope, version, cwd, run: io.run })
|
|
136
|
+
if (result.ok) {
|
|
137
|
+
console.log(`Plugin: registered ${result.spec} in the ${scope} OpenCode config.`)
|
|
138
|
+
console.log("Restart OpenCode to load the plugin.")
|
|
139
|
+
} else {
|
|
140
|
+
console.log(
|
|
141
|
+
`Plugin: not registered (${result.reason}). ` +
|
|
142
|
+
`Add "${PLUGIN_NAME}" to the "plugin" array in your opencode.json.`
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (command === "status") {
|
|
148
|
+
const plugins = readConfiguredPlugin({ cwd, run: io.run })
|
|
149
|
+
if (plugins === undefined) {
|
|
150
|
+
console.log("plugin: opencode CLI unavailable; cannot read the config")
|
|
151
|
+
} else {
|
|
152
|
+
const entry = plugins.find((item) => String(item).startsWith(PLUGIN_NAME))
|
|
153
|
+
console.log(entry ? `plugin: configured (${entry})` : "plugin: not configured")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return code
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Dispatches a command line.
|
|
162
|
+
*
|
|
163
|
+
* Bare options (for example `opencode-codeops --project`) mean `install`, so the
|
|
164
|
+
* common case needs no subcommand.
|
|
165
|
+
*
|
|
166
|
+
* @param argv - Arguments after the executable
|
|
167
|
+
* @param io - Injectable environment (`cwd`, `home`) for tests
|
|
168
|
+
* @returns The process exit code
|
|
169
|
+
*/
|
|
170
|
+
export async function dispatch(argv, io = {}) {
|
|
171
|
+
const first = argv[0]
|
|
172
|
+
const rest = argv.slice(1)
|
|
61
173
|
|
|
62
174
|
if (!first || first === "help" || first === "-h" || first === "--help") {
|
|
63
175
|
printUsage()
|
|
64
|
-
return
|
|
176
|
+
return 0
|
|
65
177
|
}
|
|
66
178
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
process.exitCode = skillsCode || agentsCode
|
|
73
|
-
return
|
|
179
|
+
// Keep the unified help; the internal installers still document the old
|
|
180
|
+
// component commands in their own usage text.
|
|
181
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
182
|
+
printUsage()
|
|
183
|
+
return 0
|
|
74
184
|
}
|
|
75
185
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
186
|
+
if (first.startsWith("-")) return runCombined("install", argv, io)
|
|
187
|
+
|
|
188
|
+
if (!COMMANDS.has(first)) {
|
|
189
|
+
console.error(`error: unknown command '${first}'`)
|
|
190
|
+
printUsage()
|
|
191
|
+
return 2
|
|
80
192
|
}
|
|
81
193
|
|
|
82
|
-
const
|
|
83
|
-
|
|
194
|
+
const command = first === "update" ? "install" : first
|
|
195
|
+
return runCombined(command, rest, io)
|
|
84
196
|
}
|
|
85
197
|
|
|
86
198
|
/**
|
|
87
|
-
*
|
|
199
|
+
* Runs the CLI when this module is the process entry point.
|
|
88
200
|
*
|
|
89
201
|
* The comparison resolves symlinks because npm installs the bin as a symlink in
|
|
90
202
|
* `node_modules/.bin`, so `process.argv[1]` is the link path, not the real path.
|
|
203
|
+
*/
|
|
204
|
+
async function run() {
|
|
205
|
+
process.exitCode = await dispatch(process.argv.slice(2))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* True when this module is the process entry point.
|
|
91
210
|
*
|
|
92
211
|
* @returns True when this file is the entry point
|
|
93
212
|
*/
|
package/bin/install-agents.mjs
CHANGED
|
@@ -81,20 +81,17 @@ export function readPackageVersion() {
|
|
|
81
81
|
/**
|
|
82
82
|
* Resolves the directory that holds the packaged agent definitions.
|
|
83
83
|
*
|
|
84
|
-
* The `
|
|
85
|
-
*
|
|
84
|
+
* The package's own `agents/` directory is always the source, so the installed
|
|
85
|
+
* agents match the package that was invoked. The `CODEOPS_PLUGIN_ROOT`
|
|
86
|
+
* environment variable is deliberately ignored: the plugin exports it into
|
|
87
|
+
* every shell, and honouring it would install a different checkout's agents.
|
|
88
|
+
* Pass `--source` to override for development.
|
|
86
89
|
*
|
|
87
90
|
* @param override - Optional explicit agents directory
|
|
88
91
|
* @returns Absolute path to the agents directory
|
|
89
92
|
*/
|
|
90
93
|
export function resolveSourceDir(override) {
|
|
91
94
|
if (override) return resolve(override)
|
|
92
|
-
|
|
93
|
-
const envRoot = process.env.CODEOPS_PLUGIN_ROOT
|
|
94
|
-
if (envRoot && existsSync(join(envRoot, "agents"))) {
|
|
95
|
-
return join(envRoot, "agents")
|
|
96
|
-
}
|
|
97
|
-
|
|
98
95
|
return join(PACKAGE_ROOT, "agents")
|
|
99
96
|
}
|
|
100
97
|
|
|
@@ -375,7 +372,7 @@ export function main(argv, io = {}) {
|
|
|
375
372
|
const cwd = io.cwd ?? process.cwd()
|
|
376
373
|
const version = io.version ?? readPackageVersion()
|
|
377
374
|
const sourceDir = io.source ?? resolveSourceDir(options.source)
|
|
378
|
-
const targetDir = resolveTarget(options, cwd, "agents")
|
|
375
|
+
const targetDir = resolveTarget(options, cwd, "agents", io.home)
|
|
379
376
|
|
|
380
377
|
if (!existsSync(sourceDir)) {
|
|
381
378
|
console.error(`error: agents directory not found: ${sourceDir}`)
|
|
@@ -404,8 +401,8 @@ export function main(argv, io = {}) {
|
|
|
404
401
|
if (command === "uninstall") {
|
|
405
402
|
const result = uninstallAgents({ targetDir, dryRun: options.dryRun })
|
|
406
403
|
if (result.error) {
|
|
407
|
-
console.
|
|
408
|
-
return
|
|
404
|
+
console.log(`nothing to remove at ${targetDir} (${result.error})`)
|
|
405
|
+
return 0
|
|
409
406
|
}
|
|
410
407
|
console.log(
|
|
411
408
|
`${options.dryRun ? "would remove" : "removed"} ${result.removed.length} agent(s); ` +
|
package/bin/install-skills.mjs
CHANGED
|
@@ -81,20 +81,17 @@ export function readPackageVersion() {
|
|
|
81
81
|
/**
|
|
82
82
|
* Resolves the directory that holds the shipped skills.
|
|
83
83
|
*
|
|
84
|
-
* The `
|
|
85
|
-
*
|
|
84
|
+
* The package's own `skills/` directory is always the source, so the installed
|
|
85
|
+
* skills match the package that was invoked. The `CODEOPS_PLUGIN_ROOT`
|
|
86
|
+
* environment variable is deliberately ignored: the plugin exports it into
|
|
87
|
+
* every shell, and honouring it would install a different checkout's skills.
|
|
88
|
+
* Pass `--source` to override for development.
|
|
86
89
|
*
|
|
87
90
|
* @param override - Optional explicit skills directory
|
|
88
91
|
* @returns Absolute path to the skills directory
|
|
89
92
|
*/
|
|
90
93
|
export function resolveSourceDir(override) {
|
|
91
94
|
if (override) return resolve(override)
|
|
92
|
-
|
|
93
|
-
const envRoot = process.env.CODEOPS_PLUGIN_ROOT
|
|
94
|
-
if (envRoot && existsSync(join(envRoot, "skills"))) {
|
|
95
|
-
return join(envRoot, "skills")
|
|
96
|
-
}
|
|
97
|
-
|
|
98
95
|
return join(PACKAGE_ROOT, "skills")
|
|
99
96
|
}
|
|
100
97
|
|
|
@@ -385,7 +382,7 @@ export function main(argv, io = {}) {
|
|
|
385
382
|
const cwd = io.cwd ?? process.cwd()
|
|
386
383
|
const version = io.version ?? readPackageVersion()
|
|
387
384
|
const sourceDir = io.source ?? resolveSourceDir(options.source)
|
|
388
|
-
const targetDir = resolveTarget(options, cwd, "skills")
|
|
385
|
+
const targetDir = resolveTarget(options, cwd, "skills", io.home)
|
|
389
386
|
|
|
390
387
|
if (!existsSync(sourceDir)) {
|
|
391
388
|
console.error(`error: skills directory not found: ${sourceDir}`)
|
|
@@ -414,8 +411,8 @@ export function main(argv, io = {}) {
|
|
|
414
411
|
if (command === "uninstall") {
|
|
415
412
|
const result = uninstallSkills({ targetDir, dryRun: options.dryRun })
|
|
416
413
|
if (result.error) {
|
|
417
|
-
console.
|
|
418
|
-
return
|
|
414
|
+
console.log(`nothing to remove at ${targetDir} (${result.error})`)
|
|
415
|
+
return 0
|
|
419
416
|
}
|
|
420
417
|
console.log(
|
|
421
418
|
`${options.dryRun ? "would remove" : "removed"} ${result.removed.length} skill(s); ` +
|
|
@@ -167,12 +167,13 @@ export function linkEntry({ from, dest, type = "dir" }) {
|
|
|
167
167
|
* @param options.project - Use a project-relative directory instead of global
|
|
168
168
|
* @param cwd - Project root used for `--project`
|
|
169
169
|
* @param kind - Subdirectory name under `.opencode` (for example `skills`)
|
|
170
|
+
* @param home - Home directory used for the global default (injectable for tests)
|
|
170
171
|
* @returns Absolute path to the target directory
|
|
171
172
|
*/
|
|
172
|
-
export function resolveTarget(options, cwd, kind) {
|
|
173
|
+
export function resolveTarget(options, cwd, kind, home = homedir()) {
|
|
173
174
|
if (options.target) return resolve(options.target)
|
|
174
175
|
if (options.project) return resolve(cwd, ".opencode", kind)
|
|
175
|
-
return join(
|
|
176
|
+
return join(home, ".config", "opencode", kind)
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
/**
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Registers (or inspects) the CodeOps plugin in OpenCode's own config.
|
|
4
|
+
*
|
|
5
|
+
* OpenCode ships a command that installs a plugin and updates the config
|
|
6
|
+
* safely, including JSON/JSONC formatting and version replacement:
|
|
7
|
+
*
|
|
8
|
+
* opencode plugin <module> [--global] [--force]
|
|
9
|
+
*
|
|
10
|
+
* The installer delegates to that command instead of editing `opencode.json`
|
|
11
|
+
* itself, so it never has to parse JSONC or risk clobbering a user's config.
|
|
12
|
+
* All calls are best-effort: if the `opencode` executable is missing or the
|
|
13
|
+
* command fails, the caller still completes the file install and tells the user
|
|
14
|
+
* how to register the plugin manually.
|
|
15
|
+
*
|
|
16
|
+
* @module lib/opencode-plugin
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawnSync } from "node:child_process"
|
|
20
|
+
|
|
21
|
+
/** The npm package name of the plugin. */
|
|
22
|
+
export const PLUGIN_NAME = "opencode-codeops"
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runs a command and normalizes the result.
|
|
26
|
+
*
|
|
27
|
+
* @param command - Executable to run
|
|
28
|
+
* @param args - Argument list
|
|
29
|
+
* @param options - Spawn options (for example `cwd`)
|
|
30
|
+
* @returns The exit status, stdout, stderr, and any spawn error
|
|
31
|
+
*/
|
|
32
|
+
export function defaultRun(command, args, options = {}) {
|
|
33
|
+
const result = spawnSync(command, args, { encoding: "utf-8", ...options })
|
|
34
|
+
return {
|
|
35
|
+
status: result.status ?? 1,
|
|
36
|
+
stdout: result.stdout ?? "",
|
|
37
|
+
stderr: result.stderr ?? "",
|
|
38
|
+
error: result.error,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds the arguments for `opencode plugin`.
|
|
44
|
+
*
|
|
45
|
+
* @param details - Plugin registration inputs
|
|
46
|
+
* @param details.scope - `"global"` adds `--global`; `"project"` omits it
|
|
47
|
+
* @param details.version - Exact version to pin, or `undefined` for the bare name
|
|
48
|
+
* @returns Arguments after the `opencode` executable
|
|
49
|
+
* @example
|
|
50
|
+
* buildPluginArgs({ scope: "global", version: "1.0.0" })
|
|
51
|
+
* // ["plugin", "opencode-codeops@1.0.0", "--global", "--force"]
|
|
52
|
+
*/
|
|
53
|
+
export function buildPluginArgs({ scope, version }) {
|
|
54
|
+
const module = version ? `${PLUGIN_NAME}@${version}` : PLUGIN_NAME
|
|
55
|
+
const args = ["plugin", module]
|
|
56
|
+
if (scope === "global") args.push("--global")
|
|
57
|
+
args.push("--force")
|
|
58
|
+
return args
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Registers the plugin in the OpenCode config.
|
|
63
|
+
*
|
|
64
|
+
* Tries the exact version first so plugin and files stay in sync; if the CLI
|
|
65
|
+
* rejects the versioned spec, falls back to the bare package name. Never throws.
|
|
66
|
+
*
|
|
67
|
+
* @param details - Registration inputs
|
|
68
|
+
* @param details.scope - `"global"` or `"project"`
|
|
69
|
+
* @param details.version - Version to pin
|
|
70
|
+
* @param details.cwd - Working directory (used for project scope)
|
|
71
|
+
* @param details.run - Command runner, injectable for tests
|
|
72
|
+
* @returns Whether registration succeeded, the spec used, and a reason on failure
|
|
73
|
+
*/
|
|
74
|
+
export function registerPlugin({ scope, version, cwd, run = defaultRun }) {
|
|
75
|
+
try {
|
|
76
|
+
const probe = run("opencode", ["--version"], { cwd })
|
|
77
|
+
if (probe.error || probe.status !== 0) {
|
|
78
|
+
return { ok: false, spec: null, reason: "opencode CLI not found on PATH" }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const pinned = run("opencode", buildPluginArgs({ scope, version }), { cwd })
|
|
82
|
+
if (!pinned.error && pinned.status === 0) {
|
|
83
|
+
return { ok: true, spec: `${PLUGIN_NAME}@${version}` }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const bare = run("opencode", buildPluginArgs({ scope, version: null }), { cwd })
|
|
87
|
+
if (!bare.error && bare.status === 0) {
|
|
88
|
+
return { ok: true, spec: PLUGIN_NAME }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const message = (bare.stderr || bare.stdout || pinned.stderr || pinned.stdout || "").trim()
|
|
92
|
+
return { ok: false, spec: null, reason: message.split("\n").pop() || "opencode plugin failed" }
|
|
93
|
+
} catch (caught) {
|
|
94
|
+
return { ok: false, spec: null, reason: caught.message }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Reads the resolved `plugin` list from OpenCode's effective config.
|
|
100
|
+
*
|
|
101
|
+
* @param details - Inspection inputs
|
|
102
|
+
* @param details.cwd - Working directory
|
|
103
|
+
* @param details.run - Command runner, injectable for tests
|
|
104
|
+
* @returns The plugin entries, or `undefined` when the config cannot be read
|
|
105
|
+
*/
|
|
106
|
+
export function readConfiguredPlugin({ cwd, run = defaultRun } = {}) {
|
|
107
|
+
try {
|
|
108
|
+
const result = run("opencode", ["debug", "config"], { cwd })
|
|
109
|
+
if (result.error || result.status !== 0) return undefined
|
|
110
|
+
|
|
111
|
+
const config = JSON.parse(result.stdout)
|
|
112
|
+
return Array.isArray(config.plugin) ? config.plugin : []
|
|
113
|
+
} catch {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
}
|
package/install.sh
CHANGED
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
# defaults to "latest").
|
|
18
18
|
#
|
|
19
19
|
# Subcommands (see `npx opencode-codeops help` for the full option list):
|
|
20
|
-
# install
|
|
21
|
-
#
|
|
22
|
-
# status
|
|
23
|
-
# uninstall
|
|
24
|
-
# help
|
|
20
|
+
# install Install or upgrade skills and subagents (default)
|
|
21
|
+
# update Alias of install
|
|
22
|
+
# status Show the installed versions
|
|
23
|
+
# uninstall Remove the managed files
|
|
24
|
+
# help Show installer help
|
|
25
25
|
|
|
26
26
|
set -euo pipefail
|
|
27
27
|
|
|
@@ -40,12 +40,12 @@ if ! command -v npx >/dev/null 2>&1; then
|
|
|
40
40
|
exit 1
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
-
# The subcommand is optional and defaults to install
|
|
44
|
-
#
|
|
45
|
-
subcommand="install
|
|
43
|
+
# The subcommand is optional and defaults to install. Any remaining arguments
|
|
44
|
+
# are passed through to the installer unchanged.
|
|
45
|
+
subcommand="install"
|
|
46
46
|
if (( $# > 0 )); then
|
|
47
47
|
case "$1" in
|
|
48
|
-
install |
|
|
48
|
+
install | update | status | uninstall | help)
|
|
49
49
|
subcommand="$1"
|
|
50
50
|
shift
|
|
51
51
|
;;
|
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -79,9 +79,9 @@ function installedSkillsVersion(skillsDir: string): string | undefined {
|
|
|
79
79
|
|
|
80
80
|
// ---------------------------------------------------------------------------
|
|
81
81
|
// Helper — warn (non-blocking) when the installed skills were written by a
|
|
82
|
-
// different CodeOps version than this plugin. The plugin and the
|
|
82
|
+
// different CodeOps version than this plugin. The plugin and the files are
|
|
83
83
|
// installed by separate commands, so their versions can drift; a mismatch
|
|
84
|
-
// usually means the skills need `npx opencode-codeops
|
|
84
|
+
// usually means the skills need `npx opencode-codeops update` again.
|
|
85
85
|
// ---------------------------------------------------------------------------
|
|
86
86
|
async function warnOnVersionSkew(
|
|
87
87
|
client: Parameters<Plugin>[0]["client"],
|
|
@@ -103,7 +103,7 @@ async function warnOnVersionSkew(
|
|
|
103
103
|
message:
|
|
104
104
|
`CodeOps skills at ${skillsDir} are version ${installed}, ` +
|
|
105
105
|
`but the plugin is version ${packageVersion}. ` +
|
|
106
|
-
`Run \`npx opencode-codeops@${packageVersion}
|
|
106
|
+
`Run \`npx opencode-codeops@${packageVersion} update\` to match them.`,
|
|
107
107
|
},
|
|
108
108
|
})
|
|
109
109
|
}
|
|
@@ -76,24 +76,24 @@ default when it is absent — so this line is a convenience/pin, not a requireme
|
|
|
76
76
|
- Scaffolding is intentionally simple, so it lives in skill prose; only the *migration* path
|
|
77
77
|
needs the deterministic engine. For migration, see [migration.md](migration.md).
|
|
78
78
|
|
|
79
|
-
##
|
|
79
|
+
## Project file installation
|
|
80
80
|
|
|
81
|
-
After scaffolding, install the CodeOps
|
|
82
|
-
|
|
81
|
+
After scaffolding, install the CodeOps skills and subagents into the project. Prefer the installer
|
|
82
|
+
bundled with the running plugin, so the installed files always match the plugin version:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
node "${CODEOPS_PLUGIN_ROOT}/bin/
|
|
85
|
+
node "${CODEOPS_PLUGIN_ROOT}/bin/index.mjs" install --project
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
When the plugin is not active, the published installer does the same thing:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
npx -y opencode-codeops@latest install
|
|
91
|
+
npx -y opencode-codeops@latest install --project
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
This installs the 12
|
|
95
|
-
etc.) into `.opencode/agents
|
|
96
|
-
are safe to commit to git. Files the installer owns are
|
|
97
|
-
does not own are left untouched (pass `--force` to replace
|
|
98
|
-
agent models in `opencode.json` under the `agent` key.
|
|
94
|
+
This installs the skills into `.opencode/skills/` and the 12 subagent definitions (`executor`,
|
|
95
|
+
`explorer`, `correctness-reviewer`, etc.) into `.opencode/agents/`, where OpenCode discovers and
|
|
96
|
+
loads them automatically. These files are safe to commit to git. Files the installer owns are
|
|
97
|
+
replaced on upgrade; same-named files it does not own are left untouched (pass `--force` to replace
|
|
98
|
+
them). Users can override individual agent models in `opencode.json` under the `agent` key.
|
|
99
99
|
|