@ory/argus 0.14.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +31 -46
  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 +39 -91
  6. package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -17
  7. package/assets/skills/ory-temporal-worker/SKILL.md +31 -33
  8. package/assets/skills/permissions-onboarding/SKILL.md +131 -104
  9. package/dist/adapters.d.ts +88 -32
  10. package/dist/adapters.js +443 -164
  11. package/dist/agent-auth.d.ts +226 -67
  12. package/dist/agent-auth.js +951 -205
  13. package/dist/auth-store.d.ts +37 -2
  14. package/dist/auth-store.js +37 -3
  15. package/dist/auth.d.ts +33 -4
  16. package/dist/auth.js +163 -20
  17. package/dist/bash-parser.d.ts +98 -0
  18. package/dist/bash-parser.js +396 -0
  19. package/dist/branding.d.ts +77 -16
  20. package/dist/branding.js +93 -23
  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 -239
  26. package/dist/client.d.ts +175 -138
  27. package/dist/client.js +672 -391
  28. package/dist/config.d.ts +217 -54
  29. package/dist/config.js +461 -42
  30. package/dist/context.d.ts +10 -0
  31. package/dist/context.js +21 -0
  32. package/dist/contract-suite.d.ts +3 -5
  33. package/dist/contract-suite.js +75 -47
  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 +28 -23
  47. package/dist/index.js +173 -58
  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.js +7 -7
  54. package/dist/local/index.d.ts +2 -2
  55. package/dist/local/index.js +24 -10
  56. package/dist/local/manager.d.ts +20 -1
  57. package/dist/local/manager.js +159 -36
  58. package/dist/local/ports.d.ts +158 -0
  59. package/dist/local/ports.js +443 -0
  60. package/dist/local/seed.d.ts +13 -5
  61. package/dist/local/seed.js +62 -38
  62. package/dist/logger.d.ts +54 -25
  63. package/dist/logger.js +329 -63
  64. package/dist/mcp.d.ts +2 -2
  65. package/dist/mcp.js +10 -5
  66. package/dist/mirror-bootstrap.d.ts +48 -0
  67. package/dist/mirror-bootstrap.js +254 -0
  68. package/dist/opl.d.ts +289 -0
  69. package/dist/opl.js +446 -0
  70. package/dist/permission-mode.d.ts +87 -0
  71. package/dist/permission-mode.js +307 -0
  72. package/dist/permissions-cli.d.ts +13 -49
  73. package/dist/permissions-cli.js +154 -348
  74. package/dist/permissions.d.ts +148 -38
  75. package/dist/permissions.js +591 -45
  76. package/dist/post-install.d.ts +33 -0
  77. package/dist/post-install.js +127 -0
  78. package/dist/read-credential.d.ts +65 -0
  79. package/dist/read-credential.js +86 -0
  80. package/dist/registry/cli.js +5 -2
  81. package/dist/registry/config.d.ts +0 -17
  82. package/dist/registry/config.js +0 -23
  83. package/dist/registry/index.d.ts +1 -1
  84. package/dist/registry/index.js +2 -2
  85. package/dist/registry/manager.d.ts +4 -21
  86. package/dist/registry/manager.js +21 -37
  87. package/dist/runtime-credential.d.ts +140 -0
  88. package/dist/runtime-credential.js +572 -0
  89. package/dist/runtime.d.ts +408 -0
  90. package/dist/runtime.js +748 -0
  91. package/dist/setup.d.ts +23 -47
  92. package/dist/setup.js +59 -116
  93. package/dist/skills.js +0 -7
  94. package/dist/status-cli.d.ts +27 -11
  95. package/dist/status-cli.js +121 -115
  96. package/dist/status-data.d.ts +116 -17
  97. package/dist/status-data.js +121 -38
  98. package/dist/subject.d.ts +126 -20
  99. package/dist/subject.js +215 -30
  100. package/dist/testing.d.ts +74 -38
  101. package/dist/testing.js +185 -68
  102. package/dist/tool-catalog.d.ts +53 -11
  103. package/dist/tool-catalog.js +164 -13
  104. package/dist/tool-metadata.d.ts +7 -6
  105. package/dist/tool-metadata.js +6 -5
  106. package/dist/types.d.ts +11 -1
  107. package/dist/uninstall.d.ts +71 -34
  108. package/dist/uninstall.js +217 -85
  109. package/dist/user-login.d.ts +9 -10
  110. package/dist/user-login.js +56 -75
  111. package/dist/watch-cli.d.ts +6 -0
  112. package/dist/watch-cli.js +217 -0
  113. package/package.json +4 -27
  114. package/assets/commands/dashboard.md +0 -34
  115. package/dist/dashboard-cli.d.ts +0 -8
  116. package/dist/dashboard-cli.js +0 -70
  117. package/dist/dev.d.ts +0 -103
  118. package/dist/dev.js +0 -583
  119. package/dist/interactive-setup.d.ts +0 -286
  120. package/dist/interactive-setup.js +0 -1734
  121. package/dist/local/jaeger-main.d.ts +0 -13
  122. package/dist/local/jaeger-main.js +0 -85
  123. package/dist/local/jaeger.d.ts +0 -50
  124. package/dist/local/jaeger.js +0 -162
  125. package/dist/otel/exporter.d.ts +0 -17
  126. package/dist/otel/exporter.js +0 -12
  127. package/dist/otel/index.d.ts +0 -2
  128. package/dist/otel/index.js +0 -8
  129. package/dist/otel/otlp.d.ts +0 -103
  130. package/dist/otel/otlp.js +0 -385
  131. package/dist/project-api-key.d.ts +0 -69
  132. package/dist/project-api-key.js +0 -147
  133. package/dist/setup-actions.d.ts +0 -232
  134. package/dist/setup-actions.js +0 -507
  135. package/dist/tracer.d.ts +0 -190
  136. package/dist/tracer.js +0 -481
  137. package/dist/watch-sandbox.d.ts +0 -9
  138. package/dist/watch-sandbox.js +0 -81
  139. package/dist/web/api.d.ts +0 -33
  140. package/dist/web/api.js +0 -294
  141. package/dist/web/launch.d.ts +0 -11
  142. package/dist/web/launch.js +0 -96
  143. package/dist/web/server.d.ts +0 -20
  144. package/dist/web/server.js +0 -233
  145. package/dist/web/types.d.ts +0 -65
  146. package/dist/web/types.js +0 -2
  147. package/dist/webapp/assets/index-Wucl4SZs.css +0 -1
  148. package/dist/webapp/assets/index-m-GtEdq0.js +0 -49
  149. package/dist/webapp/favicon.ico +0 -0
  150. package/dist/webapp/index.html +0 -15
