@ory/argus 0.13.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +31 -45
  2. package/assets/commands/temporal-up.md +1 -1
  3. package/assets/skills/auth-setup/SKILL.md +1 -1
  4. package/assets/skills/local-dev/SKILL.md +17 -7
  5. package/assets/skills/ory-build-agent/SKILL.md +43 -97
  6. package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
  7. package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
  8. package/assets/skills/permissions-onboarding/SKILL.md +131 -104
  9. package/dist/adapters.d.ts +93 -30
  10. package/dist/adapters.js +464 -136
  11. package/dist/agent-auth.d.ts +258 -68
  12. package/dist/agent-auth.js +998 -202
  13. package/dist/auth-store.d.ts +37 -2
  14. package/dist/auth-store.js +37 -3
  15. package/dist/auth.d.ts +40 -4
  16. package/dist/auth.js +247 -19
  17. package/dist/bash-parser.d.ts +98 -0
  18. package/dist/bash-parser.js +396 -0
  19. package/dist/branding.d.ts +128 -0
  20. package/dist/branding.js +151 -0
  21. package/dist/build-info.json +4 -4
  22. package/dist/cli-invocation.d.ts +1 -1
  23. package/dist/cli-invocation.js +2 -1
  24. package/dist/cli.d.ts +20 -29
  25. package/dist/cli.js +271 -278
  26. package/dist/client.d.ts +175 -138
  27. package/dist/client.js +672 -391
  28. package/dist/config.d.ts +249 -57
  29. package/dist/config.js +486 -62
  30. package/dist/context.d.ts +10 -0
  31. package/dist/context.js +21 -0
  32. package/dist/contract-suite.d.ts +8 -8
  33. package/dist/contract-suite.js +88 -69
  34. package/dist/denial.d.ts +36 -3
  35. package/dist/denial.js +79 -10
  36. package/dist/event-reporter.d.ts +77 -0
  37. package/dist/event-reporter.js +776 -0
  38. package/dist/external-registrations-main.d.ts +10 -0
  39. package/dist/external-registrations-main.js +38 -0
  40. package/dist/external-registrations.d.ts +79 -0
  41. package/dist/external-registrations.js +188 -0
  42. package/dist/help-cli.d.ts +39 -0
  43. package/dist/help-cli.js +55 -0
  44. package/dist/hook-timeout.d.ts +64 -0
  45. package/dist/hook-timeout.js +88 -0
  46. package/dist/index.d.ts +31 -19
  47. package/dist/index.js +182 -31
  48. package/dist/lifecycle.d.ts +3 -3
  49. package/dist/lifecycle.js +38 -6
  50. package/dist/local/cli.js +11 -6
  51. package/dist/local/configs.d.ts +74 -18
  52. package/dist/local/configs.js +291 -84
  53. package/dist/local/health.d.ts +14 -0
  54. package/dist/local/health.js +50 -4
  55. package/dist/local/index.d.ts +2 -2
  56. package/dist/local/index.js +24 -10
  57. package/dist/local/manager.d.ts +20 -1
  58. package/dist/local/manager.js +160 -39
  59. package/dist/local/ports.d.ts +158 -0
  60. package/dist/local/ports.js +443 -0
  61. package/dist/local/seed.d.ts +22 -25
  62. package/dist/local/seed.js +88 -56
  63. package/dist/logger.d.ts +54 -25
  64. package/dist/logger.js +329 -63
  65. package/dist/mcp.d.ts +2 -2
  66. package/dist/mcp.js +10 -5
  67. package/dist/mirror-bootstrap.d.ts +48 -0
  68. package/dist/mirror-bootstrap.js +254 -0
  69. package/dist/opl.d.ts +289 -0
  70. package/dist/opl.js +446 -0
  71. package/dist/permission-mode.d.ts +87 -0
  72. package/dist/permission-mode.js +307 -0
  73. package/dist/permissions-cli.d.ts +13 -49
  74. package/dist/permissions-cli.js +154 -348
  75. package/dist/permissions.d.ts +148 -38
  76. package/dist/permissions.js +591 -45
  77. package/dist/post-install.d.ts +33 -0
  78. package/dist/post-install.js +127 -0
  79. package/dist/read-credential.d.ts +65 -0
  80. package/dist/read-credential.js +86 -0
  81. package/dist/registry/cli.js +5 -2
  82. package/dist/registry/config.d.ts +0 -17
  83. package/dist/registry/config.js +0 -23
  84. package/dist/registry/index.d.ts +1 -1
  85. package/dist/registry/index.js +2 -2
  86. package/dist/registry/manager.d.ts +4 -21
  87. package/dist/registry/manager.js +83 -55
  88. package/dist/runtime-credential.d.ts +140 -0
  89. package/dist/runtime-credential.js +572 -0
  90. package/dist/runtime.d.ts +408 -0
  91. package/dist/runtime.js +748 -0
  92. package/dist/setup.d.ts +23 -28
  93. package/dist/setup.js +57 -84
  94. package/dist/status-cli.d.ts +29 -13
  95. package/dist/status-cli.js +124 -144
  96. package/dist/status-data.d.ts +195 -0
  97. package/dist/status-data.js +333 -0
  98. package/dist/status-system.d.ts +24 -0
  99. package/dist/status-system.js +56 -0
  100. package/dist/subject.d.ts +126 -20
  101. package/dist/subject.js +215 -30
  102. package/dist/testing.d.ts +74 -38
  103. package/dist/testing.js +185 -68
  104. package/dist/tool-catalog.d.ts +53 -11
  105. package/dist/tool-catalog.js +164 -13
  106. package/dist/tool-metadata.d.ts +7 -6
  107. package/dist/tool-metadata.js +6 -5
  108. package/dist/types.d.ts +11 -1
  109. package/dist/uninstall.d.ts +74 -19
  110. package/dist/uninstall.js +224 -49
  111. package/dist/user-login.d.ts +22 -16
  112. package/dist/user-login.js +67 -96
  113. package/dist/watch-cli.d.ts +6 -0
  114. package/dist/watch-cli.js +217 -0
  115. package/package.json +3 -11
  116. package/dist/dev.d.ts +0 -103
  117. package/dist/dev.js +0 -584
  118. package/dist/interactive-setup.d.ts +0 -165
  119. package/dist/interactive-setup.js +0 -1546
  120. package/dist/local/jaeger-main.d.ts +0 -13
  121. package/dist/local/jaeger-main.js +0 -85
  122. package/dist/local/jaeger.d.ts +0 -50
  123. package/dist/local/jaeger.js +0 -162
  124. package/dist/otel/exporter.d.ts +0 -17
  125. package/dist/otel/exporter.js +0 -12
  126. package/dist/otel/index.d.ts +0 -2
  127. package/dist/otel/index.js +0 -8
  128. package/dist/otel/otlp.d.ts +0 -103
  129. package/dist/otel/otlp.js +0 -385
  130. package/dist/tracer.d.ts +0 -190
  131. package/dist/tracer.js +0 -481
  132. package/dist/watch-sandbox.d.ts +0 -9
  133. package/dist/watch-sandbox.js +0 -81
