fullstackgtm 0.10.0 → 0.11.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 CHANGED
@@ -5,6 +5,112 @@ 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
6
  The path to 1.0 is planned in [docs/roadmap-to-1.0.md](./docs/roadmap-to-1.0.md).
7
7
 
8
+ ## [0.11.0] — 2026-06-11
9
+
10
+ Canonicalizes the paths discovered dogfooding against a real portal: the
11
+ suggest chain (every `requires_human_account_selection` answer was derivable
12
+ from the snapshot but required ad-hoc scripting), governed record creation,
13
+ client-ready reports, and multi-org profiles.
14
+
15
+ ### Added
16
+
17
+ - **`fullstackgtm suggest --plan-id <id> | --plan <path> [source options]`**:
18
+ deterministic value suggestions for `requires_human_*` placeholder
19
+ operations, derived from snapshot evidence — account-name matching
20
+ cross-checked against contact→account associations, plus the
21
+ only-active-user case for owner selection. Every suggestion carries a
22
+ confidence level (`high`/`low`/`create`/`none`) and a written reason;
23
+ conflicting or ambiguous evidence yields no suggestion, never a guess.
24
+ `--out` writes a suggestions file; `--json` for agents. Exposed
25
+ programmatically as `suggestValues` and over MCP as `fullstackgtm_suggest`.
26
+ - **`plans approve <id> --values-from <suggestions.json>`**: bulk-approve
27
+ with suggested values — high-confidence only by default,
28
+ `--min-confidence low` and `--include-creates` widen the bar explicitly.
29
+ Explicit `--value` flags still win.
30
+ - **`create:<Name>` link values**: approving a `link_record` operation with
31
+ `--value <op>=create:Acme` creates the company (HubSpot) / account
32
+ (Salesforce) and links to it in one audited operation — record creation
33
+ stays inside the typed, human-approved model instead of a side channel.
34
+ - New builtin rule `duplicate-open-deal` (data-quality): flags multiple open
35
+ deals sharing a normalized name, scoped to the account when linked —
36
+ typically an integration re-creating deals instead of upserting, which
37
+ counts the same revenue several times in pipeline and forecast. Emits one
38
+ finding and one approval-gated merge-review task per duplicate group.
39
+ Found dogfooding: an outreach-tool sync had tripled five open deals in our
40
+ own portal and no existing rule caught it.
41
+ - `fullstackgtm report` — render an audit (or an existing plan via `--plan`)
42
+ as a client-ready deliverable in markdown or self-contained HTML:
43
+ at-a-glance metrics, prose summary, per-rule detail with capped example
44
+ records (`--max-examples`), and recommended next steps. `--client`,
45
+ `--title`, `--prepared-by`, and `--format` customize the output;
46
+ `--out report.html` infers HTML. Exposed programmatically as
47
+ `auditReportToMarkdown` / `auditReportToHtml`.
48
+ - Credential profiles for multi-organization use: the global
49
+ `--profile <name>` flag (or `FULLSTACKGTM_PROFILE`) scopes stored logins
50
+ AND stored plans to `profiles/<name>/` under the fullstackgtm home, so one
51
+ operator can hold several clients' credentials without mixing them — and a
52
+ plan proposed against one org's CRM can never be applied through
53
+ another's. New `profiles` command lists them; `doctor` reports the active
54
+ profile. The default profile keeps the existing flat layout, so current
55
+ installs are unaffected. Exposed programmatically as `setActiveProfile`,
56
+ `activeProfile`, `listProfiles`, and `DEFAULT_PROFILE`.
57
+
58
+ ### Fixed
59
+
60
+ - `validateHubspotToken` / `validateSalesforceToken` no longer echo the
61
+ provider's raw error body into the login failure message (observed live: a
62
+ HubSpot 401 body printed to the terminal). Status line only, matching the
63
+ no-body-interpolation rule applied elsewhere in 1.0.1.
64
+ - Unreachable hosts during `login salesforce --instance-url` and
65
+ `login --via <url>` now name the target and what to check, completing the
66
+ 0.10.1 fix that only covered the audit/connector path.
67
+
68
+ ## [0.10.1] — 2026-06-11
69
+
70
+ Fixes from a full fresh-user journey audit (install → demo → MCP → real CRM),
71
+ focused on the first-contact experience.
72
+
73
+ ### Added
74
+
75
+ - **`fullstackgtm --version`** (also `-v` / `version`) — previously exited 1
76
+ with a usage dump.
77
+ - **README "Connect your CRM" section**: HubSpot private-app walkthrough with
78
+ the exact read scopes the audit needs (`crm.objects.{owners,companies,contacts,deals}.read`)
79
+ and the write scopes `apply` needs; Salesforce Connected App prerequisites
80
+ (admin-created, device flow enabled, `api` + `refresh_token` scopes,
81
+ propagation delay); Stripe restricted-key guidance. Previously the word
82
+ "scope" appeared nowhere in the docs.
83
+ - Roadmap: documented the known real-portal gaps to close before 1.0
84
+ (pipeline-aware closed-deal detection, 429/retry, fetchChanges 10k
85
+ truncation, MCP plan hand-off, scope-complete login validation).
86
+
87
+ ### Fixed
88
+
89
+ - **`FSGTM_NO_BROWSER=1` suppresses OS browser opens** in all login flows
90
+ (broker pairing, Salesforce device flow, HubSpot OAuth) — for agent
91
+ sandboxes, CI, and headless use; verification URLs are always printed.
92
+ The broker test suite sets it, so running `npm test` no longer pops a
93
+ real browser tab at a mock pairing URL on every run.
94
+ - **MCP server now works from inside existing projects.** When launched via
95
+ `npx -p ... fullstackgtm-mcp` from a directory whose node_modules already
96
+ contains the peers (but not fullstackgtm), npx skips installing them into
97
+ its cache and module resolution failed; the server now falls back to
98
+ resolving the peers from the working directory — peer-dependency semantics.
99
+ Previously this made the README's `claude mcp add` setup produce a dead
100
+ server in most agent-tooling repos.
101
+ - **README auth examples matched the CLI again**: `login salesforce --token ...`
102
+ and `login hubspot --oauth ... --client-secret ...` showed argv-secret forms
103
+ the CLI deliberately rejects; both now use stdin.
104
+ - The no-credentials Stripe error suggested `login stripe --token sk_...`,
105
+ which the CLI itself refuses — it now shows the stdin form and mentions
106
+ restricted keys.
107
+ - Unreachable hosts no longer surface as a bare `fetch failed`: HubSpot and
108
+ Salesforce connection errors name the target URL and (for Salesforce) point
109
+ at SALESFORCE_INSTANCE_URL.
110
+ - Credential errors now point at `fullstackgtm doctor` and the README scope
111
+ guide; the MCP audit tool description now mentions the `demo` source;
112
+ README rule count corrected (11 built-ins, not five).
113
+
8
114
  ## [0.10.0] — 2026-06-10