@@ -39,25 +39,30 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  };
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.JAEGER_OTLP_HTTP_URL = exports.JAEGER_UI_URL = exports.JAEGER_OTLP_HTTP_PORT = exports.JAEGER_UI_PORT = exports.HYDRA_ADMIN_PORT = exports.HYDRA_PUBLIC_PORT = exports.KETO_WRITE_PORT = exports.KETO_READ_PORT = exports.KRATOS_ADMIN_PORT = exports.KRATOS_PUBLIC_PORT = exports.LOGIN_UI_URL = exports.LOGIN_UI_PORT = exports.CONSOLE_URL = exports.CONSOLE_PORT = exports.GATEWAY_URL = exports.GATEWAY_PORT = exports.DEFAULT_CONSOLE_LITE_SUBPATH = void 0;
42
+ exports.OATHKEEPER_API_PORT = exports.OATHKEEPER_PROXY_PORT = exports.HYDRA_TOKEN_USER_PORT = exports.HYDRA_ADMIN_PORT = exports.HYDRA_PUBLIC_PORT = exports.KETO_WRITE_PORT = exports.KETO_READ_PORT = exports.KRATOS_ADMIN_PORT = exports.KRATOS_PUBLIC_PORT = exports.LOGIN_UI_URL = exports.LOGIN_UI_PORT = exports.CONSOLE_URL = exports.CONSOLE_PORT = exports.GATEWAY_URL = exports.GATEWAY_PORT = exports.LOCAL_STACK_HOST_SOURCE = exports.LOCAL_STACK_HOST = exports.LOCAL_PORT_WARNINGS = exports.DEFAULT_CONSOLE_LITE_SUBPATH = void 0;
43
43
  exports.getGatewayConfig = getGatewayConfig;
44
44
  exports.getConsoleConfig = getConsoleConfig;
45
+ exports.getOathkeeperConfig = getOathkeeperConfig;
45
46
  exports.dockerComposeYaml = dockerComposeYaml;
46
47
  exports.kratosConfigYaml = kratosConfigYaml;
47
48
  exports.kratosIdentitySchema = kratosIdentitySchema;
48
49
  exports.ketoConfigYaml = ketoConfigYaml;
49
50
  exports.hydraConfigYaml = hydraConfigYaml;
51
+ exports.oathkeeperConfigYaml = oathkeeperConfigYaml;
52
+ exports.oathkeeperAccessRulesYaml = oathkeeperAccessRulesYaml;
50
53
  exports.nginxConf = nginxConf;
54
+ exports.getLocalPorts = getLocalPorts;
51
55
  exports.gatewayHealthUrl = gatewayHealthUrl;
52
56
  const fs = __importStar(require("node:fs"));
53
57
  const path = __importStar(require("node:path"));
58
+ const ports_js_1 = require("./ports.js");
54
59
  /** Default location of the Console Lite source tree, sibling-checkout layout. */
55
60
  exports.DEFAULT_CONSOLE_LITE_SUBPATH = path.join("..", "cloud", "console", "console");
56
61
  /**
57
62
  * Console Lite listens on this container-internal port (Dockerfile sets
58
63
  * `ENV PORT=3000`). Bound to the host on CONSOLE_PORT.
59
64
  */
60
- const CONSOLE_LITE_INTERNAL_PORT = 3000;
65
+ const CONSOLE_LITE_INTERNAL_PORT = ports_js_1.CONSOLE_CONTAINER_PORT;
61
66
  /**
62
67
  * The Console Lite Dockerfile builds from the **cloud monorepo root**, not the
63
68
  * console app dir: it `COPY`s `package.json`, `pnpm-workspace.yaml`,
@@ -188,21 +193,73 @@ function getConsoleConfig(projectRoot) {
188
193
  }
189
194
  return consoleConfig(buildContext, dockerfile, elementsContext, buildMode, source);
190
195
  }
196
+ /**
197
+ * Resolve whether the local stack should front Keto's token-authenticated
198
+ * `/user/*` read path with a real **Oathkeeper** service.
199
+ *
200
+ * **On by default** so the local stack mirrors the hosted token-introspection
201
+ * contract: an Oathkeeper service configured like the hosted
202
+ * `ory-corp:cloud:keto:read:user-token` rule — `oauth2_introspection`
203
+ * authenticator against Hydra, `allow` authorizer, `noop` mutator, `strip_path:/user`
204
+ * upstream to keto-read — so a valid plugin OAuth2 access token is *required* to
205
+ * read permissions locally, exactly as it is on a hosted Ory project. (The
206
+ * plugin's agent DCR / user PKCE tokens are populated before any permission read
207
+ * runs, so this is transparent in the normal flow.)
208
+ *
209
+ * Set `ORY_LOCAL_OATHKEEPER=0` (also `false`/`no`/`off`) to opt out and fall back
210
+ * to a transparent nginx passthrough that strips `/user` and forwards straight to
211
+ * keto-read with **no** auth — a leaner stack that matches the hosted *path shape*
212
+ * only, useful when iterating on something unrelated to the auth layer.
213
+ */
214
+ function getOathkeeperConfig() {
215
+ const raw = (process.env.ORY_LOCAL_OATHKEEPER ?? "").trim().toLowerCase();
216
+ const disabled = raw === "0" || raw === "false" || raw === "no" || raw === "off";
217
+ return { enabled: !disabled };
218
+ }
191
219
  // ─── Docker Compose ────────────────────────────────────────────────