@@ -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";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEV_JAEGER_IMAGE = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = 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.GATEWAY_PORT = exports.GATEWAY_URL = exports.USER_SUBJECT_NAMESPACE = exports.USER_CLIENT_ID = exports.seedLocalEnvironment = exports.waitForGateway = exports.checkAllServices = exports.ensureLocalOryStack = exports.localConfigure = exports.localEnv = exports.localReset = exports.localLogs = exports.localSeed = exports.localStatus = exports.localDown = exports.localUp = exports.runLocalCommand = void 0;
3
+ exports.hostUrl = exports.describePortConflicts = exports.findPortConflicts = exports.isPortAvailable = exports.isLocalDockerDaemon = exports.hostFromDockerHost = exports.resolveLocalHost = exports.resolveLocalPorts = exports.DEFAULT_LOCAL_HOST = exports.PORT_OFFSET_ENV_VAR = exports.LOCAL_HOST_ENV_VAR = exports.LOCAL_PORT_SPECS = exports.LOCAL_PORT_NAMES = exports.getLocalPorts = exports.LOCAL_PORT_WARNINGS = exports.LOCAL_STACK_HOST_SOURCE = exports.LOCAL_STACK_HOST = exports.LOGIN_UI_URL = exports.LOGIN_UI_PORT = exports.CONSOLE_URL = exports.CONSOLE_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.GATEWAY_PORT = exports.GATEWAY_URL = exports.USER_SUBJECT_NAMESPACE = exports.USER_CLIENT_ID = exports.seedLocalEnvironment = exports.waitForGateway = exports.checkAllServices = exports.ensureLocalOryStack = exports.localConfigure = exports.localEnv = exports.localReset = exports.localLogs = exports.localSeed = exports.localStatus = exports.localDown = exports.localUp = exports.runLocalCommand = void 0;
4
4
  var cli_js_1 = require("./cli.js");
5
5
  Object.defineProperty(exports, "runLocalCommand", { enumerable: true, get: function () { return cli_js_1.runLocalCommand; } });
6
6
  var manager_js_1 = require("./manager.js");
