@agenthippo-ai/agenthippo 0.1.1 → 1.110.9

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 (158) hide show
  1. package/out/agentpack/index.js +2 -1
  2. package/out/agentpack/paths.js +23 -0
  3. package/out/analytics/dashboard/fleetDisplayUtils.js +3 -0
  4. package/out/analytics/dashboard/provider.js +13 -2
  5. package/out/analytics/dashboard/webview/panel.js +293 -82
  6. package/out/analytics/identity.js +0 -0
  7. package/out/analytics/service.js +23 -25
  8. package/out/analytics/sessionEvents.js +109 -22
  9. package/out/auth/authBaseUrl.js +59 -0
  10. package/out/auth/bootstrapApply.js +9 -10
  11. package/out/auth/brokerClient.js +49 -15
  12. package/out/auth/onboardingPrompt.js +112 -0
  13. package/out/auth/provider.js +9 -22
  14. package/out/auth/sessionStore.js +3 -40
  15. package/out/auth/signInFlow.js +2 -1
  16. package/out/auth-broker/config.js +54 -4
  17. package/out/auth-broker/server.js +152 -20
  18. package/out/auth-broker/store.js +25 -24
  19. package/out/authority/warrant.js +206 -0
  20. package/out/branding.js +27 -1
  21. package/out/cli/commands/ask.js +3 -0
  22. package/out/cli/commands/chat.js +3 -0
  23. package/out/cli/commands/conformance.js +249 -0
  24. package/out/cli/commands/data-plane.js +67 -0
  25. package/out/cli/commands/deploy.js +50 -10
  26. package/out/cli/commands/gateway.js +58 -0
  27. package/out/cli/commands/idp.js +85 -0
  28. package/out/cli/commands/pack.js +182 -3
  29. package/out/cli/commands/sandbox.js +332 -0
  30. package/out/cli/commands/serve.js +1165 -82
  31. package/out/cli/commands/store.js +12 -4
  32. package/out/cli/credential-context.js +42 -80
  33. package/out/cli/data-plane/broker.js +78 -0
  34. package/out/cli/data-plane/connector-host.js +121 -0
  35. package/out/cli/data-plane/egress.js +189 -0
  36. package/out/cli/data-plane/gateway.js +218 -0
  37. package/out/cli/data-plane/index.js +152 -0
  38. package/out/cli/data-plane/registry.js +89 -0
  39. package/out/cli/data-plane/remote.js +106 -0
  40. package/out/cli/data-plane/sidecar.js +161 -0
  41. package/out/cli/deploy/blueprints/registry.js +84 -0
  42. package/out/cli/deploy/layers/identity.js +172 -0
  43. package/out/cli/deploy-init.js +1182 -347
  44. package/out/cli/deps.js +106 -2
  45. package/out/cli/idp-init.js +338 -0
  46. package/out/cli/index.js +192 -23
  47. package/out/cli/pack-install-ref.js +15 -0
  48. package/out/cli/pack-loader.js +45 -3
  49. package/out/cli/pack-runtime.js +61 -0
  50. package/out/cli/runtime.js +44 -3
  51. package/out/cli/serve-hardening.js +139 -3
  52. package/out/cli/state-sync.js +16 -15
  53. package/out/cli/utils.js +12 -1
  54. package/out/engines/base.js +32 -3
  55. package/out/engines/claude.js +47 -8
  56. package/out/engines/codex.js +125 -22
  57. package/out/engines/custom.js +54 -12
  58. package/out/evidence/sessionShip.js +610 -0
  59. package/out/extension.js +13 -2
  60. package/out/gateway/bridge.js +40 -2
  61. package/out/gateway/client.js +34 -1
  62. package/out/gateway/installSource.js +68 -0
  63. package/out/gateway/lifecycle.js +185 -51
  64. package/out/gateway/outboundPaths.js +4 -1
  65. package/out/gateway/outboundSend.js +6 -6
  66. package/out/gateway/secretsPreflight.js +90 -0
  67. package/out/gateway/service.js +7 -2
  68. package/out/headless/index.js +40 -9
  69. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  70. package/out/live-probe/connector.mjs +8 -0
  71. package/out/live-probe/env-report.mjs +8 -0
  72. package/out/mcp.js +115 -67
  73. package/out/pack/caps.js +271 -0
  74. package/out/pack/governor.js +309 -0
  75. package/out/pack/lease-schema.js +270 -0
  76. package/out/pack/lease.js +640 -0
  77. package/out/pack/reconcile.js +217 -0
  78. package/out/pack/signature.js +839 -0
  79. package/out/remote/handleRemoteChat.js +44 -3
  80. package/out/remote/remoteTrust.js +277 -0
  81. package/out/remote/serveClient.js +52 -5
  82. package/out/runner/host.js +131 -0
  83. package/out/runner/protocol.js +108 -0
  84. package/out/runner/remoteEngine.js +130 -0
  85. package/out/runtime/interfaces/edit-tracker.js +2 -0
  86. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  87. package/out/sandbox/containerDriver.js +570 -0
  88. package/out/sandbox/processDriver.js +315 -0
  89. package/out/sandbox/sandboxRunner.js +191 -0
  90. package/out/sandbox/support.js +301 -0
  91. package/out/sandbox/types.js +55 -0
  92. package/out/scripts/agentide-litellm.sh +67 -5
  93. package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
  94. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -118
  95. package/out/scripts/analytics/setup-analytics.ps1 +37 -13
  96. package/out/scripts/analytics/setup-analytics.sh +93 -13
  97. package/out/scripts/analytics/start-analytics.sh +86 -14
  98. package/out/scripts/gateway/setup-gateway.sh +72 -32
  99. package/out/shared/models.js +12 -111
  100. package/out/shared/providers.js +2 -2
  101. package/out/spotlight/install.js +7 -1
  102. package/out/store/index.js +41 -10
  103. package/out/sync.js +3 -1
  104. package/out/test/analytics.test.js +198 -34
  105. package/out/test/auth-baseurl.test.js +132 -0
  106. package/out/test/auth-bootstrapApply.test.js +27 -6
  107. package/out/test/auth-broker-hardening.test.js +227 -0
  108. package/out/test/auth-broker.test.js +112 -9
  109. package/out/test/blueprint-registry.test.js +105 -0
  110. package/out/test/branding.test.js +13 -0
  111. package/out/test/caps-governor.test.js +455 -0
  112. package/out/test/codex-new-file-survival.test.js +119 -0
  113. package/out/test/configuration-scopes.test.js +131 -0
  114. package/out/test/credential-context.test.js +23 -28
  115. package/out/test/custom-env-layering.test.js +151 -0
  116. package/out/test/data-plane-engine-wiring.test.js +250 -0
  117. package/out/test/data-plane.test.js +311 -0
  118. package/out/test/deploy-init.test.js +295 -4
  119. package/out/test/egress-broker.test.js +172 -0
  120. package/out/test/fleetDisplayUtils.test.js +4 -0
  121. package/out/test/gateway-client.test.js +28 -0
  122. package/out/test/gateway-install-source.test.js +111 -0
  123. package/out/test/gateway-lifecycle.test.js +1 -1
  124. package/out/test/gateway-outbound.test.js +7 -0
  125. package/out/test/gateway-secrets-preflight.test.js +127 -0
  126. package/out/test/gateway-session-routing.test.js +18 -0
  127. package/out/test/governor-hardening.test.js +217 -0
  128. package/out/test/headless-sync.test.js +22 -0
  129. package/out/test/idp-init.test.js +151 -0
  130. package/out/test/lease-signature.test.js +357 -0
  131. package/out/test/lease-trust-root.test.js +273 -0
  132. package/out/test/lease-validation.test.js +169 -0
  133. package/out/test/lease.test.js +114 -0
  134. package/out/test/mcp-obo.test.js +52 -27
  135. package/out/test/mcp.test.js +69 -3
  136. package/out/test/model-routing.test.js +133 -0
  137. package/out/test/pack-admission.test.js +216 -0
  138. package/out/test/pack-digest-parity.test.js +140 -0
  139. package/out/test/pack-reconcile.test.js +183 -0
  140. package/out/test/pack-signature-artifact.test.js +103 -0
  141. package/out/test/pack-signature.test.js +176 -0
  142. package/out/test/pack-trust-root.test.js +202 -0
  143. package/out/test/remote-trust.test.js +242 -0
  144. package/out/test/runner-protocol.test.js +249 -0
  145. package/out/test/sandbox-driver.test.js +553 -0
  146. package/out/test/sandbox-hardening.test.js +395 -0
  147. package/out/test/sandbox-runner.test.js +343 -0
  148. package/out/test/serve-gateway-turn.test.js +267 -0
  149. package/out/test/serve-hardening.test.js +125 -6
  150. package/out/test/serveClient.test.js +38 -5
  151. package/out/test/sessionShip.test.js +560 -0
  152. package/out/test/spotlightPublish.test.js +28 -0
  153. package/out/test/state-sync-peer.test.js +18 -5
  154. package/out/test/store-install-safe.test.js +69 -0
  155. package/out/test/tls-bypass.test.js +90 -0
  156. package/out/test/warrant.test.js +138 -0
  157. package/out/utils.js +73 -20
  158. package/package.json +1 -1
