@zaip-ai/cli 1.3.8 → 1.4.1

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 (315) hide show
  1. package/README.md +58 -3
  2. package/dist/commands/init.d.ts +1 -0
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +17 -6
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/services/adapters.js +4 -4
  11. package/dist/services/adapters.js.map +1 -1
  12. package/dist/services/codex-config-template.d.ts +2 -2
  13. package/dist/services/codex-config-template.d.ts.map +1 -1
  14. package/dist/services/codex-config-template.js +3 -3
  15. package/dist/services/codex-config-template.js.map +1 -1
  16. package/dist/services/codex-rules.d.ts +1 -1
  17. package/dist/services/codex-rules.d.ts.map +1 -1
  18. package/dist/services/codex-rules.js +9 -24
  19. package/dist/services/codex-rules.js.map +1 -1
  20. package/dist/services/delivery-git-preflight.d.ts +23 -0
  21. package/dist/services/delivery-git-preflight.d.ts.map +1 -0
  22. package/dist/services/delivery-git-preflight.js +69 -0
  23. package/dist/services/delivery-git-preflight.js.map +1 -0
  24. package/dist/services/doctor.d.ts.map +1 -1
  25. package/dist/services/doctor.js +13 -1
  26. package/dist/services/doctor.js.map +1 -1
  27. package/dist/services/install-agents-template.d.ts.map +1 -1
  28. package/dist/services/install-agents-template.js +22 -28
  29. package/dist/services/install-agents-template.js.map +1 -1
  30. package/dist/services/install-profile.d.ts +8 -0
  31. package/dist/services/install-profile.d.ts.map +1 -0
  32. package/dist/services/install-profile.js +56 -0
  33. package/dist/services/install-profile.js.map +1 -0
  34. package/dist/services/logger.d.ts.map +1 -1
  35. package/dist/services/logger.js +6 -5
  36. package/dist/services/logger.js.map +1 -1
  37. package/dist/services/stack-git.d.ts +9 -0
  38. package/dist/services/stack-git.d.ts.map +1 -0
  39. package/dist/services/stack-git.js +188 -0
  40. package/dist/services/stack-git.js.map +1 -0
  41. package/dist/services/stack-plan.d.ts +3 -1
  42. package/dist/services/stack-plan.d.ts.map +1 -1
  43. package/dist/services/stack-plan.js +15 -1
  44. package/dist/services/stack-plan.js.map +1 -1
  45. package/dist/services/stack-terraform.d.ts +13 -0
  46. package/dist/services/stack-terraform.d.ts.map +1 -0
  47. package/dist/services/stack-terraform.js +274 -0
  48. package/dist/services/stack-terraform.js.map +1 -0
  49. package/dist/services/template-manifest.d.ts.map +1 -1
  50. package/dist/services/template-manifest.js +14 -1
  51. package/dist/services/template-manifest.js.map +1 -1
  52. package/dist/types.d.ts +1 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/templates/.agents/.env.example +34 -0
  56. package/templates/{README.md → .agents/README.md} +124 -60
  57. package/templates/.agents/config/agents/catalog.yaml +23 -23
  58. package/templates/.agents/config/clients/codex/agents/zaip-agent-analyst.toml +1 -0
  59. package/templates/.agents/config/clients/codex/agents/zaip-agent-auditor.toml +1 -0
  60. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +5 -4
  61. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +1 -0
  62. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +1 -0
  63. package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +10 -7
  64. package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +1 -0
  65. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +1 -0
  66. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -0
  67. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +5 -4
  68. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +1 -0
  69. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +1 -0
  70. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +1 -0
  71. package/templates/.agents/config/clients/codex/agents/zaip-agent-integrator.toml +1 -0
  72. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +5 -4
  73. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -0
  74. package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +1 -0
  75. package/templates/.agents/config/clients/codex/agents/zaip-agent-reviewer.toml +1 -0
  76. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +1 -0
  77. package/templates/.agents/config/clients/codex/agents/zaip-agent-security-auditor.toml +1 -0
  78. package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +1 -0
  79. package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +5 -4
  80. package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +1 -0
  81. package/templates/.agents/config/pricing/models.json +25 -1
  82. package/templates/.agents/config/project.yaml +2 -0
  83. package/templates/.agents/dashboard/.zaip-managed.json +200 -88
  84. package/templates/.agents/dashboard/DATA-DICTIONARY.md +1 -1
  85. package/templates/.agents/dashboard/README.md +58 -19
  86. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DwVZOmMV.js → CostCalculationGuidePage-snUCQYTC.js} +1 -1
  87. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-VcLx9iOO.js → DashboardHelpDialog-oZVlf-l5.js} +72 -8
  88. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CyjMXkm8.js → PricingSettingsDialog-BPtVUGgs.js} +1 -1
  89. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-3Awyaovx.js +3 -0
  90. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-DFZwIXSC.js → TraceabilityWorkspace-BYjVOHI_.js} +1 -1
  91. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DbaLbU2q.js +68 -0
  92. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DXBOtWNI.js → icons-vendor-TPUT2IUt.js} +1 -1
  93. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Cmlx8OYH.css +1 -0
  94. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +2 -0
  95. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DaNXQPyk.js → react-vendor-Ct48XHGL.js} +1 -1
  96. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-I4AKqVDX.js → traceability-BDe4_Ubl.js} +1 -1
  97. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  98. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +73 -67
  99. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +35 -7
  100. package/templates/.agents/dashboard/apps/frontend/src/AppWorkBoard.ts +55 -0
  101. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +11 -0
  102. package/templates/.agents/dashboard/apps/frontend/src/app-loading-boundary.css +98 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +5 -5
  104. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-reconciliation.ts +33 -0
  105. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +75 -0
  106. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +7 -8
  107. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +14 -2
  108. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +35 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +78 -2
  110. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +28 -0
  111. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +8 -5
  112. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +180 -1
  113. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +7 -4
  114. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +13 -7
  115. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +14 -0
  116. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableDisplay.ts +211 -0
  117. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +152 -157
  118. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +155 -142
  119. package/templates/.agents/dashboard/modules/cost-monitor/src/react/InvocationManifestQuery.test.ts +145 -0
  120. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +7 -5
  121. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +4 -1
  122. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +4 -1
  123. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +4 -1
  124. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +4 -4
  125. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +79 -1
  126. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +105 -11
  127. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +482 -332
  128. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +430 -285
  129. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +14 -2
  130. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +6 -26
  131. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +1 -1
  132. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-persistent-launch.mjs +268 -0
  133. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +482 -332
  134. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +90 -1
  135. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +112 -22
  136. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-assets.ts +39 -0
  137. package/templates/.agents/dashboard/packages/local-runtime/src/server-browser-lease.ts +33 -0
  138. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +148 -11
  139. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +68 -0
  140. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.test.ts +19 -0
  141. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +39 -43
  142. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.test.ts +42 -0
  143. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.ts +26 -0
  144. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +34 -0
  145. package/templates/.agents/dashboard/packages/local-runtime/src/server-startup.ts +30 -0
  146. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +89 -0
  147. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +88 -0
  148. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +68 -7
  149. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +11 -5
  150. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +129 -99
  151. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.test.ts +111 -0
  152. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.ts +140 -0
  153. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +127 -4
  154. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +145 -5
  155. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +156 -23
  156. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +18 -9
  157. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-projection.ts +52 -0
  158. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +35 -13
  159. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-force-canonical.test.ts +56 -0
  160. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.test.ts +29 -0
  161. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.ts +136 -0
  162. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +251 -0
  163. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +22 -7
  164. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +1 -1
  165. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +42 -0
  166. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +793 -0
  167. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-duration.test.ts +270 -0
  168. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +3 -2
  169. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +18 -4
  170. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +87 -0
  171. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +105 -16
  172. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +131 -59
  173. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1000 -1115
  174. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +13 -92
  175. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-claims.test.mjs +79 -0
  176. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +27 -0
  177. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +6 -6
  178. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +1 -1
  179. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +2 -10
  180. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +142 -0
  181. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +231 -239
  182. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +18 -29
  183. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +6 -0
  184. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  185. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  186. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +47 -0
  187. package/templates/.agents/requirements/comparative-audit.md +1 -1
  188. package/templates/.agents/requirements/context-engine.md +1 -1
  189. package/templates/.agents/requirements/cost-observability.md +1 -1
  190. package/templates/.agents/requirements/dashboard-launch.md +2 -2
  191. package/templates/.agents/requirements/data-export.md +1 -1
  192. package/templates/.agents/requirements/design-system.md +1 -1
  193. package/templates/.agents/requirements/execution-engine.md +1 -1
  194. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  195. package/templates/.agents/requirements/model-orchestration.md +1 -1
  196. package/templates/.agents/requirements/public-distribution.md +1 -1
  197. package/templates/.agents/requirements/software-architecture.md +1 -1
  198. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  199. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  200. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  201. package/templates/.agents/rules/autonomy-decision.md +1 -1
  202. package/templates/.agents/rules/cloud-hostinger-autonomy.md +21 -0
  203. package/templates/.agents/rules/core.md +5 -4
  204. package/templates/.agents/rules/local-sqlite-storage.md +18 -0
  205. package/templates/.agents/rules/meta-mcp.md +21 -0
  206. package/templates/.agents/rules/workflow.md +1 -0
  207. package/templates/.agents/runtime/bin/zaip.mjs +305 -137
  208. package/templates/.agents/runtime/checksums.sha256 +2 -2
  209. package/templates/.agents/runtime/provenance.json +2 -2
  210. package/templates/.agents/skills/zaip-skill-api-to-mcp/SKILL.md +4 -0
  211. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +25 -9
  212. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +2 -2
  213. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +2 -2
  214. package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +15 -0
  215. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +13 -5
  216. package/templates/.agents/skills/zaip-skill-data-analyst/SKILL.md +10 -5
  217. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +5 -4
  218. package/templates/.agents/skills/zaip-skill-frontend-launcher/SKILL.md +4 -3
  219. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +5 -3
  220. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -5
  221. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +78 -13
  222. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +59 -0
  223. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/yaml-parser.mjs +157 -0
  224. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +11 -6
  225. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +17 -0
  226. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +7 -0
  227. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-cloud-inputs.example.json +41 -0
  228. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-runner.md +84 -0
  229. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/operational-contract.md +19 -0
  230. package/templates/.agents/skills/zaip-skill-local-credentials/SKILL.md +43 -0
  231. package/templates/.agents/skills/zaip-skill-local-credentials/agents/openai.yaml +6 -0
  232. package/templates/.agents/skills/zaip-skill-local-credentials/references/dotenv-resolution.md +47 -0
  233. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +8 -3
  234. package/templates/.agents/skills/zaip-skill-meta-ads/SKILL.md +16 -0
  235. package/templates/.agents/skills/zaip-skill-meta-ads/agents/openai.yaml +4 -0
  236. package/templates/.agents/skills/zaip-skill-meta-ads/references/setup.md +3 -0
  237. package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +24 -0
  238. package/templates/.agents/skills/zaip-skill-meta-ads-setup/agents/openai.yaml +4 -0
  239. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/diagnostics.md +13 -0
  240. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/official-sources.md +10 -0
  241. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/SKILL.md +12 -0
  242. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/agents/openai.yaml +4 -0
  243. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/LICENSE +21 -0
  244. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/upstream.md +77 -0
  245. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/SKILL.md +12 -0
  246. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/agents/openai.yaml +4 -0
  247. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/LICENSE +21 -0
  248. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/upstream.md +126 -0
  249. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/SKILL.md +12 -0
  250. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/agents/openai.yaml +4 -0
  251. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/LICENSE +21 -0
  252. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/upstream.md +67 -0
  253. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/SKILL.md +12 -0
  254. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/agents/openai.yaml +4 -0
  255. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/LICENSE +21 -0
  256. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/upstream.md +69 -0
  257. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/SKILL.md +12 -0
  258. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/agents/openai.yaml +4 -0
  259. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/LICENSE +21 -0
  260. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/upstream.md +79 -0
  261. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/SKILL.md +14 -0
  262. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/agents/openai.yaml +4 -0
  263. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/LICENSE +21 -0
  264. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/upstream.md +144 -0
  265. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/SKILL.md +12 -0
  266. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/agents/openai.yaml +4 -0
  267. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/LICENSE +21 -0
  268. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/upstream.md +83 -0
  269. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/SKILL.md +10 -0
  270. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/agents/openai.yaml +4 -0
  271. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/LICENSE +21 -0
  272. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/setup.md +11 -0
  273. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/upstream.md +55 -0
  274. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/SKILL.md +12 -0
  275. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/agents/openai.yaml +4 -0
  276. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/LICENSE +21 -0
  277. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/upstream.md +56 -0
  278. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +12 -7
  279. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +11 -4
  280. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +6 -4
  281. package/templates/.agents/skills/zaip-skill-tool-setup/references/codex-config.md +4 -2
  282. package/templates/.agents/skills/zaip-skill-tool-setup/references/secrets.md +1 -0
  283. package/templates/.agents/tools/catalog.yaml +6 -0
  284. package/templates/.agents/tools/cloud-runner-dns.mjs +28 -0
  285. package/templates/.agents/tools/cloud-runner-terraform.mjs +212 -0
  286. package/templates/.agents/tools/cloud-runner.integration.test.mjs +239 -0
  287. package/templates/.agents/tools/cloud-runner.mjs +786 -0
  288. package/templates/.agents/tools/cloud-runner.test.mjs +534 -0
  289. package/templates/.agents/tools/cloud-terraform-plan-policy.mjs +262 -0
  290. package/templates/.agents/tools/mcp/development/meta-devtools/README.md +14 -0
  291. package/templates/.agents/tools/mcp/development/meta-devtools/manifest.yaml +28 -0
  292. package/templates/.agents/tools/mcp/infra/cloudflare/manifest.yaml +2 -3
  293. package/templates/.agents/tools/mcp/infra/hostinger/manifest.yaml +1 -3
  294. package/templates/.agents/tools/mcp/marketing/meta-ads/README.md +15 -13
  295. package/templates/.agents/tools/mcp/marketing/meta-ads/manifest.yaml +15 -10
  296. package/templates/.agents/tools/profiles/development.yaml +3 -0
  297. package/templates/infra/terraform/.terraform.lock.hcl +42 -0
  298. package/templates/infra/terraform/README.md +31 -0
  299. package/templates/infra/terraform/backend.tf.json +13 -0
  300. package/templates/infra/terraform/deployment-inputs.example.yaml +22 -0
  301. package/templates/infra/terraform/fixtures/approved-targets.json +32 -0
  302. package/templates/infra/terraform/fixtures/plan-approved.json +67 -0
  303. package/templates/infra/terraform/main.tf +77 -0
  304. package/templates/infra/terraform/outputs.tf +20 -0
  305. package/templates/infra/terraform/terraform.tfvars.example +26 -0
  306. package/templates/infra/terraform/variables.tf +91 -0
  307. package/templates/infra/terraform/versions.tf +18 -0
  308. package/templates/scripts/validate-cloud-hostinger-contract.mjs +89 -0
  309. package/templates/scripts/validate-cloud-terraform-plan.mjs +1 -0
  310. package/templates/template-manifest.json +599 -167
  311. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BwnJpl02.js +0 -3
  312. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +0 -68
  313. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Dyo_wpL4.js +0 -2
  314. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +0 -1
  315. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +0 -50