@@ -29,12 +29,26 @@ Object.defineProperty(exports, "KETO_READ_PORT", { enumerable: true, get: functi
29
29
  Object.defineProperty(exports, "KETO_WRITE_PORT", { enumerable: true, get: function () { return configs_js_1.KETO_WRITE_PORT; } });
30
30
  Object.defineProperty(exports, "HYDRA_PUBLIC_PORT", { enumerable: true, get: function () { return configs_js_1.HYDRA_PUBLIC_PORT; } });
31
31
  Object.defineProperty(exports, "HYDRA_ADMIN_PORT", { enumerable: true, get: function () { return configs_js_1.HYDRA_ADMIN_PORT; } });
32
- Object.defineProperty(exports, "JAEGER_UI_PORT", { enumerable: true, get: function () { return configs_js_1.JAEGER_UI_PORT; } });
33
- Object.defineProperty(exports, "JAEGER_OTLP_HTTP_PORT", { enumerable: true, get: function () { return configs_js_1.JAEGER_OTLP_HTTP_PORT; } });
34
- Object.defineProperty(exports, "JAEGER_UI_URL", { enumerable: true, get: function () { return configs_js_1.JAEGER_UI_URL; } });
35
- Object.defineProperty(exports, "JAEGER_OTLP_HTTP_URL", { enumerable: true, get: function () { return configs_js_1.JAEGER_OTLP_HTTP_URL; } });
36
- var jaeger_js_1 = require("./jaeger.js");
37
- Object.defineProperty(exports, "ensureDevJaeger", { enumerable: true, get: function () { return jaeger_js_1.ensureDevJaeger; } });
38
- Object.defineProperty(exports, "stopDevJaeger", { enumerable: true, get: function () { return jaeger_js_1.stopDevJaeger; } });
39
- Object.defineProperty(exports, "DEV_JAEGER_CONTAINER", { enumerable: true, get: function () { return jaeger_js_1.DEV_JAEGER_CONTAINER; } });
40
- Object.defineProperty(exports, "DEV_JAEGER_IMAGE", { enumerable: true, get: function () { return jaeger_js_1.DEV_JAEGER_IMAGE; } });
32
+ Object.defineProperty(exports, "HYDRA_TOKEN_USER_PORT", { enumerable: true, get: function () { return configs_js_1.HYDRA_TOKEN_USER_PORT; } });
33
+ Object.defineProperty(exports, "CONSOLE_PORT", { enumerable: true, get: function () { return configs_js_1.CONSOLE_PORT; } });
34
+ Object.defineProperty(exports, "CONSOLE_URL", { enumerable: true, get: function () { return configs_js_1.CONSOLE_URL; } });
35
+ Object.defineProperty(exports, "LOGIN_UI_PORT", { enumerable: true, get: function () { return configs_js_1.LOGIN_UI_PORT; } });
36
+ Object.defineProperty(exports, "LOGIN_UI_URL", { enumerable: true, get: function () { return configs_js_1.LOGIN_UI_URL; } });
37
+ Object.defineProperty(exports, "LOCAL_STACK_HOST", { enumerable: true, get: function () { return configs_js_1.LOCAL_STACK_HOST; } });
38
+ Object.defineProperty(exports, "LOCAL_STACK_HOST_SOURCE", { enumerable: true, get: function () { return configs_js_1.LOCAL_STACK_HOST_SOURCE; } });
39
+ Object.defineProperty(exports, "LOCAL_PORT_WARNINGS", { enumerable: true, get: function () { return configs_js_1.LOCAL_PORT_WARNINGS; } });
40
+ Object.defineProperty(exports, "getLocalPorts", { enumerable: true, get: function () { return configs_js_1.getLocalPorts; } });
41
+ var ports_js_1 = require("./ports.js");
42
+ Object.defineProperty(exports, "LOCAL_PORT_NAMES", { enumerable: true, get: function () { return ports_js_1.LOCAL_PORT_NAMES; } });
43
+ Object.defineProperty(exports, "LOCAL_PORT_SPECS", { enumerable: true, get: function () { return ports_js_1.LOCAL_PORT_SPECS; } });
44
+ Object.defineProperty(exports, "LOCAL_HOST_ENV_VAR", { enumerable: true, get: function () { return ports_js_1.LOCAL_HOST_ENV_VAR; } });
45
+ Object.defineProperty(exports, "PORT_OFFSET_ENV_VAR", { enumerable: true, get: function () { return ports_js_1.PORT_OFFSET_ENV_VAR; } });
46
+ Object.defineProperty(exports, "DEFAULT_LOCAL_HOST", { enumerable: true, get: function () { return ports_js_1.DEFAULT_LOCAL_HOST; } });
47
+ Object.defineProperty(exports, "resolveLocalPorts", { enumerable: true, get: function () { return ports_js_1.resolveLocalPorts; } });
48
+ Object.defineProperty(exports, "resolveLocalHost", { enumerable: true, get: function () { return ports_js_1.resolveLocalHost; } });
49
+ Object.defineProperty(exports, "hostFromDockerHost", { enumerable: true, get: function () { return ports_js_1.hostFromDockerHost; } });
50
+ Object.defineProperty(exports, "isLocalDockerDaemon", { enumerable: true, get: function () { return ports_js_1.isLocalDockerDaemon; } });
51
+ Object.defineProperty(exports, "isPortAvailable", { enumerable: true, get: function () { return ports_js_1.isPortAvailable; } });
52
+ Object.defineProperty(exports, "findPortConflicts", { enumerable: true, get: function () { return ports_js_1.findPortConflicts; } });
53
+ Object.defineProperty(exports, "describePortConflicts", { enumerable: true, get: function () { return ports_js_1.describePortConflicts; } });
54
+ Object.defineProperty(exports, "hostUrl", { enumerable: true, get: function () { return ports_js_1.hostUrl; } });
@@ -12,7 +12,7 @@
12
12
  * .ory-dev/registry/ owned by the local npm registry manager.
13
13
  */
14
14
  export interface EnsureLocalOryStackResult {
15
- status: "already-running" | "started" | "skipped:no-docker" | "compose-failed" | "gateway-unhealthy" | "console-misconfigured";
15
+ status: "already-running" | "started" | "skipped:no-docker" | "port-conflict" | "compose-failed" | "gateway-unhealthy" | "console-misconfigured";
16
16
  /** Local Ory gateway URL. Set when status is `already-running` or `started`. */
17
17
  gatewayUrl?: string;
18
18
  /** Filesystem path to the compose project (for log hints). */
@@ -44,4 +44,23 @@ export declare function localSeed(): Promise<void>;
44
44
  export declare function localLogs(args: string[]): void;
45
45
  export declare function localReset(): Promise<void>;
46
46
  export declare function localEnv(): void;
47
+ /**
48
+ * Point the shared config at the local Ory stack.
49
+ *
50
+ * The local stack is part of the plugin's developer-experience surface and
51
+ * runs perfectly well without this: `local up` starts Kratos, Hydra, Keto,
52
+ * and the Console regardless of what any plugin is connected to, and
53
+ * connecting a plugin to a hosted Ory project takes none of that away.
54
+ *
55
+ * This command is the *opt-in* extra step of also pointing the security half
56
+ * at the local stack, so sign-in runs against local Kratos/Hydra and permission
57
+ * checks use the canonical local Agent Security origin. Because that writes the
58
+ * connection values into the config every installed plugin shares, an existing
59
+ * connection is reported before it is replaced — silently repointing someone's
60
+ * hosted project at localhost is the kind of surprise this warns about.
61
+ *
62
+ * The Agent Security service is provided separately at
63
+ * ``https://agents.console.ory:8080``; when it is not running, checks and
64
+ * delegation recording retain their normal classified fail-open behavior.
65
+ */
47
66
  export declare function localConfigure(): void;
@@ -60,9 +60,11 @@ const path = __importStar(require("node:path"));
60
60
  const crypto = __importStar(require("node:crypto"));
61
61
  const node_child_process_1 = require("node:child_process");
62
62
  const configs_js_1 = require("./configs.js");
63
+ const ports_js_1 = require("./ports.js");
63
64
  const health_js_1 = require("./health.js");
64
65
  const seed_js_1 = require("./seed.js");
65
66
  const config_js_1 = require("../config.js");
67
+ const opl_js_1 = require("../opl.js");
66
68
  const LOCAL_DIR_NAME = ".ory-dev/ory";
67
69
  async function isGatewayHealthy(timeoutMs, url) {
68
70
  try {
@@ -179,6 +181,51 @@ function runCompose(composeArgs, localDir, opts) {
179
181
  stderr: result.stderr?.toString(),
180
182
  };
181
183
  }
184
+ /**
185
+ * Does this compose project have containers (running or stopped)? Used to
186
+ * decide whether a port probe means anything — our own containers hold the
187
+ * stack's ports legitimately, so probing with them present reports the stack
188
+ * as conflicting with itself.
189
+ *
190
+ * Note the compose project name is fixed (`ory-local`), so this also answers
191
+ * "yes" for a stack brought up from a *different* directory. That is the right
192
+ * answer for the preflight anyway — that stack is this stack, and `up` will
193
+ * reconcile it onto the current ports rather than collide with it.
194
+ *
195
+ * Treated as "yes, containers exist" on any failure: skipping the preflight is
196
+ * the safe direction, since the failure path still diagnoses a real conflict
197
+ * after `docker compose up` reports one.
198
+ */
199
+ function composeProjectHasContainers(localDir) {
200
+ if (!fs.existsSync(path.join(localDir, "docker-compose.yml")))
201
+ return false;
202
+ try {
203
+ const result = runCompose(["ps", "-aq"], localDir, { stdio: "pipe" });
204
+ if (result.status !== 0)
205
+ return true;
206
+ return (result.stdout ?? "").trim().length > 0;
207
+ }
208
+ catch {
209
+ return true;
210
+ }
211
+ }
212
+ /**
213
+ * Render the port-conflict report, or `undefined` when there is nothing to
214
+ * report.
215
+ *
216
+ * Returns nothing for a remote Docker daemon (the stack's ports are published
217
+ * on *that* host, so probing this machine's sockets would answer a question
218
+ * nobody asked) or when probing is switched off entirely — see
219
+ * `ORY_LOCAL_SKIP_PORT_CHECK`.
220
+ */
221
+ async function describeStartupPortConflicts() {
222
+ if ((0, ports_js_1.portCheckDisabled)() || !(0, ports_js_1.isLocalDockerDaemon)())
223
+ return undefined;
224
+ const conflicts = await (0, ports_js_1.findPortConflicts)((0, configs_js_1.getLocalPorts)());
225
+ if (conflicts.length === 0)
226
+ return undefined;
227
+ return (0, ports_js_1.describePortConflicts)(conflicts);
228
+ }
182
229
  /**
183
230
  * Dump captured output from a failing piped command to stderr so the user
184
231
  * can see the underlying error. In non-piped mode, the output already
@@ -203,11 +250,11 @@ function dumpCapturedOutput(label, result) {
203
250
  * `writeConfigs` writes these and `configFingerprint` hashes them, so the
204
251
  * on-disk stack and its change-detection signal never drift.
205
252
  */
206
- function renderConfigs(projectRoot, consoleCfg) {
207
- return [
253
+ function renderConfigs(projectRoot, consoleCfg, oathkeeperCfg) {
254
+ const configs = [
208
255
  {
209
256
  relPath: "docker-compose.yml",
210
- content: (0, configs_js_1.dockerComposeYaml)(projectRoot, consoleCfg),
257
+ content: (0, configs_js_1.dockerComposeYaml)(projectRoot, consoleCfg, oathkeeperCfg),
211
258
  },
212
259
  { relPath: "kratos/kratos.yml", content: (0, configs_js_1.kratosConfigYaml)() },
213
260
  {
@@ -215,10 +262,20 @@ function renderConfigs(projectRoot, consoleCfg) {
215
262
  content: (0, configs_js_1.kratosIdentitySchema)(),
216
263
  },
217
264
  { relPath: "keto/keto.yml", content: (0, configs_js_1.ketoConfigYaml)() },
265
+ // OPL namespace definitions referenced by keto.yml's `namespaces.location`.
266
+ // Defines the block-aware `access` permit on both the AgentTool and
267
+ // ShellTool namespaces (buildAgentSecurityOpl emits both).
268
+ { relPath: "keto/namespaces.keto.ts", content: (0, opl_js_1.buildAgentSecurityOpl)() },
218
269
  { relPath: "hydra/hydra.yml", content: (0, configs_js_1.hydraConfigYaml)() },
219
- // nginx config is always emitted — the gateway is no longer optional.
220
- { relPath: "nginx/nginx.conf", content: (0, configs_js_1.nginxConf)() },
270
+ // nginx config is always emitted — the gateway is no longer optional. Its
271
+ // /user/* routing depends on whether Oathkeeper fronts that path.
272
+ { relPath: "nginx/nginx.conf", content: (0, configs_js_1.nginxConf)(oathkeeperCfg.enabled) },
221
273
  ];
274
+ // Oathkeeper config is only materialized when the /user override is enabled.
275
+ if (oathkeeperCfg.enabled) {
276
+ configs.push({ relPath: "oathkeeper/oathkeeper.yml", content: (0, configs_js_1.oathkeeperConfigYaml)() }, { relPath: "oathkeeper/access-rules.yml", content: (0, configs_js_1.oathkeeperAccessRulesYaml)() });
277
+ }
278
+ return configs;
222
279
  }
223
280
  /**
224
281
  * Stable content hash of the rendered config set. Used to decide whether an
@@ -226,9 +283,9 @@ function renderConfigs(projectRoot, consoleCfg) {
226
283
  * generate; a change (e.g. the login-UI port, a service env var) invalidates
227
284
  * the fast-path short-circuit so the stack is reconciled instead of reused.
228
285
  */
229
- function configFingerprint(projectRoot, consoleCfg) {
286
+ function configFingerprint(projectRoot, consoleCfg, oathkeeperCfg) {
230
287
  const hash = crypto.createHash("sha256");
231
- for (const { relPath, content } of renderConfigs(projectRoot, consoleCfg)) {
288
+ for (const { relPath, content } of renderConfigs(projectRoot, consoleCfg, oathkeeperCfg)) {
232
289
  hash.update(relPath);
233
290
  hash.update("\0");
234
291
  hash.update(content);
@@ -257,13 +314,16 @@ function writeConfigFingerprint(localDir, fingerprint) {
257
314
  // Best-effort: a missing fingerprint only costs a reconcile next launch.
258
315
  }
259
316
  }
260
- function writeConfigs(localDir, projectRoot, consoleCfg) {
317
+ function writeConfigs(localDir, projectRoot, consoleCfg, oathkeeperCfg) {
261
318
  ensureDir(localDir);
262
319
  ensureDir(path.join(localDir, "kratos"));
263
320
  ensureDir(path.join(localDir, "keto"));
264
321
  ensureDir(path.join(localDir, "hydra"));
265
322
  ensureDir(path.join(localDir, "nginx"));
266
- for (const { relPath, content } of renderConfigs(projectRoot, consoleCfg)) {
323
+ if (oathkeeperCfg.enabled) {
324
+ ensureDir(path.join(localDir, "oathkeeper"));
325
+ }
326
+ for (const { relPath, content } of renderConfigs(projectRoot, consoleCfg, oathkeeperCfg)) {
267
327
  fs.writeFileSync(path.join(localDir, relPath), content);
268
328
  }
269
329
  }
@@ -289,6 +349,7 @@ async function ensureLocalOryStack(opts = {}) {
289
349
  if (cfgError) {
290
350
  return { status: "console-misconfigured", localDir, detail: cfgError };
291
351
  }
352
+ const oathkeeperCfg = (0, configs_js_1.getOathkeeperConfig)();
292
353
  const gatewayCfg = (0, configs_js_1.getGatewayConfig)();
293
354
  const healthUrl = `${configs_js_1.GATEWAY_URL}${gatewayCfg.healthPath}`;
294
355
  // Short-circuit if the gateway is already serving AND the running stack was
@@ -306,7 +367,7 @@ async function ensureLocalOryStack(opts = {}) {
306
367
  // source tree behind the path may have changed, which the fingerprint of the
307
368
  // generated config can't see).
308
369
  const explicitConsoleLiteOverride = (process.env.ORY_CONSOLE_LITE_PATH ?? "").trim() !== "";
309
- const fingerprint = configFingerprint(projectRoot, consoleCfg);
370
+ const fingerprint = configFingerprint(projectRoot, consoleCfg, oathkeeperCfg);
310
371
  const configUnchanged = readConfigFingerprint(localDir) === fingerprint;
311
372
  if (!explicitConsoleLiteOverride &&
312
373
  configUnchanged &&
@@ -323,10 +384,32 @@ async function ensureLocalOryStack(opts = {}) {
323
384
  detail: "Docker is not running. Start Docker Desktop and try again.",
324
385
  };
325
386
  }
326
- writeConfigs(localDir, projectRoot, consoleCfg);
387
+ // A typo'd port override would otherwise fall back to a default the user
388
+ // thinks they changed, and only show up as a collision much later.
389
+ for (const warning of configs_js_1.LOCAL_PORT_WARNINGS)
390
+ log(` Warning: ${warning}`);
391
+ // Preflight, but only when the answer is trustworthy: with no containers in
392
+ // this compose project, every host port we want is either free or held by
393
+ // something that isn't us. Once containers exist they legitimately hold
394
+ // those ports and a probe would report the stack as conflicting with itself.
395
+ if (!composeProjectHasContainers(localDir)) {
396
+ const conflictReport = await describeStartupPortConflicts();
397
+ if (conflictReport) {
398
+ return { status: "port-conflict", localDir, detail: conflictReport };
399
+ }
400
+ }
401
+ writeConfigs(localDir, projectRoot, consoleCfg, oathkeeperCfg);
327
402
  log(" Config files written.");
328
403
  log(" Gateway: bundled nginx API passthrough.");
329
404
  log(" Login UI: oryd/kratos-selfservice-ui-node.");
405
+ if (oathkeeperCfg.enabled) {
406
+ log(" Oathkeeper: /user Keto read path fronted with OAuth2 token " +
407
+ "introspection (default; set ORY_LOCAL_OATHKEEPER=0 to disable).");
408
+ }
409
+ else {
410
+ log(" Oathkeeper: disabled (ORY_LOCAL_OATHKEEPER=0) — /user path is a " +
411
+ "transparent passthrough with no auth.");
412
+ }
330
413
  if (consoleCfg.enabled) {
331
414
  const sourceLabel = consoleCfg.source === "default"
332
415
  ? "default sibling checkout"
@@ -351,21 +434,20 @@ async function ensureLocalOryStack(opts = {}) {
351
434
  stdio: opts.quiet ? "pipe" : "inherit",
352
435
  });
353
436
  if (buildResult.status !== 0) {
354
- // Surface the actual build error first in dev launcher (quiet)
355
- // mode the build runs piped, so without dumping the captured
356
- // output the user only sees "exit N" with no clue why.
437
+ // Surface the actual build error first. In quiet mode the build runs
438
+ // piped, so otherwise the user only sees "exit N" with no clue why.
357
439
  dumpCapturedOutput("docker compose build console", buildResult);
358
440
  // Graceful fallback: a Console Lite source-tree build failure
359
441
  // shouldn't block the rest of the local stack from coming up.
360
442
  // Disable Console for this run, regenerate the compose YAML
361
443
  // without it, and continue. The rest of the stack (API gateway,
362
- // login UI, Kratos/Hydra/Keto, Jaeger) is unaffected. The user
444
+ // login UI and Kratos/Hydra/Keto) is unaffected. The user
363
445
  // can fix the cloud repo and rerun to bring Console back.
364
446
  log(` Warning: Ory Console Lite build failed (exit ${buildResult.status}). ` +
365
447
  `Continuing without Console — the rest of the stack will come up. ` +
366
448
  `Re-run the build directly to iterate: cd ${localDir} && docker compose build console`);
367
449
  consoleCfg = { enabled: false };
368
- writeConfigs(localDir, projectRoot, consoleCfg);
450
+ writeConfigs(localDir, projectRoot, consoleCfg, oathkeeperCfg);
369
451
  }
370
452
  }
371
453
  log(" Starting services...");
@@ -374,6 +456,13 @@ async function ensureLocalOryStack(opts = {}) {
374
456
  });
375
457
  if (upResult.status !== 0) {
376
458
  dumpCapturedOutput("docker compose up", upResult);
459
+ // Docker's own message names a port but not the service that wanted it,
460
+ // and says nothing about how to move it. Probe now that we know the start
461
+ // failed: at this point a held port is unambiguously someone else's.
462
+ const conflictReport = await describeStartupPortConflicts();
463
+ if (conflictReport) {
464
+ return { status: "port-conflict", localDir, detail: conflictReport };
465
+ }
377
466
  return {
378
467
  status: "compose-failed",
379
468
  localDir,
@@ -394,7 +483,7 @@ async function ensureLocalOryStack(opts = {}) {
394
483
  // Record the fingerprint of the config the stack was actually brought up
395
484
  // with (consoleCfg may have fallen back to disabled above), so the next
396
485
  // launch can short-circuit only while the config is unchanged.
397
- writeConfigFingerprint(localDir, configFingerprint(projectRoot, consoleCfg));
486
+ writeConfigFingerprint(localDir, configFingerprint(projectRoot, consoleCfg, oathkeeperCfg));
398
487
  return { status: "started", gatewayUrl: configs_js_1.GATEWAY_URL, localDir };
399
488
  }
400
489
  async function localUp(opts = {}) {
@@ -407,6 +496,12 @@ async function localUp(opts = {}) {
407
496
  case "console-misconfigured":
408
497
  console.error(`Error: ${result.detail}`);
409
498
  process.exit(1);
499
+ case "port-conflict":
500
+ // The report is already a complete, actionable message; a log hint would
501
+ // point at containers that were never created.
502
+ console.error("");
503
+ console.error(result.detail);
504
+ process.exit(1);
410
505
  case "compose-failed":
411
506
  case "gateway-unhealthy":
412
507
  console.error(`Error: ${result.detail}`);
@@ -542,7 +637,7 @@ async function localStatus() {
542
637
  }
543
638
  }
544
639
  async function localSeed() {
545
- const namespace = process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
640
+ const namespace = process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTool";
546
641
  try {
547
642
  const result = await (0, seed_js_1.seedLocalEnvironment)(namespace);
548
643
  printSeedResult(result);
@@ -687,31 +782,60 @@ async function localReset() {
687
782
  function localEnv() {
688
783
  console.log("# Add these to your shell profile or .env file:");
689
784
  console.log(`export ORY_PROJECT_URL=${configs_js_1.GATEWAY_URL}`);
785
+ console.log(`export ORY_AGENT_SECURITY_URL=${config_js_1.LOCAL_AGENT_SECURITY_URL}`);
690
786
  console.log("export ORY_AGENT_DEBUG=true");
691
787
  console.log("");
692
- console.log("# If you ran 'local seed', also set the session token:");
693
- console.log("# export ORY_SESSION_TOKEN=<token from seed output>");
694
- console.log("");
695
- console.log("# Ship traces to the bundled Jaeger (UI: http://localhost:16686):");
696
- console.log(`export OTEL_EXPORTER_OTLP_ENDPOINT=${configs_js_1.JAEGER_OTLP_HTTP_URL}`);
788
+ console.log("# 'local seed' registers the PKCE login client it prints:");
789
+ console.log("# export ORY_OAUTH2_CLIENT_ID=<client id from seed output>");
790
+ console.log("# (sign in with the seeded email + password when the browser opens;");
791
+ console.log("# or set ORY_USER_OAUTH2_TOKEN=<access token> to skip the flow)");
697
792
  }
793
+ /**
794
+ * Point the shared config at the local Ory stack.
795
+ *
796
+ * The local stack is part of the plugin's developer-experience surface and
797
+ * runs perfectly well without this: `local up` starts Kratos, Hydra, Keto,
798
+ * and the Console regardless of what any plugin is connected to, and
799
+ * connecting a plugin to a hosted Ory project takes none of that away.
800
+ *
801
+ * This command is the *opt-in* extra step of also pointing the security half
802
+ * at the local stack, so sign-in runs against local Kratos/Hydra and permission
803
+ * checks use the canonical local Agent Security origin. Because that writes the
804
+ * connection values into the config every installed plugin shares, an existing
805
+ * connection is reported before it is replaced — silently repointing someone's
806
+ * hosted project at localhost is the kind of surprise this warns about.
807
+ *
808
+ * The Agent Security service is provided separately at
809
+ * ``https://agents.console.ory:8080``; when it is not running, checks and
810
+ * delegation recording retain their normal classified fail-open behavior.
811
+ */
698
812
  function localConfigure() {
813
+ const previous = (0, config_js_1.loadConfig)();
814
+ const replacing = previous.projectUrl && previous.projectUrl !== configs_js_1.GATEWAY_URL ? previous.projectUrl : undefined;
815
+ if (replacing) {
816
+ console.log(`Note: this replaces the currently configured project URL (${replacing}).`);
817
+ console.log(" That connection is shared by every installed Ory agent plugin.");
818
+ console.log("");
819
+ }
699
820
  console.log(`Saving local Ory gateway URL to shared config...`);
700
- // Persist the seeded user PKCE client id alongside the project URL so
701
- // a subsequent `configure --project-url <hosted>` doesn't trip the
702
- // require-OAuth2-client check, and the user gate resolves the right
703
- // client out of the box.
821
+ // Persist the canonical local broker origin instead of falling back to the
822
+ // project gateway, which does not implement Agent Security broker routes.
704
823
  (0, config_js_1.saveConfig)({
705
824
  projectUrl: configs_js_1.GATEWAY_URL,
825
+ agentSecurityUrl: config_js_1.LOCAL_AGENT_SECURITY_URL,
706
826
  oauth2ClientId: seed_js_1.USER_CLIENT_ID,
707
- auditOnly: false,
708
827
  });
709
828
  console.log(` Project URL: ${configs_js_1.GATEWAY_URL}`);
829
+ console.log(` Agent Security: ${config_js_1.LOCAL_AGENT_SECURITY_URL}`);
710
830
  console.log(` OAuth2 Client ID: ${seed_js_1.USER_CLIENT_ID}`);
711
831
  console.log("");
712
- console.log("All Ory agent plugins will now connect to the local environment.");
713
- console.log("This persists across sessions. Run 'configure --project-url <URL>'");
714
- console.log("to switch back to a remote Ory project.");
832
+ console.log("Sign-in and permission checks now target the local environment.");
833
+ console.log(`Start local Agent Security at ${config_js_1.LOCAL_AGENT_SECURITY_URL} for broker calls.`);
834
+ console.log("");
835
+ console.log("This persists across sessions. Point at a hosted project with");
836
+ console.log("'configure --project-url <URL> --agent-security-url <URL>',");
837
+ console.log("or turn checks off");
838
+ console.log("with 'configure --disconnect' — the local stack keeps running either way.");
715
839
  }
716
840
  // ─── Helpers ───────────────────────────────────────────────────────
717
841
  function printSeedResult(result) {
@@ -719,8 +843,7 @@ function printSeedResult(result) {
719
843
  console.log("Seed complete!");
720
844
  console.log("==============");
721
845
  console.log("");
722
- console.log(` Agent identity: ${result.agent.identity.email} (${result.agent.identity.id})`);
723
- console.log(` (OAuth2 client self-registers on first run via DCR)`);
846
+ console.log(` Agent identity: self-registers on first run via DCR (nothing seeded)`);
724
847
  console.log("");
725
848
  console.log(` User identity: ${result.user.identity.email} (${result.user.identity.id})`);
726
849
  console.log(` User password: ${result.user.password}`);
@@ -731,7 +854,7 @@ function printSeedResult(result) {
731
854
  console.log("To use with any Ory agent plugin, set these environment variables:");
732
855
  console.log("");
733
856
  console.log(` export ORY_PROJECT_URL=${configs_js_1.GATEWAY_URL}`);
734
- console.log(` export ORY_USER_LOGIN=true`);
857
+ console.log(` export ORY_AGENT_SECURITY_URL=${config_js_1.LOCAL_AGENT_SECURITY_URL}`);
735
858
  console.log(` export ORY_OAUTH2_CLIENT_ID=${result.user.client.clientId}`);
736
859
  console.log(` export ORY_USER_SUBJECT_NAMESPACE=User`);
737
860
  console.log(` export ORY_USER_SUBJECT_ID=${result.user.identity.id}`);
@@ -750,11 +873,9 @@ function printConnectionInfo() {
750
873
  console.log("===============");
751
874
  console.log("");
752
875
  console.log(` Gateway URL: ${configs_js_1.GATEWAY_URL}`);
753
- console.log(` Kratos: http://localhost:4433 (public) / http://localhost:4434 (admin)`);
754
- console.log(` Keto: http://localhost:4466 (read) / http://localhost:4467 (write)`);
755
- console.log(` Hydra: http://localhost:4444 (public) / http://localhost:4445 (admin)`);
756
- console.log(` Jaeger UI: ${configs_js_1.JAEGER_UI_URL}`);
757
- console.log(` OTLP HTTP: ${configs_js_1.JAEGER_OTLP_HTTP_URL} (set OTEL_EXPORTER_OTLP_ENDPOINT to ship traces)`);
876
+ console.log(` Kratos: ${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KRATOS_PUBLIC_PORT)} (public) / ${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KRATOS_ADMIN_PORT)} (admin)`);
877
+ console.log(` Keto: ${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KETO_READ_PORT)} (read) / ${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KETO_WRITE_PORT)} (write)`);
878
+ console.log(` Hydra: ${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.HYDRA_PUBLIC_PORT)} (public) / ${(0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.HYDRA_ADMIN_PORT)} (admin)`);
758
879
  console.log("");
759
880
  console.log("Run 'local seed' to create test data, or 'local env' to print env vars.");
760
881
  }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Host-facing addressing for the local Ory stack — which ports it publishes on
3
+ * the host, and which host name everything else should use to reach it.
4
+ *
5
+ * Two kinds of port live in this stack, and conflating them is the bug this
6
+ * module exists to prevent:
7
+ *
8
+ * - **Container-internal ports** are what each service listens on *inside* the
9
+ * compose network, and what one service uses to address another
10
+ * (`http://kratos:4433`). They live on a private bridge network, so they can
11
+ * never collide with anything on the host and there is no reason to make them
12
+ * configurable. They are fixed constants below.
13
+ * - **Host ports** are the left-hand side of a compose `ports:` mapping — the
14
+ * only ports that are actually claimed on the developer's machine, and so the
15
+ * only ones that can collide. Every one of them is overridable.
16
+ *
17
+ * The defaults are the stock upstream Ory ports (4433/4434, 4444/4445,
18
+ * 4466/4467, …), which is deliberate — they are the ports every Ory doc and
19
+ * tutorial uses. It also means a developer who already runs Ory locally, which
20
+ * is precisely this stack's audience, collides on first run. Before #63 there
21
+ * was no override and no diagnosis: `docker compose up` failed with Docker's
22
+ * raw "port is already allocated" and the only apparent fix was to hand-edit
23
+ * the generated compose file, which the next `local up` regenerates away.
24
+ *
25
+ * Three levels of control, cheapest first:
26
+ *
27
+ * 1. `ORY_LOCAL_PORT_OFFSET=100` shifts *every* default by a constant, which is
28
+ * the common case ("something else owns the Ory ports, move the whole
29
+ * stack"). Relative spacing is preserved, so the set stays collision-free
30
+ * with itself.
31
+ * 2. `ORY_LOCAL_<SERVICE>_PORT` pins one service exactly, and wins over the
32
+ * offset.
33
+ * 3. `ORY_LOCAL_HOST` changes the host every generated URL advertises, for a
34
+ * remote Docker daemon or an agent running inside a container. It defaults
35
+ * to the host parsed out of `DOCKER_HOST` when that points somewhere other
36
+ * than this machine, so the remote-daemon case usually needs no flag at all.
37
+ */
38
+ export declare const KRATOS_PUBLIC_CONTAINER_PORT = 4433;
39
+ export declare const KRATOS_ADMIN_CONTAINER_PORT = 4434;
40
+ export declare const KETO_READ_CONTAINER_PORT = 4466;
41
+ export declare const KETO_WRITE_CONTAINER_PORT = 4467;
42
+ export declare const HYDRA_PUBLIC_CONTAINER_PORT = 4444;
43
+ export declare const HYDRA_ADMIN_CONTAINER_PORT = 4445;
44
+ export declare const HYDRA_TOKEN_USER_CONTAINER_PORT = 5555;
45
+ export declare const GATEWAY_CONTAINER_PORT = 4000;
46
+ export declare const LOGIN_UI_CONTAINER_PORT = 4455;
47
+ export declare const OATHKEEPER_PROXY_CONTAINER_PORT = 4456;
48
+ export declare const OATHKEEPER_API_CONTAINER_PORT = 4457;
49
+ /** Console Lite's Dockerfile sets `ENV PORT=3000`. */
50
+ export declare const CONSOLE_CONTAINER_PORT = 3000;
51
+ /** Every host port the stack publishes, in the order `local status` lists them. */
52
+ export declare const LOCAL_PORT_NAMES: readonly ["gateway", "console", "loginUi", "kratosPublic", "kratosAdmin", "ketoRead", "ketoWrite", "hydraPublic", "hydraAdmin", "hydraTokenUser", "oathkeeperProxy", "oathkeeperApi"];
53
+ export type LocalPortName = (typeof LOCAL_PORT_NAMES)[number];
54
+ export type ResolvedLocalPorts = Record<LocalPortName, number>;
55
+ interface PortSpec {
56
+ /** Default host port — the stock Ory port, and the container port it maps to. */
57
+ default: number;
58
+ /** Env var that pins this port exactly. */
59
+ envVar: string;
60
+ /** Human label used in conflict messages and banners. */
61
+ label: string;
62
+ }
63
+ /**
64
+ * The port table. `default` doubles as the container-internal port for every
65
+ * service, which is what keeps the generated mapping readable
66
+ * (`"4433:4433"` by default, `"5433:4433"` once overridden).
67
+ */
68
+ export declare const LOCAL_PORT_SPECS: Record<LocalPortName, PortSpec>;
69
+ /** Shifts every default port by a constant. Overridden per-port by the specific var. */
70
+ export declare const PORT_OFFSET_ENV_VAR = "ORY_LOCAL_PORT_OFFSET";
71
+ /** Overrides the host name every generated URL advertises. */
72
+ export declare const LOCAL_HOST_ENV_VAR = "ORY_LOCAL_HOST";
73
+ /**
74
+ * Skips every port probe — the pre-start preflight *and* the post-failure
75
+ * diagnosis.
76
+ *
77
+ * Both open real sockets, which is the right thing before starting containers
78
+ * and the wrong thing in a unit test (the core suite sets this so no test
79
+ * depends on which ports happen to be free on the machine running it) or in a
80
+ * sandbox that forbids listening. The sandbox case is why this covers the
81
+ * diagnosis too: where binding is denied, `isPortAvailable` answers "taken" for
82
+ * every port, so an unrelated startup failure would otherwise be reported as a
83
+ * confident and completely wrong "every port is in use".
84
+ */
85
+ export declare const SKIP_PORT_CHECK_ENV_VAR = "ORY_LOCAL_SKIP_PORT_CHECK";
86
+ export declare function portCheckDisabled(env?: Env): boolean;
87
+ type Env = Record<string, string | undefined>;
88
+ /**
89
+ * Resolve every host port from the environment.
90
+ *
91
+ * Invalid values are **ignored with a warning** rather than throwing: this runs
92
+ * on the way into `local up`, and refusing to start because one env var has a
93
+ * typo would be a worse outcome than starting on the documented default and
94
+ * saying so. Warnings are returned rather than printed so the caller controls
95
+ * the output stream.
96
+ */
97
+ export declare function resolveLocalPorts(env?: Env): {
98
+ ports: ResolvedLocalPorts;
99
+ warnings: string[];
100
+ };
101
+ export type LocalHostSource = "env" | "docker-host" | "default";
102
+ export declare const DEFAULT_LOCAL_HOST = "localhost";
103
+ /**
104
+ * Pull the host out of a `DOCKER_HOST` value.
105
+ *
106
+ * `unix://` and `npipe://` sockets are this machine, so they yield nothing and
107
+ * the default stands. A `tcp://` or `ssh://` daemon publishes the stack's ports
108
+ * on *its* host, not ours, so that host is what generated URLs have to
109
+ * advertise — otherwise every URL we print and persist points at a machine that
110
+ * isn't running the stack.
111
+ */
112
+ export declare function hostFromDockerHost(dockerHost: string | undefined): string | undefined;
113
+ export declare function isLoopbackHost(host: string): boolean;
114
+ /**
115
+ * The host name every generated URL should advertise: config files, banners,
116
+ * the health probe, and the `projectUrl` that `local configure` persists.
117
+ */
118
+ export declare function resolveLocalHost(env?: Env): {
119
+ host: string;
120
+ source: LocalHostSource;
121
+ };
122
+ /**
123
+ * True when the Docker daemon publishes ports on *this* machine, which is the
124
+ * only case where probing a local socket says anything about whether the stack
125
+ * will be able to bind. With a remote daemon the probe would test the wrong
126
+ * host, so callers skip the preflight entirely rather than report a conflict
127
+ * that isn't there (or miss one that is).
128
+ */
129
+ export declare function isLocalDockerDaemon(env?: Env): boolean;
130
+ /** Build an `http://host:port` origin, bracketing bare IPv6 literals. */
131
+ export declare function hostUrl(host: string, port: number): string;
132
+ export interface PortConflict {
133
+ name: LocalPortName;
134
+ port: number;
135
+ label: string;
136
+ envVar: string;
137
+ }
138
+ /**
139
+ * Can we bind this port? Docker publishes on all interfaces, so we test the
140
+ * same thing it will do. A port held on loopback alone still fails a
141
+ * wildcard bind, so this catches both.
142
+ *
143
+ * Anything other than a clean listen counts as unavailable: `EADDRINUSE` is the
144
+ * case we are looking for, and `EACCES` (a privileged port without rights)
145
+ * would fail the container just the same.
146
+ */
147
+ export declare function isPortAvailable(port: number, timeoutMs?: number): Promise<boolean>;
148
+ /**
149
+ * Which of the stack's host ports are already taken. Probed concurrently —
150
+ * twelve sequential binds would add noticeable latency to every `local up`.
151
+ */
152
+ export declare function findPortConflicts(ports: ResolvedLocalPorts): Promise<PortConflict[]>;
153
+ /**
154
+ * The message a user gets instead of Docker's "port is already allocated":
155
+ * which service wanted which port, and the two ways to move it.
156
+ */
157
+ export declare function describePortConflicts(conflicts: PortConflict[]): string;
158
+ export {};