@revfleet/hscli 0.8.12 → 0.9.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 (158) hide show
  1. package/CHANGELOG.md +197 -1
  2. package/README.md +243 -10
  3. package/dist/cli.js +27 -3
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/agent/contracts.d.ts +3 -0
  6. package/dist/commands/agent/contracts.js +139 -0
  7. package/dist/commands/agent/contracts.js.map +1 -0
  8. package/dist/commands/agent/evidence.d.ts +68 -0
  9. package/dist/commands/agent/evidence.js +358 -0
  10. package/dist/commands/agent/evidence.js.map +1 -0
  11. package/dist/commands/agent/index.d.ts +3 -0
  12. package/dist/commands/agent/index.js +223 -0
  13. package/dist/commands/agent/index.js.map +1 -0
  14. package/dist/commands/agent/recipes.d.ts +13 -0
  15. package/dist/commands/agent/recipes.js +108 -0
  16. package/dist/commands/agent/recipes.js.map +1 -0
  17. package/dist/commands/agent/runner.d.ts +16 -0
  18. package/dist/commands/agent/runner.js +165 -0
  19. package/dist/commands/agent/runner.js.map +1 -0
  20. package/dist/commands/audit/index.js +4 -0
  21. package/dist/commands/audit/index.js.map +1 -1
  22. package/dist/commands/auth/index.js +64 -7
  23. package/dist/commands/auth/index.js.map +1 -1
  24. package/dist/commands/automation/index.js +30 -0
  25. package/dist/commands/automation/index.js.map +1 -1
  26. package/dist/commands/backup/index.d.ts +12 -0
  27. package/dist/commands/backup/index.js +459 -0
  28. package/dist/commands/backup/index.js.map +1 -0
  29. package/dist/commands/cms/content-ops.js +1 -1
  30. package/dist/commands/cms/content-ops.js.map +1 -1
  31. package/dist/commands/communication-preferences/index.js +207 -7
  32. package/dist/commands/communication-preferences/index.js.map +1 -1
  33. package/dist/commands/content-permissions/index.d.ts +22 -0
  34. package/dist/commands/content-permissions/index.js +141 -0
  35. package/dist/commands/content-permissions/index.js.map +1 -0
  36. package/dist/commands/context/index.d.ts +3 -0
  37. package/dist/commands/context/index.js +182 -0
  38. package/dist/commands/context/index.js.map +1 -0
  39. package/dist/commands/crm/associations.js +38 -0
  40. package/dist/commands/crm/associations.js.map +1 -1
  41. package/dist/commands/crm/custom-objects.js +39 -2
  42. package/dist/commands/crm/custom-objects.js.map +1 -1
  43. package/dist/commands/crm/dated.js +3 -3
  44. package/dist/commands/crm/dated.js.map +1 -1
  45. package/dist/commands/crm/migration.js +210 -2
  46. package/dist/commands/crm/migration.js.map +1 -1
  47. package/dist/commands/crm/properties.js +2 -1
  48. package/dist/commands/crm/properties.js.map +1 -1
  49. package/dist/commands/crm/property-batch.d.ts +1 -0
  50. package/dist/commands/crm/property-batch.js +44 -5
  51. package/dist/commands/crm/property-batch.js.map +1 -1
  52. package/dist/commands/crm/shared.js +21 -2
  53. package/dist/commands/crm/shared.js.map +1 -1
  54. package/dist/commands/crm/sync.js +1 -1
  55. package/dist/commands/crm/sync.js.map +1 -1
  56. package/dist/commands/doctor/index.js +193 -0
  57. package/dist/commands/doctor/index.js.map +1 -1
  58. package/dist/commands/email-tracking/index.d.ts +16 -0
  59. package/dist/commands/email-tracking/index.js +314 -0
  60. package/dist/commands/email-tracking/index.js.map +1 -0
  61. package/dist/commands/extensions/index.js +1 -1
  62. package/dist/commands/extensions/index.js.map +1 -1
  63. package/dist/commands/forms/index.js +15 -2
  64. package/dist/commands/forms/index.js.map +1 -1
  65. package/dist/commands/forms/legacy-v2.d.ts +7 -2
  66. package/dist/commands/forms/legacy-v2.js +103 -7
  67. package/dist/commands/forms/legacy-v2.js.map +1 -1
  68. package/dist/commands/guide/index.d.ts +1 -1
  69. package/dist/commands/guide/index.js +37 -4
  70. package/dist/commands/guide/index.js.map +1 -1
  71. package/dist/commands/internal/catalog.d.ts +1 -0
  72. package/dist/commands/internal/catalog.js +2 -0
  73. package/dist/commands/internal/catalog.js.map +1 -0
  74. package/dist/commands/internal/index.d.ts +26 -0
  75. package/dist/commands/internal/index.js +823 -0
  76. package/dist/commands/internal/index.js.map +1 -0
  77. package/dist/commands/internal-session.d.ts +15 -0
  78. package/dist/commands/internal-session.js +152 -0
  79. package/dist/commands/internal-session.js.map +1 -0
  80. package/dist/commands/lists/index.js +1 -1
  81. package/dist/commands/lists/index.js.map +1 -1
  82. package/dist/commands/marketing/email-audit.d.ts +32 -0
  83. package/dist/commands/marketing/email-audit.js +121 -0
  84. package/dist/commands/marketing/email-audit.js.map +1 -0
  85. package/dist/commands/marketing/index.js +2 -0
  86. package/dist/commands/marketing/index.js.map +1 -1
  87. package/dist/commands/mcp.js +5 -3
  88. package/dist/commands/mcp.js.map +1 -1
  89. package/dist/commands/playbooks/index.d.ts +3 -0
  90. package/dist/commands/playbooks/index.js +228 -0
  91. package/dist/commands/playbooks/index.js.map +1 -0
  92. package/dist/commands/portals/index.d.ts +3 -0
  93. package/dist/commands/portals/index.js +132 -0
  94. package/dist/commands/portals/index.js.map +1 -0
  95. package/dist/commands/reports/index.d.ts +3 -0
  96. package/dist/commands/reports/index.js +479 -0
  97. package/dist/commands/reports/index.js.map +1 -0
  98. package/dist/commands/sales-content/index.d.ts +13 -0
  99. package/dist/commands/sales-content/index.js +59 -0
  100. package/dist/commands/sales-content/index.js.map +1 -0
  101. package/dist/commands/sales-templates/index.d.ts +3 -0
  102. package/dist/commands/sales-templates/index.js +88 -0
  103. package/dist/commands/sales-templates/index.js.map +1 -0
  104. package/dist/commands/seed/cms.js +28 -45
  105. package/dist/commands/seed/cms.js.map +1 -1
  106. package/dist/commands/sequences/index.d.ts +18 -0
  107. package/dist/commands/sequences/index.js +263 -0
  108. package/dist/commands/sequences/index.js.map +1 -0
  109. package/dist/commands/settings/index.js +450 -102
  110. package/dist/commands/settings/index.js.map +1 -1
  111. package/dist/commands/settings/site-settings.d.ts +3 -0
  112. package/dist/commands/settings/site-settings.js +106 -0
  113. package/dist/commands/settings/site-settings.js.map +1 -0
  114. package/dist/commands/ui/index.js +14 -4
  115. package/dist/commands/ui/index.js.map +1 -1
  116. package/dist/commands/workflows/index.js +2 -0
  117. package/dist/commands/workflows/index.js.map +1 -1
  118. package/dist/commands/workflows/preflight.d.ts +23 -0
  119. package/dist/commands/workflows/preflight.js +295 -0
  120. package/dist/commands/workflows/preflight.js.map +1 -0
  121. package/dist/core/action-contract.d.ts +43 -0
  122. package/dist/core/action-contract.js +207 -0
  123. package/dist/core/action-contract.js.map +1 -0
  124. package/dist/core/auth.d.ts +6 -0
  125. package/dist/core/auth.js +85 -0
  126. package/dist/core/auth.js.map +1 -1
  127. package/dist/core/http.d.ts +8 -0
  128. package/dist/core/http.js +37 -5
  129. package/dist/core/http.js.map +1 -1
  130. package/dist/core/id-maps.d.ts +27 -0
  131. package/dist/core/id-maps.js +204 -0
  132. package/dist/core/id-maps.js.map +1 -0
  133. package/dist/core/output.d.ts +2 -1
  134. package/dist/core/output.js +34 -0
  135. package/dist/core/output.js.map +1 -1
  136. package/dist/mcp/profiles.d.ts +14 -0
  137. package/dist/mcp/profiles.js +68 -0
  138. package/dist/mcp/profiles.js.map +1 -0
  139. package/dist/mcp/server.d.ts +5 -3
  140. package/dist/mcp/server.js +22 -5
  141. package/dist/mcp/server.js.map +1 -1
  142. package/docs/CAPABILITY_LIBRARY.md +29 -8
  143. package/docs/COMMAND_TREE.md +99 -17
  144. package/docs/COMPARISON.md +8 -5
  145. package/docs/COOKBOOK.md +124 -5
  146. package/docs/INTERNAL-ENDPOINTS-DISCOVERY.md +813 -0
  147. package/docs/INTERNAL-ENDPOINTS.md +371 -0
  148. package/docs/INTERNAL_ENDPOINTS.md +243 -0
  149. package/docs/MARKETING_SETUP.md +21 -1
  150. package/docs/MCP.md +28 -89
  151. package/docs/MCP_TOOL_CATALOG.md +192 -0
  152. package/docs/MIGRATION_ADAPTERS.md +115 -0
  153. package/docs/PORTAL_BACKUP.md +212 -0
  154. package/docs/PUBLISHING.md +1 -1
  155. package/docs/SALES_CONTENT_INTERNAL_PUNCH_LIST.md +504 -0
  156. package/docs/TUTORIALS/secure-agent-writes.md +49 -0
  157. package/docs/WHY_HOW_WHAT.md +10 -4
  158. package/package.json +4 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,185 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.9.1 - 2026-07-10
