@ory/argus 0.13.9 → 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.
Files changed (133) hide show
  1. package/README.md +31 -45
  2. package/assets/commands/temporal-up.md +1 -1
  3. package/assets/skills/auth-setup/SKILL.md +1 -1
  4. package/assets/skills/local-dev/SKILL.md +17 -7
  5. package/assets/skills/ory-build-agent/SKILL.md +43 -97
  6. package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
  7. package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
  8. package/assets/skills/permissions-onboarding/SKILL.md +131 -104
  9. package/dist/adapters.d.ts +93 -30
  10. package/dist/adapters.js +464 -136
  11. package/dist/agent-auth.d.ts +258 -68
  12. package/dist/agent-auth.js +998 -202
  13. package/dist/auth-store.d.ts +37 -2
  14. package/dist/auth-store.js +37 -3
  15. package/dist/auth.d.ts +40 -4
  16. package/dist/auth.js +247 -19
  17. package/dist/bash-parser.d.ts +98 -0
  18. package/dist/bash-parser.js +396 -0
  19. package/dist/branding.d.ts +128 -0
  20. package/dist/branding.js +151 -0
  21. package/dist/build-info.json +4 -4
  22. package/dist/cli-invocation.d.ts +1 -1
  23. package/dist/cli-invocation.js +2 -1
  24. package/dist/cli.d.ts +20 -29
  25. package/dist/cli.js +271 -278
  26. package/dist/client.d.ts +175 -138
  27. package/dist/client.js +672 -391
  28. package/dist/config.d.ts +249 -57
  29. package/dist/config.js +486 -62
  30. package/dist/context.d.ts +10 -0
  31. package/dist/context.js +21 -0
  32. package/dist/contract-suite.d.ts +8 -8
  33. package/dist/contract-suite.js +88 -69
  34. package/dist/denial.d.ts +36 -3
  35. package/dist/denial.js +79 -10
  36. package/dist/event-reporter.d.ts +77 -0
  37. package/dist/event-reporter.js +776 -0
  38. package/dist/external-registrations-main.d.ts +10 -0
  39. package/dist/external-registrations-main.js +38 -0
  40. package/dist/external-registrations.d.ts +79 -0
  41. package/dist/external-registrations.js +188 -0
  42. package/dist/help-cli.d.ts +39 -0
  43. package/dist/help-cli.js +55 -0
  44. package/dist/hook-timeout.d.ts +64 -0
  45. package/dist/hook-timeout.js +88 -0
  46. package/dist/index.d.ts +31 -19
  47. package/dist/index.js +182 -31
  48. package/dist/lifecycle.d.ts +3 -3
  49. package/dist/lifecycle.js +38 -6
  50. package/dist/local/cli.js +11 -6
  51. package/dist/local/configs.d.ts +74 -18
  52. package/dist/local/configs.js +291 -84
  53. package/dist/local/health.d.ts +14 -0
  54. package/dist/local/health.js +50 -4
  55. package/dist/local/index.d.ts +2 -2
  56. package/dist/local/index.js +24 -10
  57. package/dist/local/manager.d.ts +20 -1
  58. package/dist/local/manager.js +160 -39
  59. package/dist/local/ports.d.ts +158 -0
  60. package/dist/local/ports.js +443 -0
  61. package/dist/local/seed.d.ts +22 -25
  62. package/dist/local/seed.js +88 -56
  63. package/dist/logger.d.ts +54 -25
  64. package/dist/logger.js +329 -63
  65. package/dist/mcp.d.ts +2 -2
  66. package/dist/mcp.js +10 -5
  67. package/dist/mirror-bootstrap.d.ts +48 -0
  68. package/dist/mirror-bootstrap.js +254 -0
  69. package/dist/opl.d.ts +289 -0
  70. package/dist/opl.js +446 -0
  71. package/dist/permission-mode.d.ts +87 -0
  72. package/dist/permission-mode.js +307 -0
  73. package/dist/permissions-cli.d.ts +13 -49
  74. package/dist/permissions-cli.js +154 -348
  75. package/dist/permissions.d.ts +148 -38
  76. package/dist/permissions.js +591 -45
  77. package/dist/post-install.d.ts +33 -0
  78. package/dist/post-install.js +127 -0
  79. package/dist/read-credential.d.ts +65 -0
  80. package/dist/read-credential.js +86 -0
  81. package/dist/registry/cli.js +5 -2
  82. package/dist/registry/config.d.ts +0 -17
  83. package/dist/registry/config.js +0 -23
  84. package/dist/registry/index.d.ts +1 -1
  85. package/dist/registry/index.js +2 -2
  86. package/dist/registry/manager.d.ts +4 -21
  87. package/dist/registry/manager.js +83 -55
  88. package/dist/runtime-credential.d.ts +140 -0
  89. package/dist/runtime-credential.js +572 -0
  90. package/dist/runtime.d.ts +408 -0
  91. package/dist/runtime.js +748 -0
  92. package/dist/setup.d.ts +23 -28
  93. package/dist/setup.js +57 -84
  94. package/dist/status-cli.d.ts +29 -13
  95. package/dist/status-cli.js +124 -144
  96. package/dist/status-data.d.ts +195 -0
  97. package/dist/status-data.js +333 -0
  98. package/dist/status-system.d.ts +24 -0
  99. package/dist/status-system.js +56 -0
  100. package/dist/subject.d.ts +126 -20
  101. package/dist/subject.js +215 -30
  102. package/dist/testing.d.ts +74 -38
  103. package/dist/testing.js +185 -68
  104. package/dist/tool-catalog.d.ts +53 -11
  105. package/dist/tool-catalog.js +164 -13
  106. package/dist/tool-metadata.d.ts +7 -6
  107. package/dist/tool-metadata.js +6 -5
  108. package/dist/types.d.ts +11 -1
  109. package/dist/uninstall.d.ts +74 -19
  110. package/dist/uninstall.js +224 -49
  111. package/dist/user-login.d.ts +22 -16
  112. package/dist/user-login.js +67 -96
  113. package/dist/watch-cli.d.ts +6 -0
  114. package/dist/watch-cli.js +217 -0
  115. package/package.json +3 -11
  116. package/dist/dev.d.ts +0 -103
  117. package/dist/dev.js +0 -584
  118. package/dist/interactive-setup.d.ts +0 -165
  119. package/dist/interactive-setup.js +0 -1546
  120. package/dist/local/jaeger-main.d.ts +0 -13
  121. package/dist/local/jaeger-main.js +0 -85
  122. package/dist/local/jaeger.d.ts +0 -50
  123. package/dist/local/jaeger.js +0 -162
  124. package/dist/otel/exporter.d.ts +0 -17
  125. package/dist/otel/exporter.js +0 -12
  126. package/dist/otel/index.d.ts +0 -2
  127. package/dist/otel/index.js +0 -8
  128. package/dist/otel/otlp.d.ts +0 -103
  129. package/dist/otel/otlp.js +0 -385
  130. package/dist/tracer.d.ts +0 -190
  131. package/dist/tracer.js +0 -481
  132. package/dist/watch-sandbox.d.ts +0 -9
  133. package/dist/watch-sandbox.js +0 -81
