@agenthippo-ai/agenthippo 1.110.9 → 1.110.19

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 (533) hide show
  1. package/out/agentpack/types.js +2 -5
  2. package/out/analytics/dashboard/artifactManager.js +2 -439
  3. package/out/analytics/dashboard/fleetDisplayUtils.js +2 -162
  4. package/out/analytics/dashboard/fleetOrchestrator.js +3 -142
  5. package/out/analytics/dashboard/fleetRegistry.js +2 -79
  6. package/out/analytics/dashboard/fleetRowDefaults.js +2 -87
  7. package/out/analytics/dashboard/fleetScriptAdapter.js +2 -146
  8. package/out/analytics/dashboard/fleetTypes.js +2 -29
  9. package/out/analytics/dashboard/index.js +2 -17
  10. package/out/analytics/dashboard/indexingService.js +2 -422
  11. package/out/analytics/dashboard/localFleetAdapter.js +2 -222
  12. package/out/analytics/dashboard/performanceMonitor.js +5 -76
  13. package/out/analytics/dashboard/processManager.js +2 -209
  14. package/out/analytics/dashboard/provider.js +38 -3817
  15. package/out/analytics/dashboard/queryRunner.js +62 -750
  16. package/out/analytics/dashboard/scheduledFleetStorage.js +2 -241
  17. package/out/analytics/dashboard/sessionJsonlFleet.js +3 -204
  18. package/out/analytics/dashboard/teamViewDiscovery.js +2 -106
  19. package/out/analytics/dashboard/webview/panel.js +34 -2318
  20. package/out/analytics/dashboard/webview/runtime-bridge.js +2 -82
  21. package/out/analytics/lifecycle.js +2 -43
  22. package/out/analytics/service.js +8 -1277
  23. package/out/auth/authBaseUrl.js +2 -59
  24. package/out/auth/bootstrapApply.js +2 -71
  25. package/out/auth/brokerClient.js +2 -160
  26. package/out/auth/callbackUri.js +2 -59
  27. package/out/auth/githubStoreGate.js +2 -70
  28. package/out/auth/loopbackAuthServer.js +4 -159
  29. package/out/auth/onboardingPrompt.js +2 -112
  30. package/out/auth/pkce.js +2 -9
  31. package/out/auth/provider.js +2 -226
  32. package/out/auth/sessionStore.js +2 -156
  33. package/out/auth/signInFlow.js +2 -183
  34. package/out/cli/index.js +619 -551
  35. package/out/cli/run-custom-engine-runner.js +3 -318
  36. package/out/cli/vscode-shim.js +2 -86
  37. package/out/engines/factory.js +2 -45
  38. package/out/engines/types.js +2 -17
  39. package/out/examples/run-headless-repl.js +6 -286
  40. package/out/examples/run-headless.js +2 -187
  41. package/out/extension.js +32 -4473
  42. package/out/gateway/gatewayRouteIndex.js +6 -371
  43. package/out/gateway/gatewaySessionStore.js +3 -243
  44. package/out/gateway/index.js +2 -31
  45. package/out/gateway/resetMappings.js +2 -52
  46. package/out/gateway/service.js +5 -1249
  47. package/out/mcp/publish.js +2 -143
  48. package/out/remote/handleRemoteChat.js +2 -169
  49. package/out/remote/remotePackStatusBar.js +3 -103
  50. package/out/remote/remoteTrust.js +6 -277
  51. package/out/remote/serveClient.js +4 -277
  52. package/out/remoteSkills.js +3 -191
  53. package/out/runtime/index.js +2 -32
  54. package/out/runtime/vscode/index.js +2 -20
  55. package/out/runtime/vscode/vscode-config-provider.js +2 -77
  56. package/out/runtime/vscode/vscode-edit-tracker.js +2 -122
  57. package/out/runtime/vscode/vscode-logger.js +2 -30
  58. package/out/runtime/vscode/vscode-permission-handler.js +2 -72
  59. package/out/scripts/agentide-litellm.sh +0 -1
  60. package/out/scripts/agentide_litellm_hooks_v1.py +170 -418
  61. package/out/scripts/analytics/agent-fleet-widget.js +18 -257
  62. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +4 -4
  63. package/out/scripts/analytics/duckdbLocator.js +2 -28
  64. package/out/scripts/analytics/generate-default-charts.py +43 -323
  65. package/out/scripts/analytics/hotspot-analysis-widget.js +4 -430
  66. package/out/scripts/analytics/index-traces.py +78 -1036
  67. package/out/scripts/analytics/session-comparison-widget.js +26 -472
  68. package/out/scripts/analytics/session-timeline-widget.js +25 -846
  69. package/out/scripts/analytics/setup-analytics.ps1 +8 -1
  70. package/out/scripts/analytics/setup-analytics.sh +13 -2
  71. package/out/scripts/analytics/token-cost-flamegraph-widget.js +4 -461
  72. package/out/scripts/analytics/token-cost-trend-widget.js +4 -534
  73. package/out/scripts/analytics/workflows-collector.cjs +4 -314
  74. package/out/scripts/analytics/workflows-daemon.mjs +9 -185
  75. package/out/scripts/analytics/workflows-widget.cjs +15 -159
  76. package/out/scripts/analytics/wow-model-widget.js +10 -120
  77. package/out/scripts/vendor-tools.mjs +2 -233
  78. package/out/skills/publish.js +3 -178
  79. package/out/store/github/setup.js +7 -212
  80. package/out/store/oramaIndex.js +2 -884
  81. package/out/sync.js +26 -1829
  82. package/out/terminalLogger.js +5 -358
  83. package/out/utils/pathSafety.js +2 -60
  84. package/out/utils/pythonRunner.js +4 -258
  85. package/out/utils/toolPath.js +2 -83
  86. package/out/workspaceFolders.js +2 -59
  87. package/package.json +1 -1
  88. package/out/agentpack/github.js +0 -547
  89. package/out/agentpack/index.js +0 -67
  90. package/out/agentpack/install.js +0 -297
  91. package/out/agentpack/manager.js +0 -441
  92. package/out/agentpack/manifest.js +0 -135
  93. package/out/agentpack/paths.js +0 -498
  94. package/out/agentpack/publish.js +0 -248
  95. package/out/agentpack/stores.js +0 -554
  96. package/out/analytics/dashboard/fleetStatusUtils.js +0 -30
  97. package/out/analytics/dashboard/fleetTitleUtils.js +0 -68
  98. package/out/analytics/dashboard/refreshPolicy.js +0 -16
  99. package/out/analytics/dashboard/scheduledFleetFingerprint.js +0 -22
  100. package/out/analytics/dashboard/scheduledFleetUtils.js +0 -22
  101. package/out/analytics/dashboard/sessionEventFleetStats.js +0 -118
  102. package/out/analytics/dashboard/viewsConfigMerge.js +0 -221
  103. package/out/analytics/dashboard/webview/codicon.ttf +0 -0
  104. package/out/analytics/identity.js +0 -0
  105. package/out/analytics/sessionEvents.js +0 -323
  106. package/out/auth/callbackUriParse.js +0 -59
  107. package/out/auth/types.js +0 -12
  108. package/out/auth-broker/config.js +0 -112
  109. package/out/auth-broker/jwt.js +0 -105
  110. package/out/auth-broker/pkce.js +0 -59
  111. package/out/auth-broker/server.js +0 -677
  112. package/out/auth-broker/store.js +0 -275
  113. package/out/authority/warrant.js +0 -206
  114. package/out/base-prompts/mcp-servers/.mcp.json +0 -13
  115. package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +0 -142
  116. package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +0 -13
  117. package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +0 -1192
  118. package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +0 -23
  119. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +0 -12
  120. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +0 -102
  121. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +0 -137
  122. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +0 -118
  123. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +0 -209
  124. package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +0 -431
  125. package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +0 -270
  126. package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +0 -18
  127. package/out/base-prompts/rules/AGENTS.md +0 -204
  128. package/out/base-prompts/skills/agent-browser/SKILL.md +0 -102
  129. package/out/base-prompts/skills/agent-builder/SKILL.md +0 -73
  130. package/out/base-prompts/skills/agent-builder/references/agent-pack.md +0 -63
  131. package/out/base-prompts/skills/agent-builder/references/crewai.md +0 -66
  132. package/out/base-prompts/skills/agent-builder/references/langchain.md +0 -60
  133. package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +0 -73
  134. package/out/base-prompts/skills/agent-engine-creator/SKILL.md +0 -586
  135. package/out/base-prompts/skills/agent-engine-installer/SKILL.md +0 -106
  136. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +0 -259
  137. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +0 -189
  138. package/out/base-prompts/skills/agenthippo-cli/SKILL.md +0 -69
  139. package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +0 -63
  140. package/out/base-prompts/skills/agentpack-creator/SKILL.md +0 -375
  141. package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +0 -261
  142. package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +0 -195
  143. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +0 -391
  144. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +0 -294
  145. package/out/base-prompts/skills/agentpack-testbench/SKILL.md +0 -135
  146. package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +0 -202
  147. package/out/base-prompts/skills/algorithmic-art/SKILL.md +0 -405
  148. package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +0 -223
  149. package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +0 -599
  150. package/out/base-prompts/skills/debug-agent/SKILL.md +0 -83
  151. package/out/base-prompts/skills/desktop-automation/SKILL.md +0 -166
  152. package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +0 -441
  153. package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +0 -280
  154. package/out/base-prompts/skills/docx/LICENSE.txt +0 -30
  155. package/out/base-prompts/skills/docx/SKILL.md +0 -197
  156. package/out/base-prompts/skills/docx/docx-js.md +0 -350
  157. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  158. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  159. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  160. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  161. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  162. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  163. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  164. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  165. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  166. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  167. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  168. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  169. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  170. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  171. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  172. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  173. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  174. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  175. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  176. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  177. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  178. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  179. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  180. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  181. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  182. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  183. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  184. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  185. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  186. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  187. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  188. package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +0 -75
  189. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  190. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  191. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  192. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  193. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  194. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  195. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  196. package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +0 -159
  197. package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +0 -29
  198. package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +0 -69
  199. package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +0 -15
  200. package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +0 -951
  201. package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +0 -274
  202. package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +0 -315
  203. package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +0 -279
  204. package/out/base-prompts/skills/docx/ooxml.md +0 -610
  205. package/out/base-prompts/skills/docx/scripts/__init__.py +0 -1
  206. package/out/base-prompts/skills/docx/scripts/document.py +0 -1276
  207. package/out/base-prompts/skills/docx/scripts/templates/comments.xml +0 -3
  208. package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +0 -3
  209. package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
  210. package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +0 -3
  211. package/out/base-prompts/skills/docx/scripts/templates/people.xml +0 -3
  212. package/out/base-prompts/skills/docx/scripts/utilities.py +0 -374
  213. package/out/base-prompts/skills/gateway-outbound/SKILL.md +0 -136
  214. package/out/base-prompts/skills/mcp-builder/LICENSE.txt +0 -202
  215. package/out/base-prompts/skills/mcp-builder/SKILL.md +0 -328
  216. package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +0 -602
  217. package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  218. package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  219. package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  220. package/out/base-prompts/skills/mcp-builder/scripts/connections.py +0 -151
  221. package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +0 -373
  222. package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  223. package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +0 -2
  224. package/out/base-prompts/skills/openclaw/SKILL.md +0 -406
  225. package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +0 -61
  226. package/out/base-prompts/skills/pdf/LICENSE.txt +0 -30
  227. package/out/base-prompts/skills/pdf/SKILL.md +0 -294
  228. package/out/base-prompts/skills/pdf/forms.md +0 -205
  229. package/out/base-prompts/skills/pdf/reference.md +0 -612
  230. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +0 -70
  231. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +0 -226
  232. package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +0 -12
  233. package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +0 -35
  234. package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +0 -41
  235. package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +0 -152
  236. package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +0 -114
  237. package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -108
  238. package/out/base-prompts/skills/pptx/LICENSE.txt +0 -30
  239. package/out/base-prompts/skills/pptx/SKILL.md +0 -484
  240. package/out/base-prompts/skills/pptx/html2pptx.md +0 -625
  241. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  242. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  243. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  244. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  245. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  246. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  247. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  248. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  249. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  250. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  251. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  252. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  253. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  254. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  255. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  256. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  257. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  258. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  259. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  260. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  261. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  262. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  263. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  264. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  265. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  266. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  267. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  268. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  269. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  270. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  271. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  272. package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +0 -75
  273. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  274. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  275. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  276. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  277. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  278. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  279. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  280. package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +0 -159
  281. package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +0 -29
  282. package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +0 -69
  283. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +0 -15
  284. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +0 -951
  285. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +0 -274
  286. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +0 -315
  287. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +0 -279
  288. package/out/base-prompts/skills/pptx/ooxml.md +0 -427
  289. package/out/base-prompts/skills/pptx/scripts/html2pptx.js +0 -984
  290. package/out/base-prompts/skills/pptx/scripts/inventory.py +0 -1020
  291. package/out/base-prompts/skills/pptx/scripts/rearrange.py +0 -231
  292. package/out/base-prompts/skills/pptx/scripts/replace.py +0 -385
  293. package/out/base-prompts/skills/pptx/scripts/thumbnail.py +0 -450
  294. package/out/base-prompts/skills/prompt-lab/SKILL.md +0 -149
  295. package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +0 -2551
  296. package/out/base-prompts/skills/skill-creator/LICENSE.txt +0 -202
  297. package/out/base-prompts/skills/skill-creator/SKILL.md +0 -371
  298. package/out/base-prompts/skills/skill-creator/references/output-patterns.md +0 -82
  299. package/out/base-prompts/skills/skill-creator/references/workflows.md +0 -28
  300. package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +0 -303
  301. package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +0 -110
  302. package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +0 -95
  303. package/out/base-prompts/skills/skill-installer/SKILL.md +0 -251
  304. package/out/base-prompts/skills/skill-installer/references/store-api.md +0 -219
  305. package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +0 -83
  306. package/out/base-prompts/skills/spotlight-insights/SKILL.md +0 -101
  307. package/out/base-prompts/skills/spotlight-optimize/SKILL.md +0 -93
  308. package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +0 -2551
  309. package/out/base-prompts/skills/spotlight-query/SKILL.md +0 -314
  310. package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +0 -163
  311. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +0 -2551
  312. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +0 -744
  313. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +0 -14
  314. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +0 -7
  315. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +0 -5
  316. package/out/base-prompts/skills/spotlight-report/SKILL.md +0 -86
  317. package/out/base-prompts/skills/spotlight-visualize/SKILL.md +0 -92
  318. package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +0 -2551
  319. package/out/base-prompts/skills/xlsx/LICENSE.txt +0 -30
  320. package/out/base-prompts/skills/xlsx/SKILL.md +0 -289
  321. package/out/base-prompts/skills/xlsx/recalc.py +0 -178
  322. package/out/branding.js +0 -49
  323. package/out/cli/commands/anywhere.js +0 -66
  324. package/out/cli/commands/ask.js +0 -246
  325. package/out/cli/commands/auth-broker.js +0 -24
  326. package/out/cli/commands/auth.js +0 -219
  327. package/out/cli/commands/chat.js +0 -357
  328. package/out/cli/commands/conformance.js +0 -249
  329. package/out/cli/commands/data-plane.js +0 -67
  330. package/out/cli/commands/deploy.js +0 -263
  331. package/out/cli/commands/gateway.js +0 -218
  332. package/out/cli/commands/idp.js +0 -85
  333. package/out/cli/commands/pack.js +0 -399
  334. package/out/cli/commands/probe-custom-engine.js +0 -82
  335. package/out/cli/commands/sandbox.js +0 -332
  336. package/out/cli/commands/serve.js +0 -1758
  337. package/out/cli/commands/store.js +0 -927
  338. package/out/cli/credential-context.js +0 -91
  339. package/out/cli/data-plane/broker.js +0 -78
  340. package/out/cli/data-plane/connector-host.js +0 -121
  341. package/out/cli/data-plane/egress.js +0 -189
  342. package/out/cli/data-plane/gateway.js +0 -218
  343. package/out/cli/data-plane/index.js +0 -152
  344. package/out/cli/data-plane/registry.js +0 -89
  345. package/out/cli/data-plane/remote.js +0 -106
  346. package/out/cli/data-plane/sidecar.js +0 -161
  347. package/out/cli/deploy/blueprints/registry.js +0 -84
  348. package/out/cli/deploy/layers/identity.js +0 -172
  349. package/out/cli/deploy-init.js +0 -3023
  350. package/out/cli/deps.js +0 -342
  351. package/out/cli/idp-init.js +0 -338
  352. package/out/cli/pack-install-ref.js +0 -15
  353. package/out/cli/pack-loader.js +0 -418
  354. package/out/cli/pack-runtime.js +0 -61
  355. package/out/cli/runtime.js +0 -450
  356. package/out/cli/serve-hardening.js +0 -607
  357. package/out/cli/state-sync.js +0 -424
  358. package/out/cli/utils.js +0 -231
  359. package/out/engines/base.js +0 -188
  360. package/out/engines/claude.js +0 -1254
  361. package/out/engines/codex.js +0 -862
  362. package/out/engines/custom.js +0 -674
  363. package/out/engines/customEngineIpc.js +0 -95
  364. package/out/engines/customEngineRunnerEmbed.js +0 -107
  365. package/out/engines/customRegistry.js +0 -274
  366. package/out/engines/embeddedCustomEngineRunner.generated.js +0 -9
  367. package/out/engines/engineApiKey.js +0 -138
  368. package/out/engines/install.js +0 -372
  369. package/out/engines/persistentEngineWorker.js +0 -320
  370. package/out/engines/publish.js +0 -341
  371. package/out/evidence/sessionShip.js +0 -610
  372. package/out/gateway/bridge.js +0 -843
  373. package/out/gateway/cli-agent-id.js +0 -36
  374. package/out/gateway/client.js +0 -336
  375. package/out/gateway/client.test.js +0 -395
  376. package/out/gateway/gateway-scope-errors.js +0 -14
  377. package/out/gateway/gatewayBackup.js +0 -76
  378. package/out/gateway/installSource.js +0 -68
  379. package/out/gateway/lifecycle.js +0 -3190
  380. package/out/gateway/openclaw-channel-routing.js +0 -317
  381. package/out/gateway/outboundHandles.js +0 -373
  382. package/out/gateway/outboundPaths.js +0 -75
  383. package/out/gateway/outboundSend.js +0 -254
  384. package/out/gateway/protocol.js +0 -48
  385. package/out/gateway/secretsPreflight.js +0 -90
  386. package/out/gateway/sessionRouting.js +0 -77
  387. package/out/headless/index.js +0 -539
  388. package/out/headless/sync.js +0 -473
  389. package/out/live-e2e/mcp-demo/server.mjs +0 -32
  390. package/out/live-probe/connector.mjs +0 -8
  391. package/out/live-probe/env-report.mjs +0 -8
  392. package/out/mcp.js +0 -654
  393. package/out/pack/caps.js +0 -271
  394. package/out/pack/governor.js +0 -309
  395. package/out/pack/lease-schema.js +0 -270
  396. package/out/pack/lease.js +0 -640
  397. package/out/pack/reconcile.js +0 -217
  398. package/out/pack/signature.js +0 -839
  399. package/out/permissions.js +0 -108
  400. package/out/permissions.mapping.yaml +0 -68
  401. package/out/registry/index.js +0 -332
  402. package/out/runner/host.js +0 -131
  403. package/out/runner/protocol.js +0 -108
  404. package/out/runner/remoteEngine.js +0 -130
  405. package/out/runtime/interfaces/config-provider.js +0 -32
  406. package/out/runtime/interfaces/edit-tracker.js +0 -24
  407. package/out/runtime/interfaces/index.js +0 -29
  408. package/out/runtime/interfaces/logger.js +0 -40
  409. package/out/runtime/interfaces/permission-handler.js +0 -66
  410. package/out/runtime/interfaces/session-store.js +0 -342
  411. package/out/runtime/vscode/vscode-session-store.js +0 -22
  412. package/out/sandbox/containerDriver.js +0 -570
  413. package/out/sandbox/processDriver.js +0 -315
  414. package/out/sandbox/sandboxRunner.js +0 -191
  415. package/out/sandbox/support.js +0 -301
  416. package/out/sandbox/types.js +0 -55
  417. package/out/scripts/analytics/fleet-local-adapter.js +0 -246
  418. package/out/scripts/analytics/live-fleet-4-collector.cjs +0 -315
  419. package/out/scripts/analytics/live-fleet-4-daemon.mjs +0 -134
  420. package/out/scripts/analytics/live-fleet-4-widget.cjs +0 -433
  421. package/out/scripts/gateway/Dockerfile.serve +0 -45
  422. package/out/scripts/gateway/docker-serve.sh +0 -248
  423. package/out/scripts/gateway/setup-gateway-server.ps1 +0 -953
  424. package/out/scripts/gateway/setup-gateway-server.sh +0 -935
  425. package/out/session.js +0 -272
  426. package/out/shared/agentEngines.js +0 -170
  427. package/out/shared/index.js +0 -109
  428. package/out/shared/models.js +0 -136
  429. package/out/shared/providers.js +0 -205
  430. package/out/spotlight/install.js +0 -345
  431. package/out/spotlight/publish.js +0 -544
  432. package/out/spotlight/yaml.js +0 -31
  433. package/out/store/index.js +0 -786
  434. package/out/store/shared.js +0 -241
  435. package/out/test/agentEnvFile.test.js +0 -97
  436. package/out/test/agentYaml.test.js +0 -373
  437. package/out/test/agentpack.test.js +0 -639
  438. package/out/test/analytics-lifecycle.test.js +0 -174
  439. package/out/test/analytics.test.js +0 -756
  440. package/out/test/auth-baseurl.test.js +0 -132
  441. package/out/test/auth-bootstrapApply.test.js +0 -123
  442. package/out/test/auth-broker-hardening.test.js +0 -227
  443. package/out/test/auth-broker.test.js +0 -541
  444. package/out/test/auth-callback.test.js +0 -62
  445. package/out/test/blueprint-registry.test.js +0 -105
  446. package/out/test/branding.test.js +0 -242
  447. package/out/test/caps-governor.test.js +0 -455
  448. package/out/test/cli-agent-id.test.js +0 -55
  449. package/out/test/codex-new-file-survival.test.js +0 -119
  450. package/out/test/configuration-scopes.test.js +0 -131
  451. package/out/test/credential-context.test.js +0 -160
  452. package/out/test/custom-env-layering.test.js +0 -151
  453. package/out/test/customEngineInstall.test.js +0 -100
  454. package/out/test/customRegistry.test.js +0 -122
  455. package/out/test/data-plane-engine-wiring.test.js +0 -250
  456. package/out/test/data-plane.test.js +0 -311
  457. package/out/test/deploy-init.test.js +0 -854
  458. package/out/test/deps-auto-install.test.js +0 -81
  459. package/out/test/egress-broker.test.js +0 -172
  460. package/out/test/engine-integration.test.js +0 -297
  461. package/out/test/enginePaths.test.js +0 -195
  462. package/out/test/enginePublish.test.js +0 -82
  463. package/out/test/fleetDisplayUtils.test.js +0 -100
  464. package/out/test/gateway-client.test.js +0 -409
  465. package/out/test/gateway-install-source.test.js +0 -111
  466. package/out/test/gateway-lifecycle.test.js +0 -608
  467. package/out/test/gateway-outbound.test.js +0 -538
  468. package/out/test/gateway-reset-mappings.test.js +0 -221
  469. package/out/test/gateway-route-index.test.js +0 -235
  470. package/out/test/gateway-secrets-preflight.test.js +0 -127
  471. package/out/test/gateway-session-routing.test.js +0 -65
  472. package/out/test/gateway-session-store.test.js +0 -216
  473. package/out/test/github.test.js +0 -232
  474. package/out/test/governor-hardening.test.js +0 -217
  475. package/out/test/headless-sync.test.js +0 -301
  476. package/out/test/helpers/manifest-fixtures.js +0 -27
  477. package/out/test/idp-init.test.js +0 -151
  478. package/out/test/lease-signature.test.js +0 -357
  479. package/out/test/lease-trust-root.test.js +0 -273
  480. package/out/test/lease-validation.test.js +0 -169
  481. package/out/test/lease.test.js +0 -114
  482. package/out/test/mcp-obo.test.js +0 -107
  483. package/out/test/mcp.test.js +0 -389
  484. package/out/test/model-routing.test.js +0 -133
  485. package/out/test/openclaw-channel-routing.test.js +0 -191
  486. package/out/test/pack-admission.test.js +0 -216
  487. package/out/test/pack-digest-parity.test.js +0 -140
  488. package/out/test/pack-reconcile.test.js +0 -183
  489. package/out/test/pack-signature-artifact.test.js +0 -103
  490. package/out/test/pack-signature.test.js +0 -176
  491. package/out/test/pack-trust-root.test.js +0 -202
  492. package/out/test/pathSafety.test.js +0 -63
  493. package/out/test/permissions.test.js +0 -175
  494. package/out/test/persistent-engine-worker.test.js +0 -220
  495. package/out/test/refreshPolicy.test.js +0 -60
  496. package/out/test/registry.test.js +0 -351
  497. package/out/test/remote-trust.test.js +0 -242
  498. package/out/test/runner-protocol.test.js +0 -249
  499. package/out/test/runtime.test.js +0 -349
  500. package/out/test/sandbox-driver.test.js +0 -553
  501. package/out/test/sandbox-hardening.test.js +0 -395
  502. package/out/test/sandbox-runner.test.js +0 -343
  503. package/out/test/scheduledFleetStorage.test.js +0 -72
  504. package/out/test/serve-gateway-turn.test.js +0 -267
  505. package/out/test/serve-hardening.test.js +0 -418
  506. package/out/test/serveClient.test.js +0 -209
  507. package/out/test/sessionJsonlFleet.test.js +0 -78
  508. package/out/test/sessionShip.test.js +0 -560
  509. package/out/test/shared.test.js +0 -173
  510. package/out/test/skills-mcp.test.js +0 -299
  511. package/out/test/spotlightPublish.test.js +0 -327
  512. package/out/test/spotlightYaml.test.js +0 -58
  513. package/out/test/state-sync-peer.test.js +0 -156
  514. package/out/test/store-install-safe.test.js +0 -69
  515. package/out/test/store.test.js +0 -331
  516. package/out/test/stores.test.js +0 -504
  517. package/out/test/sync-switch.test.js +0 -251
  518. package/out/test/teamViewDiscovery.test.js +0 -90
  519. package/out/test/tls-bypass.test.js +0 -90
  520. package/out/test/toolPath.test.js +0 -120
  521. package/out/test/utils.test.js +0 -308
  522. package/out/test/viewsConfigMerge.test.js +0 -180
  523. package/out/test/warrant.test.js +0 -138
  524. package/out/test/windows-command-line.test.js +0 -75
  525. package/out/utils/agentEnvFile.js +0 -131
  526. package/out/utils/binary.js +0 -175
  527. package/out/utils/index.js +0 -37
  528. package/out/utils/network.js +0 -74
  529. package/out/utils/paths.js +0 -116
  530. package/out/utils/pid.js +0 -180
  531. package/out/utils/resolveNodeBinary.js +0 -168
  532. package/out/utils/windowsCommandLine.js +0 -29
  533. package/out/utils.js +0 -968