@@ -0,0 +1,786 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { access, mkdir, readFile, realpath, writeFile } from "node:fs/promises";
4
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parseEnv } from "node:util";
7
+ import { authHeaders, dnsBody, dnsInventory, sameDns } from "./cloud-runner-dns.mjs";
8
+ import { runTerraform } from "./cloud-runner-terraform.mjs";
9
+
10
+ const root = resolve(import.meta.dirname, "..", "..");
11
+ const main =
12
+ process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
13
+ if (main) {
14
+ try {
15
+ process.stdout.write(
16
+ `${JSON.stringify({ ok: true, ...(await run(parse(process.argv.slice(2)))) })}\n`,
17
+ );
18
+ } catch (error) {
19
+ process.stderr.write(`${JSON.stringify({ ok: false, code: errorCode(error) })}\n`);
20
+ process.exitCode = 1;
21
+ }
22
+ }
23
+
24
+ /** Runs one contract-bound operation. Dependencies are injectable for offline tests. */
25
+ export async function run(args, injected = {}) {
26
+ const deps = dependencies(injected);
27
+ if (args.operation === "contract:derive") {
28
+ exact(args, ["operation", "credentials-source"]);
29
+ if (args.credentialsSource !== "root") fail("invalid_credentials_source");
30
+ const values = await dotenvValues("root", deps);
31
+ if (values.ZAIP_CLOUD_PROFILE !== "autonomous-cloud") fail("invalid_contract");
32
+ const contract = JSON.parse(
33
+ await deps.readFile(insideRoot(".agents/config/autonomous-cloud-inputs.json"), "utf8"),
34
+ );
35
+ if (
36
+ contract.version !== 1 ||
37
+ contract.profile !== "autonomous-cloud" ||
38
+ contract.authorization?.standing !== true ||
39
+ !nonempty(contract.authorization.reference)
40
+ )
41
+ fail("authorization_unavailable");
42
+ contract.cloudflare = {
43
+ ...contract.cloudflare,
44
+ account_id: requiredEnv(values, "TF_VAR_cloudflare_account_id"),
45
+ zone_id: requiredEnv(values, "TF_VAR_cloudflare_zone_id"),
46
+ zone_name: requiredEnv(values, "TF_VAR_cloudflare_zone_name"),
47
+ };
48
+ contract.hostinger = {
49
+ ...contract.hostinger,
50
+ account_id: requiredEnv(values, "ZAIP_HOSTINGER_ACCOUNT_ID"),
51
+ vps: structuredClone(contract.hostinger?.vps ?? {}),
52
+ };
53
+ for (const role of ["edge", "application"]) {
54
+ const target = contract.hostinger.vps[role] ?? {};
55
+ const id = nonempty(values[`TF_VAR_${role}_vps_id`]) ?? nonempty(target.id);
56
+ if (id) {
57
+ target.id = id;
58
+ target.ssh_host =
59
+ nonempty(values[`TF_VAR_${role}_vps_ipv4`]) ?? requireString(target, "ssh_host");
60
+ target.host_key_sha256 = nonempty(values[`ZAIP_${role.toUpperCase()}_SSH_HOSTKEY_SHA256`]);
61
+ target.private_key_path = nonempty(values.ZAIP_CLOUD_SSH_PRIVATE_KEY_PATH);
62
+ } else if (
63
+ !target.creation ||
64
+ !(contract.authorization.max_cost > 0) ||
65
+ !nonempty(contract.authorization.currency)
66
+ )
67
+ fail("target_unavailable");
68
+ contract.hostinger.vps[role] = target;
69
+ }
70
+ const credentials = credentialsFrom(values);
71
+ await preflight(contract, credentials, deps);
72
+ contract.observed_at = new Date().toISOString();
73
+ const path = ".agents/config/autonomous-cloud-contract.json";
74
+ await deps.writeFile(insideRoot(path), `${JSON.stringify(contract, null, 2)}\n`, {
75
+ mode: 0o600,
76
+ });
77
+ return {
78
+ ...result("contract:derive", null, [
79
+ contract.cloudflare.account_id,
80
+ contract.cloudflare.zone_id,
81
+ contract.hostinger.account_id,
82
+ ]),
83
+ contract_path: path,
84
+ };
85
+ }
86
+ const contractPath = requireValue(args, "contract");
87
+ const contract = JSON.parse(await deps.readFile(insideRoot(contractPath), "utf8"));
88
+ if (contract.version !== 1 || contract.profile !== "autonomous-cloud") fail("invalid_contract");
89
+ const credentials = await dotenv(requireValue(args, "credentials-source"), deps);
90
+ if (args.operation.startsWith("cloudflare:dns:"))
91
+ return cloudflare(args, contract, credentials, deps);
92
+ if (args.operation.startsWith("hostinger:")) return hostinger(args, contract, credentials, deps);
93
+ if (args.operation.startsWith("terraform:"))
94
+ return runTerraform(args, contract, credentials, {
95
+ deps,
96
+ root,
97
+ dirname,
98
+ exact,
99
+ fail,
100
+ insideRoot,
101
+ nonempty,
102
+ requiredEnv,
103
+ requireString,
104
+ result,
105
+ sha256,
106
+ preflight,
107
+ validPlan,
108
+ verifyPolicyTargets,
109
+ verifyBackendSource,
110
+ verifyBackend,
111
+ receipt,
112
+ observeState,
113
+ });
114
+ fail("operation_not_allowed");
115
+ }
116
+
117
+ async function cloudflare(args, contract, credentials, deps) {
118
+ exact(args, [
119
+ "contract",
120
+ "operation",
121
+ "provider",
122
+ "account-id",
123
+ "credentials-source",
124
+ "zone-id",
125
+ "dns-record",
126
+ ]);
127
+ const scope = contract.cloudflare;
128
+ if (!credentials.cloudflare || args.provider !== "cloudflare") fail("credential_unavailable");
129
+ if (!scope || args.accountId !== scope.account_id || args.zoneId !== scope.zone_id)
130
+ fail("target_mismatch");
131
+ if (
132
+ !new Set(["cloudflare:dns:read", "cloudflare:dns:create", "cloudflare:dns:edit"]).has(
133
+ args.operation,
134
+ ) ||
135
+ !scope.allowed_operations?.includes(args.operation)
136
+ )
137
+ fail("operation_not_allowed");
138
+ const managed = dnsInventory(scope).has(args.dnsRecord);
139
+ const direct = scope.direct_dns_records?.[args.dnsRecord];
140
+ if (args.operation === "cloudflare:dns:read" ? !managed && !direct : !direct || managed)
141
+ fail("operation_not_allowed");
142
+ const headers = authHeaders(credentials.cloudflare);
143
+ const zone = await request(
144
+ deps,
145
+ `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(args.zoneId)}`,
146
+ { method: "GET", headers },
147
+ );
148
+ if (zone?.result?.id !== args.zoneId || zone?.result?.account?.id !== args.accountId)
149
+ fail("target_mismatch");
150
+ const listUrl = `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(args.zoneId)}/dns_records?name=${encodeURIComponent(args.dnsRecord)}`;
151
+ const listed = await request(deps, listUrl, { method: "GET", headers });
152
+ const matches = Array.isArray(listed?.result)
153
+ ? listed.result.filter((record) => record?.name === args.dnsRecord)
154
+ : [];
155
+ if (matches.length > 1) fail("ambiguous_dns_record");
156
+ const current = matches[0];
157
+ if (args.operation === "cloudflare:dns:read") {
158
+ if (!current) fail("record_not_found");
159
+ return result(
160
+ args.operation,
161
+ "cloudflare",
162
+ [args.accountId, args.zoneId, args.dnsRecord, current.id].filter(Boolean),
163
+ );
164
+ }
165
+ const body = dnsBody(direct, fail);
166
+ if (
167
+ body.name !== args.dnsRecord ||
168
+ !inZone(body.name, scope.zone_name) ||
169
+ body.content === contract.hostinger?.vps?.application?.ssh_host
170
+ )
171
+ fail("target_mismatch");
172
+ if (args.operation === "cloudflare:dns:create" && current) fail("record_already_exists");
173
+ if (args.operation === "cloudflare:dns:edit" && !current?.id) fail("record_not_found");
174
+ const endpoint = current?.id
175
+ ? `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(args.zoneId)}/dns_records/${encodeURIComponent(current.id)}`
176
+ : `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(args.zoneId)}/dns_records`;
177
+ await request(deps, endpoint, {
178
+ method: current?.id ? "PATCH" : "POST",
179
+ headers,
180
+ body: JSON.stringify(body),
181
+ });
182
+ const observed = await request(deps, listUrl, { method: "GET", headers });
183
+ const after = Array.isArray(observed?.result)
184
+ ? observed.result.find((record) => record?.name === args.dnsRecord)
185
+ : null;
186
+ if (!after?.id || !sameDns(after, body)) fail("read_after_write_failed");
187
+ return result(args.operation, "cloudflare", [
188
+ args.accountId,
189
+ args.zoneId,
190
+ args.dnsRecord,
191
+ after.id,
192
+ ]);
193
+ }
194
+
195
+ async function hostinger(args, contract, credentials, deps) {
196
+ const ssh = args.operation.startsWith("hostinger:ssh:");
197
+ exact(
198
+ args,
199
+ ssh
200
+ ? [
201
+ "contract",
202
+ "operation",
203
+ "provider",
204
+ "account-id",
205
+ "credentials-source",
206
+ "vps-role",
207
+ "vps-id",
208
+ "host-key-sha256",
209
+ ]
210
+ : [
211
+ "contract",
212
+ "operation",
213
+ "provider",
214
+ "account-id",
215
+ "credentials-source",
216
+ "vps-role",
217
+ "vps-id",
218
+ ],
219
+ );
220
+ const scope = contract.hostinger;
221
+ const vps = scope?.vps?.[args.vpsRole];
222
+ if ((!ssh && !credentials.hostinger) || args.provider !== "hostinger")
223
+ fail("credential_unavailable");
224
+ if (
225
+ !scope ||
226
+ args.accountId !== scope.account_id ||
227
+ !vps ||
228
+ args.vpsId !== vps.id ||
229
+ !scope.allowed_operations?.includes(args.operation)
230
+ )
231
+ fail("target_mismatch");
232
+ if (args.operation === "hostinger:vps:read") {
233
+ await observeVps(vps, credentials.hostinger, deps);
234
+ return result(args.operation, "hostinger", [args.accountId, args.vpsRole, args.vpsId]);
235
+ }
236
+ if (
237
+ !new Set(["hostinger:ssh:read", "hostinger:ssh:test", "hostinger:ssh:configure"]).has(
238
+ args.operation,
239
+ )
240
+ )
241
+ fail("operation_not_allowed");
242
+ if (args.hostKeySha256 !== vps.host_key_sha256) fail("target_mismatch");
243
+ const key = insideRoot(requireString(vps, "private_key_path"));
244
+ const knownHosts = insideRoot(requireString(vps, "known_hosts_path"));
245
+ await deps.access(key);
246
+ const timeout = positiveTimeout(vps.ssh_timeout_seconds);
247
+ const port =
248
+ Number.isInteger(vps.ssh_port) && vps.ssh_port > 0 && vps.ssh_port <= 65535 ? vps.ssh_port : 22;
249
+ if (
250
+ typeof vps.ssh_host !== "string" ||
251
+ !/^[A-Za-z0-9.:-]+$/.test(vps.ssh_host) ||
252
+ vps.ssh_host.startsWith("-")
253
+ )
254
+ fail("invalid_contract");
255
+ const hostIdentity = port === 22 ? vps.ssh_host : `[${vps.ssh_host}]:${port}`;
256
+ let known,
257
+ scanned = false;
258
+ try {
259
+ await deps.access(knownHosts);
260
+ known = await deps.exec("ssh-keygen", ["-F", hostIdentity, "-f", knownHosts], {
261
+ timeout: timeout * 1000,
262
+ });
263
+ } catch (error) {
264
+ if (error.code !== "ENOENT") throw error;
265
+ scanned = true;
266
+ known = await deps.exec(
267
+ "ssh-keyscan",
268
+ ["-T", String(timeout), "-p", String(port), vps.ssh_host],
269
+ { timeout: timeout * 1000 },
270
+ );
271
+ }
272
+ const keyLines = String(known.stdout ?? "")
273
+ .split(/\r?\n/)
274
+ .filter((line) => line && !line.startsWith("#"))
275
+ .join("\n");
276
+ const lines = keyLines.split("\n").filter(Boolean);
277
+ const fingerprints = lines.map((line) => {
278
+ const fields = line.trim().split(/\s+/);
279
+ if (fields[0].startsWith("@") || fields.length < 3 || !/^[A-Za-z0-9+/]+={0,2}$/.test(fields[2]))
280
+ fail("host_key_mismatch");
281
+ return `SHA256:${createHash("sha256").update(Buffer.from(fields[2], "base64")).digest("base64").replace(/=+$/, "")}`;
282
+ });
283
+ if (
284
+ known.code !== 0 ||
285
+ !fingerprints.length ||
286
+ (!scanned && fingerprints.some((value) => value !== args.hostKeySha256))
287
+ )
288
+ fail("host_key_mismatch");
289
+ const pinned = lines.filter((_, index) => fingerprints[index] === args.hostKeySha256);
290
+ if (!pinned.length) fail("host_key_mismatch");
291
+ if (scanned) {
292
+ await deps.mkdir(dirname(knownHosts), { recursive: true });
293
+ await deps.writeFile(knownHosts, `${pinned.join("\n")}\n`, { mode: 0o600 });
294
+ }
295
+ const user = requireString(vps, "ssh_user");
296
+ if (!/^[a-z_][a-z0-9_-]{0,31}$/.test(user)) fail("invalid_contract");
297
+ const command = args.operation === "hostinger:ssh:read" ? "uname -s" : "true";
298
+ const argv = [
299
+ "-F",
300
+ process.platform === "win32" ? "NUL" : "/dev/null",
301
+ "-i",
302
+ key,
303
+ "-l",
304
+ user,
305
+ "-o",
306
+ "IdentitiesOnly=yes",
307
+ "-o",
308
+ "IdentityAgent=none",
309
+ "-o",
310
+ "BatchMode=yes",
311
+ "-o",
312
+ "StrictHostKeyChecking=yes",
313
+ "-o",
314
+ "GlobalKnownHostsFile=none",
315
+ "-o",
316
+ `UserKnownHostsFile=${knownHosts}`,
317
+ "-o",
318
+ `ConnectTimeout=${timeout}`,
319
+ "-p",
320
+ String(port),
321
+ vps.ssh_host,
322
+ command,
323
+ ];
324
+ const options = { input: undefined, timeout: timeout * 1000 };
325
+ if (args.operation === "hostinger:ssh:configure") {
326
+ options.input = await deps.readFile(insideRoot(requireString(vps, "ssh_script_path")), "utf8");
327
+ if (sha256(options.input) !== vps.ssh_script_sha256) fail("script_hash_mismatch");
328
+ argv.splice(-1, 1, "sh -s");
329
+ }
330
+ if ((await deps.exec("ssh", argv, options)).code !== 0)
331
+ fail(
332
+ args.operation === "hostinger:ssh:configure"
333
+ ? "remote_result_requires_reconciliation"
334
+ : "ssh_failed",
335
+ );
336
+ return result(args.operation, "hostinger", [args.accountId, args.vpsRole, args.vpsId]);
337
+ }
338
+
339
+ async function preflight(contract, credentials, deps) {
340
+ if (!credentials.cloudflare || !credentials.hostinger) fail("credential_unavailable");
341
+ const scope = contract.cloudflare;
342
+ if (!scope?.account_id || !scope.zone_id || !scope.zone_name) fail("target_unavailable");
343
+ const zone = await request(
344
+ deps,
345
+ `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(scope.zone_id)}`,
346
+ { method: "GET", headers: authHeaders(credentials.cloudflare) },
347
+ );
348
+ if (
349
+ zone.result?.id !== scope.zone_id ||
350
+ zone.result?.account?.id !== scope.account_id ||
351
+ zone.result?.name !== scope.zone_name
352
+ )
353
+ fail("target_mismatch");
354
+ if (!nonempty(contract.hostinger?.account_id)) fail("target_unavailable");
355
+ let creating = false;
356
+ for (const role of ["edge", "application"]) {
357
+ const target = contract.hostinger.vps?.[role];
358
+ if (target?.id) await observeVps(target, credentials.hostinger, deps);
359
+ else if (
360
+ target?.creation &&
361
+ contract.authorization?.max_cost > 0 &&
362
+ nonempty(contract.authorization.currency)
363
+ )
364
+ creating = true;
365
+ else fail("target_unavailable");
366
+ }
367
+ // Hostinger's VPS endpoint proves token visibility of IDs, not an account ID.
368
+ // The account label remains an explicit operator input, never an observed claim.
369
+ if (creating) {
370
+ await request(deps, "https://developers.hostinger.com/api/vps/v1/virtual-machines", {
371
+ method: "GET",
372
+ headers: authHeaders(credentials.hostinger),
373
+ });
374
+ const response = await request(
375
+ deps,
376
+ "https://developers.hostinger.com/api/billing/v1/catalog?category=VPS",
377
+ { method: "GET", headers: authHeaders(credentials.hostinger) },
378
+ );
379
+ const items = Array.isArray(response) ? response : response.data;
380
+ if (!Array.isArray(items)) fail("price_unavailable");
381
+ let total = 0;
382
+ for (const target of Object.values(contract.hostinger.vps)) {
383
+ if (target.id) continue;
384
+ const quote = target.creation.quote;
385
+ const item = items.find((item) => item.id === quote?.catalog_item_id);
386
+ const price = item?.prices?.find((price) => price.id === target.creation.plan);
387
+ if (
388
+ !price ||
389
+ quote.price_id !== price.id ||
390
+ quote.currency !== price.currency ||
391
+ quote.currency !== contract.authorization.currency ||
392
+ quote.price_cents !== price.price ||
393
+ quote.first_period_price_cents !== price.first_period_price ||
394
+ quote.period !== price.period ||
395
+ quote.period_unit !== price.period_unit
396
+ )
397
+ fail("price_changed_or_unavailable");
398
+ if (
399
+ ![price.price, price.first_period_price].every(
400
+ (value) => Number.isSafeInteger(value) && value >= 0,
401
+ )
402
+ )
403
+ fail("price_unavailable");
404
+ total += Math.max(price.price, price.first_period_price);
405
+ }
406
+ if (total > Math.floor(contract.authorization.max_cost * 100)) fail("budget_exceeded");
407
+ }
408
+ }
409
+ async function observeVps(target, token, deps) {
410
+ const body = await request(
411
+ deps,
412
+ `https://developers.hostinger.com/api/vps/v1/virtual-machines/${encodeURIComponent(target.id)}`,
413
+ { method: "GET", headers: authHeaders(token) },
414
+ );
415
+ const actual = body.data ?? body;
416
+ if (String(actual.id) !== String(target.id)) fail("target_mismatch");
417
+ const addresses = [
418
+ actual.ipv4_address,
419
+ ...(Array.isArray(actual.ipv4)
420
+ ? actual.ipv4.map((ip) => (typeof ip === "string" ? ip : ip.address))
421
+ : []),
422
+ ];
423
+ if (target.ssh_host && !addresses.includes(target.ssh_host)) fail("target_mismatch");
424
+ return actual;
425
+ }
426
+ async function observeState(state, contract, credentials, deps) {
427
+ const resources = [];
428
+ const walk = (module) => {
429
+ resources.push(...(module?.resources ?? []));
430
+ for (const child of module?.child_modules ?? []) walk(child);
431
+ };
432
+ walk(state.values?.root_module);
433
+ const targets = [],
434
+ bindings = [];
435
+ for (const resource of resources) {
436
+ const value = resource.values;
437
+ if (resource.type === "hostinger_vps") {
438
+ const role =
439
+ resource.address === "hostinger_vps.edge[0]"
440
+ ? "edge"
441
+ : resource.address === "hostinger_vps.application[0]"
442
+ ? "application"
443
+ : null;
444
+ if (!role || !value?.id || !value.ipv4_address) fail("read_after_write_failed");
445
+ const approved = contract.hostinger.vps[role];
446
+ if (approved.id && String(approved.id) !== String(value.id)) fail("target_mismatch");
447
+ if (!approved.id && !approved.creation) fail("target_mismatch");
448
+ await observeVps(
449
+ { id: String(value.id), ssh_host: value.ipv4_address },
450
+ credentials.hostinger,
451
+ deps,
452
+ );
453
+ targets.push(String(value.id));
454
+ if (!approved.id) bindings.push({ role, id: String(value.id), ipv4: value.ipv4_address });
455
+ } else if (resource.type === "cloudflare_dns_record") {
456
+ if (value.zone_id !== contract.cloudflare.zone_id || !value.id) fail("target_mismatch");
457
+ const body = await request(
458
+ deps,
459
+ `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(value.zone_id)}/dns_records/${encodeURIComponent(value.id)}`,
460
+ { method: "GET", headers: authHeaders(credentials.cloudflare) },
461
+ );
462
+ if (!sameDns(body.result ?? {}, value)) fail("read_after_write_failed");
463
+ targets.push(value.id);
464
+ } else if (resource.mode !== "data") fail("unexpected_state_resource");
465
+ }
466
+ return { targets, bindings };
467
+ }
468
+ function inZone(name, zone) {
469
+ return typeof zone === "string" && (name === zone || name.endsWith(`.${zone}`));
470
+ }
471
+
472
+ async function dotenv(source, deps) {
473
+ return credentialsFrom(await dotenvValues(source, deps));
474
+ }
475
+ function credentialsFrom(values) {
476
+ return {
477
+ values,
478
+ cloudflare: nonempty(values.CLOUDFLARE_API_TOKEN),
479
+ hostinger: nonempty(values.HOSTINGER_API_TOKEN),
480
+ };
481
+ }
482
+ function requiredEnv(values, key) {
483
+ const value = nonempty(values[key]);
484
+ if (!value || /^(replace[-_ ]|your[-_ ]|<)/i.test(value)) fail("target_unavailable");
485
+ return value;
486
+ }
487
+ async function dotenvValues(source, deps) {
488
+ if (!new Set(["root", "agents"]).has(source)) fail("invalid_credentials_source");
489
+ const canonical = await deps.realpath(insideRoot(source === "root" ? ".env" : ".agents/.env"));
490
+ if (!insideRoot(canonical)) fail("path_outside_project");
491
+ return parseEnv(await deps.readFile(canonical, "utf8"));
492
+ }
493
+ function dependencies(overrides) {
494
+ return {
495
+ access: guarded(access),
496
+ mkdir: guarded(mkdir),
497
+ readFile: guarded(readFile),
498
+ realpath: guarded(realpath),
499
+ writeFile: guarded(writeFile),
500
+ http: boundedFetch,
501
+ exec: execute,
502
+ ...overrides,
503
+ };
504
+ }
505
+ function guarded(operation) {
506
+ return async (path, ...args) => {
507
+ await guardRealPath(path);
508
+ return operation(path, ...args);
509
+ };
510
+ }
511
+ async function guardRealPath(path) {
512
+ let candidate = insideRoot(path);
513
+ const canonicalRoot = await realpath(root);
514
+ for (;;) {
515
+ try {
516
+ const canonical = await realpath(candidate);
517
+ const rel = relative(canonicalRoot, canonical);
518
+ if (isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`))
519
+ fail("path_outside_project");
520
+ return;
521
+ } catch (error) {
522
+ if (error.code !== "ENOENT") throw error;
523
+ candidate = dirname(candidate);
524
+ }
525
+ }
526
+ }
527
+ async function request(deps, url, options) {
528
+ try {
529
+ const response = await deps.http(url, {
530
+ ...options,
531
+ redirect: "error",
532
+ signal: AbortSignal.timeout(15_000),
533
+ });
534
+ if (!response?.ok) fail("provider_request_failed");
535
+ const body = typeof response.json === "function" ? await response.json() : response;
536
+ if (body.success === false) fail("provider_request_failed");
537
+ return body;
538
+ } catch (error) {
539
+ if (options.method !== "GET") fail("remote_result_requires_reconciliation");
540
+ throw error;
541
+ }
542
+ }
543
+ function boundedFetch(url, options) {
544
+ return fetch(url, options);
545
+ }
546
+ export function execute(file, args, options = {}) {
547
+ return new Promise((done, reject) => {
548
+ const child = spawn(file, args, {
549
+ cwd: options.cwd,
550
+ env: safeEnv(options.env),
551
+ stdio: ["pipe", "pipe", "pipe"],
552
+ windowsHide: true,
553
+ });
554
+ let stdout = "",
555
+ stderr = "",
556
+ overflow = false,
557
+ timedOut = false;
558
+ const limit = options.maxOutput ?? 1_000_000;
559
+ const append = (current, chunk) => {
560
+ if (Buffer.byteLength(current) + chunk.length > limit) {
561
+ overflow = true;
562
+ child.kill();
563
+ return current;
564
+ }
565
+ return current + chunk.toString("utf8");
566
+ };
567
+ child.stdout.on("data", (chunk) => {
568
+ stdout = append(stdout, chunk);
569
+ });
570
+ child.stderr.on("data", (chunk) => {
571
+ stderr = append(stderr, chunk);
572
+ });
573
+ const timer = setTimeout(() => {
574
+ timedOut = true;
575
+ child.kill();
576
+ }, options.timeout ?? 30_000);
577
+ child.once("error", (error) => {
578
+ clearTimeout(timer);
579
+ reject(error);
580
+ });
581
+ child.stdin.on("error", () => {});
582
+ child.stdin.end(options.input);
583
+ child.once("close", (code) => {
584
+ clearTimeout(timer);
585
+ done({ code: overflow || timedOut ? 1 : (code ?? 1), stdout, stderr, overflow, timedOut });
586
+ });
587
+ });
588
+ }
589
+ function safeEnv(extra = {}) {
590
+ const allowed = ["PATH", "Path", "SYSTEMROOT", "SystemRoot", "WINDIR", "ComSpec", "PATHEXT"];
591
+ const base = Object.fromEntries(
592
+ allowed.filter((key) => process.env[key]).map((key) => [key, process.env[key]]),
593
+ );
594
+ return { ...base, ...extra };
595
+ }
596
+ function validPlan(plan, accountId) {
597
+ if (
598
+ !plan ||
599
+ !canonicalPlan(plan.path) ||
600
+ !canonicalPlan(plan.policy_json_path, ".json") ||
601
+ typeof plan.policy_contract_path !== "string"
602
+ )
603
+ fail("invalid_contract");
604
+ const backend = plan.backend;
605
+ if (
606
+ backend?.type !== "s3" ||
607
+ !nonempty(backend.config?.bucket) ||
608
+ !nonempty(backend.config?.key) ||
609
+ !nonempty(backend.config?.region) ||
610
+ backend.config.use_lockfile !== true
611
+ )
612
+ fail("remote_backend_unavailable");
613
+ const allowed = new Set(["bucket", "key", "region", "encrypt", "use_lockfile"]);
614
+ if (backend.config.endpoints) {
615
+ if (
616
+ stable(backend.config.endpoints) !==
617
+ stable({ s3: `https://${accountId}.r2.cloudflarestorage.com` }) ||
618
+ backend.config.region !== "auto" ||
619
+ backend.config.encrypt === true
620
+ )
621
+ fail("invalid_backend_config");
622
+ const flags = [
623
+ "skip_credentials_validation",
624
+ "skip_metadata_api_check",
625
+ "skip_region_validation",
626
+ "skip_requesting_account_id",
627
+ "skip_s3_checksum",
628
+ "use_path_style",
629
+ ];
630
+ for (const flag of flags) {
631
+ allowed.add(flag);
632
+ if (backend.config[flag] !== true) fail("invalid_backend_config");
633
+ }
634
+ allowed.add("endpoints");
635
+ } else if (backend.config.encrypt !== true) fail("remote_backend_unavailable");
636
+ if (Object.keys(backend.config).some((key) => !allowed.has(key))) fail("invalid_backend_config");
637
+ insideRoot(plan.policy_contract_path);
638
+ return plan;
639
+ }
640
+ async function verifyBackendSource(plan, cwd, deps) {
641
+ const source = JSON.parse(await deps.readFile(resolve(cwd, "backend.tf.json"), "utf8"));
642
+ if (
643
+ stable(source) !==
644
+ stable({ terraform: { backend: { [plan.backend.type]: plan.backend.config } } })
645
+ )
646
+ fail("backend_mismatch");
647
+ }
648
+ async function verifyBackend(plan, cwd, deps) {
649
+ const metadata = JSON.parse(
650
+ await deps.readFile(resolve(cwd, ".terraform/terraform.tfstate"), "utf8"),
651
+ );
652
+ if (
653
+ metadata.backend?.type !== plan.backend.type ||
654
+ Object.entries(plan.backend.config).some(
655
+ ([key, value]) => stable(metadata.backend.config?.[key]) !== stable(value),
656
+ )
657
+ )
658
+ fail("backend_mismatch");
659
+ }
660
+ async function receipt(contract, policyOutput, deps) {
661
+ const copy = structuredClone(contract);
662
+ delete copy.terraform.plan.sha256;
663
+ delete copy.terraform.plan.policy_result_sha256;
664
+ return sha256(
665
+ stable({
666
+ contract: copy,
667
+ policyOutput,
668
+ policy: await deps.readFile(insideRoot(copy.terraform.plan.policy_contract_path), "utf8"),
669
+ lock: await deps.readFile(insideRoot("infra/terraform/.terraform.lock.hcl"), "utf8"),
670
+ }),
671
+ );
672
+ }
673
+ async function verifyPolicyTargets(contract, deps) {
674
+ const policy = JSON.parse(
675
+ await deps.readFile(insideRoot(contract.terraform.plan.policy_contract_path), "utf8"),
676
+ );
677
+ for (const field of ["account_id", "zone_id", "zone_name"])
678
+ if (policy.cloudflare?.[field] !== contract.cloudflare[field]) fail("policy_target_mismatch");
679
+ for (const role of ["edge", "application"]) {
680
+ const primary = contract.hostinger.vps[role],
681
+ expected = policy.hostinger?.[role];
682
+ if (!expected || String(expected.id ?? "") !== String(primary.id ?? ""))
683
+ fail("policy_target_mismatch");
684
+ if (primary.creation)
685
+ for (const field of ["plan", "data_center_id", "template_id", "hostname", "ssh_key_ids"])
686
+ if (stable(expected[field]) !== stable(primary.creation[field]))
687
+ fail("policy_target_mismatch");
688
+ }
689
+ if (
690
+ contract.hostinger.vps.edge.ssh_host &&
691
+ policy.cloudflare.edge_ipv4 !== contract.hostinger.vps.edge.ssh_host
692
+ )
693
+ fail("policy_target_mismatch");
694
+ if (
695
+ contract.hostinger.vps.application.ssh_host &&
696
+ policy.hostinger.application.ipv4 !== contract.hostinger.vps.application.ssh_host
697
+ )
698
+ fail("policy_target_mismatch");
699
+ const full = (name) =>
700
+ name === "@"
701
+ ? contract.cloudflare.zone_name
702
+ : inZone(name, contract.cloudflare.zone_name)
703
+ ? name
704
+ : `${name}.${contract.cloudflare.zone_name}`;
705
+ const primaryNames = [...dnsInventory(contract.cloudflare)].map(full).sort();
706
+ const policyNames = Object.keys(policy.cloudflare.dns_records ?? {})
707
+ .map(full)
708
+ .sort();
709
+ if (stable(primaryNames) !== stable(policyNames)) fail("policy_target_mismatch");
710
+ }
711
+ function stable(value) {
712
+ if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
713
+ if (value && typeof value === "object")
714
+ return `{${Object.keys(value)
715
+ .sort()
716
+ .map((key) => `${JSON.stringify(key)}:${stable(value[key])}`)
717
+ .join(",")}}`;
718
+ return JSON.stringify(value);
719
+ }
720
+ function canonicalPlan(path, extension = ".tfplan") {
721
+ return new RegExp(`^\\.local/cloud/plans/[A-Za-z0-9_-]+${extension.replace(".", "\\.")}$`).test(
722
+ path ?? "",
723
+ );
724
+ }
725
+ function requireString(object, key) {
726
+ if (typeof object?.[key] !== "string" || !object[key].trim()) fail("invalid_contract");
727
+ return object[key];
728
+ }
729
+ function positiveTimeout(value) {
730
+ return Number.isInteger(value) && value > 0 && value <= 120 ? value : 15;
731
+ }
732
+ function result(operation, provider, targets) {
733
+ return { operation, provider, targets, observed_at: new Date().toISOString() };
734
+ }
735
+ function nonempty(value) {
736
+ return typeof value === "string" && value.trim() ? value : null;
737
+ }
738
+ function sha256(value) {
739
+ return createHash("sha256").update(value).digest("hex");
740
+ }
741
+ function parse(values) {
742
+ const out = {};
743
+ for (let index = 0; index < values.length; index += 2) {
744
+ if (
745
+ !values[index]?.startsWith("--") ||
746
+ values[index + 1] === undefined ||
747
+ values[index + 1].startsWith("--")
748
+ )
749
+ fail("invalid_arguments");
750
+ const key = normalize(values[index].slice(2));
751
+ if (Object.hasOwn(out, key)) fail("invalid_arguments");
752
+ out[key] = values[index + 1];
753
+ }
754
+ return out;
755
+ }
756
+ function exact(actual, allowed) {
757
+ const normalized = allowed.map(normalize);
758
+ if (
759
+ Object.keys(actual).some((key) => !normalized.includes(key)) ||
760
+ normalized.some((key) => actual[key] === undefined)
761
+ )
762
+ fail("invalid_arguments");
763
+ }
764
+ function requireValue(actual, key) {
765
+ const value = actual[normalize(key)];
766
+ if (!value) fail("invalid_arguments");
767
+ return value;
768
+ }
769
+ function normalize(key) {
770
+ return key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
771
+ }
772
+ function insideRoot(path) {
773
+ const absolute = resolve(root, path);
774
+ const rel = relative(root, absolute);
775
+ if (rel === "" || isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`))
776
+ fail("path_outside_project");
777
+ return absolute;
778
+ }
779
+ function fail(code) {
780
+ const error = new Error(code);
781
+ error.code = code;
782
+ throw error;
783
+ }
784
+ function errorCode(error) {
785
+ return typeof error?.code === "string" ? error.code : "invalid_request";
786
+ }