@@ -0,0 +1,748 @@
1
+ "use strict";
2
+ /**
3
+ * The plugin **runtime store**: resolve which code a harness runs *once*, at
4
+ * install time, instead of re-resolving an npm package spec on every hook
5
+ * invocation.
6
+ *
7
+ * Historically every harness plugin wrote its runtime hook command as a
8
+ * version-pinned npx spec (`npx -y -p @ory/<pkg>@<ver> ory-<h>-hook`). That
9
+ * froze the *resolution strategy* into each harness's config file: which code
10
+ * runs was decided by npm on every `PreToolUse`, forever — one `npm exec`
11
+ * resolution per tool call, dependent on network and npm cache state, and
12
+ * untestable locally without publishing to a registry first.
13
+ *
14
+ * This module replaces that with three artifacts:
15
+ *
16
+ * 1. **A runtime store** at `<dataDir>/runtime/<version>/`, an npm prefix
17
+ * holding the plugin package and its dependency closure. Populated by a
18
+ * single `npm install --prefix` at install time — the only package
19
+ * resolution that ever happens. Several harnesses installed at the same
20
+ * version share one store directory.
21
+ * 2. **A stable entry shim** per harness at
22
+ * `<dataDir>/bin/ory-hook-<harness>.js` (and one for the MCP server).
23
+ * Harness configs reference the *shim*, whose path never changes across
24
+ * upgrades — so changing the runtime, or the resolution policy itself,
25
+ * rewrites one small file instead of N harness config formats.
26
+ * 3. **A manifest** at `<dataDir>/runtime/manifest.json` recording what is
27
+ * wired where, so `status` can report the runtime a harness will actually
28
+ * load, upgrades can rewire, and unreferenced stores can be pruned.
29
+ *
30
+ * A **linked** runtime (`linkedRuntime`) points the shim straight at a
31
+ * workspace build instead of a materialized store. That is the development
32
+ * loop: `pnpm build` + relaunch, with no publish, no version bump, and no
33
+ * registry involved.
34
+ *
35
+ * ## The shim must fail open
36
+ *
37
+ * The shim is not an indirection for its own sake: it is the only place that
38
+ * can degrade gracefully. If the runtime store is missing — a wiped config
39
+ * dir, a half-finished upgrade, dotfiles synced to a new machine — a raw
40
+ * `node "<store>/…/hook.js"` command would exit non-zero, and a harness that
41
+ * treats an unanswered gate as a block (Cursor registers `preToolUse` with
42
+ * `failClosed: true`) would then block *every* tool call. So the generated
43
+ * hook shim catches an unloadable runtime, explains itself on stderr, and
44
+ * exits 0 with the harness's pass-through response. The MCP shim does the
45
+ * opposite and exits non-zero: an MCP server that cannot start gates nothing,
46
+ * and a silent no-op server is harder to diagnose than a loud failure.
47
+ */
48
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
49
+ if (k2 === undefined) k2 = k;
50
+ var desc = Object.getOwnPropertyDescriptor(m, k);
51
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
52
+ desc = { enumerable: true, get: function() { return m[k]; } };
53
+ }
54
+ Object.defineProperty(o, k2, desc);
55
+ }) : (function(o, m, k, k2) {
56
+ if (k2 === undefined) k2 = k;
57
+ o[k2] = m[k];
58
+ }));
59
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
60
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
61
+ }) : function(o, v) {
62
+ o["default"] = v;
63
+ });
64
+ var __importStar = (this && this.__importStar) || (function () {
65
+ var ownKeys = function(o) {
66
+ ownKeys = Object.getOwnPropertyNames || function (o) {
67
+ var ar = [];
68
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
69
+ return ar;
70
+ };
71
+ return ownKeys(o);
72
+ };
73
+ return function (mod) {
74
+ if (mod && mod.__esModule) return mod;
75
+ var result = {};
76
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
77
+ __setModuleDefault(result, mod);
78
+ return result;
79
+ };
80
+ })();
81
+ Object.defineProperty(exports, "__esModule", { value: true });
82
+ exports.defaultNpmInstaller = exports.MCP_SERVER_PACKAGE = void 0;
83
+ exports.getRuntimeRoot = getRuntimeRoot;
84
+ exports.getRuntimeStoreDir = getRuntimeStoreDir;
85
+ exports.getShimDir = getShimDir;
86
+ exports.getHookShimPath = getHookShimPath;
87
+ exports.getMcpShimPath = getMcpShimPath;
88
+ exports.sanitizeNpmEnv = sanitizeNpmEnv;
89
+ exports.materializeRuntime = materializeRuntime;
90
+ exports.linkedRuntime = linkedRuntime;
91
+ exports.resolveRuntimeEntry = resolveRuntimeEntry;
92
+ exports.resolveMcpRuntimeEntry = resolveMcpRuntimeEntry;
93
+ exports.hookShimBody = hookShimBody;
94
+ exports.writeHookShim = writeHookShim;
95
+ exports.writeMcpShim = writeMcpShim;
96
+ exports.hookCommand = hookCommand;
97
+ exports.isOryRuntimeCommand = isOryRuntimeCommand;
98
+ exports.mcpCommand = mcpCommand;
99
+ exports.moduleSpecifier = moduleSpecifier;
100
+ exports.readRuntimeManifest = readRuntimeManifest;
101
+ exports.recordRuntimeWiring = recordRuntimeWiring;
102
+ exports.getRuntimeWiring = getRuntimeWiring;
103
+ exports.removeRuntimeWiring = removeRuntimeWiring;
104
+ exports.pruneRuntimeStores = pruneRuntimeStores;
105
+ exports.resolveRuntimeForInstall = resolveRuntimeForInstall;
106
+ exports.requireHookCommand = requireHookCommand;
107
+ exports.wireRuntime = wireRuntime;
108
+ exports.checkRuntimeHealth = checkRuntimeHealth;
109
+ exports.describeRuntimeHealth = describeRuntimeHealth;
110
+ const fs = __importStar(require("node:fs"));
111
+ const path = __importStar(require("node:path"));
112
+ const node_child_process_1 = require("node:child_process");
113
+ const config_js_1 = require("./config.js");
114
+ // ─── Paths ─────────────────────────────────────────────────────────
115
+ /** Root of the runtime store: `<dataDir>/runtime`. */
116
+ function getRuntimeRoot() {
117
+ return path.join((0, config_js_1.getDataDir)(), "runtime");
118
+ }
119
+ /** Store directory for one version: `<dataDir>/runtime/<version>`. */
120
+ function getRuntimeStoreDir(version) {
121
+ return path.join(getRuntimeRoot(), version);
122
+ }
123
+ /** Directory holding the generated entry shims: `<dataDir>/bin`. */
124
+ function getShimDir() {
125
+ return path.join((0, config_js_1.getDataDir)(), "bin");
126
+ }
127
+ /** Stable hook-shim path for a harness. Never changes across upgrades. */
128
+ function getHookShimPath(harness) {
129
+ return path.join(getShimDir(), `ory-hook-${harness}.js`);
130
+ }
131
+ /** Stable MCP-server shim path. Shared by every harness. */
132
+ function getMcpShimPath() {
133
+ return path.join(getShimDir(), "ory-mcp-server.js");
134
+ }
135
+ function getManifestPath() {
136
+ return path.join(getRuntimeRoot(), "manifest.json");
137
+ }
138
+ // ─── Runtime targets ───────────────────────────────────────────────
139
+ /** The npm package providing the MCP server, materialized alongside plugins. */
140
+ exports.MCP_SERVER_PACKAGE = "@ory/mcp-server";
141
+ /** Read a package version from a package directory, if present. */
142
+ function readPackageVersion(packageDir) {
143
+ try {
144
+ const parsed = JSON.parse(fs.readFileSync(path.join(packageDir, "package.json"), "utf-8"));
145
+ return typeof parsed.version === "string" ? parsed.version : undefined;
146
+ }
147
+ catch {
148
+ return undefined;
149
+ }
150
+ }
151
+ /**
152
+ * Default installer: one `npm install --prefix <store> <spec>…`.
153
+ *
154
+ * `--prefix` keeps the install entirely inside the store directory (npm
155
+ * creates `<store>/node_modules` and, if absent, a minimal `package.json`),
156
+ * and `--no-save` keeps it from rewriting a manifest we do not own. The
157
+ * user's own npm configuration — registry, auth, proxy — applies, which is
158
+ * the point: this is the *one* resolution, done with the user's settings, at
159
+ * a moment where a failure is visible and actionable.
160
+ */
161
+ const defaultNpmInstaller = ({ prefix, specs }) => {
162
+ const result = (0, node_child_process_1.spawnSync)("npm", [
163
+ "install",
164
+ "--prefix",
165
+ prefix,
166
+ ...specs,
167
+ "--no-audit",
168
+ "--no-fund",
169
+ "--no-save",
170
+ "--loglevel=error",
171
+ ], {
172
+ encoding: "utf-8",
173
+ stdio: ["ignore", "pipe", "pipe"],
174
+ env: sanitizeNpmEnv(process.env),
175
+ });
176
+ const output = ((result.stdout ?? "") + (result.stderr ?? "")).trim();
177
+ return { ok: !result.error && result.status === 0, output };
178
+ };
179
+ exports.defaultNpmInstaller = defaultNpmInstaller;
180
+ /**
181
+ * When this install runs from inside an `npx`/`npm exec` wrapper (the
182
+ * documented `npx -p @ory/<harness> ory-<harness> install` entry point), the
183
+ * outer npm resolves the user's config and *projects it into the environment*
184
+ * as `npm_config_*` vars for child processes. Our inner `npm install <spec>…`
185
+ * inherits those — which is how the user's registry/proxy/`min-release-age`
186
+ * reach the store install, so we keep them.
187
+ *
188
+ * The one exception is `allow-scripts`: npm ≥ 11.6 rejects an env/CLI-level
189
+ * `allow-scripts` in a project-scoped install (`EALLOWSCRIPTS`), because that
190
+ * source is meant for one-off/global contexts. A user with
191
+ * `allow-scripts=…` in `~/.npmrc` (an increasingly common supply-chain
192
+ * hardening) would otherwise be unable to install the runtime at all. Dropping
193
+ * only the projected env var lets the child re-read the same setting from the
194
+ * `.npmrc` cascade (user/global level, which *is* permitted here), so packages
195
+ * that legitimately need a build step — e.g. `tree-sitter-bash` for shell
196
+ * decomposition — still get it.
197
+ */
198
+ function sanitizeNpmEnv(env) {
199
+ const cleaned = {};
200
+ for (const [key, value] of Object.entries(env)) {
201
+ if (/^npm_config_allow[-_]scripts$/i.test(key)) {
202
+ continue;
203
+ }
204
+ cleaned[key] = value;
205
+ }
206
+ return cleaned;
207
+ }
208
+ /**
209
+ * Materialize a runtime into the store and return it.
210
+ *
211
+ * The MCP server is installed into the same prefix by default: it is fetched
212
+ * by the harness at runtime in exactly the same way the hook was, so it needs
213
+ * the same treatment for the registry to drop out of the runtime path
214
+ * entirely.
215
+ *
216
+ * Throws when the install fails or the expected package directory is absent
217
+ * afterwards — an install that cannot materialize its runtime must say so
218
+ * rather than write a shim pointing at nothing.
219
+ */
220
+ function materializeRuntime(opts) {
221
+ const { packageName, version } = opts;
222
+ const storeDir = getRuntimeStoreDir(version);
223
+ fs.mkdirSync(storeDir, { recursive: true });
224
+ const extras = opts.extraPackages ?? [`${exports.MCP_SERVER_PACKAGE}@${version}`];
225
+ const specs = [`${packageName}@${version}`, ...extras];
226
+ const installer = opts.installer ?? exports.defaultNpmInstaller;
227
+ const result = installer({ prefix: storeDir, specs });
228
+ if (!result.ok) {
229
+ throw new Error(`Failed to materialize the Ory runtime into ${storeDir}.\n` +
230
+ ` npm install ${specs.join(" ")}\n` +
231
+ (result.output ? ` ${result.output}\n` : ""));
232
+ }
233
+ const packageDir = path.join(storeDir, "node_modules", packageName);
234
+ const resolvedVersion = readPackageVersion(packageDir);
235
+ if (!resolvedVersion) {
236
+ throw new Error(`The Ory runtime install reported success but ${packageDir} is missing or ` +
237
+ `has no readable package.json.`);
238
+ }
239
+ return {
240
+ kind: "materialized",
241
+ packageName,
242
+ packageDir,
243
+ version: resolvedVersion,
244
+ storeDir,
245
+ };
246
+ }
247
+ /**
248
+ * Use an already-built package directory as the runtime — the development
249
+ * path (`install --link <dir>`), where the workspace build *is* the artifact.
250
+ *
251
+ * Validates that the directory looks like a built package so a typo surfaces
252
+ * at install time instead of as hooks that silently never run.
253
+ */
254
+ function linkedRuntime(opts) {
255
+ const packageDir = path.resolve(opts.packageDir);
256
+ const version = readPackageVersion(packageDir);
257
+ if (!version) {
258
+ throw new Error(`Cannot link the Ory runtime: ${packageDir} has no readable package.json.`);
259
+ }
260
+ if (!fs.existsSync(path.join(packageDir, "dist"))) {
261
+ throw new Error(`Cannot link the Ory runtime: ${packageDir}/dist is missing. Build the ` +
262
+ `package first (pnpm build).`);
263
+ }
264
+ return { kind: "linked", packageName: opts.packageName, packageDir, version };
265
+ }
266
+ /**
267
+ * Absolute path to an entry point inside a runtime, e.g. the hook script.
268
+ * Throws when the file is absent — the shim is written from this, so a bad
269
+ * relative path must fail at install time, not at the first tool call.
270
+ */
271
+ function resolveRuntimeEntry(target, relativePath) {
272
+ const entry = path.join(target.packageDir, relativePath);
273
+ if (!fs.existsSync(entry)) {
274
+ throw new Error(`Ory runtime entry not found: ${entry} (package ${target.packageName}@${target.version}).`);
275
+ }
276
+ return entry;
277
+ }
278
+ /**
279
+ * The MCP server inside a runtime, or `undefined` when it isn't there.
280
+ *
281
+ * A materialized store installs it as a sibling; a linked workspace build has
282
+ * it two levels up (`packages/mcp-server`). Optional by design: a plugin whose
283
+ * MCP registration is missing should degrade to no MCP server, not fail the
284
+ * whole install.
285
+ */
286
+ function resolveMcpRuntimeEntry(target) {
287
+ const candidates = target.kind === "materialized"
288
+ ? [path.join(target.storeDir, "node_modules", exports.MCP_SERVER_PACKAGE)]
289
+ : [
290
+ // pnpm workspace: the plugin's own node_modules links siblings.
291
+ path.join(target.packageDir, "node_modules", exports.MCP_SERVER_PACKAGE),
292
+ // …and the packages/ directory holds the source checkout.
293
+ path.join(target.packageDir, "..", "mcp-server"),
294
+ ];
295
+ for (const dir of candidates) {
296
+ const entry = path.join(dir, "dist", "index.js");
297
+ if (fs.existsSync(entry))
298
+ return entry;
299
+ }
300
+ return undefined;
301
+ }
302
+ // ─── Shims ─────────────────────────────────────────────────────────
303
+ /**
304
+ * Body of a generated hook shim.
305
+ *
306
+ * Deliberately tiny and dependency-free: it must be loadable even when the
307
+ * runtime it points at is not. `require` (not `import`) so the shim works
308
+ * regardless of how the harness invokes it, and the pass-through response is
309
+ * written *before* exiting 0 so a fail-closed gate still sees a valid answer.
310
+ */
311
+ function hookShimBody(opts) {
312
+ const { entry, packageName, version, installCommand, fallbackStdout } = opts;
313
+ return `#!/usr/bin/env node
314
+ // Ory hook entry shim — generated by ${packageName}@${version}. Do not edit.
315
+ //
316
+ // Resolves the Ory plugin runtime once, at install time: this file is the
317
+ // stable path harness configs reference, and it is rewritten (in place) when
318
+ // the runtime changes. No package resolution happens on the tool-call path.
319
+ //
320
+ // Regenerate with: ${installCommand}
321
+ "use strict";
322
+
323
+ const ENTRY = ${JSON.stringify(entry)};
324
+
325
+ try {
326
+ const mod = require(ENTRY);
327
+ // Most hook entries run their pipeline as a load-time side effect, so the
328
+ // require() above is the whole invocation. An entry that must stay
329
+ // side-effect-free on import — Amp's delegate, whose tests import it to call
330
+ // decideToolPermission directly — exports \`main\` instead and is run here.
331
+ //
332
+ // Without this, such an entry silently does nothing under the shim:
333
+ // \`require.main === module\` is false because require.main is THIS file, so its
334
+ // own guard never fires, the process exits 0, and a harness reading exit 0 as
335
+ // "allow" lets every tool through unchecked (#217).
336
+ if (mod && typeof mod.main === "function") {
337
+ Promise.resolve(mod.main()).catch(function (err) {
338
+ process.stderr.write(
339
+ "[ory-agent] hook entry failed: " + ((err && err.message) || String(err)) + "\\n"
340
+ );
341
+ ${fallbackStdout ? ` process.stdout.write(${JSON.stringify(fallbackStdout)});\n` : ""} process.exit(0);
342
+ });
343
+ }
344
+ } catch (err) {
345
+ // Fail OPEN. A harness that treats an unanswered permission gate as a block
346
+ // would otherwise block every tool call because our runtime went missing.
347
+ process.stderr.write(
348
+ "[ory-agent] plugin runtime is missing or unloadable at " +
349
+ ENTRY +
350
+ "\\n[ory-agent] tools are NOT being checked. Reinstall with: ${installCommand}\\n" +
351
+ "[ory-agent] cause: " +
352
+ ((err && err.message) || String(err)) +
353
+ "\\n"
354
+ );
355
+ ${fallbackStdout ? ` process.stdout.write(${JSON.stringify(fallbackStdout)});\n` : ""} process.exit(0);
356
+ }
357
+ `;
358
+ }
359
+ /**
360
+ * Body of the MCP-server shim. Fails **loudly** (exit 1): an MCP server that
361
+ * cannot start blocks nothing, so a clear error beats a silent no-op server.
362
+ */
363
+ function mcpShimBody(opts) {
364
+ const { entry, packageName, version, installCommand } = opts;
365
+ return `#!/usr/bin/env node
366
+ // Ory MCP server entry shim — generated by ${packageName}@${version}. Do not edit.
367
+ // Regenerate with: ${installCommand}
368
+ "use strict";
369
+
370
+ const ENTRY = ${JSON.stringify(entry)};
371
+
372
+ try {
373
+ require(ENTRY);
374
+ } catch (err) {
375
+ process.stderr.write(
376
+ "[ory-agent] MCP server runtime is missing or unloadable at " +
377
+ ENTRY +
378
+ "\\n[ory-agent] reinstall with: ${installCommand}\\n" +
379
+ "[ory-agent] cause: " +
380
+ ((err && err.message) || String(err)) +
381
+ "\\n"
382
+ );
383
+ process.exit(1);
384
+ }
385
+ `;
386
+ }
387
+ function writeExecutable(file, body) {
388
+ fs.mkdirSync(path.dirname(file), { recursive: true });
389
+ fs.writeFileSync(file, body, { mode: 0o755 });
390
+ // writeFileSync honors `mode` only when creating the file, so re-assert it
391
+ // for an overwrite of an existing shim.
392
+ try {
393
+ fs.chmodSync(file, 0o755);
394
+ }
395
+ catch {
396
+ /* best-effort — a non-executable shim still runs via `node <path>` */
397
+ }
398
+ }
399
+ /**
400
+ * Write (or rewrite) the stable hook shim for a harness and record the wiring
401
+ * in the manifest. Returns the shim path — what the harness config should
402
+ * reference, via {@link hookCommand}.
403
+ *
404
+ * `fallbackStdout` is the harness's pass-through response for the
405
+ * runtime-missing case. Most subprocess harnesses treat empty stdout plus
406
+ * exit 0 as "no opinion, proceed", which is the default; pass a JSON string
407
+ * for a harness that needs an explicit allow payload.
408
+ */
409
+ function writeHookShim(opts) {
410
+ const entry = resolveRuntimeEntry(opts.target, opts.entryRelPath ?? path.join("dist", "hook.js"));
411
+ const shimPath = getHookShimPath(opts.harness);
412
+ writeExecutable(shimPath, hookShimBody({
413
+ entry,
414
+ packageName: opts.target.packageName,
415
+ version: opts.target.version,
416
+ installCommand: opts.installCommand,
417
+ fallbackStdout: opts.fallbackStdout ?? "",
418
+ }));
419
+ recordRuntimeWiring({
420
+ harness: opts.harness,
421
+ target: opts.target,
422
+ hookShim: shimPath,
423
+ hookEntry: entry,
424
+ mcpEntry: opts.mcpEntry,
425
+ });
426
+ return shimPath;
427
+ }
428
+ /**
429
+ * Write (or rewrite) the shared MCP-server shim. Returns the shim path, or
430
+ * `undefined` when this runtime has no MCP server to point at — callers then
431
+ * skip the MCP registration rather than writing a broken one.
432
+ */
433
+ function writeMcpShim(opts) {
434
+ const entry = resolveMcpRuntimeEntry(opts.target);
435
+ if (!entry)
436
+ return undefined;
437
+ const shimPath = getMcpShimPath();
438
+ writeExecutable(shimPath, mcpShimBody({
439
+ entry,
440
+ packageName: opts.target.packageName,
441
+ version: opts.target.version,
442
+ installCommand: opts.installCommand,
443
+ }));
444
+ return shimPath;
445
+ }
446
+ // ─── Rendering into harness config ─────────────────────────────────
447
+ /**
448
+ * The hook command a harness config should carry: `node "<shim>"`.
449
+ *
450
+ * Quoted because a data dir can contain spaces, and several harnesses run the
451
+ * command through a shell.
452
+ */
453
+ function hookCommand(shimPath) {
454
+ return `node "${shimPath}"`;
455
+ }
456
+ /**
457
+ * True when a command in a harness config is one of ours.
458
+ *
459
+ * Matches the runtime shims this module writes **and** the version-pinned
460
+ * `npx -y -p @ory/<pkg> …` commands earlier releases wrote. The legacy pattern
461
+ * has to stay recognized even though nothing emits it any more: uninstall and
462
+ * the non-destructive install merges use this predicate, and a legacy entry
463
+ * left behind would keep invoking the last published release alongside the new
464
+ * shim.
465
+ */
466
+ function isOryRuntimeCommand(command) {
467
+ if (!command)
468
+ return false;
469
+ return (/ory-hook-[a-z0-9-]+\.(js|cjs|mjs)/.test(command) ||
470
+ /ory-mcp-server\.(js|cjs|mjs)/.test(command) ||
471
+ // Legacy npx form: `npx … -p @ory/<pkg>[@ver] ory-<h>-hook`.
472
+ /@ory\/[a-z0-9-]+/.test(command) ||
473
+ /\bory-[a-z0-9-]+-hook\b/.test(command));
474
+ }
475
+ /** The MCP server registration a harness config should carry. */
476
+ function mcpCommand(shimPath) {
477
+ return { command: "node", args: [shimPath] };
478
+ }
479
+ /**
480
+ * Module specifier for an *in-process* plugin: the absolute package directory.
481
+ *
482
+ * In-process harnesses (OpenCode, OpenClaw, Amp, Pi) load the plugin as a
483
+ * module rather than spawning a hook, so they reference the runtime directly
484
+ * instead of through a shim — a loader file re-exporting this path, or a
485
+ * `file://` plugin spec built from it.
486
+ */
487
+ function moduleSpecifier(target) {
488
+ return target.packageDir;
489
+ }
490
+ const EMPTY_MANIFEST = { version: 1, harnesses: {} };
491
+ /** Read the manifest. Returns an empty manifest when absent or unreadable. */
492
+ function readRuntimeManifest() {
493
+ try {
494
+ const parsed = JSON.parse(fs.readFileSync(getManifestPath(), "utf-8"));
495
+ const harnesses = parsed.harnesses && typeof parsed.harnesses === "object"
496
+ ? parsed.harnesses
497
+ : {};
498
+ return { version: 1, harnesses };
499
+ }
500
+ catch {
501
+ return { ...EMPTY_MANIFEST, harnesses: {} };
502
+ }
503
+ }
504
+ function writeRuntimeManifest(manifest) {
505
+ const file = getManifestPath();
506
+ fs.mkdirSync(path.dirname(file), { recursive: true });
507
+ fs.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n");
508
+ }
509
+ /** Record (or replace) one harness's wiring. Called by {@link writeHookShim}. */
510
+ function recordRuntimeWiring(opts) {
511
+ const wiring = {
512
+ harness: opts.harness,
513
+ packageName: opts.target.packageName,
514
+ version: opts.target.version,
515
+ kind: opts.target.kind,
516
+ packageDir: opts.target.packageDir,
517
+ ...(opts.target.storeDir ? { storeDir: opts.target.storeDir } : {}),
518
+ hookShim: opts.hookShim,
519
+ hookEntry: opts.hookEntry,
520
+ ...(opts.mcpEntry ? { mcpEntry: opts.mcpEntry } : {}),
521
+ wiredAt: new Date().toISOString(),
522
+ };
523
+ const manifest = readRuntimeManifest();
524
+ manifest.harnesses[opts.harness] = wiring;
525
+ writeRuntimeManifest(manifest);
526
+ return wiring;
527
+ }
528
+ /** The wiring for one harness, or `undefined` when it isn't installed. */
529
+ function getRuntimeWiring(harness) {
530
+ return readRuntimeManifest().harnesses[harness];
531
+ }
532
+ /**
533
+ * Forget a harness's wiring and remove its shim. Used by `uninstall`. The
534
+ * store itself is left to {@link pruneRuntimeStores}, which removes it only
535
+ * once no harness references it.
536
+ */
537
+ function removeRuntimeWiring(harness) {
538
+ const manifest = readRuntimeManifest();
539
+ const wiring = manifest.harnesses[harness];
540
+ if (wiring) {
541
+ try {
542
+ fs.rmSync(wiring.hookShim, { force: true });
543
+ }
544
+ catch {
545
+ /* best-effort */
546
+ }
547
+ delete manifest.harnesses[harness];
548
+ writeRuntimeManifest(manifest);
549
+ }
550
+ }
551
+ /**
552
+ * Delete store directories no harness references any more — the GC step of an
553
+ * upgrade. Always called *after* the shims have been rewritten, so a store in
554
+ * use is never removed. Returns the directories deleted.
555
+ */
556
+ function pruneRuntimeStores() {
557
+ const root = getRuntimeRoot();
558
+ let entries;
559
+ try {
560
+ entries = fs.readdirSync(root);
561
+ }
562
+ catch {
563
+ return [];
564
+ }
565
+ const inUse = new Set(Object.values(readRuntimeManifest().harnesses)
566
+ .map((w) => w.storeDir)
567
+ .filter((d) => !!d)
568
+ .map((d) => path.resolve(d)));
569
+ const removed = [];
570
+ for (const name of entries) {
571
+ const dir = path.join(root, name);
572
+ if (name === "manifest.json")
573
+ continue;
574
+ if (!fs.statSync(dir).isDirectory())
575
+ continue;
576
+ if (inUse.has(path.resolve(dir)))
577
+ continue;
578
+ fs.rmSync(dir, { recursive: true, force: true });
579
+ removed.push(dir);
580
+ }
581
+ return removed;
582
+ }
583
+ // ─── The install-side entry point ──────────────────────────────────
584
+ /**
585
+ * Where an `install` should get the runtime from.
586
+ *
587
+ * `--runtime-dir <dir>` → link that directory
588
+ * `ORY_PLUGIN_RUNTIME_DIR` → link that directory
589
+ * otherwise → materialize the CLI's own version
590
+ *
591
+ * The flag and the env var support explicit source installs that point at an
592
+ * already-built workspace package instead of materializing from a registry.
593
+ *
594
+ * The flag deliberately requires an explicit directory. A bare "link whatever
595
+ * package I am running from" would resolve to npx's ephemeral cache dir under
596
+ * the common `npx -p @ory/<pkg>` invocation — a runtime that disappears
597
+ * between sessions — and `--link` is already taken by at least one harness
598
+ * (Gemini CLI symlinks its *extension* with it), so the name has to be
599
+ * unambiguous too.
600
+ */
601
+ function resolveRuntimeForInstall(opts) {
602
+ const linkDir = parseRuntimeDirFlag(opts.args ?? []);
603
+ if (linkDir) {
604
+ return linkedRuntime({ packageName: opts.packageName, packageDir: linkDir });
605
+ }
606
+ const version = readPackageVersion(opts.packageRoot);
607
+ if (!version) {
608
+ throw new Error(`Cannot determine the version to install: ${opts.packageRoot}/package.json ` +
609
+ `is missing or unreadable.`);
610
+ }
611
+ return materializeRuntime({
612
+ packageName: opts.packageName,
613
+ version,
614
+ installer: opts.installer,
615
+ });
616
+ }
617
+ /**
618
+ * Read `--runtime-dir <dir>` off an argv slice, falling back to the
619
+ * `ORY_PLUGIN_RUNTIME_DIR` env var. A flag with no value is an error rather
620
+ * than a silent fallback to materialization — someone passing it means to run
621
+ * against a local build, and quietly installing from npm instead would look
622
+ * like the build simply had no effect.
623
+ */
624
+ function parseRuntimeDirFlag(args) {
625
+ const idx = args.indexOf("--runtime-dir");
626
+ if (idx !== -1) {
627
+ const next = args[idx + 1];
628
+ if (!next || next.startsWith("-")) {
629
+ throw new Error("--runtime-dir requires a directory argument.");
630
+ }
631
+ return path.resolve(next);
632
+ }
633
+ const fromEnv = process.env.ORY_PLUGIN_RUNTIME_DIR?.trim();
634
+ return fromEnv ? path.resolve(fromEnv) : undefined;
635
+ }
636
+ /**
637
+ * The hook command of a subprocess-hook runtime.
638
+ *
639
+ * `PreparedRuntime.hookCommand` is optional because an in-process integration
640
+ * imports a module instead of spawning a hook. A harness that *does* spawn one
641
+ * cannot be wired without it, so its absence is a programming error — better to
642
+ * say so than to write `undefined` into a config file.
643
+ */
644
+ function requireHookCommand(runtime) {
645
+ if (!runtime.hookCommand) {
646
+ throw new Error(`The resolved runtime for ${runtime.target.packageName} has no hook ` +
647
+ `command (wired as in-process?).`);
648
+ }
649
+ return runtime.hookCommand;
650
+ }
651
+ /**
652
+ * One call per plugin install: resolve the runtime, write the shims, record
653
+ * the wiring, and GC stores nothing references any more.
654
+ *
655
+ * Ordering matters — the shims are rewritten *before* pruning, so the store a
656
+ * harness is about to use is never a GC candidate.
657
+ */
658
+ function wireRuntime(opts) {
659
+ const target = resolveRuntimeForInstall({
660
+ packageName: opts.packageName,
661
+ packageRoot: opts.packageRoot,
662
+ args: opts.args,
663
+ installer: opts.installer,
664
+ });
665
+ const mcpEntry = opts.includeMcp === false ? undefined : resolveMcpRuntimeEntry(target);
666
+ let hookShim;
667
+ let hookCmd;
668
+ if (opts.inProcess) {
669
+ hookShim = resolveRuntimeEntry(target, opts.entryRelPath ?? path.join("dist", "index.js"));
670
+ recordRuntimeWiring({
671
+ harness: opts.harness,
672
+ target,
673
+ hookShim,
674
+ hookEntry: hookShim,
675
+ mcpEntry,
676
+ });
677
+ }
678
+ else {
679
+ hookShim = writeHookShim({
680
+ harness: opts.harness,
681
+ target,
682
+ entryRelPath: opts.entryRelPath,
683
+ installCommand: opts.installCommand,
684
+ fallbackStdout: opts.fallbackStdout,
685
+ mcpEntry,
686
+ });
687
+ hookCmd = hookCommand(hookShim);
688
+ }
689
+ const mcpShim = opts.includeMcp === false
690
+ ? undefined
691
+ : writeMcpShim({ target, installCommand: opts.installCommand });
692
+ // Record the harness in the shared config's install registry. Every install
693
+ // funnels through here, so this is the one place that knows a plugin has
694
+ // been added — and `uninstall` reads it to tell whether the shared config
695
+ // still has other consumers before wiping it.
696
+ (0, config_js_1.registerInstalledHarness)(opts.harness);
697
+ return {
698
+ target,
699
+ hookShim,
700
+ ...(hookCmd ? { hookCommand: hookCmd } : {}),
701
+ ...(mcpShim ? { mcpShim, mcpServer: mcpCommand(mcpShim) } : {}),
702
+ prunedStores: pruneRuntimeStores(),
703
+ };
704
+ }
705
+ /**
706
+ * Inspect what a harness will actually load.
707
+ *
708
+ * With the runtime resolved at install time, the wired code and the CLI a user
709
+ * happens to run (`npx @ory/<pkg>@latest status`) can legitimately differ —
710
+ * so `status` reports the runtime version found on disk, not the CLI's own.
711
+ * The unhappy states are each distinguishable because they have different
712
+ * fixes: a missing shim means the harness config points at nothing, a missing
713
+ * runtime means the shim will fail open (tools unchecked), and drift means the
714
+ * store was replaced underneath a stale shim.
715
+ */
716
+ function checkRuntimeHealth(harness) {
717
+ const wiring = getRuntimeWiring(harness);
718
+ if (!wiring)
719
+ return { state: "not_installed" };
720
+ if (!fs.existsSync(wiring.hookShim))
721
+ return { state: "shim_missing", wiring };
722
+ if (!fs.existsSync(wiring.hookEntry)) {
723
+ return { state: "runtime_missing", wiring };
724
+ }
725
+ const onDisk = readPackageVersion(wiring.packageDir);
726
+ if (onDisk && onDisk !== wiring.version) {
727
+ return { state: "version_drift", wiring, runtimeVersion: onDisk };
728
+ }
729
+ return { state: "ok", wiring };
730
+ }
731
+ /** One-line human summary of {@link checkRuntimeHealth}, for `status`. */
732
+ function describeRuntimeHealth(health) {
733
+ switch (health.state) {
734
+ case "not_installed":
735
+ return "not installed (no runtime wired)";
736
+ case "ok":
737
+ return (`${health.wiring.packageName}@${health.wiring.version} ` +
738
+ `(${health.wiring.kind}) → ${health.wiring.hookEntry}`);
739
+ case "shim_missing":
740
+ return `shim missing at ${health.wiring.hookShim} — re-run install`;
741
+ case "runtime_missing":
742
+ return (`runtime missing at ${health.wiring.hookEntry} — hooks are failing ` +
743
+ `open (tools are NOT being checked); re-run install`);
744
+ case "version_drift":
745
+ return (`wired to ${health.wiring.version} but ${health.runtimeVersion} is on ` +
746
+ `disk at ${health.wiring.packageDir} — re-run install`);
747
+ }
748
+ }