6
+
7
+ **RevFleet evidence bridge.** A read-only `portal-baseline` can now produce and
8
+ submit a signed aggregate contract without sending its raw step outputs or
9
+ turning partial coverage into a misleading portal score.
10
+
11
+ ### Added — signed aggregate evidence
12
+
13
+ - Added `agent evidence export <run-directory>` for deterministic
14
+ `revfleet.hscli-evidence/v1` JSON. The export contains only allowlisted
15
+ counts, sampled fill rates, coverage state, portal routing metadata, and a
16
+ SHA-256 digest; HubSpot records, user identities, scopes, and asset names are
17
+ excluded.
18
+ - Added `agent evidence submit <run-directory>`. The RevFleet API key is read
19
+ from `REVFLEET_API_KEY` (or an explicitly named environment variable), never
20
+ from argv. Remote submissions require HTTPS; localhost HTTP remains
21
+ available for development.
22
+ - Evidence coverage records every baseline signal as `collected`, `failed`,
23
+ `missing`, or `invalid`. Submission is explicitly evidence-only and does not
24
+ claim to create the complete RevFleet 53-check score.
25
+
26
+ ### Security and tests
27
+
28
+ - Added strict file-size, schema, property allowlist, backend-origin, API-key
29
+ environment, and PII-exclusion controls around evidence export/submission.
30
+ - Evidence export now rejects incomplete or unattributable runs, and
31
+ `audit timeline` exposes deterministic request/error totals consumed by the
32
+ evidence contract.
33
+ - Added contract, deterministic-digest, partial-coverage, CLI-command,
34
+ transport, and insecure-destination tests.
35
+
36
+ ## 0.9.0 - 2026-07-09
37
+
38
+ **Agent-safe operator release.** hscli now wraps its broad HubSpot command
39
+ surface in deterministic execution primitives suitable for consultants,
40
+ scheduled operations, Codex, Claude, and other MCP clients. The CLI remains the
41
+ only operation layer; agents receive scoped tools and verifiable plans instead
42
+ of a second implementation of HubSpot writes.
43
+
44
+ ### Added — streamable agent output
45
+
46
+ - Added `--format jsonl`. Arrays and HubSpot result pages emit one compact,
47
+ redacted envelope per item, with paging metadata repeated on each line.
48
+ - Errors use the same one-line envelope contract, making long reads and
49
+ scheduled runs safe to stream into shell pipelines and evidence stores.
50
+
51
+ ### Added — action contracts and evidence runbooks
52
+
53
+ - Added versioned `hscli.action-contract/v1` action contracts. Contracts bind
54
+ exact argument arrays to profile, portal, expiry, optional change ticket,
55
+ verification command, deterministic ID, and SHA-256 digest.
56
+ - Added `agent contract create|inspect|verify|apply`. Apply is plan-only unless
57
+ both `--execute` and global `--force` are present; execution uses
58
+ `shell:false` and runs the contract's read-only verification command.
59
+ - Added `agent run <recipe>` with `--resume` and `--continue-on-error`.
60
+ Runbooks write a manifest, checkpoint, JSONL event ledger, per-step output,
61
+ and request telemetry, so interrupted diagnostics resume without repeating
62
+ completed work.
63
+ - Schedule renderers now invoke the evidence-producing runbook path rather than
64
+ printing a plan and calling that execution.
65
+ - Added `portal-baseline`, a one-command read-only activation runbook covering
66
+ portal identity, API reachability, users, teams, deal pipelines, CRM data
67
+ readiness, workflows, and request evidence. `--continue-on-error` preserves
68
+ a useful bundle when a token lacks one optional scope.
69
+
70
+ ### Added — MCP surface profiles
71
+
72
+ - Added `minimal`, `crm`, `migration`, and `full` MCP surface profiles. New
73
+ servers default to `minimal`; raw `hub_api_request` is registered only on
74
+ `full`.
75
+ - Added `hscli mcp --surface <profile>` and `HSCLI_MCP_SURFACE`, plus a generated
76
+ tool catalog documenting each tool's domain, operation class, and surfaces.
77
+ - Registration-time filtering reduces agent context, accidental capability
78
+ exposure, and cross-purpose tool selection while preserving the complete
79
+ surface for explicit operator use.
80
+
81
+ ### Added — production OAuth profiles
82
+
83
+ - OAuth code exchange and refresh now use HubSpot's `/oauth/2026-03/token`
84
+ endpoint and support the current `scopes[]`, `hub_id`, and `user_id` response
85
+ fields while retaining legacy scope-response compatibility.
86
+ - OAuth access tokens refresh automatically before expiry. Concurrent requests
87
+ share one refresh, rotated credentials are persisted atomically, and failed
88
+ refreshes leave the previous profile untouched.
89
+ - Client secrets remain in a named environment variable; only the variable
90
+ name is persisted. `auth whoami` reports identity/routing/expiry without
91
+ tokens, and `auth refresh` provides an explicit diagnostic rotation.
92
+
93
+ ### Changed — product contract
94
+
95
+ - Positioned hscli as a HubSpot operations and migration control plane with
96
+ evidence, rather than leading only with endpoint count.
97
+ - Replaced universal idempotency claims with the implemented model:
98
+ endpoint-aware retries reuse a correlation key, while action contracts and
99
+ read-only verification protect broader approved write workflows.
100
+ - Added package-contract tests that keep install instructions, output formats,
101
+ OAuth commands, nested documentation, generated MCP catalog, and product
102
+ claims aligned with the shipped binary.
103
+
104
+ ### Added — internal capture catalogue
105
+
106
+ - Added a versioned allowlist module for HubSpot browser-session internal
107
+ capture surfaces, with validation that every POST endpoint is explicitly
108
+ tagged as a read-like search, preview, batch-read, async resolve, or Chirp RPC
109
+ read.
110
+ - Added `hscli internal coverage`, a plan-only JSON control sheet that connects
111
+ the full internal endpoint docs, discovery log, allowlisted capture module,
112
+ surface counts, parameterized endpoints, and promotion policy for future
113
+ guarded write/replay adapters.
114
+ - Added the account/security internal capture surface for account metadata,
115
+ security settings, installed apps, and sync-bridge inventory.
116
+ - Expanded allowlisted internal capture coverage for report favorites/template
117
+ discovery, async report-result polling, workflow editor helpers
118
+ (permission checks, filter descriptions, card positions), and sales-content
119
+ detail reads for sequences, sales templates, and playbooks.
120
+ - Documented how `backup plan` orchestrates HubSpot 360 backups while
121
+ `internal capture` performs the allowlisted read-only browser-session pulls.
122
+
123
+ ### Fixed — internal-adapter safety + dependency audit
124
+
125
+ - Browser-session/internal adapters now share one hardened session resolver:
126
+ `--ui-domain` is allowlisted to HubSpot app hosts only
127
+ (`app.hubspot.com`, `app-eu1.hubspot.com`, etc.), and cookie exports are
128
+ filtered to the selected HubSpot domain before use.
129
+ - `reports pull email-recipients` always requests `email` internally when
130
+ enriching recipients via contact batch-read, but only emits the contact
131
+ properties explicitly requested by the operator.
132
+ - `seed` no longer revises or pushes live arbitrary existing CMS pages; CMS
133
+ revision/publish attempts are limited to pages created by the current seed
134
+ run.
135
+ - `scripts/test-portal-writes.mjs` now requires `HSCLI_ALLOW_WRITE_PROBE=1`
136
+ before running, skips existing-resource mutation by default, and keeps
137
+ publish endpoints behind a separate opt-in.
138
+ - Refreshed `package-lock.json` to clear current npm/Dependabot advisories for
139
+ `fast-uri`, `hono`, `ip-address`, and `express-rate-limit`.
140
+ - Tightened the tracked Python demo-capture dependencies to published current
141
+ lower bounds (`pillow>=11.3.0`, `playwright>=1.59.0`) so GitHub does not keep
142
+ alerting on broad/invalid vulnerable ranges.
143
+
144
+ ### Added — reusable report pulls
145
+
146
+ - Added `hscli reports pull object-fill-rate` for CRM object property fill-rate
147
+ audits, with optional all-property schema discovery.
148
+ - Added `hscli reports pull property-distribution` for one-property value
149
+ distributions and blank-rate checks.
150
+ - Added `hscli reports pull email-recipients` for per-recipient marketing email
151
+ event pulls across selected campaign IDs (`SENT`, `DELIVERED`, `BOUNCE`,
152
+ `OPEN`, `CLICK`, `DROPPED`, `SUPPRESSED`, `DEFERRED`) with optional contact
153
+ property enrichment.
154
+ - Added `hscli reports pull source-target-parity` to compare object counts
155
+ between source and sandbox profiles.
156
+
157
+ ### Fixed — communication preferences v4 paths
158
+
159
+ - Corrected `communication-preferences v4` endpoints to use HubSpot's
160
+ documented `/communication-preferences/v4/...` path. `subscriptions-list`
161
+ now reads `/communication-preferences/v4/definitions`.
162
+
163
+ ### Discovered — subscription definition migration
164
+
165
+ - Public subscription definition reads work via
166
+ `GET /communication-preferences/v3/definitions` and
167
+ `GET /communication-preferences/v4/definitions`, but public creates return
168
+ HTTP 405 on `/communication-preferences/v3/definitions` and
169
+ `/communication-preferences/v4/definitions`.
170
+ - Browser-session replay for Email > Subscription Types uses the internal
171
+ endpoint `/api/subscriptions/v1/definitions?portalId=<portalId>`.
172
+ Migration tooling must match definitions by
173
+ `name + purpose/process + communicationMethod/channel + businessUnitId`, not
174
+ by name alone, and remap business-unit IDs before write.
175
+ - `communication-preferences definitions create --skip-existing` now uses that
176
+ composite match instead of skipping by name alone.
177
+ - Added `communication-preferences definitions create-internal`, an explicit
178
+ browser-session migration adapter for Email > Subscription Types creation via
179
+ `/api/subscriptions/v1/definitions?portalId=<id>`.
180
+ - Added `doctor internal-adapters` to list supported/planned internal
181
+ setup/migration adapters and keep them separate from generic raw API access.
182
+
3
183
  ## 0.8.12 - 2026-04-24