@@ -1,3023 +0,0 @@
1
- "use strict";
2
- /*---------------------------------------------------------------------------------------------
3
- * Copyright (c) AgentHippo.ai. All rights reserved.
4
- *--------------------------------------------------------------------------------------------*/
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.AGENTHIPPO_CLI_NPM_VERSION_FALLBACK = exports.AGENTHIPPO_CLI_NPM_PACKAGE = exports.DEPLOY_PUBLIC_HOSTNAME = exports.DEFAULT_DEPLOY_PUBLIC_HOSTNAME = exports.CLAUDE_CODE_NPM_VERSION = exports.AGENTHIPPO_IMAGE_TAG = exports.AGENTHIPPO_IMAGE_REGISTRY = exports.DEPLOY_TARGETS = void 0;
40
- exports.isDeployTarget = isDeployTarget;
41
- exports.agenthippoImage = agenthippoImage;
42
- exports.defaultRuntimeImage = defaultRuntimeImage;
43
- exports.packAttributionBody = packAttributionBody;
44
- exports.packAttributionComment = packAttributionComment;
45
- exports.withPackAttribution = withPackAttribution;
46
- exports.serveCmdArgs = serveCmdArgs;
47
- exports.resolveAgentHippoCliNpmVersion = resolveAgentHippoCliNpmVersion;
48
- exports.resolveDeployPublicHostname = resolveDeployPublicHostname;
49
- exports.scanEnvRefs = scanEnvRefs;
50
- exports.renderEnvExample = renderEnvExample;
51
- exports.renderAuthEnvExample = renderAuthEnvExample;
52
- exports.renderAuthBrokerDockerfile = renderAuthBrokerDockerfile;
53
- exports.isSecretLikeBasename = isSecretLikeBasename;
54
- exports.dataPlaneTokenEnvVar = dataPlaneTokenEnvVar;
55
- exports.renderTarget = renderTarget;
56
- /**
57
- * `agenthippo deploy init` — generator for platform-native deploy artifacts
58
- * (serve-v3 §A4 / P2). We *generate templates into the pack repo*; we never run
59
- * `docker build` or call a cloud. The customer's security team reads a 5-line
60
- * Dockerfile and their CI/CLI does the rest.
61
- *
62
- * Tiers (serve-v3 §3): vm = T0.5/T1, compose = T2, cloudrun/cloudflare =
63
- * T1-generic, databricks-app/spcs = T1-native.
64
- *
65
- * Deployment model (org IdP vs generated staging): see enterprise-deploy/README.md in the repo root.
66
- * `deploy init` only writes templates; the customer runs scripts in deploy-out/ to deploy.
67
- *
68
- * This module is pure (string in, string out) apart from `scanEnvRefs`, which
69
- * reads the pack to make its secret surface self-documenting. Kept separate
70
- * from the command so it is unit-testable without fs or a cloud.
71
- */
72
- const fs = __importStar(require("fs"));
73
- const path = __importStar(require("path"));
74
- const serve_hardening_1 = require("./serve-hardening");
75
- const identity_1 = require("./deploy/layers/identity");
76
- exports.DEPLOY_TARGETS = ['vm', 'compose', 'cloudrun', 'databricks-app', 'spcs', 'cloudflare', 'aws-ecs', 'azure-container-apps'];
77
- function isDeployTarget(value) {
78
- return exports.DEPLOY_TARGETS.includes(value);
79
- }
80
- /** GHCR namespace for every AgentHippo-published image. Canonical — `agenthippoai`,
81
- * matching what scripts/release/publish-runtime-image.sh actually pushes to.
82
- * (`ghcr.io/agenthippo/` — no trailing "ai" — is a dead namespace; do not reintroduce it.) */
83
- exports.AGENTHIPPO_IMAGE_REGISTRY = 'ghcr.io/agenthippoai';
84
- /**
85
- * The ONE dated tag every generated artifact pins to.
86
- *
87
- * Generated customer artifacts must never say `:latest`: a floating tag silently
88
- * re-points a customer's deployment at a different image, which is exactly what the
89
- * dated-tag policy in scripts/release/publish-runtime-image.sh exists to prevent.
90
- *
91
- * MUST match the `--version` used by scripts/release/publish-runtime-image.sh for the
92
- * currently promoted release (scheme: `YYYY.MM` or `YYYY.MM.DD` — see
93
- * scripts/release/README.md § Versioning). Bump here and in the publish run together.
94
- */
95
- exports.AGENTHIPPO_IMAGE_TAG = '2026.07';
96
- /**
97
- * Claude Code CLI version pinned into generated customer artifacts.
98
- *
99
- * `"latest"` here meant every customer install resolved a different version, so two
100
- * deploys of the same pack were not the same software. Keep this equal to the
101
- * `CLAUDE_CODE_VERSION` build ARG in extensions/agentide/scripts/Dockerfile.runtime,
102
- * so the source-based (Databricks) path and the container path ship the same engine.
103
- */
104
- exports.CLAUDE_CODE_NPM_VERSION = '2.1.220';
105
- /** `ghcr.io/agenthippoai/<name>:<dated tag>` — the only way to name a published image. */
106
- function agenthippoImage(name, variant) {
107
- return `${exports.AGENTHIPPO_IMAGE_REGISTRY}/${name}:${exports.AGENTHIPPO_IMAGE_TAG}${variant ? `-${variant}` : ''}`;
108
- }
109
- /** Default runtime image for a pack engine (cursor needs the /engines layer). */
110
- function defaultRuntimeImage(engine) {
111
- return engine.trim().toLowerCase() === 'cursor'
112
- ? agenthippoImage('runtime', 'cursor')
113
- : agenthippoImage('runtime');
114
- }
115
- /** Provenance line for generated deploy artifacts (pack-standard WS2). */
116
- function packAttributionBody(ctx) {
117
- return `Generated from Agent Pack ${ctx.packName}@${ctx.packVersion} — packs.agenthippo.ai`;
118
- }
119
- function packAttributionComment(ctx, kind = 'hash') {
120
- const body = packAttributionBody(ctx);
121
- if (kind === 'html') {
122
- return `<!-- ${body} -->`;
123
- }
124
- if (kind === 'sql') {
125
- return `-- ${body}`;
126
- }
127
- return `# ${body}`;
128
- }
129
- /**
130
- * Prepend pack provenance to a generated file. Skips JSON/TOML/JSONC (no comment syntax).
131
- * Keeps shebangs first. Idempotent if packs.agenthippo.ai is already present near the top.
132
- */
133
- function withPackAttribution(ctx, relPath, content) {
134
- const head = content.slice(0, 400);
135
- if (head.includes('packs.agenthippo.ai')) {
136
- return content;
137
- }
138
- const base = path.basename(relPath);
139
- const ext = path.extname(base).toLowerCase();
140
- // Structured configs: don't invent comment syntax.
141
- if (ext === '.json' || ext === '.jsonc' || base === 'wrangler.toml' || base === 'package.json') {
142
- return content;
143
- }
144
- const kind = ext === '.html' || ext === '.md' ? 'html' :
145
- ext === '.sql' ? 'sql' :
146
- 'hash';
147
- const line = packAttributionComment(ctx, kind);
148
- if (content.startsWith('#!')) {
149
- const nl = content.indexOf('\n');
150
- if (nl === -1) {
151
- return `${content}\n${line}\n`;
152
- }
153
- return `${content.slice(0, nl + 1)}${line}\n${content.slice(nl + 1)}`;
154
- }
155
- return `${line}\n${content}`;
156
- }
157
- /** `serve` argv baked into generated Dockerfiles (matches agent.yaml engine/model). */
158
- function serveCmdArgs(ctx, opts) {
159
- const args = [
160
- 'serve',
161
- '--agent',
162
- '/app/pack',
163
- '--workspace',
164
- '/state/workspace',
165
- '--state-dir',
166
- '/state',
167
- '--port',
168
- String(opts?.port ?? ctx.port),
169
- ];
170
- const engine = ctx.engine?.trim().toLowerCase();
171
- if (engine && !['claude', 'codex'].includes(engine)) {
172
- args.push('--engine', engine);
173
- }
174
- const model = ctx.model?.trim();
175
- if (model) {
176
- args.push('--model', model);
177
- }
178
- if (opts?.wwwDir) {
179
- args.push('--www-dir', opts.wwwDir);
180
- }
181
- return args;
182
- }
183
- const TEXT_EXT = new Set(['.json', '.yaml', '.yml', '.md', '.env', '.example', '.txt', '.mcp']);
184
- const SKIP_DIRS = new Set(['node_modules', '.git', 'deploy', '.agent-hippo', 'dist', 'out']);
185
- /** Default public hostname when `--hostname` is not passed to `deploy init`. */
186
- exports.DEFAULT_DEPLOY_PUBLIC_HOSTNAME = 'auth.agenthippo.ai';
187
- /** @deprecated Use {@link DEFAULT_DEPLOY_PUBLIC_HOSTNAME}. */
188
- exports.DEPLOY_PUBLIC_HOSTNAME = exports.DEFAULT_DEPLOY_PUBLIC_HOSTNAME;
189
- /** npm package for Databricks Apps source deploy (org: agenthippo-ai). */
190
- exports.AGENTHIPPO_CLI_NPM_PACKAGE = '@agenthippo-ai/agenthippo';
191
- /** Fallback when monorepo package.json is not present (customer runs published CLI). */
192
- exports.AGENTHIPPO_CLI_NPM_VERSION_FALLBACK = 'latest';
193
- /**
194
- * Pin @agenthippo-ai/agenthippo to the monorepo release version when `deploy init` runs from
195
- * a dev checkout; otherwise fall back to `latest`.
196
- */
197
- function resolveAgentHippoCliNpmVersion() {
198
- try {
199
- const pkgPath = path.join(__dirname, '..', '..', '..', '..', 'package.json');
200
- if (fs.existsSync(pkgPath)) {
201
- const raw = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
202
- const v = raw.version?.trim();
203
- if (v) {
204
- return v;
205
- }
206
- }
207
- }
208
- catch {
209
- // ignore
210
- }
211
- return exports.AGENTHIPPO_CLI_NPM_VERSION_FALLBACK;
212
- }
213
- function resolveDeployPublicHostname(value) {
214
- const trimmed = value?.trim();
215
- return trimmed || exports.DEFAULT_DEPLOY_PUBLIC_HOSTNAME;
216
- }
217
- /**
218
- * Recursively scan a pack for `${env:VAR}` references and return the sorted,
219
- * de-duplicated variable names. This is what makes `.env.example` honest: the
220
- * pack's secret surface is whatever it actually interpolates.
221
- */
222
- function scanEnvRefs(dir) {
223
- const found = new Set();
224
- const re = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g;
225
- const walk = (current) => {
226
- let entries;
227
- try {
228
- entries = fs.readdirSync(current, { withFileTypes: true });
229
- }
230
- catch {
231
- return;
232
- }
233
- for (const entry of entries) {
234
- const full = path.join(current, entry.name);
235
- if (entry.isDirectory()) {
236
- if (!SKIP_DIRS.has(entry.name)) {
237
- walk(full);
238
- }
239
- continue;
240
- }
241
- // `.mcp.json` → ext `.json`; also accept files with a meaningful name even if extensionless config.
242
- const ext = path.extname(entry.name).toLowerCase();
243
- if (!TEXT_EXT.has(ext)) {
244
- continue;
245
- }
246
- let text;
247
- try {
248
- text = fs.readFileSync(full, 'utf8');
249
- }
250
- catch {
251
- continue;
252
- }
253
- for (const m of text.matchAll(re)) {
254
- found.add(m[1]);
255
- }
256
- }
257
- };
258
- walk(dir);
259
- return [...found].sort();
260
- }
261
- /**
262
- * Render `.env.example`. Always seeds the two serve needs (a bearer secret and
263
- * a model credential) as comments, then lists every scanned pack variable.
264
- */
265
- function renderEnvExample(vars, opts) {
266
- const isCursor = opts?.engine?.trim().toLowerCase() === 'cursor';
267
- const hostname = resolveDeployPublicHostname(opts?.hostname);
268
- const lines = [
269
- '# Generated by `agenthippo deploy init`. Copy to .env and fill in.',
270
- ];
271
- if (isCursor) {
272
- lines.push('# Cursor engine (pack default model: auto):', 'CURSOR_API_KEY= # https://cursor.com/settings → API keys');
273
- }
274
- else if (opts?.gatewayDirect) {
275
- lines.push('# Gateway-direct routing (agent → gateway.agenthippo.ai — no in-pod LiteLLM sidecar):', 'AGENTHIPPO_LITELLM_BASE_URL=https://gateway.agenthippo.ai/v1', 'AGENTHIPPO_LITELLM_API_KEY= # ahgw_… from create-gateway-key on the gateway host');
276
- }
277
- else {
278
- lines.push('# Model credential — pick ONE path (serve-v3 §10.2):', '# AgentHippo hosted gateway (colocated agents / CI — long-lived service key):', '# AGENTHIPPO_LITELLM_BASE_URL=https://gateway.agenthippo.ai/v1', '# AGENTHIPPO_LITELLM_API_KEY=ahgw_… # bun run create-gateway-key on gateway host', '# Direct Anthropic:', 'ANTHROPIC_API_KEY=', '# CLAUDE_CODE_USE_BEDROCK=1 # existing AWS Bedrock entitlement (no new key)', '# CLAUDE_CODE_USE_VERTEX=1 # existing GCP Vertex entitlement (no new key)');
279
- }
280
- if (vars.length > 0) {
281
- lines.push('', '# Pack-referenced secrets (scanned from ${env:...}):');
282
- for (const v of vars) {
283
- lines.push(`${v}=`);
284
- }
285
- }
286
- if (opts?.edge) {
287
- lines.push('', '# Public hostname — set once; Caddyfile reads {$AGENTHIPPO_PUBLIC_HOSTNAME}.', `AGENTHIPPO_PUBLIC_HOSTNAME=${hostname}`, '', '# Multi-replica (serve-v4 P1): sticky by X-Agent-User in Caddyfile.', '# docker compose up --scale agent=N', '# AGENTHIPPO_STATE_S3_URI=s3://bucket/prefix # S3-compatible (MinIO/R2/GCS/AWS)', '# AGENTHIPPO_STATE_S3_ENDPOINT= # optional, e.g. http://minio:9000', '# AGENTHIPPO_STATE_RESTORE_BOOT=none # restore-on-miss (required when N>1)', '# Scale-down: drain (/ready inflight=0) then remove replica — see serve-v3-RUNBOOK.', '', '# Edge auth secrets (JWT signing key, Google OAuth client/cookie) live in .env.auth,', '# which is loaded ONLY by the auth-broker and oauth2-proxy services — never the agent.', '# Keeping the token-signing key out of the agent container limits blast radius if the', '# agent (which runs model-driven tool calls) is compromised. Copy .env.auth.example.', '', '# Cloudflare Tunnel (free — no Access seats), config-file mode in compose:', '# cloudflared tunnel create <name> → save the JSON as deploy/tunnel-credentials.json (gitignored)', '# set the tunnel id in deploy/cloudflared.yml; cloudflared tunnel route dns <name> <hostname>', '# No token/env needed — the credentials JSON is mounted into the cloudflared container.');
288
- }
289
- return lines.join('\n') + '\n';
290
- }
291
- /**
292
- * Render `.env.auth.example` — the edge auth secrets (JWT signing key, Google OAuth
293
- * client/cookie secrets, team bootstrap config). Loaded ONLY by the auth-broker and
294
- * oauth2-proxy services (see docker-compose.yml), never the agent: the agent has no use
295
- * for the token-signing key, so keeping it out of that container limits the blast radius
296
- * of a compromised (model-driven) agent — it can no longer forge tokens for any identity.
297
- */
298
- function renderAuthEnvExample(opts) {
299
- const hostname = resolveDeployPublicHostname(opts?.hostname);
300
- if (opts?.fed) {
301
- const idp = (opts.idpUrl || 'https://auth.agenthippo.ai').replace(/\/+$/, '');
302
- const cookieDomain = opts.cookieDomain || '.agenthippo.ai';
303
- return [
304
- '# Generated by `agenthippo deploy init --ingress edge-fed`. Copy to .env.auth and fill in.',
305
- '# Federated edge (A′): NO Google OAuth client here. Google login happens once at the',
306
- `# central IdP (${idp}); this agent only VERIFIES the shared ${cookieDomain} AgentHippo JWT.`,
307
- '# Loaded only by auth-broker. Never given to the agent (limits token-forgery blast radius).',
308
- '',
309
- '# AgentHippo JWT signing key — MUST match the central IdP (auth.agenthippo.ai).',
310
- '# Copy it from there (or the GCP secret it stores) so tokens issued by the IdP verify here.',
311
- 'AGENTHIPPO_AUTH_JWT_SECRET= # copy from auth.agenthippo.ai (openssl rand -hex 32 to generate one)',
312
- `AGENTHIPPO_AUTH_PUBLIC_URL=https://${hostname}`,
313
- 'AGENTHIPPO_AUTH_ALLOWED_DOMAINS=agenthippo.ai',
314
- 'AGENTHIPPO_AUTH_DEV=0 # 1 enables /oauth/dev/login (tests only; never in production)',
315
- `AGENTHIPPO_AUTH_COOKIE_DOMAIN=${cookieDomain} # scope the shared cookie to all subdomains`,
316
- `AGENTHIPPO_AUTH_IDP_URL=${idp} # where browsers are sent to log in`,
317
- 'AGENTHIPPO_AUTH_COOKIE_NAME=ah_token',
318
- '',
319
- '# Team bootstrap (store + gateway routing) — same as the IdP if you want parity.',
320
- 'AGENTHIPPO_TEAM_ID=team_default',
321
- 'AGENTHIPPO_TEAM_NAME=Default Team',
322
- 'AGENTHIPPO_TEAM_STORE_REPO=agenthippoai/agenthippo-store',
323
- 'AGENTHIPPO_TEAM_STORE_INDEX_URL=https://agenthippoai.github.io/agenthippo-store',
324
- 'AGENTHIPPO_TEAM_GITHUB_ORG=agenthippoai',
325
- '# An agenthippo-gateway deployment (JWT lane) — NOT bare LiteLLM. No key: the account',
326
- '# JWT is the routing credential.',
327
- 'AGENTHIPPO_TEAM_GATEWAY_URL=https://gateway.agenthippo.ai/v1',
328
- '',
329
- '# No OAUTH2_PROXY_* here — this agent does not talk to Google. The one oauth2-proxy +',
330
- '# one Google OAuth client live only at the IdP.',
331
- ].join('\n') + '\n';
332
- }
333
- const cookieDomain = opts?.cookieDomain || '.agenthippo.ai';
334
- return [
335
- '# Generated by `agenthippo deploy init`. Copy to .env.auth and fill in.',
336
- '# Loaded only by auth-broker + oauth2-proxy (docker-compose.yml). Never given to the agent.',
337
- '',
338
- '# Google SSO (oauth2-proxy). GCP → OAuth client (Web):',
339
- `# Authorized origins: https://${hostname}`,
340
- `# Redirect URI: https://${hostname}/oauth2/callback`,
341
- 'OAUTH2_PROXY_CLIENT_ID=',
342
- 'OAUTH2_PROXY_CLIENT_SECRET=',
343
- 'OAUTH2_PROXY_COOKIE_SECRET= # openssl rand -base64 32 | head -c 32',
344
- 'OAUTH2_PROXY_PROVIDER=google',
345
- 'OAUTH2_PROXY_EMAIL_DOMAINS=example.com',
346
- 'OAUTH2_PROXY_REVERSE_PROXY=true',
347
- `OAUTH2_PROXY_REDIRECT_URL=https://${hostname}/oauth2/callback`,
348
- '# Federated SSO (A′) — set when this edge is the central IdP for a fleet of agents:',
349
- `OAUTH2_PROXY_COOKIE_DOMAINS=${cookieDomain} # scope the oauth2-proxy session cookie to all subdomains`,
350
- '',
351
- '# IDE auth broker (team bootstrap for store + LiteLLM routing)',
352
- 'AGENTHIPPO_AUTH_JWT_SECRET= # openssl rand -hex 32',
353
- 'AGENTHIPPO_AUTH_PUBLIC_URL=https://' + hostname,
354
- 'AGENTHIPPO_AUTH_ALLOWED_DOMAINS=example.com',
355
- 'AGENTHIPPO_AUTH_DEV=0 # 1 enables /oauth/dev/login (tests only; never in production)',
356
- 'AGENTHIPPO_AUTH_COOKIE_NAME=ah_token # federated cookie holding the AgentHippo JWT',
357
- `AGENTHIPPO_AUTH_COOKIE_DOMAIN=${cookieDomain} # set so /oauth/browser-login stamps a cross-subdomain cookie`,
358
- 'AGENTHIPPO_TEAM_ID=team_default',
359
- 'AGENTHIPPO_TEAM_NAME=Default Team',
360
- 'AGENTHIPPO_TEAM_STORE_REPO=agenthippoai/agenthippo-store',
361
- 'AGENTHIPPO_TEAM_STORE_INDEX_URL=https://agenthippoai.github.io/agenthippo-store',
362
- 'AGENTHIPPO_TEAM_GITHUB_ORG=agenthippoai',
363
- '# An agenthippo-gateway deployment (JWT lane) — NOT bare LiteLLM. No key: the account',
364
- '# JWT is the routing credential.',
365
- 'AGENTHIPPO_TEAM_GATEWAY_URL=https://gateway.agenthippo.ai/v1',
366
- ].join('\n') + '\n';
367
- }
368
- /** Auth broker sidecar for IDE sign-in + team bootstrap API. */
369
- function renderAuthBrokerDockerfile() {
370
- return [
371
- '# Auth broker for IDE sign-in + team bootstrap API.',
372
- '# Build context = pack root: docker build -f deploy/Dockerfile.auth-broker -t <pack>-auth-broker:local .',
373
- `FROM ${agenthippoImage('runtime')}`,
374
- 'ENV AGENTHIPPO_AUTH_PORT=9780',
375
- 'ENV AGENTHIPPO_AUTH_DATA_DIR=/data',
376
- 'EXPOSE 9780',
377
- 'VOLUME ["/data"]',
378
- 'CMD ["auth-broker"]',
379
- '',
380
- ].join('\n');
381
- }
382
- /** Secret-like basenames beyond `.env` and `*credentials*.json` — private keys, keystores, and
383
- * credential files of any extension. Shared by renderDockerignore() below and copyPackToStaging()
384
- * in commands/deploy.ts so the staging dir on disk and the generated .dockerignore never diverge. */
385
- function isSecretLikeBasename(base) {
386
- if (/credentials/i.test(base))
387
- return true;
388
- if (/\.(pem|key|p12|pfx|jks|gpg|pgp)$/i.test(base))
389
- return true;
390
- if (/^id_(rsa|dsa|ecdsa|ed25519)$/i.test(base))
391
- return true;
392
- return false;
393
- }
394
- /** .dockerignore emitted alongside every image-building target's Dockerfile.
395
- * Keeps skills/, references/, scripts/ (required at runtime) and drops only VCS,
396
- * cache, and build cruft. node_modules is excluded so the linux/amd64 image
397
- * reinstalls fresh deps via the Dockerfile skill-deps step — dev-machine
398
- * node_modules may be wrong-arch (arm64/darwin) for the image. */
399
- function renderDockerignore() {
400
- return [
401
- '# Generated by `agenthippo deploy init`. Keeps skills/, references/, scripts/',
402
- '# (needed at runtime) and drops only VCS/cache/build cruft. node_modules is',
403
- '# excluded so the Dockerfile reinstalls fresh linux/amd64 deps for skills.',
404
- '.git',
405
- '.gitignore',
406
- '.gitattributes',
407
- '**/node_modules',
408
- '**/__pycache__',
409
- '**/.pytest_cache',
410
- '**/.mypy_cache',
411
- '**/.ruff_cache',
412
- '**/.venv',
413
- '**/venv',
414
- '**/*.log',
415
- '**/dist',
416
- '**/build',
417
- '**/.next',
418
- '**/.turbo',
419
- '**/.cache',
420
- '.DS_Store',
421
- '**/.DS_Store',
422
- '.idea',
423
- '.vscode',
424
- '*.swp',
425
- '# Secrets anywhere in the pack (defensive; deploy/ secrets are also covered below).',
426
- '# Keep in sync with isSecretLikeBasename() above.',
427
- '**/.env',
428
- // `**/.env` alone does NOT match `.env.auth` / `.env.local` / `.env.production`.
429
- // `.env.auth` carries AGENTHIPPO_AUTH_JWT_SECRET and OAUTH2_PROXY_* — baking it
430
- // into a published layer leaks fleet-wide token-forgery material.
431
- '**/.env.*',
432
- '**/*credentials*',
433
- '**/*.pem',
434
- '**/*.key',
435
- '**/*.p12',
436
- '**/*.pfx',
437
- '**/*.jks',
438
- '**/*.gpg',
439
- '**/*.pgp',
440
- '**/id_rsa',
441
- '**/id_dsa',
442
- '**/id_ecdsa',
443
- '**/id_ed25519',
444
- '# …but *.example templates are safe and are what the README tells customers to copy.',
445
- '!**/.env.example',
446
- '!**/.env.*.example',
447
- '# deploy/ holds build/deploy artifacts + secrets; only deploy/www/ ships at runtime.',
448
- 'deploy/*',
449
- '!deploy/www',
450
- '!deploy/www/**',
451
- '',
452
- ].join('\n');
453
- }
454
- /** Build-time install of skill dependencies (skills/** and scripts/**). Skills that
455
- * ship their own requirements.txt / package.json need their libs present at runtime,
456
- * but AGENTHIPPO_NO_AUTO_INSTALL=1 skips runtime installs — so we install at BUILD
457
- * time. Pure no-op when no dep files are present (keeps minimal packs lean). pip3 is
458
- * ensured only when a requirements.txt exists (the runtime base ships python3 but not
459
- * necessarily pip). Runs as root, then drops back to the non-root agent user. */
460
- function skillDepsInstallLines() {
461
- return [
462
- '# Skill dependencies — installed at BUILD time so skills that ship their own',
463
- '# requirements.txt / package.json have their libs present at runtime',
464
- '# (AGENTHIPPO_NO_AUTO_INSTALL=1 skips runtime installs). No-op when none exist.',
465
- 'USER root',
466
- 'RUN set -e; \\',
467
- ' py_reqs=$(find /app/pack/skills /app/pack/scripts -type f -name requirements.txt -not -path "*/node_modules/*" 2>/dev/null || true); \\',
468
- ' if [ -n "$py_reqs" ]; then \\',
469
- ' pip3 --version 2>/dev/null || (apt-get update && apt-get install -y --no-install-recommends python3-pip && rm -rf /var/lib/apt/lists/*); \\',
470
- ' echo "$py_reqs" | while IFS= read -r f; do [ -n "$f" ] && pip3 install --no-cache-dir --break-system-packages -r "$f" || true; done; \\',
471
- ' fi; \\',
472
- ' pkg_json=$(find /app/pack/skills /app/pack/scripts -type f -name package.json -not -path "*/node_modules/*" 2>/dev/null || true); \\',
473
- ' if [ -n "$pkg_json" ]; then \\',
474
- ' echo "$pkg_json" | while IFS= read -r f; do [ -n "$f" ] && (cd "$(dirname "$f")" && npm install --omit=dev --no-audit --no-fund || true) || true; done; \\',
475
- ' fi',
476
- 'USER agent',
477
- ];
478
- }
479
- /** Cloud Run / compose agent env: bind on all interfaces inside the container network. */
480
- function agentServeHostEnvYaml(indent = ' ') {
481
- return `${indent}- { name: ${serve_hardening_1.SERVE_HOST_ENV}, value: ${serve_hardening_1.CONTAINER_SERVE_HOST} }`;
482
- }
483
- /** The Dockerfile shared by image-based targets (serve-v3 §A2).
484
- * `extraEnv`: platform-specific ENV lines — e.g. pinning AGENTHIPPO_IDENTITY_HEADER for a
485
- * target whose container may be directly reachable (governance: identity-header pinning). */
486
- function sharedDockerfile(ctx, opts) {
487
- const cmd = serveCmdArgs(ctx, opts).map((a) => `"${a}"`).join(',');
488
- return [
489
- `${packAttributionComment(ctx)}. Build context = pack root:`,
490
- `# docker build -f deploy/Dockerfile -t ${ctx.packName}:local .`,
491
- `FROM ${ctx.image}`,
492
- `LABEL ai.agenthippo.pack.name="${ctx.packName}"`,
493
- `LABEL ai.agenthippo.pack.version="${ctx.packVersion}"`,
494
- `LABEL ai.agenthippo.pack.engine="${ctx.engine}"`,
495
- `COPY . /app/pack/`,
496
- ...skillDepsInstallLines(),
497
- `ENV AGENTHIPPO_NO_AUTO_INSTALL=1`,
498
- `ENV ${serve_hardening_1.SERVE_HOST_ENV}=${serve_hardening_1.CONTAINER_SERVE_HOST}`,
499
- ...Object.entries(opts?.extraEnv ?? {}).map(([k, v]) => `ENV ${k}=${v}`),
500
- `EXPOSE ${ctx.port}`,
501
- // Runtime base image sets ENTRYPOINT ["agenthippo"]; CMD args append to it.
502
- `CMD [${cmd}]`,
503
- '',
504
- ].join('\n');
505
- }
506
- /** Minimal post-SSO landing page (static chat UI → /v1/chat). */
507
- function renderLandingPage(packName) {
508
- return [
509
- '<!DOCTYPE html>',
510
- '<html lang="en">',
511
- '<head>',
512
- ' <meta charset="utf-8" />',
513
- ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
514
- ` <title>${packName}</title>`,
515
- ' <style>',
516
- ' :root { color-scheme: light dark; font-family: system-ui, sans-serif; }',
517
- ' body { max-width: 720px; margin: 2rem auto; padding: 0 1rem; line-height: 1.5; }',
518
- ' h1 { font-size: 1.4rem; margin-bottom: 0.25rem; }',
519
- ' .sub { opacity: 0.7; font-size: 0.9rem; margin-bottom: 1.5rem; }',
520
- ' #log { border: 1px solid #8884; border-radius: 8px; min-height: 200px; padding: 1rem; white-space: pre-wrap; margin-bottom: 1rem; }',
521
- ' form { display: flex; gap: 0.5rem; }',
522
- ' input[type=text] { flex: 1; padding: 0.6rem 0.75rem; border-radius: 6px; border: 1px solid #8886; font: inherit; }',
523
- ' button { padding: 0.6rem 1rem; border-radius: 6px; border: 0; background: #2563eb; color: #fff; font: inherit; cursor: pointer; }',
524
- ' button:disabled { opacity: 0.6; cursor: wait; }',
525
- ' </style>',
526
- '</head>',
527
- '<body>',
528
- ` <h1>${packName}</h1>`,
529
- ' <p class="sub">Signed in via Google SSO. Chat uses the agent API behind this page.</p>',
530
- ' <div id="log">Ready. Send a message below.</div>',
531
- ' <form id="chat">',
532
- ' <input id="msg" type="text" placeholder="Ask the agent…" autocomplete="off" required />',
533
- ' <button type="submit">Send</button>',
534
- ' </form>',
535
- ' <script>',
536
- ' const log = document.getElementById("log");',
537
- ' const form = document.getElementById("chat");',
538
- ' const input = document.getElementById("msg");',
539
- ' // Stable per-browser session id so turns resume the same conversation (multi-turn).',
540
- ' const sessionId = (self.crypto && crypto.randomUUID) ? crypto.randomUUID() : ("web-" + Date.now().toString(36) + Math.random().toString(36).slice(2));',
541
- ' function append(text) {',
542
- ' log.textContent += (log.textContent.endsWith("\\n") || log.textContent === "Ready. Send a message below." ? "" : "\\n") + text;',
543
- ' }',
544
- ' form.addEventListener("submit", async (e) => {',
545
- ' e.preventDefault();',
546
- ' const message = input.value.trim();',
547
- ' if (!message) return;',
548
- ' input.value = "";',
549
- ' form.querySelector("button").disabled = true;',
550
- ' append("\\nYou: " + message + "\\nAgent: ");',
551
- ' try {',
552
- ' const body = { message, session_id: sessionId };',
553
- ' const res = await fetch("/v1/chat", {',
554
- ' method: "POST",',
555
- ' headers: { "Content-Type": "application/json" },',
556
- ' body: JSON.stringify(body),',
557
- ' });',
558
- ' const data = await res.json();',
559
- ' if (!res.ok) throw new Error(data.error || res.statusText);',
560
- ' append(data.text || data.response || JSON.stringify(data));',
561
- ' } catch (err) {',
562
- ' append("[error] " + (err.message || err));',
563
- ' } finally {',
564
- ' form.querySelector("button").disabled = false;',
565
- ' input.focus();',
566
- ' }',
567
- ' });',
568
- ' input.focus();',
569
- ' </script>',
570
- '</body>',
571
- '</html>',
572
- '',
573
- ].join('\n');
574
- }
575
- /** Sticky upstream for multi-replica serve (serve-v4 P1.1).
576
- * Hash on X-Agent-User (SSO identity the edge already injects) so a user's
577
- * sessions stay on one replica — keeps local state + in-process RateLimiter
578
- * correct without Redis. Fallback ip_hash for tokened API callers without SSO.
579
- * Scale: `docker compose up --scale agent=N` (set AGENTHIPPO_STATE_RESTORE_BOOT=none).
580
- */
581
- function renderStickyAgentProxy(port, indent = ' ') {
582
- return [
583
- `${indent}# serve-v4 P1.1: sticky by user identity (not session body — edge can't hash POST).`,
584
- `${indent}# In-process RateLimiter is globally correct per user under this policy.`,
585
- `${indent}reverse_proxy agent:${port} {`,
586
- `${indent} lb_policy header X-Agent-User {`,
587
- `${indent} fallback ip_hash`,
588
- `${indent} }`,
589
- `${indent}}`,
590
- ];
591
- }
592
- /** Caddy config for the `edge` compose profile (oauth2-proxy + agent on Docker network). */
593
- function renderCaddyfile(ctx) {
594
- const fed = ctx.ingress === 'edge-fed';
595
- const idp = (ctx.idpUrl || '').trim().replace(/\/+$/, '');
596
- const catchallBody = [
597
- ' @api path /v1/*',
598
- ' handle @api {',
599
- ...renderStickyAgentProxy(ctx.port, ' '),
600
- ' }',
601
- ' root * /srv/www',
602
- ' file_server',
603
- ];
604
- return [
605
- '# SSO edge + Cloudflare Tunnel. Set AGENTHIPPO_PUBLIC_HOSTNAME in .env (Caddy reads it below).',
606
- '# TLS terminates at Cloudflare; origin listens on HTTP only.',
607
- ...(fed
608
- ? [
609
- `# No oauth2-proxy — Google login happens once at the central IdP (${idp || '<IDP_URL>'});`,
610
- '# this agent only verifies the shared AgentHippo JWT (Bearer for IDE, cookie for browser).',
611
- ]
612
- : []),
613
- '{',
614
- ' auto_https off',
615
- '}',
616
- '',
617
- 'http://{$AGENTHIPPO_PUBLIC_HOSTNAME} {',
618
- ...(fed
619
- ? (0, identity_1.renderIdentityLayer)({
620
- mode: 'federated',
621
- authBrokerAddr: 'auth-broker:9780',
622
- idpUrl: idp,
623
- healthProxy: renderStickyAgentProxy(ctx.port, ' '),
624
- agentProxy: renderStickyAgentProxy(ctx.port, ' '),
625
- catchallBody,
626
- })
627
- : (0, identity_1.renderIdentityLayer)({
628
- mode: 'self-issued',
629
- oauth2ProxyAddr: 'oauth2-proxy:4180',
630
- authBrokerAddr: 'auth-broker:9780',
631
- healthProxy: renderStickyAgentProxy(ctx.port, ' '),
632
- agentProxy: renderStickyAgentProxy(ctx.port, ' '),
633
- catchallBody,
634
- })),
635
- '}',
636
- '',
637
- ].join('\n');
638
- }
639
- /** cloudflared config-file mode (optional). Prefer CLOUDFLARE_TUNNEL_TOKEN + compose `tunnel` profile. */
640
- function renderCloudflaredYml(hostname) {
641
- return [
642
- '# Cloudflare Tunnel — run as the compose `cloudflared` service (config-file mode).',
643
- '# One-time setup:',
644
- '# cloudflared tunnel create <name> # creates the tunnel + a credentials JSON',
645
- '# cp <that-creds>.json deploy/tunnel-credentials.json # mounted into the container (gitignore it!)',
646
- `# cloudflared tunnel route dns <name> ${hostname}`,
647
- '# set the tunnel id below, then: docker compose --profile edge --profile tunnel up -d',
648
- 'tunnel: <TUNNEL_ID>',
649
- 'credentials-file: /etc/cloudflared/credentials.json # mounted from deploy/tunnel-credentials.json',
650
- 'ingress:',
651
- ` - hostname: ${hostname} # must match AGENTHIPPO_PUBLIC_HOSTNAME in .env`,
652
- ' service: http://caddy:80 # cloudflared container → Caddy over the compose network',
653
- ' - service: http_status:404',
654
- '',
655
- ].join('\n');
656
- }
657
- /** Shared `agent` service block for vm/compose docker-compose.yml (build context = pack root). */
658
- function renderAgentComposeService(ctx, extraLines = []) {
659
- return [
660
- ' agent:',
661
- ' build:',
662
- ' context: ..',
663
- ' dockerfile: deploy/Dockerfile',
664
- ' env_file:',
665
- ' - path: .env',
666
- ' required: false',
667
- ...extraLines,
668
- ' volumes: ["agenthippo-state:/state"]',
669
- ` expose: ["${ctx.port}"]`,
670
- ' # No host port — edge reaches agent on the Docker network only.',
671
- ' # T0.5a VPN: bash deploy/run.sh binds 127.0.0.1:' + ctx.port + ' via docker run -p.',
672
- '',
673
- ];
674
- }
675
- /** oauth2-proxy (self-issued only) + auth-broker + Caddy services for `--profile edge`.
676
- * `fed`: federated (verify-only against a central IdP) — no oauth2-proxy, no local Google client. */
677
- function renderEdgeComposeServices(fed) {
678
- const oauth2Proxy = fed ? [] : [
679
- ' oauth2-proxy:',
680
- ' profiles: ["edge"]',
681
- ' image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1',
682
- ' env_file:',
683
- ' - path: .env',
684
- ' required: false',
685
- ' - path: .env.auth # OAuth client/cookie secrets — not shared with the agent',
686
- ' required: false',
687
- ' environment:',
688
- ' OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180',
689
- ' OAUTH2_PROXY_UPSTREAMS: file:///dev/null',
690
- ' OAUTH2_PROXY_SET_XAUTHREQUEST: "true"',
691
- ' OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER: "true"',
692
- ' OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"',
693
- ' OAUTH2_PROXY_REVERSE_PROXY: "true"',
694
- ' expose: ["4180"]',
695
- '',
696
- ];
697
- return [
698
- ...oauth2Proxy,
699
- ' auth-broker:',
700
- ' profiles: ["edge"]',
701
- ' build:',
702
- ' context: ..',
703
- ' dockerfile: deploy/Dockerfile.auth-broker',
704
- ' env_file:',
705
- ' - path: .env',
706
- ' required: false',
707
- ' - path: .env.auth # JWT signing key + team config — not shared with the agent',
708
- ' required: false',
709
- ' environment:',
710
- ' AGENTHIPPO_AUTH_PORT: "9780"',
711
- ' AGENTHIPPO_AUTH_DATA_DIR: /data',
712
- ' user: "0:0" # named volume /data is root-owned on first create',
713
- ' volumes: ["auth-broker-data:/data"]',
714
- ' expose: ["9780"]',
715
- '',
716
- ' caddy:',
717
- ' profiles: ["edge"]',
718
- ' image: caddy:2.8-alpine',
719
- ' env_file:',
720
- ' - path: .env',
721
- ' required: false',
722
- ' ports: ["80:80", "443:443"] # use 80:80 not 127.0.0.1:80 — breaks vmnetd on some Docker Desktop builds',
723
- ' volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro", "./www:/srv/www:ro"]',
724
- ` depends_on: [${fed ? 'agent, auth-broker' : 'oauth2-proxy, agent, auth-broker'}]`,
725
- '',
726
- ];
727
- }
728
- /** cloudflared in compose — outbound tunnel to Cloudflare (free; no Access). */
729
- function renderTunnelComposeServices() {
730
- return [
731
- ' cloudflared:',
732
- ' profiles: ["tunnel"]',
733
- ' image: cloudflare/cloudflared:2024.12.2',
734
- ' restart: unless-stopped',
735
- ' # Config-file mode: routing lives in deploy/cloudflared.yml (GitOps), credentials',
736
- ' # in deploy/tunnel-credentials.json (gitignored secret from `cloudflared tunnel create`).',
737
- ' command: tunnel --config /etc/cloudflared/config.yml run',
738
- ' volumes:',
739
- ' - ./cloudflared.yml:/etc/cloudflared/config.yml:ro',
740
- ' - ./tunnel-credentials.json:/etc/cloudflared/credentials.json:ro',
741
- ' depends_on: [caddy]',
742
- '',
743
- ];
744
- }
745
- function renderEdgeTunnelUpScript(hostname) {
746
- return [
747
- '#!/usr/bin/env bash',
748
- '# T0.5b — Cloudflare Tunnel + Google SSO (no Access seats). Run: bash deploy/run-edge-tunnel.sh',
749
- 'set -euo pipefail',
750
- 'cd "$(dirname "$0")"',
751
- 'docker compose --profile edge --profile tunnel up -d --build',
752
- 'echo "Public URL: https://' + hostname + ' (after DNS + tunnel route configured)"',
753
- '',
754
- ].join('\n');
755
- }
756
- /**
757
- * Same-host colocation (PLAN/deploy.md §4 state 4, `d5-colocate`): this agent rides the org
758
- * IdP's already-running Caddy + auth-broker instead of shipping its own — no Caddy, no
759
- * oauth2-proxy, no local auth-broker, no local auth secrets at all. Ships only the agent
760
- * container (joining the IdP's `agenthippo-idp-colocate` docker network via a fixed,
761
- * predictable container name) plus a Caddy route snippet for the operator to drop into the
762
- * IdP's `agents/` import directory (see enterprise-deploy/idp/Caddyfile + agents/README.md).
763
- */
764
- /** Env var (in the IdP/edge stack's env) holding the connector sidecar's register token —
765
- * matches the connector's own AGENTHIPPO_DATA_PLANE_TOKEN. Convention proven by the
766
- * dynamodb-analyst overlay: pack `dynamodb-analyst` → `DYNAMODB_ANALYST_DATA_PLANE_TOKEN`. */
767
- function dataPlaneTokenEnvVar(packName) {
768
- return packName.toUpperCase().replace(/[^A-Z0-9]/g, '_') + '_DATA_PLANE_TOKEN';
769
- }
770
- function renderColocatedArtifacts(ctx) {
771
- const idp = (ctx.idpUrl || '').trim().replace(/\/+$/, '');
772
- const containerName = `${ctx.packName}-agent`;
773
- const turnTokenEnvVar = dataPlaneTokenEnvVar(ctx.packName);
774
- const compose = [
775
- '# Colocated agent (PLAN/deploy.md §4 state 4) — no Caddy/oauth2-proxy/auth-broker of its',
776
- '# own. Joins the org IdP\'s docker network so its already-running Caddy can reach this',
777
- '# agent by name; identity is verified entirely by the IdP\'s auth-broker (see',
778
- '# deploy/agent-route.caddy for the route to copy onto the IdP host).',
779
- `name: ${ctx.packName}`,
780
- 'services:',
781
- ' agent:',
782
- ` container_name: ${containerName}`,
783
- ' build:',
784
- ' context: ..',
785
- ' dockerfile: deploy/Dockerfile',
786
- ' env_file:',
787
- ' - path: .env',
788
- ' required: false',
789
- ' volumes: ["agenthippo-state:/state"]',
790
- ' networks: [colocate]',
791
- ` expose: ["${ctx.port}"]`,
792
- '',
793
- 'networks:',
794
- ' colocate:',
795
- ' external: true',
796
- ' name: ${AGENTHIPPO_IDP_NETWORK:-agenthippo-idp-colocate}',
797
- '',
798
- 'volumes:',
799
- ' agenthippo-state:',
800
- '',
801
- ].join('\n');
802
- const snippet = [
803
- `# Colocated route for ${ctx.publicHostname} — copy into enterprise-deploy/idp/agents/`,
804
- '# (rename e.g. to ' + ctx.packName + '.caddy), then on the IdP host:',
805
- '# docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile',
806
- '# Requires this agent\'s docker-compose.yml (already generated) to be up, so',
807
- `# ${containerName} exists on the shared \`agenthippo-idp-colocate\` network.`,
808
- '# http:// (not https://) to match the IdP Caddyfile\'s TLS model: auto_https is off',
809
- '# there and TLS terminates in front (Cloudflare Tunnel/nginx), so every site block —',
810
- '# including this imported one — must be plain HTTP, same as the IdP\'s own site address.',
811
- `http://${ctx.publicHostname} {`,
812
- ...(0, identity_1.renderIdentityLayer)({
813
- mode: 'federated',
814
- authBrokerAddr: 'auth-broker:9780',
815
- idpUrl: idp,
816
- healthProxy: [` reverse_proxy ${containerName}:${ctx.port}`],
817
- agentProxy: [` reverse_proxy ${containerName}:${ctx.port}`],
818
- ...(ctx.edgeTurnConnector
819
- ? { turnRegistration: { connectorAddr: ctx.edgeTurnConnector, tokenEnvVar: turnTokenEnvVar } }
820
- : {}),
821
- }),
822
- '}',
823
- '',
824
- ].join('\n');
825
- return {
826
- 'Dockerfile': sharedDockerfile(ctx, { wwwDir: '/app/pack/deploy/www' }),
827
- 'docker-compose.yml': compose,
828
- 'agent-route.caddy': snippet,
829
- 'www/index.html': renderLandingPage(ctx.packName),
830
- 'README.md': [
831
- `# Deploy: ${ctx.packName} — colocated with the org IdP`,
832
- '',
833
- 'No Caddy, no oauth2-proxy, no local auth-broker, no auth secrets on this host — this',
834
- 'agent rides the org IdP\'s already-running edge (`enterprise-deploy/idp/`) instead.',
835
- '',
836
- '```bash',
837
- 'cp deploy/.env.example deploy/.env # model key only — no auth secrets here at all',
838
- '# Point at the IdP\'s colocate network if it was created under a different compose',
839
- '# project name: export AGENTHIPPO_IDP_NETWORK=agenthippo-idp-colocate (the default)',
840
- 'docker compose -f deploy/docker-compose.yml up -d --build',
841
- '',
842
- '# Wire the route into the IdP\'s Caddy (one-time per agent):',
843
- `cp deploy/agent-route.caddy <idp-host>:enterprise-deploy/idp/agents/${ctx.packName}.caddy`,
844
- ...(ctx.edgeTurnConnector
845
- ? [
846
- '',
847
- '# EDGE turn registration (P1 fix): the route registers each chat turn with the connector',
848
- `# sidecar (${ctx.edgeTurnConnector}). Set the register token in the IdP stack's env —`,
849
- `# same value as the connector's AGENTHIPPO_DATA_PLANE_TOKEN — before reloading:`,
850
- `# echo "${turnTokenEnvVar}=<token>" >> enterprise-deploy/idp/.env`,
851
- '# The agent container never holds this token (that is the point).',
852
- ]
853
- : []),
854
- 'docker compose -f enterprise-deploy/idp/docker-compose.yml exec caddy \\',
855
- ' caddy reload --config /etc/caddy/Caddyfile',
856
- '```',
857
- '',
858
- `DNS: point \`${ctx.publicHostname}\` at the IdP host (same as every other agent it fronts).`,
859
- '',
860
- '**hippopennyg / bare-metal (Cloudflare → nginx:443 → IdP Caddy :8080):** `deploy init` does',
861
- '**not** create DNS or nginx for you. Without an explicit Cloudflare **A record**, the name',
862
- 'falls through to the `*.agenthippo.ai` wildcard (`ghs.googlehosted.com` from',
863
- '`setup-agenthippo-domain.sh` for Cloud Run) — the site will look "broken" or hit Google.',
864
- 'After the steps above, run:',
865
- '',
866
- '```bash',
867
- './enterprise-deploy/scripts/setup-colocated-agent-edge.sh \\',
868
- ` --hostname ${ctx.publicHostname} --ssh root@168.119.174.239`,
869
- '```',
870
- '',
871
- 'See `enterprise-deploy/idp/agents/README.md` for the full checklist.',
872
- '**Browser:** after Google SSO, open `https://' + ctx.publicHostname + '/` for the static chat UI',
873
- '(`deploy/www/index.html` served by the agent container at GET /).',
874
- '',
875
- ...renderEgressNote(ctx),
876
- ].join('\n'),
877
- };
878
- }
879
- function vmArtifacts(ctx) {
880
- if (ctx.ingress === 'edge-fed' && ctx.colocate) {
881
- return renderColocatedArtifacts(ctx);
882
- }
883
- const hostname = ctx.publicHostname;
884
- const run = [
885
- '#!/usr/bin/env bash',
886
- '# T0.5a — VPN + localhost only (no SSO edge). Binds 127.0.0.1 so LAN cannot bypass the proxy.',
887
- 'set -euo pipefail',
888
- ...anchorToStagingRoot(),
889
- `IMAGE="${ctx.packName}:local"`,
890
- 'docker build -f deploy/Dockerfile -t "$IMAGE" .',
891
- 'docker rm -f agenthippo-agent 2>/dev/null || true',
892
- 'docker run -d --name agenthippo-agent --restart unless-stopped \\',
893
- ' --env-file deploy/.env \\',
894
- ` -v agenthippo-state:/state -p 127.0.0.1:${ctx.port}:${ctx.port} "$IMAGE"`,
895
- `echo "agent on 127.0.0.1:${ctx.port} — health: curl localhost:${ctx.port}/health"`,
896
- '',
897
- ].join('\n');
898
- const service = [
899
- '# systemd user unit — survives logout (run: loginctl enable-linger $USER).',
900
- '# VPN path: cp deploy/agenthippo.service ~/.config/systemd/user/',
901
- '# SSO path: use agenthippo-edge.service (compose --profile edge)',
902
- '# install: systemctl --user enable --now agenthippo',
903
- '[Unit]',
904
- 'Description=AgentHippo agent (serve)',
905
- 'After=network-online.target docker.service',
906
- '',
907
- '[Service]',
908
- `WorkingDirectory=%h/${ctx.packName}`,
909
- 'ExecStart=/usr/bin/env bash deploy/run.sh',
910
- 'Restart=always',
911
- 'RestartSec=5',
912
- '',
913
- '[Install]',
914
- 'WantedBy=default.target',
915
- '',
916
- ].join('\n');
917
- const edgeService = [
918
- '# systemd user unit — Cloudflare Tunnel + Google SSO + agent.',
919
- '# install: cp deploy/agenthippo-edge.service ~/.config/systemd/user/',
920
- '# loginctl enable-linger $USER && systemctl --user enable --now agenthippo-edge',
921
- '[Unit]',
922
- 'Description=AgentHippo agent (Tunnel + Google SSO)',
923
- 'After=network-online.target docker.service',
924
- '',
925
- '[Service]',
926
- `WorkingDirectory=%h/${ctx.packName}/deploy`,
927
- 'ExecStart=/usr/bin/env docker compose -f docker-compose.yml --profile edge --profile tunnel up --build',
928
- 'ExecStop=/usr/bin/env docker compose -f docker-compose.yml --profile edge --profile tunnel down',
929
- 'Restart=always',
930
- 'RestartSec=5',
931
- '',
932
- '[Install]',
933
- 'WantedBy=default.target',
934
- '',
935
- ].join('\n');
936
- const vmCompose = [
937
- '# T0.5 workbench/VM. Paths:',
938
- '# T0.5a VPN: bash deploy/run.sh',
939
- '# T0.5b remote: bash deploy/run-edge-tunnel.sh (Tunnel + Google SSO — recommended WFH)',
940
- '# edge only: docker compose --profile edge up -d (SSO on :443, no tunnel)',
941
- 'services:',
942
- ...renderAgentComposeService(ctx),
943
- ...renderEdgeComposeServices(ctx.ingress === 'edge-fed'),
944
- ...renderTunnelComposeServices(),
945
- 'volumes:',
946
- ' agenthippo-state:',
947
- ' auth-broker-data:',
948
- '',
949
- ].join('\n');
950
- return {
951
- 'Dockerfile': sharedDockerfile(ctx),
952
- 'Dockerfile.auth-broker': renderAuthBrokerDockerfile(),
953
- 'docker-compose.yml': vmCompose,
954
- 'run.sh': run,
955
- 'run-edge-tunnel.sh': renderEdgeTunnelUpScript(hostname),
956
- 'agenthippo.service': service,
957
- 'agenthippo-edge.service': edgeService,
958
- 'Caddyfile': renderCaddyfile(ctx),
959
- 'www/index.html': renderLandingPage(ctx.packName),
960
- 'cloudflared.yml': renderCloudflaredYml(hostname),
961
- 'README.md': [
962
- `# Deploy: ${ctx.packName} — T0.5 workbench/VM`,
963
- '',
964
- 'Staging root = this directory\'s parent (full pack copy). Commands below run from the staging root unless noted.',
965
- '',
966
- 'To regenerate: run `agenthippo deploy init <path-to-pack> --target vm` from the **parent** of the pack (not from inside the pack), then `cd deploy-out/' + ctx.packName + '-vm`.',
967
- '',
968
- '`cp deploy/.env.example deploy/.env` and fill in, then pick a path:',
969
- '',
970
- '**T0.5a — VPN only (simplest internal pilot)**',
971
- '```bash',
972
- 'bash deploy/run.sh',
973
- '# curl http://127.0.0.1:' + ctx.port + '/v1/chat -d \'{"message":"hi"}\' ...',
974
- '```',
975
- '',
976
- '**T0.5b — Cloudflare Tunnel + Google SSO (recommended for WFH, no Access seats)**',
977
- '```bash',
978
- 'cp deploy/.env.example deploy/.env # agent: model key + public hostname',
979
- 'cp deploy/.env.auth.example deploy/.env.auth # broker/oauth2-proxy: JWT + OAuth secrets (never loaded by the agent)',
980
- '# 1. Google Cloud → OAuth Web client for https://' + hostname,
981
- '# Authorized origin + redirect: https://' + hostname + '/oauth2/callback',
982
- '# 2. cloudflared tunnel create <name>',
983
- '# cp ~/.cloudflared/<tunnel-id>.json ~/.agent-hippo/tunnel-credentials.json # secret',
984
- '# set tunnel: <tunnel-id> in deploy/cloudflared.yml',
985
- '# cloudflared tunnel route dns <name> ' + hostname,
986
- '# 3. Fill deploy/.env (hostname, model key) and deploy/.env.auth (OAUTH2_PROXY_*, AGENTHIPPO_AUTH_JWT_SECRET)',
987
- 'bash deploy/run-edge-tunnel.sh',
988
- '# Users open https://' + hostname + ' → Google login → static chat UI → /v1/chat',
989
- '```',
990
- 'Tunnel routing is **config-file mode** (`deploy/cloudflared.yml` → `http://caddy:80` on the compose network), not the Cloudflare dashboard public-hostname UI alone.',
991
- 'Optional: `agenthippo-edge.service` + `loginctl enable-linger $USER` for persistence.',
992
- '',
993
- '**Edge only (no tunnel)** — `docker compose --profile edge up -d` on LAN/VPN.',
994
- '',
995
- 'Identity: Google → oauth2-proxy → `X-Agent-User` → serve (`www/index.html` sends a per-tab `session_id` for multi-turn).',
996
- 'Per-user *data* scoping needs Design B (§11.1) in the app layer — generic `serve` alone does not filter warehouse rows.',
997
- '',
998
- '**Do not commit** `deploy/.env`, `deploy/.env.auth`, or `deploy/tunnel-credentials.json`.',
999
- '',
1000
- ...renderEgressNote(ctx),
1001
- ].join('\n'),
1002
- };
1003
- }
1004
- /** Compose-tuned OTEL collector config: same pipeline as the canonical Spotlight
1005
- * collector, but bound to 0.0.0.0 so a sibling container (litellm) can reach it,
1006
- * and writing JSONL to the shared traces volume DuckDB/query-analytics.sh read. */
1007
- const OTEL_COLLECTOR_COMPOSE_YAML = [
1008
- '# Spotlight OTEL collector (compose-tuned: 0.0.0.0 bind). Writes JSONL to',
1009
- '# ${AGENT_HOME_DIR}/analytics/traces/ — query with query-analytics.sh (DuckDB).',
1010
- 'receivers:',
1011
- ' otlp:',
1012
- ' protocols:',
1013
- ' http: { endpoint: 0.0.0.0:4318 }',
1014
- ' grpc: { endpoint: 0.0.0.0:4317 }',
1015
- 'processors:',
1016
- ' batch: { timeout: 500ms, send_batch_size: 10 }',
1017
- ' resource:',
1018
- ' attributes:',
1019
- ' - { key: service.name, value: agentide-litellm, action: upsert }',
1020
- 'exporters:',
1021
- ' file/jsonl:',
1022
- ' path: ${AGENT_HOME_DIR}/analytics/traces/traces.jsonl',
1023
- ' rotation: { max_megabytes: 100, max_days: 30, max_backups: 10 }',
1024
- 'service:',
1025
- ' pipelines:',
1026
- ' traces: { receivers: [otlp], processors: [batch, resource], exporters: [file/jsonl] }',
1027
- '',
1028
- ].join('\n');
1029
- function composeArtifacts(ctx) {
1030
- if (ctx.ingress === 'edge-fed' && ctx.colocate) {
1031
- return renderColocatedArtifacts(ctx);
1032
- }
1033
- const channels = ctx.gatewayChannels?.trim();
1034
- const hostname = ctx.publicHostname;
1035
- // Agent Anywhere: override the agent command to also run the gateway bridge,
1036
- // and add the OpenClaw gateway service (channels profile).
1037
- const agentGateway = channels
1038
- ? [
1039
- ' command:',
1040
- ` ["serve","--agent","/app/pack","--workspace","/state/workspace",`,
1041
- ` "--state-dir","/state","--port","${ctx.port}",`,
1042
- ` "--gateway-bridge","--bind","${channels}"]`,
1043
- ]
1044
- : [];
1045
- const channelsService = channels
1046
- ? [
1047
- '',
1048
- ' # Agent Anywhere gateway (OpenClaw) — Slack/Telegram bridge.',
1049
- ' # Build: docker build -f extensions/agentide/scripts/Dockerfile.gateway -t this-tag .',
1050
- ' # NOTE: the gateway runs in secured auth mode — the agent bridge needs',
1051
- ' # OPENCLAW_GATEWAY_TOKEN (or a paired device) to connect. Set it in .env.',
1052
- ' openclaw-gateway:',
1053
- ' profiles: ["channels"]',
1054
- ` image: ${agenthippoImage('openclaw-gateway')}`,
1055
- ' env_file:',
1056
- ' - path: .env',
1057
- ' required: false',
1058
- ' volumes: ["openclaw-config:/root/.openclaw"]',
1059
- ' expose: ["18789"]',
1060
- ]
1061
- : [];
1062
- const agentExtra = [
1063
- ' environment:',
1064
- ' # Routed through LiteLLM only when the gateway profile is up; harmless otherwise.',
1065
- ' AGENTHIPPO_LITELLM_BASE_URL: http://litellm:4443',
1066
- ...(channels ? [' OPENCLAW_GATEWAY_URL: ws://openclaw-gateway:18789'] : []),
1067
- ...agentGateway,
1068
- ];
1069
- const compose = [
1070
- '# T2 team/fleet stack (serve-v3 §8). Profiles:',
1071
- '# default = agent only (Docker network; no host port — use deploy/run.sh for T0.5a VPN)',
1072
- '# --profile edge = + Caddy + oauth2-proxy (Google SSO)',
1073
- '# --profile tunnel = + cloudflared (with edge; route hostname → http://caddy:80)',
1074
- '# --profile gateway = + LiteLLM + Spotlight OTEL collector',
1075
- '# --profile channels = + OpenClaw gateway (Agent Anywhere)',
1076
- '# e.g.: docker compose -f deploy/docker-compose.yml --profile edge --profile tunnel up -d',
1077
- 'services:',
1078
- ...renderAgentComposeService(ctx, agentExtra),
1079
- ...renderEdgeComposeServices(ctx.ingress === 'edge-fed'),
1080
- ...renderTunnelComposeServices(),
1081
- ' # LiteLLM gateway — self-contained image bakes the Spotlight config + OTEL hooks',
1082
- ' # (build: docker build -f extensions/agentide/scripts/Dockerfile.litellm -t this-tag .)',
1083
- ' litellm:',
1084
- ' profiles: ["gateway"]',
1085
- ` image: ${agenthippoImage('litellm-gateway')}`,
1086
- ' env_file:',
1087
- ' - path: .env',
1088
- ' required: false',
1089
- ' environment:',
1090
- ' OTEL_EXPORTER: otlp_http',
1091
- ' OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4318',
1092
- ' # The image defaults to --host 127.0.0.1 (safe when someone runs it bare).',
1093
- ' # Here it must answer the agent container over the compose network, so bind',
1094
- ' # 0.0.0.0 — and keep `expose:` rather than `ports:` so it is never reachable',
1095
- ' # from the host. Requires general_settings.master_key in the baked config.',
1096
- ' command: ["--config", "/config/litellm.config.yaml", "--port", "4443", "--host", "0.0.0.0"]',
1097
- ' expose: ["4443"]',
1098
- ' depends_on: [otel-collector]',
1099
- '',
1100
- ' # Spotlight collector → JSONL traces volume (query with query-analytics.sh / DuckDB).',
1101
- ' otel-collector:',
1102
- ' profiles: ["gateway"]',
1103
- ' image: otel/opentelemetry-collector-contrib:0.118.0',
1104
- ' command: ["--config=/etc/otel-collector.yaml"]',
1105
- ' # Root so the collector can create /data/analytics on a fresh named volume',
1106
- ' # (the contrib image runs non-root; the volume root is root-owned).',
1107
- ' user: "0:0"',
1108
- ' environment: { AGENT_HOME_DIR: /data }',
1109
- ' volumes:',
1110
- ' - ./otel-collector.yaml:/etc/otel-collector.yaml:ro',
1111
- ' - spotlight-traces:/data',
1112
- ...channelsService,
1113
- '',
1114
- 'volumes:',
1115
- ' agenthippo-state:',
1116
- ' auth-broker-data:',
1117
- ' spotlight-traces:',
1118
- ...(channels ? [' openclaw-config:'] : []),
1119
- '',
1120
- ].join('\n');
1121
- return {
1122
- 'Dockerfile': sharedDockerfile(ctx),
1123
- 'Dockerfile.auth-broker': renderAuthBrokerDockerfile(),
1124
- 'docker-compose.yml': compose,
1125
- 'Caddyfile': renderCaddyfile(ctx),
1126
- 'www/index.html': renderLandingPage(ctx.packName),
1127
- 'cloudflared.yml': renderCloudflaredYml(hostname),
1128
- 'run-edge-tunnel.sh': renderEdgeTunnelUpScript(hostname),
1129
- 'otel-collector.yaml': OTEL_COLLECTOR_COMPOSE_YAML,
1130
- 'README.md': [
1131
- `# Deploy: ${ctx.packName} — T2 team/fleet`,
1132
- '',
1133
- '- Agent only (VPN): `docker compose -f deploy/docker-compose.yml up -d`',
1134
- '- + Tunnel + Google SSO: `bash deploy/run-edge-tunnel.sh` (see vm README T0.5b).',
1135
- '- + LiteLLM gateway + Spotlight: `--profile gateway`.',
1136
- '- + Agent Anywhere: `--profile channels` + gateway env vars in `.env`.',
1137
- '',
1138
- 'Profiles compose: e.g. `--profile gateway --profile edge --profile tunnel up -d`.',
1139
- 'LiteLLM must speak Anthropic API format — our gateway image does.',
1140
- '',
1141
- ...renderEgressNote(ctx),
1142
- ].join('\n'),
1143
- };
1144
- }
1145
- /**
1146
- * Cloud Run multi-container (sidecars) service — agent + LiteLLM + Spotlight OTEL collector,
1147
- * the Cloud Run analogue of the compose `--profile gateway` stack. Sidecars share the pod's
1148
- * localhost, so the agent routes to LiteLLM on 127.0.0.1:4443 and LiteLLM exports OTLP to the
1149
- * collector on 127.0.0.1:4318; the collector writes JSONL to the GCS-backed /state volume
1150
- * (query with query-analytics.sh / DuckDB). Deployed via `gcloud run services replace`.
1151
- *
1152
- * Placeholders (${SERVICE} ${IMAGE} ${STATE_BUCKET} ${LITELLM_IMAGE}) are substituted by the
1153
- * deploy script — kept as a checked-in template so the wiring is reviewable in git.
1154
- */
1155
- /**
1156
- * The env var the LiteLLM gateway reads for a model's provider key — must match the
1157
- * `os.environ/<NAME>` reference in the gateway's model map (e.g. gpt-5.5 → OPENAI_API_KEY).
1158
- */
1159
- function litellmProviderKeyEnv(model) {
1160
- const m = (model || '').toLowerCase();
1161
- if (m === 'ah-auto') {
1162
- return 'AGENTHIPPO_GATEWAY_KEY';
1163
- }
1164
- if (m.includes('codex')) {
1165
- return 'OPENAI_CODEX_API_KEY';
1166
- }
1167
- if (m.includes('gpt') || m.includes('openai')) {
1168
- return 'OPENAI_API_KEY';
1169
- }
1170
- if (m.includes('claude') || m.includes('anthropic')) {
1171
- return 'ANTHROPIC_API_KEY';
1172
- }
1173
- return 'OPENAI_API_KEY';
1174
- }
1175
- /** True when the pack uses the managed Auto placeholder (routes via gateway.agenthippo.ai). */
1176
- function isAhAutoModel(model) {
1177
- return (model || '').trim().toLowerCase() === 'ah-auto';
1178
- }
1179
- /** Org gateway base URL — agent gateway-direct mode points here (same as colocated agents). */
1180
- const AGENTHIPPO_GATEWAY_BASE_URL = 'https://gateway.agenthippo.ai/v1';
1181
- function usesGatewayDirect(ctx) {
1182
- return ctx.modelRouting === 'gateway';
1183
- }
1184
- /** The model-provider API host this pack's engine actually talks to — the one thing every
1185
- * target's container needs outbound network access to. Generalizes the `spcs` target's
1186
- * enforced "one EAI to api.anthropic.com" egress allowlist into a documented note every
1187
- * other target ships too (governance layer, PLAN/deploy.md §6). */
1188
- function egressHost(ctx) {
1189
- if (ctx.engine.trim().toLowerCase() === 'cursor') {
1190
- return 'api2.cursor.sh';
1191
- }
1192
- if (isAhAutoModel(ctx.model)) {
1193
- return 'gateway.agenthippo.ai';
1194
- }
1195
- const m = (ctx.model || '').toLowerCase();
1196
- if (m.includes('codex') || m.includes('gpt') || m.includes('openai')) {
1197
- return 'api.openai.com';
1198
- }
1199
- return 'api.anthropic.com';
1200
- }
1201
- /** Egress allowlist note, appended to a target's README (governance layer). */
1202
- function renderEgressNote(ctx) {
1203
- const sidecarNote = usesGatewayDirect(ctx)
1204
- ? '.'
1205
- : ' — and, if the LiteLLM gateway/Spotlight sidecar is enabled, its own outbound is the same host.';
1206
- return [
1207
- `**Egress:** this container only needs outbound access to \`${egressHost(ctx)}:443\` (your model provider)${sidecarNote}`,
1208
- 'Restrict egress to this allowlist if your org requires it.',
1209
- '',
1210
- ];
1211
- }
1212
- /**
1213
- * Data & access summary (governance layer, PLAN/deploy.md §3/§6): one generated page
1214
- * enumerating what secrets exist, what leaves the network, and the blast radius per
1215
- * component — pre-answering the questions a security review asks before it asks them.
1216
- * The `spcs` target's "one EAI to api.anthropic.com" README line is the model this
1217
- * generalizes to every target instead of leaving it to just one platform.
1218
- */
1219
- function renderDataAccessSummary(ctx, target, edge, fed, colocate) {
1220
- const secrets = [
1221
- `\`${litellmProviderKeyEnv(ctx.model)}\` (or the pack's configured provider key) — your model provider credential`,
1222
- ];
1223
- const blastRadius = [
1224
- '**agent container** — can reach your model provider and read/write its own state volume. ' +
1225
- 'Never holds the JWT signing secret or any OAuth client secret (`.env` vs `.env.auth` split).',
1226
- ];
1227
- if (edge) {
1228
- secrets.push('`AGENTHIPPO_AUTH_JWT_SECRET` — signs/verifies IDE bootstrap tokens (auth-broker only, never the agent)');
1229
- blastRadius.push('**auth-broker** — holds the JWT signing secret' +
1230
- (fed ? ' (shared with the central IdP)' : '') +
1231
- '; mints/verifies IDE tokens. Does not hold model provider credentials.');
1232
- if (!fed) {
1233
- secrets.push('`OAUTH2_PROXY_CLIENT_SECRET` + `OAUTH2_PROXY_COOKIE_SECRET` — this deployment\'s own Google OAuth client (oauth2-proxy only, never the agent)');
1234
- blastRadius.push('**oauth2-proxy** — holds this deployment\'s Google OAuth client secret; terminates the Google ' +
1235
- 'login flow. Does not hold the JWT signing secret or model credentials.');
1236
- }
1237
- }
1238
- else if (colocate) {
1239
- blastRadius.push('**this deployment holds no auth secrets at all** — identity is verified entirely by the ' +
1240
- 'org IdP\'s own auth-broker (see `agent-route.caddy`); nothing here signs or stores a JWT secret.');
1241
- }
1242
- return [
1243
- `# Data & access summary — ${ctx.packName} (${target}${ctx.ingress ? ` / ${ctx.ingress}` : ''})`,
1244
- '',
1245
- 'Generated by `agenthippo deploy init` — answers the questions a security review asks first.',
1246
- '',
1247
- '## Secrets',
1248
- ...secrets.map((s) => `- ${s}`),
1249
- '',
1250
- '## Egress',
1251
- `- Outbound network access is needed only to \`${egressHost(ctx)}:443\` (your model provider).`,
1252
- ...(edge && fed ? [
1253
- '- API/Bearer requests are verified locally against the shared JWT secret — no network call ' +
1254
- 'to the central IdP per request. The one-time browser login flow (unauthenticated visitors) ' +
1255
- 'does redirect to the central IdP\'s public URL to sign in.',
1256
- ] : []),
1257
- ...(edge && !fed ? [
1258
- '- Google SSO calls `accounts.google.com` during login only (oauth2-proxy); no other third party is contacted per request.',
1259
- ] : []),
1260
- ...(colocate ? [
1261
- '- Identity is verified via the org IdP\'s own auth-broker over the shared private docker ' +
1262
- 'network (`agenthippo-idp-colocate`), not a public network call. The one-time browser login ' +
1263
- 'flow (unauthenticated visitors) still redirects to the central IdP\'s public URL to sign in.',
1264
- ] : []),
1265
- '',
1266
- '## Blast radius per component',
1267
- ...blastRadius.map((b) => `- ${b}`),
1268
- '',
1269
- ].join('\n');
1270
- }
1271
- function renderCloudrunServiceYaml(ctx) {
1272
- const providerKeyEnv = litellmProviderKeyEnv(ctx.model);
1273
- return [
1274
- '# Cloud Run multi-container: agent (ingress) + LiteLLM + Spotlight OTEL collector.',
1275
- '# Generated by `agenthippo deploy init`. Deploy with `bash deploy/cloudrun-deploy.sh`',
1276
- '# (it substitutes ${...} and runs `gcloud run services replace`). Traces land in the GCS',
1277
- '# /state volume at /state/analytics/traces — query with query-analytics.sh (DuckDB).',
1278
- 'apiVersion: serving.knative.dev/v1',
1279
- 'kind: Service',
1280
- 'metadata:',
1281
- ' name: ${SERVICE}',
1282
- 'spec:',
1283
- ' template:',
1284
- ' metadata:',
1285
- ' annotations:',
1286
- ' # Sidecars need CPU between requests (the collector flushes async); no scale-to-zero throttle.',
1287
- ' run.googleapis.com/cpu-throttling: "false"',
1288
- ' run.googleapis.com/startup-cpu-boost: "true"',
1289
- ' # Start order: collector → litellm → agent, so traces are never dropped on cold start.',
1290
- " run.googleapis.com/container-dependencies: '{\"litellm\":[\"otel-collector\"],\"agent\":[\"litellm\"]}'",
1291
- ' autoscaling.knative.dev/maxScale: "1"',
1292
- // An inbound channel socket can't scale to zero — pin one always-on instance.
1293
- ...(ctx.gatewayChannels?.trim() ? [' autoscaling.knative.dev/minScale: "1"'] : []),
1294
- ' spec:',
1295
- ' timeoutSeconds: 3600',
1296
- ' containers:',
1297
- ' # Ingress container — the ONLY one with a port. Routes the engine through LiteLLM.',
1298
- ' - name: agent',
1299
- ' image: ${IMAGE}',
1300
- ' ports:',
1301
- ' - name: http1',
1302
- ` containerPort: ${ctx.port}`,
1303
- ' env:',
1304
- ' - { name: AGENTHIPPO_HOME, value: /state }',
1305
- ' - { name: AGENTHIPPO_NO_AUTO_INSTALL, value: "1" }',
1306
- // Pin serve to trust ONLY the IAP-verified header, so an authenticated caller
1307
- // cannot impersonate another user via a spoofed X-Agent-User header. The deploy
1308
- // script fills this with x-goog-authenticated-user-email when IAP is enabled, or
1309
- // leaves it empty (unpinned) during an ENABLE_IAP=0 bootstrap deploy.
1310
- ' - { name: AGENTHIPPO_IDENTITY_HEADER, value: "${IDENTITY_HEADER_VALUE}" }',
1311
- agentServeHostEnvYaml(),
1312
- ' - { name: AGENTHIPPO_LITELLM_BASE_URL, value: http://localhost:4443 }',
1313
- ' - name: AGENTHIPPO_LITELLM_API_KEY',
1314
- ' valueFrom:',
1315
- ' secretKeyRef: { name: ${SERVICE}-litellm-key, key: latest }',
1316
- ' volumeMounts:',
1317
- ' - { name: state, mountPath: /state }',
1318
- ' # LiteLLM gateway sidecar — exports every call to the collector (Spotlight cost/latency/tokens).',
1319
- ' - name: litellm',
1320
- ' image: ${LITELLM_IMAGE}',
1321
- ' env:',
1322
- ' - { name: OTEL_EXPORTER, value: otlp_http }',
1323
- ' - { name: OTEL_EXPORTER_OTLP_ENDPOINT, value: http://localhost:4318 }',
1324
- ' - name: LITELLM_MASTER_KEY',
1325
- ' valueFrom:',
1326
- ' secretKeyRef: { name: ${SERVICE}-litellm-key, key: latest }',
1327
- ` # Upstream provider key for ${ctx.model || 'this model'} (matches the gateway's os.environ/${providerKeyEnv} ref).`,
1328
- ' # Other models need their own key var — rename to match your model map.',
1329
- ` - name: ${providerKeyEnv}`,
1330
- ' valueFrom:',
1331
- ' secretKeyRef: { name: ${SERVICE}-provider-key, key: latest }',
1332
- ' # Spotlight OTEL collector sidecar — writes JSONL traces into the shared /state volume.',
1333
- ' - name: otel-collector',
1334
- ' image: otel/opentelemetry-collector-contrib:0.118.0',
1335
- ' args: ["--config=/etc/otel/config.yaml"]',
1336
- ' env:',
1337
- ' - { name: AGENT_HOME_DIR, value: /state }',
1338
- ' volumeMounts:',
1339
- ' - { name: otel-config, mountPath: /etc/otel, readOnly: true }',
1340
- ' - { name: state, mountPath: /state }',
1341
- ' volumes:',
1342
- ' - name: state',
1343
- ' csi:',
1344
- ' driver: gcsfuse.run.googleapis.com',
1345
- ' volumeAttributes:',
1346
- ' bucketName: ${STATE_BUCKET}',
1347
- ' - name: otel-config',
1348
- ' secret:',
1349
- ' secretName: ${SERVICE}-otel-config',
1350
- ' items:',
1351
- ' - { key: latest, path: config.yaml }',
1352
- '',
1353
- ].join('\n');
1354
- }
1355
- /** Anchor a generated deploy script to the staging root (parent of `deploy/`). The script
1356
- * lives at <staging>/deploy/<script>.sh; `docker build -f deploy/Dockerfile .` and the
1357
- * `deploy/service.yaml` / `deploy/.env` relative paths must resolve against the staging
1358
- * root, so the script works no matter which cwd it is invoked from. (compose-based scripts
1359
- * don't need this — `build: context: ..` in docker-compose.yml resolves relative to the
1360
- * compose file itself.) */
1361
- function anchorToStagingRoot() {
1362
- return [
1363
- '# Anchor to the staging root (parent of deploy/) — this script runs from any cwd.',
1364
- 'cd "$(dirname "$0")/.."',
1365
- ];
1366
- }
1367
- /** IAP finalization (corp SSO). `--iap` is beta-track only on `gcloud run`, and public
1368
- * access on an existing service is controlled via the run.invoker IAM binding (there
1369
- * is no `--no-allow-unauthenticated` flag on `services update`). So: enable IAP on the
1370
- * beta track, strip any allUsers invoker binding (keeps the service private), and grant
1371
- * IAP-secured Web User to each principal in IAP_MEMBERS (space-separated). Used by both
1372
- * the Spotlight deploy (`services replace`) and the T1-generic deploy (`run deploy`). */
1373
- function iapFinalizeLines() {
1374
- return [
1375
- '# Ingress: corp SSO via IAP (forwards X-Goog-Authenticated-User-Email → user_id).',
1376
- '# --iap is beta-track only on `gcloud run`; public access on an existing service is',
1377
- '# controlled via the run.invoker IAM binding (no --no-allow-unauthenticated on',
1378
- '# `services update`). So: enable IAP on the beta track, strip any allUsers invoker',
1379
- '# binding (keeps the service private), and grant IAP-secured Web User to each',
1380
- '# principal in IAP_MEMBERS (space-separated, e.g.',
1381
- "# IAP_MEMBERS='user:you@corp.com serviceAccount:sa@project.iam.gserviceaccount.com'",
1382
- '# ). Omit IAP_MEMBERS to rely on bindings you manage out-of-band.',
1383
- 'if [[ "${ENABLE_IAP:-1}" == "1" ]]; then',
1384
- ' gcloud beta run services update "$SERVICE" --region "$REGION" --iap',
1385
- ' gcloud run services remove-iam-policy-binding "$SERVICE" --region "$REGION" \\',
1386
- ' --member=allUsers --role=roles/run.invoker >/dev/null 2>&1 || true',
1387
- ' for m in ${IAP_MEMBERS:-}; do',
1388
- ' gcloud beta iap web add-iam-policy-binding \\',
1389
- ' --resource-type=cloud-run --service="$SERVICE" --region="$REGION" \\',
1390
- ' --member="$m" --role=roles/iap.httpsResourceAccessor',
1391
- ' done',
1392
- 'fi',
1393
- ];
1394
- }
1395
- /** Deploy script for the Cloud Run Spotlight (multi-container) stack. */
1396
- function renderCloudrunSpotlightDeploy(ctx) {
1397
- return [
1398
- '#!/usr/bin/env bash',
1399
- `# Cloud Run + Spotlight: agent + LiteLLM + OTEL collector sidecars (engine: ${ctx.engine}${ctx.model ? `, model ${ctx.model}` : ''}).`,
1400
- '# Traces persist in the GCS /state bucket; query with query-analytics.sh (DuckDB).',
1401
- 'set -euo pipefail',
1402
- ...anchorToStagingRoot(),
1403
- `SERVICE="${ctx.packName}"`,
1404
- 'REGION="${REGION:-us-central1}"',
1405
- 'PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project)}"',
1406
- 'REPO="${ARTIFACT_REPO:-agenthippo}"',
1407
- 'IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${SERVICE}:latest"',
1408
- `LITELLM_IMAGE="\${LITELLM_IMAGE:-${agenthippoImage('litellm-gateway')}}"`,
1409
- 'STATE_BUCKET="${STATE_BUCKET:?set STATE_BUCKET=your-gcs-bucket (gcloud storage buckets create gs://...)}"',
1410
- '# LITELLM_KEY = the proxy key the agent presents to LiteLLM (and LiteLLM master key).',
1411
- 'LITELLM_KEY="${LITELLM_KEY:?set LITELLM_KEY=<litellm proxy key>}"',
1412
- `# PROVIDER_KEY = upstream key the gateway uses for ${ctx.model || 'your model'} (gateway reads ${litellmProviderKeyEnv(ctx.model)}).`,
1413
- 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<upstream provider key>}"',
1414
- '',
1415
- 'gcloud artifacts repositories describe "$REPO" --location="$REGION" 2>/dev/null \\',
1416
- ' || gcloud artifacts repositories create "$REPO" --repository-format=docker --location="$REGION"',
1417
- 'gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet',
1418
- '',
1419
- '# Rewrite the runtime base to this project Artifact Registry. Customer artifacts pin a',
1420
- '# dated ghcr.io/agenthippoai/runtime tag; mirror that exact tag into AR. No-op if already AR.',
1421
- 'DF_TMP="$(mktemp)"',
1422
- 'sed "s|FROM ghcr.io/agenthippoai/runtime:|FROM ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/runtime:|" deploy/Dockerfile > "$DF_TMP"',
1423
- 'docker build --pull --platform linux/amd64 -f "$DF_TMP" -t "$IMAGE" .',
1424
- 'docker push "$IMAGE"',
1425
- '',
1426
- '# Secrets: Spotlight collector config + the LiteLLM proxy key.',
1427
- 'ensure_secret() { gcloud secrets describe "$1" >/dev/null 2>&1 || gcloud secrets create "$1" --replication-policy=automatic; }',
1428
- 'ensure_secret "${SERVICE}-otel-config"; gcloud secrets versions add "${SERVICE}-otel-config" --data-file=deploy/otel-collector.yaml',
1429
- 'ensure_secret "${SERVICE}-litellm-key"; printf %s "$LITELLM_KEY" | gcloud secrets versions add "${SERVICE}-litellm-key" --data-file=-',
1430
- 'ensure_secret "${SERVICE}-provider-key"; printf %s "$PROVIDER_KEY" | gcloud secrets versions add "${SERVICE}-provider-key" --data-file=-',
1431
- '',
1432
- '# IAP (finalized below) forwards X-Goog-Authenticated-User-Email; pin serve to trust ONLY',
1433
- '# that header so an authenticated caller cannot impersonate another user via a spoofed',
1434
- '# X-Agent-User header. Skipped when ENABLE_IAP=0 (first-deploy bootstrap) since IAP never',
1435
- '# sets the header then.',
1436
- 'IDENTITY_HEADER_VALUE=""',
1437
- '[[ "${ENABLE_IAP:-1}" == "1" ]] && IDENTITY_HEADER_VALUE="x-goog-authenticated-user-email" || true',
1438
- '',
1439
- '# Render the multi-container template for this project (sed — no gettext dependency) and deploy.',
1440
- 'RENDERED="$(mktemp)"',
1441
- 'sed -e "s|\\${SERVICE}|${SERVICE}|g" -e "s|\\${IMAGE}|${IMAGE}|g" \\',
1442
- ' -e "s|\\${LITELLM_IMAGE}|${LITELLM_IMAGE}|g" -e "s|\\${STATE_BUCKET}|${STATE_BUCKET}|g" \\',
1443
- ' -e "s|\\${IDENTITY_HEADER_VALUE}|${IDENTITY_HEADER_VALUE}|g" \\',
1444
- ' deploy/service.yaml > "$RENDERED"',
1445
- 'gcloud run services replace "$RENDERED" --region "$REGION"',
1446
- '',
1447
- ...iapFinalizeLines(),
1448
- '',
1449
- '# Grant the runtime SA access to the secrets if needed:',
1450
- '# SA="$(gcloud run services describe "$SERVICE" --region "$REGION" --format=\'value(spec.template.spec.serviceAccountName)\')"',
1451
- '# for s in "${SERVICE}-otel-config" "${SERVICE}-litellm-key"; do gcloud secrets add-iam-policy-binding "$s" --member="serviceAccount:$SA" --role=roles/secretmanager.secretAccessor; done',
1452
- '',
1453
- ].join('\n');
1454
- }
1455
- /**
1456
- * Option B — AgentHippo edge on Cloud Run. Caddy is the ingress (the only container with a
1457
- * port) and fronts the agent with the SAME SSO flow the IDE already uses for `remote-vm`:
1458
- * - Browser → oauth2-proxy (Google SSO) → X-Agent-User → agent
1459
- * - IDE/API → auth-broker validates the AgentHippo Bearer JWT → X-Agent-User → agent
1460
- * No IAP, so the IDE points `spec.remote.url` straight at the public hostname and the existing
1461
- * `ensureSignedIn` + `buildServeAuthHeaders` path works unchanged.
1462
- *
1463
- * Cloud Run multi-container shares the pod's network namespace, so every sidecar is reached
1464
- * via `localhost:<port>` (NOT docker-DNS names like the compose edge). Cloud Run terminates TLS
1465
- * at the edge, so Caddy listens on HTTP `:{$PORT}` (`auto_https off`). The agent is NOT the
1466
- * ingress here, so it listens on a distinct internal port (EDGE_AGENT_PORT) to avoid clashing
1467
- * with Caddy on $PORT. The Caddyfile renderers themselves live in `deploy/layers/identity.ts`
1468
- * (`renderSidecarEdgeCaddyfile`/`renderSidecarFedCaddyfile`) — reused verbatim by aws-ecs and
1469
- * azure-container-apps, which share this exact "TLS in front, one localhost" shape.
1470
- */
1471
- /** Cloud Run multi-container service behind the AgentHippo edge (Option B). Caddy is ingress. */
1472
- function renderCloudrunEdgeServiceYaml(ctx) {
1473
- const isCursor = ctx.engine.trim().toLowerCase() === 'cursor';
1474
- const providerKeyEnv = litellmProviderKeyEnv(ctx.model);
1475
- const agentArgs = serveCmdArgs(ctx, { wwwDir: '/app/pack/deploy/www', port: identity_1.EDGE_AGENT_PORT })
1476
- .map((a) => `"${a.replace(/"/g, '\\"')}"`).join(', ');
1477
- // Start order: sidecars before caddy (ingress) so Caddy's first request finds everyone up.
1478
- const deps = isCursor
1479
- ? '{"caddy":["agent","oauth2-proxy","auth-broker"]}'
1480
- : '{"caddy":["agent","oauth2-proxy","auth-broker"],"agent":["litellm"],"litellm":["otel-collector"]}';
1481
- const lines = [
1482
- '# Cloud Run + AgentHippo edge (Option B): Caddy (ingress) + oauth2-proxy + auth-broker',
1483
- '# + agent (+ LiteLLM + OTEL collector for Spotlight). No IAP — Caddy gates via SSO,',
1484
- '# so the IDE hits the public hostname directly with its AgentHippo Bearer token.',
1485
- '# Generated by `agenthippo deploy init --target cloudrun --ingress edge`.',
1486
- 'apiVersion: serving.knative.dev/v1',
1487
- 'kind: Service',
1488
- 'metadata:',
1489
- ' name: ${SERVICE}',
1490
- 'spec:',
1491
- ' template:',
1492
- ' metadata:',
1493
- ' annotations:',
1494
- ' run.googleapis.com/cpu-throttling: "false"',
1495
- ' run.googleapis.com/startup-cpu-boost: "true"',
1496
- ` run.googleapis.com/container-dependencies: '${deps}'`,
1497
- ' autoscaling.knative.dev/maxScale: "1"',
1498
- ...(ctx.gatewayChannels?.trim() ? [' autoscaling.knative.dev/minScale: "1"'] : []),
1499
- ' spec:',
1500
- ' timeoutSeconds: 3600',
1501
- ' containers:',
1502
- ' # Ingress — the ONLY container with a port. SSO edge for browsers + IDE Bearer.',
1503
- ' - name: caddy',
1504
- ' image: caddy:2.8-alpine',
1505
- ' ports:',
1506
- ' - name: http1',
1507
- ` containerPort: ${ctx.port}`,
1508
- ' volumeMounts:',
1509
- ' - { name: caddy-config, mountPath: /etc/caddy, readOnly: true }',
1510
- ' # Auth broker — validates IDE Bearer JWTs (/api/ide/me) + IDE sign-in (/oauth/*).',
1511
- ' # Reuses the agent runtime image (the `agenthippo` CLI ships `auth-broker`).',
1512
- ' - name: auth-broker',
1513
- ' image: ${IMAGE}',
1514
- ' args: ["auth-broker"]',
1515
- ' env:',
1516
- ' - { name: AGENTHIPPO_AUTH_PORT, value: "9780" }',
1517
- ' - { name: AGENTHIPPO_AUTH_DATA_DIR, value: /data }',
1518
- ' - { name: AGENTHIPPO_AUTH_PUBLIC_URL, value: https://${PUBLIC_HOSTNAME} }',
1519
- ' - { name: AGENTHIPPO_AUTH_ALLOWED_DOMAINS, value: ${ALLOWED_DOMAINS} }',
1520
- ' - name: AGENTHIPPO_AUTH_JWT_SECRET',
1521
- ' valueFrom:',
1522
- ' secretKeyRef: { name: ${SERVICE}-auth-jwt-secret, key: latest }',
1523
- ' # oauth2-proxy — Google SSO for browser clients (provider=google).',
1524
- ' - name: oauth2-proxy',
1525
- ' image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1',
1526
- ' env:',
1527
- ' - { name: OAUTH2_PROXY_HTTP_ADDRESS, value: 0.0.0.0:4180 }',
1528
- ' - { name: OAUTH2_PROXY_UPSTREAMS, value: file:///dev/null }',
1529
- ' - { name: OAUTH2_PROXY_SET_XAUTHREQUEST, value: "true" }',
1530
- ' - { name: OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER, value: "true" }',
1531
- ' - { name: OAUTH2_PROXY_SKIP_PROVIDER_BUTTON, value: "true" }',
1532
- ' - { name: OAUTH2_PROXY_REVERSE_PROXY, value: "true" }',
1533
- ' - { name: OAUTH2_PROXY_PROVIDER, value: google }',
1534
- ' - { name: OAUTH2_PROXY_EMAIL_DOMAINS, value: ${ALLOWED_DOMAINS} }',
1535
- ' - { name: OAUTH2_PROXY_REDIRECT_URL, value: https://${PUBLIC_HOSTNAME}/oauth2/callback }',
1536
- ' - name: OAUTH2_PROXY_CLIENT_ID',
1537
- ' valueFrom:',
1538
- ' secretKeyRef: { name: ${SERVICE}-oauth-client-id, key: latest }',
1539
- ' - name: OAUTH2_PROXY_CLIENT_SECRET',
1540
- ' valueFrom:',
1541
- ' secretKeyRef: { name: ${SERVICE}-oauth-client-secret, key: latest }',
1542
- ' - name: OAUTH2_PROXY_COOKIE_SECRET',
1543
- ' valueFrom:',
1544
- ' secretKeyRef: { name: ${SERVICE}-oauth-cookie-secret, key: latest }',
1545
- ' # Agent — serves www + /v1/* on localhost (NOT ingress). Routed through LiteLLM.',
1546
- ' - name: agent',
1547
- ' image: ${IMAGE}',
1548
- ` args: [${agentArgs}]`,
1549
- ' env:',
1550
- ' - { name: AGENTHIPPO_HOME, value: /state }',
1551
- ' - { name: AGENTHIPPO_NO_AUTO_INSTALL, value: "1" }',
1552
- agentServeHostEnvYaml(),
1553
- ...(isCursor ? [
1554
- ' - { name: AGENTHIPPO_CUSTOM_ENGINES_DIR, value: /engines }',
1555
- ' - name: CURSOR_API_KEY',
1556
- ' valueFrom:',
1557
- ' secretKeyRef: { name: ${SERVICE}-provider-key, key: latest }',
1558
- ] : [
1559
- ' - { name: AGENTHIPPO_LITELLM_BASE_URL, value: http://localhost:4443 }',
1560
- ' - name: AGENTHIPPO_LITELLM_API_KEY',
1561
- ' valueFrom:',
1562
- ' secretKeyRef: { name: ${SERVICE}-litellm-key, key: latest }',
1563
- ]),
1564
- ' volumeMounts:',
1565
- ' - { name: state, mountPath: /state }',
1566
- ...startupProbeLines(identity_1.EDGE_AGENT_PORT, true),
1567
- ...resourceLimits('2000m', '2Gi'),
1568
- ];
1569
- if (!isCursor) {
1570
- lines.push(' # LiteLLM gateway sidecar — exports every call to the collector (Spotlight).', ' - name: litellm', ' image: ${LITELLM_IMAGE}', ' env:', ' - { name: OTEL_EXPORTER, value: otlp_http }', ' - { name: OTEL_EXPORTER_OTLP_ENDPOINT, value: http://localhost:4318 }', ' - name: LITELLM_MASTER_KEY', ' valueFrom:', ' secretKeyRef: { name: ${SERVICE}-litellm-key, key: latest }', ` # Upstream provider key for ${ctx.model || 'this model'} (matches the gateway's os.environ/${providerKeyEnv} ref).`, ` - name: ${providerKeyEnv}`, ' valueFrom:', ' secretKeyRef: { name: ${SERVICE}-provider-key, key: latest }', ...startupProbeLines(4443), ...resourceLimits('1000m', '1Gi'), ' # Spotlight OTEL collector sidecar — writes JSONL traces into the shared /state volume.', ' - name: otel-collector', ' image: otel/opentelemetry-collector-contrib:0.118.0', ' args: ["--config=/etc/otel/config.yaml"]', ' env:', ' - { name: AGENT_HOME_DIR, value: /state }', ' volumeMounts:', ' - { name: otel-config, mountPath: /etc/otel, readOnly: true }', ' - { name: state, mountPath: /state }', ...startupProbeLines(4318), ...resourceLimits('1000m', '512Mi'));
1571
- }
1572
- lines.push(' volumes:', ' - name: state', ' csi:', ' driver: gcsfuse.run.googleapis.com', ' volumeAttributes:', ' bucketName: ${STATE_BUCKET}', ' - name: caddy-config', ' secret:', ' secretName: ${SERVICE}-caddyfile', ' items:', ' - { key: latest, path: Caddyfile }');
1573
- if (!isCursor) {
1574
- lines.push(' - name: otel-config', ' secret:', ' secretName: ${SERVICE}-otel-config', ' items:', ' - { key: latest, path: config.yaml }');
1575
- }
1576
- lines.push('');
1577
- return lines.join('\n');
1578
- }
1579
- /** Deploy script for the Cloud Run edge (Option B): build, upsert secrets, replace, make public. */
1580
- function renderCloudrunEdgeDeploy(ctx) {
1581
- const isCursor = ctx.engine.trim().toLowerCase() === 'cursor';
1582
- const modelNote = ctx.model?.trim() ? `, model ${ctx.model.trim()}` : '';
1583
- const lines = [
1584
- '#!/usr/bin/env bash',
1585
- `# Cloud Run + AgentHippo edge (Option B): Caddy + oauth2-proxy + auth-broker front the agent.`,
1586
- `# Engine: ${ctx.engine}${modelNote}. No IAP — Caddy gates via SSO (Google for browsers, AgentHippo JWT for IDE).`,
1587
- '# The IDE points spec.remote.url straight at https://$PUBLIC_HOSTNAME (same flow as remote-vm).',
1588
- 'set -euo pipefail',
1589
- ...anchorToStagingRoot(),
1590
- `SERVICE="${ctx.packName}"`,
1591
- 'REGION="${REGION:-us-central1}"',
1592
- 'PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project)}"',
1593
- 'REPO="${ARTIFACT_REPO:-agenthippo}"',
1594
- 'IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${SERVICE}:latest"',
1595
- `LITELLM_IMAGE="\${LITELLM_IMAGE:-${agenthippoImage('litellm-gateway')}}"`,
1596
- '# Public hostname (custom domain mapped to the service) + the Google Workspace domain allowed by SSO.',
1597
- 'PUBLIC_HOSTNAME="${PUBLIC_HOSTNAME:?set PUBLIC_HOSTNAME=your.edge.hostname (gcloud run domain-mappings create --service $SERVICE --domain ...)}"',
1598
- 'ALLOWED_DOMAINS="${ALLOWED_DOMAINS:?set ALLOWED_DOMAINS=yourworkspace.com (oauth2-proxy email domain + broker allowed domains)}"',
1599
- 'STATE_BUCKET="${STATE_BUCKET:?set STATE_BUCKET=your-gcs-bucket (gcloud storage buckets create gs://...)}"',
1600
- '# Edge secrets (from .env.auth): Google OAuth client + cookie secret + AgentHippo JWT signing key.',
1601
- 'OAUTH_CLIENT_ID="${OAUTH_CLIENT_ID:?set OAUTH_CLIENT_ID=<google oauth web client id>}"',
1602
- 'OAUTH_CLIENT_SECRET="${OAUTH_CLIENT_SECRET:?set OAUTH_CLIENT_SECRET=<google oauth web client secret>}"',
1603
- 'OAUTH_COOKIE_SECRET="${OAUTH_COOKIE_SECRET:?set OAUTH_COOKIE_SECRET=$(openssl rand -base64 32 | head -c 32)}"',
1604
- 'AUTH_JWT_SECRET="${AUTH_JWT_SECRET:?set AUTH_JWT_SECRET=$(openssl rand -hex 32)}"',
1605
- ...(isCursor ? [
1606
- 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<cursor api key>}"',
1607
- ] : [
1608
- '# LiteLLM proxy key + the upstream provider key the gateway uses for the model.',
1609
- 'LITELLM_KEY="${LITELLM_KEY:?set LITELLM_KEY=<litellm proxy key>}"',
1610
- 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<upstream provider key>}"',
1611
- ]),
1612
- '',
1613
- 'gcloud artifacts repositories describe "$REPO" --location="$REGION" 2>/dev/null \\',
1614
- ' || gcloud artifacts repositories create "$REPO" --repository-format=docker --location="$REGION"',
1615
- 'gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet',
1616
- '',
1617
- '# One agent image serves both the agent and the auth-broker containers (CLI ships `auth-broker`).',
1618
- '# Rewrite the runtime base to this project Artifact Registry. Customer artifacts pin a',
1619
- '# dated ghcr.io/agenthippoai/runtime tag; mirror that exact tag into AR. No-op if already AR.',
1620
- 'DF_TMP="$(mktemp)"',
1621
- 'sed "s|FROM ghcr.io/agenthippoai/runtime:|FROM ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/runtime:|" deploy/Dockerfile > "$DF_TMP"',
1622
- 'docker build --pull --platform linux/amd64 -f "$DF_TMP" -t "$IMAGE" .',
1623
- 'docker push "$IMAGE"',
1624
- '',
1625
- '# Secrets: edge config + provider/litellm keys.',
1626
- 'ensure_secret() { gcloud secrets describe "$1" >/dev/null 2>&1 || gcloud secrets create "$1" --replication-policy=automatic; }',
1627
- 'ensure_secret "${SERVICE}-caddyfile"; gcloud secrets versions add "${SERVICE}-caddyfile" --data-file=deploy/Caddyfile',
1628
- 'ensure_secret "${SERVICE}-auth-jwt-secret"; printf %s "$AUTH_JWT_SECRET" | gcloud secrets versions add "${SERVICE}-auth-jwt-secret" --data-file=-',
1629
- 'ensure_secret "${SERVICE}-oauth-client-id"; printf %s "$OAUTH_CLIENT_ID" | gcloud secrets versions add "${SERVICE}-oauth-client-id" --data-file=-',
1630
- 'ensure_secret "${SERVICE}-oauth-client-secret"; printf %s "$OAUTH_CLIENT_SECRET" | gcloud secrets versions add "${SERVICE}-oauth-client-secret" --data-file=-',
1631
- 'ensure_secret "${SERVICE}-oauth-cookie-secret"; printf %s "$OAUTH_COOKIE_SECRET" | gcloud secrets versions add "${SERVICE}-oauth-cookie-secret" --data-file=-',
1632
- 'ensure_secret "${SERVICE}-provider-key"; printf %s "$PROVIDER_KEY" | gcloud secrets versions add "${SERVICE}-provider-key" --data-file=-',
1633
- ];
1634
- if (!isCursor) {
1635
- lines.push('ensure_secret "${SERVICE}-otel-config"; gcloud secrets versions add "${SERVICE}-otel-config" --data-file=deploy/otel-collector.yaml', 'ensure_secret "${SERVICE}-litellm-key"; printf %s "$LITELLM_KEY" | gcloud secrets versions add "${SERVICE}-litellm-key" --data-file=-');
1636
- }
1637
- lines.push('', '# Render the multi-container template for this project (sed — no gettext dependency) and deploy.', 'RENDERED="$(mktemp)"', 'sed -e "s|\\${SERVICE}|${SERVICE}|g" -e "s|\\${IMAGE}|${IMAGE}|g" \\', ' -e "s|\\${LITELLM_IMAGE}|${LITELLM_IMAGE}|g" -e "s|\\${STATE_BUCKET}|${STATE_BUCKET}|g" \\', ' -e "s|\\${PUBLIC_HOSTNAME}|${PUBLIC_HOSTNAME}|g" -e "s|\\${ALLOWED_DOMAINS}|${ALLOWED_DOMAINS}|g" \\', ' deploy/service.yaml > "$RENDERED"', 'gcloud run services replace "$RENDERED" --region "$REGION"', '', '# No IAP: Caddy is the gate, so Cloud Run itself must allow public ingress (allUsers invoker).', '# Without this, Cloud Run 403s before Caddy ever sees the request to run SSO.', 'gcloud run services add-iam-policy-binding "$SERVICE" --region "$REGION" \\', ' --member=allUsers --role=roles/run.invoker >/dev/null 2>&1 || true', '', '# Map the custom domain (once): gcloud run domain-mappings create --service "$SERVICE" --region "$REGION" --domain "$PUBLIC_HOSTNAME"', '# Then add the CNAME Cloud Run prints to your DNS. Google OAuth Web client must list', '# https://$PUBLIC_HOSTNAME as an authorized origin + https://$PUBLIC_HOSTNAME/oauth2/callback as a redirect.', '', '# Grant the runtime SA access to the secrets if needed:', '# SA="$(gcloud run services describe "$SERVICE" --region "$REGION" --format=\'value(spec.template.spec.serviceAccountName)\')"', '# for s in "${SERVICE}-caddyfile" "${SERVICE}-auth-jwt-secret" "${SERVICE}-oauth-client-id" "${SERVICE}-oauth-client-secret" "${SERVICE}-oauth-cookie-secret" "${SERVICE}-provider-key"; do', '# gcloud secrets add-iam-policy-binding "$s" --member="serviceAccount:$SA" --role=roles/secretmanager.secretAccessor', '# done', '');
1638
- return lines.join('\n');
1639
- }
1640
- function cloudrunArtifacts(ctx) {
1641
- const inbound = Boolean(ctx.gatewayChannels?.trim()); // 1b: holds an always-listening channel socket
1642
- const isCursor = ctx.engine.trim().toLowerCase() === 'cursor';
1643
- // Option B: AgentHippo edge on Cloud Run (Caddy + auth-broker + oauth2-proxy ingress).
1644
- // Same SSO flow the IDE already uses for remote-vm — point spec.remote.url at the public
1645
- // hostname and the existing ensureSignedIn + Bearer path works. No IAP, no IDE changes.
1646
- if (ctx.ingress === 'edge') {
1647
- return cloudrunEdgeArtifacts(ctx, inbound, isCursor);
1648
- }
1649
- // A′: federated edge — Caddy + auth-broker only (no oauth2-proxy). Google login stays at the
1650
- // central IdP; this agent verifies the shared .agenthippo.ai JWT. Scales to N agents with zero
1651
- // per-service OAuth client config. No IAP, no IDE changes.
1652
- if (ctx.ingress === 'edge-fed') {
1653
- return cloudrunFedArtifacts(ctx, inbound, isCursor);
1654
- }
1655
- // Model-routed engines (claude/codex) get the Spotlight multi-container stack: the agent
1656
- // calls a LiteLLM sidecar (so a `litellm/*` model resolves in-pod) and the OTEL collector
1657
- // lands cost/latency/token traces in the GCS /state volume. This also resolves the
1658
- // CLAUDE_CODE_USE_VERTEX-vs-LiteLLM contradiction — provider routing now lives on the gateway.
1659
- // Cursor talks to its own API (not through LiteLLM), so it keeps the lean single-container path.
1660
- if (!isCursor) {
1661
- return {
1662
- 'Dockerfile': sharedDockerfile(ctx, { wwwDir: '/app/pack/deploy/www' }),
1663
- 'www/index.html': renderLandingPage(ctx.packName),
1664
- 'service.yaml': renderCloudrunServiceYaml(ctx),
1665
- 'otel-collector.yaml': OTEL_COLLECTOR_COMPOSE_YAML,
1666
- 'cloudrun-deploy.sh': renderCloudrunSpotlightDeploy(ctx),
1667
- 'README.md': [
1668
- `# Deploy: ${ctx.packName} — Cloud Run + Spotlight (multi-container)`,
1669
- '',
1670
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}. Runtime base: \`${ctx.image}\`.`,
1671
- '',
1672
- 'Three sidecars share the pod\'s localhost (Cloud Run multi-container):',
1673
- '- **agent** (ingress) → routes the engine to `http://localhost:4443`',
1674
- '- **litellm** gateway → exports OTLP to `http://localhost:4318`',
1675
- '- **otel-collector** → writes JSONL traces to the GCS `/state` volume (`/state/analytics/traces`).',
1676
- '',
1677
- '```bash',
1678
- 'gcloud storage buckets create gs://<your-state-bucket> # once',
1679
- 'export STATE_BUCKET=<your-state-bucket> LITELLM_KEY=<litellm proxy key>',
1680
- 'bash deploy/cloudrun-deploy.sh # builds linux/amd64, creates secrets, `gcloud run services replace service.yaml`',
1681
- '```',
1682
- '',
1683
- '`service.yaml` is the multi-container template (checked in so the wiring is reviewable);',
1684
- 'the deploy script substitutes `${SERVICE} ${IMAGE} ${STATE_BUCKET} ${LITELLM_IMAGE}`.',
1685
- 'Provider creds (Anthropic key / Vertex ADC / OpenAI …) go on the **litellm** sidecar, not the agent.',
1686
- '',
1687
- inbound
1688
- ? '**Inbound channel (1b):** `minScale: 1` pins one always-on instance (the socket can\'t scale to zero).'
1689
- : '**No inbound channel (1a):** scales to zero; traces + sessions persist on the GCS `/state` volume.',
1690
- '',
1691
- '**Spotlight:** once traffic flows, query the GCS-backed traces with `query-analytics.sh` (DuckDB) against `/state/analytics/traces`.',
1692
- '',
1693
- '**Lean alternative (no Spotlight):** a single-container deploy that calls a provider directly',
1694
- '(or an external LiteLLM) — set `AGENTHIPPO_LITELLM_BASE_URL` to your endpoint and drop the sidecars.',
1695
- '',
1696
- ...renderEgressNote(ctx),
1697
- ].join('\n'),
1698
- };
1699
- }
1700
- // 1a (no inbound channel): scale-to-zero OK. 1b (inbound socket): pin a single,
1701
- // always-on instance with CPU always allocated so the socket survives.
1702
- const instanceFlags = inbound
1703
- ? ' --min-instances 1 --max-instances 1 --no-cpu-throttling \\'
1704
- : ' --min-instances 0 --max-instances 1 \\';
1705
- const runEnvVars = isCursor
1706
- ? 'AGENTHIPPO_NO_AUTO_INSTALL=1,AGENTHIPPO_HOME=/state,AGENTHIPPO_CUSTOM_ENGINES_DIR=/engines'
1707
- : 'CLAUDE_CODE_USE_VERTEX=1,AGENTHIPPO_NO_AUTO_INSTALL=1,AGENTHIPPO_HOME=/state';
1708
- const runSecrets = isCursor
1709
- ? 'CURSOR_API_KEY=agenthippo-cursor-key:latest'
1710
- : undefined;
1711
- const modelNote = ctx.model?.trim() ? `, model ${ctx.model.trim()}` : '';
1712
- const deploy = [
1713
- '#!/usr/bin/env bash',
1714
- `# T1-generic: Cloud Run with built-in IAP (corp SSO, no load balancer).`,
1715
- `# Engine: ${ctx.engine}${modelNote}. IAP forwards X-Goog-Authenticated-User-Email → user_id.`,
1716
- 'set -euo pipefail',
1717
- ...anchorToStagingRoot(),
1718
- `SERVICE="${ctx.packName}"`,
1719
- 'REGION="${REGION:-us-central1}"',
1720
- 'PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project)}"',
1721
- 'REPO="${ARTIFACT_REPO:-agenthippo}"',
1722
- 'IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${SERVICE}:latest"',
1723
- '# Q1: durable state. Cloud Run local disk is ephemeral (wiped on scale-to-zero),',
1724
- '# so mount a GCS bucket at /state — sessions/provider-sessions/analytics survive.',
1725
- 'STATE_BUCKET="${STATE_BUCKET:?set STATE_BUCKET=your-gcs-bucket (gcloud storage buckets create gs://...)}"',
1726
- '',
1727
- 'gcloud artifacts repositories describe "$REPO" --location="$REGION" 2>/dev/null \\',
1728
- ' || gcloud artifacts repositories create "$REPO" --repository-format=docker --location="$REGION"',
1729
- 'gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet',
1730
- '',
1731
- '# Build from pack root; Dockerfile is deploy/Dockerfile (not pack root — gcloud --source . misses it).',
1732
- '# Rewrite the runtime base to this project Artifact Registry. Customer artifacts pin a',
1733
- '# dated ghcr.io/agenthippoai/runtime tag; mirror that exact tag into AR. No-op if already AR.',
1734
- 'DF_TMP="$(mktemp)"',
1735
- 'sed "s|FROM ghcr.io/agenthippoai/runtime:|FROM ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/runtime:|" deploy/Dockerfile > "$DF_TMP"',
1736
- 'docker build --pull --platform linux/amd64 -f "$DF_TMP" -t "$IMAGE" .',
1737
- 'docker push "$IMAGE"',
1738
- '',
1739
- '# IAP (when enabled below) forwards X-Goog-Authenticated-User-Email; pin serve to trust',
1740
- '# ONLY that header so an authenticated caller cannot impersonate another user via a',
1741
- '# spoofed X-Agent-User header. Skipped when ENABLE_IAP=0 (first-deploy bootstrap) since',
1742
- '# IAP never sets the header then — pinning to an absent header would make every request',
1743
- '# anonymous instead of leaving body-supplied identity usable during bootstrap.',
1744
- 'IDENTITY_ENV=""',
1745
- '[[ "${ENABLE_IAP:-1}" == "1" ]] && IDENTITY_ENV=",AGENTHIPPO_IDENTITY_HEADER=x-goog-authenticated-user-email" || true',
1746
- '',
1747
- '# Deploy private (no --allow-unauthenticated). IAP + role grants are applied below on the beta track.',
1748
- 'gcloud run deploy "$SERVICE" \\',
1749
- ' --image "$IMAGE" \\',
1750
- ' --no-allow-unauthenticated \\',
1751
- ` --port ${ctx.port} \\`,
1752
- instanceFlags,
1753
- ' --timeout 3600 \\',
1754
- ' --add-volume name=state,type=cloud-storage,bucket="$STATE_BUCKET" \\',
1755
- ' --add-volume-mount volume=state,mount-path=/state \\',
1756
- ` --set-env-vars "${runEnvVars}\${IDENTITY_ENV}" \\`,
1757
- ...(runSecrets ? [` --set-secrets ${runSecrets} \\`] : []),
1758
- ' --region "$REGION"',
1759
- '',
1760
- ...iapFinalizeLines(),
1761
- '',
1762
- '# Notes:',
1763
- inbound
1764
- ? '# - INBOUND channel (1b): pinned min=max=1 + CPU-always-on so the socket stays alive (can\'t scale to zero).'
1765
- : '# - No inbound channel (1a): scales to zero; state on the GCS volume survives the instance dying.',
1766
- '# - GCS-FUSE adds latency on session read/write vs local disk; fine for chat, watch for heavy I/O.',
1767
- isCursor
1768
- ? `# - Cursor engine: CURSOR_API_KEY secret + ${agenthippoImage('runtime', 'cursor')} base (AGENTHIPPO_CUSTOM_ENGINES_DIR=/engines).`
1769
- : '# - Vertex path = keyless Claude on your existing GCP entitlement; swap for ANTHROPIC_API_KEY if preferred.',
1770
- '# - First deploy without IAP org setup: ENABLE_IAP=0 bash deploy/cloudrun-deploy.sh',
1771
- '',
1772
- ].join('\n');
1773
- const secretSteps = isCursor
1774
- ? [
1775
- '2. `gcloud secrets create agenthippo-cursor-key --data-file=-` (paste your `CURSOR_API_KEY`).',
1776
- '3. Grant Cloud Run access: `gcloud secrets add-iam-policy-binding agenthippo-cursor-key --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com --role=roles/secretmanager.secretAccessor`.',
1777
- '4. `bash deploy/cloudrun-deploy.sh` — builds linux/amd64, pushes to Artifact Registry, deploys with GCS /state.',
1778
- ]
1779
- : [
1780
- '2. `bash deploy/cloudrun-deploy.sh` — builds linux/amd64, pushes to Artifact Registry, deploys with `--iap` + GCS /state.',
1781
- ];
1782
- return {
1783
- 'Dockerfile': sharedDockerfile(ctx, { wwwDir: '/app/pack/deploy/www' }),
1784
- 'www/index.html': renderLandingPage(ctx.packName),
1785
- 'cloudrun-deploy.sh': deploy,
1786
- 'README.md': [
1787
- `# Deploy: ${ctx.packName} — Cloud Run (T1-generic)`,
1788
- '',
1789
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}. Runtime base: \`${ctx.image}\`.`,
1790
- '',
1791
- '1. `gcloud storage buckets create gs://<your-state-bucket>` then `export STATE_BUCKET=<your-state-bucket>`.',
1792
- ...secretSteps,
1793
- '',
1794
- 'SSO + ingress: built-in IAP (`X-Goog-Authenticated-User-Email`). **Q1:** `/state` is a mounted GCS bucket.',
1795
- ctx.gatewayChannels ? '**Inbound channel (1b):** pinned single always-on instance (no scale-to-zero).' : '**No inbound channel (1a):** scales to zero; state persists on GCS.',
1796
- '',
1797
- '**Browser:** open the service URL → Google login → chat UI at `/` (same as vm `deploy/www/index.html`).',
1798
- '',
1799
- '**Verify** (smoke test via local proxy — bypasses IAP auth for direct testing):',
1800
- '```bash',
1801
- `gcloud run services proxy ${ctx.packName} --region "\${REGION:-us-central1}" &`,
1802
- 'PROXY=http://127.0.0.1:8080',
1803
- 'curl -s "$PROXY/ready"',
1804
- 'curl -s -XPOST "$PROXY/v1/chat" \\',
1805
- ' -H "X-Goog-Authenticated-User-Email: accounts.google.com:you@corp.com" \\',
1806
- ' -H "Content-Type: application/json" \\',
1807
- ' -d \'{"message":"Reply with exactly: CLOUDRUN_OK","session_id":"cr-1"}\'',
1808
- '```',
1809
- '',
1810
- ...renderEgressNote(ctx),
1811
- ].join('\n'),
1812
- };
1813
- }
1814
- /** Option B artifacts: Cloud Run behind the AgentHippo edge (Caddy + auth-broker + oauth2-proxy). */
1815
- function cloudrunEdgeArtifacts(ctx, inbound, isCursor) {
1816
- const sidecars = isCursor
1817
- ? 'Caddy (ingress) + oauth2-proxy + auth-broker + agent.'
1818
- : 'Caddy (ingress) + oauth2-proxy + auth-broker + agent + LiteLLM + OTEL collector (Spotlight).';
1819
- const files = {
1820
- 'Dockerfile': sharedDockerfile(ctx, { wwwDir: '/app/pack/deploy/www' }),
1821
- 'www/index.html': renderLandingPage(ctx.packName),
1822
- 'Caddyfile': (0, identity_1.renderSidecarEdgeCaddyfile)(),
1823
- 'service.yaml': renderCloudrunEdgeServiceYaml(ctx),
1824
- 'cloudrun-deploy.sh': renderCloudrunEdgeDeploy(ctx),
1825
- };
1826
- if (!isCursor) {
1827
- files['otel-collector.yaml'] = OTEL_COLLECTOR_COMPOSE_YAML;
1828
- }
1829
- files['README.md'] = [
1830
- `# Deploy: ${ctx.packName} — Cloud Run + AgentHippo edge (Option B)`,
1831
- '',
1832
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}. Runtime base: \`${ctx.image}\`.`,
1833
- '',
1834
- `${sidecars} Sidecars share the pod\'s localhost (Cloud Run multi-container):`,
1835
- '- **caddy** (ingress, the only port) → SSO edge: oauth2-proxy for browsers, auth-broker for IDE Bearer',
1836
- `- **agent** → serves www + /v1/* on localhost:${identity_1.EDGE_AGENT_PORT} (NOT ingress)`,
1837
- '- **auth-broker** → validates IDE AgentHippo JWTs (`/api/ide/me`) + IDE sign-in (`/oauth/*`)',
1838
- '- **oauth2-proxy** → Google SSO for browser clients (`provider=google`)',
1839
- ...(isCursor ? [] : [
1840
- '- **litellm** gateway → exports OTLP to `http://localhost:4318`',
1841
- '- **otel-collector** → writes JSONL traces to the GCS `/state` volume',
1842
- ]),
1843
- '',
1844
- '**No IAP** — Caddy is the gate, so the IDE points `spec.remote.url` straight at',
1845
- '`https://$PUBLIC_HOSTNAME` and the existing AgentHippo SSO flow works (same as `remote-vm`).',
1846
- 'Cloud Run itself is public (`allUsers` invoker) so traffic can reach Caddy to be authed.',
1847
- '',
1848
- '```bash',
1849
- 'gcloud storage buckets create gs://<state-bucket> # once',
1850
- 'cp deploy/.env.example deploy/.env && cp deploy/.env.auth.example deploy/.env.auth # fill both',
1851
- 'export PUBLIC_HOSTNAME=<your.edge.hostname> ALLOWED_DOMAINS=<yourworkspace.com>',
1852
- 'export OAUTH_CLIENT_ID=… OAUTH_CLIENT_SECRET=… OAUTH_COOKIE_SECRET=… AUTH_JWT_SECRET=…',
1853
- ...(isCursor ? ['export PROVIDER_KEY=<cursor api key>'] : ['export LITELLM_KEY=… PROVIDER_KEY=… STATE_BUCKET=<state-bucket>']),
1854
- 'bash deploy/cloudrun-deploy.sh # builds linux/amd64, upserts secrets, `gcloud run services replace`',
1855
- 'gcloud run domain-mappings create --service "$SERVICE" --region "${REGION:-us-central1}" --domain "$PUBLIC_HOSTNAME" # once',
1856
- '```',
1857
- '',
1858
- 'Google OAuth Web client (for oauth2-proxy): authorized origin `https://$PUBLIC_HOSTNAME`,',
1859
- 'redirect `https://$PUBLIC_HOSTNAME/oauth2/callback`. Restrict to your Workspace domain via',
1860
- '`ALLOWED_DOMAINS` (oauth2-proxy `email_domains` + broker `allowed_domains`).',
1861
- '',
1862
- '`service.yaml` placeholders (`${SERVICE} ${IMAGE} ${LITELLM_IMAGE} ${STATE_BUCKET} ${PUBLIC_HOSTNAME} ${ALLOWED_DOMAINS}`)',
1863
- 'are substituted by the deploy script. The Caddyfile is stored in Secret Manager (`${SERVICE}-caddyfile`).',
1864
- '',
1865
- inbound
1866
- ? '**Inbound channel (1b):** `minScale: 1` pins one always-on instance (the socket can\'t scale to zero).'
1867
- : '**No inbound channel (1a):** scales to zero; traces + sessions persist on the GCS `/state` volume.',
1868
- '',
1869
- ...renderEgressNote(ctx),
1870
- ].join('\n');
1871
- return files;
1872
- }
1873
- /** Cloud Run requires a startupProbe on any container that is a `depends_on` target, so the
1874
- * platform knows when the dependency is ready. `slow` = one generous 240s attempt (agent, which
1875
- * may warm an engine); otherwise 30×5s polls (sidecars that start fast). */
1876
- function startupProbeLines(port, slow = false) {
1877
- if (slow) {
1878
- // Slow starter (agent): check every 5s but tolerate up to 240s total. A single
1879
- // failureThreshold:1/periodSeconds:240 probe would only check ONCE at 240s, leaving
1880
- // the instance "no available instance" for the whole cold start and aborting requests.
1881
- return [
1882
- ' startupProbe:',
1883
- ' failureThreshold: 48',
1884
- ' periodSeconds: 5',
1885
- ' tcpSocket:',
1886
- ` port: ${port}`,
1887
- ' timeoutSeconds: 3',
1888
- ];
1889
- }
1890
- return [
1891
- ' startupProbe:',
1892
- ' failureThreshold: 30',
1893
- ' periodSeconds: 5',
1894
- ' tcpSocket:',
1895
- ` port: ${port}`,
1896
- ' timeoutSeconds: 3',
1897
- ];
1898
- }
1899
- /** Per-container CPU/memory limits. Cloud Run multi-container shares one instance whose total
1900
- * memory = sum of container limits, so the streaming agent + Python LiteLLM need real headroom
1901
- * (defaults of 256Mi each OOM-kill the instance mid-request → 503 "Service Unavailable"). */
1902
- function resourceLimits(cpu, memory) {
1903
- return [
1904
- ' resources:',
1905
- ' limits:',
1906
- ` cpu: ${cpu}`,
1907
- ` memory: ${memory}`,
1908
- ];
1909
- }
1910
- /** A′ — Federated edge multi-container service. Caddy (ingress) + auth-broker + agent
1911
- * (+ LiteLLM + OTEL for Spotlight). No oauth2-proxy container; in-memory emptyDir state. */
1912
- function renderCloudrunFedServiceYaml(ctx) {
1913
- const isCursor = ctx.engine.trim().toLowerCase() === 'cursor';
1914
- const providerKeyEnv = litellmProviderKeyEnv(ctx.model);
1915
- const agentArgs = serveCmdArgs(ctx, { wwwDir: '/app/pack/deploy/www', port: identity_1.EDGE_AGENT_PORT })
1916
- .map((a) => `"${a.replace(/"/g, '\\"')}"`).join(', ');
1917
- const cookieDomain = (ctx.cookieDomain || '.agenthippo.ai').trim();
1918
- const idpUrl = (ctx.idpUrl || 'https://auth.agenthippo.ai').trim().replace(/\/+$/, '');
1919
- const deps = isCursor
1920
- ? '{"caddy":["agent","auth-broker"]}'
1921
- : '{"caddy":["agent","auth-broker"],"agent":["litellm"],"litellm":["otel-collector"]}';
1922
- const lines = [
1923
- '# Cloud Run + AgentHippo federated edge (A′): Caddy (ingress) + auth-broker + agent',
1924
- '# (+ LiteLLM + OTEL collector for Spotlight). No oauth2-proxy — Google login happens once',
1925
- '# at the central IdP; this agent verifies the shared .agenthippo.ai AgentHippo JWT.',
1926
- '# Generated by `agenthippo deploy init --target cloudrun --ingress edge-fed`.',
1927
- 'apiVersion: serving.knative.dev/v1',
1928
- 'kind: Service',
1929
- 'metadata:',
1930
- ' name: ${SERVICE}',
1931
- 'spec:',
1932
- ' template:',
1933
- ' metadata:',
1934
- ' annotations:',
1935
- ' run.googleapis.com/cpu-throttling: "false"',
1936
- ' run.googleapis.com/startup-cpu-boost: "true"',
1937
- ` run.googleapis.com/container-dependencies: '${deps}'`,
1938
- ' autoscaling.knative.dev/maxScale: "1"',
1939
- ...(ctx.gatewayChannels?.trim() ? [' autoscaling.knative.dev/minScale: "1"'] : []),
1940
- ' spec:',
1941
- ' timeoutSeconds: 3600',
1942
- ' containers:',
1943
- ' # Ingress — the ONLY container with a port. Verifies the shared JWT (no Google here).',
1944
- ' - name: caddy',
1945
- ' image: caddy:2.8-alpine',
1946
- ' ports:',
1947
- ' - name: http1',
1948
- ` containerPort: ${ctx.port}`,
1949
- ' volumeMounts:',
1950
- ' - { name: caddy-config, mountPath: /etc/caddy, readOnly: true }',
1951
- ...resourceLimits('1000m', '512Mi'),
1952
- ' # Auth broker — verifies IDE Bearer JWTs AND the federated .agenthippo.ai cookie (/api/ide/me).',
1953
- ' # Same shared AGENTHIPPO_AUTH_JWT_SECRET as the central IdP so its tokens verify here.',
1954
- ' - name: auth-broker',
1955
- ' image: ${IMAGE}',
1956
- ' args: ["auth-broker"]',
1957
- ' env:',
1958
- ' - { name: AGENTHIPPO_AUTH_PORT, value: "9780" }',
1959
- ' - { name: AGENTHIPPO_AUTH_DATA_DIR, value: /data }',
1960
- ' - { name: AGENTHIPPO_AUTH_PUBLIC_URL, value: https://${PUBLIC_HOSTNAME} }',
1961
- ' - { name: AGENTHIPPO_AUTH_ALLOWED_DOMAINS, value: ${ALLOWED_DOMAINS} }',
1962
- ` - { name: AGENTHIPPO_AUTH_COOKIE_DOMAIN, value: "${cookieDomain}" }`,
1963
- ` - { name: AGENTHIPPO_AUTH_IDP_URL, value: "${idpUrl}" }`,
1964
- ' - { name: AGENTHIPPO_AUTH_COOKIE_NAME, value: "ah_token" }',
1965
- ' - name: AGENTHIPPO_AUTH_JWT_SECRET',
1966
- ' valueFrom:',
1967
- ' secretKeyRef: { name: ${SERVICE}-auth-jwt-secret, key: latest }',
1968
- ...startupProbeLines(9780),
1969
- ...resourceLimits('1000m', '512Mi'),
1970
- ' # Agent — serves www + /v1/* on localhost (NOT ingress). Routed through LiteLLM.',
1971
- ' - name: agent',
1972
- ' image: ${IMAGE}',
1973
- ` args: [${agentArgs}]`,
1974
- ' env:',
1975
- ' - { name: AGENTHIPPO_HOME, value: /state }',
1976
- ' - { name: AGENTHIPPO_NO_AUTO_INSTALL, value: "1" }',
1977
- agentServeHostEnvYaml(),
1978
- ...(isCursor ? [
1979
- ' - { name: AGENTHIPPO_CUSTOM_ENGINES_DIR, value: /engines }',
1980
- ' - name: CURSOR_API_KEY',
1981
- ' valueFrom:',
1982
- ' secretKeyRef: { name: ${SERVICE}-provider-key, key: latest }',
1983
- ] : [
1984
- ' - { name: AGENTHIPPO_LITELLM_BASE_URL, value: http://localhost:4443 }',
1985
- ' - name: AGENTHIPPO_LITELLM_API_KEY',
1986
- ' valueFrom:',
1987
- ' secretKeyRef: { name: ${SERVICE}-litellm-key, key: latest }',
1988
- ]),
1989
- ' volumeMounts:',
1990
- ' - { name: state, mountPath: /state }',
1991
- ...startupProbeLines(identity_1.EDGE_AGENT_PORT, true),
1992
- ...resourceLimits('2000m', '2Gi'),
1993
- ];
1994
- if (!isCursor) {
1995
- lines.push(' # LiteLLM gateway sidecar — exports every call to the collector (Spotlight).', ' - name: litellm', ' image: ${LITELLM_IMAGE}', ' env:', ' - { name: OTEL_EXPORTER, value: otlp_http }', ' - { name: OTEL_EXPORTER_OTLP_ENDPOINT, value: http://localhost:4318 }', ' - name: LITELLM_MASTER_KEY', ' valueFrom:', ' secretKeyRef: { name: ${SERVICE}-litellm-key, key: latest }', isAhAutoModel(ctx.model)
1996
- ? ' # ah-auto → gateway.agenthippo.ai (AGENTHIPPO_GATEWAY_KEY = ahgw_… service key).'
1997
- : ` # Upstream provider key for ${ctx.model || 'this model'} (matches os.environ/${providerKeyEnv}).`, ` - name: ${providerKeyEnv}`, ' valueFrom:', ' secretKeyRef: { name: ${SERVICE}-provider-key, key: latest }', ...startupProbeLines(4443), ...resourceLimits('1000m', '1Gi'), ' # Spotlight OTEL collector sidecar — writes JSONL traces into the shared /state volume.', ' - name: otel-collector', ' image: otel/opentelemetry-collector-contrib:0.118.0', ' args: ["--config=/etc/otel/config.yaml"]', ' env:', ' - { name: AGENT_HOME_DIR, value: /state }', ' volumeMounts:', ' - { name: otel-config, mountPath: /etc/otel, readOnly: true }', ' - { name: state, mountPath: /state }', ...startupProbeLines(4318), ...resourceLimits('1000m', '512Mi'));
1998
- }
1999
- lines.push(' volumes:', ' # In-memory state (matches the working spotlight deploy — no GCS SA perms needed).', ' - name: state', ' emptyDir:', ' medium: Memory', ' sizeLimit: 1Gi', ' - name: caddy-config', ' secret:', ' secretName: ${SERVICE}-caddyfile', ' items:', ' - { key: latest, path: Caddyfile }');
2000
- if (!isCursor) {
2001
- lines.push(' - name: otel-config', ' secret:', ' secretName: ${SERVICE}-otel-config', ' items:', ' - { key: latest, path: config.yaml }');
2002
- }
2003
- lines.push('');
2004
- return lines.join('\n');
2005
- }
2006
- /** A′ gateway-direct — federated edge without LiteLLM/OTEL sidecars. Agent calls the org gateway
2007
- * (gateway.agenthippo.ai) directly — same wiring as colocated agents on hippopennyg. */
2008
- function renderCloudrunFedGatewayServiceYaml(ctx) {
2009
- const agentArgs = serveCmdArgs(ctx, { wwwDir: '/app/pack/deploy/www', port: identity_1.EDGE_AGENT_PORT })
2010
- .map((a) => `"${a.replace(/"/g, '\\"')}"`).join(', ');
2011
- const cookieDomain = (ctx.cookieDomain || '.agenthippo.ai').trim();
2012
- const idpUrl = (ctx.idpUrl || 'https://auth.agenthippo.ai').trim().replace(/\/+$/, '');
2013
- const lines = [
2014
- '# Cloud Run + AgentHippo federated edge (A′) — gateway-direct (lean):',
2015
- '# Caddy (ingress) + auth-broker + agent. No LiteLLM/OTEL sidecars.',
2016
- '# Agent routes ah-auto (and other gateway models) to gateway.agenthippo.ai directly.',
2017
- '# Generated by `agenthippo deploy init --target cloudrun --ingress edge-fed --model-routing gateway`.',
2018
- 'apiVersion: serving.knative.dev/v1',
2019
- 'kind: Service',
2020
- 'metadata:',
2021
- ' name: ${SERVICE}',
2022
- 'spec:',
2023
- ' template:',
2024
- ' metadata:',
2025
- ' annotations:',
2026
- ' run.googleapis.com/cpu-throttling: "false"',
2027
- ' run.googleapis.com/startup-cpu-boost: "true"',
2028
- " run.googleapis.com/container-dependencies: '{\"caddy\":[\"agent\",\"auth-broker\"]}'",
2029
- ' autoscaling.knative.dev/maxScale: "1"',
2030
- ...(ctx.gatewayChannels?.trim() ? [' autoscaling.knative.dev/minScale: "1"'] : []),
2031
- ' spec:',
2032
- ' timeoutSeconds: 3600',
2033
- ' containers:',
2034
- ' - name: caddy',
2035
- ' image: caddy:2.8-alpine',
2036
- ' ports:',
2037
- ' - name: http1',
2038
- ` containerPort: ${ctx.port}`,
2039
- ' volumeMounts:',
2040
- ' - { name: caddy-config, mountPath: /etc/caddy, readOnly: true }',
2041
- ...resourceLimits('1000m', '512Mi'),
2042
- ' - name: auth-broker',
2043
- ' image: ${IMAGE}',
2044
- ' args: ["auth-broker"]',
2045
- ' env:',
2046
- ' - { name: AGENTHIPPO_AUTH_PORT, value: "9780" }',
2047
- ' - { name: AGENTHIPPO_AUTH_DATA_DIR, value: /data }',
2048
- ' - { name: AGENTHIPPO_AUTH_PUBLIC_URL, value: https://${PUBLIC_HOSTNAME} }',
2049
- ' - { name: AGENTHIPPO_AUTH_ALLOWED_DOMAINS, value: ${ALLOWED_DOMAINS} }',
2050
- ` - { name: AGENTHIPPO_AUTH_COOKIE_DOMAIN, value: "${cookieDomain}" }`,
2051
- ` - { name: AGENTHIPPO_AUTH_IDP_URL, value: "${idpUrl}" }`,
2052
- ' - { name: AGENTHIPPO_AUTH_COOKIE_NAME, value: "ah_token" }',
2053
- ' - name: AGENTHIPPO_AUTH_JWT_SECRET',
2054
- ' valueFrom:',
2055
- ' secretKeyRef: { name: ${SERVICE}-auth-jwt-secret, key: latest }',
2056
- ...startupProbeLines(9780),
2057
- ...resourceLimits('1000m', '512Mi'),
2058
- ' - name: agent',
2059
- ' image: ${IMAGE}',
2060
- ` args: [${agentArgs}]`,
2061
- ' env:',
2062
- ' - { name: AGENTHIPPO_HOME, value: /state }',
2063
- ' - { name: AGENTHIPPO_NO_AUTO_INSTALL, value: "1" }',
2064
- agentServeHostEnvYaml(),
2065
- ` - { name: AGENTHIPPO_LITELLM_BASE_URL, value: "${AGENTHIPPO_GATEWAY_BASE_URL}" }`,
2066
- ' - name: AGENTHIPPO_LITELLM_API_KEY',
2067
- ' valueFrom:',
2068
- ' secretKeyRef: { name: ${SERVICE}-provider-key, key: latest }',
2069
- ' volumeMounts:',
2070
- ' - { name: state, mountPath: /state }',
2071
- ...startupProbeLines(identity_1.EDGE_AGENT_PORT, true),
2072
- ...resourceLimits('2000m', '2Gi'),
2073
- ' volumes:',
2074
- ' - name: state',
2075
- ' emptyDir:',
2076
- ' medium: Memory',
2077
- ' sizeLimit: 1Gi',
2078
- ' - name: caddy-config',
2079
- ' secret:',
2080
- ' secretName: ${SERVICE}-caddyfile',
2081
- ' items:',
2082
- ' - { key: latest, path: Caddyfile }',
2083
- '',
2084
- ];
2085
- return lines.join('\n');
2086
- }
2087
- /** Deploy script for gateway-direct federated edge — no LiteLLM/OTEL secrets or sidecars. */
2088
- function renderCloudrunFedGatewayDeploy(ctx) {
2089
- const modelNote = ctx.model?.trim() ? `, model ${ctx.model.trim()}` : '';
2090
- const providerHint = isAhAutoModel(ctx.model)
2091
- ? 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<ahgw_… from create-gateway-key on gateway host>}"'
2092
- : 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<gateway service key or provider key>}"';
2093
- return [
2094
- '#!/usr/bin/env bash',
2095
- '# Cloud Run + AgentHippo federated edge (A′) — gateway-direct (lean).',
2096
- `# Engine: ${ctx.engine}${modelNote}. Caddy + auth-broker + agent only — agent calls`,
2097
- '# gateway.agenthippo.ai directly (no in-pod LiteLLM/OTEL sidecars). Same model routing',
2098
- '# as colocated agents on hippopennyg.',
2099
- 'set -euo pipefail',
2100
- ...anchorToStagingRoot(),
2101
- `SERVICE="${ctx.packName}"`,
2102
- 'REGION="${REGION:-us-central1}"',
2103
- 'PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project)}"',
2104
- 'REPO="${ARTIFACT_REPO:-agenthippo}"',
2105
- 'IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${SERVICE}:latest"',
2106
- 'PUBLIC_HOSTNAME="${PUBLIC_HOSTNAME:?set PUBLIC_HOSTNAME=<subdomain>.agenthippo.ai}"',
2107
- 'ALLOWED_DOMAINS="${ALLOWED_DOMAINS:-agenthippo.ai}"',
2108
- 'AUTH_JWT_SECRET="${AUTH_JWT_SECRET:?set AUTH_JWT_SECRET=<copy from auth.agenthippo.ai>}"',
2109
- providerHint,
2110
- '',
2111
- 'gcloud artifacts repositories describe "$REPO" --location="$REGION" 2>/dev/null \\',
2112
- ' || gcloud artifacts repositories create "$REPO" --repository-format=docker --location="$REGION"',
2113
- 'gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet',
2114
- '',
2115
- 'DF_TMP="$(mktemp)"',
2116
- 'sed "s|FROM ghcr.io/agenthippoai/runtime:|FROM ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/runtime:|" deploy/Dockerfile > "$DF_TMP"',
2117
- 'docker build --pull --platform linux/amd64 -f "$DF_TMP" -t "$IMAGE" .',
2118
- 'docker push "$IMAGE"',
2119
- '',
2120
- 'ensure_secret() { gcloud secrets describe "$1" >/dev/null 2>&1 || gcloud secrets create "$1" --replication-policy=automatic; }',
2121
- 'ensure_secret "${SERVICE}-caddyfile"; gcloud secrets versions add "${SERVICE}-caddyfile" --data-file=deploy/Caddyfile',
2122
- 'ensure_secret "${SERVICE}-auth-jwt-secret"; printf %s "$AUTH_JWT_SECRET" | gcloud secrets versions add "${SERVICE}-auth-jwt-secret" --data-file=-',
2123
- 'ensure_secret "${SERVICE}-provider-key"; printf %s "$PROVIDER_KEY" | gcloud secrets versions add "${SERVICE}-provider-key" --data-file=-',
2124
- '',
2125
- 'RENDERED="$(mktemp)"',
2126
- 'sed -e "s|\\${SERVICE}|${SERVICE}|g" -e "s|\\${IMAGE}|${IMAGE}|g" \\',
2127
- ' -e "s|\\${PUBLIC_HOSTNAME}|${PUBLIC_HOSTNAME}|g" -e "s|\\${ALLOWED_DOMAINS}|${ALLOWED_DOMAINS}|g" \\',
2128
- ' deploy/service.yaml > "$RENDERED"',
2129
- 'gcloud run services replace "$RENDERED" --region "$REGION"',
2130
- '',
2131
- 'gcloud run services add-iam-policy-binding "$SERVICE" --region "$REGION" \\',
2132
- ' --member=allUsers --role=roles/run.invoker >/dev/null 2>&1 || true',
2133
- '',
2134
- 'if [[ "$PUBLIC_HOSTNAME" != *.run.app ]]; then \\',
2135
- ' gcloud beta run domain-mappings describe "$PUBLIC_HOSTNAME" --region "$REGION" >/dev/null 2>&1 \\',
2136
- ' || gcloud beta run domain-mappings create --service "$SERVICE" --region "$REGION" --domain "$PUBLIC_HOSTNAME"; \\',
2137
- 'fi',
2138
- '',
2139
- ].join('\n');
2140
- }
2141
- /** Deploy script for the federated edge (A′): build, upsert secrets (no OAuth), replace, make
2142
- * public, map the subdomain. No IAP; no Google OAuth client. */
2143
- function renderCloudrunFedDeploy(ctx) {
2144
- const isCursor = ctx.engine.trim().toLowerCase() === 'cursor';
2145
- const modelNote = ctx.model?.trim() ? `, model ${ctx.model.trim()}` : '';
2146
- const lines = [
2147
- '#!/usr/bin/env bash',
2148
- '# Cloud Run + AgentHippo federated edge (A′): Caddy + auth-broker front the agent.',
2149
- `# Engine: ${ctx.engine}${modelNote}. No oauth2-proxy, no per-service Google OAuth client —`,
2150
- '# Google login happens once at the central IdP (auth.agenthippo.ai); this agent verifies',
2151
- '# the shared .agenthippo.ai AgentHippo JWT (Bearer for IDE, cookie for browser). No IAP.',
2152
- '# The IDE points spec.remote.url straight at https://$PUBLIC_HOSTNAME (same flow as remote-vm).',
2153
- 'set -euo pipefail',
2154
- ...anchorToStagingRoot(),
2155
- `SERVICE="${ctx.packName}"`,
2156
- 'REGION="${REGION:-us-central1}"',
2157
- 'PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project)}"',
2158
- 'REPO="${ARTIFACT_REPO:-agenthippo}"',
2159
- 'IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${SERVICE}:latest"',
2160
- `LITELLM_IMAGE="\${LITELLM_IMAGE:-${agenthippoImage('litellm-gateway')}}"`,
2161
- '# Public hostname = a *.agenthippo.ai subdomain mapped to this service (one-time wildcard DNS).',
2162
- 'PUBLIC_HOSTNAME="${PUBLIC_HOSTNAME:?set PUBLIC_HOSTNAME=<subdomain>.agenthippo.ai}"',
2163
- 'ALLOWED_DOMAINS="${ALLOWED_DOMAINS:-agenthippo.ai}"',
2164
- '# Shared AgentHippo JWT signing key — MUST match the central IdP (auth.agenthippo.ai).',
2165
- '# Copy it from the IdP (or the GCP secret it stores) so tokens issued there verify here.',
2166
- 'AUTH_JWT_SECRET="${AUTH_JWT_SECRET:?set AUTH_JWT_SECRET=<copy from auth.agenthippo.ai>}"',
2167
- ...(isCursor ? [
2168
- 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<cursor api key>}"',
2169
- ] : isAhAutoModel(ctx.model) ? [
2170
- '# LiteLLM proxy key + AgentHippo gateway service key (ahgw_…) for ah-auto routing.',
2171
- 'LITELLM_KEY="${LITELLM_KEY:?set LITELLM_KEY=<litellm proxy key>}"',
2172
- 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<ahgw_… from create-gateway-key on gateway host>}"',
2173
- ] : [
2174
- '# LiteLLM proxy key + the upstream provider key the gateway uses for the model.',
2175
- 'LITELLM_KEY="${LITELLM_KEY:?set LITELLM_KEY=<litellm proxy key>}"',
2176
- 'PROVIDER_KEY="${PROVIDER_KEY:?set PROVIDER_KEY=<upstream provider key>}"',
2177
- ]),
2178
- '',
2179
- 'gcloud artifacts repositories describe "$REPO" --location="$REGION" 2>/dev/null \\',
2180
- ' || gcloud artifacts repositories create "$REPO" --repository-format=docker --location="$REGION"',
2181
- 'gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet',
2182
- '',
2183
- '# One agent image serves both the agent and the auth-broker containers (CLI ships `auth-broker`).',
2184
- '# Rewrite the runtime base to this project Artifact Registry. Customer artifacts pin a',
2185
- '# dated ghcr.io/agenthippoai/runtime tag; mirror that exact tag into AR. No-op if already AR.',
2186
- 'DF_TMP="$(mktemp)"',
2187
- 'sed "s|FROM ghcr.io/agenthippoai/runtime:|FROM ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/runtime:|" deploy/Dockerfile > "$DF_TMP"',
2188
- 'docker build --pull --platform linux/amd64 -f "$DF_TMP" -t "$IMAGE" .',
2189
- 'docker push "$IMAGE"',
2190
- '',
2191
- '# Secrets: caddyfile + shared JWT secret + provider/litellm keys. NO OAuth client secrets.',
2192
- 'ensure_secret() { gcloud secrets describe "$1" >/dev/null 2>&1 || gcloud secrets create "$1" --replication-policy=automatic; }',
2193
- 'ensure_secret "${SERVICE}-caddyfile"; gcloud secrets versions add "${SERVICE}-caddyfile" --data-file=deploy/Caddyfile',
2194
- 'ensure_secret "${SERVICE}-auth-jwt-secret"; printf %s "$AUTH_JWT_SECRET" | gcloud secrets versions add "${SERVICE}-auth-jwt-secret" --data-file=-',
2195
- 'ensure_secret "${SERVICE}-provider-key"; printf %s "$PROVIDER_KEY" | gcloud secrets versions add "${SERVICE}-provider-key" --data-file=-',
2196
- ];
2197
- if (!isCursor) {
2198
- lines.push('ensure_secret "${SERVICE}-otel-config"; gcloud secrets versions add "${SERVICE}-otel-config" --data-file=deploy/otel-collector.yaml', 'ensure_secret "${SERVICE}-litellm-key"; printf %s "$LITELLM_KEY" | gcloud secrets versions add "${SERVICE}-litellm-key" --data-file=-');
2199
- }
2200
- lines.push('', '# Render the multi-container template (sed — no gettext) and deploy.', 'RENDERED="$(mktemp)"', 'sed -e "s|\\${SERVICE}|${SERVICE}|g" -e "s|\\${IMAGE}|${IMAGE}|g" \\', ' -e "s|\\${LITELLM_IMAGE}|${LITELLM_IMAGE}|g" \\', ' -e "s|\\${PUBLIC_HOSTNAME}|${PUBLIC_HOSTNAME}|g" -e "s|\\${ALLOWED_DOMAINS}|${ALLOWED_DOMAINS}|g" \\', ' deploy/service.yaml > "$RENDERED"', 'gcloud run services replace "$RENDERED" --region "$REGION"', '', '# No IAP: Caddy is the gate, so Cloud Run must allow public ingress (allUsers invoker).', 'gcloud run services add-iam-policy-binding "$SERVICE" --region "$REGION" \\', ' --member=allUsers --role=roles/run.invoker >/dev/null 2>&1 || true', '', '# Map the *.agenthippo.ai subdomain (idempotent). Requires one-time wildcard DNS', '# *.agenthippo.ai → Cloud Run (gctrl.run.app). No per-subdomain DNS, no Google OAuth edit.', '# Skip for *.run.app default URLs (no custom mapping needed there).', 'if [[ "$PUBLIC_HOSTNAME" != *.run.app ]]; then \\', ' gcloud beta run domain-mappings describe "$PUBLIC_HOSTNAME" --region "$REGION" >/dev/null 2>&1 \\', ' || gcloud beta run domain-mappings create --service "$SERVICE" --region "$REGION" --domain "$PUBLIC_HOSTNAME"; \\', 'fi', '', '# Grant the runtime SA access to the secrets if needed:', '# SA="$(gcloud run services describe "$SERVICE" --region "$REGION" --format=\'value(spec.template.spec.serviceAccountName)\')"', '# for s in caddyfile auth-jwt-secret provider-key; do', '# gcloud secrets add-iam-policy-binding "${SERVICE}-$s" --member="serviceAccount:$SA" --role=roles/secretmanager.secretAccessor', '# done', '');
2201
- return lines.join('\n');
2202
- }
2203
- /** A′ artifacts: Cloud Run behind the federated AgentHippo edge (Caddy + auth-broker; no oauth2-proxy). */
2204
- function cloudrunFedArtifacts(ctx, inbound, isCursor) {
2205
- const gatewayDirect = usesGatewayDirect(ctx) && !isCursor;
2206
- const sidecars = isCursor
2207
- ? 'Caddy (ingress) + auth-broker + agent.'
2208
- : gatewayDirect
2209
- ? 'Caddy (ingress) + auth-broker + agent (gateway-direct — no LiteLLM/OTEL sidecars).'
2210
- : 'Caddy (ingress) + auth-broker + agent + LiteLLM + OTEL collector (Spotlight).';
2211
- const idp = (ctx.idpUrl || 'https://auth.agenthippo.ai').trim().replace(/\/+$/, '');
2212
- const files = {
2213
- 'Dockerfile': sharedDockerfile(ctx, { wwwDir: '/app/pack/deploy/www' }),
2214
- 'www/index.html': renderLandingPage(ctx.packName),
2215
- 'Caddyfile': (0, identity_1.renderSidecarFedCaddyfile)(ctx.idpUrl),
2216
- 'service.yaml': gatewayDirect ? renderCloudrunFedGatewayServiceYaml(ctx) : renderCloudrunFedServiceYaml(ctx),
2217
- 'cloudrun-deploy.sh': gatewayDirect ? renderCloudrunFedGatewayDeploy(ctx) : renderCloudrunFedDeploy(ctx),
2218
- };
2219
- if (!isCursor && !gatewayDirect) {
2220
- files['otel-collector.yaml'] = OTEL_COLLECTOR_COMPOSE_YAML;
2221
- }
2222
- files['README.md'] = [
2223
- `# Deploy: ${ctx.packName} — Cloud Run + AgentHippo federated edge (A′${gatewayDirect ? ', gateway-direct' : ''})`,
2224
- '',
2225
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}. Runtime base: \`${ctx.image}\`.`,
2226
- '',
2227
- `${sidecars} Sidecars share the pod\'s localhost (Cloud Run multi-container):`,
2228
- '- **caddy** (ingress, the only port) → verifies the shared AgentHippo JWT (Bearer for IDE, cookie for browser)',
2229
- `- **agent** → serves www + /v1/* on localhost:${identity_1.EDGE_AGENT_PORT} (NOT ingress)`,
2230
- '- **auth-broker** → validates the shared `.agenthippo.ai` JWT via `/api/ide/me` (same secret as the IdP)',
2231
- ...(gatewayDirect ? [
2232
- `- **model routing** → agent calls \`${AGENTHIPPO_GATEWAY_BASE_URL}\` directly (ahgw_… service key) — same as colocated agents`,
2233
- '- **no Spotlight sidecars** — traces are not collected in-pod; use gateway/Spotlight fleet analytics instead',
2234
- ] : isCursor ? [] : [
2235
- '- **litellm** gateway → exports OTLP to `http://localhost:4318`',
2236
- '- **otel-collector** → writes JSONL traces to the in-memory `/state` volume',
2237
- ]),
2238
- '',
2239
- '**No oauth2-proxy, no per-service Google OAuth client.** Google login happens once at the',
2240
- `central IdP (${idp}); this agent only verifies the resulting shared \`.agenthippo.ai\` JWT.`,
2241
- 'The IDE points `spec.remote.url` at `https://$PUBLIC_HOSTNAME` and the existing AgentHippo',
2242
- 'SSO flow works (same as `remote-vm`). Browser visits with no cookie are redirected to the',
2243
- 'IdP to log in, then return. Cloud Run is public (`allUsers` invoker) so traffic reaches Caddy.',
2244
- '',
2245
- '```bash',
2246
- 'cp deploy/.env.example deploy/.env && cp deploy/.env.auth.example deploy/.env.auth # fill both',
2247
- 'export PUBLIC_HOSTNAME=<subdomain>.agenthippo.ai # e.g. cs.agenthippo.ai',
2248
- 'export AUTH_JWT_SECRET=<copy from auth.agenthippo.ai> # shared signing key',
2249
- ...(isCursor ? ['export PROVIDER_KEY=<cursor api key>'] : gatewayDirect
2250
- ? ['export PROVIDER_KEY=<ahgw_… from create-gateway-key on gateway host>']
2251
- : isAhAutoModel(ctx.model)
2252
- ? ['export LITELLM_KEY=… PROVIDER_KEY=<ahgw_… from create-gateway-key on gateway host>']
2253
- : ['export LITELLM_KEY=… PROVIDER_KEY=…']),
2254
- 'bash deploy/cloudrun-deploy.sh # builds linux/amd64, upserts secrets, `gcloud run services replace`, maps the subdomain',
2255
- '```',
2256
- '',
2257
- '**One-time setup (not per agent):** wildcard DNS `*.agenthippo.ai` → Cloud Run (`gctrl.run.app`),',
2258
- `and the IdP (${idp}) must run the \`/oauth/browser-login\` route (oauth2-proxy forward_auth →`,
2259
- 'auth-broker sets the `ah_token` cookie on `.agenthippo.ai`). After that, every new agent is',
2260
- gatewayDirect
2261
- ? 'just: `deploy init --ingress edge-fed --model-routing gateway` + this script — zero Console/OAuth work.'
2262
- : 'just: `deploy init --ingress edge-fed` + this script — zero Console/OAuth work.',
2263
- '',
2264
- gatewayDirect
2265
- ? '`service.yaml` placeholders (`${SERVICE} ${IMAGE} ${PUBLIC_HOSTNAME} ${ALLOWED_DOMAINS}`) are substituted by the deploy script. The Caddyfile is stored in Secret Manager (`${SERVICE}-caddyfile`).'
2266
- : '`service.yaml` placeholders (`${SERVICE} ${IMAGE} ${LITELLM_IMAGE} ${PUBLIC_HOSTNAME} ${ALLOWED_DOMAINS}`) are substituted by the deploy script. The Caddyfile is stored in Secret Manager (`${SERVICE}-caddyfile`).',
2267
- '`AGENTHIPPO_AUTH_COOKIE_DOMAIN` / `AGENTHIPPO_AUTH_IDP_URL` are baked in at generation time.',
2268
- '',
2269
- inbound
2270
- ? '**Inbound channel (1b):** `minScale: 1` pins one always-on instance (the socket can\'t scale to zero).'
2271
- : '**No inbound channel (1a):** scales to zero; in-memory `/state` is wiped on cold start (use the `edge` mode + GCS bucket if you need durable state).',
2272
- '',
2273
- ...renderEgressNote(ctx),
2274
- ].join('\n');
2275
- return files;
2276
- }
2277
- function databricksAppArtifacts(ctx) {
2278
- // Databricks Apps take SOURCE, not images — the platform builds it. serve-v3 §5.
2279
- const appYaml = [
2280
- '# Databricks App (serverless source app). Databricks runs `npm ci` then start.',
2281
- 'command: ["node", "start.js"]',
2282
- 'env:',
2283
- ` - name: PORT`,
2284
- ` value: "${ctx.port}"`,
2285
- 'user_authorization:',
2286
- ' # OBO: Databricks forwards X-Forwarded-Access-Token (the asking user\'s',
2287
- ' # downscoped token) so SQL runs as that user → Unity Catalog enforces',
2288
- ' # per-user grants. serve-v3 §11.1.',
2289
- ' scopes: ["sql"]',
2290
- '',
2291
- ].join('\n');
2292
- const pkg = JSON.stringify({
2293
- name: ctx.packName,
2294
- private: true,
2295
- dependencies: {
2296
- [exports.AGENTHIPPO_CLI_NPM_PACKAGE]: resolveAgentHippoCliNpmVersion(),
2297
- '@anthropic-ai/claude-code': exports.CLAUDE_CODE_NPM_VERSION,
2298
- },
2299
- }, null, 2) + '\n';
2300
- const start = [
2301
- "// Databricks App entrypoint: exec serve against the bundled pack.",
2302
- "const { spawn } = require('node:child_process');",
2303
- "const port = process.env.PORT || '" + ctx.port + "';",
2304
- "const child = spawn('agenthippo', [",
2305
- " 'serve', '--agent', './pack',",
2306
- " '--workspace', '/tmp/agenthippo/workspace',",
2307
- " '--state-dir', '/tmp/agenthippo/state',",
2308
- " '--port', port,",
2309
- "], { stdio: 'inherit', env: {",
2310
- " ...process.env,",
2311
- " AGENTHIPPO_SERVE_HOST: '0.0.0.0',",
2312
- " // Databricks Apps workspace SSO sets X-Forwarded-Email; pin serve to trust ONLY that",
2313
- " // header so an authenticated caller cannot impersonate another user via a spoofed",
2314
- " // X-Agent-User header (Databricks does not know about or strip that header).",
2315
- " // Skipped when AGENTHIPPO_PIN_IDENTITY=0 (bootstrap escape hatch) — same idea as",
2316
- " // Cloud Run's ENABLE_IAP=0: pinning to an absent header would make every request",
2317
- " // anonymous instead of leaving body-supplied identity usable during bootstrap.",
2318
- " ...(process.env.AGENTHIPPO_PIN_IDENTITY === '0' ? {} : { AGENTHIPPO_IDENTITY_HEADER: 'x-forwarded-email' }),",
2319
- "} });",
2320
- "child.on('exit', (code) => process.exit(code ?? 0));",
2321
- '',
2322
- ].join('\n');
2323
- return {
2324
- 'app.yaml': appYaml,
2325
- 'package.json': pkg,
2326
- 'start.js': start,
2327
- 'README.md': [
2328
- `# Deploy: ${ctx.packName} — Databricks App (T1-native, the wedge)`,
2329
- '',
2330
- '```bash',
2331
- 'cp -r . ./databricks-app/pack # bundle the pack next to start.js',
2332
- 'databricks apps deploy ' + ctx.packName,
2333
- '```',
2334
- '',
2335
- '**Per-user permissions:** workspace SSO authenticates the user; OBO forwards',
2336
- '`X-Forwarded-Access-Token`, and the SQL tool authenticates to the warehouse as',
2337
- 'that user, so Unity Catalog row filters apply automatically (serve-v3 §11.1).',
2338
- '',
2339
- '> ⚠️ The OBO *credential* seam (per-turn token → SQL tool) is the load-bearing',
2340
- '> spike (serve-v3 §13.2). Without it serve queries as one app identity. Identity',
2341
- '> attribution (`user_id`) works today; per-user *data* enforcement needs the spike.',
2342
- '',
2343
- ...renderEgressNote(ctx),
2344
- ].join('\n'),
2345
- };
2346
- }
2347
- function spcsArtifacts(ctx) {
2348
- const spec = [
2349
- '# Snowpark Container Services spec. Push the runtime image to your account',
2350
- '# registry, then run deploy.sql in a worksheet. serve-v3 §6.',
2351
- 'spec:',
2352
- ' containers:',
2353
- ` - name: ${ctx.packName}`,
2354
- ` image: /<db>/<schema>/<repo>/${ctx.packName}:latest`,
2355
- ' env:',
2356
- ' AGENTHIPPO_NO_AUTO_INSTALL: "1"',
2357
- ' AGENTHIPPO_HOME: /state',
2358
- ' readinessProbe:',
2359
- ` port: ${ctx.port}`,
2360
- ' path: /ready',
2361
- ' # Q1: durable state on a block volume — survives compute-pool suspend/resume.',
2362
- ' volumeMounts:',
2363
- ' - name: state',
2364
- ' mountPath: /state',
2365
- ' volumes:',
2366
- ' - name: state',
2367
- ' source: block',
2368
- ' size: 10Gi',
2369
- ' endpoints:',
2370
- ` - name: api`,
2371
- ` port: ${ctx.port}`,
2372
- ' public: true',
2373
- '',
2374
- ].join('\n');
2375
- const sql = [
2376
- '-- Run in a Snowflake worksheet. SPCS injects an OAuth session token at',
2377
- '-- /snowflake/session/token → the agent queries as the service role (no key-pair).',
2378
- 'CREATE COMPUTE POOL IF NOT EXISTS agenthippo_pool',
2379
- ' MIN_NODES = 1 MAX_NODES = 1 INSTANCE_FAMILY = CPU_X64_S AUTO_SUSPEND_SECS = 600;',
2380
- '',
2381
- '-- Only egress: the model API. Everything else stays inside Snowflake.',
2382
- 'CREATE OR REPLACE NETWORK RULE anthropic_rule',
2383
- " MODE = EGRESS TYPE = HOST_PORT VALUE_LIST = ('api.anthropic.com:443');",
2384
- 'CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION anthropic_eai',
2385
- ' ALLOWED_NETWORK_RULES = (anthropic_rule) ENABLED = TRUE;',
2386
- '',
2387
- `CREATE SERVICE ${ctx.packName}`,
2388
- ' IN COMPUTE POOL agenthippo_pool',
2389
- ` FROM SPECIFICATION_FILE = 'spec.yaml'`,
2390
- ' EXTERNAL_ACCESS_INTEGRATIONS = (anthropic_eai);',
2391
- '',
2392
- ].join('\n');
2393
- return {
2394
- 'Dockerfile': sharedDockerfile(ctx),
2395
- 'spec.yaml': spec,
2396
- 'deploy.sql': sql,
2397
- 'README.md': [
2398
- `# Deploy: ${ctx.packName} — Snowflake SPCS (T1-native)`,
2399
- '',
2400
- '1. Build + push: `docker build -f deploy/Dockerfile -t <account-registry>/.../' + ctx.packName + ':latest . && docker push ...`',
2401
- '2. Upload `spec.yaml` to a stage, then run `deploy.sql` in a worksheet.',
2402
- '',
2403
- 'Zero Snowflake credentials: SPCS injects a session token; the agent queries as the',
2404
- 'service role, so RBAC is the permission system. Security review = "one EAI to api.anthropic.com".',
2405
- '',
2406
- ].join('\n'),
2407
- };
2408
- }
2409
- function cloudflareArtifacts(ctx) {
2410
- const wrangler = JSON.stringify({
2411
- name: ctx.packName,
2412
- compatibility_date: '2026-05-01',
2413
- containers: [
2414
- {
2415
- class_name: 'Agent',
2416
- image: './deploy/Dockerfile',
2417
- max_instances: 1,
2418
- instance_type: { vcpu: 2, memory_mib: 6144, disk_mb: 8192 },
2419
- },
2420
- ],
2421
- durable_objects: { bindings: [{ name: 'AGENT', class_name: 'Agent' }] },
2422
- migrations: [{ tag: 'v1', new_sqlite_classes: ['Agent'] }],
2423
- }, null, 2) + '\n';
2424
- return {
2425
- // Pin identity to X-Agent-User so serve trusts ONLY the header the fronting Worker sets —
2426
- // unpinned, serve falls back to the same header name as an alias and a caller who reaches
2427
- // the container directly (bypassing the Worker/Access) could spoof it themselves.
2428
- 'Dockerfile': sharedDockerfile(ctx, { extraEnv: { AGENTHIPPO_IDENTITY_HEADER: 'x-agent-user' } }),
2429
- 'wrangler.jsonc': wrangler,
2430
- 'README.md': [
2431
- `# Deploy: ${ctx.packName} — Cloudflare Container (T1-generic)`,
2432
- '',
2433
- '`wrangler deploy` builds the Dockerfile and ships the container (scale-to-zero).',
2434
- 'Front it with a Worker that does Cloudflare Access (SSO) and sets `X-Agent-User`,',
2435
- 'then proxies to the container binding. For the *external/unauthenticated* case',
2436
- '(customer support), use the full reference at `examples/deploy/external-support/`.',
2437
- '',
2438
- '**Identity pin:** the container trusts *only* `X-Agent-User` (`AGENTHIPPO_IDENTITY_HEADER`,',
2439
- 'baked into the image) — the Worker must set it after Cloudflare Access verifies the user.',
2440
- 'Requests without it resolve to no identity, even if the container is ever reachable',
2441
- 'directly, bypassing the Worker.',
2442
- '',
2443
- '**Egress:** this container only needs outbound access to your model provider',
2444
- '(e.g. `api.anthropic.com:443`) — restrict it with a Cloudflare Worker/Zero Trust',
2445
- 'egress policy if your org requires one.',
2446
- '',
2447
- ].join('\n'),
2448
- };
2449
- }
2450
- /**
2451
- * AWS ECS Fargate (T1-generic, `d8-aws-adapter`). Fargate's `awsvpc` network mode shares one
2452
- * task's network namespace across its containers — architecturally identical to Cloud Run's
2453
- * sidecar model, so the edge/edge-fed Caddyfiles and `EDGE_AGENT_PORT` convention are reused
2454
- * verbatim (PLAN/deploy.md §5, "what adding AWS looks like"): no new identity code, just a new
2455
- * platform adapter around the same shared identity layer.
2456
- *
2457
- * Scope: identity/exposure only (bare / edge / edge-fed) — no Spotlight/LiteLLM sidecar variant
2458
- * yet (that's the orthogonal model-gateway layer, not part of this pass). Newer and far less
2459
- * exercised than the Cloud Run path — read `deploy.sh` before running it against a real account.
2460
- */
2461
- function awsEcsArtifacts(ctx) {
2462
- const fed = ctx.ingress === 'edge-fed';
2463
- const edge = ctx.ingress === 'edge' || fed;
2464
- const providerKeyEnv = litellmProviderKeyEnv(ctx.model);
2465
- if (!edge) {
2466
- // Bare single-container task — sits behind whatever ingress (ALB, API Gateway, internal
2467
- // VPC access) the org already runs, same positioning as cloudflare/databricks-app/spcs.
2468
- const taskDef = {
2469
- family: '${SERVICE}',
2470
- networkMode: 'awsvpc',
2471
- requiresCompatibilities: ['FARGATE'],
2472
- cpu: '512',
2473
- memory: '1024',
2474
- containerDefinitions: [
2475
- {
2476
- name: 'agent',
2477
- image: '${IMAGE}',
2478
- essential: true,
2479
- portMappings: [{ containerPort: ctx.port, protocol: 'tcp' }],
2480
- environment: [{ name: 'AGENTHIPPO_NO_AUTO_INSTALL', value: '1' }],
2481
- secrets: [{ name: providerKeyEnv, valueFrom: '${PROVIDER_KEY_SECRET_ARN}' }],
2482
- logConfiguration: {
2483
- logDriver: 'awslogs',
2484
- options: {
2485
- 'awslogs-group': '/ecs/${SERVICE}',
2486
- 'awslogs-region': '${REGION}',
2487
- 'awslogs-stream-prefix': 'agent',
2488
- },
2489
- },
2490
- },
2491
- ],
2492
- };
2493
- const deploy = [
2494
- '#!/usr/bin/env bash',
2495
- '# T1-generic: AWS ECS Fargate, single container — bring your own ingress (ALB/API',
2496
- '# Gateway/internal VPC). For a self-contained ALB + AgentHippo edge, use --ingress edge',
2497
- '# or --ingress edge-fed instead.',
2498
- 'set -euo pipefail',
2499
- ...anchorToStagingRoot(),
2500
- 'SERVICE="' + ctx.packName + '"',
2501
- 'REGION="${AWS_REGION:-us-east-1}"',
2502
- 'ACCOUNT_ID="${ACCOUNT_ID:-$(aws sts get-caller-identity --query Account --output text)}"',
2503
- 'REPO="agenthippo/${SERVICE}"',
2504
- 'IMAGE="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:latest"',
2505
- '',
2506
- 'aws ecr describe-repositories --repository-names "$REPO" --region "$REGION" >/dev/null 2>&1 \\',
2507
- ' || aws ecr create-repository --repository-name "$REPO" --region "$REGION"',
2508
- 'aws ecr get-login-password --region "$REGION" \\',
2509
- ' | docker login --username AWS --password-stdin "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"',
2510
- '',
2511
- 'docker build --platform linux/amd64 -f deploy/Dockerfile -t "$IMAGE" .',
2512
- 'docker push "$IMAGE"',
2513
- '',
2514
- `PROVIDER_KEY_SECRET_ARN="\${PROVIDER_KEY_SECRET_ARN:?set PROVIDER_KEY_SECRET_ARN — Secrets Manager ARN holding ${providerKeyEnv}}"`,
2515
- 'TASK_JSON="$(sed -e "s|\\${IMAGE}|${IMAGE}|g" -e "s|\\${SERVICE}|${SERVICE}|g" \\',
2516
- ' -e "s|\\${REGION}|${REGION}|g" -e "s|\\${PROVIDER_KEY_SECRET_ARN}|${PROVIDER_KEY_SECRET_ARN}|g" \\',
2517
- ' deploy/task-definition.json)"',
2518
- 'aws ecs register-task-definition --region "$REGION" --cli-input-json "$TASK_JSON" >/dev/null',
2519
- '',
2520
- 'CLUSTER="${CLUSTER:?set CLUSTER — the ECS cluster to run in}"',
2521
- 'aws ecs describe-services --cluster "$CLUSTER" --services "$SERVICE" --region "$REGION" 2>/dev/null \\',
2522
- ' | grep -q \'"status": "ACTIVE"\' \\',
2523
- ' && aws ecs update-service --cluster "$CLUSTER" --service "$SERVICE" \\',
2524
- ' --task-definition "$SERVICE" --region "$REGION" >/dev/null \\',
2525
- ' || aws ecs create-service --cluster "$CLUSTER" --service-name "$SERVICE" \\',
2526
- ' --task-definition "$SERVICE" --desired-count 1 --launch-type FARGATE \\',
2527
- ' --network-configuration "awsvpcConfiguration={subnets=[${SUBNETS:?set SUBNETS}],securityGroups=[${SECURITY_GROUPS:?set SECURITY_GROUPS}],assignPublicIp=ENABLED}" \\',
2528
- ' --region "$REGION" >/dev/null',
2529
- 'echo "Deployed $SERVICE to ECS cluster $CLUSTER"',
2530
- '',
2531
- ].join('\n');
2532
- return {
2533
- 'Dockerfile': sharedDockerfile(ctx),
2534
- 'task-definition.json': JSON.stringify(taskDef, null, 2) + '\n',
2535
- 'deploy.sh': deploy,
2536
- 'README.md': [
2537
- `# Deploy: ${ctx.packName} — AWS ECS Fargate (T1-generic)`,
2538
- '',
2539
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}.`,
2540
- '',
2541
- 'Single container, no ingress opinion — front it with whatever your org already runs',
2542
- '(ALB, API Gateway, internal VPC access). Use `--ingress edge` or `--ingress edge-fed`',
2543
- 'for a self-contained ALB + AgentHippo edge instead.',
2544
- '',
2545
- '```bash',
2546
- 'export CLUSTER=<ecs-cluster> SUBNETS=<subnet-ids> SECURITY_GROUPS=<sg-ids>',
2547
- 'export PROVIDER_KEY_SECRET_ARN=<Secrets Manager ARN for your model provider key>',
2548
- 'bash deploy/deploy.sh',
2549
- '```',
2550
- '',
2551
- '> ⚠️ Newer and far less exercised than the Cloud Run target — read `deploy.sh` before',
2552
- '> running it against a real account.',
2553
- '',
2554
- ...renderEgressNote(ctx),
2555
- ].join('\n'),
2556
- };
2557
- }
2558
- // Edge / edge-fed: Caddy + (self-issued: oauth2-proxy) + auth-broker + agent, one Fargate
2559
- // task (awsvpc network mode = shared localhost, exactly like Cloud Run sidecars) — reuses
2560
- // the identical Caddyfiles Cloud Run already renders.
2561
- const containers = [
2562
- {
2563
- name: 'caddy',
2564
- image: 'caddy:2.8-alpine',
2565
- essential: true,
2566
- portMappings: [{ containerPort: 8080, protocol: 'tcp' }],
2567
- environment: [{ name: 'AGENTHIPPO_PUBLIC_HOSTNAME', value: ctx.publicHostname }],
2568
- // Caddyfile is written to a mounted config volume by the deploy script (sed-templated,
2569
- // same "checked-in template, script substitutes it" pattern as the Cloud Run edge).
2570
- mountPoints: [{ sourceVolume: 'caddy-config', containerPath: '/etc/caddy' }],
2571
- logConfiguration: {
2572
- logDriver: 'awslogs',
2573
- options: { 'awslogs-group': '/ecs/${SERVICE}', 'awslogs-region': '${REGION}', 'awslogs-stream-prefix': 'caddy' },
2574
- },
2575
- },
2576
- ...(fed ? [] : [{
2577
- name: 'oauth2-proxy',
2578
- image: 'quay.io/oauth2-proxy/oauth2-proxy:v7.7.1',
2579
- essential: true,
2580
- environment: [
2581
- { name: 'OAUTH2_PROXY_HTTP_ADDRESS', value: '0.0.0.0:4180' },
2582
- { name: 'OAUTH2_PROXY_UPSTREAMS', value: 'file:///dev/null' },
2583
- { name: 'OAUTH2_PROXY_SET_XAUTHREQUEST', value: 'true' },
2584
- { name: 'OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER', value: 'true' },
2585
- { name: 'OAUTH2_PROXY_SKIP_PROVIDER_BUTTON', value: 'true' },
2586
- { name: 'OAUTH2_PROXY_REVERSE_PROXY', value: 'true' },
2587
- ],
2588
- secrets: [
2589
- { name: 'OAUTH2_PROXY_CLIENT_ID', valueFrom: '${OAUTH_CLIENT_ID_SECRET_ARN}' },
2590
- { name: 'OAUTH2_PROXY_CLIENT_SECRET', valueFrom: '${OAUTH_CLIENT_SECRET_SECRET_ARN}' },
2591
- { name: 'OAUTH2_PROXY_COOKIE_SECRET', valueFrom: '${OAUTH_COOKIE_SECRET_SECRET_ARN}' },
2592
- ],
2593
- logConfiguration: {
2594
- logDriver: 'awslogs',
2595
- options: { 'awslogs-group': '/ecs/${SERVICE}', 'awslogs-region': '${REGION}', 'awslogs-stream-prefix': 'oauth2-proxy' },
2596
- },
2597
- }]),
2598
- {
2599
- name: 'auth-broker',
2600
- image: '${IMAGE}',
2601
- essential: true,
2602
- command: ['auth-broker'],
2603
- environment: [
2604
- { name: 'AGENTHIPPO_AUTH_PORT', value: '9780' },
2605
- ...(fed ? [
2606
- { name: 'AGENTHIPPO_AUTH_COOKIE_DOMAIN', value: ctx.cookieDomain || '' },
2607
- { name: 'AGENTHIPPO_AUTH_IDP_URL', value: ctx.idpUrl || '' },
2608
- ] : []),
2609
- ],
2610
- secrets: [{ name: 'AGENTHIPPO_AUTH_JWT_SECRET', valueFrom: '${AUTH_JWT_SECRET_SECRET_ARN}' }],
2611
- logConfiguration: {
2612
- logDriver: 'awslogs',
2613
- options: { 'awslogs-group': '/ecs/${SERVICE}', 'awslogs-region': '${REGION}', 'awslogs-stream-prefix': 'auth-broker' },
2614
- },
2615
- },
2616
- {
2617
- name: 'agent',
2618
- image: '${IMAGE}',
2619
- essential: true,
2620
- command: serveCmdArgs(ctx, { wwwDir: '/app/pack/deploy/www', port: identity_1.EDGE_AGENT_PORT }),
2621
- environment: [{ name: 'AGENTHIPPO_NO_AUTO_INSTALL', value: '1' }],
2622
- secrets: [{ name: providerKeyEnv, valueFrom: '${PROVIDER_KEY_SECRET_ARN}' }],
2623
- logConfiguration: {
2624
- logDriver: 'awslogs',
2625
- options: { 'awslogs-group': '/ecs/${SERVICE}', 'awslogs-region': '${REGION}', 'awslogs-stream-prefix': 'agent' },
2626
- },
2627
- },
2628
- ];
2629
- const taskDef = {
2630
- family: '${SERVICE}',
2631
- networkMode: 'awsvpc',
2632
- requiresCompatibilities: ['FARGATE'],
2633
- cpu: '1024',
2634
- memory: '2048',
2635
- volumes: [{ name: 'caddy-config' }],
2636
- containerDefinitions: containers,
2637
- };
2638
- const secretArgs = fed
2639
- ? 'PROVIDER_KEY_SECRET_ARN=<...> AUTH_JWT_SECRET_SECRET_ARN=<...>'
2640
- : 'PROVIDER_KEY_SECRET_ARN=<...> AUTH_JWT_SECRET_SECRET_ARN=<...> ' +
2641
- 'OAUTH_CLIENT_ID_SECRET_ARN=<...> OAUTH_CLIENT_SECRET_SECRET_ARN=<...> OAUTH_COOKIE_SECRET_SECRET_ARN=<...>';
2642
- const deploy = [
2643
- '#!/usr/bin/env bash',
2644
- `# AWS ECS Fargate + AgentHippo edge (${fed ? 'edge-fed: federated identity, no oauth2-proxy' : 'edge: self-issued'}).`,
2645
- '# One-time setup (not per deploy): create an ALB + target group (protocol HTTP, port 8080,',
2646
- '# target type ip) pointed at this service, and an HTTPS listener forwarding to it — ALB',
2647
- '# terminates TLS, same as Cloud Run\'s edge path (Caddy listens on plain HTTP behind it).',
2648
- 'set -euo pipefail',
2649
- ...anchorToStagingRoot(),
2650
- 'SERVICE="' + ctx.packName + '"',
2651
- 'REGION="${AWS_REGION:-us-east-1}"',
2652
- 'ACCOUNT_ID="${ACCOUNT_ID:-$(aws sts get-caller-identity --query Account --output text)}"',
2653
- 'REPO="agenthippo/${SERVICE}"',
2654
- 'IMAGE="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:latest"',
2655
- '',
2656
- 'aws ecr describe-repositories --repository-names "$REPO" --region "$REGION" >/dev/null 2>&1 \\',
2657
- ' || aws ecr create-repository --repository-name "$REPO" --region "$REGION"',
2658
- 'aws ecr get-login-password --region "$REGION" \\',
2659
- ' | docker login --username AWS --password-stdin "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"',
2660
- '',
2661
- '# auth-broker reuses the agent image (CLI ships `auth-broker`).',
2662
- 'docker build --platform linux/amd64 -f deploy/Dockerfile -t "$IMAGE" .',
2663
- 'docker push "$IMAGE"',
2664
- '',
2665
- 'PROVIDER_KEY_SECRET_ARN="${PROVIDER_KEY_SECRET_ARN:?set PROVIDER_KEY_SECRET_ARN}"',
2666
- 'AUTH_JWT_SECRET_SECRET_ARN="${AUTH_JWT_SECRET_SECRET_ARN:?set AUTH_JWT_SECRET_SECRET_ARN}"',
2667
- ...(fed ? [] : [
2668
- 'OAUTH_CLIENT_ID_SECRET_ARN="${OAUTH_CLIENT_ID_SECRET_ARN:?set OAUTH_CLIENT_ID_SECRET_ARN}"',
2669
- 'OAUTH_CLIENT_SECRET_SECRET_ARN="${OAUTH_CLIENT_SECRET_SECRET_ARN:?set OAUTH_CLIENT_SECRET_SECRET_ARN}"',
2670
- 'OAUTH_COOKIE_SECRET_SECRET_ARN="${OAUTH_COOKIE_SECRET_SECRET_ARN:?set OAUTH_COOKIE_SECRET_SECRET_ARN}"',
2671
- ]),
2672
- 'TASK_JSON="$(sed -e "s|\\${IMAGE}|${IMAGE}|g" -e "s|\\${SERVICE}|${SERVICE}|g" -e "s|\\${REGION}|${REGION}|g" \\',
2673
- ' -e "s|\\${PROVIDER_KEY_SECRET_ARN}|${PROVIDER_KEY_SECRET_ARN}|g" \\',
2674
- ' -e "s|\\${AUTH_JWT_SECRET_SECRET_ARN}|${AUTH_JWT_SECRET_SECRET_ARN}|g" \\',
2675
- ...(fed ? [] : [
2676
- ' -e "s|\\${OAUTH_CLIENT_ID_SECRET_ARN}|${OAUTH_CLIENT_ID_SECRET_ARN}|g" \\',
2677
- ' -e "s|\\${OAUTH_CLIENT_SECRET_SECRET_ARN}|${OAUTH_CLIENT_SECRET_SECRET_ARN}|g" \\',
2678
- ' -e "s|\\${OAUTH_COOKIE_SECRET_SECRET_ARN}|${OAUTH_COOKIE_SECRET_SECRET_ARN}|g" \\',
2679
- ]),
2680
- ' deploy/task-definition.json)"',
2681
- 'aws ecs register-task-definition --region "$REGION" --cli-input-json "$TASK_JSON" >/dev/null',
2682
- '',
2683
- 'CLUSTER="${CLUSTER:?set CLUSTER}"',
2684
- 'TARGET_GROUP_ARN="${TARGET_GROUP_ARN:?set TARGET_GROUP_ARN — from the one-time ALB setup above}"',
2685
- 'aws ecs describe-services --cluster "$CLUSTER" --services "$SERVICE" --region "$REGION" 2>/dev/null \\',
2686
- ' | grep -q \'"status": "ACTIVE"\' \\',
2687
- ' && aws ecs update-service --cluster "$CLUSTER" --service "$SERVICE" \\',
2688
- ' --task-definition "$SERVICE" --region "$REGION" >/dev/null \\',
2689
- ' || aws ecs create-service --cluster "$CLUSTER" --service-name "$SERVICE" \\',
2690
- ' --task-definition "$SERVICE" --desired-count 1 --launch-type FARGATE \\',
2691
- ' --load-balancers "targetGroupArn=$TARGET_GROUP_ARN,containerName=caddy,containerPort=8080" \\',
2692
- ' --network-configuration "awsvpcConfiguration={subnets=[${SUBNETS:?set SUBNETS}],securityGroups=[${SECURITY_GROUPS:?set SECURITY_GROUPS}],assignPublicIp=ENABLED}" \\',
2693
- ' --region "$REGION" >/dev/null',
2694
- 'echo "Deployed $SERVICE behind the AgentHippo edge on ECS cluster $CLUSTER"',
2695
- '',
2696
- ].join('\n');
2697
- return {
2698
- 'Dockerfile': sharedDockerfile(ctx, { wwwDir: '/app/pack/deploy/www' }),
2699
- 'www/index.html': renderLandingPage(ctx.packName),
2700
- 'Caddyfile': fed ? (0, identity_1.renderSidecarFedCaddyfile)(ctx.idpUrl) : (0, identity_1.renderSidecarEdgeCaddyfile)(),
2701
- 'task-definition.json': JSON.stringify(taskDef, null, 2) + '\n',
2702
- 'deploy.sh': deploy,
2703
- 'README.md': [
2704
- `# Deploy: ${ctx.packName} — AWS ECS Fargate + AgentHippo edge (${fed ? 'edge-fed' : 'edge'})`,
2705
- '',
2706
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}.`,
2707
- '',
2708
- 'One Fargate task, containers share the task\'s localhost (`awsvpc` network mode) —',
2709
- 'the same sidecar shape Cloud Run uses, so the Caddyfile is identical:',
2710
- '- **caddy** (ingress, the only published port) → ' + (fed
2711
- ? 'verifies the shared AgentHippo JWT'
2712
- : 'SSO edge: oauth2-proxy for browsers, auth-broker for IDE Bearer'),
2713
- `- **agent** → serves www + /v1/* on localhost:${identity_1.EDGE_AGENT_PORT} (not published)`,
2714
- '- **auth-broker** → validates IDE AgentHippo JWTs (`/api/ide/me`)' + (fed ? ' against the shared IdP secret' : ''),
2715
- ...(fed ? [] : ['- **oauth2-proxy** → Google SSO for browser clients']),
2716
- '',
2717
- '**One-time setup (not per deploy):** an ALB + HTTPS listener + target group (HTTP, port',
2718
- '8080, target type `ip`) pointed at this service — ALB terminates TLS, Caddy stays HTTP.',
2719
- '',
2720
- '```bash',
2721
- `export CLUSTER=<ecs-cluster> SUBNETS=<subnet-ids> SECURITY_GROUPS=<sg-ids> TARGET_GROUP_ARN=<from ALB setup>`,
2722
- `export ${secretArgs}`,
2723
- 'bash deploy/deploy.sh',
2724
- '```',
2725
- '',
2726
- '> ⚠️ Newer and far less exercised than the Cloud Run target — read `deploy.sh` before',
2727
- '> running it against a real account.',
2728
- '',
2729
- ...renderEgressNote(ctx),
2730
- ].join('\n'),
2731
- };
2732
- }
2733
- /**
2734
- * Azure Container Apps (T1-generic, `d9-azure-adapter`). One Container App's containers run
2735
- * as a single replica sharing localhost — the same sidecar shape as Cloud Run/ECS Fargate, so
2736
- * the edge/edge-fed Caddyfiles and `EDGE_AGENT_PORT` convention are reused verbatim again
2737
- * (PLAN/deploy.md §5). Container Apps environments provide built-in ingress + TLS termination,
2738
- * so unlike aws-ecs there's no separate one-time load-balancer setup — `--target-port` on the
2739
- * container app itself is the whole ingress story.
2740
- *
2741
- * Scope: identity/exposure only (bare / edge / edge-fed), same as aws-ecs — no Spotlight/LiteLLM
2742
- * sidecar variant yet. Newer and far less exercised than the Cloud Run path.
2743
- */
2744
- function azureContainerAppsArtifacts(ctx) {
2745
- const fed = ctx.ingress === 'edge-fed';
2746
- const edge = ctx.ingress === 'edge' || fed;
2747
- const providerKeyEnv = litellmProviderKeyEnv(ctx.model);
2748
- const PORT = 8080;
2749
- if (!edge) {
2750
- const spec = {
2751
- properties: {
2752
- configuration: {
2753
- ingress: { external: true, targetPort: ctx.port },
2754
- secrets: [{ name: 'provider-key', value: '${PROVIDER_KEY}' }],
2755
- },
2756
- template: {
2757
- containers: [
2758
- {
2759
- name: 'agent',
2760
- image: '${IMAGE}',
2761
- env: [
2762
- { name: 'AGENTHIPPO_NO_AUTO_INSTALL', value: '1' },
2763
- { name: providerKeyEnv, secretRef: 'provider-key' },
2764
- ],
2765
- resources: { cpu: 0.5, memory: '1Gi' },
2766
- },
2767
- ],
2768
- },
2769
- },
2770
- };
2771
- const deploy = [
2772
- '#!/usr/bin/env bash',
2773
- '# T1-generic: Azure Container Apps, single container — built-in ingress + TLS, no separate',
2774
- '# load balancer to stand up. For a self-contained AgentHippo edge, use --ingress edge or',
2775
- '# --ingress edge-fed instead.',
2776
- 'set -euo pipefail',
2777
- ...anchorToStagingRoot(),
2778
- 'SERVICE="' + ctx.packName + '"',
2779
- 'RESOURCE_GROUP="${RESOURCE_GROUP:?set RESOURCE_GROUP}"',
2780
- 'ENVIRONMENT="${CONTAINERAPPS_ENVIRONMENT:?set CONTAINERAPPS_ENVIRONMENT — az containerapp env create (one-time)}"',
2781
- 'ACR_NAME="${ACR_NAME:?set ACR_NAME — an Azure Container Registry (one-time: az acr create)}"',
2782
- 'IMAGE="${ACR_NAME}.azurecr.io/${SERVICE}:latest"',
2783
- `PROVIDER_KEY="\${PROVIDER_KEY:?set PROVIDER_KEY — your ${providerKeyEnv}}"`,
2784
- '',
2785
- '# Builds in ACR (no local docker login/push step needed).',
2786
- 'az acr build --registry "$ACR_NAME" --image "${SERVICE}:latest" --file deploy/Dockerfile .',
2787
- '',
2788
- 'az containerapp show --name "$SERVICE" --resource-group "$RESOURCE_GROUP" >/dev/null 2>&1 \\',
2789
- ' && az containerapp update --name "$SERVICE" --resource-group "$RESOURCE_GROUP" \\',
2790
- ' --image "$IMAGE" >/dev/null \\',
2791
- ' || az containerapp create --name "$SERVICE" --resource-group "$RESOURCE_GROUP" \\',
2792
- ' --environment "$ENVIRONMENT" --image "$IMAGE" \\',
2793
- ' --target-port ' + ctx.port + ' --ingress external \\',
2794
- ' --secrets "provider-key=$PROVIDER_KEY" \\',
2795
- ` --env-vars "AGENTHIPPO_NO_AUTO_INSTALL=1" "${providerKeyEnv}=secretref:provider-key" \\`,
2796
- ' >/dev/null',
2797
- 'echo "Deployed $SERVICE to Container Apps environment $ENVIRONMENT"',
2798
- '',
2799
- ].join('\n');
2800
- return {
2801
- 'Dockerfile': sharedDockerfile(ctx),
2802
- 'containerapp.json': JSON.stringify(spec, null, 2) + '\n',
2803
- 'deploy.sh': deploy,
2804
- 'README.md': [
2805
- `# Deploy: ${ctx.packName} — Azure Container Apps (T1-generic)`,
2806
- '',
2807
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}.`,
2808
- '',
2809
- 'Single container, built-in ingress + TLS — no separate load balancer to stand up.',
2810
- 'Use `--ingress edge` or `--ingress edge-fed` for a self-contained AgentHippo edge instead.',
2811
- '',
2812
- '```bash',
2813
- 'export RESOURCE_GROUP=<rg> CONTAINERAPPS_ENVIRONMENT=<env> ACR_NAME=<registry>',
2814
- `export PROVIDER_KEY=<your ${providerKeyEnv}>`,
2815
- 'bash deploy/deploy.sh',
2816
- '```',
2817
- '',
2818
- '> ⚠️ Newer and far less exercised than the Cloud Run target — read `deploy.sh` before',
2819
- '> running it against a real account.',
2820
- '',
2821
- ...renderEgressNote(ctx),
2822
- ].join('\n'),
2823
- };
2824
- }
2825
- // Edge / edge-fed: Caddy + (self-issued: oauth2-proxy) + auth-broker + agent, one Container
2826
- // App (containers in one replica share localhost, same sidecar shape as Cloud Run/ECS) —
2827
- // reuses the identical Caddyfiles Cloud Run already renders.
2828
- const containers = [
2829
- {
2830
- name: 'caddy',
2831
- image: 'caddy:2.8-alpine',
2832
- env: [{ name: 'PORT', value: String(PORT) }, { name: 'AGENTHIPPO_PUBLIC_HOSTNAME', value: ctx.publicHostname }],
2833
- resources: { cpu: 0.25, memory: '0.5Gi' },
2834
- },
2835
- ...(fed ? [] : [{
2836
- name: 'oauth2-proxy',
2837
- image: 'quay.io/oauth2-proxy/oauth2-proxy:v7.7.1',
2838
- env: [
2839
- { name: 'OAUTH2_PROXY_HTTP_ADDRESS', value: '0.0.0.0:4180' },
2840
- { name: 'OAUTH2_PROXY_UPSTREAMS', value: 'file:///dev/null' },
2841
- { name: 'OAUTH2_PROXY_SET_XAUTHREQUEST', value: 'true' },
2842
- { name: 'OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER', value: 'true' },
2843
- { name: 'OAUTH2_PROXY_SKIP_PROVIDER_BUTTON', value: 'true' },
2844
- { name: 'OAUTH2_PROXY_REVERSE_PROXY', value: 'true' },
2845
- { name: 'OAUTH2_PROXY_CLIENT_ID', secretRef: 'oauth-client-id' },
2846
- { name: 'OAUTH2_PROXY_CLIENT_SECRET', secretRef: 'oauth-client-secret' },
2847
- { name: 'OAUTH2_PROXY_COOKIE_SECRET', secretRef: 'oauth-cookie-secret' },
2848
- ],
2849
- resources: { cpu: 0.25, memory: '0.5Gi' },
2850
- }]),
2851
- {
2852
- name: 'auth-broker',
2853
- image: '${IMAGE}',
2854
- command: ['agenthippo', 'auth-broker'],
2855
- env: [
2856
- { name: 'AGENTHIPPO_AUTH_PORT', value: '9780' },
2857
- { name: 'AGENTHIPPO_AUTH_JWT_SECRET', secretRef: 'auth-jwt-secret' },
2858
- ...(fed ? [
2859
- { name: 'AGENTHIPPO_AUTH_COOKIE_DOMAIN', value: ctx.cookieDomain || '' },
2860
- { name: 'AGENTHIPPO_AUTH_IDP_URL', value: ctx.idpUrl || '' },
2861
- ] : []),
2862
- ],
2863
- resources: { cpu: 0.25, memory: '0.5Gi' },
2864
- },
2865
- {
2866
- name: 'agent',
2867
- image: '${IMAGE}',
2868
- command: serveCmdArgs(ctx, { wwwDir: '/app/pack/deploy/www', port: identity_1.EDGE_AGENT_PORT }),
2869
- env: [
2870
- { name: 'AGENTHIPPO_NO_AUTO_INSTALL', value: '1' },
2871
- { name: providerKeyEnv, secretRef: 'provider-key' },
2872
- ],
2873
- resources: { cpu: 0.5, memory: '1Gi' },
2874
- },
2875
- ];
2876
- const spec = {
2877
- properties: {
2878
- configuration: {
2879
- ingress: { external: true, targetPort: PORT },
2880
- secrets: [
2881
- { name: 'auth-jwt-secret', value: '${AUTH_JWT_SECRET}' },
2882
- { name: 'provider-key', value: '${PROVIDER_KEY}' },
2883
- ...(fed ? [] : [
2884
- { name: 'oauth-client-id', value: '${OAUTH_CLIENT_ID}' },
2885
- { name: 'oauth-client-secret', value: '${OAUTH_CLIENT_SECRET}' },
2886
- { name: 'oauth-cookie-secret', value: '${OAUTH_COOKIE_SECRET}' },
2887
- ]),
2888
- ],
2889
- },
2890
- template: { containers },
2891
- },
2892
- };
2893
- const secretExports = [
2894
- 'AUTH_JWT_SECRET="${AUTH_JWT_SECRET:?set AUTH_JWT_SECRET}"',
2895
- `PROVIDER_KEY="\${PROVIDER_KEY:?set PROVIDER_KEY — your ${providerKeyEnv}}"`,
2896
- ...(fed ? [] : [
2897
- 'OAUTH_CLIENT_ID="${OAUTH_CLIENT_ID:?set OAUTH_CLIENT_ID}"',
2898
- 'OAUTH_CLIENT_SECRET="${OAUTH_CLIENT_SECRET:?set OAUTH_CLIENT_SECRET}"',
2899
- 'OAUTH_COOKIE_SECRET="${OAUTH_COOKIE_SECRET:?set OAUTH_COOKIE_SECRET}"',
2900
- ]),
2901
- ];
2902
- const deploy = [
2903
- '#!/usr/bin/env bash',
2904
- `# Azure Container Apps + AgentHippo edge (${fed ? 'edge-fed: federated identity, no oauth2-proxy' : 'edge: self-issued'}).`,
2905
- '# Container Apps environments provide built-in ingress + TLS — no separate load balancer.',
2906
- 'set -euo pipefail',
2907
- ...anchorToStagingRoot(),
2908
- 'SERVICE="' + ctx.packName + '"',
2909
- 'RESOURCE_GROUP="${RESOURCE_GROUP:?set RESOURCE_GROUP}"',
2910
- 'ENVIRONMENT="${CONTAINERAPPS_ENVIRONMENT:?set CONTAINERAPPS_ENVIRONMENT — az containerapp env create (one-time)}"',
2911
- 'ACR_NAME="${ACR_NAME:?set ACR_NAME — an Azure Container Registry (one-time: az acr create)}"',
2912
- 'IMAGE="${ACR_NAME}.azurecr.io/${SERVICE}:latest"',
2913
- '',
2914
- '# auth-broker reuses the agent image (CLI ships `auth-broker`). Builds in ACR.',
2915
- 'az acr build --registry "$ACR_NAME" --image "${SERVICE}:latest" --file deploy/Dockerfile .',
2916
- '',
2917
- ...secretExports,
2918
- 'SPEC_JSON="$(sed -e "s|\\${IMAGE}|${IMAGE}|g" -e "s|\\${AUTH_JWT_SECRET}|${AUTH_JWT_SECRET}|g" \\',
2919
- ' -e "s|\\${PROVIDER_KEY}|${PROVIDER_KEY}|g" \\',
2920
- ...(fed ? [] : [
2921
- ' -e "s|\\${OAUTH_CLIENT_ID}|${OAUTH_CLIENT_ID}|g" -e "s|\\${OAUTH_CLIENT_SECRET}|${OAUTH_CLIENT_SECRET}|g" \\',
2922
- ' -e "s|\\${OAUTH_COOKIE_SECRET}|${OAUTH_COOKIE_SECRET}|g" \\',
2923
- ]),
2924
- ' deploy/containerapp.json)"',
2925
- 'echo "$SPEC_JSON" > /tmp/${SERVICE}-containerapp.json',
2926
- '',
2927
- '# `--yaml` also accepts JSON (JSON is valid YAML) — kept as .json so it\'s reviewable',
2928
- '# alongside the other checked-in templates without a format switch.',
2929
- 'az containerapp show --name "$SERVICE" --resource-group "$RESOURCE_GROUP" >/dev/null 2>&1 \\',
2930
- ' && az containerapp update --name "$SERVICE" --resource-group "$RESOURCE_GROUP" \\',
2931
- ' --yaml /tmp/${SERVICE}-containerapp.json >/dev/null \\',
2932
- ' || az containerapp create --name "$SERVICE" --resource-group "$RESOURCE_GROUP" \\',
2933
- ' --environment "$ENVIRONMENT" --yaml /tmp/${SERVICE}-containerapp.json >/dev/null',
2934
- 'echo "Deployed $SERVICE behind the AgentHippo edge in $ENVIRONMENT"',
2935
- '',
2936
- ].join('\n');
2937
- return {
2938
- 'Dockerfile': sharedDockerfile(ctx, { wwwDir: '/app/pack/deploy/www' }),
2939
- 'www/index.html': renderLandingPage(ctx.packName),
2940
- 'Caddyfile': fed ? (0, identity_1.renderSidecarFedCaddyfile)(ctx.idpUrl) : (0, identity_1.renderSidecarEdgeCaddyfile)(),
2941
- 'containerapp.json': JSON.stringify(spec, null, 2) + '\n',
2942
- 'deploy.sh': deploy,
2943
- 'README.md': [
2944
- `# Deploy: ${ctx.packName} — Azure Container Apps + AgentHippo edge (${fed ? 'edge-fed' : 'edge'})`,
2945
- '',
2946
- `Engine: **${ctx.engine}**${ctx.model ? `, model: **${ctx.model}**` : ''}.`,
2947
- '',
2948
- 'One Container App, containers share the replica\'s localhost — the same sidecar shape',
2949
- 'Cloud Run/ECS Fargate use, so the Caddyfile is identical:',
2950
- '- **caddy** (ingress, the only published port) → ' + (fed
2951
- ? 'verifies the shared AgentHippo JWT'
2952
- : 'SSO edge: oauth2-proxy for browsers, auth-broker for IDE Bearer'),
2953
- `- **agent** → serves www + /v1/* on localhost:${identity_1.EDGE_AGENT_PORT} (not published)`,
2954
- '- **auth-broker** → validates IDE AgentHippo JWTs (`/api/ide/me`)' + (fed ? ' against the shared IdP secret' : ''),
2955
- ...(fed ? [] : ['- **oauth2-proxy** → Google SSO for browser clients']),
2956
- '',
2957
- 'Built-in ingress + TLS termination — no separate load balancer to stand up.',
2958
- '',
2959
- '```bash',
2960
- 'export RESOURCE_GROUP=<rg> CONTAINERAPPS_ENVIRONMENT=<env> ACR_NAME=<registry>',
2961
- ...secretExports.map((l) => 'export ' + l.split('=')[0] + '=<...>'),
2962
- 'bash deploy/deploy.sh',
2963
- '```',
2964
- '',
2965
- '> ⚠️ Newer and far less exercised than the Cloud Run target — read `deploy.sh` before',
2966
- '> running it against a real account.',
2967
- '',
2968
- ...renderEgressNote(ctx),
2969
- ].join('\n'),
2970
- };
2971
- }
2972
- const RENDERERS = {
2973
- vm: vmArtifacts,
2974
- compose: composeArtifacts,
2975
- cloudrun: cloudrunArtifacts,
2976
- 'databricks-app': databricksAppArtifacts,
2977
- spcs: spcsArtifacts,
2978
- cloudflare: cloudflareArtifacts,
2979
- 'aws-ecs': awsEcsArtifacts,
2980
- 'azure-container-apps': azureContainerAppsArtifacts,
2981
- };
2982
- /**
2983
- * Render every artifact for a target (relative path → file content), always
2984
- * including a scanned `.env.example`. Pure: no fs writes.
2985
- */
2986
- function renderTarget(target, ctx) {
2987
- const files = RENDERERS[target](ctx);
2988
- // edge-fed applies to any Caddy-based target (vm/compose/cloudrun) — federates identity
2989
- // against a central IdP instead of each target running its own oauth2-proxy + Google client.
2990
- const fed = ctx.ingress === 'edge-fed';
2991
- // Colocated agents (state 4) ship no edge of their own at all — no Caddy, no oauth2-proxy,
2992
- // no local auth-broker, so no auth secrets (.env.auth.example / DATA-ACCESS.md) either.
2993
- const colocate = Boolean(ctx.colocate) && fed && (target === 'vm' || target === 'compose');
2994
- const gatewayDirect = usesGatewayDirect(ctx) && target === 'cloudrun' && fed;
2995
- const edge = !colocate && (target === 'vm' || target === 'compose'
2996
- || ((target === 'cloudrun' || target === 'aws-ecs' || target === 'azure-container-apps') && (ctx.ingress === 'edge' || fed)));
2997
- const hasDockerfile = Object.keys(files).some((k) => k === 'Dockerfile' || k.endsWith('/Dockerfile'));
2998
- const raw = {
2999
- ...files,
3000
- ...(hasDockerfile ? { '.dockerignore': renderDockerignore() } : {}),
3001
- '.env.example': renderEnvExample(ctx.envVars, {
3002
- edge,
3003
- engine: ctx.engine,
3004
- hostname: ctx.publicHostname,
3005
- gatewayDirect,
3006
- }),
3007
- // Edge auth secrets are split out so the agent container never loads them.
3008
- ...(edge ? {
3009
- '.env.auth.example': renderAuthEnvExample({
3010
- hostname: ctx.publicHostname,
3011
- fed,
3012
- idpUrl: ctx.idpUrl,
3013
- cookieDomain: ctx.cookieDomain,
3014
- }),
3015
- } : {}),
3016
- 'DATA-ACCESS.md': renderDataAccessSummary(ctx, target, edge, fed, colocate),
3017
- };
3018
- const out = {};
3019
- for (const [rel, content] of Object.entries(raw)) {
3020
- out[rel] = withPackAttribution(ctx, rel, content);
3021
- }
3022
- return out;
3023
- }