@ory/argus 0.1.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 (80) hide show
  1. package/README.md +134 -0
  2. package/assets/commands/local-down.md +19 -0
  3. package/assets/commands/local-up.md +27 -0
  4. package/assets/skills/auth-setup/SKILL.md +279 -0
  5. package/assets/skills/local-dev/SKILL.md +206 -0
  6. package/assets/skills/login-flow/SKILL.md +383 -0
  7. package/assets/skills/social-login/SKILL.md +312 -0
  8. package/dist/agent-auth.d.ts +204 -0
  9. package/dist/agent-auth.js +553 -0
  10. package/dist/auth-gate.d.ts +71 -0
  11. package/dist/auth-gate.js +308 -0
  12. package/dist/auth-store.d.ts +75 -0
  13. package/dist/auth-store.js +261 -0
  14. package/dist/auth.d.ts +93 -0
  15. package/dist/auth.js +323 -0
  16. package/dist/cli.d.ts +73 -0
  17. package/dist/cli.js +484 -0
  18. package/dist/client.d.ts +158 -0
  19. package/dist/client.js +679 -0
  20. package/dist/config.d.ts +135 -0
  21. package/dist/config.js +344 -0
  22. package/dist/denial.d.ts +79 -0
  23. package/dist/denial.js +103 -0
  24. package/dist/dev.d.ts +95 -0
  25. package/dist/dev.js +514 -0
  26. package/dist/index.d.ts +20 -0
  27. package/dist/index.js +137 -0
  28. package/dist/local/cli.d.ts +12 -0
  29. package/dist/local/cli.js +95 -0
  30. package/dist/local/configs.d.ts +89 -0
  31. package/dist/local/configs.js +634 -0
  32. package/dist/local/health.d.ts +32 -0
  33. package/dist/local/health.js +65 -0
  34. package/dist/local/index.d.ts +6 -0
  35. package/dist/local/index.js +38 -0
  36. package/dist/local/jaeger-main.d.ts +13 -0
  37. package/dist/local/jaeger-main.js +85 -0
  38. package/dist/local/jaeger.d.ts +50 -0
  39. package/dist/local/jaeger.js +162 -0
  40. package/dist/local/main.d.ts +7 -0
  41. package/dist/local/main.js +14 -0
  42. package/dist/local/manager.d.ts +45 -0
  43. package/dist/local/manager.js +676 -0
  44. package/dist/local/seed.d.ts +71 -0
  45. package/dist/local/seed.js +237 -0
  46. package/dist/logger.d.ts +29 -0
  47. package/dist/logger.js +139 -0
  48. package/dist/mcp.d.ts +76 -0
  49. package/dist/mcp.js +122 -0
  50. package/dist/otel/exporter.d.ts +17 -0
  51. package/dist/otel/exporter.js +12 -0
  52. package/dist/otel/index.d.ts +2 -0
  53. package/dist/otel/index.js +8 -0
  54. package/dist/otel/otlp-http.d.ts +116 -0
  55. package/dist/otel/otlp-http.js +322 -0
  56. package/dist/registry/cli.d.ts +12 -0
  57. package/dist/registry/cli.js +76 -0
  58. package/dist/registry/config.d.ts +23 -0
  59. package/dist/registry/config.js +80 -0
  60. package/dist/registry/index.d.ts +3 -0
  61. package/dist/registry/index.js +21 -0
  62. package/dist/registry/main.d.ts +7 -0
  63. package/dist/registry/main.js +14 -0
  64. package/dist/registry/manager.d.ts +38 -0
  65. package/dist/registry/manager.js +674 -0
  66. package/dist/setup.d.ts +118 -0
  67. package/dist/setup.js +398 -0
  68. package/dist/skills.d.ts +78 -0
  69. package/dist/skills.js +264 -0
  70. package/dist/subject.d.ts +43 -0
  71. package/dist/subject.js +55 -0
  72. package/dist/tool-metadata.d.ts +41 -0
  73. package/dist/tool-metadata.js +127 -0
  74. package/dist/tracer.d.ts +172 -0
  75. package/dist/tracer.js +452 -0
  76. package/dist/types.d.ts +57 -0
  77. package/dist/types.js +3 -0
  78. package/dist/watch-sandbox.d.ts +9 -0
  79. package/dist/watch-sandbox.js +81 -0
  80. package/package.json +79 -0