4
184
 
5
185
  **Terminal operator home + `/fetch` alias.** Two incremental adds on
@@ -19,7 +199,23 @@ top of 0.8.11's operator surface.
19
199
  over read-shaped commands so agents can pick up HubSpot records
20
200
  with the same grammar they use to read vault state.
21
201
 
22
- One new test (308 total passing).
202
+ ### Added migration replay hardening
203
+
204
+ - Legacy forms/v2 consent replay now extracts `metaData[].legalConsentOptions`,
205
+ preserves `submitButtonText`, and requires target subscription remaps via
206
+ `--subscription-type-map` unless `--allow-unmapped-consent` is passed.
207
+ - `communication-preferences definitions list|create` can replay subscription
208
+ definitions with business-unit remapping (`--business-unit-map`,
209
+ `--business-unit-mode strict|drop|preserve`) and `--skip-existing`.
210
+ - `settings business-units capture` reads the internal
211
+ `/api/business-units/v1/business-units` browser-session endpoint and can emit
212
+ a source-ID keyed id-map seed for subscription-definition BU remapping.
213
+ - `workflows preflight` checks workflow exports against migration id-maps before
214
+ replay, including actions, enrollment criteria, suppression lists, custom
215
+ objects, association labels, users/owners, sequences, lists, subscriptions,
216
+ campaigns, marketing emails, and pipeline/stage IDs.
217
+
218
+ Migration replay regression tests added.
23
219
 