9
115
 
10
116
  **Versioning reset to reflect beta status.** The 1.x numbering below
@@ -45,7 +45,15 @@ Credential resolution ladder, first match wins:
45
45
  4. Broker pairing: `fullstackgtm login --via <hosted url>` (a human approves the pairing code)
46
46
 
47
47
  In an agent sandbox, prefer rung 1 or 2. Never echo tokens into argv —
48
- `login` reads secrets from stdin only.
48
+ `login` reads secrets from stdin only. Set `FSGTM_NO_BROWSER=1` in headless
49
+ environments — login flows then print verification URLs instead of opening
50
+ the OS browser.
51
+
52
+ Provider prerequisites (what the human must create, and which scopes) are in
53
+ the README's **"Connect your CRM"** section: HubSpot needs a private app with
54
+ four `crm.objects.*.read` scopes (plus write scopes only for `apply`);
55
+ Salesforce needs an admin-created Connected App with device flow enabled;
56
+ Stripe works with a restricted key (Customers + Subscriptions read).
49
57
 
50
58
  ```bash
51
59
  HUBSPOT_ACCESS_TOKEN=$TOKEN fullstackgtm audit --provider hubspot --json --out plan.json
@@ -65,15 +73,33 @@ fullstackgtm audit --provider hubspot --save # persists plan to ~/.fullstack
65
73
  fullstackgtm plans list # a human reviews and approves
66
74
  ```
67
75
 
76
+ For `requires_human_*` placeholders, chain the deterministic suggestion engine
77
+ instead of guessing values yourself:
78
+
79
+ ```bash
80
+ fullstackgtm suggest --plan-id <id> --provider hubspot --out suggestions.json # read-only
81
+ fullstackgtm plans approve <id> --values-from suggestions.json # high-confidence only
82
+ fullstackgtm apply --plan-id <id> --provider hubspot
83
+ ```
84
+
85
+ Every suggestion carries a confidence (`high`/`low`/`create`/`none`) and a
86
+ reason derived from snapshot evidence. Surface `low`/`create`/`none` entries
87
+ to your human rather than widening the bar unilaterally — `create:<Name>`
88
+ values create a new CRM record when applied.
89
+
68
90
  ## 6. MCP server (optional)
69
91
 
70
92
  The MCP entrypoint needs optional peers that plain `npx fullstackgtm-mcp`
71
93
  does not install:
72
94
 
