@hs-x/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +1001 -0
  2. package/dist/account-store.d.ts +51 -0
  3. package/dist/account-store.d.ts.map +1 -0
  4. package/dist/account-store.js +138 -0
  5. package/dist/account-store.js.map +1 -0
  6. package/dist/bin/hs-x.d.ts +3 -0
  7. package/dist/bin/hs-x.d.ts.map +1 -0
  8. package/dist/bin/hs-x.js +47 -0
  9. package/dist/bin/hs-x.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +595 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli-error.d.ts +36 -0
  15. package/dist/cli-error.d.ts.map +1 -0
  16. package/dist/cli-error.js +40 -0
  17. package/dist/cli-error.js.map +1 -0
  18. package/dist/cloudflare-auth.d.ts +25 -0
  19. package/dist/cloudflare-auth.d.ts.map +1 -0
  20. package/dist/cloudflare-auth.js +251 -0
  21. package/dist/cloudflare-auth.js.map +1 -0
  22. package/dist/cloudflare-kv.d.ts +23 -0
  23. package/dist/cloudflare-kv.d.ts.map +1 -0
  24. package/dist/cloudflare-kv.js +101 -0
  25. package/dist/cloudflare-kv.js.map +1 -0
  26. package/dist/cloudflare-oauth-store.d.ts +16 -0
  27. package/dist/cloudflare-oauth-store.d.ts.map +1 -0
  28. package/dist/cloudflare-oauth-store.js +80 -0
  29. package/dist/cloudflare-oauth-store.js.map +1 -0
  30. package/dist/cloudflare-oauth.d.ts +82 -0
  31. package/dist/cloudflare-oauth.d.ts.map +1 -0
  32. package/dist/cloudflare-oauth.js +336 -0
  33. package/dist/cloudflare-oauth.js.map +1 -0
  34. package/dist/cloudflare-pointer.d.ts +13 -0
  35. package/dist/cloudflare-pointer.d.ts.map +1 -0
  36. package/dist/cloudflare-pointer.js +46 -0
  37. package/dist/cloudflare-pointer.js.map +1 -0
  38. package/dist/command-history.d.ts +7 -0
  39. package/dist/command-history.d.ts.map +1 -0
  40. package/dist/command-history.js +34 -0
  41. package/dist/command-history.js.map +1 -0
  42. package/dist/commands/account.d.ts +7 -0
  43. package/dist/commands/account.d.ts.map +1 -0
  44. package/dist/commands/account.js +315 -0
  45. package/dist/commands/account.js.map +1 -0
  46. package/dist/commands/api.d.ts +36 -0
  47. package/dist/commands/api.d.ts.map +1 -0
  48. package/dist/commands/api.js +521 -0
  49. package/dist/commands/api.js.map +1 -0
  50. package/dist/commands/completion.d.ts +7 -0
  51. package/dist/commands/completion.d.ts.map +1 -0
  52. package/dist/commands/completion.js +121 -0
  53. package/dist/commands/completion.js.map +1 -0
  54. package/dist/commands/connect.d.ts +7 -0
  55. package/dist/commands/connect.d.ts.map +1 -0
  56. package/dist/commands/connect.js +1123 -0
  57. package/dist/commands/connect.js.map +1 -0
  58. package/dist/commands/control-plane-read.d.ts +22 -0
  59. package/dist/commands/control-plane-read.d.ts.map +1 -0
  60. package/dist/commands/control-plane-read.js +350 -0
  61. package/dist/commands/control-plane-read.js.map +1 -0
  62. package/dist/commands/deploy-promote.d.ts +14 -0
  63. package/dist/commands/deploy-promote.d.ts.map +1 -0
  64. package/dist/commands/deploy-promote.js +105 -0
  65. package/dist/commands/deploy-promote.js.map +1 -0
  66. package/dist/commands/deploy.d.ts +18 -0
  67. package/dist/commands/deploy.d.ts.map +1 -0
  68. package/dist/commands/deploy.js +2764 -0
  69. package/dist/commands/deploy.js.map +1 -0
  70. package/dist/commands/dev.d.ts +7 -0
  71. package/dist/commands/dev.d.ts.map +1 -0
  72. package/dist/commands/dev.js +913 -0
  73. package/dist/commands/dev.js.map +1 -0
  74. package/dist/commands/doctor.d.ts +8 -0
  75. package/dist/commands/doctor.d.ts.map +1 -0
  76. package/dist/commands/doctor.js +258 -0
  77. package/dist/commands/doctor.js.map +1 -0
  78. package/dist/commands/flags.d.ts +22 -0
  79. package/dist/commands/flags.d.ts.map +1 -0
  80. package/dist/commands/flags.js +185 -0
  81. package/dist/commands/flags.js.map +1 -0
  82. package/dist/commands/help-command.d.ts +13 -0
  83. package/dist/commands/help-command.d.ts.map +1 -0
  84. package/dist/commands/help-command.js +482 -0
  85. package/dist/commands/help-command.js.map +1 -0
  86. package/dist/commands/history.d.ts +6 -0
  87. package/dist/commands/history.d.ts.map +1 -0
  88. package/dist/commands/history.js +42 -0
  89. package/dist/commands/history.js.map +1 -0
  90. package/dist/commands/init.d.ts +8 -0
  91. package/dist/commands/init.d.ts.map +1 -0
  92. package/dist/commands/init.js +233 -0
  93. package/dist/commands/init.js.map +1 -0
  94. package/dist/commands/link.d.ts +26 -0
  95. package/dist/commands/link.d.ts.map +1 -0
  96. package/dist/commands/link.js +441 -0
  97. package/dist/commands/link.js.map +1 -0
  98. package/dist/commands/login.d.ts +8 -0
  99. package/dist/commands/login.d.ts.map +1 -0
  100. package/dist/commands/login.js +381 -0
  101. package/dist/commands/login.js.map +1 -0
  102. package/dist/commands/migrate.d.ts +8 -0
  103. package/dist/commands/migrate.d.ts.map +1 -0
  104. package/dist/commands/migrate.js +258 -0
  105. package/dist/commands/migrate.js.map +1 -0
  106. package/dist/commands/rollback.d.ts +21 -0
  107. package/dist/commands/rollback.d.ts.map +1 -0
  108. package/dist/commands/rollback.js +301 -0
  109. package/dist/commands/rollback.js.map +1 -0
  110. package/dist/commands/secrets.d.ts +7 -0
  111. package/dist/commands/secrets.d.ts.map +1 -0
  112. package/dist/commands/secrets.js +230 -0
  113. package/dist/commands/secrets.js.map +1 -0
  114. package/dist/commands/status.d.ts +7 -0
  115. package/dist/commands/status.d.ts.map +1 -0
  116. package/dist/commands/status.js +241 -0
  117. package/dist/commands/status.js.map +1 -0
  118. package/dist/commands/unlink.d.ts +21 -0
  119. package/dist/commands/unlink.d.ts.map +1 -0
  120. package/dist/commands/unlink.js +83 -0
  121. package/dist/commands/unlink.js.map +1 -0
  122. package/dist/commands/update.d.ts +11 -0
  123. package/dist/commands/update.d.ts.map +1 -0
  124. package/dist/commands/update.js +154 -0
  125. package/dist/commands/update.js.map +1 -0
  126. package/dist/commands/validate.d.ts +9 -0
  127. package/dist/commands/validate.d.ts.map +1 -0
  128. package/dist/commands/validate.js +39 -0
  129. package/dist/commands/validate.js.map +1 -0
  130. package/dist/config.d.ts +12 -0
  131. package/dist/config.d.ts.map +1 -0
  132. package/dist/config.js +64 -0
  133. package/dist/config.js.map +1 -0
  134. package/dist/constants.d.ts +4 -0
  135. package/dist/constants.d.ts.map +1 -0
  136. package/dist/constants.js +4 -0
  137. package/dist/constants.js.map +1 -0
  138. package/dist/control-plane-fetch.d.ts +34 -0
  139. package/dist/control-plane-fetch.d.ts.map +1 -0
  140. package/dist/control-plane-fetch.js +73 -0
  141. package/dist/control-plane-fetch.js.map +1 -0
  142. package/dist/control-plane-loader.d.ts +16 -0
  143. package/dist/control-plane-loader.d.ts.map +1 -0
  144. package/dist/control-plane-loader.js +24 -0
  145. package/dist/control-plane-loader.js.map +1 -0
  146. package/dist/dev/compat-shim.d.ts +40 -0
  147. package/dist/dev/compat-shim.d.ts.map +1 -0
  148. package/dist/dev/compat-shim.js +65 -0
  149. package/dist/dev/compat-shim.js.map +1 -0
  150. package/dist/dev/event-bus.d.ts +27 -0
  151. package/dist/dev/event-bus.d.ts.map +1 -0
  152. package/dist/dev/event-bus.js +32 -0
  153. package/dist/dev/event-bus.js.map +1 -0
  154. package/dist/dev/log-server.d.ts +52 -0
  155. package/dist/dev/log-server.d.ts.map +1 -0
  156. package/dist/dev/log-server.js +216 -0
  157. package/dist/dev/log-server.js.map +1 -0
  158. package/dist/dev/session-manager.d.ts +33 -0
  159. package/dist/dev/session-manager.d.ts.map +1 -0
  160. package/dist/dev/session-manager.js +132 -0
  161. package/dist/dev/session-manager.js.map +1 -0
  162. package/dist/dev/stream-renderer.d.ts +22 -0
  163. package/dist/dev/stream-renderer.d.ts.map +1 -0
  164. package/dist/dev/stream-renderer.js +65 -0
  165. package/dist/dev/stream-renderer.js.map +1 -0
  166. package/dist/dev/tunnel.d.ts +40 -0
  167. package/dist/dev/tunnel.d.ts.map +1 -0
  168. package/dist/dev/tunnel.js +139 -0
  169. package/dist/dev/tunnel.js.map +1 -0
  170. package/dist/effect-http.d.ts +10 -0
  171. package/dist/effect-http.d.ts.map +1 -0
  172. package/dist/effect-http.js +38 -0
  173. package/dist/effect-http.js.map +1 -0
  174. package/dist/errors-registry.d.ts +11 -0
  175. package/dist/errors-registry.d.ts.map +1 -0
  176. package/dist/errors-registry.js +554 -0
  177. package/dist/errors-registry.js.map +1 -0
  178. package/dist/errors.d.ts +58 -0
  179. package/dist/errors.d.ts.map +1 -0
  180. package/dist/errors.js +30 -0
  181. package/dist/errors.js.map +1 -0
  182. package/dist/help.d.ts +6 -0
  183. package/dist/help.d.ts.map +1 -0
  184. package/dist/help.js +100 -0
  185. package/dist/help.js.map +1 -0
  186. package/dist/history.d.ts +15 -0
  187. package/dist/history.d.ts.map +1 -0
  188. package/dist/history.js +69 -0
  189. package/dist/history.js.map +1 -0
  190. package/dist/hubspot-auth.d.ts +53 -0
  191. package/dist/hubspot-auth.d.ts.map +1 -0
  192. package/dist/hubspot-auth.js +301 -0
  193. package/dist/hubspot-auth.js.map +1 -0
  194. package/dist/hubspot-developer-client.d.ts +10 -0
  195. package/dist/hubspot-developer-client.d.ts.map +1 -0
  196. package/dist/hubspot-developer-client.js +212 -0
  197. package/dist/hubspot-developer-client.js.map +1 -0
  198. package/dist/index.d.ts +5 -0
  199. package/dist/index.d.ts.map +1 -0
  200. package/dist/index.js +4 -0
  201. package/dist/index.js.map +1 -0
  202. package/dist/init/templates.d.ts +18 -0
  203. package/dist/init/templates.d.ts.map +1 -0
  204. package/dist/init/templates.js +239 -0
  205. package/dist/init/templates.js.map +1 -0
  206. package/dist/load-env.d.ts +16 -0
  207. package/dist/load-env.d.ts.map +1 -0
  208. package/dist/load-env.js +69 -0
  209. package/dist/load-env.js.map +1 -0
  210. package/dist/machine-id.d.ts +3 -0
  211. package/dist/machine-id.d.ts.map +1 -0
  212. package/dist/machine-id.js +41 -0
  213. package/dist/machine-id.js.map +1 -0
  214. package/dist/paths.d.ts +4 -0
  215. package/dist/paths.d.ts.map +1 -0
  216. package/dist/paths.js +19 -0
  217. package/dist/paths.js.map +1 -0
  218. package/dist/prompt.d.ts +43 -0
  219. package/dist/prompt.d.ts.map +1 -0
  220. package/dist/prompt.js +379 -0
  221. package/dist/prompt.js.map +1 -0
  222. package/dist/reporter/human.d.ts +28 -0
  223. package/dist/reporter/human.d.ts.map +1 -0
  224. package/dist/reporter/human.js +126 -0
  225. package/dist/reporter/human.js.map +1 -0
  226. package/dist/reporter/index.d.ts +14 -0
  227. package/dist/reporter/index.d.ts.map +1 -0
  228. package/dist/reporter/index.js +37 -0
  229. package/dist/reporter/index.js.map +1 -0
  230. package/dist/reporter/json.d.ts +43 -0
  231. package/dist/reporter/json.d.ts.map +1 -0
  232. package/dist/reporter/json.js +146 -0
  233. package/dist/reporter/json.js.map +1 -0
  234. package/dist/reporter/style.d.ts +34 -0
  235. package/dist/reporter/style.d.ts.map +1 -0
  236. package/dist/reporter/style.js +97 -0
  237. package/dist/reporter/style.js.map +1 -0
  238. package/dist/reporter/types.d.ts +41 -0
  239. package/dist/reporter/types.d.ts.map +1 -0
  240. package/dist/reporter/types.js +2 -0
  241. package/dist/reporter/types.js.map +1 -0
  242. package/dist/result.d.ts +4 -0
  243. package/dist/result.d.ts.map +1 -0
  244. package/dist/result.js +2 -0
  245. package/dist/result.js.map +1 -0
  246. package/dist/services/account-store.d.ts +31 -0
  247. package/dist/services/account-store.d.ts.map +1 -0
  248. package/dist/services/account-store.js +135 -0
  249. package/dist/services/account-store.js.map +1 -0
  250. package/dist/services/app-paths.d.ts +25 -0
  251. package/dist/services/app-paths.d.ts.map +1 -0
  252. package/dist/services/app-paths.js +34 -0
  253. package/dist/services/app-paths.js.map +1 -0
  254. package/dist/services/cloudflare-auth.d.ts +83 -0
  255. package/dist/services/cloudflare-auth.d.ts.map +1 -0
  256. package/dist/services/cloudflare-auth.js +30 -0
  257. package/dist/services/cloudflare-auth.js.map +1 -0
  258. package/dist/services/cloudflare-kv.d.ts +45 -0
  259. package/dist/services/cloudflare-kv.d.ts.map +1 -0
  260. package/dist/services/cloudflare-kv.js +151 -0
  261. package/dist/services/cloudflare-kv.js.map +1 -0
  262. package/dist/services/command-history.d.ts +29 -0
  263. package/dist/services/command-history.d.ts.map +1 -0
  264. package/dist/services/command-history.js +62 -0
  265. package/dist/services/command-history.js.map +1 -0
  266. package/dist/services/control-plane.d.ts +32 -0
  267. package/dist/services/control-plane.d.ts.map +1 -0
  268. package/dist/services/control-plane.js +57 -0
  269. package/dist/services/control-plane.js.map +1 -0
  270. package/dist/services/env-loader.d.ts +18 -0
  271. package/dist/services/env-loader.d.ts.map +1 -0
  272. package/dist/services/env-loader.js +34 -0
  273. package/dist/services/env-loader.js.map +1 -0
  274. package/dist/services/http.d.ts +19 -0
  275. package/dist/services/http.d.ts.map +1 -0
  276. package/dist/services/http.js +9 -0
  277. package/dist/services/http.js.map +1 -0
  278. package/dist/services/live.d.ts +16 -0
  279. package/dist/services/live.d.ts.map +1 -0
  280. package/dist/services/live.js +26 -0
  281. package/dist/services/live.js.map +1 -0
  282. package/dist/services/machine-id.d.ts +18 -0
  283. package/dist/services/machine-id.d.ts.map +1 -0
  284. package/dist/services/machine-id.js +39 -0
  285. package/dist/services/machine-id.js.map +1 -0
  286. package/dist/services/reporter.d.ts +55 -0
  287. package/dist/services/reporter.d.ts.map +1 -0
  288. package/dist/services/reporter.js +49 -0
  289. package/dist/services/reporter.js.map +1 -0
  290. package/dist/state-store.d.ts +39 -0
  291. package/dist/state-store.d.ts.map +1 -0
  292. package/dist/state-store.js +89 -0
  293. package/dist/state-store.js.map +1 -0
  294. package/dist/telemetry.d.ts +13 -0
  295. package/dist/telemetry.d.ts.map +1 -0
  296. package/dist/telemetry.js +129 -0
  297. package/dist/telemetry.js.map +1 -0
  298. package/dist/tenant-state.d.ts +69 -0
  299. package/dist/tenant-state.d.ts.map +1 -0
  300. package/dist/tenant-state.js +161 -0
  301. package/dist/tenant-state.js.map +1 -0
  302. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,1001 @@