24
220
  ## 0.8.11 - 2026-04-24
25
221
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>Your HubSpot portal, in one binary.</strong>
6
+ <strong>The agent-safe HubSpot operator.</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -24,10 +24,21 @@
24
24
 
25
25
  ## Why
26
26
 
27
- 1,180 HubSpot endpoints, one TypeScript binary. Every write blocked unless you explicitly `--force` it. Every request auditable. Token stays on your machine. MCP-ready for Claude Desktop, Cursor, and any agent that speaks the protocol.
27
+ `hscli` is a HubSpot operations and migration control plane for humans and
28
+ agents. Every write is blocked unless you explicitly `--force` it. Every
29
+ request can produce local audit evidence. Tokens stay on your machine. The same
30
+ operation layer serves the CLI, deterministic runbooks, and scoped MCP tools.
28
31
 
29
32
  No SaaS middleman, no proprietary dashboard, no "download the CLI, then also install the desktop app". Just a CLI that respects production.
30
33
 
34
+ HubSpot is moving toward an agent-ready platform: APIs, MCP, CLI, and open
35
+ connectors so agents can run on HubSpot and run HubSpot. `hscli` is the
36
+ operator layer for that world: public API first, MCP-compatible, scheduled
37
+ runbooks, deterministic scoring, trace/audit evidence, migration adapters, and
38
+ explicit write guardrails. It is not a generic internal API tunnel; internal
39
+ browser-session surfaces stay allowlisted and task-specific for backup,
40
+ migration, and setup work.
41
+
31
42
  ## Install & auth
32
43
 
33
44
  ```bash
@@ -45,6 +56,59 @@ printf '%s' "$HUBSPOT_TOKEN" | hscli auth login --profile live --token-stdin --h
45
56
  hscli auth set-hublet live eu1
46
57
  ```
47
58
 