73
95
  ```bash
74
- npx -p fullstackgtm -p @modelcontextprotocol/sdk -p zod fullstackgtm-mcp
96
+ npx -y -p fullstackgtm -p @modelcontextprotocol/sdk -p zod fullstackgtm-mcp
75
97
  ```
76
98
 
99
+ If the working directory's project already has the peers in its node_modules,
100
+ the server resolves them from there (peer-dependency semantics) — so this
101
+ works from inside existing projects too.
102
+
77
103
  Tools exposed over stdio: `fullstackgtm_audit` (read-only),
78
104
  `fullstackgtm_rules`, `fullstackgtm_apply` (requires `approvedOperationIds`).
79
105
 
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # fullstackgtm
2
2
 
3
+ [![CI](https://github.com/fullstackgtm/core/actions/workflows/ci.yml/badge.svg)](https://github.com/fullstackgtm/core/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/fullstackgtm)](https://www.npmjs.com/package/fullstackgtm) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](./LICENSE)
4
+
3
5
  **Plan/apply for your GTM stack.** An open-source framework for managing disparate go-to-market data spread across third-party systems: a canonical CRM/GTM data model, deterministic hygiene audits, reviewable dry-run patch plans, and approval-gated write-back to providers.
4
6
 
5
7
  Think `terraform plan` for your CRM: agents and scripts may *read* everything, but every proposed change becomes a typed patch operation — object, field, before, after, reason, risk — that a human approves before any provider write happens.
@@ -45,6 +47,39 @@ HUBSPOT_ACCESS_TOKEN=pat-... npx fullstackgtm apply \
45
47
 
46
48
  Nothing is ever written without an explicit `--approve`. Operations whose value is a human decision (`requires_human_*` placeholders, e.g. which owner to assign) are refused unless you supply a concrete `--value` override.
47
49
 
50
+ ## From findings to fixes: the suggest chain
51
+
52
+ Most placeholder answers are already derivable from your own CRM data. `suggest` computes them deterministically — account-name matching cross-checked against contact associations — with a confidence level and a written reason per operation, so you (or an agent) approve evidence, not guesses:
53
+
54
+ ```bash
55
+ fullstackgtm audit --provider hubspot --save # → Saved plan patch_plan_abc123
56
+ fullstackgtm suggest --plan-id patch_plan_abc123 --provider hubspot --out suggestions.json
57
+ # review suggestions.json: every value carries confidence (high/low/create/none) + a reason
58
+ fullstackgtm plans approve patch_plan_abc123 --values-from suggestions.json # high-confidence only by default
59
+ fullstackgtm apply --plan-id patch_plan_abc123 --provider hubspot
60
+ ```
61
+
62
+ Widen the bar deliberately: `--min-confidence low` accepts single-signal matches; `--include-creates` accepts `create:<Name>` values — approving one **creates the missing company/account record and links to it** in a single audited operation, so even record creation stays inside the typed, human-approved model. Conflicting or ambiguous evidence always yields *no* suggestion with an explanation, never a guess.
63
+
64
+ ```bash
65
+ # 3. Hand the findings to whoever owns the CRM: a client-ready report
66
+ npx fullstackgtm report --provider hubspot --client "Acme" --out acme-health.html
67
+ ```
68
+
69
+ `report` renders the same audit as a deliverable — severity counts up front, a prose summary, per-rule detail with example records, and next steps — as markdown or self-contained HTML (printable, emailable, no external assets).
70
+
71
+ ### Working across organizations
72
+
73
+ Consultants and fractional operators hold credentials for several CRMs at once. A profile scopes stored logins *and* stored plans to one organization:
74
+
75
+ ```bash
76
+ fullstackgtm --profile acme login hubspot
77
+ fullstackgtm --profile acme audit --provider hubspot --save
78
+ fullstackgtm profiles # list profiles, * marks the active one
79
+ ```
80
+
81
+ Set `FULLSTACKGTM_PROFILE=acme` to pin a shell (or agent sandbox) to one client. Plans saved under a profile are invisible to every other profile, so a patch plan proposed against one client's CRM can never be applied through another client's credentials.
82
+
48
83
  ## Built for agents (and the RevOps humans they work for)
49
84
 
50
85
  Every command is designed to compose in an agent loop — deterministic output, machine-readable everywhere, meaningful exit codes:
@@ -93,28 +128,61 @@ fullstackgtm login hubspot
93
128
 
94
129
  # HubSpot, bring-your-own-app OAuth. The browser is used exactly once — the
95
130
  # consent grant — captured on a 127.0.0.1 loopback (RFC 8252); the CLI
96
- # exchanges the code itself and refreshes silently from then on.
97
- fullstackgtm login hubspot --oauth --client-id <id> --client-secret <secret>
131
+ # exchanges the code itself and refreshes silently from then on. The client
132
+ # secret is read from stdin or an interactive prompt — never as a flag.
133
+ echo "$CLIENT_SECRET" | fullstackgtm login hubspot --oauth --client-id <id>
98
134
  # (register http://localhost:8763/callback as a redirect URL on your app)
99
135
 
100
136
  # Salesforce: native device flow — confirm a code on any device, no localhost
101
137
  # server, no client secret, silent refresh. Needs a Connected App consumer key
102
- # with device flow enabled.
138
+ # with device flow enabled (see "Connect your CRM" below).
103
139
  fullstackgtm login salesforce --device --client-id <consumer key>
104
- # ...or a session token directly:
105
- fullstackgtm login salesforce --token <t> --instance-url https://yourorg.my.salesforce.com
140
+ # ...or a session token directly (token on stdin, never as a flag):
141
+ echo "$SF_SESSION_TOKEN" | fullstackgtm login salesforce --instance-url https://yourorg.my.salesforce.com
106
142
 
107
143
  fullstackgtm logout hubspot # or: salesforce | broker
108
144
  ```
109
145
 
110
146
  A direct `login hubspot` always wins over a broker pairing, so an operator can override the team default. 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.
111
147
 
148
+ ## Connect your CRM
149
+
150
+ What each provider actually requires before `audit --provider <name>` works on your data.
151
+
152
+ ### HubSpot: create a private app (~2 minutes, needs super-admin)
153
+
154
+ 1. In HubSpot: **Settings → Integrations → Private Apps → Create a private app.**
155
+ 2. On the **Scopes** tab, grant the read scopes the audit needs:
156
+ - `crm.objects.owners.read`
157
+ - `crm.objects.companies.read`
158
+ - `crm.objects.contacts.read`
159
+ - `crm.objects.deals.read`
160
+ 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).
161
+ 4. Create the app, copy the token (`pat-...`), then: `echo "$TOKEN" | fullstackgtm login hubspot`.
162
+
163
+ 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.
164
+
165
+ ### Salesforce: a Connected App (one-time, usually needs an admin)
166
+
167
+ Device-flow login requires a Connected App in your org — if you're not an admin, this is the step to ask one for:
168
+
169
+ 1. **Setup → App Manager → New Connected App**, enable OAuth settings.
170
+ 2. Check **Enable Device Flow**.
171
+ 3. OAuth scopes: **Manage user data via APIs (`api`)** and **Perform requests at any time (`refresh_token`)** — the CLI requests exactly these.
172
+ 4. Save (Salesforce can take ~2–10 minutes to propagate a new Connected App), copy the **Consumer Key**, then: `fullstackgtm login salesforce --device --client-id <consumer key>`.
173
+
174
+ Writeback needs no extra OAuth scope — applies are gated by the logged-in user's normal object/field permissions.
175
+
176
+ ### Stripe: a restricted key is enough (read-only connector)
177
+
178
+ The Stripe connector only reads customers and subscriptions, and `apply` is read-only by construction. Create a **restricted key** with just **Customers: Read** and **Subscriptions: Read** (Developers → API keys → Create restricted key) instead of pasting a full-access secret key: `echo "$KEY" | fullstackgtm login stripe`.
179
+
112
180
  ## Concepts
113
181
 
114
182
  | Concept | What it is |
115
183
  |---|---|
116
184
  | **Canonical snapshot** | Provider-independent view of users, accounts, contacts, deals, activities. Records carry `identities` — `(provider, externalId)` claims — so the same real-world entity can be tracked across several systems. |
117
- | **Audit rule** | A deterministic function `(context) => { findings, operations }`. Five built-ins cover orphan accounts, ownerless deals, unlinked deals, past close dates, and stale pipeline. Write your own in ~10 lines. |
185
+ | **Audit rule** | A deterministic function `(context) => { findings, operations }`. Eleven built-ins cover orphan accounts, ownerless/unlinked/amount-less deals, past close dates, stale pipeline, duplicates, and more — `fullstackgtm rules` lists them all. Write your own in ~10 lines. |
118
186
  | **Patch plan** | The dry-run output of an audit: findings plus typed patch operations with before/after values, reasons, risk levels, and approval flags. Always a proposal, never a mutation. |
119
187
  | **Connector** | A provider adapter: `fetchSnapshot()` for reads, optional `applyOperation()` for writes. HubSpot and Salesforce reference connectors ship in the package; connectors never drop records they can't fully resolve — the audit flags them instead. |
120
188
  | **Patch plan run** | The audit record of one apply attempt: per-operation applied/failed/skipped results. |
package/dist/cli.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare function doctorReport(env?: Record<string, string | undefined>):
12
12
  ok: boolean;
13
13
  required: string;
14
14
  };
15
+ profile: string;
15
16
  credentialStore: {
16
17
  path: string;
17
18
  exists: boolean;