192
- function nginxGatewayService() {
220
+ function nginxGatewayService(oathkeeperEnabled) {
221
+ // When Oathkeeper fronts the /user/* path, the gateway proxies those reads to
222
+ // it, so order Oathkeeper's start ahead of the gateway (nginx resolves the
223
+ // upstream name at config load).
224
+ const oathkeeperDep = oathkeeperEnabled ? " - oathkeeper\n" : "";
193
225
  return ` gateway:
194
226
  image: nginx:alpine
195
227
  container_name: gateway
196
228
  mem_limit: 128m
197
229
  cpus: "1.0"
198
230
  ports:
199
- - "${exports.GATEWAY_PORT}:${exports.GATEWAY_PORT}"
231
+ - "${exports.GATEWAY_PORT}:${ports_js_1.GATEWAY_CONTAINER_PORT}"
200
232
  volumes:
201
233
  - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
202
234
  depends_on:
203
235
  - kratos
204
236
  - hydra
205
237
  - keto
238
+ ${oathkeeperDep} networks:
239
+ - ory-local
240
+ `;
241
+ }
242
+ /**
243
+ * Oathkeeper service that fronts Keto's `/user/*` read path with real OAuth2
244
+ * token introspection, mirroring the hosted access rule. Only emitted when
245
+ * `ORY_LOCAL_OATHKEEPER` is set. Serves the proxy (where the gateway forwards
246
+ * `/user/*`) and the read/rule API.
247
+ */
248
+ function oathkeeperService() {
249
+ return ` oathkeeper:
250
+ image: oryd/oathkeeper
251
+ container_name: oathkeeper
252
+ command: serve -c /etc/config/oathkeeper/oathkeeper.yml
253
+ mem_limit: 128m
254
+ cpus: "1.0"
255
+ ports:
256
+ - "${exports.OATHKEEPER_PROXY_PORT}:${ports_js_1.OATHKEEPER_PROXY_CONTAINER_PORT}"
257
+ - "${exports.OATHKEEPER_API_PORT}:${ports_js_1.OATHKEEPER_API_CONTAINER_PORT}"
258
+ volumes:
259
+ - ./oathkeeper:/etc/config/oathkeeper
260
+ depends_on:
261
+ - hydra
262
+ - keto
206
263
  networks:
207
264
  - ory-local
208
265
  `;
@@ -233,12 +290,12 @@ function loginUiService() {
233
290
  mem_limit: 192m
234
291
  cpus: "1.0"
235
292
  ports:
236
- - "${exports.LOGIN_UI_PORT}:${exports.LOGIN_UI_PORT}"
293
+ - "${exports.LOGIN_UI_PORT}:${ports_js_1.LOGIN_UI_CONTAINER_PORT}"
237
294
  environment:
238
- PORT: "${exports.LOGIN_UI_PORT}"
239
- KRATOS_PUBLIC_URL: "http://kratos:${exports.KRATOS_PUBLIC_PORT}"
295
+ PORT: "${ports_js_1.LOGIN_UI_CONTAINER_PORT}"
296
+ KRATOS_PUBLIC_URL: "http://kratos:${ports_js_1.KRATOS_PUBLIC_CONTAINER_PORT}"
240
297
  KRATOS_BROWSER_URL: "${exports.GATEWAY_URL}"
241
- HYDRA_ADMIN_URL: "http://hydra:${exports.HYDRA_ADMIN_PORT}"
298
+ HYDRA_ADMIN_URL: "http://hydra:${ports_js_1.HYDRA_ADMIN_CONTAINER_PORT}"
242
299
  BASE_URL: "${exports.LOGIN_UI_URL}"
243
300
  COOKIE_SECRET: "ory-agent-plugins-local-cookie-secret-32+"
244
301
  CSRF_COOKIE_NAME: "ory_agent_plugins_local_csrf"
@@ -276,10 +333,10 @@ function consoleService(cfg) {
276
333
  # service URLs at runtime; they're resolved over the docker network
277
334
  # using the compose service hostnames + admin ports.
278
335
  environment:
279
- ORY_KRATOS_URL: "http://kratos:${exports.KRATOS_ADMIN_PORT}"
280
- ORY_HYDRA_URL: "http://hydra:${exports.HYDRA_ADMIN_PORT}"
281
- ORY_KETO_READ_URL: "http://keto:${exports.KETO_READ_PORT}"
282
- ORY_KETO_WRITE_URL: "http://keto:${exports.KETO_WRITE_PORT}"
336
+ ORY_KRATOS_URL: "http://kratos:${ports_js_1.KRATOS_ADMIN_CONTAINER_PORT}"
337
+ ORY_HYDRA_URL: "http://hydra:${ports_js_1.HYDRA_ADMIN_CONTAINER_PORT}"
338
+ ORY_KETO_READ_URL: "http://keto:${ports_js_1.KETO_READ_CONTAINER_PORT}"
339
+ ORY_KETO_WRITE_URL: "http://keto:${ports_js_1.KETO_WRITE_CONTAINER_PORT}"
283
340
  depends_on:
284
341
  - kratos
285
342
  - hydra
@@ -295,9 +352,11 @@ function consoleService(cfg) {
295
352
  * emitted. When omitted, it is resolved via `getConsoleConfig(projectRoot)`.
296
353
  * The nginx API gateway and the login UI are always emitted.
297
354
  */
298
- function dockerComposeYaml(projectRoot, consoleCfg) {
355
+ function dockerComposeYaml(projectRoot, consoleCfg, oathkeeperCfg) {
299
356
  const console = consoleCfg ?? getConsoleConfig(projectRoot);
300
357
  const consoleBlock = console.enabled ? consoleService(console) : "";
358
+ const oathkeeper = oathkeeperCfg ?? getOathkeeperConfig();
359
+ const oathkeeperBlock = oathkeeper.enabled ? oathkeeperService() : "";
301
360
  return `# Generated by @ory-agent-plugins — local development environment
302
361
  # Do not edit manually; re-run "local up" to regenerate.
303
362
 
@@ -311,8 +370,8 @@ services:
311
370
  mem_limit: 128m
312
371
  cpus: "1.0"
313
372
  ports:
314
- - "4433:4433"
315
- - "4434:4434"
373
+ - "${exports.KRATOS_PUBLIC_PORT}:${ports_js_1.KRATOS_PUBLIC_CONTAINER_PORT}"
374
+ - "${exports.KRATOS_ADMIN_PORT}:${ports_js_1.KRATOS_ADMIN_CONTAINER_PORT}"
316
375
  volumes:
317
376
  - ./kratos:/etc/config/kratos
318
377
  networks:
@@ -325,8 +384,8 @@ services:
325
384
  mem_limit: 128m
326
385
  cpus: "1.0"
327
386
  ports:
328
- - "4466:4466"
329
- - "4467:4467"
387
+ - "${exports.KETO_READ_PORT}:${ports_js_1.KETO_READ_CONTAINER_PORT}"
388
+ - "${exports.KETO_WRITE_PORT}:${ports_js_1.KETO_WRITE_CONTAINER_PORT}"
330
389
  volumes:
331
390
  - ./keto:/etc/config/keto
332
391
  networks:
@@ -339,31 +398,17 @@ services:
339
398
  mem_limit: 128m
340
399
  cpus: "1.0"
341
400
  ports:
342
- - "4444:4444" # Public port
343
- - "4445:4445" # Admin port
344
- - "5555:5555" # Port for hydra token user
401
+ - "${exports.HYDRA_PUBLIC_PORT}:${ports_js_1.HYDRA_PUBLIC_CONTAINER_PORT}" # Public port
402
+ - "${exports.HYDRA_ADMIN_PORT}:${ports_js_1.HYDRA_ADMIN_CONTAINER_PORT}" # Admin port
403
+ - "${exports.HYDRA_TOKEN_USER_PORT}:${ports_js_1.HYDRA_TOKEN_USER_CONTAINER_PORT}" # Port for hydra token user
345
404
  volumes:
346
405
  - ./hydra:/etc/config/hydra
347
406
  networks:
348
407
  - ory-local
349
408
 
350
- ${nginxGatewayService()}
351
- ${loginUiService()}
352
- ${consoleBlock} jaeger:
353
- image: jaegertracing/all-in-one:1
354
- container_name: jaeger
355
- mem_limit: 384m
356
- cpus: "1.0"
357
- environment:
358
- - COLLECTOR_OTLP_ENABLED=true
359
- - LOG_LEVEL=warn
360
- ports:
361
- - "16686:16686" # Jaeger UI
362
- - "4318:4318" # OTLP HTTP receiver
363
- networks:
364
- - ory-local
365
-
366
-
409
+ ${nginxGatewayService(oathkeeper.enabled)}
410
+ ${oathkeeperBlock}${loginUiService()}
411
+ ${consoleBlock}
367
412
  networks:
368
413
  ory-local:
369
414
  driver: bridge
@@ -378,13 +423,15 @@ dsn: memory
378
423
 
379
424
  serve:
380
425
  public:
381
- base_url: http://localhost:4433/
426
+ # Host-reachable base URLs: Kratos advertises these outward, so they carry
427
+ # the host port and the advertised host, not the container-internal pair.
428
+ base_url: ${(0, ports_js_1.hostUrl)(LOCAL_HOST, exports.KRATOS_PUBLIC_PORT)}/
382
429
  cors:
383
430
  enabled: true
384
431
  allowed_origins:
385
432
  - '*'
386
433
  admin:
387
- base_url: http://localhost:4434/
434
+ base_url: ${(0, ports_js_1.hostUrl)(LOCAL_HOST, exports.KRATOS_ADMIN_PORT)}/
388
435
 
389
436
  selfservice:
390
437
  # Where Kratos sends browsers when there's no explicit return_to. The
@@ -414,7 +461,7 @@ oauth2_provider:
414
461
  # that Hydra forwards through the login UI. Without it, Kratos returns
415
462
  # "refusing to parse login_challenge query parameter because
416
463
  # oauth2_provider.url is invalid or unset" and the OAuth2 flow stalls.
417
- url: http://hydra:${exports.HYDRA_ADMIN_PORT}
464
+ url: http://hydra:${ports_js_1.HYDRA_ADMIN_CONTAINER_PORT}
418
465
 
419
466
  identity:
420
467
  default_schema_id: agent
@@ -481,21 +528,32 @@ function ketoConfigYaml() {
481
528
  dsn: memory
482
529
 
483
530
  serve:
531
+ # Container-internal listen ports. These stay fixed no matter which host
532
+ # ports the stack publishes on — they are what the other services address
533
+ # over the compose network (\`http://keto:${ports_js_1.KETO_READ_CONTAINER_PORT}\`).
484
534
  read:
485
535
  host: 0.0.0.0
486
- port: 4466
536
+ port: ${ports_js_1.KETO_READ_CONTAINER_PORT}
487
537
  write:
488
538
  host: 0.0.0.0
489
- port: 4467
539
+ port: ${ports_js_1.KETO_WRITE_CONTAINER_PORT}
490
540
  metrics:
491
541
  host: 0.0.0.0
492
542
  port: 4468
493
543
 
544
+ # Namespaces are defined via the Ory Permission Language (OPL) file rather
545
+ # than the inline id/name list, so the AgentTool namespace can carry a
546
+ # block-aware permit (\`access = use && !blocked\`) instead of relying purely
547
+ # on relation-tuple existence. See buildAgentSecurityOpl() in ../opl.ts.
494
548
  namespaces:
495
- - id: 0
496
- name: AgentTools
497
- - id: 1
498
- name: User
549
+ location: file:///etc/config/keto/namespaces.keto.ts
550
+
551
+ # Shell-command decomposition (issue #76) checks each command word in a batch.
552
+ # Keto's default cap is 10; raise it so the local stack matches the gate's
553
+ # chunking headroom and word-heavy commands stay in as few round-trips as
554
+ # possible. The gate still chunks defensively for real projects on the default.
555
+ limit:
556
+ max_batch_check_size: 100
499
557
 
500
558
  log:
501
559
  level: debug
@@ -550,13 +608,136 @@ log:
550
608
  level: debug
551
609
  `;
552
610
  }
611
+ // ─── Oathkeeper Configuration (optional /user override) ────────────
612
+ /**
613
+ * Oathkeeper server config. Only written when `ORY_LOCAL_OATHKEEPER` is set.
614
+ *
615
+ * Serves the proxy (the gateway forwards `/user/*` here) and the API port, and
616
+ * declares the handlers the access rule references: the `oauth2_introspection`
617
+ * authenticator introspects the plugin's OAuth2 access token against Hydra's
618
+ * admin introspection endpoint, `allow` authorizes, and `noop` mutates. Rules
619
+ * use the `regexp` matching strategy so the `/user/*` URL pattern resolves.
620
+ */
621
+ function oathkeeperConfigYaml() {
622
+ return `# Generated by @ory-agent-plugins — local development environment
623
+ # Do not edit manually; re-run "local up" to regenerate.
624
+
625
+ serve:
626
+ # Container-internal listen ports; the gateway reaches the proxy over the
627
+ # compose network. Host publishing is handled by the compose mapping.
628
+ proxy:
629
+ port: ${ports_js_1.OATHKEEPER_PROXY_CONTAINER_PORT}
630
+ api:
631
+ port: ${ports_js_1.OATHKEEPER_API_CONTAINER_PORT}
632
+
633
+ access_rules:
634
+ matching_strategy: regexp
635
+ repositories:
636
+ - file:///etc/config/oathkeeper/access-rules.yml
637
+
638
+ errors:
639
+ fallback:
640
+ - json
641
+ handlers:
642
+ json:
643
+ enabled: true
644
+ config:
645
+ verbose: true
646
+
647
+ authenticators:
648
+ oauth2_introspection:
649
+ enabled: true
650
+ config:
651
+ # Hydra admin introspection endpoint, reachable over the docker network.
652
+ # A valid (active) plugin access token → 200 and the read is forwarded to
653
+ # keto-read; a missing/invalid/inactive token → 401 at the proxy.
654
+ introspection_url: http://hydra:${ports_js_1.HYDRA_ADMIN_CONTAINER_PORT}/admin/oauth2/introspect
655
+ noop:
656
+ enabled: true
657
+
658
+ authorizers:
659
+ allow:
660
+ enabled: true
661
+ deny:
662
+ enabled: true
663
+
664
+ mutators:
665
+ noop:
666
+ enabled: true
667
+
668
+ log:
669
+ level: debug
670
+ format: json
671
+ `;
672
+ }
673
+ /**
674
+ * Oathkeeper access rule mirroring the hosted
675
+ * `ory-corp:cloud:keto:read:user-token` rule: token-introspect the caller,
676
+ * `allow`, and forward to keto-read with the `/user` prefix stripped. Scoped to
677
+ * the same read surface as hosted — `/user/relation-tuples*` and
678
+ * `/user/namespaces*` — on GET/HEAD/POST.
679
+ */
680
+ function oathkeeperAccessRulesYaml() {
681
+ return `# Generated by @ory-agent-plugins — local development environment
682
+ # Do not edit manually; re-run "local up" to regenerate.
683
+
684
+ - id: ory-local:keto:read:user-token
685
+ match:
686
+ methods:
687
+ - GET
688
+ - HEAD
689
+ - POST
690
+ url: "http://<[^/]+>/user/<(relation-tuples|namespaces).*>"
691
+ authenticators:
692
+ - handler: oauth2_introspection
693
+ authorizer:
694
+ handler: allow
695
+ mutators:
696
+ - handler: noop
697
+ upstream:
698
+ url: http://keto:${ports_js_1.KETO_READ_CONTAINER_PORT}
699
+ strip_path: /user
700
+ preserve_host: false
701
+ `;
702
+ }
553
703
  // ─── Nginx Gateway ─────────────────────────────────────────────────
554
704
  /**
555
705
  * Nginx config that acts as a unified API gateway, replicating what
556
706
  * Ory Network does: route all API paths to the correct backend service
557
707
  * so the @ory/client SDK works with a single basePath.
558
708
  */
559
- function nginxConf() {
709
+ function nginxConf(oathkeeperEnabled = false) {
710
+ // The token-authenticated Keto read path (`/user/*`). Two shapes:
711
+ // • default — a transparent passthrough: strip `/user` and forward straight
712
+ // to keto-read. Matches the hosted *path shape* but enforces no auth.
713
+ // • ORY_LOCAL_OATHKEEPER — forward the whole `/user/*` request (prefix
714
+ // intact) to the Oathkeeper proxy, which introspects the OAuth2 access
715
+ // token, then strips `/user` and forwards to keto-read itself. This
716
+ // exercises the hosted token-introspection contract locally.
717
+ const userReadBlock = oathkeeperEnabled
718
+ ? ` # ── Keto read API (token-authenticated /user path) ────
719
+ # ORY_LOCAL_OATHKEEPER: front the /user/* read path with a real
720
+ # Oathkeeper proxy that introspects the plugin's OAuth2 access token
721
+ # (mirrors the hosted ory-corp:cloud:keto:read:user-token rule). The
722
+ # /user prefix is kept so Oathkeeper's access rule matches; Oathkeeper
723
+ # strips it before forwarding to keto-read. A missing/invalid token is
724
+ # rejected with 401 here instead of silently reaching Keto.
725
+ location /user/ {
726
+ proxy_pass http://oathkeeper:${ports_js_1.OATHKEEPER_PROXY_CONTAINER_PORT};
727
+ }`
728
+ : ` # ── Keto read API (token-authenticated /user path) ────
729
+ # ORY_LOCAL_OATHKEEPER=0 (opt-out): transparent passthrough. Strip /user
730
+ # and forward straight to keto-read with NO auth. Matches the hosted path
731
+ # shape only — token introspection is NOT exercised. The default (flag
732
+ # unset) fronts this path with a real Oathkeeper proxy instead.
733
+ location /user/relation-tuples {
734
+ rewrite ^/user(/.*)$ $1 break;
735
+ proxy_pass http://keto:${ports_js_1.KETO_READ_CONTAINER_PORT};
736
+ }
737
+ location /user/namespaces {
738
+ rewrite ^/user(/.*)$ $1 break;
739
+ proxy_pass http://keto:${ports_js_1.KETO_READ_CONTAINER_PORT};
740
+ }`;
560
741
  return `# Generated by @ory-agent-plugins — local development environment
561
742
  # Do not edit manually; re-run "local up" to regenerate.
562
743
 
@@ -575,62 +756,64 @@ http {
575
756
  proxy_set_header X-Forwarded-Proto $scheme;
576
757
 
577
758
  server {
578
- listen 4000;
759
+ listen ${ports_js_1.GATEWAY_CONTAINER_PORT};
579
760
  server_name localhost;
580
761
 
581
762
  # ── Kratos public API ──────────────────────────────────
582
763
  # Sessions (FrontendApi.toSession → GET /sessions/whoami)
583
764
  location /sessions/ {
584
- proxy_pass http://kratos:4433;
765
+ proxy_pass http://kratos:${ports_js_1.KRATOS_PUBLIC_CONTAINER_PORT};
585
766
  }
586
767
 
587
768
  # Self-service flows (login, registration, etc.)
588
769
  location /self-service/ {
589
- proxy_pass http://kratos:4433;
770
+ proxy_pass http://kratos:${ports_js_1.KRATOS_PUBLIC_CONTAINER_PORT};
590
771
  }
591
772
 
592
773
  # ── Kratos admin API ──────────────────────────────────
593
774
  # Identity management
594
775
  location /admin/identities {
595
- proxy_pass http://kratos:4434;
776
+ proxy_pass http://kratos:${ports_js_1.KRATOS_ADMIN_CONTAINER_PORT};
596
777
  }
597
778
 
598
779
  # ── Hydra admin API ───────────────────────────────────
599
780
  # Token introspection (OAuth2Api.introspectOAuth2Token → POST /admin/oauth2/introspect)
600
781
  location /admin/oauth2/ {
601
- proxy_pass http://hydra:4445;
782
+ proxy_pass http://hydra:${ports_js_1.HYDRA_ADMIN_CONTAINER_PORT};
602
783
  }
603
784
 
604
785
  # ── Hydra public API ──────────────────────────────────
605
786
  location /oauth2/ {
606
- proxy_pass http://hydra:4444;
787
+ proxy_pass http://hydra:${ports_js_1.HYDRA_PUBLIC_CONTAINER_PORT};
607
788
  }
608
789
 
609
790
  location /.well-known/ {
610
- proxy_pass http://hydra:4444;
791
+ proxy_pass http://hydra:${ports_js_1.HYDRA_PUBLIC_CONTAINER_PORT};
611
792
  }
612
793
 
613
- # ── Keto read API ─────────────────────────────────────
614
- # Permission checks (PermissionApi.checkPermission → GET /relation-tuples/check/openapi)
615
- # Batch checks (PermissionApi.batchCheckPermission POST /relation-tuples/batch-check)
616
- # No trailing slash on the location so a bare GET /relation-tuples
617
- # (the listing endpoint) also matches and proxies through.
794
+ ${userReadBlock}
795
+
796
+ # ── Keto read API (legacy un-prefixed) ────────────────
797
+ # Retained for direct callers (the local seed step, curl, the Console).
798
+ # The plugin itself reads through the /user route above. No trailing
799
+ # slash on the location so a bare GET /relation-tuples matches.
618
800
  location /relation-tuples {
619
- proxy_pass http://keto:4466;
801
+ proxy_pass http://keto:${ports_js_1.KETO_READ_CONTAINER_PORT};
620
802
  }
621
803
 
622
804
  # ── Keto write API ────────────────────────────────────
623
- # RelationshipApi.createRelationship issues PUT /admin/relation-tuples
624
- # (no trailing slash), and deleteRelationships issues DELETE on the
625
- # same path with query params. The location must match both the
626
- # slashless form and any sub-path; dropping the trailing slash on
627
- # the nginx location does both.
805
+ # The plugin never writes here (permissions are provisioned in the Ory
806
+ # Console); this is for the local seed step and for hand-writing grants
807
+ # during a demo PUT /admin/relation-tuples (no trailing slash) and
808
+ # DELETE on the same path with query params. The location must match
809
+ # both the slashless form and any sub-path; dropping the trailing slash
810
+ # on the nginx location does both.
628
811
  location /admin/relation-tuples {
629
- proxy_pass http://keto:4467;
812
+ proxy_pass http://keto:${ports_js_1.KETO_WRITE_CONTAINER_PORT};
630
813
  }
631
814
 
632
815
  location /opl/ {
633
- proxy_pass http://keto:4467;
816
+ proxy_pass http://keto:${ports_js_1.KETO_WRITE_CONTAINER_PORT};
634
817
  }
635
818
 
636
819
  # ── Gateway health ────────────────────────────────────
@@ -648,23 +831,47 @@ http {
648
831
  }
649
832
  `;
650
833
  }
651
- // ─── Default Ports ─────────────────────────────────────────────────
652
- exports.GATEWAY_PORT = 4000;
653
- exports.GATEWAY_URL = `http://localhost:${exports.GATEWAY_PORT}`;
654
- exports.CONSOLE_PORT = 4100;
655
- exports.CONSOLE_URL = `http://localhost:${exports.CONSOLE_PORT}`;
656
- exports.LOGIN_UI_PORT = 4455;
657
- exports.LOGIN_UI_URL = `http://localhost:${exports.LOGIN_UI_PORT}`;
658
- exports.KRATOS_PUBLIC_PORT = 4433;
659
- exports.KRATOS_ADMIN_PORT = 4434;
660
- exports.KETO_READ_PORT = 4466;
661
- exports.KETO_WRITE_PORT = 4467;
662
- exports.HYDRA_PUBLIC_PORT = 4444;
663
- exports.HYDRA_ADMIN_PORT = 4445;
664
- exports.JAEGER_UI_PORT = 16686;
665
- exports.JAEGER_OTLP_HTTP_PORT = 4318;
666
- exports.JAEGER_UI_URL = `http://localhost:${exports.JAEGER_UI_PORT}`;
667
- exports.JAEGER_OTLP_HTTP_URL = `http://localhost:${exports.JAEGER_OTLP_HTTP_PORT}`;
834
+ // ─── Host ports and URLs ───────────────────────────────────────────
835
+ //
836
+ // Resolved once, at module load, from the environment — see `ports.ts` for the
837
+ // override rules and for why container-internal ports are *not* part of this.
838
+ // Module-load resolution keeps these as plain `const`s so every existing
839
+ // consumer (and the public export surface in `index.ts`) is unchanged; the
840
+ // process reads its environment at startup, which is when a CLI is configured.
841
+ const { ports: LOCAL_PORTS, warnings: LOCAL_PORT_WARNINGS_INTERNAL } = (0, ports_js_1.resolveLocalPorts)();
842
+ const { host: LOCAL_HOST, source: LOCAL_HOST_SOURCE } = (0, ports_js_1.resolveLocalHost)();
843
+ /**
844
+ * Problems found while resolving ports (an unparseable value, an offset that
845
+ * runs off the end of the port range, two services pinned to one port). The
846
+ * manager prints these before starting so a typo'd override doesn't silently
847
+ * fall back to a default the user thought they had changed.
848
+ */
849
+ exports.LOCAL_PORT_WARNINGS = LOCAL_PORT_WARNINGS_INTERNAL;
850
+ /** The host every generated URL advertises, and where that value came from. */
851
+ exports.LOCAL_STACK_HOST = LOCAL_HOST;
852
+ exports.LOCAL_STACK_HOST_SOURCE = LOCAL_HOST_SOURCE;
853
+ exports.GATEWAY_PORT = LOCAL_PORTS.gateway;
854
+ exports.GATEWAY_URL = (0, ports_js_1.hostUrl)(LOCAL_HOST, exports.GATEWAY_PORT);
855
+ exports.CONSOLE_PORT = LOCAL_PORTS.console;
856
+ exports.CONSOLE_URL = (0, ports_js_1.hostUrl)(LOCAL_HOST, exports.CONSOLE_PORT);
857
+ exports.LOGIN_UI_PORT = LOCAL_PORTS.loginUi;
858
+ exports.LOGIN_UI_URL = (0, ports_js_1.hostUrl)(LOCAL_HOST, exports.LOGIN_UI_PORT);
859
+ exports.KRATOS_PUBLIC_PORT = LOCAL_PORTS.kratosPublic;
860
+ exports.KRATOS_ADMIN_PORT = LOCAL_PORTS.kratosAdmin;
861
+ exports.KETO_READ_PORT = LOCAL_PORTS.ketoRead;
862
+ exports.KETO_WRITE_PORT = LOCAL_PORTS.ketoWrite;
863
+ exports.HYDRA_PUBLIC_PORT = LOCAL_PORTS.hydraPublic;
864
+ exports.HYDRA_ADMIN_PORT = LOCAL_PORTS.hydraAdmin;
865
+ exports.HYDRA_TOKEN_USER_PORT = LOCAL_PORTS.hydraTokenUser;
866
+ /** Oathkeeper proxy port — the gateway forwards `/user/*` here when
867
+ * `ORY_LOCAL_OATHKEEPER` is set. Defaults clear of the login-UI port. */
868
+ exports.OATHKEEPER_PROXY_PORT = LOCAL_PORTS.oathkeeperProxy;
869
+ /** Oathkeeper API/rules port. */
870
+ exports.OATHKEEPER_API_PORT = LOCAL_PORTS.oathkeeperApi;
871
+ /** The resolved host-port set, for the preflight check and `local status`. */
872
+ function getLocalPorts() {
873
+ return { ...LOCAL_PORTS };
874
+ }
668
875
  /** URL the manager probes to confirm the gateway is healthy. */
669
876
  function gatewayHealthUrl() {
670
877
  return `${exports.GATEWAY_URL}${getGatewayConfig().healthPath}`;
@@ -7,6 +7,7 @@ exports.checkAllServices = checkAllServices;
7
7
  exports.waitForSeedTargets = waitForSeedTargets;
8
8
  exports.waitForGateway = waitForGateway;
9
9
  const configs_js_1 = require("./configs.js");
10
+ const ports_js_1 = require("./ports.js");
10
11
  async function checkEndpoint(name, url) {
11
12
  try {
12
13
  const res = await fetch(url, { signal: AbortSignal.timeout(3000) });
@@ -32,10 +33,9 @@ async function checkAllServices(options = {}) {
32
33
  const gateway = options.gatewayUrl ?? (0, configs_js_1.gatewayHealthUrl)();
33
34
  const checks = [
34
35
  checkEndpoint("Gateway", gateway),
35
- checkEndpoint("Kratos", `http://localhost:${configs_js_1.KRATOS_PUBLIC_PORT}/health/ready`),
36
- checkEndpoint("Keto", `http://localhost:${configs_js_1.KETO_READ_PORT}/health/ready`),
37
- checkEndpoint("Hydra", `http://localhost:${configs_js_1.HYDRA_PUBLIC_PORT}/health/ready`),
38
- checkEndpoint("Jaeger", `http://localhost:${configs_js_1.JAEGER_UI_PORT}/`),
36
+ checkEndpoint("Kratos", `${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KRATOS_PUBLIC_PORT)}/health/ready`),
37
+ checkEndpoint("Keto", `${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KETO_READ_PORT)}/health/ready`),
38
+ checkEndpoint("Hydra", `${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.HYDRA_PUBLIC_PORT)}/health/ready`),
39
39
  ];
40
40
  return Promise.all(checks);
41
41
  }
@@ -55,9 +55,9 @@ async function checkAllServices(options = {}) {
55
55
  */
56
56
  function seedTargetServices() {
57
57
  return [
58
- { name: "Kratos", url: `http://localhost:${configs_js_1.KRATOS_ADMIN_PORT}/health/ready` },
59
- { name: "Keto", url: `http://localhost:${configs_js_1.KETO_WRITE_PORT}/health/ready` },
60
- { name: "Hydra", url: `http://localhost:${configs_js_1.HYDRA_ADMIN_PORT}/health/ready` },
58
+ { name: "Kratos", url: `${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KRATOS_ADMIN_PORT)}/health/ready` },
59
+ { name: "Keto", url: `${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KETO_WRITE_PORT)}/health/ready` },
60
+ { name: "Hydra", url: `${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.HYDRA_ADMIN_PORT)}/health/ready` },
61
61
  ];
62
62
  }
63
63
  /**
@@ -2,5 +2,5 @@ export { runLocalCommand } from "./cli.js";
2
2
  export { localUp, localDown, localStatus, localSeed, localLogs, localReset, localEnv, localConfigure, ensureLocalOryStack, type EnsureLocalOryStackResult, } from "./manager.js";
3
3
  export { checkAllServices, waitForGateway, type ServiceHealth } from "./health.js";
4
4
  export { seedLocalEnvironment, USER_CLIENT_ID, USER_SUBJECT_NAMESPACE, type SeedResult, } from "./seed.js";
5
- export { GATEWAY_URL, GATEWAY_PORT, KRATOS_PUBLIC_PORT, KRATOS_ADMIN_PORT, KETO_READ_PORT, KETO_WRITE_PORT, HYDRA_PUBLIC_PORT, HYDRA_ADMIN_PORT, JAEGER_UI_PORT, JAEGER_OTLP_HTTP_PORT, JAEGER_UI_URL, JAEGER_OTLP_HTTP_URL, } from "./configs.js";
6
- export { ensureDevJaeger, stopDevJaeger, DEV_JAEGER_CONTAINER, DEV_JAEGER_IMAGE, type EnsureDevJaegerResult, type StopDevJaegerResult, } from "./jaeger.js";
5
+ export { GATEWAY_URL, GATEWAY_PORT, KRATOS_PUBLIC_PORT, KRATOS_ADMIN_PORT, KETO_READ_PORT, KETO_WRITE_PORT, HYDRA_PUBLIC_PORT, HYDRA_ADMIN_PORT, HYDRA_TOKEN_USER_PORT, CONSOLE_PORT, CONSOLE_URL, LOGIN_UI_PORT, LOGIN_UI_URL, LOCAL_STACK_HOST, LOCAL_STACK_HOST_SOURCE, LOCAL_PORT_WARNINGS, getLocalPorts, } from "./configs.js";
6
+ export { LOCAL_PORT_NAMES, LOCAL_PORT_SPECS, LOCAL_HOST_ENV_VAR, PORT_OFFSET_ENV_VAR, DEFAULT_LOCAL_HOST, resolveLocalPorts, resolveLocalHost, hostFromDockerHost, isLocalDockerDaemon, isPortAvailable, findPortConflicts, describePortConflicts, hostUrl, type LocalPortName, type ResolvedLocalPorts, type LocalHostSource, type PortConflict, } from "./ports.js";