59
+ OAuth developer-app profiles use HubSpot's date-based token endpoint and
60
+ refresh automatically before expiry. Store the client secret in an environment
61
+ variable; hscli persists only the variable name:
62
+
63
+ ```bash
64
+ export HSCLI_OAUTH_CLIENT_SECRET="..."
65
+ hscli auth oauth-url --client-id "$CLIENT_ID" --redirect-uri "$REDIRECT_URI" \
66
+ --scopes "crm.objects.contacts.read"
67
+ hscli auth oauth-exchange --client-id "$CLIENT_ID" --code "$AUTH_CODE" \
68
+ --redirect-uri "$REDIRECT_URI" --profile oauth-prod
69
+ hscli --profile oauth-prod auth whoami
70
+ ```
71
+
72
+ ## First proof in one command
73
+
74
+ Run the read-only portal baseline before learning the full command tree. It
75
+ captures portal identity and API reachability, users and teams, deal pipelines,
76
+ data readiness for contacts/companies/deals, and workflow exposure:
77
+
78
+ ```bash
79
+ hscli --profile client-readonly agent run portal-baseline \
80
+ --output ./evidence/portal-baseline \
81
+ --continue-on-error
82
+ ```
83
+
84
+ The output is a portable evidence bundle, not an AI summary: `manifest.json`
85
+ records the exact steps and completion state, `requests.jsonl` records redacted
86
+ HubSpot request telemetry, `events.jsonl` records runbook progress, and
87
+ `steps/*.stdout.jsonl` preserves every deterministic result. Missing scopes are
88
+ kept as explicit failed steps while the rest of the baseline continues.
89
+
90
+ Turn that local bundle into a signed, aggregate-only RevFleet evidence
91
+ contract without uploading raw steps, CRM records, user identities, or tokens:
92
+
93
+ ```bash
94
+ hscli agent evidence export ./evidence/portal-baseline \
95
+ --output ./evidence/revfleet-evidence.json
96
+
97
+ export REVFLEET_API_KEY="<scan:run key from RevFleet>"
98
+ export REVFLEET_BACKEND_URL="https://your-revfleet-origin.example"
99
+ hscli agent evidence submit ./evidence/portal-baseline
100
+ ```
101
+
102
+ `--backend-url <origin>` overrides `REVFLEET_BACKEND_URL`. hscli has no hidden
103
+ hosted default: the destination must be explicit and remote origins must use
104
+ HTTPS.
105
+
106
+ The exported `revfleet.hscli-evidence/v1` document declares coverage for every
107
+ baseline signal and carries a deterministic SHA-256 digest. A missing or failed
108
+ signal remains `missing` or `failed`; it is never converted into a bad portal
109
+ score. RevFleet stores this evidence separately from the complete 53-check
110
+ extension scan.
111
+
48
112
  ## Operator workflows
49
113
 
50
114
  Start from intent instead of memorizing the whole command tree:
@@ -58,15 +122,161 @@ hscli /fetch # get records or metadata without mutation
58
122
  hscli /get # alias for /fetch
59
123
  hscli /write # target-portal write workflow
60
124
  hscli /guardrails # read-only, policy, trace, scope checks
125
+ hscli /backup # 360-degree portal backup workflow
126
+ ```
127
+
128
+ Agent-safe operator runbooks give Codex/Claude/Cursor deterministic plans
129
+ instead of open-ended portal access:
130
+
131
+ ```bash
132
+ hscli agent list
133
+ hscli agent plan portal-baseline --json
134
+ hscli agent evidence export ./evidence/portal-baseline --output ./evidence/revfleet-evidence.json
135
+ hscli agent plan automation-risk --json
136
+ hscli --profile sandbox agent run automation-risk --output ./evidence/automation-risk
137
+ hscli --profile sandbox agent run automation-risk --output ./evidence/automation-risk --resume
138
+ hscli --profile sandbox --policy-file ./policy.json --change-ticket CHG-123 \
139
+ agent plan data-cleanup --json
140
+ hscli --profile live agent schedule render pipeline-risk \
141
+ --target cron --cron "0 8 * * 1"
61
142
  ```
62
143
 
144
+ Runbooks are public-API-first, MCP-compatible, schedule-renderable, and
145
+ auditable. `agent plan` never calls HubSpot; it prints the exact read/report
146
+ commands. `agent run` executes those argument arrays with `shell:false` and
147
+ writes a manifest, checkpoint, per-step output, request telemetry, and JSONL
148
+ event ledger. `--resume` skips completed steps; `--continue-on-error` preserves
149
+ partial evidence while finishing the remaining diagnostics.
150
+
151
+ For approved fixes, seal the exact hscli argv in a versioned action contract.
152
+ Contracts are offline-verifiable and bind execution to one profile, portal,
153
+ expiry, and optional change ticket. They never contain tokens or shell text:
154
+
155
+ ```bash
156
+ hscli --json agent contract create --input ./action-draft.json | jq '.data' > ./action-contract.json
157
+ hscli --json --profile sandbox agent contract verify ./action-contract.json
158
+ hscli --json --profile sandbox agent contract apply ./action-contract.json
159
+ hscli --json --profile sandbox --force agent contract apply ./action-contract.json --execute
160
+ ```
161
+
162
+ `apply` remains plan-only unless both `--execute` and global `--force` are
163
+ present. Approved operations use argument arrays with `shell:false`, then run
164
+ the contract's read-only verification command.
165
+
63
166
  Scope checks are built in:
64
167
 
65
168
  ```bash
66
169
  hscli doctor scopes diff --required real-mirror-read
67
170
  hscli doctor scopes explain sales-email-read
