regent-code 3.0.0 → 3.0.1
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/.opencode/INSTALL.md +16 -2
- package/README.md +20 -3
- package/mcp/cli.js +80 -6
- package/mcp/index.js +1 -1
- package/mcp/install.js +40 -31
- package/package.json +2 -2
package/.opencode/INSTALL.md
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
# Installation
|
|
2
2
|
|
|
3
|
+
## One-command install
|
|
4
|
+
|
|
5
|
+
If the package is published to npm, both the plugin and the MCP server install
|
|
6
|
+
with a single command against any existing `opencode.json` / `opencode.jsonc`:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx -y regent-code@3.0.1 install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Patches the project config (or the global `~/.config/opencode/` config) to add
|
|
13
|
+
`mcp.servers.regent` and the `plugins` entry. Idempotent and non-destructive;
|
|
14
|
+
`--global` forces the user config, `--file <path>` targets an exact file,
|
|
15
|
+
`--help` for options. Restart the OpenCode session afterwards.
|
|
16
|
+
|
|
3
17
|
## Add to opencode.jsonc
|
|
4
18
|
|
|
5
19
|
```jsonc
|
|
6
20
|
{
|
|
7
21
|
"$schema": "https://opencode.ai/config.json",
|
|
8
|
-
"plugins": ["regent-code@git+https://github.com/nathwn12/regent-code.git#
|
|
22
|
+
"plugins": ["regent-code@git+https://github.com/nathwn12/regent-code.git#v3.0.1"],
|
|
9
23
|
}
|
|
10
24
|
```
|
|
11
25
|
|
|
@@ -18,7 +32,7 @@
|
|
|
18
32
|
}
|
|
19
33
|
```
|
|
20
34
|
|
|
21
|
-
The pinned version is recommended. Use the unpinned branch only when you intentionally want the latest changes. The `
|
|
35
|
+
The pinned version is recommended. Use the unpinned branch only when you intentionally want the latest changes. The `v3.0.1` git tag must be pushed to GitHub before the pinned spec resolves.
|
|
22
36
|
|
|
23
37
|
## Single-source rule (duplicate plugin ID)
|
|
24
38
|
|
package/README.md
CHANGED
|
@@ -51,14 +51,31 @@ Add Regent to your OpenCode configuration:
|
|
|
51
51
|
```jsonc
|
|
52
52
|
{
|
|
53
53
|
"$schema": "https://opencode.ai/config.json",
|
|
54
|
-
"plugins": ["regent-code@git+https://github.com/nathwn12/regent-code.git#
|
|
54
|
+
"plugins": ["regent-code@git+https://github.com/nathwn12/regent-code.git#v3.0.1"],
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
This release targets the OpenCode v2 beta plugin API (`@opencode-ai/plugin@0.0.0-beta-
|
|
58
|
+
This release targets the OpenCode v2 beta plugin API (`@opencode-ai/plugin@0.0.0-beta-18314`); the beta API may change. The `v3.0.1` git tag must be pushed to GitHub before this pinned spec resolves.
|
|
59
59
|
|
|
60
60
|
> **Windows dev-machine warning (single-source rule):** when this repository is open as an OpenCode project, its own `.opencode/plugins/regent.js` is auto-loaded as a project plugin. Do NOT also pin regent in `opencode.jsonc` on the same machine — two active sources make host plugin reloads fail with `Duplicate plugin ID: regent`, leaving sessions with a torn tool surface and blocking live skill/plugin edits. Either develop unpinned (project plugin only) or pin the repo file directly: `"plugins": ["file:///Q:/PROJECTS/PERSONAL/regent-code/.opencode/plugins/regent.js"]`. One source of truth, always.
|
|
61
61
|
|
|
62
|
+
## One-command install
|
|
63
|
+
|
|
64
|
+
The fastest way to get **both** the plugin and the MCP server on any machine — no cloning, no manual config edits, no local files:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx -y regent-code@3.0.1 install
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The installer finds an existing `opencode.json` / `opencode.jsonc` (project config in the current directory first, then the global `~/.config/opencode/` config) and adds both entries:
|
|
71
|
+
|
|
72
|
+
- **MCP server**: `mcp.servers.regent` → runs `["npx", "-y", "regent-code@3.0.1"]`
|
|
73
|
+
- **Plugin**: `plugins` → `regent-code@3.0.1`
|
|
74
|
+
|
|
75
|
+
It is **idempotent and non-destructive** — it only adds or updates regent entries, preserving comments, trailing commas, and every unrelated setting in the file. Re-run it to upgrade the pinned version. Flags: `--global` forces the user config, `--file <path>` targets an exact file, `--help` explains all options. Restart the OpenCode session afterwards — plugin load and MCP connection happen on config load.
|
|
76
|
+
|
|
77
|
+
> On the machine that develops regent-code itself, respect the single-source rule above: do not add a second pin when the repo is open as a project.
|
|
78
|
+
|
|
62
79
|
## MCP Server
|
|
63
80
|
|
|
64
81
|
Since v3.0.0, regent-code ships a second distribution alongside the plugin: a Model Context Protocol (MCP) server that exposes the same six tools (`delegate`, `delegate_many`, `research`, `explore`, `changed-files`, `verify`) plus the Regent command and skill corpus as MCP prompts. It is out-of-process and works from any MCP client (Claude Desktop, Cursor, or OpenCode).
|
|
@@ -91,7 +108,7 @@ Configure it in OpenCode by adding a local MCP server:
|
|
|
91
108
|
"servers": {
|
|
92
109
|
"regent": {
|
|
93
110
|
"type": "local",
|
|
94
|
-
"command": ["npx", "-y", "regent-code@3.0.
|
|
111
|
+
"command": ["npx", "-y", "regent-code@3.0.1"]
|
|
95
112
|
}
|
|
96
113
|
}
|
|
97
114
|
}
|
package/mcp/cli.js
CHANGED
|
@@ -1,9 +1,83 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Regent CLI entry (package `bin`).
|
|
2
|
+
// no arguments -> run the MCP server over stdio (that is what a local
|
|
3
|
+
// `mcp.servers` entry in OpenCode invokes)
|
|
4
|
+
// install -> patch an existing opencode.json / opencode.jsonc to
|
|
5
|
+
// register the Regent MCP server AND plugin; idempotent and
|
|
6
|
+
// non-destructive, can create the config when missing
|
|
7
|
+
// anything else -> usage error
|
|
4
8
|
import { main } from './index.js';
|
|
9
|
+
import { install, InstallError, PLUGIN_SPEC, SERVER_NAME } from './install.js';
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
const USAGE = `Usage: npx -y ${PLUGIN_SPEC} install [--global] [--file <path>] [--help]
|
|
12
|
+
|
|
13
|
+
Patches an existing opencode.json / opencode.jsonc (V2 OpenCode config) to
|
|
14
|
+
register:
|
|
15
|
+
- MCP server "${SERVER_NAME}" running ${PLUGIN_SPEC}
|
|
16
|
+
- plugin ${PLUGIN_SPEC}
|
|
17
|
+
|
|
18
|
+
Without flags: patches ./opencode.json(c) or ./.opencode/opencode.json(c) when
|
|
19
|
+
present, otherwise the global ~/.config/opencode config, creating it if needed.
|
|
20
|
+
|
|
21
|
+
--global patch (or create) the user-global config instead
|
|
22
|
+
--file P patch the exact file path (wins over --global)
|
|
23
|
+
--help show this help
|
|
24
|
+
|
|
25
|
+
Non-destructive and idempotent: only adds or updates regent entries, leaving
|
|
26
|
+
comments, formatting, and unrelated settings untouched. Restart the OpenCode
|
|
27
|
+
session afterwards — plugin load and MCP connection happen on config load.`;
|
|
28
|
+
|
|
29
|
+
function runInstaller(args) {
|
|
30
|
+
const options = { cwd: process.cwd() };
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
switch (args[i]) {
|
|
33
|
+
case '--global':
|
|
34
|
+
options.global = true;
|
|
35
|
+
break;
|
|
36
|
+
case '--file':
|
|
37
|
+
if (!args[i + 1]) throw new InstallError('--file requires a path argument');
|
|
38
|
+
options.file = args[++i];
|
|
39
|
+
break;
|
|
40
|
+
case '--help':
|
|
41
|
+
console.log(USAGE);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
throw new InstallError(`unknown argument: ${args[i]}\n\n${USAGE}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const report = install(options);
|
|
50
|
+
const lines = [
|
|
51
|
+
`Installed into: ${report.path} (${report.scope}${report.created ? ', created' : ''})`,
|
|
52
|
+
` MCP server "${SERVER_NAME}": ${report.mcp}`,
|
|
53
|
+
` Plugin ${PLUGIN_SPEC}: ${report.plugin}`,
|
|
54
|
+
'',
|
|
55
|
+
...report.warnings.map((warning) => ` warning: ${warning}`),
|
|
56
|
+
'',
|
|
57
|
+
'Restart your OpenCode session — the plugin registers on load and the',
|
|
58
|
+
'MCP server connects on the next config load.',
|
|
59
|
+
];
|
|
60
|
+
console.log(lines.join('\n'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const args = process.argv.slice(2);
|
|
64
|
+
|
|
65
|
+
if (args[0] === 'install') {
|
|
66
|
+
try {
|
|
67
|
+
runInstaller(args.slice(1));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (err instanceof InstallError) {
|
|
70
|
+
console.error(err.message);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
} else if (args.length === 0) {
|
|
76
|
+
main().catch((err) => {
|
|
77
|
+
console.error(err);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
console.error(`Unknown arguments: ${args.join(' ')}\n\n${USAGE}`);
|
|
8
82
|
process.exit(1);
|
|
9
|
-
}
|
|
83
|
+
}
|
package/mcp/index.js
CHANGED
package/mcp/install.js
CHANGED
|
@@ -9,13 +9,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
|
9
9
|
import { join, dirname, resolve } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
|
-
import {
|
|
13
|
-
parseTree,
|
|
14
|
-
findNodeAtLocation,
|
|
15
|
-
getNodeValue,
|
|
16
|
-
modify,
|
|
17
|
-
applyEdits,
|
|
18
|
-
} from 'jsonc-parser';
|
|
12
|
+
import { parseTree, findNodeAtLocation, getNodeValue, modify, applyEdits } from 'jsonc-parser';
|
|
19
13
|
|
|
20
14
|
const require = createRequire(import.meta.url);
|
|
21
15
|
const { version } = require('../package.json');
|
|
@@ -58,10 +52,20 @@ function findExisting(candidates) {
|
|
|
58
52
|
return null;
|
|
59
53
|
}
|
|
60
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {Object} InstallOptions
|
|
57
|
+
* @property {string} [cwd] Working directory for resolving relative paths and project configs.
|
|
58
|
+
* @property {string} [file] Explicit config path (wins over every other mode).
|
|
59
|
+
* @property {boolean} [global] Force the user-global config.
|
|
60
|
+
*/
|
|
61
|
+
|
|
61
62
|
// Decide which file to patch. Never writes; the installer writes after
|
|
62
63
|
// resolving. Explicit --file wins, then forced global, then: an existing
|
|
63
64
|
// project config in cwd, else an existing global config, else the global
|
|
64
65
|
// location (created on demand).
|
|
66
|
+
/**
|
|
67
|
+
* @param {InstallOptions} [options]
|
|
68
|
+
*/
|
|
65
69
|
export function resolveTarget({ cwd = process.cwd(), file, global = false } = {}) {
|
|
66
70
|
if (file) return { path: resolve(cwd, file), scope: 'explicit' };
|
|
67
71
|
if (global) {
|
|
@@ -91,7 +95,9 @@ export function patchText(
|
|
|
91
95
|
{ pluginSpec = PLUGIN_SPEC, serverName = SERVER_NAME, serverConfig = SERVER_CONFIG } = {},
|
|
92
96
|
) {
|
|
93
97
|
const errors = [];
|
|
94
|
-
|
|
98
|
+
// JSONC by contract: comments are allowed by default, trailing commas must
|
|
99
|
+
// be opted into — both are common in real opencode.json(c) files.
|
|
100
|
+
const root = parseTree(text, errors, { allowTrailingComma: true });
|
|
95
101
|
if (!root || errors.length) {
|
|
96
102
|
throw new InstallError(`invalid JSONC: ${errors[0]?.error ?? 'could not parse'}`);
|
|
97
103
|
}
|
|
@@ -101,8 +107,17 @@ export function patchText(
|
|
|
101
107
|
|
|
102
108
|
const eol = detectEol(text);
|
|
103
109
|
const formattingOptions = { insertSpaces: true, tabSize: 2, eol };
|
|
110
|
+
/** @type {{ mcp: string, plugin: string, warnings: string[] }} */
|
|
104
111
|
const report = { mcp: 'unchanged', plugin: 'unchanged', warnings: [] };
|
|
105
|
-
|
|
112
|
+
|
|
113
|
+
// Apply each edit batch against the CURRENT text, never the original:
|
|
114
|
+
// two insertions at the same location (e.g. missing mcp + missing plugins,
|
|
115
|
+
// both inserted before the closing brace) overlap when computed against one
|
|
116
|
+
// shared base text and make jsonc-parser throw "Overlapping edit".
|
|
117
|
+
let current = text;
|
|
118
|
+
const apply = (edits) => {
|
|
119
|
+
if (edits.length) current = applyEdits(current, edits);
|
|
120
|
+
};
|
|
106
121
|
|
|
107
122
|
// ---- MCP server entry ----
|
|
108
123
|
const mcpNode = findNodeAtLocation(root, ['mcp']);
|
|
@@ -115,24 +130,18 @@ export function patchText(
|
|
|
115
130
|
if (deepEqual(existing, serverConfig)) {
|
|
116
131
|
report.mcp = 'unchanged';
|
|
117
132
|
} else {
|
|
118
|
-
|
|
119
|
-
modify(text, ['mcp', 'servers', serverName], serverConfig, { formattingOptions }),
|
|
120
|
-
);
|
|
133
|
+
apply(modify(current, ['mcp', 'servers', serverName], serverConfig, { formattingOptions }));
|
|
121
134
|
report.mcp = 'updated';
|
|
122
135
|
}
|
|
123
136
|
} else {
|
|
124
|
-
report.warnings.push(
|
|
125
|
-
`mcp.servers.${serverName} exists but is not an object; left untouched`,
|
|
126
|
-
);
|
|
137
|
+
report.warnings.push(`mcp.servers.${serverName} exists but is not an object; left untouched`);
|
|
127
138
|
}
|
|
128
139
|
} else if (mcpNode && mcpNode.type !== 'object') {
|
|
129
140
|
report.warnings.push('existing "mcp" key is not an object; left untouched');
|
|
130
141
|
} else if (serversNode && serversNode.type !== 'object') {
|
|
131
142
|
report.warnings.push('existing "mcp.servers" is not an object; left untouched');
|
|
132
143
|
} else {
|
|
133
|
-
|
|
134
|
-
modify(text, ['mcp', 'servers', serverName], serverConfig, { formattingOptions }),
|
|
135
|
-
);
|
|
144
|
+
apply(modify(current, ['mcp', 'servers', serverName], serverConfig, { formattingOptions }));
|
|
136
145
|
report.mcp = 'added';
|
|
137
146
|
}
|
|
138
147
|
|
|
@@ -150,12 +159,11 @@ export function patchText(
|
|
|
150
159
|
(entry) => /^file:/.test(entry) && entry.toLowerCase().includes('regent'),
|
|
151
160
|
);
|
|
152
161
|
const hasNpmPin =
|
|
153
|
-
entries.some(
|
|
154
|
-
|
|
162
|
+
entries.some(
|
|
163
|
+
(entry) => entry.startsWith('regent-code@') && !/^regent-code@git/.test(entry),
|
|
164
|
+
) || entries.includes('regent-code');
|
|
155
165
|
|
|
156
|
-
const next = entries
|
|
157
|
-
.filter((entry) => !gitEntries.includes(entry))
|
|
158
|
-
.concat(nonString);
|
|
166
|
+
const next = entries.filter((entry) => !gitEntries.includes(entry)).concat(nonString);
|
|
159
167
|
let reportPlugin;
|
|
160
168
|
if (hasNpmPin && gitEntries.length === 0) {
|
|
161
169
|
reportPlugin = 'unchanged';
|
|
@@ -164,31 +172,32 @@ export function patchText(
|
|
|
164
172
|
}
|
|
165
173
|
if (!hasNpmPin) next.push(pluginSpec);
|
|
166
174
|
if (filePin) {
|
|
167
|
-
report.warnings.push(
|
|
168
|
-
'regent plugin pinned via file:// (dev loop); entry left untouched',
|
|
169
|
-
);
|
|
175
|
+
report.warnings.push('regent plugin pinned via file:// (dev loop); entry left untouched');
|
|
170
176
|
}
|
|
171
177
|
if (!deepEqual(next, existing)) {
|
|
172
|
-
|
|
178
|
+
apply(modify(current, ['plugins'], next, { formattingOptions }));
|
|
173
179
|
report.plugin = reportPlugin;
|
|
174
180
|
}
|
|
175
181
|
}
|
|
176
182
|
} else {
|
|
177
|
-
|
|
183
|
+
apply(modify(current, ['plugins'], [pluginSpec], { formattingOptions }));
|
|
178
184
|
report.plugin = 'added';
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
return { text:
|
|
187
|
+
return { text: current, report };
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
// Full install against the filesystem: resolves the target config, patches it
|
|
185
191
|
// (creating the file with only the regent entries when none exists), and
|
|
186
192
|
// returns the report plus target info.
|
|
193
|
+
/**
|
|
194
|
+
* @param {InstallOptions} [options]
|
|
195
|
+
*/
|
|
187
196
|
export function install(options = {}) {
|
|
188
197
|
const target = resolveTarget(options);
|
|
189
198
|
const existingText = existsSync(target.path) ? readFileSync(target.path, 'utf8') : null;
|
|
190
199
|
const base = existingText ?? `{\n}\n`;
|
|
191
|
-
const { text, report } = patchText(base
|
|
200
|
+
const { text, report } = patchText(base);
|
|
192
201
|
|
|
193
202
|
if (text !== existingText) {
|
|
194
203
|
mkdirSync(dirname(target.path), { recursive: true });
|
|
@@ -201,4 +210,4 @@ export function install(options = {}) {
|
|
|
201
210
|
scope: target.scope,
|
|
202
211
|
created: existingText === null,
|
|
203
212
|
};
|
|
204
|
-
}
|
|
213
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "regent-code",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Agent orchestration for OpenCode. From idea to shipped — zero ceremony. Plugin + MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": ".opencode/plugins/regent.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"format": "prettier --write .opencode/plugins/ skills/ mcp/ --no-error-on-unmatched-pattern",
|
|
31
31
|
"format:check": "prettier --check .opencode/plugins/ skills/ mcp/ --no-error-on-unmatched-pattern",
|
|
32
32
|
"typecheck": "tsc --noEmit",
|
|
33
|
-
"test": "node --test .opencode/tests/regent.test.js .opencode/tests/regent.live-test.js .opencode/tests/regent.v2.test.js .opencode/tests/regent.hybrid.v2.6.1.test.js .opencode/tests/regent.runtime.v2.6.1.test.js mcp/tests/mcp.test.js",
|
|
33
|
+
"test": "node --test .opencode/tests/regent.test.js .opencode/tests/regent.live-test.js .opencode/tests/regent.v2.test.js .opencode/tests/regent.hybrid.v2.6.1.test.js .opencode/tests/regent.runtime.v2.6.1.test.js mcp/tests/mcp.test.js mcp/tests/install.test.js",
|
|
34
34
|
"verify": "npm run format:check && npm run lint && npm run typecheck && npm test"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|