@@ -0,0 +1,676 @@
1
+ "use strict";
2
+ /**
3
+ * Local Ory environment manager.
4
+ *
5
+ * Manages the Docker Compose lifecycle for running Kratos, Keto, Hydra,
6
+ * and an API gateway locally. The gateway defaults to Ory Console Lite when
7
+ * its source tree is present at `<projectRoot>/../cloud/console/console`,
8
+ * and falls back to the bundled nginx routing layer otherwise.
9
+ * `ORY_CONSOLE_LITE_PATH` overrides the path explicitly.
10
+ *
11
+ * Runtime directory: .ory-dev/ory/ (relative to where the command is run,
12
+ * or the project root when invoked from a plugin CLI). Sibling of
13
+ * .ory-dev/registry/ owned by the local npm registry manager.
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.ensureLocalOryStack = ensureLocalOryStack;
50
+ exports.localUp = localUp;
51
+ exports.localDown = localDown;
52
+ exports.localStatus = localStatus;
53
+ exports.localSeed = localSeed;
54
+ exports.localLogs = localLogs;
55
+ exports.localReset = localReset;
56
+ exports.localEnv = localEnv;
57
+ exports.localConfigure = localConfigure;
58
+ const fs = __importStar(require("node:fs"));
59
+ const path = __importStar(require("node:path"));
60
+ const node_child_process_1 = require("node:child_process");
61
+ const configs_js_1 = require("./configs.js");
62
+ const health_js_1 = require("./health.js");
63
+ const seed_js_1 = require("./seed.js");
64
+ const config_js_1 = require("../config.js");
65
+ const LOCAL_DIR_NAME = ".ory-dev/ory";
66
+ async function isGatewayHealthy(timeoutMs, url) {
67
+ try {
68
+ const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
69
+ return res.ok;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ /**
76
+ * Verify the Console Lite build context when it came from an explicit
77
+ * `ORY_CONSOLE_LITE_PATH` override. Default-resolved configs are skipped
78
+ * (resolution already proved the path is usable, and a missing default
79
+ * silently disables Console). Returns an error string when the explicit
80
+ * path is unusable, null otherwise.
81
+ */
82
+ function validateConsoleConfig(cfg) {
83
+ if (!cfg.enabled || cfg.source !== "env")
84
+ return null;
85
+ const ctx = cfg.buildContext ?? "";
86
+ if (!ctx) {
87
+ return "ORY_CONSOLE_LITE_PATH is empty.";
88
+ }
89
+ if (!fs.existsSync(ctx)) {
90
+ return `ORY_CONSOLE_LITE_PATH=${ctx} does not exist.`;
91
+ }
92
+ const dockerfile = path.resolve(ctx, cfg.dockerfile ?? "Dockerfile");
93
+ if (!fs.existsSync(dockerfile)) {
94
+ return `Dockerfile not found at ${dockerfile} (set ORY_CONSOLE_LITE_DOCKERFILE to override).`;
95
+ }
96
+ const elements = cfg.elementsContext ?? "";
97
+ if (!elements) {
98
+ return "Elements context path is empty (set ORY_CONSOLE_LITE_ELEMENTS_PATH to override).";
99
+ }
100
+ if (!fs.existsSync(elements)) {
101
+ return (`Elements context not found at ${elements}. ` +
102
+ `Console Lite's Dockerfile expects '../../elements' relative to the console dir; ` +
103
+ `set ORY_CONSOLE_LITE_ELEMENTS_PATH to override.`);
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Find the project root anchor for `.ory-dev/` and the Console Lite
109
+ * sibling-checkout default.
110
+ *
111
+ * Walks up to the outermost `.git` ancestor: this resolves to the
112
+ * workspace root from any sub-package, and to the main repo root from
113
+ * any worktree (where `.git` is a file pointer at the leaf and a real
114
+ * directory at the main checkout further up). Falls back to the first
115
+ * `package.json` ancestor when no `.git` is found (e.g. end users
116
+ * invoking via the plugin CLI inside their own project).
117
+ */
118
+ function findProjectRoot(startDir) {
119
+ let dir = startDir;
120
+ let outermostGit = null;
121
+ let firstPackageJson = null;
122
+ while (dir !== path.dirname(dir)) {
123
+ if (fs.existsSync(path.join(dir, ".git"))) {
124
+ outermostGit = dir;
125
+ }
126
+ if (firstPackageJson === null &&
127
+ fs.existsSync(path.join(dir, "package.json"))) {
128
+ firstPackageJson = dir;
129
+ }
130
+ dir = path.dirname(dir);
131
+ }
132
+ return outermostGit ?? firstPackageJson ?? startDir;
133
+ }
134
+ function getLocalDir() {
135
+ const root = findProjectRoot(process.cwd());
136
+ return path.join(root, LOCAL_DIR_NAME);
137
+ }
138
+ function ensureDir(dir) {
139
+ if (!fs.existsSync(dir)) {
140
+ fs.mkdirSync(dir, { recursive: true });
141
+ }
142
+ }
143
+ /**
144
+ * Detect docker compose command. Prefers `docker compose` (v2),
145
+ * falls back to `docker-compose` (v1).
146
+ */
147
+ function getComposeCommand() {
148
+ try {
149
+ (0, node_child_process_1.execSync)("docker compose version", { stdio: "ignore" });
150
+ return ["docker", "compose"];
151
+ }
152
+ catch {
153
+ try {
154
+ (0, node_child_process_1.execSync)("docker-compose version", { stdio: "ignore" });
155
+ return ["docker-compose"];
156
+ }
157
+ catch {
158
+ throw new Error("Docker Compose is not installed. Install Docker Desktop or docker-compose.");
159
+ }
160
+ }
161
+ }
162
+ function runCompose(composeArgs, localDir, opts) {
163
+ const cmd = getComposeCommand();
164
+ const args = [
165
+ ...cmd.slice(1),
166
+ "-f",
167
+ path.join(localDir, "docker-compose.yml"),
168
+ ...composeArgs,
169
+ ];
170
+ const result = (0, node_child_process_1.spawnSync)(cmd[0], args, {
171
+ cwd: localDir,
172
+ stdio: opts?.stdio ?? "inherit",
173
+ env: process.env,
174
+ });
175
+ return {
176
+ status: result.status ?? 1,
177
+ stdout: result.stdout?.toString(),
178
+ stderr: result.stderr?.toString(),
179
+ };
180
+ }
181
+ /**
182
+ * Dump captured output from a failing piped command to stderr so the user
183
+ * can see the underlying error. In non-piped mode, the output already
184
+ * streamed to the terminal — nothing to dump.
185
+ */
186
+ function dumpCapturedOutput(label, result) {
187
+ const out = (result.stdout ?? "").trim();
188
+ const err = (result.stderr ?? "").trim();
189
+ if (!out && !err)
190
+ return;
191
+ process.stderr.write(`\n── ${label} output ──\n`);
192
+ if (out)
193
+ process.stderr.write(out + "\n");
194
+ if (err)
195
+ process.stderr.write(err + "\n");
196
+ process.stderr.write(`── end ${label} output ──\n\n`);
197
+ }
198
+ // ─── Write config files ────────────────────────────────────────────
199
+ function writeConfigs(localDir, projectRoot, consoleCfg) {
200
+ ensureDir(localDir);
201
+ ensureDir(path.join(localDir, "kratos"));
202
+ ensureDir(path.join(localDir, "keto"));
203
+ ensureDir(path.join(localDir, "hydra"));
204
+ ensureDir(path.join(localDir, "nginx"));
205
+ fs.writeFileSync(path.join(localDir, "docker-compose.yml"), (0, configs_js_1.dockerComposeYaml)(projectRoot, consoleCfg));
206
+ fs.writeFileSync(path.join(localDir, "kratos", "kratos.yml"), (0, configs_js_1.kratosConfigYaml)());
207
+ fs.writeFileSync(path.join(localDir, "kratos", "identity-schema.json"), (0, configs_js_1.kratosIdentitySchema)());
208
+ fs.writeFileSync(path.join(localDir, "keto", "keto.yml"), (0, configs_js_1.ketoConfigYaml)());
209
+ fs.writeFileSync(path.join(localDir, "hydra", "hydra.yml"), (0, configs_js_1.hydraConfigYaml)());
210
+ // nginx config is always emitted — the gateway is no longer optional.
211
+ fs.writeFileSync(path.join(localDir, "nginx", "nginx.conf"), (0, configs_js_1.nginxConf)());
212
+ }
213
+ /**
214
+ * Bring up the local Ory stack and wait for the gateway to be healthy.
215
+ *
216
+ * Idempotent and fail-graceful: returns a typed result instead of exiting.
217
+ * If the gateway is already healthy the function short-circuits without
218
+ * touching Docker. When `quiet` is set, progress is logged to stderr only
219
+ * (suitable for the dev launcher); otherwise it prints to stdout in the
220
+ * format `localUp` historically used.
221
+ */
222
+ async function ensureLocalOryStack(opts = {}) {
223
+ const localDir = opts.localDir ?? getLocalDir();
224
+ const projectRoot = path.dirname(localDir);
225
+ const log = opts.quiet
226
+ ? (s) => process.stderr.write(s + "\n")
227
+ : (s) => console.log(s);
228
+ let consoleCfg = (0, configs_js_1.getConsoleConfig)(projectRoot);
229
+ const cfgError = validateConsoleConfig(consoleCfg);
230
+ if (cfgError) {
231
+ return { status: "console-misconfigured", localDir, detail: cfgError };
232
+ }
233
+ const gatewayCfg = (0, configs_js_1.getGatewayConfig)();
234
+ const healthUrl = `${configs_js_1.GATEWAY_URL}${gatewayCfg.healthPath}`;
235
+ // Short-circuit if the gateway is already serving — keeps repeated dev
236
+ // launches fast and avoids re-pulling/restarting healthy containers.
237
+ // Skip the short-circuit when ORY_CONSOLE_LITE_PATH is explicitly set so
238
+ // a path change on a subsequent run actually triggers a rebuild (compose
239
+ // diffs the regenerated YAML and recreates the console when needed).
240
+ const explicitConsoleLiteOverride = (process.env.ORY_CONSOLE_LITE_PATH ?? "").trim() !== "";
241
+ if (!explicitConsoleLiteOverride &&
242
+ (await isGatewayHealthy(1_500, healthUrl))) {
243
+ return { status: "already-running", gatewayUrl: configs_js_1.GATEWAY_URL, localDir };
244
+ }
245
+ try {
246
+ (0, node_child_process_1.execSync)("docker info", { stdio: "ignore" });
247
+ }
248
+ catch {
249
+ return {
250
+ status: "skipped:no-docker",
251
+ localDir,
252
+ detail: "Docker is not running. Start Docker Desktop and try again.",
253
+ };
254
+ }
255
+ writeConfigs(localDir, projectRoot, consoleCfg);
256
+ log(" Config files written.");
257
+ log(" Gateway: bundled nginx API passthrough.");
258
+ log(" Login UI: oryd/kratos-selfservice-ui-node.");
259
+ if (consoleCfg.enabled) {
260
+ const sourceLabel = consoleCfg.source === "default"
261
+ ? "default sibling checkout"
262
+ : "ORY_CONSOLE_LITE_PATH";
263
+ log(` Console: Ory Console Lite (${sourceLabel} → ${consoleCfg.buildContext}).`);
264
+ log(` Elements build context: ${consoleCfg.elementsContext}`);
265
+ log(` NEXT_PUBLIC_BUILD_MODE: ${consoleCfg.buildMode}`);
266
+ }
267
+ else {
268
+ log(" Console: disabled (Console Lite source not found at default path).");
269
+ }
270
+ log(" Pulling Docker images (first run may take a minute)...");
271
+ const pullResult = runCompose(["pull", "--quiet"], localDir, {
272
+ stdio: opts.quiet ? "pipe" : "inherit",
273
+ });
274
+ if (pullResult.status !== 0) {
275
+ log(" Warning: Some images may not have pulled. Continuing...");
276
+ }
277
+ if (consoleCfg.enabled) {
278
+ log(" Building Ory Console Lite image (first run may take a few minutes)...");
279
+ const buildResult = runCompose(["build", "console"], localDir, {
280
+ stdio: opts.quiet ? "pipe" : "inherit",
281
+ });
282
+ if (buildResult.status !== 0) {
283
+ // Surface the actual build error first — in dev launcher (quiet)
284
+ // mode the build runs piped, so without dumping the captured
285
+ // output the user only sees "exit N" with no clue why.
286
+ dumpCapturedOutput("docker compose build console", buildResult);
287
+ // Graceful fallback: a Console Lite source-tree build failure
288
+ // shouldn't block the rest of the local stack from coming up.
289
+ // Disable Console for this run, regenerate the compose YAML
290
+ // without it, and continue. The rest of the stack (API gateway,
291
+ // login UI, Kratos/Hydra/Keto, Jaeger) is unaffected. The user
292
+ // can fix the cloud repo and rerun to bring Console back.
293
+ log(` Warning: Ory Console Lite build failed (exit ${buildResult.status}). ` +
294
+ `Continuing without Console — the rest of the stack will come up. ` +
295
+ `Re-run the build directly to iterate: cd ${localDir} && docker compose build console`);
296
+ consoleCfg = { enabled: false };
297
+ writeConfigs(localDir, projectRoot, consoleCfg);
298
+ }
299
+ }
300
+ log(" Starting services...");
301
+ const upResult = runCompose(["up", "-d", "--wait"], localDir, {
302
+ stdio: opts.quiet ? "pipe" : "inherit",
303
+ });
304
+ if (upResult.status !== 0) {
305
+ dumpCapturedOutput("docker compose up", upResult);
306
+ return {
307
+ status: "compose-failed",
308
+ localDir,
309
+ detail: `docker compose up exited ${upResult.status}.`,
310
+ };
311
+ }
312
+ log(" Waiting for gateway to become healthy...");
313
+ const healthy = await (0, health_js_1.waitForGateway)(120_000, 2000, {
314
+ gatewayUrl: healthUrl,
315
+ });
316
+ if (!healthy) {
317
+ return {
318
+ status: "gateway-unhealthy",
319
+ localDir,
320
+ detail: "Gateway did not become healthy within 2 minutes.",
321
+ };
322
+ }
323
+ return { status: "started", gatewayUrl: configs_js_1.GATEWAY_URL, localDir };
324
+ }
325
+ async function localUp(opts = {}) {
326
+ console.log("Setting up local Ory environment...");
327
+ console.log(` Directory: ${getLocalDir()}`);
328
+ console.log("");
329
+ const result = await ensureLocalOryStack();
330
+ switch (result.status) {
331
+ case "skipped:no-docker":
332
+ case "console-misconfigured":
333
+ console.error(`Error: ${result.detail}`);
334
+ process.exit(1);
335
+ case "compose-failed":
336
+ case "gateway-unhealthy":
337
+ console.error(`Error: ${result.detail}`);
338
+ console.error("Check service logs:");
339
+ console.error(` cd ${result.localDir} && docker compose logs`);
340
+ process.exit(1);
341
+ case "already-running":
342
+ console.log(" Stack already running — reusing.");
343
+ break;
344
+ case "started":
345
+ console.log("");
346
+ console.log(" All services are running!");
347
+ console.log("");
348
+ break;
349
+ }
350
+ // Seed if requested
351
+ if (opts.seed !== false) {
352
+ try {
353
+ const result = await (0, seed_js_1.seedLocalEnvironment)();
354
+ printSeedResult(result);
355
+ }
356
+ catch (err) {
357
+ const msg = err instanceof Error ? err.message : String(err);
358
+ console.error(`\nWarning: Seed failed: ${msg}`);
359
+ console.error("Services are running — you can seed later with: local seed");
360
+ }
361
+ }
362
+ else {
363
+ printConnectionInfo();
364
+ }
365
+ }
366
+ /**
367
+ * List container IDs labelled as part of the `ory-local` Compose project.
368
+ * Returns an empty array on any docker error (e.g. daemon unreachable).
369
+ */
370
+ function findOryLocalContainers() {
371
+ try {
372
+ const result = (0, node_child_process_1.spawnSync)("docker", [
373
+ "ps",
374
+ "-a",
375
+ "-q",
376
+ "--filter",
377
+ "label=com.docker.compose.project=ory-local",
378
+ ], { encoding: "utf-8" });
379
+ if (result.status !== 0)
380
+ return [];
381
+ return (result.stdout ?? "")
382
+ .split("\n")
383
+ .map((s) => s.trim())
384
+ .filter(Boolean);
385
+ }
386
+ catch {
387
+ return [];
388
+ }
389
+ }
390
+ async function localDown() {
391
+ const localDir = getLocalDir();
392
+ if (fs.existsSync(path.join(localDir, "docker-compose.yml"))) {
393
+ console.log("Stopping local Ory environment...");
394
+ runCompose(["down"], localDir);
395
+ console.log("Done. Data volumes are preserved. Use 'local reset' to remove everything.");
396
+ return;
397
+ }
398
+ // No compose file at the resolved localDir — fall back to label-based
399
+ // discovery so a runaway stack started from a different cwd (e.g. an
400
+ // older worktree) can still be torn down.
401
+ const ids = findOryLocalContainers();
402
+ if (ids.length === 0) {
403
+ console.log("No local Ory environment found. Nothing to stop.");
404
+ return;
405
+ }
406
+ console.log(`No compose file at ${localDir}, but found ${ids.length} container(s) ` +
407
+ `with the 'ory-local' Compose project label. Stopping them directly...`);
408
+ const rm = (0, node_child_process_1.spawnSync)("docker", ["rm", "-f", ...ids], { stdio: "inherit" });
409
+ if (rm.status !== 0) {
410
+ console.error(`Warning: 'docker rm -f' exited ${rm.status}. Some containers may still be running.`);
411
+ return;
412
+ }
413
+ console.log("Done. Data volumes are preserved. Use 'local reset' to remove everything.");
414
+ }
415
+ async function localStatus() {
416
+ const localDir = getLocalDir();
417
+ const projectRoot = path.dirname(localDir);
418
+ console.log("Local Ory Environment Status");
419
+ console.log("============================");
420
+ console.log("");
421
+ console.log(` Directory: ${localDir}`);
422
+ console.log(` Gateway: ${configs_js_1.GATEWAY_URL}`);
423
+ console.log("");
424
+ // Check if compose file exists
425
+ if (!fs.existsSync(path.join(localDir, "docker-compose.yml"))) {
426
+ console.log(" Status: NOT INITIALIZED");
427
+ console.log(" Run 'local up' to start the local Ory environment.");
428
+ return;
429
+ }
430
+ // Check container states
431
+ const ps = runCompose(["ps", "--format", "json"], localDir, {
432
+ stdio: "pipe",
433
+ });
434
+ if (ps.stdout) {
435
+ // docker compose ps --format json outputs one JSON object per line
436
+ const lines = ps.stdout.trim().split("\n").filter(Boolean);
437
+ let running = 0;
438
+ let total = 0;
439
+ for (const line of lines) {
440
+ try {
441
+ const container = JSON.parse(line);
442
+ total++;
443
+ const isRunning = container.State === "running";
444
+ if (isRunning)
445
+ running++;
446
+ const icon = isRunning ? "+" : "!";
447
+ const health = container.Health ? ` (${container.Health})` : "";
448
+ console.log(` [${icon}] ${container.Name}: ${container.State}${health}`);
449
+ }
450
+ catch {
451
+ // May not be JSON — older docker-compose versions
452
+ }
453
+ }
454
+ if (total > 0) {
455
+ console.log("");
456
+ console.log(` ${running}/${total} containers running`);
457
+ }
458
+ }
459
+ // Health check endpoints
460
+ console.log("");
461
+ console.log("Service Health:");
462
+ const health = await (0, health_js_1.checkAllServices)({ projectRoot });
463
+ for (const svc of health) {
464
+ const icon = svc.healthy ? "+" : "!";
465
+ const status = svc.healthy ? "healthy" : `unhealthy: ${svc.error}`;
466
+ console.log(` [${icon}] ${svc.name}: ${status}`);
467
+ }
468
+ }
469
+ async function localSeed() {
470
+ const namespace = process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
471
+ try {
472
+ const result = await (0, seed_js_1.seedLocalEnvironment)(namespace);
473
+ printSeedResult(result);
474
+ }
475
+ catch (err) {
476
+ const msg = err instanceof Error ? err.message : String(err);
477
+ console.error(`Seed failed: ${msg}`);
478
+ console.error("");
479
+ console.error("Make sure the local environment is running: local up");
480
+ process.exit(1);
481
+ }
482
+ }
483
+ function localLogs(args) {
484
+ const localDir = getLocalDir();
485
+ if (!fs.existsSync(path.join(localDir, "docker-compose.yml"))) {
486
+ console.log("No local Ory environment found. Run 'local up' first.");
487
+ return;
488
+ }
489
+ const service = args[0]; // Optional: filter to a specific service
490
+ const composeArgs = ["logs", "--tail", "50"];
491
+ if (args.includes("-f") || args.includes("--follow")) {
492
+ composeArgs.push("-f");
493
+ }
494
+ if (service && !service.startsWith("-")) {
495
+ composeArgs.push(service);
496
+ }
497
+ runCompose(composeArgs, localDir);
498
+ }
499
+ /**
500
+ * List named volumes labelled as part of the `ory-local` Compose project.
501
+ *
502
+ * Anonymous volumes created by container `VOLUME` directives (e.g. some Ory
503
+ * service images) only carry `com.docker.volume.anonymous` and so won't show
504
+ * up here — capture those via `findContainerVolumes` BEFORE removing the
505
+ * containers, since once the containers are gone those volumes can no longer
506
+ * be associated with the project.
507
+ */
508
+ function findOryLocalVolumes() {
509
+ try {
510
+ const result = (0, node_child_process_1.spawnSync)("docker", [
511
+ "volume",
512
+ "ls",
513
+ "-q",
514
+ "--filter",
515
+ "label=com.docker.compose.project=ory-local",
516
+ ], { encoding: "utf-8" });
517
+ if (result.status !== 0)
518
+ return [];
519
+ return (result.stdout ?? "")
520
+ .split("\n")
521
+ .map((s) => s.trim())
522
+ .filter(Boolean);
523
+ }
524
+ catch {
525
+ return [];
526
+ }
527
+ }
528
+ /**
529
+ * For the given container IDs, return all volume names mounted into them
530
+ * (named + anonymous). Used to capture volume IDs before `docker rm` so
531
+ * anonymous volumes don't become unrecoverable orphans.
532
+ */
533
+ function findContainerVolumes(containerIds) {
534
+ if (containerIds.length === 0)
535
+ return [];
536
+ try {
537
+ const result = (0, node_child_process_1.spawnSync)("docker", [
538
+ "inspect",
539
+ "--format",
540
+ '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}}\n{{end}}{{end}}',
541
+ ...containerIds,
542
+ ], { encoding: "utf-8" });
543
+ if (result.status !== 0)
544
+ return [];
545
+ const seen = new Set();
546
+ for (const line of (result.stdout ?? "").split("\n")) {
547
+ const v = line.trim();
548
+ if (v)
549
+ seen.add(v);
550
+ }
551
+ return [...seen];
552
+ }
553
+ catch {
554
+ return [];
555
+ }
556
+ }
557
+ async function localReset() {
558
+ const localDir = getLocalDir();
559
+ const composeExists = fs.existsSync(path.join(localDir, "docker-compose.yml"));
560
+ if (composeExists) {
561
+ console.log("Resetting local Ory environment (removing all data)...");
562
+ runCompose(["down", "-v"], localDir);
563
+ console.log("Data volumes removed.");
564
+ }
565
+ else {
566
+ // Fall back to label-based discovery so an orphan stack from a
567
+ // different cwd (e.g. an older worktree) can still be torn down.
568
+ const containerIds = findOryLocalContainers();
569
+ // Capture volume IDs from running containers BEFORE removing them —
570
+ // once the containers are gone, anonymous volumes (which carry no
571
+ // compose-project label) can't be tied back to the project.
572
+ const containerVolumes = findContainerVolumes(containerIds);
573
+ const labelledVolumes = findOryLocalVolumes();
574
+ const allVolumes = [
575
+ ...new Set([...containerVolumes, ...labelledVolumes]),
576
+ ];
577
+ if (containerIds.length === 0 && allVolumes.length === 0) {
578
+ console.log("No local Ory environment found. Nothing to reset.");
579
+ return;
580
+ }
581
+ console.log(`No compose file at ${localDir}, but found ${containerIds.length} container(s) ` +
582
+ `and ${allVolumes.length} volume(s) tied to the 'ory-local' Compose project. ` +
583
+ `Removing them directly...`);
584
+ if (containerIds.length > 0) {
585
+ // -v removes anonymous volumes attached to these containers.
586
+ const rm = (0, node_child_process_1.spawnSync)("docker", ["rm", "-fv", ...containerIds], {
587
+ stdio: "inherit",
588
+ });
589
+ if (rm.status !== 0) {
590
+ console.error(`Warning: 'docker rm -fv' exited ${rm.status}. Some containers may still be running.`);
591
+ }
592
+ }
593
+ if (allVolumes.length > 0) {
594
+ // Belt-and-suspenders: remove any volumes still around (named, or
595
+ // anonymous if -v didn't catch them because the container was
596
+ // already gone).
597
+ const vrm = (0, node_child_process_1.spawnSync)("docker", ["volume", "rm", "-f", ...allVolumes], { stdio: "inherit" });
598
+ if (vrm.status !== 0) {
599
+ console.error(`Warning: 'docker volume rm' exited ${vrm.status}. Some volumes may remain.`);
600
+ }
601
+ }
602
+ console.log("Data volumes removed.");
603
+ }
604
+ // Remove config files (no-op if directory is absent)
605
+ if (fs.existsSync(localDir)) {
606
+ fs.rmSync(localDir, { recursive: true, force: true });
607
+ console.log(`Removed ${localDir}`);
608
+ }
609
+ console.log("");
610
+ console.log("Run 'local up' to start fresh.");
611
+ }
612
+ function localEnv() {
613
+ console.log("# Add these to your shell profile or .env file:");
614
+ console.log(`export ORY_PROJECT_URL=${configs_js_1.GATEWAY_URL}`);
615
+ console.log("export ORY_AGENT_DEBUG=true");
616
+ console.log("");
617
+ console.log("# If you ran 'local seed', also set the session token:");
618
+ console.log("# export ORY_SESSION_TOKEN=<token from seed output>");
619
+ console.log("");
620
+ console.log("# Ship traces to the bundled Jaeger (UI: http://localhost:16686):");
621
+ console.log(`export OTEL_EXPORTER_OTLP_ENDPOINT=${configs_js_1.JAEGER_OTLP_HTTP_URL}`);
622
+ }
623
+ function localConfigure() {
624
+ console.log(`Saving local Ory gateway URL to shared config...`);
625
+ (0, config_js_1.saveConfig)({ projectUrl: configs_js_1.GATEWAY_URL, auditOnly: false });
626
+ console.log(` Project URL: ${configs_js_1.GATEWAY_URL}`);
627
+ console.log("");
628
+ console.log("All Ory agent plugins will now connect to the local environment.");
629
+ console.log("This persists across sessions. Run 'configure --project-url <URL>'");
630
+ console.log("to switch back to a remote Ory project.");
631
+ }
632
+ // ─── Helpers ───────────────────────────────────────────────────────
633
+ function printSeedResult(result) {
634
+ console.log("");
635
+ console.log("Seed complete!");
636
+ console.log("==============");
637
+ console.log("");
638
+ console.log(` Agent identity: ${result.agent.identity.email} (${result.agent.identity.id})`);
639
+ console.log(` (OAuth2 client self-registers on first run via DCR)`);
640
+ console.log("");
641
+ console.log(` User identity: ${result.user.identity.email} (${result.user.identity.id})`);
642
+ console.log(` User password: ${result.user.password}`);
643
+ console.log(` User OAuth2: ${result.user.client.clientId} (PKCE)`);
644
+ console.log("");
645
+ console.log(` Permissions: ${result.permissions.tuples} tuples in '${result.permissions.namespace}' for ${result.permissions.subject}`);
646
+ console.log("");
647
+ console.log("To use with any Ory agent plugin, set these environment variables:");
648
+ console.log("");
649
+ console.log(` export ORY_PROJECT_URL=${configs_js_1.GATEWAY_URL}`);
650
+ console.log(` export ORY_AUTH_GATE=1`);
651
+ console.log(` export ORY_OAUTH2_CLIENT_ID=${result.user.client.clientId}`);
652
+ console.log(` export ORY_USER_SUBJECT_NAMESPACE=User`);
653
+ console.log(` export ORY_USER_SUBJECT_ID=${result.user.identity.id}`);
654
+ console.log("");
655
+ console.log("The user gate runs PKCE in the browser — sign in with the email and");
656
+ console.log("password printed above. The agent OAuth2 client is registered");
657
+ console.log("automatically via Dynamic Client Registration (RFC 7591) on first");
658
+ console.log("session using the user's bearer as the initial access token.");
659
+ console.log("");
660
+ console.log("Or save the project URL to the shared config (persists across sessions):");
661
+ console.log("");
662
+ console.log(" npx ory-<harness> local configure");
663
+ }
664
+ function printConnectionInfo() {
665
+ console.log("Connection Info");
666
+ console.log("===============");
667
+ console.log("");
668
+ console.log(` Gateway URL: ${configs_js_1.GATEWAY_URL}`);
669
+ console.log(` Kratos: http://localhost:4433 (public) / http://localhost:4434 (admin)`);
670
+ console.log(` Keto: http://localhost:4466 (read) / http://localhost:4467 (write)`);
671
+ console.log(` Hydra: http://localhost:4444 (public) / http://localhost:4445 (admin)`);
672
+ console.log(` Jaeger UI: ${configs_js_1.JAEGER_UI_URL}`);
673
+ console.log(` OTLP HTTP: ${configs_js_1.JAEGER_OTLP_HTTP_URL} (set OTEL_EXPORTER_OTLP_ENDPOINT to ship traces)`);
674
+ console.log("");
675
+ console.log("Run 'local seed' to create test data, or 'local env' to print env vars.");
676
+ }