171
+ hscli doctor parity
68
172
  ```
69
173
 
174
+ Multi-portal management stays local and plan-only by default. Use it when you
175
+ manage source/sandbox/live profiles, global + regional portals, or many client
176
+ tenants and want every command to keep an explicit profile boundary:
177
+
178
+ ```bash
179
+ hscli --json portals list
180
+ hscli --json portals plan \
181
+ --profiles live,sandbox \
182
+ --runbook automation-risk \
183
+ --include-context \
184
+ --sample-limit 25
185
+ ```
186
+
187
+ `portals plan` does not call HubSpot. It renders one `context build` and one
188
+ `agent plan` command per profile so Codex/Claude/scheduled workers can operate
189
+ without cross-tenant ambiguity.
190
+
191
+ Reusable reporting pulls are built in for audits, migration checks, and
192
+ matchback files:
193
+
194
+ ```bash
195
+ hscli reports pull object-fill-rate --object contacts --limit 5000
196
+ hscli reports pull property-distribution --object contacts --property lifecyclestage
197
+ hscli reports pull email-recipients \
198
+ --campaign-ids 407801550,407801857 \
199
+ --event-types sent,delivered,bounce,open,click,dropped,suppressed,deferred \
200
+ --contact-properties email,firstname,lastname,company,lifecyclestage,hs_all_assigned_business_unit_ids
201
+ hscli reports pull source-target-parity \
202
+ --source-profile live-readonly --target-profile sandbox-read --objects contacts,companies,deals
203
+ ```
204
+
205
+ Full-portal backup planning is available for downgrade, migration, and forensic
206
+ archive work. It is designed as a HubSpot 360 capture model: CRM, engagements,
207
+ marketing, sales, service, CMS, commerce, automation, reporting, settings,
208
+ integrations, files, and governance. It renders the public API capture runbook
209
+ plus the explicit internal/browser-session brief without making HubSpot calls.
210
+ The allowlisted internal endpoint catalogue lives in
211
+ [`docs/INTERNAL_ENDPOINTS.md`](docs/INTERNAL_ENDPOINTS.md), with the full
212
+ discovery archive in [`docs/INTERNAL-ENDPOINTS.md`](docs/INTERNAL-ENDPOINTS.md)
213
+ and [`docs/INTERNAL-ENDPOINTS-DISCOVERY.md`](docs/INTERNAL-ENDPOINTS-DISCOVERY.md):
214
+
215
+ ```bash
216
+ hscli --profile source-readonly backup plan \
217
+ --portal-id "$PORTAL_ID" \
218
+ --out-dir "$HOME/client-migration-assets/portal-$PORTAL_ID-full-backup" \
219
+ --include-internal \
220
+ --json
221
+ ```
222
+
223
+ When the operator has an authenticated HubSpot browser session, the catalogue
224
+ can be turned into a read-only allowlisted capture. Placeholder-heavy endpoints
225
+ are skipped into the manifest instead of guessed:
226
+
227
+ ```bash
228
+ hscli --json internal surfaces
229
+ hscli --profile source-readonly --json internal coverage \
230
+ > "$HOME/client-migration-assets/portal-$PORTAL_ID-full-backup/internal-coverage.json"
231
+ hscli --profile source-readonly --json internal capture \
232
+ --surface business-units \
233
+ --portal-id "$PORTAL_ID" \
234
+ --cookie-file "$HUBSPOT_COOKIE_FILE" \
235
+ --out-dir "$HOME/client-migration-assets/portal-$PORTAL_ID-full-backup/internal-api"
236
+ hscli --profile source-readonly --json internal capture \
237
+ --all \
238
+ --portal-id "$PORTAL_ID" \
239
+ --cookie-file "$HUBSPOT_COOKIE_FILE" \
240
+ --out-dir "$HOME/client-migration-assets/portal-$PORTAL_ID-full-backup/internal-api/allowlisted-capture"
241
+ ```
242
+
243
+ For internal read-like POST/RPC endpoints that need an exact payload, capture a
244
+ single allowlisted endpoint with a JSON body file. hscli stores a redacted
245
+ `<endpoint>.request.json` proof file and a request-body SHA-256 in the manifest:
246
+
247
+ ```bash
248
+ hscli --profile source-readonly --json internal capture \
249
+ --surface permission-access \
250
+ --endpoint app-users-batch \
251
+ --portal-id "$PORTAL_ID" \
252
+ --cookie-file "$HUBSPOT_COOKIE_FILE" \
253
+ --body-file app-users-batch=./bodies/app-users-batch.json \
254
+ --out-dir "$HOME/client-migration-assets/portal-$PORTAL_ID-full-backup/internal-api/permission-access"
255
+ ```
256
+
257
+ Migration/setup adapters stay explicit instead of becoming a generic internal
258
+ API passthrough. The capture allowlist is versioned in
259
+ `src/commands/internal/catalog.ts`; write-like internal adapters stay named,
260
+ auditable, and guarded by `--force`, while generic POST capture remains limited
261
+ to read-like HubSpot operations such as search, preview, batch hydration,
262
+ async resolve, or Chirp RPC reads:
263
+
264
+ ```bash
265
+ hscli settings business-units capture --portal-id 12345 --cookie-file ./session.cookie
266
+ hscli settings business-units create-internal --portal-id 12345 --data '{"name":"Example Business Unit"}' --skip-existing
267
+ hscli settings permission-sets roles list --portal-id 12345 --cookie-file ./session.cookie
268
+ hscli settings permission-sets assignments list --portal-id 12345 --cookie-file ./session.cookie
269
+ hscli settings flp property 0-1 email --portal-id 12345 --cookie-file ./session.cookie
270
+ hscli settings permission-sets users assign user-1 --portal-id 12345 --permission-set-id 70 --cookie-file ./session.cookie --force
271
+ hscli settings permission-sets users assign user-1 --portal-id 12345 --permission-set-ids 70,71 --cookie-file ./session.cookie --force
272
+ hscli settings permission-sets users assign-batch --portal-id 12345 --data '{"assignments":[{"userId":"user-1","permissionSetIds":["70","71"]}]}' --cookie-file ./session.cookie --force
273
+ hscli settings users create --data '{"email":"user@example.com"}' # invite email suppressed by default
274
+ ```
275
+
276
+ Browser-session adapters only send cookies to allowlisted HubSpot app hosts
277
+ (`app.hubspot.com`, `app-eu1.hubspot.com`, etc.). Netscape/JSON cookie exports
278
+ are filtered to the selected HubSpot domain before the request is made.
279
+
70
280
  ## Write safely
71
281
 
72
282
  ```bash
@@ -77,10 +287,15 @@ hscli crm contacts create --data '{"properties":{"email":"jane@acme.com"}}'
77
287
  # Preview what the call would look like
78
288
  hscli --dry-run crm contacts create --data '{"properties":{"email":"jane@acme.com"}}'
79
289
 
80
- # Execute — with an Idempotency-Key baked in automatically
290
+ # Execute — with an operation correlation key generated automatically
81
291
  hscli --force crm contacts create --data '{"properties":{"email":"jane@acme.com"}}'
82
292
  ```
83
293
 
294
+ hscli reuses the same correlation key across retries and honors native
295
+ idempotency semantics on endpoints that support them. It never assumes every
296
+ HubSpot mutation is replay-safe; deterministic action contracts and read-only
297
+ verification are the stronger boundary for approved agent writes.
298
+
84
299
  Policy file + change-ticket enforcement for ops teams:
85
300
 
86
301
  ```bash
@@ -93,7 +308,10 @@ hscli --force --policy-file ./policy.json --change-ticket CHG-123 \
93
308
  Drop `hscli` into any MCP client — Claude Desktop, Cursor, Claude Code, or your own agent runtime.
94
309
 
