@capybearista/opencode-agents-loader 1.0.0 → 2.0.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 +248 -27
- package/dist/agent-source.d.ts +28 -0
- package/dist/agent-source.js +443 -0
- package/dist/agent-source.js.map +1 -0
- package/dist/command-bridge.d.ts +55 -0
- package/dist/command-bridge.js +923 -0
- package/dist/command-bridge.js.map +1 -0
- package/dist/index.d.ts +13 -7
- package/dist/index.js +35 -181
- package/dist/index.js.map +1 -0
- package/package.json +19 -17
- package/server.js +1 -0
- /package/{LICENSE.md → LICENSE.txt} +0 -0
package/README.md
CHANGED
|
@@ -1,32 +1,155 @@
|
|
|
1
|
-
#
|
|
1
|
+
# opencode-agents-loader
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">Extend V2 agent and command discovery to the .agents/ directory standard</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://www.npmjs.com/package/@capybearista/opencode-agents-loader"><img alt="npm" src="https://img.shields.io/npm/dm/@capybearista/opencode-agents-loader?style=flat-square&color=6067e6" /></a>
|
|
6
|
+
<a href="https://opencode.ai"><img alt="opencode" src="https://img.shields.io/badge/OpenCode-Plugin-orange?style=flat-square&color=60a5e6" /></a>
|
|
7
|
+
<a href="https://opensource.org/licenses/MPL-2.0"><img alt="license" src="https://img.shields.io/badge/License-MPL--2.0-blue.svg?style=flat-square&color=60dfe6" /></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Release status
|
|
13
|
+
|
|
14
|
+
This README describes the V2 release candidate. Its planned package release is **2.0.0**;
|
|
15
|
+
it has not been published. The existing V1 `latest` release is **1.0.0** and is frozen. Do not
|
|
16
|
+
install this V2 candidate in a V1 host, and do not treat the existing V1 package version as a V2
|
|
17
|
+
release indicator.
|
|
18
|
+
|
|
19
|
+
See the [V1 plugin guide](../../docs/v1-plugins.md) for the frozen V1 setup and the
|
|
20
|
+
[documentation index](../../docs/README.md) for repository-wide scope notes.
|
|
21
|
+
|
|
22
|
+
## V2-only
|
|
23
|
+
|
|
24
|
+
This package targets the V2 Promise plugin API (`@opencode/plugin` **2.0.2**). That is the host API
|
|
25
|
+
dependency, not a claim about this package's unpublished release version. It exports a default
|
|
26
|
+
`Plugin.define({ id, setup })` definition and is not compatible with the V1 `server`/`config` hook
|
|
27
|
+
shape. Install it in an OpenCode V2 host; use the frozen V1 package for a V1 host.
|
|
4
28
|
|
|
5
29
|
## Why?
|
|
6
30
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
31
|
+
> The `.agents/` directory is becoming an open standard for agent-based tools. This plugin lets OpenCode read commands and agents from `~/.agents/` and `.agents/` directories, enabling interoperability with other harnesses and cleaner project organization.
|
|
32
|
+
|
|
33
|
+
## Philosophy: Extending OpenCode
|
|
34
|
+
|
|
35
|
+
OpenCode natively reads from `.opencode/` and configuration files. During awaited setup this plugin
|
|
36
|
+
creates managed relative symlinks from `.agents/` command and agent files into the matching native
|
|
37
|
+
directories. Native OpenCode therefore owns frontmatter decoding, V1 migration, argument
|
|
38
|
+
expansion, shell interpolation, subagent execution, agent permissions, and precedence. The plugin
|
|
39
|
+
does not register an agent or command transform and does not copy prompt bodies.
|
|
40
|
+
|
|
41
|
+
### Architecture
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
src/index.ts
|
|
45
|
+
└── V2 setup
|
|
46
|
+
├── awaits the scoped command symlink bridge
|
|
47
|
+
└── awaits the scoped agent symlink bridge
|
|
48
|
+
|
|
49
|
+
command-bridge.ts
|
|
50
|
+
├── materializes per-scope native command and agent links
|
|
51
|
+
├── records exact ownership in separate manifests and locks
|
|
52
|
+
└── polls bounded censuses and emits the affected host reload signal
|
|
53
|
+
|
|
54
|
+
agent-source.ts
|
|
55
|
+
└── validates V1/V2 agent frontmatter before an agent link is created
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- Discovers command and agent markdown files from `~/.agents/` and `.agents/` directories
|
|
61
|
+
- Supports `command/`, `commands/`, `agent/`, and `agents/` source subdirectory naming
|
|
62
|
+
- Materializes managed relative symlinks so native OpenCode owns parsing and execution
|
|
63
|
+
- Respects existing native files and explicit config entries — plugin entries never overwrite native config
|
|
64
|
+
- Scans scopes between the declared project root and the current working directory, not above the root
|
|
65
|
+
- YAML frontmatter support for metadata in markdown files
|
|
66
|
+
- Preserves V2 model references, system bodies, agent modes, and ordered permission rules
|
|
67
|
+
- Automatically starts both native bridges; no manual sync step is required
|
|
68
|
+
- Preserves complete command and agent source bytes and nested relative names by symlink, including
|
|
69
|
+
shell and subagent templates
|
|
70
|
+
- Uses `OPENCODE_CONFIG_DIR` for the global native destination; global sources remain in `~/.agents/`
|
|
71
|
+
|
|
72
|
+
## Install
|
|
73
|
+
|
|
74
|
+
The V2 registry release is not available yet. `@latest` currently identifies the V1 line, and
|
|
75
|
+
`@v2` is a semver range rather than a channel. The local recipe below is for an isolated V2
|
|
76
|
+
profile only.
|
|
77
|
+
|
|
78
|
+
### Local V2 directory
|
|
79
|
+
|
|
80
|
+
Build before checking a local directory or a packaged artifact. From this package directory, run
|
|
81
|
+
`bun run build`; from the monorepo root, the canonical command is `bun run build`. Then register the
|
|
82
|
+
package **directory**, not a JavaScript file:
|
|
83
|
+
|
|
84
|
+
Add the directory to your isolated V2 server profile's `opencode.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"$schema": "https://opencode.ai/config.json",
|
|
89
|
+
"plugins": ["/absolute/path/to/opencode-plugins/packages/opencode-agents-loader"]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
10
92
|
|
|
11
|
-
|
|
93
|
+
A filesystem directory target is resolved through its root `server.js`,
|
|
94
|
+
not through the package's `exports` map, and the wrapper intentionally exposes no TUI or RPC entrypoint.
|
|
95
|
+
The package-name import still uses the unchanged `exports` map, including the default and named exports
|
|
96
|
+
from `dist/index.js`. Because the wrapper imports that built file, a build is required before using a
|
|
97
|
+
filesystem-local package root. Do not configure `server.js` or `dist/index.js` directly; use the
|
|
98
|
+
directory so the host can apply its local-entrypoint rules.
|
|
12
99
|
|
|
13
|
-
|
|
100
|
+
### Registry release, after publication
|
|
14
101
|
|
|
15
|
-
|
|
102
|
+
After the planned `2.0.0` release is published, the V2 server profile
|
|
103
|
+
`~/.config/opencode/opencode.json` may contain the loader under the plural `plugins` key:
|
|
16
104
|
|
|
17
105
|
```json
|
|
18
106
|
{
|
|
19
|
-
"
|
|
107
|
+
"$schema": "https://opencode.ai/config.json",
|
|
108
|
+
"plugins": ["@capybearista/opencode-agents-loader@opencode2"]
|
|
20
109
|
}
|
|
21
110
|
```
|
|
22
111
|
|
|
23
|
-
|
|
112
|
+
Use `@2.0.0` instead of `@opencode2` for an exact pin. The V2 command definitions are separate
|
|
113
|
+
from the V1 CLI:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
opencode2 plugin add @capybearista/opencode-agents-loader@opencode2
|
|
117
|
+
opencode2 plugin check
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`check` reports available updates without installing them. Use `opencode2 plugin update` with the
|
|
121
|
+
configured target as its argument to update that package; omitting the target updates all configured
|
|
122
|
+
mutable targets. Restarting the host does not upgrade a package, and exact pins remain fixed.
|
|
123
|
+
The `opencode2` tag identifies OpenCode 2 compatibility, not the plugin's package major: later
|
|
124
|
+
package majors can use the same channel. The channel is not published yet.
|
|
125
|
+
|
|
126
|
+
Keep this V2 profile separate from V1 configuration. Both hosts may use the default
|
|
127
|
+
`~/.config/opencode/` directory. `OPENCODE_CONFIG_DIR` selects a separate configuration root;
|
|
128
|
+
isolating session data and caches requires separate data, state, cache, and home locations too.
|
|
129
|
+
The plugin does not migrate configuration or session data.
|
|
130
|
+
|
|
131
|
+
For the frozen V1 package, use a V1 host and the V1 command `opencode plugin <module>` with an
|
|
132
|
+
explicit `@1.0.0` spec. V1 reuses cached installations; restarting alone is not an update. Do not
|
|
133
|
+
use the V2 `plugin add`, `check`, or `update` commands for that host.
|
|
134
|
+
|
|
135
|
+
## Usage
|
|
136
|
+
|
|
137
|
+
Create markdown files with YAML frontmatter:
|
|
138
|
+
|
|
139
|
+
```md
|
|
140
|
+
---
|
|
141
|
+
description: "Does something useful"
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
# Command Body
|
|
145
|
+
This is the command content that the agent will process.
|
|
146
|
+
```
|
|
24
147
|
|
|
25
148
|
### Commands
|
|
26
149
|
|
|
27
150
|
Place command markdown files in `command/` or `commands/` subdirectories:
|
|
28
151
|
|
|
29
|
-
```
|
|
152
|
+
```text
|
|
30
153
|
~/.agents/
|
|
31
154
|
commands/
|
|
32
155
|
hello.md
|
|
@@ -42,7 +165,7 @@ Place command markdown files in `command/` or `commands/` subdirectories:
|
|
|
42
165
|
|
|
43
166
|
Place agent markdown files in `agent/` or `agents/` subdirectories:
|
|
44
167
|
|
|
45
|
-
```
|
|
168
|
+
```text
|
|
46
169
|
~/.agents/
|
|
47
170
|
agents/
|
|
48
171
|
reviewer.md
|
|
@@ -53,28 +176,126 @@ Place agent markdown files in `agent/` or `agents/` subdirectories:
|
|
|
53
176
|
project-expert.md
|
|
54
177
|
```
|
|
55
178
|
|
|
56
|
-
|
|
179
|
+
Only `agent/` and `agents/` are plugin source directories. `mode/` and `modes/` remain native
|
|
180
|
+
OpenCode aliases and are not treated as new plugin agent sources; a native mode with the same name
|
|
181
|
+
still reserves that name. A source agent's declared `mode` is preserved, including `subagent`; the
|
|
182
|
+
bridge never infers or forces `primary`.
|
|
183
|
+
|
|
184
|
+
### Native bridges, precedence, and ownership
|
|
185
|
+
|
|
186
|
+
Setup walks lexical ancestor scopes from the project root to the current working directory. The
|
|
187
|
+
initial command and agent syncs are awaited before setup returns so native OpenCode startup
|
|
188
|
+
discovery can see the links. Each scope maps both `.agents/command/` and `.agents/commands/` into
|
|
189
|
+
that **same** scope's `.opencode/commands/` directory, and both `.agents/agent/` and
|
|
190
|
+
`.agents/agents/` into that scope's `.opencode/agents/` directory. Global files map from
|
|
191
|
+
`~/.agents/{command,commands}` and `~/.agents/{agent,agents}` into the corresponding
|
|
192
|
+
`$OPENCODE_CONFIG_DIR` directories, or `~/.config/opencode/` when the variable is absent. Original
|
|
193
|
+
source files and OpenCode config files are never rewritten.
|
|
194
|
+
Startup may create native command and agent directories, managed symlinks, and manifest metadata; importing
|
|
195
|
+
the module alone performs no filesystem writes.
|
|
196
|
+
|
|
197
|
+
OpenCode resolves scope first. At a given scope, native command and agent files, native aliases,
|
|
198
|
+
and explicit JSON/JSONC config names reserve a name before the bridge creates a link. A nearer
|
|
199
|
+
project `.agents/` scope is materialized in its own nearer `.opencode/` directory and therefore
|
|
200
|
+
remains nearer than an ancestor scope; every eligible scope keeps its own links. The bridge does
|
|
201
|
+
not delete an ancestor artifact merely because a nearer scope has the same name. Global files map
|
|
202
|
+
from `~/.agents/` to the global native root, while native JSON/JSONC and `OPENCODE_CONFIG*`
|
|
203
|
+
ordering remain OpenCode's authority. The bridge reserves names at a scope; it does not promise to
|
|
204
|
+
reorder native sources.
|
|
205
|
+
|
|
206
|
+
If both `.agents/command/name.md` and `.agents/commands/name.md` exist at one scope, the plural
|
|
207
|
+
`commands/` spelling wins deterministically. Nested names remain nested (`review/code.md` stays
|
|
208
|
+
`review/code.md`); this is the native command naming behavior and may differ from old V1
|
|
209
|
+
flattening.
|
|
210
|
+
|
|
211
|
+
Each destination parent has version-1, domain-specific ownership metadata at `.agents-loader/`.
|
|
212
|
+
Commands use `commands-manifest.json` and `commands.lock`; agents use `agents-manifest.json` and
|
|
213
|
+
`agents.lock`. For a project destination these live under `.opencode/.agents-loader/`; for the
|
|
214
|
+
global destination they live under `$OPENCODE_CONFIG_DIR/.agents-loader/`. Entries record the
|
|
215
|
+
relative destination, source path, and literal `readlink` payload. Only an exact recorded symlink
|
|
216
|
+
can be removed. Regular files, directories, unowned symlinks, alias collisions, malformed
|
|
217
|
+
manifests, traversal, and parent-symlink escapes fail closed with diagnostics; consecutive duplicate
|
|
218
|
+
messages are suppressed. Neither bridge adopts or removes the other domain's artifacts.
|
|
219
|
+
|
|
220
|
+
Manifest writes are atomic. Cooperating instances use a bounded per-domain lock and never
|
|
221
|
+
guess-delete a stale lock. A pre-existing or crash-held lock fails closed without changing that
|
|
222
|
+
scope. Lock diagnostics include the exact lock path; manually remove that exact
|
|
223
|
+
`commands.lock` or `agents.lock` only after all affected OpenCode instances are stopped, then
|
|
224
|
+
retry. An uncooperative process can still win a filesystem TOCTOU race between validation and
|
|
225
|
+
`symlink`/`unlink`; the bridge does not claim to eliminate that operating-system limitation.
|
|
226
|
+
|
|
227
|
+
Each bridge polls at a modest fixed interval (500 ms by default). Source sync detects source edits
|
|
228
|
+
even when a symlink is unchanged; creation, deletion, rename, invalid-source removal, and
|
|
229
|
+
native-name reservation changes reconcile and may emit the affected `ctx.command.reload()` or
|
|
230
|
+
`ctx.agent.reload()` once per observed change. A reload signal is not itself a native rescan: a
|
|
231
|
+
host watcher must rescan the materialized links, or restart OpenCode as the fallback. Whether an
|
|
232
|
+
initially missing native config directory is picked up by the host's startup scan is a human
|
|
233
|
+
verification gate. Dispose clears each timer. In the observed V2 check, new definitions appeared
|
|
234
|
+
without a restart, description changes took effect after a restart, and removal cleanup succeeded.
|
|
235
|
+
No deletion-latency or universal hot-reload guarantee is made.
|
|
236
|
+
|
|
237
|
+
### Limitations
|
|
238
|
+
|
|
239
|
+
- Command execution is intentionally not emulated by this package. Native OpenCode owns `$ARGUMENTS`,
|
|
240
|
+
`$N`, the exclamation/backtick shell syntax, and `subagent` behavior; live execution checks remain
|
|
241
|
+
a human responsibility.
|
|
242
|
+
- Any source file or directory symlink under the active `.agents/{command,commands,agent,agents}`
|
|
243
|
+
sources makes the affected bridge's census at that scope unsafe. That bridge emits a diagnostic
|
|
244
|
+
and preserves its last known managed links rather than reconciling an incomplete census.
|
|
245
|
+
- Destination directories must be writable and support symlink creation. There is no copy fallback.
|
|
246
|
+
- JSON/JSONC inspection is limited to same-scope name reservation. OpenCode remains the parser and
|
|
247
|
+
ordering authority; the bridge never normalizes or rewrites those files.
|
|
248
|
+
- A current working directory outside the declared project root is rejected safely; the bridge does
|
|
249
|
+
not silently fall back to writing the project root or broaden discovery to `/`.
|
|
250
|
+
- Invalid/unsupported agent metadata, including malformed permission rules, is skipped rather than silently
|
|
251
|
+
falling back to broader tool access. V2 `request` accepts only `headers` and `body`; `settings` and
|
|
252
|
+
allowed-tools-like unknown permission aliases are rejected with a diagnostic. Mixing legacy
|
|
253
|
+
`tools`/`permission` fields with V2 `permissions` is rejected clearly rather than merged. Legacy
|
|
254
|
+
`disable`, `options`, `temperature`, and model variants are validated using the native V1 migration shape.
|
|
255
|
+
If an existing source becomes invalid, its owned link can be removed; native catalog changes still
|
|
256
|
+
require a host rescan or restart.
|
|
257
|
+
- Agent prompts and frontmatter bytes are never copied or rewritten; the native loader follows the managed
|
|
258
|
+
symlink and owns V1/V2 decoding. This package does not implement prompt inheritance or an agent transform;
|
|
259
|
+
there is no V2 inheritance feature.
|
|
260
|
+
|
|
261
|
+
## Configuration
|
|
262
|
+
|
|
263
|
+
There are no plugin-specific settings. After registering the plugin in the V2 server profile, it
|
|
264
|
+
reads the documented `.agents/` directories automatically. Keep the V2 profile separate from any
|
|
265
|
+
V1 configuration as described in [Install](#install).
|
|
266
|
+
|
|
267
|
+
## Troubleshooting
|
|
268
|
+
|
|
269
|
+
- If commands or agents don't appear, verify your files end in `.md` and include valid YAML frontmatter
|
|
270
|
+
- Confirm the directory naming is `commands/` or `command/`, `agents/` or `agent/`
|
|
271
|
+
- Inspect the scoped `.agents-loader/{commands,agents}-manifest.json` and diagnostics before removing
|
|
272
|
+
anything; unowned native files are intentionally preserved
|
|
57
273
|
|
|
58
|
-
|
|
59
|
-
- Commands: https://opencode.ai/docs/commands/#markdown
|
|
60
|
-
- Agents: https://opencode.ai/docs/agents/#markdown
|
|
274
|
+
## Verification notes
|
|
61
275
|
|
|
62
|
-
|
|
276
|
+
The package checks include the real local-directory resolver regression, build/static checks, and
|
|
277
|
+
the scoped bridge tests. In one isolated V2 host, human verification confirmed agent and command
|
|
278
|
+
discovery and native execution, global/project precedence fixtures, live additions, restart-required
|
|
279
|
+
description changes, and removal cleanup. These observations do not promise identical watcher
|
|
280
|
+
behavior for every host, platform, provider, or config layout.
|
|
63
281
|
|
|
64
|
-
|
|
282
|
+
## Contributing
|
|
65
283
|
|
|
66
|
-
|
|
284
|
+
This package lives in the `opencode-plugins` monorepo.
|
|
67
285
|
|
|
68
|
-
|
|
69
|
-
2. `.agents/` directory (closest to project root)
|
|
70
|
-
3. `.agents/` parent directories (walking up the tree)
|
|
71
|
-
4. `~/.config/opencode/` directory (global)
|
|
72
|
-
5. `~/.agents/` directory (global)
|
|
286
|
+
See the [contribution guidelines](../../CONTRIBUTING.md) before opening a pull request.
|
|
73
287
|
|
|
74
|
-
|
|
288
|
+
- From the monorepo root, run `bun run build` before artifact checks, then `bun run typecheck`,
|
|
289
|
+
`bun run lint`, and the canonical `bun run test` Turbo pipeline. Do not use bare root `bun test`
|
|
290
|
+
as the workspace check.
|
|
291
|
+
- For a focused run, `bun test` is supported from this package directory; its tests include the
|
|
292
|
+
real local-directory resolver regression.
|
|
293
|
+
- `bun run check` writes Biome changes; use it only when formatting changes are intended.
|
|
294
|
+
- Keep the plugin focused on agent/command discovery from `.agents/` directories.
|
|
295
|
+
- Prefer small, direct changes.
|
|
75
296
|
|
|
76
|
-
|
|
297
|
+
Please open an issue or check for existing ones before creating a pull request.
|
|
77
298
|
|
|
78
299
|
## License
|
|
79
300
|
|
|
80
|
-
MPL-2.0
|
|
301
|
+
[MPL-2.0](./LICENSE.txt)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Agent as AgentSchema } from "@opencode/plugin";
|
|
2
|
+
export interface MarkdownEntry {
|
|
3
|
+
readonly data: Record<string, unknown>;
|
|
4
|
+
readonly content: string;
|
|
5
|
+
}
|
|
6
|
+
type AgentModel = NonNullable<AgentSchema.Info["model"]>;
|
|
7
|
+
type AgentPermission = AgentSchema.Info["permissions"][number];
|
|
8
|
+
export interface AgentPatch {
|
|
9
|
+
readonly system: string;
|
|
10
|
+
readonly model?: AgentModel;
|
|
11
|
+
readonly request?: {
|
|
12
|
+
readonly headers?: Record<string, string>;
|
|
13
|
+
readonly body?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
readonly description?: string;
|
|
16
|
+
readonly mode?: AgentSchema.Info["mode"];
|
|
17
|
+
readonly hidden?: boolean;
|
|
18
|
+
readonly color?: NonNullable<AgentSchema.Info["color"]>;
|
|
19
|
+
readonly steps?: NonNullable<AgentSchema.Info["steps"]>;
|
|
20
|
+
readonly disabled?: boolean;
|
|
21
|
+
readonly permissions: AgentPermission[];
|
|
22
|
+
}
|
|
23
|
+
export declare function fallbackSanitization(content: string): string;
|
|
24
|
+
export declare function parseMarkdown(filePath: string): Promise<MarkdownEntry>;
|
|
25
|
+
export declare function parseMarkdownContent(template: string, source: string): MarkdownEntry;
|
|
26
|
+
export declare function validateAgentContent(source: string, content: Buffer, home?: string): string | undefined;
|
|
27
|
+
export declare function convertAgent(_name: string, entry: MarkdownEntry, home?: string): AgentPatch | undefined;
|
|
28
|
+
export {};
|