@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.
- package/README.md +31 -45
- package/assets/commands/temporal-up.md +1 -1
- package/assets/skills/auth-setup/SKILL.md +1 -1
- package/assets/skills/local-dev/SKILL.md +17 -7
- package/assets/skills/ory-build-agent/SKILL.md +43 -97
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
- package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +93 -30
- package/dist/adapters.js +464 -136
- package/dist/agent-auth.d.ts +258 -68
- package/dist/agent-auth.js +998 -202
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +40 -4
- package/dist/auth.js +247 -19
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +128 -0
- package/dist/branding.js +151 -0
- package/dist/build-info.json +4 -4
- package/dist/cli-invocation.d.ts +1 -1
- package/dist/cli-invocation.js +2 -1
- package/dist/cli.d.ts +20 -29
- package/dist/cli.js +271 -278
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +249 -57
- package/dist/config.js +486 -62
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +8 -8
- package/dist/contract-suite.js +88 -69
- package/dist/denial.d.ts +36 -3
- package/dist/denial.js +79 -10
- package/dist/event-reporter.d.ts +77 -0
- package/dist/event-reporter.js +776 -0
- package/dist/external-registrations-main.d.ts +10 -0
- package/dist/external-registrations-main.js +38 -0
- package/dist/external-registrations.d.ts +79 -0
- package/dist/external-registrations.js +188 -0
- package/dist/help-cli.d.ts +39 -0
- package/dist/help-cli.js +55 -0
- package/dist/hook-timeout.d.ts +64 -0
- package/dist/hook-timeout.js +88 -0
- package/dist/index.d.ts +31 -19
- package/dist/index.js +182 -31
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +38 -6
- package/dist/local/cli.js +11 -6
- package/dist/local/configs.d.ts +74 -18
- package/dist/local/configs.js +291 -84
- package/dist/local/health.d.ts +14 -0
- package/dist/local/health.js +50 -4
- package/dist/local/index.d.ts +2 -2
- package/dist/local/index.js +24 -10
- package/dist/local/manager.d.ts +20 -1
- package/dist/local/manager.js +160 -39
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +22 -25
- package/dist/local/seed.js +88 -56
- package/dist/logger.d.ts +54 -25
- package/dist/logger.js +329 -63
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +10 -5
- package/dist/mirror-bootstrap.d.ts +48 -0
- package/dist/mirror-bootstrap.js +254 -0
- package/dist/opl.d.ts +289 -0
- package/dist/opl.js +446 -0
- package/dist/permission-mode.d.ts +87 -0
- package/dist/permission-mode.js +307 -0
- package/dist/permissions-cli.d.ts +13 -49
- package/dist/permissions-cli.js +154 -348
- package/dist/permissions.d.ts +148 -38
- package/dist/permissions.js +591 -45
- package/dist/post-install.d.ts +33 -0
- package/dist/post-install.js +127 -0
- package/dist/read-credential.d.ts +65 -0
- package/dist/read-credential.js +86 -0
- package/dist/registry/cli.js +5 -2
- package/dist/registry/config.d.ts +0 -17
- package/dist/registry/config.js +0 -23
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +2 -2
- package/dist/registry/manager.d.ts +4 -21
- package/dist/registry/manager.js +83 -55
- package/dist/runtime-credential.d.ts +140 -0
- package/dist/runtime-credential.js +572 -0
- package/dist/runtime.d.ts +408 -0
- package/dist/runtime.js +748 -0
- package/dist/setup.d.ts +23 -28
- package/dist/setup.js +57 -84
- package/dist/status-cli.d.ts +29 -13
- package/dist/status-cli.js +124 -144
- package/dist/status-data.d.ts +195 -0
- package/dist/status-data.js +333 -0
- package/dist/status-system.d.ts +24 -0
- package/dist/status-system.js +56 -0
- package/dist/subject.d.ts +126 -20
- package/dist/subject.js +215 -30
- package/dist/testing.d.ts +74 -38
- package/dist/testing.js +185 -68
- package/dist/tool-catalog.d.ts +53 -11
- package/dist/tool-catalog.js +164 -13
- package/dist/tool-metadata.d.ts +7 -6
- package/dist/tool-metadata.js +6 -5
- package/dist/types.d.ts +11 -1
- package/dist/uninstall.d.ts +74 -19
- package/dist/uninstall.js +224 -49
- package/dist/user-login.d.ts +22 -16
- package/dist/user-login.js +67 -96
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +3 -11
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -584
- package/dist/interactive-setup.d.ts +0 -165
- package/dist/interactive-setup.js +0 -1546
- package/dist/local/jaeger-main.d.ts +0 -13
- package/dist/local/jaeger-main.js +0 -85
- package/dist/local/jaeger.d.ts +0 -50
- package/dist/local/jaeger.js +0 -162
- package/dist/otel/exporter.d.ts +0 -17
- package/dist/otel/exporter.js +0 -12
- package/dist/otel/index.d.ts +0 -2
- package/dist/otel/index.js +0 -8
- package/dist/otel/otlp.d.ts +0 -103
- package/dist/otel/otlp.js +0 -385
- package/dist/tracer.d.ts +0 -190
- package/dist/tracer.js +0 -481
- package/dist/watch-sandbox.d.ts +0 -9
- package/dist/watch-sandbox.js +0 -81
package/dist/runtime.js
ADDED
|
@@ -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
|
+
}
|