95
310
  ```bash
96
- hscli mcp
311
+ hscli mcp # 29 essential read tools (default)
312
+ hscli mcp --surface crm # curated CRM read/write tools
313
+ hscli mcp --surface migration # curated cross-product migration tools
314
+ hscli mcp --surface full # all 180 tools, including raw API
97
315
  ```
98
316
 
99
317
  For Claude Desktop, add to `claude_desktop_config.json`:
@@ -103,19 +321,23 @@ For Claude Desktop, add to `claude_desktop_config.json`:
103
321
  "mcpServers": {
104
322
  "hscli": {
105
323
  "command": "hscli",
106
- "args": ["mcp"],
324
+ "args": ["mcp", "--surface", "minimal"],
107
325
  "env": { "HSCLI_MCP_PROFILE": "default" }
108
326
  }
109
327
  }
110
328
  }
111
329
  ```
112
330
 
113
- The MCP server exposes three tool families:
331
+ The MCP server exposes three tool families across explicit surfaces:
114
332
 
115
333
  - **Per-object tools** — `crm_contacts_list`, `crm_deals_create`, `crm_custom_records_search`, …
116
334
  - **HubSpot Remote MCP compat** — same tool names as `mcp.hubspot.com` (`search_crm_objects`, `manage_crm_objects`, …) so agents built for HubSpot's hosted MCP drop in without changes
117
335
  - **Extension surface** — workflows, files, forms, webhooks, HubDB, conversations, and more, all with the same `--force` + policy gates as the CLI
118
336
 
337
+ `minimal` is intentionally the default so an agent does not pay the context
338
+ and security cost of 180 tools for a simple CRM read. The raw `hub_api_request`
339
+ tool exists only in `full`. See the generated [tool catalog](docs/MCP_TOOL_CATALOG.md).
340
+
119
341
  Every MCP tool call goes through the same safety rails as the CLI. Token redaction is enforced on every response. `HSCLI_MCP_PROFILE` locks the stdio server to one auth profile to prevent cross-tenant access.
120
342
 
121
343
  ## Observability
@@ -150,7 +372,9 @@ Every request hscli makes is append-only JSONL. Pair `trace` with `audit` for fu
150
372
  - **Legacy v1/v2** — every pre-v3 surface preserved for portals that still need them
151
373
  - **Raw API** command with path-scope controls + full OAuth flow support
152
374
 
153
- Enterprise defaults: `--dry-run`, `--force`, policy files, change tickets, capability probing, shared rate-limit enforcement, token redaction, path scope allowlisting, idempotency keys on every write.
375
+ Enterprise defaults: `--dry-run`, `--force`, policy files, change tickets,
376
+ capability probing, shared rate-limit enforcement, token redaction, path scope
377
+ allowlisting, endpoint-aware retries, and correlation keys on writes.
154
378
 
155
379
  </details>
156
380
 
@@ -180,19 +404,27 @@ Exact endpoint → tier mapping in [docs/TIERS.md](docs/TIERS.md).
180
404
  ## Design principles
181
405
 
182
406
  1. **CLI-first, MCP as a peer.** Every MCP tool has a matching `hscli` command — same write gates, same redaction, same capability probing in both.
183
- 2. **Self-hosted, token-sovereign.** Your HubSpot private app token stays on your machine. No telemetry, no phone-home.
184
- 3. **Safe by default.** Mutations are blocked unless `--force` is explicit. `--dry-run` previews every write. Idempotency-Key on every mutation.
185
- 4. **HubSpot-native.** Reads `X-HubSpot-RateLimit-*` headers, throttles proactively across clients, stops on daily hard-limit exhaustion, caches capabilities by `portalId + scopes`, validates payloads offline.
407
+ 2. **Agent-safe operator layer.** Deterministic context, plans, reports, traces, and policy checks happen before AI writes or summaries.
408
+ 3. **Self-hosted, token-sovereign.** Your HubSpot private app token stays on your machine. No telemetry, no phone-home.
409
+ 4. **Safe by default.** Mutations are blocked unless `--force` is explicit. `--dry-run` previews every write. Approved actions bind exact arguments to verification steps; endpoint-aware retries reuse one correlation key.
410
+ 5. **Multi-portal explicitness.** Every plan and command keeps `--profile` visible so source, sandbox, and production portals cannot blur together.
411
+ 6. **HubSpot-native.** Reads `X-HubSpot-RateLimit-*` headers, throttles proactively across clients, stops on daily hard-limit exhaustion, caches capabilities by `portalId + scopes`, validates payloads offline.
186
412
 
187
413
  ## Output modes
188
414
 
189
415
  ```bash
190
416
  hscli --json crm companies list --limit 3 # JSON envelope
417
+ hscli --format jsonl crm contacts list --limit 100 # one compact envelope per result
191
418
  hscli --format csv crm contacts list --limit 5 # CSV
192
419
  hscli --format yaml crm deals get 123 # YAML
193
420
  hscli --format table crm contacts list --limit 5 # table (default)
