@ory/opencode 0.14.0 → 1.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/dist/cli/main.js CHANGED
@@ -46,11 +46,22 @@ const path = __importStar(require("node:path"));
46
46
  const fs = __importStar(require("node:fs"));
47
47
  const argus_1 = require("@ory/argus");
48
48
  const assets_js_1 = require("./assets.js");
49
+ const config_js_1 = require("./config.js");
49
50
  const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
50
51
  function main() {
51
52
  const [command, ...args] = process.argv.slice(2);
53
+ // `--help` after a matched subcommand: print usage, do nothing else.
54
+ // Without this the switch below ignores the flag and `install --help`
55
+ // performs a real install (#221).
56
+ if ((0, argus_1.shouldPrintHelp)(command, args))
57
+ return help();
52
58
  switch (command) {
53
- case "install":
59
+ case "install": {
60
+ if (args.includes("--print")) {
61
+ process.argv = ["node", "setup.js", ...args];
62
+ require("./setup.js");
63
+ break;
64
+ }
54
65
  (0, argus_1.beginDeferNextSteps)();
55
66
  install(args);
56
67
  (0, argus_1.runPostInstall)("ory-opencode", "opencode", args).then(() => process.exit(0), (err) => {
@@ -58,9 +69,13 @@ function main() {
58
69
  process.exit(1);
59
70
  });
60
71
  break;
72
+ }
61
73
  case "uninstall":
62
74
  uninstall(args);
63
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
75
+ (0, argus_1.clearCredentialsForUninstall)({
76
+ harness: "opencode",
77
+ purge: args.includes("--purge"),
78
+ }).then(() => process.exit(0), (err) => {
64
79
  console.error(err.message ?? err);
65
80
  process.exit(1);
66
81
  });
@@ -69,7 +84,7 @@ function main() {
69
84
  (0, argus_1.runConfigureCommand)("ory-opencode", args);
70
85
  break;
71
86
  case "agent":
72
- (0, argus_1.runAgentCommand)("ory-opencode", args).then((code) => process.exit(code), (err) => {
87
+ (0, argus_1.runAgentCommand)("ory-opencode", "opencode", args).then((code) => process.exit(code), (err) => {
73
88
  console.error(err.message ?? err);
74
89
  process.exit(1);
75
90
  });
@@ -86,17 +101,11 @@ function main() {
86
101
  process.exit(1);
87
102
  });
88
103
  break;
89
- case "local":
90
- (0, argus_1.runLocalCommand)("ory-opencode", args).catch((err) => {
91
- console.error(err.message ?? err);
92
- process.exit(1);
93
- });
94
- break;
95
104
  case "watch":
96
- (0, argus_1.runWatchCommand)("opencode", args);
105
+ (0, argus_1.runWatchCommand)("ory-opencode", "opencode", args).then((code) => process.exit(code), (err) => { console.error(err.message ?? err); process.exit(1); });
97
106
  break;
98
- case "dashboard":
99
- (0, argus_1.runDashboardCommand)("ory-opencode", "opencode", args).then((code) => process.exit(code), (err) => {
107
+ case "local":
108
+ (0, argus_1.runLocalCommand)("ory-opencode", args).catch((err) => {
100
109
  console.error(err.message ?? err);
101
110
  process.exit(1);
102
111
  });
@@ -124,39 +133,51 @@ function parseProjectDir(args) {
124
133
  return args[idx + 1];
125
134
  return process.cwd();
126
135
  }
127
- const PLUGIN_MODULE = "@ory/opencode";
128
- const MCP_SERVER_NAME = "ory";
129
- const MCP_SERVER_COMMAND = [
130
- "npx",
131
- "-y",
132
- "--package=@ory/mcp-server",
133
- "ory-mcp-server",
134
- ];
135
- const OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
136
136
  function install(args) {
137
137
  const projectDir = parseProjectDir(args);
138
138
  const configPath = path.join(projectDir, "opencode.json");
139
139
  const fileExists = fs.existsSync(configPath);
140
- const config = (0, argus_1.readJsonFile)(configPath);
140
+ let config = (0, argus_1.readJsonFile)(configPath);
141
141
  // Set $schema for editor autocomplete on fresh configs only — don't
142
142
  // overwrite a user's existing schema reference.
143
143
  if (!fileExists && !config.$schema) {
144
- config.$schema = OPENCODE_SCHEMA_URL;
144
+ config.$schema = config_js_1.OPENCODE_SCHEMA_URL;
145
+ }
146
+ // Resolve the runtime once, here. The spec written below is a path into it,
147
+ // so OpenCode imports this exact copy instead of running its own npm install
148
+ // into a cache it never re-checks.
149
+ const runtime = (0, argus_1.wireRuntime)({
150
+ harness: "opencode",
151
+ packageName: "@ory/opencode",
152
+ packageRoot: PACKAGE_ROOT,
153
+ installCommand: "npx -y -p @ory/opencode ory-opencode install",
154
+ args,
155
+ // OpenCode loads the plugin in-process, so there is no hook subprocess and
156
+ // no hook shim — the entry it imports is the package's main module.
157
+ entryRelPath: "dist/index.js",
158
+ inProcess: true,
159
+ });
160
+ const spec = (0, config_js_1.runtimePluginSpec)((0, argus_1.moduleSpecifier)(runtime.target));
161
+ config = (0, config_js_1.mergePlugin)(config, spec);
162
+ if (runtime.mcpServer) {
163
+ config = (0, config_js_1.mergeMcp)(config, [
164
+ runtime.mcpServer.command,
165
+ ...runtime.mcpServer.args,
166
+ ]);
145
167
  }
146
- const plugin = (config.plugin ?? []).filter((p) => (typeof p === "string" ? p : p[0]) !== PLUGIN_MODULE);
147
- plugin.push(PLUGIN_MODULE);
148
- config.plugin = plugin;
149
- // OpenCode's config schema uses `mcp` (singular) with a `local` connector
150
- // shape `{ type: "local", command: [...] }` — not the generic
151
- // `mcpServers` / `{command, args}` form used by Claude Code.
152
- const mcp = (config.mcp ?? {});
153
- mcp[MCP_SERVER_NAME] = { type: "local", command: MCP_SERVER_COMMAND };
154
- config.mcp = mcp;
155
168
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
156
169
  (0, assets_js_1.installOpencodeOryAssets)(projectDir);
170
+ console.log(runtime.target.kind === "linked"
171
+ ? `Runtime: linked to ${runtime.target.packageDir} (dev)`
172
+ : `Runtime: ${runtime.target.packageName}@${runtime.target.version} in ${runtime.target.storeDir}`);
173
+ for (const pruned of runtime.prunedStores) {
174
+ console.log(` Removed stale runtime: ${pruned}`);
175
+ }
157
176
  console.log(`Ory plugin and MCP server registered in ${configPath}`);
158
- console.log(` Plugin: ${PLUGIN_MODULE}`);
159
- console.log(` MCP server: ${MCP_SERVER_NAME} (${MCP_SERVER_COMMAND.join(" ")})`);
177
+ console.log(` Plugin: ${spec}`);
178
+ if (runtime.mcpServer) {
179
+ console.log(` MCP server: ${config_js_1.MCP_SERVER_NAME} (${runtime.mcpServer.command} ${runtime.mcpServer.args.join(" ")})`);
180
+ }
160
181
  console.log(` Skills + commands installed under ${path.join(projectDir, ".opencode")}`);
161
182
  (0, argus_1.printEnvHelp)("ory-opencode");
162
183
  }
@@ -167,22 +188,9 @@ function uninstall(args) {
167
188
  console.log("No opencode.json found. Nothing to uninstall.");
168
189
  return;
169
190
  }
170
- const config = (0, argus_1.readJsonFile)(configPath);
171
- const plugin = (config.plugin ?? []).filter((p) => (typeof p === "string" ? p : p[0]) !== PLUGIN_MODULE);
172
- if (plugin.length === 0) {
173
- delete config.plugin;
174
- }
175
- else {
176
- config.plugin = plugin;
177
- }
178
- const mcp = (config.mcp ?? {});
179
- delete mcp[MCP_SERVER_NAME];
180
- if (Object.keys(mcp).length === 0) {
181
- delete config.mcp;
182
- }
183
- else {
184
- config.mcp = mcp;
185
- }
191
+ let config = (0, argus_1.readJsonFile)(configPath);
192
+ config = (0, config_js_1.removePlugin)(config);
193
+ config = (0, config_js_1.removeMcp)(config);
186
194
  // If the file is now effectively empty (we created it, only $schema left
187
195
  // from our own install) delete it instead of leaving `{}` behind. Keep
188
196
  // it if there's any user-authored content.
@@ -196,6 +204,10 @@ function uninstall(args) {
196
204
  console.log(`Ory plugin and MCP server removed from ${configPath}`);
197
205
  }
198
206
  (0, assets_js_1.uninstallOpencodeOryAssets)(projectDir);
207
+ (0, argus_1.removeRuntimeWiring)("opencode");
208
+ for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
209
+ console.log(` Removed runtime: ${pruned}`);
210
+ }
199
211
  }
200
212
  async function status(args) {
201
213
  const projectDir = parseProjectDir(args);
@@ -204,14 +216,32 @@ async function status(args) {
204
216
  title: "OpenCode",
205
217
  printPluginSection: () => {
206
218
  const config = (0, argus_1.readJsonFile)(configPath);
207
- const moduleName = "@ory/opencode";
208
- const plugin = (config.plugin ?? []);
209
- const oryPlugin = plugin.find((p) => (typeof p === "string" ? p : p[0]) === moduleName);
219
+ const entry = (0, config_js_1.findOryPluginEntry)(config);
210
220
  console.log("Hooks & plugin:");
211
221
  console.log(` Config file: ${configPath}`);
212
- console.log(` Plugin registered: ${oryPlugin ? "yes" : "NO"}`);
213
- if (oryPlugin) {
214
- console.log(` Module: ${typeof oryPlugin === "string" ? oryPlugin : oryPlugin[0]}`);
222
+ console.log(` Plugin registered: ${entry ? "yes" : "NO"}`);
223
+ console.log(` Runtime: ${(0, argus_1.describeRuntimeHealth)((0, argus_1.checkRuntimeHealth)("opencode"))}`);
224
+ if (!entry)
225
+ return;
226
+ const spec = (0, config_js_1.entrySpec)(entry);
227
+ console.log(` Spec: ${spec}`);
228
+ // Registration in opencode.json is not the same as the plugin loading.
229
+ // The spec is a path into the resolved runtime, so this reports whether
230
+ // that copy is actually there — the npm-cache failure modes (stale build,
231
+ // silently retried failed install) can no longer occur, but a runtime
232
+ // deleted out of band still can.
233
+ const resolved = (0, config_js_1.resolveInstalledPlugin)(spec);
234
+ if (resolved.version) {
235
+ console.log(` Loads: ${resolved.version} (${resolved.source})`);
236
+ console.log(` From: ${resolved.dir}`);
237
+ }
238
+ else {
239
+ console.log(` Loads: MISSING — no package at ${resolved.dir ?? spec}; re-run install`);
240
+ }
241
+ // Cache directories left behind by an earlier npm-spec install. Nothing
242
+ // reads them now, and OpenCode will not clean them up itself.
243
+ for (const other of resolved.otherCached) {
244
+ console.log(` Stale cache: ${other.spec}${other.version ? ` (${other.version})` : ""} — safe to delete`);
215
245
  }
216
246
  },
217
247
  });
@@ -225,29 +255,30 @@ Usage:
225
255
 
226
256
  Commands:
227
257
  install [--project-dir <path>] Register the Ory plugin and MCP server in opencode.json
258
+ [--plugin-spec <spec>] override the plugin spec written
259
+ to opencode.json (defaults to this version, pinned; pass
260
+ a file:// path to load a local build instead of npm)
228
261
  uninstall [--project-dir <path>] Remove the Ory plugin and MCP server from opencode.json
229
262
  configure Set or view Ory project URL and API key
230
263
  agent <status|unregister> Manage the agent's OAuth2 (DCR) identity
231
- permissions <cmd> Manage permission mode and tool permissions
232
- (status, bootstrap, observe, enforce)
264
+ permissions Show the live permission mode and per-tool coverage
233
265
  local <cmd> Manage local Ory dev environment
234
266
  (up, down, status, seed, logs, env, configure, reset)
235
267
  status [--project-dir <path>] Show plugin configuration and status
236
- dashboard Open the Ory Agent dashboard (system health + configuration)
237
- watch [trace-file] Tail the trace stream (OTel spans) live
268
+ watch [--json] [--lines <count>] Follow the live activity/debug log
238
269
  version Show version and the ory-agent-plugins build commit
239
270
 
240
271
  After installing, the plugin registers these OpenCode hooks:
241
272
  config Log resolved Ory config at startup
242
- chat.message Trace user prompts per turn
273
+ chat.message Record user prompt activity per turn
243
274
  permission.ask Authorize actions via Ory Permissions
244
275
  tool.execute.before Check tool permissions via Ory Permissions
245
276
  tool.execute.after Audit log tool executions
246
277
 
247
278
  Examples:
248
279
  npx -y -p @ory/opencode ory-opencode install
249
- npx -y -p @ory/opencode ory-opencode configure --project-url https://<slug>.projects.oryapis.com --api-key ory_pat_...
250
- npx -y -p @ory/opencode ory-opencode permissions bootstrap
280
+ npx -y -p @ory/opencode ory-opencode configure --project-url https://<slug>.projects.oryapis.com --agent-security-url https://agents.console.ory.com
281
+ npx -y -p @ory/opencode ory-opencode permissions
251
282
  npx -y -p @ory/opencode ory-opencode local up
252
283
  npx -y -p @ory/opencode ory-opencode status
253
284
  `);
@@ -7,5 +7,8 @@
7
7
  * npx ory-opencode-setup --project-dir /path/to/repo
8
8
  * npx ory-opencode-setup --print # Print config to stdout without writing
9
9
  * npx ory-opencode-setup --uninstall # Remove Ory plugin from config
10
+ * npx ory-opencode-setup --plugin-spec <s> # Override the plugin spec written
11
+ * # to opencode.json (defaults to
12
+ * # this version, pinned)
10
13
  */
11
14
  export {};
package/dist/cli/setup.js CHANGED
@@ -8,6 +8,9 @@
8
8
  * npx ory-opencode-setup --project-dir /path/to/repo
9
9
  * npx ory-opencode-setup --print # Print config to stdout without writing
10
10
  * npx ory-opencode-setup --uninstall # Remove Ory plugin from config
11
+ * npx ory-opencode-setup --plugin-spec <s> # Override the plugin spec written
12
+ * # to opencode.json (defaults to
13
+ * # this version, pinned)
11
14
  */
12
15
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
16
  if (k2 === undefined) k2 = k;
@@ -47,66 +50,21 @@ const fs = __importStar(require("node:fs"));
47
50
  const path = __importStar(require("node:path"));
48
51
  const argus_1 = require("@ory/argus");
49
52
  const assets_js_1 = require("./assets.js");
50
- const PLUGIN_MODULE = "@ory/opencode";
51
- const MCP_SERVER_NAME = "ory";
52
- const MCP_SERVER_COMMAND = [
53
- "npx",
54
- "-y",
55
- "--package=@ory/mcp-server",
56
- "ory-mcp-server",
57
- ];
58
- const OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
59
- // OpenCode's config schema uses `mcp` (singular) with a `local` connector
60
- // shape `{ type: "local", command: [...] }`, not the generic `mcpServers`
61
- // helper from core.
62
- function mergeOpencodeMcp(existing) {
63
- const merged = { ...existing };
64
- const mcp = (merged.mcp ?? {});
65
- mcp[MCP_SERVER_NAME] = { type: "local", command: MCP_SERVER_COMMAND };
66
- merged.mcp = mcp;
67
- return merged;
68
- }
69
- function removeOpencodeMcp(existing) {
70
- const merged = { ...existing };
71
- const mcp = (merged.mcp ?? {});
72
- delete mcp[MCP_SERVER_NAME];
73
- if (Object.keys(mcp).length === 0) {
74
- delete merged.mcp;
75
- }
76
- else {
77
- merged.mcp = mcp;
78
- }
79
- return merged;
80
- }
53
+ const config_js_1 = require("./config.js");
54
+ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
81
55
  function getConfigPath(args) {
82
56
  return path.join(args.projectDir, "opencode.json");
83
57
  }
84
- function mergePlugin(existing) {
85
- const merged = { ...existing };
86
- const plugin = (merged.plugin ?? []).filter((p) => (typeof p === "string" ? p : p[0]) !== PLUGIN_MODULE);
87
- plugin.push(PLUGIN_MODULE);
88
- merged.plugin = plugin;
89
- return merged;
90
- }
91
- function removePlugin(existing) {
92
- const merged = { ...existing };
93
- const plugin = (merged.plugin ?? []).filter((p) => (typeof p === "string" ? p : p[0]) !== PLUGIN_MODULE);
94
- if (plugin.length === 0) {
95
- delete merged.plugin;
96
- }
97
- else {
98
- merged.plugin = plugin;
99
- }
100
- return merged;
101
- }
102
58
  function main() {
103
59
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
104
60
  (0, argus_1.printSetupHelp)("ory-opencode-setup", "OpenCode");
105
61
  process.exit(0);
106
62
  }
107
63
  const args = (0, argus_1.parseSetupArgs)();
64
+ const argv = process.argv.slice(2);
108
65
  if (args.print) {
109
- console.log(JSON.stringify({ plugin: [PLUGIN_MODULE] }, null, 2));
66
+ // Nothing is resolved or written on a --print run: report the shape only.
67
+ console.log(JSON.stringify({ plugin: ["file://<resolved runtime>"] }, null, 2));
110
68
  return;
111
69
  }
112
70
  const configPath = getConfigPath(args);
@@ -117,8 +75,8 @@ function main() {
117
75
  console.log("No opencode.json found. Nothing to uninstall.");
118
76
  return;
119
77
  }
120
- let updated = removePlugin(existing);
121
- updated = removeOpencodeMcp(updated);
78
+ let updated = (0, config_js_1.removePlugin)(existing);
79
+ updated = (0, config_js_1.removeMcp)(updated);
122
80
  const remainingKeys = Object.keys(updated).filter((k) => k !== "$schema");
123
81
  if (remainingKeys.length === 0) {
124
82
  fs.unlinkSync(configPath);
@@ -130,18 +88,40 @@ function main() {
130
88
  }
131
89
  (0, assets_js_1.uninstallOpencodeOryAssets)(args.projectDir);
132
90
  console.log(`Removed Ory skills and commands from ${path.join(args.projectDir, ".opencode")}`);
91
+ (0, argus_1.removeRuntimeWiring)("opencode");
92
+ for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
93
+ console.log(` Removed runtime: ${pruned}`);
94
+ }
133
95
  return;
134
96
  }
135
- let merged = mergePlugin(existing);
136
- merged = mergeOpencodeMcp(merged);
97
+ // Resolve the runtime once, here, and point the plugin spec at it.
98
+ const runtime = (0, argus_1.wireRuntime)({
99
+ harness: "opencode",
100
+ packageName: "@ory/opencode",
101
+ packageRoot: PACKAGE_ROOT,
102
+ installCommand: "npx -y -p @ory/opencode ory-opencode install",
103
+ args: argv,
104
+ entryRelPath: "dist/index.js",
105
+ inProcess: true,
106
+ });
107
+ const spec = (0, config_js_1.runtimePluginSpec)((0, argus_1.moduleSpecifier)(runtime.target));
108
+ let merged = (0, config_js_1.mergePlugin)(existing, spec);
109
+ if (runtime.mcpServer) {
110
+ merged = (0, config_js_1.mergeMcp)(merged, [
111
+ runtime.mcpServer.command,
112
+ ...runtime.mcpServer.args,
113
+ ]);
114
+ }
137
115
  // Add $schema for editor autocomplete on fresh configs only.
138
116
  if (!fileExists && !merged.$schema) {
139
- merged = { $schema: OPENCODE_SCHEMA_URL, ...merged };
117
+ merged = { $schema: config_js_1.OPENCODE_SCHEMA_URL, ...merged };
140
118
  }
141
119
  (0, argus_1.writeJsonFile)(configPath, merged);
142
120
  (0, assets_js_1.installOpencodeOryAssets)(args.projectDir);
143
121
  console.log(`Ory plugin and MCP server configured in ${configPath}`);
122
+ console.log(` Plugin: ${spec}`);
144
123
  console.log(`Ory skills and commands installed to ${path.join(args.projectDir, ".opencode")}`);
124
+ console.log(`Verify what OpenCode will load: npx -y -p @ory/opencode ory-opencode status`);
145
125
  (0, argus_1.printNextSteps)("OpenCode", "npx -y -p @ory/opencode ory-opencode-setup --uninstall", {
146
126
  binName: "ory-opencode",
147
127
  harness: "opencode",
@@ -152,7 +132,10 @@ main();
152
132
  // stored Ory credentials. When required by the plugin's main CLI, that
153
133
  // command owns the purge, so the `require.main` guard prevents a double run.
154
134
  if (require.main === module && process.argv.includes("--uninstall")) {
155
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
135
+ (0, argus_1.clearCredentialsForUninstall)({
136
+ harness: "opencode",
137
+ purge: process.argv.includes("--purge"),
138
+ }).then(() => process.exit(0), (err) => {
156
139
  console.error(err.message ?? err);
157
140
  process.exit(1);
158
141
  });
package/dist/plugin.d.ts CHANGED
@@ -1,24 +1,9 @@
1
- import { OryAgentClient, ensureUserAuthenticated, ensureAgentIdentity } from "@ory/argus";
1
+ import { OryAgentClient, ensureAgentIdentity, ensureUserAuthenticated } from "@ory/argus";
2
2
  import type { PluginModule } from "./types.js";
3
3
  export interface CreateOryPluginDeps {
4
- /** Test injection point for the user login flow. */
5
4
  userLogin?: typeof ensureUserAuthenticated;
6
- /** Test injection point for the agent identity gate. */
7
5
  agentGate?: typeof ensureAgentIdentity;
8
6
  }
9
- /**
10
- * Create the Ory plugin module for OpenCode.
11
- *
12
- * OpenCode plugins export { server: Plugin } where Plugin receives
13
- * context and returns a Hooks object. Hook functions use a mutating
14
- * (input, output) => Promise<void> pattern.
15
- *
16
- * The user login runs on every session at startup — it emits the audit
17
- * span, refreshes tokens, and may launch a browser flow when interactive.
18
- * It never blocks the session; enforcement is governed solely by
19
- * permissionMode at tool-call time.
20
- */
21
7
  export declare function createOryPlugin(clientOrConfig?: OryAgentClient | {
22
8
  projectUrl: string;
23
- apiKey?: string;
24
9
  }, deps?: CreateOryPluginDeps): PluginModule;