package/out/cli/deps.js CHANGED
@@ -37,12 +37,14 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.getDepsDir = getDepsDir;
40
+ exports.resolveFromOwnDeps = resolveFromOwnDeps;
40
41
  exports.areDepsInstalled = areDepsInstalled;
41
42
  exports.installDeps = installDeps;
42
43
  exports.isAutoInstallDisabled = isAutoInstallDisabled;
43
44
  exports.ensureDeps = ensureDeps;
44
45
  exports.getClaudeSdkCliPath = getClaudeSdkCliPath;
45
46
  exports.getCodexSdkBinaryPath = getCodexSdkBinaryPath;
47
+ exports.installedEngineVersion = installedEngineVersion;
46
48
  /**
47
49
  * CLI Dependency Management
48
50
  *
@@ -50,6 +52,7 @@ exports.getCodexSdkBinaryPath = getCodexSdkBinaryPath;
50
52
  * This lets npm/bun handle platform-specific binaries automatically.
51
53
  */
52
54
  const fs = __importStar(require("fs"));
55
+ const os = __importStar(require("os"));
53
56
  const path = __importStar(require("path"));
54
57
  const child_process_1 = require("child_process");
55
58
  const utils_1 = require("./utils");
@@ -84,11 +87,60 @@ function resolveEngineDeps() {
84
87
  // Dependencies required for the engines (resolved once at startup).
85
88
  const ENGINE_DEPS = resolveEngineDeps();
86
89
  /**
87
- * Get the deps directory (next to the binary)
90
+ * Where engine SDKs are installed.
91
+ *
92
+ * Normally beside the binary — a self-contained install that survives `$HOME` changes. But
93
+ * `process.execPath` is the *interpreter* when the CLI runs as `node out/cli/index.js`, which
94
+ * on a locked-down host means `/usr/bin`, and the first run dies with
95
+ * `EACCES: mkdir '/usr/bin/.agenthippo-deps'` (observed on Amazon Linux, 2026-07-27). A
96
+ * runtime must not need write access to the system bin dir, so fall back to the agent home.
88
97
  */
89
98
  function getDepsDir() {
90
99
  const binaryDir = path.dirname(process.execPath);
91
- return path.join(binaryDir, branding_1.AGENTIDE_DEPS_DIR_NAME);
100
+ const beside = path.join(binaryDir, branding_1.AGENTIDE_DEPS_DIR_NAME);
101
+ if (fs.existsSync(beside)) {
102
+ return beside;
103
+ }
104
+ try {
105
+ fs.accessSync(binaryDir, fs.constants.W_OK);
106
+ return beside;
107
+ }
108
+ catch {
109
+ // Not writable (system node, read-only image, non-root user): use the agent home.
110
+ return path.join(os.homedir(), branding_1.AGENTIDE_DIR_NAME, branding_1.AGENTIDE_DEPS_DIR_NAME);
111
+ }
112
+ }
113
+ /**
114
+ * Engine SDKs resolved from the CLI's OWN dependency tree.
115
+ *
116
+ * When the runtime ships as a node app (`node out/cli/index.js` beside a `node_modules/`) the
117
+ * SDKs are already installed as ordinary dependencies — there is nothing to fetch, and asking
118
+ * the user to set CLAUDE_CODE_PATH by hand for a dependency we declare ourselves is a
119
+ * papercut that costs a deployment. `require.resolve` finds them wherever the tree lives.
120
+ */
121
+ function resolveFromOwnDeps(pkgName, relativeFile) {
122
+ // A node_modules walk, deliberately, rather than `require.resolve`. Neither SDK can be
123
+ // resolved by specifier: @anthropic-ai/claude-agent-sdk publishes an `exports` map that
124
+ // omits ./cli.js (deep resolve → ERR_PACKAGE_PATH_NOT_EXPORTED) and @openai/codex declares
125
+ // no main/exports at all, only a `bin` (bare resolve → MODULE_NOT_FOUND). Both were
126
+ // verified against the shipped versions on 2026-07-27. Walking for the file we actually
127
+ // want is immune to how a package chooses to advertise itself.
128
+ let dir = __dirname;
129
+ for (let hops = 0; hops < 8; hops++) {
130
+ const candidate = path.join(dir, 'node_modules', ...pkgName.split('/'), ...relativeFile.split('/'));
131
+ try {
132
+ if (fs.existsSync(candidate)) {
133
+ return candidate;
134
+ }
135
+ }
136
+ catch { /* keep walking */ }
137
+ const parent = path.dirname(dir);
138
+ if (parent === dir) {
139
+ break;
140
+ }
141
+ dir = parent;
142
+ }
143
+ return undefined;
92
144
  }
93
145
  /**
94
146
  * Check if engine dependencies are installed
@@ -183,6 +235,14 @@ async function ensureDeps(verbose = false) {
183
235
  if (areDepsInstalled()) {
184
236
  return;
185
237
  }
238
+ // Already satisfied by our own dependency tree (node-app runtime): nothing to install, and
239
+ // attempting it would fail on a read-only or non-writable install dir for no benefit.
240
+ if (resolveFromOwnDeps('@anthropic-ai/claude-agent-sdk', 'cli.js') && resolveFromOwnDeps('@openai/codex', 'bin/codex.js')) {
241
+ if (verbose) {
242
+ console.error(`${utils_1.COLORS.dim}Engine SDKs resolved from the CLI's own node_modules — skipping install.${utils_1.COLORS.reset}`);
243
+ }
244
+ return;
245
+ }
186
246
  if (isAutoInstallDisabled()) {
187
247
  if (verbose) {
188
248
  console.error(`${utils_1.COLORS.dim}AGENTHIPPO_NO_AUTO_INSTALL set — skipping engine dependency install (expecting a self-contained image).${utils_1.COLORS.reset}`);
@@ -236,3 +296,47 @@ function getCodexPlatform() {
236
296
  }
237
297
  return undefined;
238
298
  }
299
+ /**
300
+ * The engine version actually installed next to this binary.
301
+ *
302
+ * Reported on the lease poll so the console can answer "what is the fleet really running?" —
303
+ * pack version alone cannot explain a behaviour change that came from an engine upgrade, and the
304
+ * CLI is NOT engine-version agnostic (see AGENTIDE_CODEX_CONFIG_VERSION: config we write is only
305
+ * valid on codex >= 0.131, and getting it wrong silently cancels headless tool calls).
306
+ *
307
+ * Best effort by design: a deployment that vendors its engines elsewhere reports nothing rather
308
+ * than guessing, and the console shows a dash. Never throws — this is on the boot path.
309
+ *
310
+ * Expect nothing under a CONTAINER sandbox: there the engine runs inside the sandbox image, not
311
+ * beside serve, so serve genuinely does not know its version and the image digest is what
312
+ * identifies that build. Reporting serve's own (absent) copy would be a guess about someone
313
+ * else's filesystem. Process sandboxes and in-process runs share this binary's deps, so those
314
+ * report truthfully.
315
+ */
316
+ function installedEngineVersion(engine) {
317
+ const pkg = engine === 'codex' ? path.join('@openai', 'codex')
318
+ : engine === 'claude' ? path.join('@anthropic-ai', 'claude-agent-sdk')
319
+ : undefined;
320
+ if (!pkg) {
321
+ return undefined;
322
+ }
323
+ const candidates = [
324
+ path.join(getDepsDir(), 'node_modules', pkg, 'package.json'),
325
+ // Runtime images install engines globally rather than beside the binary.
326
+ path.join('/usr/local/lib/node_modules', pkg, 'package.json'),
327
+ path.join('/usr/lib/node_modules', pkg, 'package.json'),
328
+ ];
329
+ for (const candidate of candidates) {
330
+ try {
331
+ if (!fs.existsSync(candidate)) {
332
+ continue;
333
+ }
334
+ const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
335
+ if (parsed.version) {
336
+ return parsed.version;
337
+ }
338
+ }
339
+ catch { /* unreadable or malformed — try the next candidate */ }
340
+ }
341
+ return undefined;
342
+ }
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) AgentHippo.ai. All rights reserved.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderIdpArtifacts = renderIdpArtifacts;
7
+ /**
8
+ * `agenthippo idp init` — generator for the central IdP stack (PLAN/deploy.md, "customer 0").
9
+ *
10
+ * Why this exists: the org IdP used to be static files checked into this source repo
11
+ * (enterprise-deploy/idp/), which meant the actual identity routes could drift from
12
+ * renderIdentityLayer() (fixed once already) AND — the bigger problem — an enterprise
13
+ * customer standing up their own IdP had no way to get any of it at all, since they only
14
+ * receive the compiled `agenthippo` binary, never this repo. Generating it here, from the
15
+ * same shared identity/auth-env/Dockerfile renderers every `deploy init` target already
16
+ * uses, fixes both: it ships in the binary, and it can never drift from what agents expect.
17
+ *
18
+ * AgentHippo's own auth.agenthippo.ai instance is "customer 0" — stood up with this exact
19
+ * command, not a bespoke internal setup.
20
+ */
21
+ const identity_1 = require("./deploy/layers/identity");
22
+ const deploy_init_1 = require("./deploy-init");
23
+ function resolveCookieDomain(ctx) {
24
+ if (ctx.cookieDomain?.trim()) {
25
+ return ctx.cookieDomain.trim();
26
+ }
27
+ const parts = ctx.hostname.split('.');
28
+ return parts.length > 2 ? `.${parts.slice(-2).join('.')}` : `.${ctx.hostname}`;
29
+ }
30
+ function renderIdpComposeYaml() {
31
+ return [
32
+ '# Central IdP. See PLAN/deploy-runbook.md scenario E/F.',
33
+ '#',
34
+ '# ./run.sh',
35
+ '#',
36
+ '# Env split (matches `agenthippo deploy init`):',
37
+ '# ~/.agent-hippo/.env (+ optional ./.env) — hostname only',
38
+ '# ~/.agent-hippo/.env.auth (+ optional ./.env.auth) — JWT/OAuth; broker + oauth2-proxy ONLY',
39
+ '# Profiles: edge (SSO stack), tunnel (Cloudflare → Caddy)',
40
+ '# Org identity infra only — no agent runs here. Per-agent edges are generated separately',
41
+ '# by `agenthippo deploy init` and deployed on their own host/service.',
42
+ 'services:',
43
+ ' oauth2-proxy:',
44
+ ' profiles: ["edge"]',
45
+ ' image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1',
46
+ ' env_file:',
47
+ ' - path: ${HOME}/.agent-hippo/.env',
48
+ ' required: false',
49
+ ' - path: .env',
50
+ ' required: false',
51
+ ' - path: ${HOME}/.agent-hippo/.env.auth',
52
+ ' required: false',
53
+ ' - path: .env.auth',
54
+ ' required: false',
55
+ ' environment:',
56
+ ' OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180',
57
+ ' OAUTH2_PROXY_UPSTREAMS: file:///dev/null',
58
+ ' OAUTH2_PROXY_SET_XAUTHREQUEST: "true"',
59
+ ' OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER: "true"',
60
+ ' OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"',
61
+ ' OAUTH2_PROXY_REVERSE_PROXY: "true"',
62
+ ' expose: ["4180"]',
63
+ '',
64
+ ' auth-broker:',
65
+ ' profiles: ["edge"]',
66
+ ' build:',
67
+ ' context: .',
68
+ ' dockerfile: Dockerfile.auth-broker',
69
+ ' env_file:',
70
+ ' - path: ${HOME}/.agent-hippo/.env',
71
+ ' required: false',
72
+ ' - path: .env',
73
+ ' required: false',
74
+ ' - path: ${HOME}/.agent-hippo/.env.auth',
75
+ ' required: false',
76
+ ' - path: .env.auth',
77
+ ' required: false',
78
+ ' environment:',
79
+ ' AGENTHIPPO_AUTH_PORT: "9780"',
80
+ ' AGENTHIPPO_AUTH_DATA_DIR: /data',
81
+ ' # The broker now defaults to binding 127.0.0.1 and REFUSES a public bind unless told',
82
+ ' # otherwise. Here it must listen on the container interface, because Caddy reaches it',
83
+ ' # as `auth-broker:9780` over the compose network — a loopback bind makes it unreachable.',
84
+ ' # Safe: the port is `expose`d, never published to the host, so the only thing that can',
85
+ ' # dial it is another service on this network.',
86
+ ' AGENTHIPPO_AUTH_BIND: "0.0.0.0"',
87
+ ' AGENTHIPPO_AUTH_ALLOW_PUBLIC_BIND: "1"',
88
+ ' # Shared secret proving an X-Auth-Request-Email came from THIS Caddy. Without it the',
89
+ ' # broker treats that header as unverified and 401s the forward_auth-gated routes.',
90
+ ' # No default on purpose: compose FAILS to start rather than silently running with an',
91
+ ' # empty secret on both sides, which would accept every caller. Put it in ./.env.',
92
+ ' AGENTHIPPO_AUTH_PROXY_SECRET: "${AGENTHIPPO_AUTH_PROXY_SECRET:?set AGENTHIPPO_AUTH_PROXY_SECRET in .env (openssl rand -hex 32)}"',
93
+ ' # Proxy hops in front of the broker: 1 = Caddy only (edge profile), 2 = Cloudflare',
94
+ ' # Tunnel → Caddy (run.sh sets 2; at 1 the rate limiter would key every tunnel',
95
+ ' # client on cloudflared\'s IP — one shared bucket).',
96
+ ' AGENTHIPPO_XFF_TRUSTED_HOPS: ${AGENTHIPPO_XFF_TRUSTED_HOPS:-1}',
97
+ ' volumes: ["auth-broker-data:/data"]',
98
+ ' expose: ["9780"]',
99
+ '',
100
+ ' caddy:',
101
+ ' profiles: ["edge"]',
102
+ ' image: caddy:2.8-alpine',
103
+ ' env_file:',
104
+ ' - path: ${HOME}/.agent-hippo/.env',
105
+ ' required: false',
106
+ ' - path: .env',
107
+ ' required: false',
108
+ ' # With the tunnel profile, set AGENTHIPPO_CADDY_BIND=127.0.0.1 so a public-IP host',
109
+ ' # cannot be hit directly on 80/443, bypassing Cloudflare.',
110
+ ' # Bare-metal / BYO nginx or ALB already on 80/443? Set AGENTHIPPO_CADDY_HTTP_PORT /',
111
+ ' # AGENTHIPPO_CADDY_HTTPS_PORT to alternate host ports and point your own reverse proxy',
112
+ ' # at them — the container still listens on 80 internally, only the host mapping moves.',
113
+ ' ports:',
114
+ ' - "${AGENTHIPPO_CADDY_BIND:-0.0.0.0}:${AGENTHIPPO_CADDY_HTTP_PORT:-80}:80"',
115
+ ' - "${AGENTHIPPO_CADDY_BIND:-0.0.0.0}:${AGENTHIPPO_CADDY_HTTPS_PORT:-443}:443"',
116
+ ' environment:',
117
+ ' # Caddy stamps this on the two broker hops that forward a verified X-Auth-Request-Email',
118
+ ' # (/oauth/authorize, /oauth/browser-login). Same value the broker checks; same fail-closed',
119
+ ' # rule — compose refuses to start rather than send an empty secret.',
120
+ ' AGENTHIPPO_AUTH_PROXY_SECRET: "${AGENTHIPPO_AUTH_PROXY_SECRET:?set AGENTHIPPO_AUTH_PROXY_SECRET in .env (openssl rand -hex 32)}"',
121
+ ' volumes:',
122
+ ' - ./Caddyfile:/etc/caddy/Caddyfile:ro',
123
+ ' # Colocated agents (deploy init --colocate) drop a route snippet here — see Caddyfile.',
124
+ ' - ./agents:/etc/caddy/agents:ro',
125
+ ' networks: [default, colocate]',
126
+ ' depends_on: [oauth2-proxy, auth-broker]',
127
+ '',
128
+ ' cloudflared:',
129
+ ' profiles: ["tunnel"]',
130
+ ' image: cloudflare/cloudflared:2024.12.2',
131
+ ' restart: unless-stopped',
132
+ ' command: tunnel --config /etc/cloudflared/config.yml run',
133
+ ' volumes:',
134
+ ' - ./cloudflared.yml:/etc/cloudflared/config.yml:ro',
135
+ ' # Set by run.sh from its argument (./run.sh /path/to/tunnel-credentials.json);',
136
+ ' # falls back to the conventional location for a direct `docker compose` invocation.',
137
+ ' - ${AGENTHIPPO_TUNNEL_CREDENTIALS:-${HOME}/.agent-hippo/tunnel-credentials.json}:/etc/cloudflared/credentials.json:ro',
138
+ ' depends_on: [caddy]',
139
+ '',
140
+ 'volumes:',
141
+ ' auth-broker-data:',
142
+ '',
143
+ 'networks:',
144
+ ' default:',
145
+ ' # Stable, explicitly-named network (not the implicit "idp_default") so a colocated agent\'s',
146
+ ' # own compose project can join it reliably regardless of which directory `docker compose`',
147
+ ' # runs from. Only Caddy needs it — its per-agent snippets `reverse_proxy` straight to the',
148
+ ' # agent container by name; auth-broker is reached over the (already-shared) default network.',
149
+ ' colocate:',
150
+ ' name: agenthippo-idp-colocate',
151
+ '',
152
+ ].join('\n');
153
+ }
154
+ function renderIdpCaddyfile() {
155
+ const identityLines = (0, identity_1.renderIdentityLayer)({
156
+ mode: 'self-issued',
157
+ oauth2ProxyAddr: 'oauth2-proxy:4180',
158
+ authBrokerAddr: 'auth-broker:9780',
159
+ healthProxy: [' reverse_proxy auth-broker:9780'],
160
+ // No agent behind the IdP: a Bearer request to /v1/* doesn't exist here (404, not a
161
+ // silent pass-through), and an unauthenticated browser sees a plain confirmation once
162
+ // past the SSO gate.
163
+ agentProxy: [' respond "not_found" 404'],
164
+ catchallBody: [' respond "AgentHippo IdP" 200'],
165
+ });
166
+ return [
167
+ '# SSO edge + Cloudflare Tunnel. Set AGENTHIPPO_PUBLIC_HOSTNAME in .env (Caddy reads it below).',
168
+ '# TLS terminates at Cloudflare; origin listens on HTTP only.',
169
+ '{',
170
+ ' auto_https off',
171
+ '}',
172
+ '',
173
+ '# Same-host colocation: agents deployed with `deploy init --ingress edge-fed --colocate`',
174
+ '# on THIS host ship a route snippet instead of their own Caddy/oauth2-proxy/auth-broker.',
175
+ '# Drop it in ./agents/<name>.caddy (mounted in docker-compose.yml) and',
176
+ '# `docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile` to pick it up —',
177
+ '# no rebuild, no restart of the IdP itself.',
178
+ 'import agents/*.caddy',
179
+ '',
180
+ `http://{$AGENTHIPPO_PUBLIC_HOSTNAME} {`,
181
+ ...identityLines,
182
+ '}',
183
+ '',
184
+ ].join('\n');
185
+ }
186
+ /** Render every artifact for `agenthippo idp init` (relative path → file content). Pure: no fs writes. */
187
+ function renderIdpArtifacts(ctx) {
188
+ const cookieDomain = resolveCookieDomain(ctx);
189
+ return {
190
+ 'docker-compose.yml': renderIdpComposeYaml(),
191
+ 'Dockerfile.auth-broker': (0, deploy_init_1.renderAuthBrokerDockerfile)(),
192
+ 'Caddyfile': renderIdpCaddyfile(),
193
+ '.env.example': [
194
+ '# Caddy hostname for the central IdP. No agent runs on this stack — auth secrets:',
195
+ '# see .env.auth.example → ~/.agent-hippo/.env.auth (broker + oauth2-proxy only).',
196
+ '',
197
+ `AGENTHIPPO_PUBLIC_HOSTNAME=${ctx.hostname}`,
198
+ '',
199
+ '# REQUIRED. Shared secret between Caddy and auth-broker, proving that an',
200
+ '# X-Auth-Request-Email reached the broker through this Caddy rather than from a client',
201
+ '# that merely found the port. The broker 401s that header without a matching secret, and',
202
+ '# docker compose refuses to start if this is unset — an empty secret on both sides would',
203
+ '# accept everyone. It lives HERE (not .env.auth) because Caddy needs it too, and Caddy',
204
+ '# must not be handed the OAuth client secret or the JWT signing key.',
205
+ '# openssl rand -hex 32',
206
+ 'AGENTHIPPO_AUTH_PROXY_SECRET=',
207
+ '',
208
+ '# Proxy hops / Caddy bind (optional; run.sh sets XFF hops=2 for tunnel)',
209
+ '# AGENTHIPPO_XFF_TRUSTED_HOPS=1',
210
+ '# AGENTHIPPO_CADDY_BIND=0.0.0.0',
211
+ '',
212
+ '# Bare metal / BYO nginx or ALB already on 80/443? Rebind Caddy to alternate host ports',
213
+ '# and point your reverse proxy at them (container still listens on 80 internally):',
214
+ '# AGENTHIPPO_CADDY_HTTP_PORT=8080',
215
+ '# AGENTHIPPO_CADDY_HTTPS_PORT=8443',
216
+ '',
217
+ ].join('\n'),
218
+ '.env.auth.example': (0, deploy_init_1.renderAuthEnvExample)({ hostname: ctx.hostname, fed: false, cookieDomain }),
219
+ 'cloudflared.yml.example': [
220
+ '# Copy to cloudflared.yml; the credentials JSON lives in ~/.agent-hippo/ (never in the repo).',
221
+ '# cloudflared tunnel create agenthippo-auth',
222
+ '# cp ~/.cloudflared/<tunnel-id>.json ~/.agent-hippo/tunnel-credentials.json',
223
+ `# cloudflared tunnel route dns agenthippo-auth ${ctx.hostname}`,
224
+ 'tunnel: REPLACE_WITH_TUNNEL_ID',
225
+ 'credentials-file: /etc/cloudflared/credentials.json',
226
+ 'ingress:',
227
+ ` - hostname: ${ctx.hostname}`,
228
+ ' service: http://caddy:80',
229
+ ' - service: http_status:404',
230
+ '',
231
+ ].join('\n'),
232
+ 'run.sh': [
233
+ '#!/usr/bin/env bash',
234
+ '# Start central IdP: Caddy + oauth2-proxy + auth-broker (+ Cloudflare tunnel, if requested).',
235
+ '# No agent here — per-agent edges are generated separately by `agenthippo deploy init`.',
236
+ '#',
237
+ '# Usage:',
238
+ '# ./run.sh bare metal / BYO nginx or ALB in front',
239
+ '# ./run.sh /path/to/tunnel-credentials.json Cloudflare Tunnel',
240
+ '#',
241
+ '# Bare metal / BYO nginx or ALB: run with no argument — starts --profile edge only.',
242
+ '# If 80/443 are already taken by your reverse proxy, set AGENTHIPPO_CADDY_HTTP_PORT/',
243
+ '# _HTTPS_PORT in .env first so Caddy rebinds elsewhere.',
244
+ 'set -euo pipefail',
245
+ 'cd "$(dirname "$0")"',
246
+ 'if [[ $# -ge 1 ]]; then',
247
+ ' export AGENTHIPPO_TUNNEL_CREDENTIALS="$1"',
248
+ ' [[ -f "$AGENTHIPPO_TUNNEL_CREDENTIALS" ]] || {',
249
+ ' echo "Tunnel credentials file not found: $AGENTHIPPO_TUNNEL_CREDENTIALS" >&2; exit 1; }',
250
+ ' # Two proxy hops behind the tunnel (cloudflared → Caddy) — see docker-compose.yml.',
251
+ ' export AGENTHIPPO_XFF_TRUSTED_HOPS="${AGENTHIPPO_XFF_TRUSTED_HOPS:-2}"',
252
+ ' docker compose --profile edge --profile tunnel up -d --build',
253
+ ` echo "IdP: https://\${AGENTHIPPO_PUBLIC_HOSTNAME:-${ctx.hostname}} (via Cloudflare Tunnel)"`,
254
+ 'else',
255
+ ' echo "No tunnel credentials path given — starting --profile edge only (no tunnel)."',
256
+ ' echo "Front this host with your own nginx/ALB; see AGENTHIPPO_CADDY_HTTP_PORT/AGENTHIPPO_CADDY_BIND in .env."',
257
+ ' echo "(To use a Cloudflare Tunnel instead: ./run.sh /path/to/tunnel-credentials.json)"',
258
+ ' docker compose --profile edge up -d --build',
259
+ ` echo "IdP: http://\${AGENTHIPPO_PUBLIC_HOSTNAME:-${ctx.hostname}} (behind your own reverse proxy)"`,
260
+ 'fi',
261
+ '',
262
+ ].join('\n'),
263
+ 'agents/README.md': [
264
+ '# Colocated agent routes',
265
+ '',
266
+ 'Drop `*.caddy` route snippets here for agents deployed on **this same host** via:',
267
+ '',
268
+ '```bash',
269
+ 'agenthippo deploy init <pack> --target compose --ingress edge-fed --colocate',
270
+ '```',
271
+ '',
272
+ 'That command generates `deploy/agent-route.caddy` (copy it here, renamed to something',
273
+ 'identifying the agent) plus a `docker-compose.yml` containing only the `agent` service',
274
+ '(no Caddy/oauth2-proxy/auth-broker of its own — it joins this IdP\'s',
275
+ '`agenthippo-idp-colocate` network and rides the IdP\'s existing edge).',
276
+ '',
277
+ 'After copying a new snippet in:',
278
+ '',
279
+ '```bash',
280
+ 'docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile',
281
+ '```',
282
+ '',
283
+ 'No rebuild, no restart of the IdP itself. See `../Caddyfile`\'s `import agents/*.caddy` and',
284
+ '`PLAN/deploy-runbook.md` scenario F for the full picture.',
285
+ '',
286
+ ].join('\n'),
287
+ 'README.md': [
288
+ '# Central IdP',
289
+ '',
290
+ '**One per organization.** Hosts Google SSO (oauth2-proxy) and the IDE auth-broker only —',
291
+ 'no agent runs here. Agent edges are generated and deployed separately.',
292
+ '',
293
+ 'Generated by `agenthippo idp init` — regenerate (don\'t hand-edit `Caddyfile`, which comes',
294
+ 'straight from the same identity module every agent\'s Caddyfile uses) if you need to change',
295
+ 'routing: that lives in the CLI itself, not in this output.',
296
+ '',
297
+ 'This stack runs on **any host with Docker** — local laptop, team VM, cloud VM, or on-prem.',
298
+ '`./run.sh` picks the path below based on whether you pass it a tunnel credentials path.',
299
+ '',
300
+ '## Quick start',
301
+ '',
302
+ '```bash',
303
+ 'cp .env.example ~/.agent-hippo/.env',
304
+ 'cp .env.auth.example ~/.agent-hippo/.env.auth # fill in; never put AGENTHIPPO_AUTH_JWT_SECRET in .env',
305
+ '',
306
+ '# REQUIRED: the Caddy↔broker proxy secret. It must be visible to `docker compose`',
307
+ '# ITSELF (compose interpolates it into both services), so it goes in ./.env here —',
308
+ '# not only in ~/.agent-hippo/.env, which is read by containers but not by compose.',
309
+ 'echo "AGENTHIPPO_AUTH_PROXY_SECRET=$(openssl rand -hex 32)" >> .env',
310
+ '```',
311
+ '',
312
+ 'Without it `docker compose up` fails immediately with a message naming the variable. That',
313
+ 'is deliberate: the broker refuses an `X-Auth-Request-Email` that arrives without a matching',
314
+ '`x-agenthippo-proxy-secret`, so an empty secret on both sides would accept every caller.',
315
+ '',
316
+ '**Cloudflare Tunnel** (no public ports to manage yourself):',
317
+ '',
318
+ '```bash',
319
+ 'cp cloudflared.yml.example cloudflared.yml # set tunnel id',
320
+ 'cloudflared tunnel create agenthippo-auth # writes ~/.cloudflared/<tunnel-id>.json',
321
+ 'cp ~/.cloudflared/<tunnel-id>.json ~/.agent-hippo/tunnel-credentials.json',
322
+ './run.sh ~/.agent-hippo/tunnel-credentials.json',
323
+ '```',
324
+ '',
325
+ '**Bare metal / BYO nginx or ALB already fronting this host:**',
326
+ '',
327
+ '```bash',
328
+ '# skip cloudflared.yml entirely. If 80/443 are already taken by your reverse proxy,',
329
+ '# set AGENTHIPPO_CADDY_HTTP_PORT/AGENTHIPPO_CADDY_HTTPS_PORT in .env first.',
330
+ './run.sh # no argument — starts --profile edge only',
331
+ '# point your nginx/ALB at this host\'s Caddy (AGENTHIPPO_CADDY_BIND / _HTTP_PORT above)',
332
+ '```',
333
+ '',
334
+ 'Agent edges (e.g. `cs.corp.example`) are separate — see `PLAN/deploy-runbook.md` scenarios E/F.',
335
+ '',
336
+ ].join('\n'),
337
+ };
338
+ }