1
+ # HS-X CLI Interfaces
2
+
3
+ > **Status: design doc, not a current-state reference.**
4
+ >
5
+ > This file is the CLI *contract* — what `hs-x` will look like at completion. Many sections below describe commands, flags, or packages that are **not yet implemented**. For the actually-shipped CLI surface, run `hs-x --help` or read `packages/cli/src/index.ts` (the dispatch switch is the source of truth).
6
+ >
7
+ > Sections that drift from current implementation are marked inline with `[NOT YET IMPLEMENTED]` or `[PARTIAL]`. As of 2026-05-20, shipped top-level commands are: `init`/`create`, `check`, `validate`, `deploy`, `promote`, `connect`, `secrets`, `login`, `whoami`, `logout`, `completion`, `api`, `account`, `audit`, `drift`, `checkpoint`/`logs`, `routing`, `rollback`, `dev`, `migrate`, `rollout`/`flags`, `doctor`, `status`, `history`, `update`/`upgrade`. The planned monorepo package layout below does **not** match the actual `packages/` tree — see the workspace for the real list.
8
+
9
+ *Working draft. This is the CLI contract for `hs-x` after the SDK/interface review pass. It assumes a monorepo implementation and keeps the CLI as orchestration over reusable packages. The CLI is the human surface, the CI surface, and the coding-agent surface; every command that matters must have stable JSON output and a matching MCP tool.*
10
+
11
+ ## What changed in this pass
12
+
13
+ This update folds in the root docs, `sdk/README.md`, and the current review findings that were missing from the first CLI draft:
14
+
15
+ - first-run setup needs a single `hs-x connect` golden path;
16
+ - portal-scoped dev routing requires an initial deployed runtime, so `dev` cannot pretend it is purely local;
17
+ - the migration/card path should support Cloudflare-light or HubSpot-only first value where possible;
18
+ - deploys use ADR-009 attestation-gated promotion, compatibility classification, rollback, and promotion;
19
+ - metadata iteration needs an explicit command path (`dev upload` or `deploy --metadata-only`);
20
+ - dev overrides should be exact capability matches in v1, not wildcard by default;
21
+ - data-plane drift needs first-class commands (`doctor`, `drift`, `repair`);
22
+ - sync needs operational commands (`status`, `run`, `pause`, `resume`, `reset`, `diff`);
23
+ - CLI JSON must stay token-efficient for agents.
24
+
25
+ ## Design goals
26
+
27
+ - One CLI surface for humans, CI, and coding agents.
28
+ - Commands are thin orchestration over reusable workspace packages.
29
+ - Every project-bound command resolves account, project, environment, and runtime posture the same way.
30
+ - Every high-value command has stable `--json` output and predictable exit codes.
31
+ - Interactive prompts are optional; CI and agents can pass flags or environment variables instead.
32
+ - The CLI understands monorepos without forcing every package to be an HS-X app.
33
+ - Every command that mutates HubSpot, Cloudflare, or rollout state prints a plan in human mode and emits a structured plan in JSON mode.
34
+ - The CLI is honest about external prerequisites: HubSpot developer/test portal, Cloudflare account, Workers Paid primitives, and Cloudflare DNS/custom-domain requirements.
35
+
36
+ ## Monorepo package layout
37
+
38
+ > **[PLANNED — does not match current `packages/` tree]** The actual workspace uses a smaller set of packages (`checkpoint`, `codegen`, `control-plane`, `design`, `e2e`, `email`, `fixtures`, `hubspot-cli`, `hubspot-client`, `hubspot-mock`, `mcp`, `runtime`, `sdk`, `stream-relay`, `studio`, `types`, `validator`). The breakdown below reflects the target decomposition, not what exists today.
39
+
40
+ The CLI package owns command registration, option parsing, prompts, and rendering. It does not own the core logic.
41
+
42
+ ```text
43
+ packages/
44
+ cli/ # `hs-x` binary; command registration, prompts, rendering
45
+ common/ # ids, diagnostics, errors, result envelopes, exit codes
46
+ config/ # workspace/project discovery, project links, account resolution
47
+ sdk/ # public `@hs-x/sdk` authoring API
48
+ compiler/ # source discovery, capability graph, hsmeta generation
49
+ validator/ # `hs-x validate` rules and autofix engine
50
+ types-generator/ # `hs-x types` implementation
51
+ refs/ # generated @hs-x/refs support
52
+ deployer/ # deploy state machine, attestation-gated promotion, rollback
53
+ dev-server/ # dev loop, tunnel, override registration, wrangler/local-dev-lib orchestration
54
+ hubspot/ # @hubspot/local-dev-lib wrapper and HubSpot API adapters
55
+ cloudflare/ # Cloudflare account checks, route/domain checks, credential lease use
56
+ control-client/ # typed control-plane API client
57
+ observability/ # Checkpoint logs, AE/D1/R2 readers, live tail rendering
58
+ sync-engine/ # sync operational controls exposed by `hs-x sync ...`
59
+ migration/ # legacy app/card migration and rollout scaffolding
60
+ ```
61
+
62
+ The same packages should be usable by the MCP server, tests, Studio export/deploy flows, scripts in CI, and future programmatic APIs.
63
+
64
+ ## Workspace and project discovery
65
+
66
+ A monorepo can contain several HS-X apps plus shared packages.
67
+
68
+ ```text
69
+ repo/
70
+ hsx.workspace.json
71
+ packages/shared/
72
+ apps/deal-enricher/
73
+ hsx.config.ts
74
+ hsx.account.json
75
+ apps/support-copilot/
76
+ hsx.config.ts
77
+ hsx.account.json
78
+ ```
79
+
80
+ `hsx.workspace.json`:
81
+
82
+ ```ts
83
+ export interface HsxWorkspaceConfig {
84
+ version: 1;
85
+ projects: HsxWorkspaceProjectRef[];
86
+ defaultProject?: string;
87
+ }
88
+
89
+ export interface HsxWorkspaceProjectRef {
90
+ /** Stable local name used by `--project`. */
91
+ name: string;
92
+ /** Path relative to the workspace root. */
93
+ path: string;
94
+ /** Optional default environment for project-bound commands. */
95
+ defaultEnv?: HsxEnvironmentName;
96
+ }
97
+ ```
98
+
99
+ Example:
100
+
101
+ ```json
102
+ {
103
+ "version": 1,
104
+ "defaultProject": "deal-enricher",
105
+ "projects": [
106
+ { "name": "deal-enricher", "path": "apps/deal-enricher", "defaultEnv": "staging" },
107
+ { "name": "support-copilot", "path": "apps/support-copilot" }
108
+ ]
109
+ }
110
+ ```
111
+
112
+ If `hsx.workspace.json` is absent, the resolver walks upward from `cwd` until it finds `hsx.config.ts`.
113
+
114
+ ### Resolver rules
115
+
116
+ Project-bound commands resolve the project in this order:
117
+
118
+ 1. `--project <name-or-path>`
119
+ 2. `HSX_PROJECT`
120
+ 3. nearest parent containing `hsx.config.ts`
121
+ 4. workspace `defaultProject`
122
+ 5. fail with a list of known projects
123
+
124
+ Project-bound commands include:
125
+
126
+ ```text
127
+ dev, deploy, promote, rollback, types, validate, rollout, logs,
128
+ sync, upgrade, check, doctor, drift, checkpoint, repair, optimize
129
+ ```
130
+
131
+ Account resolution is project-bound once `hsx.account.json` exists. A globally active account is only a fallback for unlinked project creation/linking. If a user passes `--account` that conflicts with the linked project account, the CLI fails closed.
132
+
133
+ ```ts
134
+ export interface ResolvedProjectContext {
135
+ workspaceRoot: string;
136
+ projectRoot: string;
137
+ projectName: string;
138
+ configPath: string;
139
+ link?: ProjectLinkFile;
140
+ account: ResolvedAccount;
141
+ environment: HsxEnvironmentName;
142
+ runtimePosture: RuntimePosture;
143
+ invokedFrom: string;
144
+ }
145
+
146
+ export type RuntimePosture = "byo-cloudflare" | "managed-cloudflare" | "hubspot-only";
147
+ ```
148
+
149
+ `hubspot-only` is for Cloudflare-light paths such as App Card migration or UI-only projects that do not need an external backend. If a command requires Workers, DOs, KV, D1, Queues, or routes, it upgrades the posture requirement and explains why.
150
+
151
+ ## Local account/project link
152
+
153
+ `hsx.account.json` is repository-local state, not app config. It is the link file written by `hs-x connect` and read by project-bound commands.
154
+
155
+ ```ts
156
+ export interface ProjectLinkFile {
157
+ version: 1;
158
+ accountId: string;
159
+ accountSlug: string;
160
+ projectId: string;
161
+ projectSlug: string;
162
+ hubspotAppId?: string;
163
+ defaultEnvironment: HsxEnvironmentName;
164
+ runtimePosture: RuntimePosture;
165
+ environments: Record<HsxEnvironmentName, LinkedEnvironment>;
166
+ linkedAt: string;
167
+ }
168
+
169
+ export type HsxEnvironmentName = "development" | "staging" | "production" | (string & {});
170
+
171
+ export interface LinkedEnvironment {
172
+ environmentId: string;
173
+ hubspotProjectId?: string;
174
+ cloudflareAccountId?: string;
175
+ routeBaseUrl?: string;
176
+ activeDeployId?: string;
177
+ lastManifestHash?: string;
178
+ }
179
+ ```
180
+
181
+ Human output starts with a resolved context header. JSON output includes the same context in `context`.
182
+
183
+ ```text
184
+ Account: client-a
185
+ Project: deal-enricher
186
+ Environment: staging
187
+ Runtime: BYO Cloudflare
188
+ Cloudflare: Client A Production CF
189
+ Active deploy: deploy_abc123 / rev_green
190
+ ```
191
+
192
+ ## Shared command contract
193
+
194
+ All commands build a `CommandContext` before doing work.
195
+
196
+ ```ts
197
+ export interface CommandContext {
198
+ mode: "human" | "json";
199
+ cwd: string;
200
+ project?: ResolvedProjectContext;
201
+ account?: ResolvedAccount;
202
+ auth: AuthSession;
203
+ io: CommandIO;
204
+ control: ControlPlaneClient;
205
+ clock: Clock;
206
+ }
207
+
208
+ export interface CommandIO {
209
+ stdout(message: string): void;
210
+ stderr(message: string): void;
211
+ prompt<T>(request: PromptRequest<T>): Promise<T>;
212
+ spinner<T>(label: string, task: () => Promise<T>): Promise<T>;
213
+ }
214
+ ```
215
+
216
+ Commands return a typed result. The renderer converts the result to human text or JSON.
217
+
218
+ ```ts
219
+ export interface CommandResult<T = unknown> {
220
+ ok: true;
221
+ command: string;
222
+ context: ResultContext;
223
+ data: T;
224
+ warnings?: HsxDiagnostic[];
225
+ }
226
+
227
+ export interface CommandFailure {
228
+ ok: false;
229
+ command: string;
230
+ context?: Partial<ResultContext>;
231
+ error: HsxErrorEnvelope;
232
+ diagnostics?: HsxDiagnostic[];
233
+ }
234
+
235
+ export interface ResultContext {
236
+ account?: { id: string; slug: string; name: string };
237
+ project?: { id?: string; slug?: string; name: string; root: string };
238
+ environment?: string;
239
+ runtimePosture?: RuntimePosture;
240
+ }
241
+ ```
242
+
243
+ JSON mode writes exactly one JSON document to stdout. Progress logs go to stderr or are suppressed.
244
+
245
+ ## Global flags and environment variables
246
+
247
+ ```text
248
+ --project <name-or-path> Select a project in a monorepo.
249
+ --account <slug-or-id> Select an HS-X account; project-bound commands fail if this conflicts with the linked project.
250
+ --env <name> Select an environment; defaults from project link or workspace.
251
+ --json Emit stable machine-readable JSON.
252
+ --quiet, -q Suppress progress output without changing output format.
253
+ --no-telemetry Disable CLI telemetry for this invocation.
254
+ --no-update-check Disable the non-blocking version hint for this invocation.
255
+ --no-color Disable ANSI output.
256
+ --yes Accept safe prompts; never accepts breaking/semantic deploy changes.
257
+ --non-interactive Disable prompts; missing required values become errors.
258
+ --profile <name> Select local auth profile.
259
+ --cwd <path> Run as if invoked from another directory; useful for agents/CI.
260
+ ```
261
+
262
+ Environment equivalents:
263
+
264
+ ```text
265
+ HSX_PROJECT
266
+ HSX_ACCOUNT_ID
267
+ HSX_ENV
268
+ HSX_PROFILE
269
+ HSX_TOKEN # CI/service auth
270
+ HSX_HUBSPOT_PAK # HubSpot developer personal access key for CLI/local-dev-lib operations
271
+ HSX_HUBSPOT_DEVELOPER_API_KEY
272
+ # HubSpot developer API key for hapikey endpoints only
273
+ HSX_HUBSPOT_APP_ID # HubSpot app id for app-scoped OAuth client secret setup
274
+ HSX_HUBSPOT_CLIENT_ID # HubSpot OAuth client id from the app's projects page
275
+ HSX_HUBSPOT_CLIENT_SECRET # HubSpot OAuth client secret from the app's projects page
276
+ HSX_TELEMETRY_DISABLED=1
277
+ HSX_TELEMETRY_OPTOUT=1
278
+ HSX_UPDATE_CHECK_DISABLED=1
279
+ HSX_NON_INTERACTIVE=1
280
+ NO_COLOR=1
281
+ ```
282
+
283
+ Local state follows XDG paths by default:
284
+
285
+ ```text
286
+ $XDG_CONFIG_HOME/hs-x/config.json # connected accounts
287
+ $XDG_CONFIG_HOME/hs-x/cloudflare-oauth.json # Cloudflare OAuth refresh tokens
288
+ $XDG_STATE_HOME/hs-x/history.jsonl # command history
289
+ $XDG_CACHE_HOME/hs-x/update-check.json # self-update check cache
290
+ ```
291
+
292
+ `HSX_STORE_PATH`, `HSX_CLOUDFLARE_OAUTH_STORE_PATH`,
293
+ `HSX_HISTORY_PATH`, and `HSX_UPDATE_CHECK_CACHE_PATH` override the
294
+ defaults for tests and local development.
295
+
296
+ ## CLI telemetry
297
+
298
+ The CLI sends one redacted command-completion event to the HS-X control plane
299
+ after each packaged command run. The event includes command name, optional
300
+ subcommand, CLI version, output mode, OS, architecture, runtime, CI boolean,
301
+ exit code, and duration. It does not include args, paths, account ids, project
302
+ ids, portal ids, env vars, request/response bodies, stack traces, or credential
303
+ material. Delivery is best-effort, short-timeout, and never changes command
304
+ behavior.
305
+
306
+ Disable it with `--no-telemetry`, `HSX_TELEMETRY_DISABLED=1`,
307
+ `HSX_TELEMETRY_OPTOUT=1`, or `DO_NOT_TRACK=1`.
308
+
309
+ ## Self-update hints
310
+
311
+ Packaged CLI runs check the npm registry at most once every 24 hours and append
312
+ a one-line update hint to stderr when a newer version exists. The check is
313
+ best-effort, short-timeout, skipped in `--json`, `--json-stream`, and
314
+ `--quiet`, and disabled by `--no-update-check`,
315
+ `HSX_UPDATE_CHECK_DISABLED=1`, or `NO_UPDATE_NOTIFIER=1`.
316
+
317
+ ## HubSpot Developer Auth
318
+
319
+ The CLI keeps HubSpot developer-account credentials distinct:
320
+
321
+ - **Developer personal access key (PAK):** used as bearer auth for HubSpot CLI / `@hubspot/local-dev-lib` operations such as projects, builds, deploys, migration reads, and card-view swaps. Pass `--pak`, set `HSX_HUBSPOT_PAK`, or run HubSpot's `hs accounts auth` so `hs-x` can discover the PAK from local HubSpot CLI config.
322
+ - **Developer API key (HAPIKEY):** used as the `hapikey` query parameter only for endpoints that explicitly document developer API key access. Pass `--developer-api-key` or set `HSX_HUBSPOT_DEVELOPER_API_KEY`.
323
+ - **Per-install app tokens:** OAuth/static bearer tokens for installed portal runtime access. These are not developer-account CLI credentials and should not be used with `hs-x api hubspot`.
324
+
325
+ For raw calls, `hs-x api hubspot <path>` accepts PAK-only, HAPIKEY-only, or both. When both are present, the request includes bearer auth and the `hapikey` query parameter.
326
+
327
+ ## First-run golden paths
328
+
329
+ ### Full runtime path
330
+
331
+ This path creates or links a project, connects HubSpot and Cloudflare, creates a development environment, deploys the first runtime shell needed for portal-scoped dev routing, and then starts local dev.
332
+
333
+ ```bash
334
+ npx hs-x create deal-enricher
335
+ cd deal-enricher
336
+ hs-x connect
337
+ hs-x dev --portal 12345
338
+ ```
339
+
340
+ `hs-x connect` is intentionally broad. It handles or guides:
341
+
342
+ - local HS-X auth;
343
+ - HS-X account selection;
344
+ - HubSpot auth and developer/test portal selection;
345
+ - Cloudflare setup based on tier: create or attach an HS-X-managed CF account, connect an existing CF account via OAuth, or accept a pasted API token;
346
+ - Workers Paid / required primitive check, with an explicit downgrade path to the no-DO subset when possible;
347
+ - `permittedUrls.fetch` strategy selection: custom domain on Cloudflare DNS, explicit `*.workers.dev` URLs with the refactor caveat, or deferred development-only setup;
348
+ - environment creation;
349
+ - first stub Worker deploy per declared runtime capability, so portal-scoped dev routing has a deployed interception point;
350
+ - test-portal validation to avoid accidentally targeting a production customer portal;
351
+ - `hsx.account.json` writing.
352
+
353
+ ### Cloudflare-light card migration path
354
+
355
+ For UI-only App Card migration, the CLI should not force Cloudflare before first value.
356
+
357
+ ```bash
358
+ hs-x migrate cards ./legacy-app --project migrated-cards
359
+ hs-x deploy --hubspot-only --env staging
360
+ ```
361
+
362
+ If a migrated card needs a backend, the CLI explains the upgrade path:
363
+
364
+ ```bash
365
+ hs-x connect cloudflare
366
+ hs-x deploy --env staging
367
+ ```
368
+
369
+ This path exists to keep the migration wedge honest: modern App Card preview/export/deploy first, Cloudflare only when runtime hosting is actually needed.
370
+
371
+ ## Account and project commands
372
+
373
+ ### `hs-x login` / `hs-x logout`
374
+
375
+ > **Shipped as `hs-x login` / `hs-x logout` (no `auth` prefix).**
376
+
377
+ ```text
378
+ hs-x login [--profile <name>] [--json]
379
+ hs-x logout [--profile <name>]
380
+ ```
381
+
382
+ ### `hs-x accounts`
383
+
384
+ ```text
385
+ hs-x accounts list [--json]
386
+ hs-x accounts current [--json]
387
+ hs-x accounts switch <account>
388
+ ```
389
+
390
+ ### `hs-x create`
391
+
392
+ Workspace command. Creates a new project and optionally adds it to `hsx.workspace.json`.
393
+
394
+ ```text
395
+ hs-x create <name> [--type workflow-action|sync-source|empty] [--app-name <name>] [--dir <path>] [--account <account>] [--monorepo] [--runtime byo-cloudflare|managed-cloudflare|hubspot-only]
396
+ ```
397
+
398
+ ```ts
399
+ export interface CreateOptions {
400
+ name: string;
401
+ type: "workflow-action" | "sync-source" | "empty";
402
+ targetDir: string;
403
+ account?: string;
404
+ runtimePosture?: RuntimePosture;
405
+ addToWorkspace: boolean;
406
+ installDeps: boolean;
407
+ }
408
+
409
+ export interface CreateResult {
410
+ projectRoot: string;
411
+ packageManager: "pnpm" | "npm" | "yarn" | "bun";
412
+ filesCreated: string[];
413
+ workspaceUpdated: boolean;
414
+ nextSteps: string[];
415
+ }
416
+ ```
417
+
418
+ ### `hs-x connect`
419
+
420
+ Golden-path setup command.
421
+
422
+ ```text
423
+ hs-x connect [--project <name-or-path>] [--account <account>] [--hubspot-portal <portalId>] [--cloudflare-account <id>] [--runtime byo-cloudflare|managed-cloudflare|hubspot-only] [--env development] [--non-interactive]
424
+ ```
425
+
426
+ ```ts
427
+ export interface ConnectOptions {
428
+ projectSelector?: string;
429
+ account?: string;
430
+ hubspotPortalId?: string;
431
+ cloudflareAccountId?: string;
432
+ runtimePosture?: RuntimePosture;
433
+ environment?: HsxEnvironmentName;
434
+ provisionDevRuntime: boolean;
435
+ }
436
+
437
+ export interface ConnectResult {
438
+ projectLinked: boolean;
439
+ account: { id: string; slug: string };
440
+ hubspot: { portalId?: string; appId?: string; projectId?: string };
441
+ cloudflare?: { accountId: string; workersPaidRequired: boolean; routeBaseUrl?: string };
442
+ environment: HsxEnvironmentName;
443
+ devRuntimeReady: boolean;
444
+ nextSteps: string[];
445
+ }
446
+ ```
447
+
448
+ Subcommands for explicit setup:
449
+
450
+ ```text
451
+ hs-x connect hubspot --account-id <id> --developer-account-id <id> --display-name <name> [--pak <token>] [--developer-api-key <key>] # advanced alias for HubSpot-only setup
452
+ hs-x connect cloudflare [--account <id>] [--domain <domain>] [--accept-workers-dev]
453
+ hs-x connect domain [--domain <domain>] [--zone <zoneId>] # [NOT YET IMPLEMENTED]
454
+ ```
455
+
456
+ ### `hs-x secrets`
457
+
458
+ Stores scoped app secrets that deploy copies into the tenant Cloudflare runtime. HubSpot install OAuth secrets are scoped by HS-X account, project, environment, and HubSpot app id so one Cloudflare account can host multiple HubSpot apps safely.
459
+
460
+ ```text
461
+ hs-x secrets hubspot-oauth set --account-id <id> --project-id <id> --hubspot-app-id <id> --client-id <id> --client-secret <secret> [--env production|staging|dev]
462
+ ```
463
+
464
+ The secret is never echoed. On deploy, HS-X also provisions or reuses the scoped tenant install KV namespace and writes `HSX_TOKEN_KEY` as a Worker secret for encrypted install-token storage. The same operation is available through the control-plane API, dashboard, and MCP.
465
+
466
+ ### `hs-x project` — [NOT YET IMPLEMENTED]
467
+
468
+ > No `project` command exists today. Linking is currently done through `hs-x connect`; project status flows live under other commands.
469
+
470
+ ```text
471
+ hs-x project link [--project <name-or-path>] --account <account> [--remote-project <slug-or-id>] [--env <name>]
472
+ hs-x project status [--project <name>] [--json]
473
+ hs-x project unlink [--project <name>]
474
+ ```
475
+
476
+ ```ts
477
+ export interface ProjectStatusResult {
478
+ linked: boolean;
479
+ account?: { id: string; slug: string; cloudflareAccountId?: string };
480
+ project?: { id: string; slug: string; root: string };
481
+ environments: Array<{
482
+ name: HsxEnvironmentName;
483
+ hubspotProjectId?: string;
484
+ routeBaseUrl?: string;
485
+ activeDeployId?: string;
486
+ driftState?: DriftState;
487
+ }>;
488
+ activeDevOverrides: DevOverrideSummary[];
489
+ }
490
+ ```
491
+
492
+ ## Development commands
493
+
494
+ ### `hs-x dev`
495
+
496
+ Runs the local development loop. Default mode is portal-scoped proxy through the deployed runtime.
497
+
498
+ ```text
499
+ hs-x dev --portal <portalId> [--project <name>] [--env development] [--capability <id> ...] [--ttl 2h] [--quick-tunnel] [--dev-deploy] [--mock] [--allow-production-portal]
500
+ ```
501
+
502
+ ```ts
503
+ export interface DevOptions {
504
+ portalId?: string;
505
+ capabilities: string[]; // exact capability ids in v1
506
+ ttlSeconds: number;
507
+ tunnel: "named" | "quick" | "dev-deploy" | "mock";
508
+ watch: boolean;
509
+ openHubSpotPreview: boolean;
510
+ allowProductionPortal: boolean;
511
+ }
512
+
513
+ export interface DevSessionResult {
514
+ sessionId: string;
515
+ portalId?: string;
516
+ environment: HsxEnvironmentName;
517
+ tunnelUrl?: string;
518
+ overrides: DevOverrideSummary[];
519
+ processes: Array<{ name: "wrangler" | "cloudflared" | "hubspot"; pid?: number; status: string }>;
520
+ warnings: HsxDiagnostic[];
521
+ }
522
+ ```
523
+
524
+ Rules:
525
+
526
+ - v1 dev overrides are exact `(portalId, capabilityId, developerSession)` matches. No wildcard `*` override by default.
527
+ - Production/customer portals are blocked unless `--allow-production-portal` is passed and the command is interactive or explicitly confirmed.
528
+ - `hs-x dev` checks that a deployed dev runtime exists. If not, it offers to run the dev-runtime setup portion of `hs-x connect`.
529
+ - DO-heavy paths warn in tunnel mode and recommend `--dev-deploy`.
530
+ - UI extensions hot-reload through HubSpot's `hs project dev` / `@hubspot/local-dev-lib` underneath.
531
+
532
+ Related commands:
533
+
534
+ ```text
535
+ hs-x dev status [--project <name>] [--portal <portalId>] [--json]
536
+ hs-x dev stop --portal <portalId> [--capability <id>] # [NOT YET IMPLEMENTED]
537
+ hs-x dev cleanup [--expired] [--all]
538
+ hs-x dev doctor [--json] # use top-level `hs-x doctor`
539
+ hs-x dev upload [--metadata-only] # [NOT YET IMPLEMENTED]
540
+ ```
541
+
542
+ `hs-x dev upload` is the metadata iteration escape hatch for workflow action inputs/outputs/labels, agent metadata, card metadata, and other `*-hsmeta.json` changes. Handler iteration should not require metadata upload; metadata iteration does.
543
+
544
+ ## Deploy, promotion, and rollback
545
+
546
+ ### `hs-x deploy`
547
+
548
+ Runs the Phase 1 two-cloud deploy state machine from ADR-009. A deploy records a candidate, uploads Cloudflare and HubSpot assets through local execution, waits for runtime attestation to report healthy drift, then promotes the deploy in control-plane state. Full router-based revisioned routing is deferred.
549
+
550
+ ```text
551
+ hs-x deploy [--project <name>] [--env staging|production] [--plan] [--diff] [--resume <deployId>] [--breaking] [--semantic-change] [--expected-rps <n>] [--metadata-only] [--hubspot-only] [--json]
552
+ ```
553
+
554
+ > **[PARTIAL]** The shipped flag set diverges from this prototype. Actual flags include `--cloudflare-deploy`, `--cloudflare-dry-run`, `--hubspot-upload-only`, `--promote-when-healthy`, `--record-local`, `--local-control-plane`, `--portal-schema-fixture`, `--no-manage-schema`, `--json`. Run `hs-x deploy --help` for the current set; the flags marked `--breaking`, `--semantic-change`, `--resume`, `--expected-rps`, `--metadata-only` are design-only.
555
+
556
+ ```ts
557
+ export interface DeployOptions {
558
+ environment: HsxEnvironmentName;
559
+ planOnly: boolean;
560
+ diffOnly: boolean;
561
+ resumeDeployId?: string;
562
+ allowBreaking: boolean;
563
+ allowSemanticChange: boolean;
564
+ expectedRps?: number;
565
+ metadataOnly: boolean;
566
+ hubspotOnly: boolean;
567
+ }
568
+
569
+ export interface DeployPlanResult {
570
+ deployId: string;
571
+ planOnly: true;
572
+ compatibility: CompatibilityReport;
573
+ stateMachine: DeployStepPreview[];
574
+ resources: PlannedResourceChange[];
575
+ routes: PlannedRouteChange[];
576
+ hubspotAssets: PlannedHubSpotAssetChange[];
577
+ estimatedCost: CostEstimate;
578
+ drift?: DriftReport;
579
+ requiredConfirmations: RequiredConfirmation[];
580
+ }
581
+
582
+ export interface DeployResult {
583
+ deployId: string;
584
+ environment: HsxEnvironmentName;
585
+ activeDeployId: string;
586
+ previousDeployId?: string;
587
+ steps: DeployStepResult[];
588
+ manifestPath: string;
589
+ manifestHash: string;
590
+ dashboardUrl?: string;
591
+ }
592
+ ```
593
+
594
+ Phase 1 state machine:
595
+
596
+ ```text
597
+ plan_requested
598
+ plan_received
599
+ preflight
600
+ apply_cloudflare
601
+ deploy_worker
602
+ upload_hubspot
603
+ verify_hubspot
604
+ record
605
+ attest
606
+ promote_when_healthy
607
+ reconcile_flags
608
+ active
609
+ ```
610
+
611
+ Compatibility classes:
612
+
613
+ ```ts
614
+ export type CompatibilityClass =
615
+ | "additive-safe"
616
+ | "compatible-with-shim"
617
+ | "breaking"
618
+ | "semantic";
619
+ ```
620
+
621
+ Rules:
622
+
623
+ - `--yes` does not accept `breaking` or `semantic` changes. Those require explicit flags.
624
+ - `--metadata-only` updates HubSpot metadata without changing Worker code when safe.
625
+ - `--hubspot-only` is allowed only for projects/capabilities with no Cloudflare runtime needs.
626
+ - `--plan` includes cost shape, Workers Paid requirements, streaming polling cost, custom-domain/permitted URL warnings, and drift status.
627
+ - If drift is detected, deploy offers `hs-x repair` first rather than silently deploying on top of drift.
628
+ - `--portal-schema-fixture <path>` runs the ADR-008 schema planner against deterministic observed schema JSON.
629
+ - `--portal-schema-live` reads the target portal schema through the HubSpot developer client; `--apply-schema` applies only the managed `WILL CREATE` / `WILL ALTER` actions from that plan.
630
+
631
+ ### `hs-x promote`
632
+
633
+ Promotes an already-built/tested artifact between environments.
634
+
635
+ ```text
636
+ hs-x promote <from-env> <to-env> [--project <name>] [--deploy <deployId>] [--plan] [--json]
637
+ ```
638
+
639
+ ```ts
640
+ export interface PromoteResult {
641
+ fromEnvironment: HsxEnvironmentName;
642
+ toEnvironment: HsxEnvironmentName;
643
+ deployId: string;
644
+ promotedDeployId: string;
645
+ hubspotUploadRequired: boolean;
646
+ steps: DeployStepResult[];
647
+ }
648
+ ```
649
+
650
+ `promote` should preserve the artifact identity. It is not a rebuild unless the target environment requires environment-specific generated metadata.
651
+
652
+ ### `hs-x rollback`
653
+
654
+ ```text
655
+ hs-x rollback [--project <name>] --env production [--to <deployId>] [--plan] [--json]
656
+ ```
657
+
658
+ ```ts
659
+ export interface RollbackResult {
660
+ environment: HsxEnvironmentName;
661
+ fromDeployId: string;
662
+ toDeployId: string;
663
+ activeDeployId: string;
664
+ warnings: HsxDiagnostic[];
665
+ }
666
+ ```
667
+
668
+ Rollback promotes a prior recorded deploy through the control plane and marks the former active deploy rolled back. Rollbacks past one deploy boundary warn about card/backend contract drift and require explicit force.
669
+
670
+ ## Drift and runtime health commands
671
+
672
+ BYO Cloudflare means runtime resources can drift. These commands are project-bound.
673
+
674
+ ```text
675
+ hs-x doctor [--project <name>] [--env <name>] [--json]
676
+ hs-x drift [--project <name>] [--env <name>] [--json]
677
+ hs-x logs [--project <name>] [--env <name>] [--json]
678
+ hs-x repair [--project <name>] [--env <name>] [--plan] [--json] # [NOT YET IMPLEMENTED]
679
+ ```
680
+
681
+ ```ts
682
+ export type DriftState =
683
+ | "healthy"
684
+ | "drifted"
685
+ | "credential_revoked"
686
+ | "resource_missing"
687
+ | "unknown_code"
688
+ | "billing_untrusted"
689
+ | "unknown";
690
+
691
+ export interface DriftReport {
692
+ state: DriftState;
693
+ manifestHash?: string;
694
+ observedManifestHash?: string;
695
+ resources: DriftResourceFinding[];
696
+ billingTrust: "trusted" | "ae-only" | "manual-review";
697
+ }
698
+ ```
699
+
700
+ `doctor` runs checks and summarizes. `drift` shows resource-by-resource differences. `checkpoint` reads the project Checkpoint telemetry view: aggregate invocation counts, latency percentiles, recent failures, sampled successes, and the backing observability sources. `repair` offers to recreate missing HS-X resources or remove drift-introduced resources, with explicit confirmation for destructive changes.
701
+
702
+ ## Type generation — [NOT YET IMPLEMENTED]
703
+
704
+ > No `hs-x types` command exists today. Type generation runs implicitly via `hs-x check`/`hs-x validate`. The standalone command is planned.
705
+
706
+ ```text
707
+ hs-x types [--project <name>] [--portal <portalId>] [--out .hs-x/types.ts] [--watch] [--json]
708
+ ```
709
+
710
+ ```ts
711
+ export interface TypesOptions {
712
+ portalId?: string;
713
+ outputPath: string;
714
+ includeCustomObjects: boolean;
715
+ includeAppObjects: boolean;
716
+ }
717
+
718
+ export interface TypesResult {
719
+ outputPath: string;
720
+ moduleAlias: "@hs-x/types";
721
+ schemaVersion: string;
722
+ objects: Array<{ name: string; properties: number; source: "standard" | "portal" | "app" }>;
723
+ diagnostics: HsxDiagnostic[];
724
+ }
725
+ ```
726
+
727
+ Rules:
728
+
729
+ - Marketplace apps get strict standard/app object/event types and permissive custom-property access.
730
+ - Private/single-portal apps can generate portal-exact types.
731
+ - `hs-x upgrade` runs `hs-x types` automatically.
732
+
733
+ ## Validation
734
+
735
+ ```text
736
+ hs-x validate [--project <name>] [--fix] [--format text|json|sarif] [--json]
737
+ ```
738
+
739
+ ```ts
740
+ export interface ValidateOptions {
741
+ fix: boolean;
742
+ format: "text" | "json" | "sarif";
743
+ rules?: string[];
744
+ }
745
+
746
+ export interface ValidateResult {
747
+ status: "passed" | "passed-with-warnings" | "failed";
748
+ diagnostics: HsxDiagnostic[];
749
+ fixesApplied: FixSummary[];
750
+ }
751
+ ```
752
+
753
+ Important rule groups:
754
+
755
+ - declared scopes vs SDK-mediated calls and annotated raw calls;
756
+ - direct HubSpot `fetch` warnings/errors;
757
+ - explicit `agent.expose` required;
758
+ - workflow resolved-input validity;
759
+ - batched delivery invalid in no-DO profile;
760
+ - card backend `hubspot.fetch` envelope;
761
+ - streaming backend polling/cost warnings;
762
+ - sync identity/tombstone/retry requirements;
763
+ - generated ref alias (`@hs-x/refs`) usage;
764
+ - dev override exact-match rules;
765
+ - marketplace certification checks under `hs-x check`.
766
+
767
+ ## Logs and observability
768
+
769
+ ```text
770
+ hs-x logs [--project <name>] [--env <name>] [--capability <id>] [--portal <portalId>] [--since 15m] [--follow] [--json]
771
+ hs-x runs failures [--project <name>] [--env <name>] [--since 1h] [--json] # [NOT YET IMPLEMENTED]
772
+ hs-x runs sampled [--project <name>] [--env <name>] [--since 15m] [--json] # [NOT YET IMPLEMENTED]
773
+ hs-x metrics [--project <name>] [--env <name>] [--since 24h] [--json] # [NOT YET IMPLEMENTED]
774
+ ```
775
+
776
+ > `hs-x logs` is aliased to `hs-x checkpoint` today. `runs` and `metrics` top-level commands are planned.
777
+
778
+ ```ts
779
+ export interface LogRecord {
780
+ timestamp: string;
781
+ level: "debug" | "info" | "warn" | "error";
782
+ deployId?: string;
783
+ capabilityId?: string;
784
+ portalId?: string;
785
+ runId?: string;
786
+ fingerprint?: string;
787
+ message: string;
788
+ fields?: Record<string, unknown>;
789
+ }
790
+ ```
791
+
792
+ UI/CLI labels must be honest:
793
+
794
+ - invocation totals: complete AE-backed aggregates;
795
+ - recent failures: exemplar rows plus complete occurrence counts;
796
+ - trace exemplars: persisted examples for a fingerprint;
797
+ - recent sampled successes: sampled, labeled as sampled;
798
+ - live tail: Cloudflare Workers Logs tail.
799
+
800
+ Avoid promising “last 50 runs” unless the result is actually complete.
801
+
802
+ ## Rollout and flags
803
+
804
+ ```text
805
+ hs-x flags list [--project <name>] [--env <name>] # [NOT YET IMPLEMENTED]
806
+ hs-x flags set <flag> --portal <portalId> --value on|off|<json>
807
+ hs-x flags get <flag> --portal <portalId>
808
+ hs-x flags promote <flag> --percent <n> # [NOT YET IMPLEMENTED]
809
+ hs-x flags history <flag> [--json] # [NOT YET IMPLEMENTED]
810
+ ```
811
+
812
+ > Today only `flags set` and `flags get` ship; `list`, `promote`, `history` are planned. The command is also aliased as `hs-x rollout`.
813
+
814
+ ```ts
815
+ export interface RolloutFlagState {
816
+ flagKey: string;
817
+ environment: HsxEnvironmentName;
818
+ defaultValue: unknown;
819
+ portalOverrides: number;
820
+ rollout?: { percent: number; salt: string };
821
+ hubspotCardFlagState?: "synced" | "pending" | "error";
822
+ }
823
+ ```
824
+
825
+ Flag updates are per-portal rollout controls, not emergency global kill switches unless a stronger non-KV path is implemented.
826
+
827
+ ## Migration commands
828
+
829
+ ```text
830
+ hs-x migrate inspect <path-or-app-id> [--json]
831
+ hs-x migrate cards <path-or-app-id> [--project <name>] [--no-cloudflare] [--json]
832
+ hs-x migrate app <path-or-app-id> [--project <name>] [--json]
833
+ hs-x migrate report [--project <name>] [--json]
834
+ ```
835
+
836
+ ```ts
837
+ export interface MigrationInspectResult {
838
+ source: string;
839
+ detected: Array<"classic-crm-card" | "project-card" | "serverless-function" | "workflow-action" | "settings-page">;
840
+ automatable: MigrationFinding[];
841
+ manualWork: MigrationFinding[];
842
+ cloudflareRequired: boolean;
843
+ }
844
+
845
+ export interface MigrationResult {
846
+ projectRoot: string;
847
+ createdFiles: string[];
848
+ rolloutFlags: string[];
849
+ cloudflareRequired: boolean;
850
+ nextSteps: string[];
851
+ }
852
+ ```
853
+
854
+ The App Card migration wedge should work before the full runtime where possible. If a card backend or function migration requires Cloudflare, the CLI should say exactly which migrated feature caused that requirement.
855
+
856
+ ## Sync operations — [NOT YET IMPLEMENTED]
857
+
858
+ > No `hs-x sync` command exists today; the entire subcommand surface below is planned.
859
+
860
+ Sync commands are operational controls over deployed sync capabilities.
861
+
862
+ ```text
863
+ hs-x sync list [--project <name>] [--env <name>] [--json]
864
+ hs-x sync status <syncId> --portal <portalId> [--json]
865
+ hs-x sync run <syncId> --portal <portalId> [--cursor <cursor>] [--json]
866
+ hs-x sync pause <syncId> --portal <portalId>
867
+ hs-x sync resume <syncId> --portal <portalId>
868
+ hs-x sync reset <syncId> --portal <portalId> [--confirm]
869
+ hs-x sync diff <syncId> --portal <portalId> [--limit <n>] [--json]
870
+ hs-x sync deadletter <syncId> --portal <portalId> [--json]
871
+ hs-x sync retry <syncId> --portal <portalId> <record-id> [--json]
872
+ ```
873
+
874
+ ```ts
875
+ export interface SyncStatusResult {
876
+ syncId: string;
877
+ portalId: string;
878
+ state: "idle" | "running" | "paused" | "error" | "backing_off";
879
+ cursor?: unknown;
880
+ lastRun?: string;
881
+ recordsProcessed?: number;
882
+ deadLetterCount: number;
883
+ mappingVersion: string;
884
+ }
885
+ ```
886
+
887
+ ## Certification and optimization
888
+
889
+ ```text
890
+ hs-x check [--project <name>] [--env <name>] [--marketplace] [--json]
891
+ hs-x optimize [--project <name>] [--plan] [--write] [--json] # [NOT YET IMPLEMENTED]
892
+ ```
893
+
894
+ `check` audits marketplace readiness and should clearly separate automatable findings from human/process requirements such as demo videos, security questionnaire, verified domain, active install count, and review responses.
895
+
896
+ `optimize` is reviewable, never silent. It can flag N+1 HubSpot calls, unbatched writes, missing cursors, over-broad scopes, and expensive streaming/card patterns, then offer a diff under `--write`.
897
+
898
+ ## Upgrade — [PARTIAL]
899
+
900
+ > `hs-x update` / `hs-x upgrade` ships as a self-update helper for the CLI binary itself. The platform-version migration flow described below (with `--to`, `--plan`) is **not yet implemented**.
901
+
902
+ ```text
903
+ hs-x upgrade [--project <name>] --to 2026.09 [--plan] [--json]
904
+ ```
905
+
906
+ Upgrade changes SDK channel, platform version, generated types, generated HubSpot metadata, and migration rules. It should emit a compatibility plan before writing.
907
+
908
+ ## Diagnostics and exit codes
909
+
910
+ ```ts
911
+ export interface HsxDiagnostic {
912
+ code: string;
913
+ severity: "info" | "warning" | "error";
914
+ message: string;
915
+ file?: string;
916
+ line?: number;
917
+ column?: number;
918
+ rule?: string;
919
+ fix?: { title: string; safe: boolean };
920
+ }
921
+ ```
922
+
923
+ Exit codes:
924
+
925
+ ```text
926
+ 0 success
927
+ 1 generic failure
928
+ 2 validation failed
929
+ 3 authentication or authorization failed
930
+ 4 project/account/environment resolution failed
931
+ 5 deploy plan refused because of breaking or semantic changes
932
+ 6 external dependency unavailable (HubSpot, Cloudflare, tunnel, DNS)
933
+ 7 non-interactive command needed input
934
+ 8 command interrupted; resume may be available
935
+ 9 runtime drift detected; repair required before requested operation
936
+ 10 command unsupported for current runtime posture
937
+ ```
938
+
939
+ ## JSON output example
940
+
941
+ `hs-x deploy --plan --json` emits one JSON document.
942
+
943
+ ```json
944
+ {
945
+ "ok": true,
946
+ "command": "deploy",
947
+ "context": {
948
+ "account": { "id": "acct_123", "slug": "client-a", "name": "Client A" },
949
+ "project": { "id": "proj_456", "slug": "deal-enricher", "name": "deal-enricher", "root": "/repo/apps/deal-enricher" },
950
+ "environment": "staging",
951
+ "runtimePosture": "byo-cloudflare"
952
+ },
953
+ "data": {
954
+ "deployId": "deploy_abc123",
955
+ "planOnly": true,
956
+ "compatibility": {
957
+ "additiveSafe": 4,
958
+ "compatibleWithShim": [],
959
+ "breaking": [],
960
+ "semantic": []
961
+ },
962
+ "stateMachine": [
963
+ { "name": "plan_received", "status": "pending" },
964
+ { "name": "apply_cloudflare", "status": "pending" },
965
+ { "name": "deploy_worker", "status": "pending" },
966
+ { "name": "upload_hubspot", "status": "pending" },
967
+ { "name": "record", "status": "pending" },
968
+ { "name": "attest", "status": "pending" },
969
+ { "name": "promote_when_healthy", "status": "pending" }
970
+ ],
971
+ "estimatedCost": {
972
+ "currency": "USD",
973
+ "monthly": 12.4,
974
+ "notes": ["streaming backend ai-summary: estimate assumes 100 concurrent streams at 400ms polling"]
975
+ },
976
+ "drift": { "state": "healthy" }
977
+ },
978
+ "warnings": []
979
+ }
980
+ ```
981
+
982
+ ## First implementation slice
983
+
984
+ 1. Create `packages/common` with result envelopes, diagnostics, ids, error envelopes, and exit codes.
985
+ 2. Create `packages/config` with workspace discovery, project discovery, `hsx.account.json` parsing, runtime posture, and account conflict handling.
986
+ 3. Create `packages/cli` with global flag parsing, JSON/human renderers, and placeholder commands.
987
+ 4. Implement `hs-x project status --json` first because it exercises monorepo/project/account resolution without HubSpot or Cloudflare mutation.
988
+ 5. Implement `hs-x connect --plan` / dry-run checks next: auth state, HubSpot presence, Cloudflare prerequisite detection, route/domain choice, and what would be provisioned.
989
+ 6. Implement `hs-x validate --json` with config/workspace/SDK declaration checks.
990
+ 7. Implement `hs-x deploy --plan --json` using compiler manifests before mutating HubSpot or Cloudflare.
991
+ 8. Implement Cloudflare-light `hs-x migrate cards` and `hs-x deploy --hubspot-only` if the migration artifacts allow it.
992
+ 9. Wire real deploy execution, dev routing, drift/repair, and sync operations package by package behind the same command contracts.
993
+
994
+ ## Open CLI questions
995
+
996
+ - Managed-CF tier: does it ship early or remain an explicit future runtime posture?
997
+ - `hs-x dev upload` vs `hs-x deploy --metadata-only`: should both exist or should one alias the other?
998
+ - Exact first-run dev runtime: does `hs-x connect` always deploy a shell Worker, or only when a capability requires runtime hosting?
999
+ - HubSpot-only migration: which legacy card shapes can truly deploy without Cloudflare?
1000
+ - Fresh-clone generated refs: should `hs-x create` install a stub `@hs-x/refs` package or rely on generated `.hs-x/refs` after first `dev`/`deploy`?
1001
+ - How much of `hs-x check` belongs in `validate` vs a marketplace-specific command?