194
421
  ```
195
422
 
423
+ `jsonl` is the streaming format for agents and shell pipelines. Top-level
424
+ arrays and HubSpot `results` pages produce one redacted JSON envelope per
425
+ record; page metadata is repeated under `meta` so each line remains
426
+ self-describing.
427
+
196
428
  ## Terminal session
197
429
 
198
430
  The HubSpot-UI recording at the top shows the *outcome*. Here's the *cause* — the same fixture built and torn down entirely from the shell:
@@ -220,6 +452,7 @@ Under `HSCLI_HOME` (default: `~/.revfleet`):
220
452
  - [docs/MCP.md](docs/MCP.md) — MCP server tool catalog
221
453
  - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) · [docs/SAFETY_MODEL.md](docs/SAFETY_MODEL.md) · [docs/WHY_HOW_WHAT.md](docs/WHY_HOW_WHAT.md)
222
454
  - [docs/ROADMAP-DATE-BASED-API.md](docs/ROADMAP-DATE-BASED-API.md) — HubSpot's `/YYYY-MM/` migration plan
455
+ - [docs/PORTAL_BACKUP.md](docs/PORTAL_BACKUP.md) — full-portal downgrade/migration backup runbook
223
456
  - [docs/PLUGIN_GUIDE.md](docs/PLUGIN_GUIDE.md) — writing plugins
224
457
  - [docs/COOKBOOK.md](docs/COOKBOOK.md) — recipes
225
458
  - [docs/PUBLISHING.md](docs/PUBLISHING.md) — release runbook (maintainers)
package/dist/cli.js CHANGED
@@ -20,6 +20,7 @@ import { registerAudit } from "./commands/audit/index.js";
20
20
  import { registerLists } from "./commands/lists/index.js";
21
21
  import { registerSales } from "./commands/sales/index.js";
22
22
  import { registerReporting } from "./commands/reporting/index.js";
23
+ import { registerReports } from "./commands/reports/index.js";
23
24
  import { registerExports } from "./commands/exports/index.js";
24
25
  import { registerSiteSearch } from "./commands/site-search/index.js";
25
26
  import { registerTimeline } from "./commands/timeline/index.js";
@@ -33,11 +34,22 @@ import { registerEmailEvents } from "./commands/email-events/index.js";
33
34
  import { registerMediaBridge } from "./commands/media-bridge/index.js";
34
35
  import { registerFeatureFlags } from "./commands/feature-flags/index.js";
35
36
  import { registerGuide } from "./commands/guide/index.js";
37
+ import { registerAgent } from "./commands/agent/index.js";
38
+ import { registerContext } from "./commands/context/index.js";
39
+ import { registerPortals } from "./commands/portals/index.js";
40
+ import { registerBackup } from "./commands/backup/index.js";
41
+ import { registerSalesContent } from "./commands/sales-content/index.js";
42
+ import { registerSequencesInternal } from "./commands/sequences/index.js";
43
+ import { registerSalesTemplates } from "./commands/sales-templates/index.js";
44
+ import { registerPlaybooks } from "./commands/playbooks/index.js";
45
+ import { registerContentPermissions } from "./commands/content-permissions/index.js";
36
46
  import { registerUi } from "./commands/ui/index.js";
37
47
  import { registerMarketingEmailsV1 } from "./commands/marketing-emails-v1/index.js";
48
+ import { registerEmailTracking } from "./commands/email-tracking/index.js";
38
49
  import { registerIntegrators } from "./commands/integrators/index.js";
39
50
  import { registerIntegrations } from "./commands/integrations/index.js";
40
51
  import { registerExtensions } from "./commands/extensions/index.js";
52
+ import { registerInternal } from "./commands/internal/index.js";
41
53
  import { registerBroadcast, registerVisitorIdentification, registerSubmissions, registerScheduler, registerTax, registerAppinstalls, registerMarketingExtras, registerOwnersExtras, registerBusinessUnits, registerBroadcastsRoot, registerFormIntegrations, } from "./commands/legacy/index.js";
42
54
  import { registerContactsV1, registerCompaniesV2, registerDealsV1, registerOwnersV2, registerEngagementsV1, registerPropertiesLegacy, registerReportsV2, registerPaymentsSubscriptions, registerContentV2, registerSalesExtensions, registerComments, registerCallingV1, registerChannels, } from "./commands/legacy-v1/index.js";
43
55
  import { CliError, printError } from "./core/output.js";
@@ -60,7 +72,7 @@ export function createProgram() {
60
72
  .version(pkg.version)
61
73
  .option("--profile <name>", "Auth profile", "default")
62
74
  .option("--json", "JSON output")
63
- .option("--format <mode>", "Output format: json|table|csv|yaml")
75
+ .option("--format <mode>", "Output format: json|jsonl|table|csv|yaml")
64
76
  .option("--dry-run", "Simulate writes")
65
77
  .option("--force", "Confirm write operations")
66
78
  .option("--strict-capabilities", "Fail fast when endpoint capability status is unknown")
@@ -126,6 +138,7 @@ export function createProgram() {
126
138
  registerLists(program, () => ctx);
127
139
  registerSales(program, () => ctx);
128
140
  registerReporting(program, () => ctx);
141
+ registerReports(program, () => ctx);
129
142
  registerExports(program, () => ctx);
130
143
  registerSiteSearch(program, () => ctx);
131
144
  registerTimeline(program, () => ctx);
@@ -140,10 +153,21 @@ export function createProgram() {
140
153
  registerFeatureFlags(program, () => ctx);
141
154
  registerUi(program, () => ctx);
142
155
  registerGuide(program, () => ctx);
156
+ registerAgent(program, () => ctx);
157
+ registerContext(program, () => ctx);
158
+ registerPortals(program, () => ctx);
159
+ registerBackup(program, () => ctx);
160
+ registerSalesContent(program, () => ctx);
161
+ registerSequencesInternal(program, () => ctx);
162
+ registerSalesTemplates(program, () => ctx);
163
+ registerPlaybooks(program, () => ctx);
164
+ registerContentPermissions(program, () => ctx);
143
165
  registerMarketingEmailsV1(program, () => ctx);
166
+ registerEmailTracking(program, () => ctx);
144
167
  registerIntegrators(program, () => ctx);
145
168
  registerIntegrations(program, () => ctx);
146
169
  registerExtensions(program, () => ctx);
170
+ registerInternal(program, () => ctx);
147
171
  registerBroadcast(program, () => ctx);
148
172
  registerVisitorIdentification(program, () => ctx);
149
173
  registerSubmissions(program, () => ctx);
@@ -243,9 +267,9 @@ function resolveApiBaseUrlOverride(hublet, rawApiBaseUrl) {
243
267
  function resolveOutputFormat(raw, jsonFlag) {
244
268
  const fallback = "table";
245
269
  const value = (raw === undefined || raw === null) ? fallback : String(raw).trim().toLowerCase();
246
- const allowed = new Set(["json", "table", "csv", "yaml"]);
270
+ const allowed = new Set(["json", "jsonl", "table", "csv", "yaml"]);
247
271
  if (!allowed.has(value)) {
248
- throw new CliError("INVALID_FORMAT", "format must be one of: json, table, csv, yaml");
272
+ throw new CliError("INVALID_FORMAT", "format must be one of: json, jsonl, table, csv, yaml");
249
273
  }
250
274
  if (jsonFlag && value !== "json" && raw !== undefined && raw !== null) {
251
275
  throw new CliError("INVALID_FORMAT", "--json cannot be combined with --format != json");