fullstackgtm 0.44.0 → 0.46.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.
- package/CHANGELOG.md +293 -1
- package/INSTALL_FOR_AGENTS.md +13 -11
- package/README.md +45 -24
- package/dist/audit.d.ts +3 -1
- package/dist/audit.js +29 -2
- package/dist/backfill.d.ts +86 -0
- package/dist/backfill.js +251 -0
- package/dist/bin.js +4 -1
- package/dist/cli/audit.d.ts +15 -0
- package/dist/cli/audit.js +392 -0
- package/dist/cli/auth.d.ts +82 -0
- package/dist/cli/auth.js +607 -0
- package/dist/cli/backfill.d.ts +1 -0
- package/dist/cli/backfill.js +125 -0
- package/dist/cli/backfillRuns.d.ts +1 -0
- package/dist/cli/backfillRuns.js +187 -0
- package/dist/cli/call.d.ts +1 -0
- package/dist/cli/call.js +387 -0
- package/dist/cli/capabilities.d.ts +30 -0
- package/dist/cli/capabilities.js +197 -0
- package/dist/cli/draft.d.ts +7 -0
- package/dist/cli/draft.js +87 -0
- package/dist/cli/enrich.d.ts +8 -0
- package/dist/cli/enrich.js +806 -0
- package/dist/cli/fix.d.ts +33 -0
- package/dist/cli/fix.js +346 -0
- package/dist/cli/help.d.ts +25 -0
- package/dist/cli/help.js +646 -0
- package/dist/cli/icp.d.ts +7 -0
- package/dist/cli/icp.js +229 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.js +58 -0
- package/dist/cli/market.d.ts +1 -0
- package/dist/cli/market.js +391 -0
- package/dist/cli/plans.d.ts +5 -0
- package/dist/cli/plans.js +456 -0
- package/dist/cli/schedule.d.ts +8 -0
- package/dist/cli/schedule.js +479 -0
- package/dist/cli/shared.d.ts +71 -0
- package/dist/cli/shared.js +342 -0
- package/dist/cli/signals.d.ts +7 -0
- package/dist/cli/signals.js +412 -0
- package/dist/cli/suggest.d.ts +49 -0
- package/dist/cli/suggest.js +135 -0
- package/dist/cli/tam.d.ts +9 -0
- package/dist/cli/tam.js +387 -0
- package/dist/cli/ui.d.ts +142 -0
- package/dist/cli/ui.js +427 -0
- package/dist/cli.d.ts +1 -57
- package/dist/cli.js +123 -5157
- package/dist/connector.d.ts +23 -0
- package/dist/connector.js +47 -0
- package/dist/connectors/hubspot.d.ts +10 -1
- package/dist/connectors/hubspot.js +244 -10
- package/dist/connectors/hubspotAuth.js +5 -1
- package/dist/connectors/linkedin.js +14 -14
- package/dist/connectors/salesforce.d.ts +10 -1
- package/dist/connectors/salesforce.js +39 -6
- package/dist/connectors/signalSources.js +7 -59
- package/dist/connectors/stripe.d.ts +37 -0
- package/dist/connectors/stripe.js +103 -31
- package/dist/enrich.d.ts +7 -0
- package/dist/enrich.js +7 -0
- package/dist/health.d.ts +11 -69
- package/dist/health.js +4 -134
- package/dist/healthScore.d.ts +71 -0
- package/dist/healthScore.js +143 -0
- package/dist/icp.d.ts +15 -11
- package/dist/icp.js +19 -36
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/judge.d.ts +2 -0
- package/dist/judge.js +6 -0
- package/dist/llm.d.ts +29 -0
- package/dist/llm.js +206 -0
- package/dist/market.d.ts +39 -1
- package/dist/market.js +116 -44
- package/dist/marketClassify.d.ts +11 -1
- package/dist/marketClassify.js +17 -2
- package/dist/marketTaxonomy.d.ts +6 -1
- package/dist/marketTaxonomy.js +4 -2
- package/dist/mcp-bin.js +31 -2
- package/dist/mcp.js +372 -166
- package/dist/progress.d.ts +96 -0
- package/dist/progress.js +142 -0
- package/dist/runReport.d.ts +24 -0
- package/dist/runReport.js +139 -4
- package/dist/schedule.d.ts +80 -2
- package/dist/schedule.js +254 -1
- package/dist/spoolFiles.d.ts +44 -0
- package/dist/spoolFiles.js +114 -0
- package/dist/types.d.ts +29 -1
- package/docs/api.md +75 -8
- package/docs/architecture.md +2 -0
- package/docs/linkedin-connector-spec.md +1 -1
- package/docs/signal-spool-format.md +13 -0
- package/llms.txt +18 -9
- package/package.json +10 -3
- package/skills/fullstackgtm/SKILL.md +2 -1
- package/src/audit.ts +27 -1
- package/src/backfill.ts +340 -0
- package/src/bin.ts +5 -1
- package/src/cli/audit.ts +447 -0
- package/src/cli/auth.ts +669 -0
- package/src/cli/backfill.ts +156 -0
- package/src/cli/backfillRuns.ts +198 -0
- package/src/cli/call.ts +414 -0
- package/src/cli/capabilities.ts +215 -0
- package/src/cli/draft.ts +101 -0
- package/src/cli/enrich.ts +908 -0
- package/src/cli/fix.ts +373 -0
- package/src/cli/help.ts +702 -0
- package/src/cli/icp.ts +265 -0
- package/src/cli/init.ts +65 -0
- package/src/cli/market.ts +423 -0
- package/src/cli/plans.ts +524 -0
- package/src/cli/schedule.ts +526 -0
- package/src/cli/shared.ts +392 -0
- package/src/cli/signals.ts +434 -0
- package/src/cli/suggest.ts +151 -0
- package/src/cli/tam.ts +435 -0
- package/src/cli/ui.ts +497 -0
- package/src/cli.ts +122 -5855
- package/src/connector.ts +66 -0
- package/src/connectors/hubspot.ts +273 -9
- package/src/connectors/hubspotAuth.ts +5 -1
- package/src/connectors/linkedin.ts +14 -14
- package/src/connectors/salesforce.ts +51 -3
- package/src/connectors/signalSources.ts +7 -56
- package/src/connectors/stripe.ts +154 -34
- package/src/enrich.ts +13 -0
- package/src/health.ts +14 -213
- package/src/healthScore.ts +223 -0
- package/src/icp.ts +19 -35
- package/src/index.ts +28 -1
- package/src/judge.ts +7 -0
- package/src/llm.ts +239 -0
- package/src/market.ts +157 -44
- package/src/marketClassify.ts +26 -2
- package/src/marketTaxonomy.ts +10 -2
- package/src/mcp-bin.ts +34 -2
- package/src/mcp.ts +270 -63
- package/src/progress.ts +197 -0
- package/src/runReport.ts +159 -4
- package/src/schedule.ts +310 -3
- package/src/spoolFiles.ts +116 -0
- package/src/types.ts +32 -2
- package/docs/dx-punch-list.md +0 -87
- package/docs/roadmap-to-1.0.md +0 -211
package/CHANGELOG.md
CHANGED
|
@@ -3,10 +3,302 @@
|
|
|
3
3
|
All notable changes to the `fullstackgtm` package are documented here.
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and the project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
-
The path to 1.0 is planned in
|
|
6
|
+
The path to 1.0 is planned in the roadmap doc in the repository.
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.46.0] — 2026-07-06
|
|
11
|
+
|
|
12
|
+
### Fixed — launch hardening
|
|
13
|
+
|
|
14
|
+
- **`fullstackgtm-mcp --help`/`--version` now work without optional peers.**
|
|
15
|
+
The MCP entrypoint parsed argv before importing `@modelcontextprotocol/sdk`
|
|
16
|
+
and `zod`, so on a clean install `fullstackgtm-mcp --help` exited 1. It now
|
|
17
|
+
prints usage (and the `npx -y -p fullstackgtm -p @modelcontextprotocol/sdk
|
|
18
|
+
-p zod fullstackgtm-mcp` invocation) and exits 0.
|
|
19
|
+
- **Packaging hygiene.** Narrowed `files` so internal planning docs
|
|
20
|
+
(`dx-punch-list`, `perf-notes`, `roadmap-to-1.0`) no longer ship in the npm
|
|
21
|
+
tarball; strengthened `prepublishOnly` to also run `npm pack --dry-run` and
|
|
22
|
+
smoke both bin `--help` paths.
|
|
23
|
+
- **Doc accuracy.** README Stripe restricted-key guidance now includes
|
|
24
|
+
Invoices:Read (required by `backfill stripe`); schedule allowlist lists
|
|
25
|
+
`enrich acquire --save`; benchmark headline points to the leaderboard
|
|
26
|
+
caveats; `llms.txt` clarifies Stripe is read-only and fixes broken public
|
|
27
|
+
mirror links; CHANGELOG clarifies unmatched Stripe customers get a proposed,
|
|
28
|
+
approval-gated account-create (not silent auto-create).
|
|
29
|
+
- **Public-surface scrub.** Replaced realistic fake LinkedIn prospect fixtures
|
|
30
|
+
with clearly-synthetic example identities and removed private-context
|
|
31
|
+
wording from shipped source/docs.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Hosted first-party CRM OAuth for the CLI.** `fullstackgtm login hubspot` and
|
|
36
|
+
`fullstackgtm login salesforce` now default to hosted browser OAuth (explicit
|
|
37
|
+
`--hosted`, `--via`/`FULLSTACKGTM_HOSTED_URL` override), storing the same local
|
|
38
|
+
broker credential used by team pairing while provider tokens are minted
|
|
39
|
+
server-side. BYO app/token paths remain as advanced fallbacks, secrets still
|
|
40
|
+
never travel via argv, and first-party app secrets never ship in the npm
|
|
41
|
+
package. Non-TTY missing-BYO-flag errors print a deterministic hosted-login
|
|
42
|
+
next command.
|
|
43
|
+
|
|
44
|
+
- **Chunked LLM insight extraction (`extractInsightsChunked`) — the TamTone
|
|
45
|
+
"langextract" method.** Transcripts are never sent whole: they are chopped
|
|
46
|
+
into ~1,500-char speaker-turn-respecting chunks (the tested quality lever —
|
|
47
|
+
small chunks yielded ~95% more grounded signals than large ones) and each
|
|
48
|
+
chunk gets its own focused few-shot extraction call. Per-chunk results pass
|
|
49
|
+
the SAME verbatim-evidence and next-step-grounding gates as before (checked
|
|
50
|
+
against the chunk they came from — a quote from another chunk is treated as
|
|
51
|
+
a hallucination), then a quality gate
|
|
52
|
+
(0.25·length + 0.40·specificity + 0.35·confidence, reject < 0.15) filters
|
|
53
|
+
filler before per-call dedupe (richer statement wins) and ranking.
|
|
54
|
+
`call parse --llm` now uses this pipeline; single-shot remains for
|
|
55
|
+
one-chunk transcripts. Bounded: ≤80 chunks/call, 4 concurrent, a failed
|
|
56
|
+
chunk is skipped (all-failed throws).
|
|
57
|
+
|
|
58
|
+
- **`backfill runs` — replay local execution history to the paired hosted
|
|
59
|
+
app.** An engagement that started CLI-only gets its full trail on pairing:
|
|
60
|
+
every stored plan's apply runs (with per-op result counts and ORIGINAL
|
|
61
|
+
timestamps) and the `health.jsonl` hygiene timeline (seeding the hosted
|
|
62
|
+
health trend). Idempotent by construction — deterministic per-run
|
|
63
|
+
`clientRunId`s and server-side timestamp dedupe make re-running safe.
|
|
64
|
+
`--dry-run` counts without sending. Privacy unchanged: only what live
|
|
65
|
+
reporting sends leaves the machine (statuses, counts, timestamps,
|
|
66
|
+
scores — never CRM field values). Live run reports also carry a stable
|
|
67
|
+
`clientRunId` now, so retried reports never duplicate.
|
|
68
|
+
|
|
69
|
+
- **`backfill stripe` — paid Stripe invoices become closed-won CRM deals,
|
|
70
|
+
through the normal governed flow.** For a business whose revenue source of
|
|
71
|
+
truth is Stripe, the CRM can now be retroactively made honest: one
|
|
72
|
+
closed-won deal per paid invoice (amount = invoice total, close date = paid
|
|
73
|
+
date, associated to the customer's company), matched to CRM accounts by
|
|
74
|
+
billing-email domain first, then exact name. Unmatched customers are either
|
|
75
|
+
report-only (when they lack a usable name/domain, or with `--skip-unmatched`)
|
|
76
|
+
or an explicit proposed ACCOUNT create in the same `needs_approval` plan. The
|
|
77
|
+
command only ever proposes: `--save` persists the plan and writes happen
|
|
78
|
+
through `plans approve` → `apply`, where the connector re-resolves each
|
|
79
|
+
invoice id and creates only on a confirmed miss — re-running never
|
|
80
|
+
double-creates. Proposed account creates are one op per customer, use the
|
|
81
|
+
billing-email domain unless freemail, and stay approval-gated like every
|
|
82
|
+
other write.
|
|
83
|
+
- **HubSpot deal creation in `create_record`.** `CreateRecordPayload` gains
|
|
84
|
+
`dealStage: "closed_won"` (a provider-neutral sentinel resolved to the
|
|
85
|
+
target pipeline's real closed-won stage id from pipeline metadata — never
|
|
86
|
+
substring guessing) and `dealPipeline` (id or label; default pipeline when
|
|
87
|
+
absent). The dedupe key is a custom deal property (default
|
|
88
|
+
`stripe_invoice_id`), ensured on demand; if it can't be ensured the create
|
|
89
|
+
is skipped rather than performed without its dedupe key. Salesforce deal
|
|
90
|
+
creation stays explicitly skipped for now.
|
|
91
|
+
- **`fetchStripePaidInvoices`** on the Stripe connector: paginated
|
|
92
|
+
`status=paid` invoice reads with `created[gte]` incremental support,
|
|
93
|
+
cents→major conversion, and paid-date extraction.
|
|
94
|
+
- **Node-free health scoring (`src/healthScore.ts`).** `computeHealth` /
|
|
95
|
+
`summarizeHealth` / `healthToMarkdown` split out of `health.ts` (which keeps
|
|
96
|
+
the fs-backed profile timeline and re-exports everything) so V8 runtimes —
|
|
97
|
+
the hosted app's Convex functions — can score CRM hygiene with the exact
|
|
98
|
+
same deterministic curve the CLI uses.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- **CLI startup is ~20–30% faster (p50), with zero output changes.** The
|
|
103
|
+
dispatcher now loads verb modules lazily instead of compiling the full
|
|
104
|
+
78-module graph on every invocation, `node:http` (which transitively
|
|
105
|
+
compiles node's bundled undici on Node 22) is deferred to the OAuth
|
|
106
|
+
loopback login that actually starts a server, and the CRM connector /
|
|
107
|
+
demo-data modules load only when that data source is selected. Measured
|
|
108
|
+
p50 over interleaved A/B runs: `--version` −31%, `capabilities --json`
|
|
109
|
+
−30%, `audit --demo --json` −23%. Every output is byte-identical to the
|
|
110
|
+
previous build (verified against 57 golden outputs across help, audit,
|
|
111
|
+
doctor, rules, dedupe, resolve, snapshot, and error paths) and the public
|
|
112
|
+
import surface of `cli.ts` is unchanged. Profiling method, opportunity
|
|
113
|
+
matrix, and honest non-wins are documented in
|
|
114
|
+
[docs/perf-notes.md](./docs/perf-notes.md).
|
|
115
|
+
|
|
116
|
+
## [0.45.0] — 2026-07-02
|
|
117
|
+
|
|
118
|
+
### Added (TUI glamour — interactive terminals only)
|
|
119
|
+
|
|
120
|
+
- **A terminal presentation layer (`src/cli/ui.ts`, zero new dependencies) —
|
|
121
|
+
styling and animation exist ONLY on an interactive TTY.** Piped, redirected,
|
|
122
|
+
`--json`, CI, and `NO_COLOR` output is byte-identical to the pre-TUI CLI
|
|
123
|
+
(verified against the previous build for 14 verbs: stdout, stderr, exit
|
|
124
|
+
codes); a new `agent-cli.yml` step and `tests/fullstackgtmAnsiSafety.test.ts`
|
|
125
|
+
enforce zero ANSI bytes in piped output permanently. `FORCE_COLOR=1` opts
|
|
126
|
+
piped output back into color per spec. On a TTY:
|
|
127
|
+
- `doctor` colors each check (green ok / red MISSING), dims what's not
|
|
128
|
+
connected, bands the health score, appends a score-history sparkline, and
|
|
129
|
+
frames "Next step" in a box. `--help` gets a dimmed brand header
|
|
130
|
+
(version · active profile), bold command names, cyan lifecycle groups.
|
|
131
|
+
- `health` renders a styled rollup: banded score, ▲/▼ delta, score
|
|
132
|
+
sparkline, aligned object-type and per-rule tables (positive finding
|
|
133
|
+
deltas red — more findings is worse — negative green).
|
|
134
|
+
- Live provider pulls show a stderr spinner with a running tally ("Pulling
|
|
135
|
+
contacts from hubspot… 2,400 fetched · 12s"): the HubSpot/Salesforce
|
|
136
|
+
connectors accept an optional per-page `onProgress` (presentation-only,
|
|
137
|
+
errors swallowed). `audit` renders its rule registry as a live checklist
|
|
138
|
+
(○ → spinner → ✓ with finding counts) via a new optional `onRule` on
|
|
139
|
+
`auditSnapshot`; `signals fetch` shows a per-domain ATS tally.
|
|
140
|
+
- `apply` and `fix --yes` tick per-operation progress ("Applying 41/120 ·
|
|
141
|
+
✓ 40 applied · 0 failed · 1 conflict") via a new optional `onOperation` on
|
|
142
|
+
`applyPatchPlan`, closing with a colored outcome badge. `enrich
|
|
143
|
+
append/refresh` gets a progress bar with rate + ETA over the Apollo pull;
|
|
144
|
+
`enrich acquire` adds a budget fuel gauge (records + spend vs day/month
|
|
145
|
+
caps, color-banded by burn).
|
|
146
|
+
- LLM waits show an elapsed status line (`call parse` / `call score`);
|
|
147
|
+
`icp judge` and `market classify` report per-account / per-vendor progress
|
|
148
|
+
via new optional `onAccount` / `onVendor` callbacks.
|
|
149
|
+
- Patch plans render with severity banding and value diffs — current value
|
|
150
|
+
red, proposed value green, `requires_human_*` placeholders yellow — in
|
|
151
|
+
`audit` and `plans show`; `plans list` becomes an aligned status-banded
|
|
152
|
+
table (summaries truncated with `…` to the terminal width so rows never
|
|
153
|
+
wrap); `fix` frames its stop-before-apply command in a box.
|
|
154
|
+
|
|
155
|
+
### Added (agent ergonomics pass 1)
|
|
156
|
+
|
|
157
|
+
- **Flag typos are caught instead of silently ignored.** Any `--flag` that is
|
|
158
|
+
documented nowhere in the help reference but sits within one edit of a
|
|
159
|
+
documented flag (after lowercasing and `_`→`-` normalization) now stops with
|
|
160
|
+
exit 1, a `Did you mean: --json` hint, and the exact corrected command;
|
|
161
|
+
`--json` callers get a structured `UNKNOWN_FLAG` envelope. Previously
|
|
162
|
+
`audit --demo --jsn` exited 0 and printed markdown where the caller asked
|
|
163
|
+
for JSON, and `reassign … --sav` exited 0 without saving. Suggest-only —
|
|
164
|
+
the correction is never auto-executed, so a typo can never change what a
|
|
165
|
+
write-shaped invocation stages. The known-flag registry is derived at
|
|
166
|
+
runtime from the help table (no parallel list); unknown flags with no
|
|
167
|
+
near-miss behave exactly as before.
|
|
168
|
+
- **Unknown commands get a did-you-mean on the plain-text path too.**
|
|
169
|
+
`fullstackgtm plan` now answers `Did you mean: fullstackgtm plans` plus one
|
|
170
|
+
pointer line each to `--help` and `capabilities --json`, instead of the
|
|
171
|
+
full usage-reference dump. (The `--json` envelope already had the hint.)
|
|
172
|
+
- **`doctor` is now the one-call triage surface.** `doctor --json` adds a
|
|
173
|
+
`workspace` slice — `healthScore`, `scoreDelta`, `lastAuditAt`,
|
|
174
|
+
`auditCount`, and `pendingPlans` (id, summary, operation/approved counts) —
|
|
175
|
+
and when a plan is awaiting approval `nextSteps` becomes the copy-pasteable
|
|
176
|
+
`plans show` → `plans approve` → `apply` chain with the connected provider
|
|
177
|
+
filled in. Text `doctor` gains the matching `Workspace:` section.
|
|
178
|
+
Previously orienting an agent took three calls (doctor + health +
|
|
179
|
+
plans list).
|
|
180
|
+
- **`audit` output is byte-deterministic under `SOURCE_DATE_EPOCH`.** The
|
|
181
|
+
plan's `createdAt` honors the reproducible-build convention (seconds since
|
|
182
|
+
the epoch) so golden tests and CI diffs can pin `audit --demo --json`
|
|
183
|
+
byte-for-byte. Unset, it remains the real clock.
|
|
184
|
+
- **GNU-style `--flag=value` is corrected too.** `audit --rules=x` was
|
|
185
|
+
silently dropped (the audit ran with all rules); an `=`-form whose base
|
|
186
|
+
resolves to a documented flag now stops with the space-separated form this
|
|
187
|
+
CLI parses (`Try: … --rules x`).
|
|
188
|
+
- **A flag-shaped first token is diagnosed as flag-before-command.**
|
|
189
|
+
`fullstackgtm --jsn` was "Unknown command: --jsn"; now it explains that a
|
|
190
|
+
command must come first and resolves the flag when it near-misses a
|
|
191
|
+
documented one.
|
|
192
|
+
- **Subverb typos get a nearest-match did-you-mean on all 7 multi-verb
|
|
193
|
+
commands** (`enrich`, `market`, `icp`, `signals`, `plans`, `schedule`,
|
|
194
|
+
`tam`): `enrich apend` → `Did you mean: fullstackgtm enrich append?` plus
|
|
195
|
+
the full list. `market <typo>` no longer dies with a `market.config.json`
|
|
196
|
+
ENOENT before the subcommand is validated.
|
|
197
|
+
|
|
198
|
+
### Changed (agent ergonomics pass 1)
|
|
199
|
+
|
|
200
|
+
- **`apply <planId>` / `suggest <planId>` (positional) now name the exact
|
|
201
|
+
corrected command** (`apply --plan-id <id> --provider <…>`) instead of
|
|
202
|
+
misdiagnosing the mistake as a missing `--provider`. Both verbs stay
|
|
203
|
+
flag-explicit; nothing is inferred into execution.
|
|
204
|
+
- **`Unknown provider: hubpsot` now adds `Did you mean hubspot?`** (CLI and
|
|
205
|
+
MCP paths); the supported-provider list is unchanged.
|
|
206
|
+
|
|
207
|
+
### Changed
|
|
208
|
+
|
|
209
|
+
- **MCP zero-install is now one short command: `npx -y fullstackgtm-mcp`.** A
|
|
210
|
+
thin wrapper package (`fullstackgtm-mcp@0.1.0`, monorepo
|
|
211
|
+
`packages/fullstackgtm-mcp`, published manually — the OIDC release flow
|
|
212
|
+
covers only this package) pins the optional MCP peers as hard dependencies
|
|
213
|
+
and delegates to this package's `dist/mcp-bin.js`. README, SKILL.md, and the
|
|
214
|
+
missing-peer help text now lead with the short command; the explicit
|
|
215
|
+
`-p fullstackgtm -p @modelcontextprotocol/sdk -p zod` form still works.
|
|
216
|
+
|
|
217
|
+
### Added
|
|
218
|
+
|
|
219
|
+
- **`schedule install` no longer needs Full Disk Access on macOS: launchd
|
|
220
|
+
LaunchAgents are the default timer there.** macOS gates `crontab` writes
|
|
221
|
+
behind FDA — an unpromptable TCC permission granted to the terminal app,
|
|
222
|
+
not the CLI — so on darwin `install` now writes one LaunchAgent plist per
|
|
223
|
+
enabled entry (`com.fullstackgtm.<profile>.<id>` in
|
|
224
|
+
`~/Library/LaunchAgents`, loaded via `launchctl bootstrap gui/<uid>`),
|
|
225
|
+
which needs no permission at all. The cron expression compiles to
|
|
226
|
+
`StartCalendarInterval` dicts (full-range fields become launchd wildcards;
|
|
227
|
+
dense expressions are capped at 512 dicts with a clear error; Vixie
|
|
228
|
+
dom+dow OR becomes a Day set plus a Weekday set). Every plist's
|
|
229
|
+
`ProgramArguments` is `… schedule run <id> --profile <p> --trigger cron`
|
|
230
|
+
and nothing else — the same single-entry-point audit story as the crontab
|
|
231
|
+
block — and re-install replaces the profile's plist fleet wholesale,
|
|
232
|
+
never touching foreign plists. launchd also coalesces firings missed
|
|
233
|
+
during sleep into one run on wake, where cron silently skips. `--timer
|
|
234
|
+
crontab|launchd` overrides the platform default (launchd on macOS, crontab
|
|
235
|
+
elsewhere); stdout/stderr of each firing lands in
|
|
236
|
+
`<home>/schedule/logs/<label>.log`. New run no-op reason
|
|
237
|
+
`duplicate_firing`: `schedule run` drops a second cron trigger in the same
|
|
238
|
+
minute, so a date matching both the Day and Weekday interval sets cannot
|
|
239
|
+
double-run a command.
|
|
240
|
+
|
|
241
|
+
- **Standardized flag aliases (additive — every legacy flag keeps working).**
|
|
242
|
+
`--dry-run` is now accepted on the plan-spine verbs (`audit`, `suggest`,
|
|
243
|
+
`bulk-update`, `dedupe`, `reassign`, `fix`, `call plan`,
|
|
244
|
+
`enrich append`/`refresh`/`acquire`, `signals fetch`, `icp judge`, `draft`,
|
|
245
|
+
`tam status`, `market overlay`): these verbs were already preview-by-default,
|
|
246
|
+
so the flag asserts that default — it suppresses `--save` (with a stderr note
|
|
247
|
+
when both are passed) and locks `fix` to its stop-before-apply path even with
|
|
248
|
+
`--yes`. Omitting it changes nothing. `--confirm` is now an accepted alias
|
|
249
|
+
for `fix --yes` (matching `tam accounts --confirm`); `--dry-run` beats both.
|
|
250
|
+
- **Staged-file ingestion converged on the spool container convention
|
|
251
|
+
(additive).** `signals fetch --from` and `enrich ingest` now also accept a
|
|
252
|
+
`*.jsonl` spool file (one JSON row per line) or a directory of `*.jsonl` /
|
|
253
|
+
`*.json` spool files — the Phase-2 webhook landing-zone format
|
|
254
|
+
(docs/signal-spool-format.md) — and `market observe --from` accepts a
|
|
255
|
+
`*.jsonl` file / directory of observation-set envelopes, each validated and
|
|
256
|
+
appended through the same gates as the single-file path. Existing `.json` /
|
|
257
|
+
`.csv` inputs parse exactly as before. Shared reader: `src/spoolFiles.ts`
|
|
258
|
+
(the `file` signal-source connector now delegates to it, error text
|
|
259
|
+
unchanged).
|
|
260
|
+
- **Machine-readable agent contract: `capabilities`, `robot-docs`, and
|
|
261
|
+
`--help --json`.** `capabilities` prints the CLI contract as JSON: the
|
|
262
|
+
command inventory with read-only vs write-shaped access derived from the
|
|
263
|
+
help table's lifecycle phases (never a hand-maintained parallel list), the
|
|
264
|
+
documented exit-code contract, safety defaults, and the MCP entrypoint.
|
|
265
|
+
`<command> --help --json` (and `help <command> --json`) prints the same
|
|
266
|
+
per-command help as JSON — plain `--help` output is unchanged except that
|
|
267
|
+
`audit --help` now documents the pre-existing `--input <path>` flag.
|
|
268
|
+
Unknown commands invoked with `--json` return a structured
|
|
269
|
+
`{ ok: false, error: { code: "UNKNOWN_COMMAND", hints: [did-you-mean] } }`
|
|
270
|
+
envelope on stdout (exit 1) instead of the full-usage text wall.
|
|
271
|
+
`robot-docs` prints the packaged agent guide (skills/fullstackgtm/SKILL.md,
|
|
272
|
+
already shipped in the npm tarball) — one maintained guide, not a third
|
|
273
|
+
parallel one.
|
|
274
|
+
- **MCP `fullstackgtm_capabilities` tool.** The MCP server's tools are now
|
|
275
|
+
declared in a single registration table and registered in a loop; the new
|
|
276
|
+
tool reports the inventory (with per-tool `writesCrm` flags — only
|
|
277
|
+
`fullstackgtm_apply` can reach a CRM) from that same table, so the
|
|
278
|
+
advertised list cannot drift from what is actually registered. The existing
|
|
279
|
+
tools' behavior is unchanged.
|
|
280
|
+
- **`--input` snapshot files are validated.** `--input` now checks the file
|
|
281
|
+
parses to the canonical snapshot shape (the JSON `snapshot --out` writes)
|
|
282
|
+
and fails with a field-level error naming what's missing, instead of a
|
|
283
|
+
blind cast that surfaced as a crash deep in a rule — or an empty "clean"
|
|
284
|
+
audit.
|
|
285
|
+
|
|
286
|
+
### Fixed
|
|
287
|
+
|
|
288
|
+
- **`bulk-update` restored to the help front door.** The grouped short-usage
|
|
289
|
+
map listed the verb in its Remediate group but silently skipped rendering
|
|
290
|
+
it (no help-table entry), and `help bulk-update` dead-ended on the short
|
|
291
|
+
map. It now has a proper entry and appears in the derived `capabilities`
|
|
292
|
+
inventory.
|
|
293
|
+
|
|
294
|
+
### Changed
|
|
295
|
+
|
|
296
|
+
- **Internal: `src/cli.ts` (6,003 lines) split into per-verb modules under
|
|
297
|
+
`src/cli/`** (help, audit, fix, call, market, enrich, signals, draft, tam,
|
|
298
|
+
icp, init, schedule, plans, auth, shared). No behavior change: `dist/bin.js`
|
|
299
|
+
stays the entry, arg parsing / help text / exit codes are unchanged, and
|
|
300
|
+
`cli.ts` still exports `runCli`, `doctorReport`, `assertSecureBrokerUrl`.
|
|
301
|
+
|
|
10
302
|
## [0.44.0] — 2026-07-01
|
|
11
303
|
|
|
12
304
|
### Added
|
package/INSTALL_FOR_AGENTS.md
CHANGED
|
@@ -45,13 +45,15 @@ Credential resolution ladder, first match wins:
|
|
|
45
45
|
|
|
46
46
|
1. `--token-env <NAME>` — explicit env var for one invocation
|
|
47
47
|
2. Ambient env: `HUBSPOT_ACCESS_TOKEN`, or `SALESFORCE_ACCESS_TOKEN` + `SALESFORCE_INSTANCE_URL`, or `STRIPE_SECRET_KEY`
|
|
48
|
-
3.
|
|
49
|
-
4.
|
|
48
|
+
3. BYO direct login: advanced token/OAuth paths stored locally; these override hosted
|
|
49
|
+
4. Hosted OAuth / broker: `fullstackgtm login hubspot` or `fullstackgtm login salesforce` (default browser OAuth; stores a broker credential and mints provider tokens server-side) or `login --via <hosted url>`
|
|
50
50
|
|
|
51
|
-
In an agent sandbox, prefer rung 1 or 2.
|
|
52
|
-
|
|
51
|
+
In an agent sandbox, prefer rung 1 or 2. If a human is at a browser, the next
|
|
52
|
+
command is usually `fullstackgtm login hubspot --hosted` or
|
|
53
|
+
`fullstackgtm login salesforce --hosted`. Never echo tokens into argv — BYO
|
|
54
|
+
secret paths read from stdin only. Set `FSGTM_NO_BROWSER=1` in headless
|
|
53
55
|
environments — login flows then print verification URLs instead of opening
|
|
54
|
-
the OS browser.
|
|
56
|
+
the OS browser. First-party app secrets never ship in the npm package.
|
|
55
57
|
|
|
56
58
|
LLM calls (`call parse`, `call score`, `market classify`): set
|
|
57
59
|
`ANTHROPIC_API_KEY` or `OPENAI_API_KEY` in the environment, or have the human
|
|
@@ -77,11 +79,11 @@ count and provider spend per day and per month. It still flows through
|
|
|
77
79
|
dry-run → approve → apply and never auto-writes — expect it to refuse rather
|
|
78
80
|
than silently exceed the meter.
|
|
79
81
|
|
|
80
|
-
Provider prerequisites (what the human must create, and which
|
|
81
|
-
the README's **"Connect your CRM"** section: HubSpot
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
Provider prerequisites for BYO fallback (what the human must create, and which
|
|
83
|
+
scopes) are in the README's **"Connect your CRM"** section: HubSpot private app
|
|
84
|
+
or BYO OAuth app scopes, Salesforce admin-created Connected App for device
|
|
85
|
+
flow, and Stripe restricted key (Customers + Subscriptions read). Hosted
|
|
86
|
+
HubSpot/Salesforce OAuth is the default when available.
|
|
85
87
|
|
|
86
88
|
```bash
|
|
87
89
|
HUBSPOT_ACCESS_TOKEN=$TOKEN fullstackgtm audit --provider hubspot --json --out plan.json
|
|
@@ -140,6 +142,6 @@ stored captures before anything is appended).
|
|
|
140
142
|
| Symptom | Fix |
|
|
141
143
|
| --- | --- |
|
|
142
144
|
| `fullstackgtm: command not found` | Re-run with `npx fullstackgtm`, or check global npm bin is on PATH |
|
|
143
|
-
| `No HubSpot credentials` (exit 1) | Set `HUBSPOT_ACCESS_TOKEN` or have a human run `fullstackgtm login hubspot` |
|
|
145
|
+
| `No HubSpot credentials` (exit 1) | Set `HUBSPOT_ACCESS_TOKEN` or have a human run `fullstackgtm login hubspot --hosted` |
|
|
144
146
|
| MCP server prints peer-dependency help | Install `@modelcontextprotocol/sdk` and `zod` (see step 6) |
|
|
145
147
|
| Need machine state for orchestration | `fullstackgtm doctor --json` |
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Think `terraform plan` for your CRM: agents and scripts may *read* everything, b
|
|
|
8
8
|
|
|
9
9
|
Licensed under [Apache-2.0](./LICENSE). The boundary is deliberate and stable: the framework, CLI, and MCP server are open source; the hosted Full Stack GTM application (dashboard, sync backend, broker service, team workflows) is a separate, proprietary product built on top of this package. Features never move from open to closed. See [CONTRIBUTING.md](./CONTRIBUTING.md) for how development and mirroring work.
|
|
10
10
|
|
|
11
|
-
**Status: beta (0.x).** The surfaces in [docs/api.md](./docs/api.md) — the canonical model, rule interface, plan/apply contract, connector contract, merge/diff, config, CLI, and MCP tools — are settling but may still break in minor releases until 1.0
|
|
11
|
+
**Status: beta (0.x).** The surfaces in [docs/api.md](./docs/api.md) — the canonical model, rule interface, plan/apply contract, connector contract, merge/diff, config, CLI, and MCP tools — are settling but may still break in minor releases until 1.0. The safety invariants (read-only audits, approval-gated writes, placeholder refusal) are not beta and do not change. Connectors: HubSpot (read/write), Salesforce (read/write), Stripe (read-only billing).
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -136,8 +136,11 @@ fullstackgtm dedupe account --key domain --keep richest --save # one merge_rec
|
|
|
136
136
|
fullstackgtm reassign --from 411 --to 902 --except-deal-stage closing --save # ownership handoff playbook
|
|
137
137
|
fullstackgtm reassign --assign-unowned --to 902 --save # claim every ownerless record for an owner
|
|
138
138
|
fullstackgtm fix --rule missing-deal-owner --provider hubspot --yes # audit one rule → suggest → approve → apply, one command
|
|
139
|
+
fullstackgtm backfill stripe --save # paid Stripe invoices → proposed closed-won deals, deduped by invoice id
|
|
139
140
|
```
|
|
140
141
|
|
|
142
|
+
`backfill stripe` makes the CRM honest when Stripe is the revenue source of truth: one closed-won deal per paid invoice (amount = invoice total, close date = paid date, company association by billing-email domain). A customer the CRM doesn't know gets a proposed account create in the same approval-gated plan (freemail billing domains are never used as a company domain; `--skip-unmatched` keeps them report-only), and apply re-resolves each invoice id so re-running never double-creates.
|
|
143
|
+
|
|
141
144
|
`bulk-update` filters the snapshot (`=`, `!=`, `~` substring, `!~` not-substring, `:empty`/`:notempty`, type-aware comparisons `<` `>` `<=` `>=` where `today` resolves to the policy date — e.g. `closeDate<today` — and date/numeric fields coerce by value form, `|` any-of, relational pseudo-fields like `account.domain` or `openDealStages`) into a dry-run patch plan — and **the full filter is re-verified per record at apply time**, with mid-apply rechecks, so a record that stopped matching between audit and apply is skipped, not clobbered. For date/count hygiene (past close dates, stale deals, missing accounts, duplicates), prefer the rule-backed `fix --rule <id>` — the rule encodes the open-deal + date logic deterministically; use `bulk-update` only when no rule covers the task. Equality filters double as preconditions; `--require` adds explicit ones; `--guard` asserts cross-record conditions; `--max-operations` caps blast radius. `--set field=from:<sourceField>` derives values per record; `--create-task <text>` is the third change mode, emitting approval-gated `create_task` operations instead of field writes; `--archive` refuses records whose identity key (account domain, contact email) is shared with another record — that's a duplicate, and duplicates are merged with `dedupe`, not archived around (`--force-archive-duplicates` overrides that refusal explicitly).
|
|
142
145
|
|
|
143
146
|
`dedupe` finds duplicate groups by normalized identity key and emits one `merge_records` operation per group with a deterministic survivor (`richest` = most populated fields, ties to lowest id; `oldest`). Merges stay irreversible-and-therefore-low-confidence-capped on approval, exactly like merge suggestions from the audit. `reassign` is the ownership-handoff playbook: one plan per object type, extra scoping account-lifted to deals and contacts, and `--except-deal-stage` excludes both deals in that stage and every record whose account has an open deal in it. `fix` is the one-shot composite for a single rule: audit → save → suggest → approve suggestion-backed operations at the confidence bar → with `--yes`, apply and print the stage-by-stage summary; without it, stop after approval and print the apply command.
|
|
@@ -254,15 +257,15 @@ Everything the CLI produces is accurate the moment it runs and silently stale af
|
|
|
254
257
|
fullstackgtm schedule add "enrich refresh --source apollo --save" --cron "0 6 * * 1" --label weekly-apollo
|
|
255
258
|
fullstackgtm schedule add "audit --provider hubspot --save" --cron "0 2 * * *" # nightly drift baseline
|
|
256
259
|
fullstackgtm schedule list # declarative entries; nothing runs yet
|
|
257
|
-
fullstackgtm schedule install # materialize enabled entries into
|
|
260
|
+
fullstackgtm schedule install # materialize enabled entries into the system timer (launchd on macOS, crontab elsewhere)
|
|
258
261
|
fullstackgtm schedule run <id> # execute now; same run record a cron firing produces
|
|
259
262
|
fullstackgtm schedule status --runs 5 # last runs, exit codes, artifacts, next + missed firings
|
|
260
263
|
fullstackgtm schedule uninstall # remove the managed block, touch nothing else
|
|
261
264
|
```
|
|
262
265
|
|
|
263
|
-
**Scheduling never auto-approves.** Schedulable commands are read/plan-side only — `audit`, `snapshot`, `enrich append|refresh`, `market capture|refresh`, `signals fetch`, `icp judge`, `icp eval`, `draft`, `suggest`, `report`, `doctor` — so unattended runs accumulate *proposals* (plans in the queue, run records, reports), never CRM writes. `apply` is schedulable only as `apply --plan-id <id>`, and every firing re-checks the plan's status is approved: an unapproved plan records a `plan_not_approved` no-op run instead of executing, and no flag relaxes this. Arbitrary shell is not schedulable — an entry's argv must resolve to a known fullstackgtm command (validated at `add` time and re-checked at run time), and the
|
|
266
|
+
**Scheduling never auto-approves.** Schedulable commands are read/plan-side only — `audit`, `snapshot`, `enrich append|refresh`, `enrich acquire --save`, `market capture|refresh`, `signals fetch`, `icp judge`, `icp eval`, `draft`, `suggest`, `report`, `doctor` — so unattended runs accumulate *proposals* (plans in the queue, run records, reports), never CRM writes. `apply` is schedulable only as `apply --plan-id <id>`, and every firing re-checks the plan's status is approved: an unapproved plan records a `plan_not_approved` no-op run instead of executing, and no flag relaxes this. Arbitrary shell is not schedulable — an entry's argv must resolve to a known fullstackgtm command (validated at `add` time and re-checked at run time), and the timer line you audit — crontab entry or LaunchAgent `ProgramArguments` — is always `fullstackgtm schedule run <id>` and nothing else.
|
|
264
267
|
|
|
265
|
-
`install`
|
|
268
|
+
`install` materializes enabled entries into the system timer; `--timer` defaults by platform. On macOS it writes one LaunchAgent plist per entry (`com.fullstackgtm.<profile>.<id>` in `~/Library/LaunchAgents`, loaded via `launchctl bootstrap`) — macOS gates `crontab` writes behind Full Disk Access, a permission no program can request, while LaunchAgents need none; launchd also coalesces firings missed during sleep into one run on wake. Re-install replaces the profile's plist fleet wholesale and never touches foreign plists. Elsewhere (or with `--timer crontab`) it renders a sentinel-delimited block (`# >>> fullstackgtm <profile> >>>` … `# <<< fullstackgtm <profile> <<<`) in your user crontab; re-install replaces the block wholesale and never touches lines outside it. Honest limitation: cron has no catch-up — a laptop asleep at firing time means a missed run. `schedule status` surfaces missed firings by comparing expected-vs-actual run history, so the gap is at least visible. Entries are provider-agnostic; cloud providers (Modal, AWS) arrive as scaffold generators that call the same `schedule run <id>` contract, and are refused as "not yet implemented" until then.
|
|
266
269
|
|
|
267
270
|
### Working across organizations
|
|
268
271
|
|
|
@@ -281,6 +284,17 @@ Set `FULLSTACKGTM_PROFILE=acme` to pin a shell (or agent sandbox) to one client.
|
|
|
281
284
|
Every command is designed to compose in an agent loop — deterministic output, machine-readable everywhere, meaningful exit codes:
|
|
282
285
|
|
|
283
286
|
```bash
|
|
287
|
+
# Discover the machine-readable contract: every command, read-only vs
|
|
288
|
+
# write-shaped access, exit codes, safety defaults (derived from the same
|
|
289
|
+
# help table humans read, so it can't drift)
|
|
290
|
+
fullstackgtm capabilities --json
|
|
291
|
+
|
|
292
|
+
# Per-command help as JSON; plain --help stays human-shaped
|
|
293
|
+
fullstackgtm audit --help --json
|
|
294
|
+
|
|
295
|
+
# Print the shipped agent operating guide (the same SKILL.md `npx skills add` installs)
|
|
296
|
+
fullstackgtm robot-docs
|
|
297
|
+
|
|
284
298
|
# Discover what the auditor checks
|
|
285
299
|
fullstackgtm rules --json
|
|
286
300
|
|
|
@@ -296,11 +310,14 @@ fullstackgtm audit --provider hubspot --fail-on warning
|
|
|
296
310
|
fullstackgtm diff --before old.json --after new.json --fail-on-new-findings
|
|
297
311
|
```
|
|
298
312
|
|
|
313
|
+
- Terminal polish never leaks into machine output: color, spinners, progress bars, and sparklines render **only on an interactive TTY** (stderr for anything animated). Piped, redirected, `--json`, CI, and `NO_COLOR` output carries **zero ANSI bytes** — enforced by tests and a CI contract check — so transcripts and pipes see the same plain text they always did (`FORCE_COLOR=1` opts back in).
|
|
299
314
|
- Finding and operation ids are **stable hashes** of rule + record, so two runs over the same data produce identical ids — agents can diff plans, track findings across runs, and approve operations by id without re-parsing.
|
|
300
315
|
- `--demo` (with `--seed`) generates a realistic mid-market CRM with injected real-world failure modes — departed owners, unlinked deals, orphan accounts, stale pipeline — so agents and CI can exercise the full snapshot → audit → apply pipeline with zero credentials.
|
|
301
316
|
- Exit codes: `0` success, `1` error, `2` findings at/above `--fail-on`.
|
|
302
317
|
|
|
303
|
-
"Built for agents" is measured, not asserted: a 1,
|
|
318
|
+
"Built for agents" is measured, not asserted: a 1,904-run benchmark (17 CRM-operations scenarios × 3 tool-surface arms × up to 4 trials, across ten models from six vendors, deterministic graders over final CRM state, τ-bench-style pass^k) shows the gated CLI surface matching or beating raw CRM-API access on completion-under-policy for every model tested — strictly better in nine of ten — and the effect is vendor-independent. Full matrix and methodology: [the leaderboard](./evals/crm/leaderboard/RESULTS.md).
|
|
319
|
+
|
|
320
|
+
Caveat: the leaderboard documents the Opus reduced protocol and mixed-version Informed arms; treat the headline as a pointer to those exact RESULTS.md conditions.
|
|
304
321
|
|
|
305
322
|
The design is **deterministic apply, governed suggest**: the parts that touch your CRM — the audit rules, the plan/apply contract, compare-and-set, the survivor/merge logic — are deterministic and replayable; the parts that read free text (`call parse`/`score`, `market classify`) are LLM-powered but bounded, with every quoted span mechanically verified against the source before it can drive a writeback. Nondeterministic suggestion, deterministic governance.
|
|
306
323
|
|
|
@@ -309,9 +326,9 @@ The design is **deterministic apply, governed suggest**: the parts that touch yo
|
|
|
309
326
|
Credential resolution is a ladder — the first rung that yields a token wins:
|
|
310
327
|
|
|
311
328
|
1. **`--token-env <NAME>`** — explicit env var for one invocation (agent sandboxes, scripts)
|
|
312
|
-
2. **`HUBSPOT_ACCESS_TOKEN
|
|
313
|
-
3. **
|
|
314
|
-
4. **
|
|
329
|
+
2. **`HUBSPOT_ACCESS_TOKEN` / Salesforce env** — ambient env (CI)
|
|
330
|
+
3. **BYO direct login** — advanced token/OAuth paths stored in `~/.fullstackgtm/credentials.json` (0600; override location with `FSGTM_HOME`); these override hosted
|
|
331
|
+
4. **Hosted OAuth / broker** — `fullstackgtm login hubspot` or `fullstackgtm login salesforce` opens hosted browser OAuth by default, stores only a broker credential locally, and mints provider tokens server-side
|
|
315
332
|
|
|
316
333
|
### Teams: auth once, point every CLI at the stored sync credentials
|
|
317
334
|
|
|
@@ -325,22 +342,26 @@ An admin connects HubSpot **once** in the hosted dashboard (the org's OAuth toke
|
|
|
325
342
|
|
|
326
343
|
**Run observability (paired CLIs).** Once paired, each command best-effort reports a run record — command, status (`success`/`partial`/`error`), duration, headline counts (e.g. ops emitted, leads created, est. cost), and structured events (plan saved, meter charged) — to the deployment's **run timeline** in the dashboard. It's opt-in by pairing (an unpaired CLI sends nothing), a 4-second-capped POST that swallows every error, and never changes the command's exit code. Setup/inspection verbs (`login`, `doctor`, `help`, `--version`) are skipped.
|
|
327
344
|
|
|
328
|
-
### Individuals:
|
|
345
|
+
### Individuals: hosted OAuth by default, BYO still available
|
|
329
346
|
|
|
330
347
|
```bash
|
|
331
|
-
#
|
|
348
|
+
# Default: hosted first-party OAuth. No provider app or provider secret in the npm package.
|
|
332
349
|
fullstackgtm login hubspot
|
|
350
|
+
fullstackgtm login salesforce
|
|
351
|
+
# optional: point at a self-hosted/team deployment
|
|
352
|
+
fullstackgtm login hubspot --hosted --via https://gtm.yourco.com
|
|
353
|
+
|
|
354
|
+
# Advanced HubSpot private app token (validated, then stored; token on stdin).
|
|
355
|
+
# Note: hosted HubSpot OAuth covers reads and object writes but cannot create
|
|
356
|
+
# tasks (HubSpot exposes no tasks scope to public apps) — use a private-app
|
|
357
|
+
# token if your workflow relies on `create_task` proposals.
|
|
358
|
+
echo "$HUBSPOT_TOKEN" | fullstackgtm login hubspot --private-token
|
|
333
359
|
|
|
334
|
-
# HubSpot
|
|
335
|
-
# consent grant — captured on a 127.0.0.1 loopback (RFC 8252); the CLI
|
|
336
|
-
# exchanges the code itself and refreshes silently from then on. The client
|
|
337
|
-
# secret is read from stdin or an interactive prompt — never as a flag.
|
|
360
|
+
# Advanced HubSpot BYO-app OAuth. Client secret is read from stdin/prompt — never a flag.
|
|
338
361
|
echo "$CLIENT_SECRET" | fullstackgtm login hubspot --oauth --client-id <id>
|
|
339
362
|
# (register http://localhost:8763/callback as a redirect URL on your app)
|
|
340
363
|
|
|
341
|
-
# Salesforce
|
|
342
|
-
# server, no client secret, silent refresh. Needs a Connected App consumer key
|
|
343
|
-
# with device flow enabled (see "Connect your CRM" below).
|
|
364
|
+
# Advanced Salesforce BYO Connected App device flow.
|
|
344
365
|
fullstackgtm login salesforce --device --client-id <consumer key>
|
|
345
366
|
# ...or a session token directly (token on stdin, never as a flag):
|
|
346
367
|
echo "$SF_SESSION_TOKEN" | fullstackgtm login salesforce --instance-url https://yourorg.my.salesforce.com
|
|
@@ -348,7 +369,7 @@ echo "$SF_SESSION_TOKEN" | fullstackgtm login salesforce --instance-url https://
|
|
|
348
369
|
fullstackgtm logout hubspot # or: salesforce | broker
|
|
349
370
|
```
|
|
350
371
|
|
|
351
|
-
|
|
372
|
+
BYO direct credentials always win over a hosted broker pairing, so an operator can override the team default. First-party app secrets never ship in the npm package; hosted login stores a local broker token and mints provider tokens server-side. HubSpot does not support the device-authorization grant or secretless public clients, which is why the bring-your-own-app OAuth path requires client credentials; they are stored locally for silent refresh, the same model as `gcloud` and `aws` CLI profiles.
|
|
352
373
|
|
|
353
374
|
## Connect your CRM
|
|
354
375
|
|
|
@@ -377,7 +398,7 @@ Connectors differ in what the provider's API allows — stated up front so nothi
|
|
|
377
398
|
- `crm.objects.contacts.read`
|
|
378
399
|
- `crm.objects.deals.read`
|
|
379
400
|
3. If you plan to **apply** approved operations (not just audit), also grant write scopes for the objects you'll let it touch: `crm.objects.deals.write` (covers `deal.next_step` and other deal fields), plus `crm.objects.contacts.write` / `crm.objects.companies.write` for contact/company patches, and the **Tasks** write scope for `create_task` operations (search "tasks" in the scope picker; naming varies by portal).
|
|
380
|
-
4. Create the app, copy the token (`pat-...`), then: `echo "$TOKEN" | fullstackgtm login hubspot`.
|
|
401
|
+
4. Create the app, copy the token (`pat-...`), then: `echo "$TOKEN" | fullstackgtm login hubspot --private-token`.
|
|
381
402
|
|
|
382
403
|
If a scope is missing you'll see a `403` mid-run whose body names the exact missing scope (`requiredGranularScopes`) — add it to the private app and re-run. Note that `login` only validates the token itself; it can't tell whether every scope you'll need is granted.
|
|
383
404
|
|
|
@@ -406,7 +427,7 @@ This is a **short-lived session token with no refresh** — ideal for a one-off
|
|
|
406
427
|
|
|
407
428
|
### Stripe: a restricted key is enough (read-only connector)
|
|
408
429
|
|
|
409
|
-
The Stripe connector
|
|
430
|
+
The Stripe connector reads customers, subscriptions, and paid invoices (`backfill stripe` calls `/v1/invoices`), and `apply` is read-only by construction. Create a **restricted key** with just **Customers: Read**, **Subscriptions: Read**, and **Invoices: Read** (Developers → API keys → Create restricted key) instead of pasting a full-access secret key: `echo "$KEY" | fullstackgtm login stripe`.
|
|
410
431
|
|
|
411
432
|
## Concepts
|
|
412
433
|
|
|
@@ -488,14 +509,14 @@ The MCP entrypoint needs the optional peer dependencies `@modelcontextprotocol/s
|
|
|
488
509
|
npm install fullstackgtm @modelcontextprotocol/sdk zod
|
|
489
510
|
HUBSPOT_ACCESS_TOKEN=pat-... npx fullstackgtm-mcp
|
|
490
511
|
|
|
491
|
-
# Zero-install
|
|
492
|
-
npx -
|
|
512
|
+
# Zero-install (the fullstackgtm-mcp wrapper package bundles the MCP peers)
|
|
513
|
+
npx -y fullstackgtm-mcp
|
|
493
514
|
```
|
|
494
515
|
|
|
495
516
|
Add it to Claude Code in one command:
|
|
496
517
|
|
|
497
518
|
```bash
|
|
498
|
-
claude mcp add fullstackgtm -e HUBSPOT_ACCESS_TOKEN=pat-... -- npx -y
|
|
519
|
+
claude mcp add fullstackgtm -e HUBSPOT_ACCESS_TOKEN=pat-... -- npx -y fullstackgtm-mcp
|
|
499
520
|
```
|
|
500
521
|
|
|
501
522
|
Or configure any MCP client (Cursor, Claude Desktop, …) with:
|
|
@@ -505,7 +526,7 @@ Or configure any MCP client (Cursor, Claude Desktop, …) with:
|
|
|
505
526
|
"mcpServers": {
|
|
506
527
|
"fullstackgtm": {
|
|
507
528
|
"command": "npx",
|
|
508
|
-
"args": ["-y", "
|
|
529
|
+
"args": ["-y", "fullstackgtm-mcp"],
|
|
509
530
|
"env": { "HUBSPOT_ACCESS_TOKEN": "pat-..." }
|
|
510
531
|
}
|
|
511
532
|
}
|
package/dist/audit.d.ts
CHANGED
|
@@ -3,5 +3,7 @@ export declare function defaultPolicy(today?: string): GtmPolicy;
|
|
|
3
3
|
/**
|
|
4
4
|
* Run every rule over the snapshot and collect the results into a single
|
|
5
5
|
* dry-run patch plan. Pass custom rules to extend or replace the built-ins.
|
|
6
|
+
* `onRule` reports per-rule progress (presentation only — a throwing callback
|
|
7
|
+
* never fails the audit).
|
|
6
8
|
*/
|
|
7
|
-
export declare function auditSnapshot(snapshot: CanonicalGtmSnapshot, policy?: GtmPolicy, rules?: GtmAuditRule[]): PatchPlan;
|
|
9
|
+
export declare function auditSnapshot(snapshot: CanonicalGtmSnapshot, policy?: GtmPolicy, rules?: GtmAuditRule[], onRule?: (ruleId: string, phase: "start" | "done", findings?: number) => void): PatchPlan;
|
package/dist/audit.js
CHANGED
|
@@ -4,6 +4,19 @@ const DEFAULT_POLICY = {
|
|
|
4
4
|
requireDealOwner: true,
|
|
5
5
|
requireAccountForDeal: true,
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Wall clock for the plan's stdout-bound `createdAt`, honoring the
|
|
9
|
+
* SOURCE_DATE_EPOCH reproducible-build convention (seconds since the Unix
|
|
10
|
+
* epoch). When set, `audit --demo --json` is byte-deterministic across
|
|
11
|
+
* re-runs — golden tests and CI diffs can pin the payload. Unset (normal
|
|
12
|
+
* use) it is the real clock, exactly as before.
|
|
13
|
+
*/
|
|
14
|
+
function nowIso() {
|
|
15
|
+
const epoch = process.env.SOURCE_DATE_EPOCH;
|
|
16
|
+
if (epoch && /^\d+$/.test(epoch))
|
|
17
|
+
return new Date(Number(epoch) * 1000).toISOString();
|
|
18
|
+
return new Date().toISOString();
|
|
19
|
+
}
|
|
7
20
|
export function defaultPolicy(today = new Date().toISOString().slice(0, 10)) {
|
|
8
21
|
return {
|
|
9
22
|
...DEFAULT_POLICY,
|
|
@@ -13,15 +26,29 @@ export function defaultPolicy(today = new Date().toISOString().slice(0, 10)) {
|
|
|
13
26
|
/**
|
|
14
27
|
* Run every rule over the snapshot and collect the results into a single
|
|
15
28
|
* dry-run patch plan. Pass custom rules to extend or replace the built-ins.
|
|
29
|
+
* `onRule` reports per-rule progress (presentation only — a throwing callback
|
|
30
|
+
* never fails the audit).
|
|
16
31
|
*/
|
|
17
|
-
export function auditSnapshot(snapshot, policy = defaultPolicy(), rules = builtinAuditRules) {
|
|
32
|
+
export function auditSnapshot(snapshot, policy = defaultPolicy(), rules = builtinAuditRules, onRule) {
|
|
18
33
|
const context = { snapshot, policy, index: buildSnapshotIndex(snapshot) };
|
|
19
34
|
const findings = [];
|
|
20
35
|
const operations = [];
|
|
21
36
|
for (const rule of rules) {
|
|
37
|
+
try {
|
|
38
|
+
onRule?.(rule.id, "start");
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// progress is presentation-only
|
|
42
|
+
}
|
|
22
43
|
const result = rule.evaluate(context);
|
|
23
44
|
findings.push(...result.findings);
|
|
24
45
|
operations.push(...result.operations);
|
|
46
|
+
try {
|
|
47
|
+
onRule?.(rule.id, "done", result.findings.length);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// progress is presentation-only
|
|
51
|
+
}
|
|
25
52
|
}
|
|
26
53
|
const evidence = buildEvidence(snapshot, findings, policy.today);
|
|
27
54
|
const pipelineFindings = buildPipelineFindings(findings, operations, evidence, policy.today);
|
|
@@ -29,7 +56,7 @@ export function auditSnapshot(snapshot, policy = defaultPolicy(), rules = builti
|
|
|
29
56
|
return {
|
|
30
57
|
id: `patch_plan_${stableHash(`${snapshot.provider}:${snapshot.generatedAt}:${findings.length}:${operations.length}`)}`,
|
|
31
58
|
title: "GTM hygiene audit patch plan",
|
|
32
|
-
createdAt:
|
|
59
|
+
createdAt: nowIso(),
|
|
33
60
|
status: operations.length > 0 ? "needs_approval" : "draft",
|
|
34
61
|
dryRun: true,
|
|
35
62
|
summary: `${findings.length} findings and ${operations.length} proposed dry-run operations.`,
|