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.
Files changed (149) hide show
  1. package/CHANGELOG.md +293 -1
  2. package/INSTALL_FOR_AGENTS.md +13 -11
  3. package/README.md +45 -24
  4. package/dist/audit.d.ts +3 -1
  5. package/dist/audit.js +29 -2
  6. package/dist/backfill.d.ts +86 -0
  7. package/dist/backfill.js +251 -0
  8. package/dist/bin.js +4 -1
  9. package/dist/cli/audit.d.ts +15 -0
  10. package/dist/cli/audit.js +392 -0
  11. package/dist/cli/auth.d.ts +82 -0
  12. package/dist/cli/auth.js +607 -0
  13. package/dist/cli/backfill.d.ts +1 -0
  14. package/dist/cli/backfill.js +125 -0
  15. package/dist/cli/backfillRuns.d.ts +1 -0
  16. package/dist/cli/backfillRuns.js +187 -0
  17. package/dist/cli/call.d.ts +1 -0
  18. package/dist/cli/call.js +387 -0
  19. package/dist/cli/capabilities.d.ts +30 -0
  20. package/dist/cli/capabilities.js +197 -0
  21. package/dist/cli/draft.d.ts +7 -0
  22. package/dist/cli/draft.js +87 -0
  23. package/dist/cli/enrich.d.ts +8 -0
  24. package/dist/cli/enrich.js +806 -0
  25. package/dist/cli/fix.d.ts +33 -0
  26. package/dist/cli/fix.js +346 -0
  27. package/dist/cli/help.d.ts +25 -0
  28. package/dist/cli/help.js +646 -0
  29. package/dist/cli/icp.d.ts +7 -0
  30. package/dist/cli/icp.js +229 -0
  31. package/dist/cli/init.d.ts +7 -0
  32. package/dist/cli/init.js +58 -0
  33. package/dist/cli/market.d.ts +1 -0
  34. package/dist/cli/market.js +391 -0
  35. package/dist/cli/plans.d.ts +5 -0
  36. package/dist/cli/plans.js +456 -0
  37. package/dist/cli/schedule.d.ts +8 -0
  38. package/dist/cli/schedule.js +479 -0
  39. package/dist/cli/shared.d.ts +71 -0
  40. package/dist/cli/shared.js +342 -0
  41. package/dist/cli/signals.d.ts +7 -0
  42. package/dist/cli/signals.js +412 -0
  43. package/dist/cli/suggest.d.ts +49 -0
  44. package/dist/cli/suggest.js +135 -0
  45. package/dist/cli/tam.d.ts +9 -0
  46. package/dist/cli/tam.js +387 -0
  47. package/dist/cli/ui.d.ts +142 -0
  48. package/dist/cli/ui.js +427 -0
  49. package/dist/cli.d.ts +1 -57
  50. package/dist/cli.js +123 -5157
  51. package/dist/connector.d.ts +23 -0
  52. package/dist/connector.js +47 -0
  53. package/dist/connectors/hubspot.d.ts +10 -1
  54. package/dist/connectors/hubspot.js +244 -10
  55. package/dist/connectors/hubspotAuth.js +5 -1
  56. package/dist/connectors/linkedin.js +14 -14
  57. package/dist/connectors/salesforce.d.ts +10 -1
  58. package/dist/connectors/salesforce.js +39 -6
  59. package/dist/connectors/signalSources.js +7 -59
  60. package/dist/connectors/stripe.d.ts +37 -0
  61. package/dist/connectors/stripe.js +103 -31
  62. package/dist/enrich.d.ts +7 -0
  63. package/dist/enrich.js +7 -0
  64. package/dist/health.d.ts +11 -69
  65. package/dist/health.js +4 -134
  66. package/dist/healthScore.d.ts +71 -0
  67. package/dist/healthScore.js +143 -0
  68. package/dist/icp.d.ts +15 -11
  69. package/dist/icp.js +19 -36
  70. package/dist/index.d.ts +3 -1
  71. package/dist/index.js +3 -1
  72. package/dist/judge.d.ts +2 -0
  73. package/dist/judge.js +6 -0
  74. package/dist/llm.d.ts +29 -0
  75. package/dist/llm.js +206 -0
  76. package/dist/market.d.ts +39 -1
  77. package/dist/market.js +116 -44
  78. package/dist/marketClassify.d.ts +11 -1
  79. package/dist/marketClassify.js +17 -2
  80. package/dist/marketTaxonomy.d.ts +6 -1
  81. package/dist/marketTaxonomy.js +4 -2
  82. package/dist/mcp-bin.js +31 -2
  83. package/dist/mcp.js +372 -166
  84. package/dist/progress.d.ts +96 -0
  85. package/dist/progress.js +142 -0
  86. package/dist/runReport.d.ts +24 -0
  87. package/dist/runReport.js +139 -4
  88. package/dist/schedule.d.ts +80 -2
  89. package/dist/schedule.js +254 -1
  90. package/dist/spoolFiles.d.ts +44 -0
  91. package/dist/spoolFiles.js +114 -0
  92. package/dist/types.d.ts +29 -1
  93. package/docs/api.md +75 -8
  94. package/docs/architecture.md +2 -0
  95. package/docs/linkedin-connector-spec.md +1 -1
  96. package/docs/signal-spool-format.md +13 -0
  97. package/llms.txt +18 -9
  98. package/package.json +10 -3
  99. package/skills/fullstackgtm/SKILL.md +2 -1
  100. package/src/audit.ts +27 -1
  101. package/src/backfill.ts +340 -0
  102. package/src/bin.ts +5 -1
  103. package/src/cli/audit.ts +447 -0
  104. package/src/cli/auth.ts +669 -0
  105. package/src/cli/backfill.ts +156 -0
  106. package/src/cli/backfillRuns.ts +198 -0
  107. package/src/cli/call.ts +414 -0
  108. package/src/cli/capabilities.ts +215 -0
  109. package/src/cli/draft.ts +101 -0
  110. package/src/cli/enrich.ts +908 -0
  111. package/src/cli/fix.ts +373 -0
  112. package/src/cli/help.ts +702 -0
  113. package/src/cli/icp.ts +265 -0
  114. package/src/cli/init.ts +65 -0
  115. package/src/cli/market.ts +423 -0
  116. package/src/cli/plans.ts +524 -0
  117. package/src/cli/schedule.ts +526 -0
  118. package/src/cli/shared.ts +392 -0
  119. package/src/cli/signals.ts +434 -0
  120. package/src/cli/suggest.ts +151 -0
  121. package/src/cli/tam.ts +435 -0
  122. package/src/cli/ui.ts +497 -0
  123. package/src/cli.ts +122 -5855
  124. package/src/connector.ts +66 -0
  125. package/src/connectors/hubspot.ts +273 -9
  126. package/src/connectors/hubspotAuth.ts +5 -1
  127. package/src/connectors/linkedin.ts +14 -14
  128. package/src/connectors/salesforce.ts +51 -3
  129. package/src/connectors/signalSources.ts +7 -56
  130. package/src/connectors/stripe.ts +154 -34
  131. package/src/enrich.ts +13 -0
  132. package/src/health.ts +14 -213
  133. package/src/healthScore.ts +223 -0
  134. package/src/icp.ts +19 -35
  135. package/src/index.ts +28 -1
  136. package/src/judge.ts +7 -0
  137. package/src/llm.ts +239 -0
  138. package/src/market.ts +157 -44
  139. package/src/marketClassify.ts +26 -2
  140. package/src/marketTaxonomy.ts +10 -2
  141. package/src/mcp-bin.ts +34 -2
  142. package/src/mcp.ts +270 -63
  143. package/src/progress.ts +197 -0
  144. package/src/runReport.ts +159 -4
  145. package/src/schedule.ts +310 -3
  146. package/src/spoolFiles.ts +116 -0
  147. package/src/types.ts +32 -2
  148. package/docs/dx-punch-list.md +0 -87
  149. package/docs/roadmap-to-1.0.md +0 -211
@@ -0,0 +1,646 @@
1
+ // Extracted verbatim from src/cli.ts (mechanical split, no behavior change).
2
+ import { DEFAULT_LOOPBACK_PORT } from "../connectors/hubspotAuth.js";
3
+ export function usage() {
4
+ return `FullStackGTM — audit GTM data across providers, propose reviewable patch plans,
5
+ and apply only explicitly approved operations.
6
+
7
+ Usage:
8
+ fullstackgtm login hubspot hosted browser OAuth (default; no app needed)
9
+ fullstackgtm login salesforce hosted browser OAuth (default; no app needed)
10
+ fullstackgtm login hubspot --hosted [--via <url>]
11
+ fullstackgtm login salesforce --hosted [--via <url>]
12
+ fullstackgtm login --via <hosted url> pair with a team deployment (broker only)
13
+ fullstackgtm login hubspot --private-token [--no-validate] advanced: paste private app token
14
+ fullstackgtm login hubspot --oauth --client-id <id> [--port ${DEFAULT_LOOPBACK_PORT}] [--scopes a,b] advanced: BYO HubSpot app
15
+ fullstackgtm login salesforce --device --client-id <consumer key> [--login-url <url>] advanced: BYO Connected App
16
+ fullstackgtm login salesforce --instance-url <url> [--no-validate] advanced: paste access token
17
+ fullstackgtm login stripe [--no-validate]
18
+ fullstackgtm login anthropic | openai store an LLM API key for call parse/score
19
+ fullstackgtm login apollo store an Apollo API key for enrich pulls\n fullstackgtm login pipe0 | explorium | theirstack store a discovery-provider key (theirstack = technographic TAM)\n fullstackgtm login heyreach store a HeyReach key for enrich acquire --source linkedin\n fullstackgtm logout <hubspot|salesforce|stripe|anthropic|openai|apollo|pipe0|explorium|heyreach|broker>
20
+
21
+ Secrets (tokens, client secrets) are NEVER passed as flags — they leak via
22
+ the process list and shell history. Pipe them on stdin or enter them at the
23
+ interactive prompt:
24
+ echo "$HUBSPOT_TOKEN" | fullstackgtm login hubspot --private-token
25
+ fullstackgtm init [--source pipe0|explorium|linkedin] [--provider hubspot|salesforce] [--out <dir>] [--force]
26
+ cold start: scaffold icp.json + enrich.config.json + a
27
+ PLAYBOOK wired for this workspace (the CLI ships primitives,
28
+ your agent is the orchestrator — see docs/recipes.md)
29
+ fullstackgtm snapshot [source options] [--since <iso>] [--out <path> | --archive <dir>]
30
+ fullstackgtm audit [source options] [audit options] [--save]
31
+ fullstackgtm report [source options] [audit options] [report options]
32
+ fullstackgtm diff --before <a.json> --after <b.json> [--json] [--fail-on-new-findings]
33
+ fullstackgtm merge --input <a.json> --input <b.json> [...] --out <merged.json> [--json]
34
+ fullstackgtm call parse --transcript <file> [--title t] [--source fathom|granola|...] [--model m] [--llm] [--heuristics] [--json|--ndjson] [--out <path>]
35
+ fullstackgtm call classify --transcript <file>|--call <parsed.json> [--llm] [--deterministic] [--json]
36
+ fullstackgtm call score --transcript <file>|--call <parsed.json> [--call-type <t>] [--rubric <rubric.json>] [--model m] [--json|--out <path>]
37
+ fullstackgtm call link --attendees <a@x.com,...> | --domain <x.com> [source options] [--json]
38
+ fullstackgtm call plan --transcript <file>|--call <parsed.json> --deal <id> [source options] [--save|--json]
39
+ calls become evidence: chunked LLM extraction by default when a
40
+ key resolves (ANTHROPIC_API_KEY/OPENAI_API_KEY or \`login
41
+ anthropic|openai\`), free deterministic fallback when none does;
42
+ --heuristics (alias --deterministic) forces the deterministic
43
+ engine, --llm forces the LLM path. Model: --model, else env
44
+ FSGTM_INSIGHTS_MODEL. Then link the call to its deal and propose
45
+ governed next-step writes.
46
+ fullstackgtm resolve <account|contact|deal> [--name N] [--domain D] [--email E] [--account-id A] [source options] [--json]
47
+ the create gate: exit 0 = safe to create, exit 2 = match
48
+ found (exists/ambiguous) — call before ANY record creation
49
+ fullstackgtm market init --category <name> start a market map: vendors + claim taxonomy as reviewable config
50
+ fullstackgtm market capture [--config <path>] [--run <label>]
51
+ fullstackgtm market classify [--run <label>] [--vendor <id>] [--model m] [--out <path>]
52
+ fullstackgtm market worksheet --vendor <id> [--out <path>]
53
+ fullstackgtm market observe --from <observations.json|sets.jsonl|spool-dir> [--unverified]
54
+ fullstackgtm market fronts [--run <label>] [--diff <prior-run>] [--json]
55
+ fullstackgtm market axes [--run <label>] [--json]
56
+ fullstackgtm market report [--run <label>] [--format md|html] [--out <path>]
57
+ fullstackgtm market overlay --snapshot <crm.json> [--calls <files>] [--save]
58
+ fullstackgtm market scale [--json]
59
+ fullstackgtm market refresh [--run <label>] [--model m]
60
+ the live competitive map: capture vendor pages (content-addressed),
61
+ classify intensity per claim (LLM bring-your-own-key, or fill the
62
+ worksheet with any agent) — every quoted span is verified verbatim
63
+ against the stored capture it cites before it's accepted — then
64
+ compute deterministic front states and drift, render the field
65
+ report. refresh = capture → classify → drift → report in one step
66
+ fullstackgtm tam estimate [--name <n>] [--icp <path>] (--accounts <n> | --source theirstack|explorium) (--acv <annual-usd> | --acv-from-crm --deal-period monthly|quarterly|annual) [--acv-basis account|buyer] [--buyers-per-account <n>] [--cross-checks <file.json>] [source options] [--json]
67
+ fullstackgtm tam status [--name <n>] <source options> [--save] [--json]
68
+ fullstackgtm tam report [--name <n>] [--out <path>]
69
+ fullstackgtm tam populate [--name <n>] --cron "<expr>" [--source pipe0|explorium|linkedin] [--provider hubspot|salesforce] [--label <l>]
70
+ size the reachable market FROM the ICP (a real account count ×
71
+ ACV; buyers/account = the contact population target), then fill
72
+ it: populate schedules plan-only enrich acquire --save (apply
73
+ stays gated), status --save stamps a coverage timeline, report
74
+ projects a burn-up + ETA. estimate --source probes the provider's
75
+ ICP-match count (else --accounts), always labeled provider-vs-assumption
76
+ fullstackgtm enrich append [--source apollo] [--objects companies,contacts] [--save] [--config <path>] [source options]
77
+ fullstackgtm enrich refresh [--source apollo] [--stale-days <n>] [--save] [--config <path>] [source options]
78
+ fullstackgtm enrich ingest <file.csv|payload.json|spool.jsonl|spool-dir> --source clay [--run-label <label>]
79
+ fullstackgtm enrich status [--runs] [--source <id>] [--json]
80
+ governed enrichment: pull (Apollo) or stage (Clay) third-party
81
+ data, match it to CRM records deterministically, and emit a
82
+ fill-blanks-only patch plan through the normal dry-run →
83
+ approve → apply gate. refresh re-checks stale stamped fields
84
+ and proposes updates only where the source value changed.
85
+ fullstackgtm backfill stripe|runs [--since <iso>] [--pipeline <id|label>] [--match-property <name>] [--skip-unmatched] [source options] [--save] [--dry-run] [--json]
86
+ revenue truth backfill: one closed-won deal per PAID Stripe
87
+ invoice (amount = invoice total, close date = paid date,
88
+ associated to the matched customer account, deduped by
89
+ invoice id). Reads Stripe + the CRM snapshot, emits a
90
+ dry-run create_record plan through the normal approve →
91
+ apply gate; unmatched customers get a proposed NEW account
92
+ in the same plan (freemail domains never used;
93
+ --skip-unmatched = report-only). HubSpot-only for now.
94
+ fullstackgtm signals fetch [--bucket job,…] [--source greenhouse,lever,ashby] [--watchlist <path|crm:seg>] [--keywords …] [--from <file.json|spool.jsonl|spool-dir>] [--save]
95
+ fullstackgtm signals list [--since 7d] [--bucket b] [--account d] [--unjudged]
96
+ fullstackgtm signals outcome --account <d> [--touch <id>] --result replied|meeting|bounced|no_reply
97
+ fullstackgtm signals weights [--explain]
98
+ detect fresh buying triggers (ATS hiring scrapes + staged
99
+ funding/company/social ingest), rank them, persist a local
100
+ signal ledger. READ-ONLY re: CRM — fetch NEVER emits a plan;
101
+ --save persists only the signal ledger. outcome feeds the
102
+ learned per-bucket weights.
103
+ fullstackgtm icp interview | set <answers.json> | show
104
+ fullstackgtm icp judge [--signals-from latest|<label>] [--with-history] [--prompt <path>] [--min-score 0] [--save]
105
+ fullstackgtm icp eval [--golden <path>|default] [--against-outcomes] [--min-accuracy 0.8] [--json]
106
+ build the ICP, then judge fresh signals into
107
+ send/nurture/skip (timing × fit × memory). Deterministic
108
+ baseline; LLM why-now + play with a key (gated verbatim).
109
+ eval grades the judge and exits 2 below the bar — the
110
+ probabilistic-judgment gate. Read-only; --save writes only
111
+ the local judge store.
112
+ fullstackgtm draft [--from-judge latest|<label>] [--min-score 80] [--prompt <path>] [--channel email|linkedin|task] [--save]
113
+ author one trigger-grounded opener per hot judge decision as
114
+ a governed create_task plan. First line must contain a
115
+ verbatim span of the why-now or the draft is rejected. Never
116
+ sends — --save stages a needs_approval plan for approve → apply.
117
+ fullstackgtm bulk-update <account|contact|deal> --where <expr> [--where …] (--set <field>=<value> [--set …] | --archive [--force-archive-duplicates] | --create-task <text>) [--require <field>=<value> …] [--guard <object>:<where>[;<where>]:<none|some> …] [source options] [--save] [--json] [--out <path>]
118
+ governed generic writes: filter the snapshot
119
+ (field=value, field!=value, field~substr, field!~substr,
120
+ field:empty, field:notempty, '|' = any-of; canonical fields
121
+ like ownerId, stage, closeDate, amount; relational
122
+ pseudo-fields account.name/domain/ownerId/contactCount/
123
+ openDealStages on deals and contacts, contactCount/
124
+ openDealCount/openDealStages on accounts) into a dry-run
125
+ patch plan. The full filter is re-verified per record at
126
+ apply time (incl. mid-apply rechecks); equality filters
127
+ double as preconditions; per-record ops apply
128
+ all-or-nothing; guards assert cross-record conditions.
129
+ --set <field>=from:<sourceField> derives the value PER
130
+ RECORD from the snapshot (relational sources like
131
+ account.ownerId included); records whose source is empty
132
+ are skipped and counted, never guessed. --archive refuses
133
+ records that share their identity key (account domain /
134
+ contact email) with another record — merge those with
135
+ \`dedupe\` instead, or --force-archive-duplicates.
136
+ fullstackgtm dedupe <account|contact|deal> --key <domain|email|name> [--keep richest|oldest] [source options] [--reason <text>] [--max-operations <n>] [--save] [--json] [--out <path>]
137
+ find duplicate groups by normalized identity key and build
138
+ a dry-run plan of merge_records operations — one per group,
139
+ deterministic survivor (richest = most populated data
140
+ fields, ties to lowest id; oldest = lowest id). Approve and
141
+ apply like any plan; merges are IRREVERSIBLE on apply.
142
+ fullstackgtm reassign (--from <ownerId> | --assign-unowned) --to <ownerId> [--objects account,contact,deal] [--where <expr> …] [--except-deal-stage <stage>] [--include-closed-deals] [source options] [--save] [--json] [--out <path>]
143
+ ownership handoff playbook: one bulk-update-style plan per
144
+ object type (ownerId=<from> → <to>). --assign-unowned instead
145
+ claims every ownerless record (ownerId:empty) for --to. Extra --where scoping
146
+ is account-lifted for deals/contacts (domain~.de becomes
147
+ account.domain~.de); --except-deal-stage <stage> excludes
148
+ deals in that stage AND every record whose account has an
149
+ open deal in it, re-verified per record at apply time.
150
+ Deal plans cover open deals only unless
151
+ --include-closed-deals.
152
+ fullstackgtm fix --rule <ruleId> --provider <name> [--min-confidence high|low] [--include-creates] [--today <iso>] [--yes|--confirm] [--dry-run]
153
+ one-shot composite: audit ONE rule → save the plan →
154
+ suggest values → approve only suggestion-backed operations
155
+ meeting the confidence bar (plus operations that need no
156
+ value) → with --yes (alias: --confirm), apply through the
157
+ provider and print a stage-by-stage summary. Without --yes
158
+ it stops after approval and prints the apply command;
159
+ --dry-run locks that stop even if --yes is present.
160
+ fullstackgtm schedule add "<command>" --cron "<expr>" [--label <name>] [--provider local]
161
+ fullstackgtm schedule list | remove <id> | enable <id> | disable <id> | run <id>
162
+ fullstackgtm schedule install | uninstall [--provider local]
163
+ fullstackgtm schedule status [<id>] [--runs <n>]
164
+ declare a cadence for any read/plan-side command (audit,
165
+ snapshot, enrich append|refresh, market capture|refresh,
166
+ suggest, report, doctor); install materializes enabled
167
+ entries into a sentinel-delimited managed crontab block.
168
+ Scheduling never auto-approves: apply is schedulable only
169
+ as apply --plan-id <id>, re-checked approved at run time.
170
+ fullstackgtm suggest --plan-id <id> | --plan <path> [source options] [--json] [--out <path>]
171
+ derive values for requires_human_* placeholders
172
+ from snapshot evidence, with confidence + reasons
173
+ fullstackgtm plans list [--status <s>] | show <id> | reject <id>
174
+ fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]
175
+ fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low] [--include-creates]
176
+ fullstackgtm apply --plan-id <id> --provider <name>
177
+ fullstackgtm apply --plan-id <id> --channel outbox (render approved openers; transmits nothing)
178
+ fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [options]
179
+ fullstackgtm audit-log export [--out <path>] | verify --in <path> tamper-evident apply-run record
180
+ fullstackgtm rules [--json]
181
+ fullstackgtm profiles [--json] list credential profiles
182
+ fullstackgtm doctor [--json] check install, credentials, and next step
183
+ fullstackgtm capabilities [--json] machine-readable CLI contract for agents
184
+ fullstackgtm robot-docs print the shipped agent guide (SKILL.md)
185
+
186
+ Profiles (multi-organization use):
187
+ --profile <name> Scope credentials AND stored plans to a named profile
188
+ (also: FULLSTACKGTM_PROFILE). One profile per client
189
+ org keeps logins isolated and prevents a plan proposed
190
+ against one CRM from being applied through another's
191
+ credentials. Omitted = the default profile.
192
+
193
+ Plan lifecycle:
194
+ audit --save persists the dry-run plan to ~/.fullstackgtm/plans. Approve
195
+ specific operations (optionally with --value <opId>=<v> for placeholders),
196
+ then apply by id — the store enforces approval and records every run.
197
+
198
+ Authentication (checked in order):
199
+ 1. --token-env <name> explicit env var for this invocation (hubspot)
200
+ 2. ambient env HUBSPOT_ACCESS_TOKEN, or SALESFORCE_ACCESS_TOKEN +
201
+ SALESFORCE_INSTANCE_URL (CI, agent sandboxes)
202
+ 3. stored provider login BYO direct credentials kept in ~/.fullstackgtm
203
+ (hubspot: --private-token or loopback OAuth;
204
+ salesforce: pasted token or device flow). These
205
+ direct credentials override broker-hosted tokens.
206
+ 4. hosted/broker login fullstackgtm login hubspot|salesforce (or --hosted):
207
+ hosted browser OAuth stores a broker credential;
208
+ provider tokens are minted server-side. \`login --via
209
+ <url>\` pairs with an existing team deployment broker.
210
+
211
+ Source options (snapshot and audit):
212
+ --sample Built-in minimal mock CRM data (default)
213
+ --demo Realistic generated mid-market CRM with injected hygiene issues
214
+ --seed <n> Demo PRNG seed (default 7; same seed, same data)
215
+ --input <path> Canonical GTM snapshot JSON file
216
+ --provider <name> Live provider snapshot: hubspot | salesforce | stripe (read-only)
217
+ --token-env <name> Env var holding the provider access token
218
+
219
+ Audit options:
220
+ --config <path> Config file (default: ./fullstackgtm.config.json if present)
221
+ { "policy": {...}, "rules": {"enabled":[],"disabled":[]},
222
+ "rulePackages": ["./team-rules.mjs"] }
223
+ --rules <ids> Comma-separated rule ids to run (default: all; see \`rules\`)
224
+ --json Print the JSON patch plan instead of markdown
225
+ --out <path> Also write the JSON patch plan to a file
226
+ --today <date> Override today's date for deterministic audits
227
+ --stale-days <n> Days without activity before an open deal is stale
228
+ --fail-on <severity> Exit 2 if any finding is at or above info|warning|critical
229
+
230
+ Report options (report renders the audit as a client-ready deliverable):
231
+ --plan <path> Render an existing plan JSON instead of re-auditing
232
+ (add --input <snapshot.json> for record counts)
233
+ --client <name> Organization name shown in the heading and summary
234
+ --title <text> Report heading (default "GTM Data Health Report")
235
+ --prepared-by <name> Attribution shown in the footer
236
+ --format <fmt> markdown (default) or html (self-contained, printable;
237
+ inferred from an --out path ending in .html)
238
+ --max-examples <n> Example records listed per rule (default 10)
239
+ --out <path> Write the report to a file instead of stdout
240
+
241
+ Apply options:
242
+ --plan <path> Patch plan JSON produced by \`audit --out\`
243
+ --provider hubspot Connector to apply through
244
+ --token-env <name> Env var holding the provider access token
245
+ --approve <ids|all> Comma-separated operation ids to apply, or "all"
246
+ --value <opId>=<v> Concrete value for a requires_human_* placeholder (repeatable)
247
+ --json Print the JSON run record instead of markdown
248
+
249
+ Exit codes:
250
+ 0 success · 1 error · 2 audit findings at/above the --fail-on threshold
251
+
252
+ Safety:
253
+ Audits are read-only. Apply writes only operations you explicitly approve,
254
+ and never writes requires_human_* placeholders without a --value override.`;
255
+ }
256
+ // Exported as the CLI's single source of truth for the command inventory:
257
+ // `capabilities` and `--help --json` derive their command lists and
258
+ // read-only/write-shaped classification from this table (via each entry's
259
+ // `phase`) instead of maintaining parallel hand-written lists.
260
+ export const HELP = {
261
+ // Get started
262
+ init: {
263
+ summary: "scaffold a workspace (icp.json + enrich.config.json + PLAYBOOK)",
264
+ phase: "Setup",
265
+ synopsis: [
266
+ "fullstackgtm init [--source pipe0|explorium|linkedin] [--provider hubspot|salesforce] [--out <dir>] [--force]",
267
+ ],
268
+ detail: "Cold start: writes a starter ICP, an acquire-ready enrich.config.json (with a visible assign seam so leads are never ownerless), and a PLAYBOOK wired with the cold-start + outbound-loop recipes for this workspace. Pure file-writer — no network, keeps existing files unless --force. The CLI ships governed primitives; your coding agent is the orchestrator (see docs/recipes.md).",
269
+ seeAlso: ["icp", "enrich", "signals"],
270
+ },
271
+ // Setup & health
272
+ login: {
273
+ summary: "connect a provider or LLM key (secrets via stdin/env, never argv)",
274
+ phase: "Setup",
275
+ synopsis: [
276
+ "fullstackgtm login hubspot | salesforce hosted browser OAuth (default)",
277
+ "fullstackgtm login --via <hosted url> pair with a team deployment",
278
+ "fullstackgtm login stripe",
279
+ "fullstackgtm login anthropic | openai | apollo",
280
+ ],
281
+ detail: "HubSpot/Salesforce use hosted browser OAuth by default; BYO app/token paths are advanced. Secrets are NEVER passed as flags — pipe on stdin or enter at the prompt: `echo \"$TOKEN\" | fullstackgtm login hubspot --private-token`.",
282
+ seeAlso: ["doctor", "logout", "profiles"],
283
+ },
284
+ logout: {
285
+ summary: "remove stored credentials for a provider",
286
+ phase: "Setup",
287
+ synopsis: ["fullstackgtm logout <hubspot|salesforce|stripe|anthropic|openai|apollo|pipe0|explorium|broker>"],
288
+ seeAlso: ["login", "doctor"],
289
+ },
290
+ doctor: {
291
+ summary: "check install, credentials, and the next step",
292
+ phase: "Setup",
293
+ synopsis: ["fullstackgtm doctor [--json]"],
294
+ detail: "Verifies Node version, the credential store, MCP peers, and prints what to run next.",
295
+ seeAlso: ["login", "audit"],
296
+ },
297
+ capabilities: {
298
+ summary: "print the machine-readable CLI contract (JSON)",
299
+ phase: "Setup",
300
+ synopsis: ["fullstackgtm capabilities [--json]"],
301
+ detail: "The agent front door: the command inventory with read-only vs write-shaped access (derived from this help table), the exit-code contract, safety defaults, and the MCP entrypoint — as JSON. Always prints JSON; --json is accepted for symmetry.",
302
+ seeAlso: ["robot-docs", "doctor"],
303
+ },
304
+ "robot-docs": {
305
+ summary: "print the shipped agent operating guide (SKILL.md)",
306
+ phase: "Setup",
307
+ synopsis: ["fullstackgtm robot-docs"],
308
+ detail: "Prints the packaged skills/fullstackgtm/SKILL.md — the maintained agent guide (the governed loop, safety invariants, and verb map; the same content `npx skills add fullstackgtm/core` installs) — instead of a third parallel hand-written guide.",
309
+ seeAlso: ["capabilities", "doctor"],
310
+ },
311
+ profiles: {
312
+ summary: "list credential profiles (one per client org)",
313
+ phase: "Setup",
314
+ synopsis: ["fullstackgtm profiles [--json]"],
315
+ detail: "`--profile <name>` (or FULLSTACKGTM_PROFILE) scopes credentials AND stored plans per org, so a plan proposed against one CRM can't be applied through another's credentials.",
316
+ seeAlso: ["login", "plans", "health"],
317
+ },
318
+ health: {
319
+ summary: "CRM health score + trend for the active profile (read-only)",
320
+ phase: "Detect",
321
+ synopsis: ["fullstackgtm health [--json]"],
322
+ detail: "The engagement-workspace rollup. Each `audit --save` stamps a deterministic 0–100 hygiene score (100 / (1 + severity-weighted findings per record)) onto the profile's timeline; `health` reports the current score, the change since the last audit, and per-rule deltas — turning episodic audits into a continuous record. Scope per client with `--profile <name>`.",
323
+ seeAlso: ["audit", "profiles", "report"],
324
+ },
325
+ // Detect — read-only
326
+ snapshot: {
327
+ summary: "pull a canonical GTM snapshot (read-only)",
328
+ phase: "Detect",
329
+ synopsis: ["fullstackgtm snapshot [source options] [--since <iso>] [--out <path> | --archive <dir>]"],
330
+ detail: "Materializes the provider's records as canonical JSON — the input every audit and write verb reads.",
331
+ options: [
332
+ ["--provider <name>", "hubspot | salesforce | stripe (read-only)"],
333
+ ["--demo", "realistic generated CRM with injected hygiene issues"],
334
+ ["--out <path>", "write the snapshot JSON to a file"],
335
+ ],
336
+ seeAlso: ["audit", "diff"],
337
+ },
338
+ audit: {
339
+ summary: "read-only hygiene audit → reviewable dry-run patch plan",
340
+ phase: "Detect",
341
+ synopsis: ["fullstackgtm audit [source options] [audit options] [--save]"],
342
+ detail: "The Detect layer of the loop. Runs deterministic rules over a snapshot and proposes a typed patch plan — nothing is written. Prints a summary (rule table + counts) by default; `--full` shows every operation. `--save` persists the plan for the suggest → approve → apply spine. For a client-ready writeup use `report`; for machine output add `--json`.",
343
+ options: [
344
+ ["--provider <name>", "live snapshot: hubspot | salesforce | stripe"],
345
+ ["--input <path>", "load a canonical GTM snapshot JSON file (from `snapshot --out`)"],
346
+ ["--demo", "try it with zero credentials on a messy generated CRM"],
347
+ ["--full", "show every operation, not just the rule summary"],
348
+ ["--rules <ids>", "comma-separated rule ids (default: all; see `rules`)"],
349
+ ["--fail-on <sev>", "exit 2 if any finding ≥ info|warning|critical"],
350
+ ["--save", "persist the dry-run plan for approve → apply"],
351
+ ["--json / --out <p>", "machine-readable plan to stdout / file"],
352
+ ],
353
+ seeAlso: ["report", "suggest", "plans", "apply", "diff"],
354
+ },
355
+ report: {
356
+ summary: "render an audit as a client-ready deliverable (md/html)",
357
+ phase: "Detect",
358
+ synopsis: ["fullstackgtm report [source options] [audit options] [report options]"],
359
+ detail: "The human-readable counterpart to `audit` — a clean summary instead of the full per-operation dump.",
360
+ options: [
361
+ ["--plan <path>", "render an existing plan JSON instead of re-auditing"],
362
+ ["--client <name>", "organization name in the heading/summary"],
363
+ ["--format <fmt>", "markdown (default) or self-contained html"],
364
+ ["--out <path>", "write to a file (html inferred from .html)"],
365
+ ],
366
+ seeAlso: ["audit"],
367
+ },
368
+ diff: {
369
+ summary: "compare two snapshots/plans; gate on new findings",
370
+ phase: "Detect",
371
+ synopsis: ["fullstackgtm diff --before <a.json> --after <b.json> [--json] [--fail-on-new-findings]"],
372
+ detail: "The regression primitive. Exit 2 when a (rule, record) pair fires that didn't before — wire it into CI to catch CRM rot.",
373
+ seeAlso: ["audit", "snapshot", "merge"],
374
+ },
375
+ rules: {
376
+ summary: "list the audit rule registry",
377
+ phase: "Detect",
378
+ synopsis: ["fullstackgtm rules [--json]"],
379
+ seeAlso: ["audit"],
380
+ },
381
+ // Prevent — gate writes before they happen
382
+ resolve: {
383
+ summary: "the create gate: exit 0 = safe to create, 2 = match exists",
384
+ phase: "Prevent",
385
+ synopsis: ["fullstackgtm resolve <account|contact|deal> [--name N] [--domain D] [--email E] [source options] [--json]"],
386
+ detail: "Prevention, not cleanup. Call before ANY record creation — a sync job, webhook, agent, or script. Returns exists/ambiguous/safe_to_create with matches and reasons; exit 2 = do not create.",
387
+ seeAlso: ["dedupe", "audit"],
388
+ },
389
+ // Remediate — governed writes (all produce plans; nothing writes outside approve → apply)
390
+ fix: {
391
+ summary: "one-shot composite: audit one rule → suggest → approve → apply",
392
+ phase: "Remediate",
393
+ synopsis: ["fullstackgtm fix --rule <ruleId> --provider <name> [--min-confidence high|low] [--include-creates] [--yes|--confirm] [--dry-run]"],
394
+ detail: "The whole governed loop for a single rule in one command. Without `--yes` (alias: `--confirm`) it stops after approval and prints the apply command; with `--yes` it applies suggestion-backed operations meeting the confidence bar and prints a stage-by-stage summary. `--dry-run` locks the stop-before-apply path even when `--yes` is present.",
395
+ seeAlso: ["audit", "suggest", "plans", "apply"],
396
+ },
397
+ // bulk-update previously had NO entry here even though shortUsage()'s
398
+ // Remediate group listed it — the render loop silently skipped it, so the
399
+ // front door never showed the verb and `help bulk-update` fell back to the
400
+ // short map. The entry also completes the derived `capabilities` inventory.
401
+ "bulk-update": {
402
+ summary: "governed generic writes: filter the snapshot into a dry-run plan",
403
+ phase: "Remediate",
404
+ synopsis: [
405
+ "fullstackgtm bulk-update <account|contact|deal> --where <expr> (--set <field>=<value> | --archive | --create-task <text>) [--save] … (run `bulk-update --help` for full options)",
406
+ ],
407
+ detail: "Filters the snapshot (field=value, field~substr, field:empty, '|' = any-of; relational pseudo-fields like account.domain) into a dry-run patch plan. The full filter is re-verified per record at apply time, equality filters double as preconditions, and per-record ops apply all-or-nothing.",
408
+ seeAlso: ["dedupe", "reassign", "plans", "apply"],
409
+ },
410
+ dedupe: {
411
+ summary: "merge duplicate groups by identity key (irreversible on apply)",
412
+ phase: "Remediate",
413
+ synopsis: ["fullstackgtm dedupe <account|contact|deal> --key <domain|email|name> [--keep richest|oldest] [--save] [--json]"],
414
+ detail: "Builds a dry-run plan of one merge_records op per duplicate group with a deterministic survivor (richest = most populated fields; oldest = lowest id). Approve and apply like any plan; merges are IRREVERSIBLE on apply.",
415
+ seeAlso: ["resolve", "plans", "apply"],
416
+ },
417
+ reassign: {
418
+ summary: "ownership handoff: one plan per object type",
419
+ phase: "Remediate",
420
+ synopsis: ["fullstackgtm reassign (--from <ownerId> | --assign-unowned) --to <ownerId> [--objects account,contact,deal] [--where <expr> …] [--save] [--json]"],
421
+ detail: "Extra `--where` scoping is account-lifted for deals/contacts. `--except-deal-stage <stage>` excludes that stage and any record whose account has an open deal in it, re-verified per record at apply.",
422
+ seeAlso: ["bulk-update", "plans", "apply"],
423
+ },
424
+ backfill: {
425
+ summary: "Stripe paid invoices → closed-won deal proposals (governed)",
426
+ phase: "Remediate",
427
+ synopsis: [
428
+ "fullstackgtm backfill stripe|runs [--since <iso>] [--pipeline <id|label>] [--match-property <name>] [--skip-unmatched] [source options] [--save] [--dry-run] [--json]",
429
+ ],
430
+ detail: "`backfill runs` replays LOCAL execution history to the paired hosted app (idempotent: deterministic run ids + server-side dedupe): every stored plan's apply runs and the health.jsonl timeline, so the hosted feed and Home health trend cover the engagement from its CLI-only beginning. Requires `login --via`; only what live reporting sends leaves the machine (statuses, counts, timestamps, scores — never CRM field values). `backfill stripe`: billing is the revenue source of truth — proposes ONE closed-won deal per paid Stripe invoice (amount = invoice total, close date = paid date, associated to the matched customer account, deduped by invoice id). Customers are matched to CRM accounts by domain, then exact name; a customer the CRM doesn't know gets an explicit proposed ACCOUNT create in the same plan (billing-email domain used unless it is freemail; a customer with no usable name/domain stays report-only; `--skip-unmatched` restores report-only for all unmatched). Emits a dry-run create_record plan — nothing is written until `plans approve` → `apply`, where the connector re-resolves each invoice id (and each account by domain/name) and creates only on a confirmed miss. Deal creation is HubSpot-only for now.",
431
+ options: [
432
+ ["--since <iso>", "only invoices created on/after this timestamp"],
433
+ ["--pipeline <id|label>", "target deal pipeline (default: the portal's default pipeline)"],
434
+ ["--match-property <name>", "deal dedupe property stamped with the invoice id (default stripe_invoice_id)"],
435
+ ["--skip-unmatched", "do not propose accounts for unmatched customers (report-only, the pre-1.3 behavior)"],
436
+ ["--provider <name>", "CRM snapshot to match against: hubspot | salesforce"],
437
+ ["--input <path>", "match against a saved snapshot JSON instead of a live pull"],
438
+ ["--save", "persist the dry-run plan for approve → apply"],
439
+ ["--json", "machine-readable {plan, counts, unmatched, proposedAccounts} (stripe) or replay summary (runs)"],
440
+ ["--dry-run", "(runs) count what would be replayed without sending anything"],
441
+ ],
442
+ seeAlso: ["enrich", "plans", "apply", "resolve"],
443
+ },
444
+ enrich: {
445
+ summary: "governed third-party enrichment (Apollo/Clay), fill-blanks-only",
446
+ phase: "Remediate",
447
+ synopsis: ["fullstackgtm enrich append|refresh|ingest|status … (run `enrich --help` for full options)"],
448
+ detail: "Pull (Apollo) or stage (Clay) data, match it to CRM records deterministically, and emit a fill-blanks-only patch plan through the normal dry-run → approve → apply gate. `refresh` re-checks stale stamped fields.",
449
+ seeAlso: ["plans", "apply", "schedule"],
450
+ },
451
+ // Calls → evidence
452
+ call: {
453
+ summary: "transcripts → evidence, rubric scores, deal links, governed writes",
454
+ phase: "Remediate",
455
+ synopsis: ["fullstackgtm call parse|classify|score|link|plan … (run `call --help` for full options)"],
456
+ detail: "`parse` normalizes any transcript into canonical segments + evidence — chunked LLM extraction by default when a key resolves (ANTHROPIC_API_KEY/OPENAI_API_KEY or `login anthropic|openai`), free deterministic fallback when none does; `--heuristics` forces the deterministic engine, `--llm` forces the LLM path, model via `--model` or env FSGTM_INSIGHTS_MODEL. `classify` picks the call type, `score` rates it against the type's rubric, `link` finds the deal, `plan` proposes governed next-step writes.",
457
+ seeAlso: ["plans", "apply"],
458
+ },
459
+ // Govern — the plan/apply spine
460
+ suggest: {
461
+ summary: "derive values for requires_human_* placeholders (evidence-backed)",
462
+ phase: "Govern",
463
+ synopsis: ["fullstackgtm suggest --plan-id <id> | --plan <path> [source options] [--json] [--out <path>]"],
464
+ detail: "Read-only. Proposes concrete values with confidence + reasons from snapshot evidence; feed the output to `plans approve --values-from`. Never guesses — low/create/none-confidence entries are left to the human.",
465
+ seeAlso: ["audit", "plans", "apply"],
466
+ },
467
+ plans: {
468
+ summary: "plan lifecycle: list / show / approve / reject saved plans",
469
+ phase: "Govern",
470
+ synopsis: [
471
+ "fullstackgtm plans list [--status <s>] | show <id> | reject <id>",
472
+ "fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]",
473
+ "fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low]",
474
+ ],
475
+ detail: "Approval is explicit and per-operation; placeholders need a concrete --value or a suggestion to be approvable.",
476
+ seeAlso: ["audit", "suggest", "apply"],
477
+ },
478
+ apply: {
479
+ summary: "write ONLY explicitly approved operations to a provider",
480
+ phase: "Govern / Verify",
481
+ synopsis: [
482
+ "fullstackgtm apply --plan-id <id> --provider <name>",
483
+ "fullstackgtm apply --plan-id <id> --channel outbox (render approved drafted openers to the outbox; transmits nothing)",
484
+ "fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [--value <opId>=<v>]",
485
+ ],
486
+ detail: "The only verb that mutates a CRM. Writes only operations approved via `plans approve` or `--approve`, with compare-and-set and readback. Never writes requires_human_* placeholders without a --value override.",
487
+ seeAlso: ["plans", "suggest", "audit-log"],
488
+ },
489
+ "audit-log": {
490
+ summary: "tamper-evident record of apply runs (export / verify)",
491
+ phase: "Verify",
492
+ synopsis: ["fullstackgtm audit-log export [--out <path>] | verify --in <path>"],
493
+ detail: "The Verify/Attribute layer — a signed, append-only record of every applied write.",
494
+ seeAlso: ["apply"],
495
+ },
496
+ merge: {
497
+ summary: "merge multiple plan/snapshot JSONs into one",
498
+ phase: "Govern",
499
+ synopsis: ["fullstackgtm merge --input <a.json> --input <b.json> [...] --out <merged.json> [--json]"],
500
+ seeAlso: ["diff", "audit"],
501
+ },
502
+ // Continuous
503
+ schedule: {
504
+ summary: "declare a cadence for read/plan-side commands (never auto-approves)",
505
+ phase: "Continuous",
506
+ synopsis: ["fullstackgtm schedule add|list|remove|enable|disable|run|install|status … (run `schedule --help` for full options)"],
507
+ detail: "Makes the loop continuous instead of episodic. Read/plan-side allowlist only (audit, snapshot, enrich, market, suggest, report, doctor). Scheduling NEVER auto-approves: apply is schedulable only as `apply --plan-id <id>`, re-checked approved at run time.",
508
+ seeAlso: ["audit", "enrich", "apply"],
509
+ },
510
+ // Market intelligence
511
+ market: {
512
+ summary: "live competitive category map (capture → classify → drift → report)",
513
+ phase: "Intelligence",
514
+ synopsis: ["fullstackgtm market init|capture|classify|worksheet|observe|fronts|axes|overlay|scale|report|refresh … (run `market --help` for full options)"],
515
+ detail: "Capture vendor pages (content-addressed), classify intensity per claim (LLM bring-your-own-key, or fill the worksheet with any agent), then compute deterministic front states and drift. Every quoted span is verified verbatim against the stored capture before it's accepted.",
516
+ seeAlso: ["tam"],
517
+ },
518
+ tam: {
519
+ summary: "size the reachable market from your ICP, then populate + track coverage",
520
+ phase: "Intelligence",
521
+ synopsis: ["fullstackgtm tam estimate|status|report|populate … (run `tam --help` for full options)"],
522
+ detail: "Estimate a defensible TAM from the ICP: a real account count (Explorium company count or --accounts) × a confirmed ANNUAL ACV (explicit --acv, or --acv-from-crm --deal-period to derive+annualize from closed-won — no band defaults, a bare --provider does NOT set ACV, refuses without one); buyers/account (explicit or CRM-derived) gives the contact target. Then iteratively fill it with scheduled `enrich acquire --save` runs (plan-only; apply stays gated). `status --save` stamps a coverage timeline so `report` projects how long full coverage will take.",
523
+ seeAlso: ["icp", "enrich", "schedule", "market"],
524
+ },
525
+ // Outbound intelligence — signals → judge → draft (the GTM brain)
526
+ signals: {
527
+ summary: "detect fresh buying triggers (ATS hiring + source connectors), ranked",
528
+ phase: "Detect",
529
+ synopsis: [
530
+ "fullstackgtm signals fetch [--bucket job,…] [--source greenhouse,lever,ashby] [--connector file,serpapi-news,hubspot-forms] [--connector-opt k=v] [--watchlist <path|crm:seg>] [--keywords …] [--from <file.json|spool.jsonl|spool-dir>] [--save]",
531
+ "fullstackgtm signals list [--since 7d] [--bucket b] [--account d] [--unjudged]",
532
+ "fullstackgtm signals outcome --account <d> [--touch <id>] --result replied|meeting|bounced|no_reply",
533
+ "fullstackgtm signals weights [--explain]",
534
+ ],
535
+ detail: "Read-only re: CRM — `fetch` NEVER emits a patch plan; `--save` persists only the local signal ledger. ATS adapters are no-auth; source connectors (`--connector`) pull from connected platforms with secrets via the credential ladder, never argv. `outcome` feeds the learned per-bucket `weights`.",
536
+ seeAlso: ["icp", "draft"],
537
+ },
538
+ icp: {
539
+ summary: "build the ICP, then judge fresh signals into send/nurture/skip",
540
+ phase: "Detect",
541
+ synopsis: [
542
+ "fullstackgtm icp interview | set <answers.json> | show",
543
+ "fullstackgtm icp judge [--signals-from latest|<label>] [--with-history] [--prompt <path>] [--min-score 0] [--save]",
544
+ "fullstackgtm icp eval [--golden <path>|default] [--against-outcomes] [--min-accuracy 0.8] [--json]",
545
+ ],
546
+ detail: "`judge` ranks unjudged signals by timing × fit × memory (deterministic baseline; LLM why-now + play with a key, gated verbatim). `eval` is the probabilistic-judgment gate — exits 2 below the bar. Read-only; --save writes only the local judge store.",
547
+ seeAlso: ["signals", "draft", "enrich"],
548
+ },
549
+ draft: {
550
+ summary: "author one trigger-grounded opener per hot decision as a governed plan",
551
+ phase: "Remediate",
552
+ synopsis: [
553
+ "fullstackgtm draft [--from-judge latest|<label>] [--min-score 80] [--prompt <path>] [--channel email|linkedin|task] [--save]",
554
+ ],
555
+ detail: "Takes hot judge decisions (send, score>=min) and stages ONE create_task opener each. The first line must contain a verbatim span of the why-now or the draft is rejected. Never sends — --save stages a needs_approval plan for `plans approve` -> `apply`.",
556
+ seeAlso: ["icp", "plans", "apply"],
557
+ },
558
+ };
559
+ // Verbs that print their own richer multi-subcommand help; runCli routes their
560
+ // `--help` to themselves, so commandHelp() only renders these via `help <verb>`.
561
+ export const BESPOKE_HELP = ["init", "call", "market", "tam", "enrich", "bulk-update", "schedule", "signals", "icp", "draft"];
562
+ // Lifecycle-grouped front door. One line per verb, organized by the
563
+ // Prevent→Detect→Remediate→Verify loop so a new user sees ~6 jobs, not 22 verbs.
564
+ export function shortUsage() {
565
+ const groups = [
566
+ ["Get started", ["init"]],
567
+ ["Setup & health", ["login", "logout", "doctor", "capabilities", "robot-docs", "profiles", "health"]],
568
+ ["Detect — read-only", ["audit", "report", "snapshot", "diff", "rules"]],
569
+ ["Prevent — gate writes", ["resolve"]],
570
+ ["Remediate — governed writes", ["fix", "bulk-update", "dedupe", "reassign", "enrich", "backfill"]],
571
+ ["Calls → evidence", ["call"]],
572
+ ["Govern — the plan/apply spine", ["suggest", "plans", "apply", "audit-log", "merge"]],
573
+ ["Market intelligence", ["market", "tam"]],
574
+ ["Outbound — signals → judge → draft", ["signals", "icp", "draft"]],
575
+ ["Schedule — make it continuous", ["schedule"]],
576
+ ];
577
+ const pad = Math.max(...Object.keys(HELP).map((k) => k.length)) + 2;
578
+ const lines = [
579
+ "FullStackGTM — plan/apply for your GTM stack.",
580
+ "Audit CRM data, propose reviewable patch plans, apply only what you approve.",
581
+ "",
582
+ "Usage: fullstackgtm <command> [options]",
583
+ "",
584
+ ];
585
+ for (const [title, cmds] of groups) {
586
+ lines.push(`${title}:`);
587
+ for (const cmd of cmds) {
588
+ const entry = HELP[cmd];
589
+ if (!entry)
590
+ continue;
591
+ lines.push(` ${cmd.padEnd(pad)}${entry.summary}`);
592
+ }
593
+ lines.push("");
594
+ }
595
+ lines.push("Zoom in: fullstackgtm <command> --help focused help for one command", "Full ref: fullstackgtm help --full every flag and option", "Try it: fullstackgtm audit --demo zero-credential demo CRM", "", "Safety: audits are read-only; apply writes only operations you approve.");
596
+ return lines.join("\n");
597
+ }
598
+ /**
599
+ * Interactive-terminal styling for the short front door: a dimmed one-line
600
+ * brand header (name · version · active profile), bold title and command
601
+ * names, cyan lifecycle-group titles. Never applied to piped output — the
602
+ * plain shortUsage() text stays byte-identical (and `capabilities` keeps
603
+ * deriving from the HELP table, which this never touches).
604
+ */
605
+ export function stylizeShortUsage(text, p, info) {
606
+ if (!p.enabled)
607
+ return text;
608
+ const lines = text.split("\n").map((line, index) => {
609
+ if (index === 0)
610
+ return p.bold(line);
611
+ if (/^[A-Z][^:]*:$/.test(line))
612
+ return p.cyan(line);
613
+ const row = line.match(/^ (\S+)(\s{2,})(.*)$/);
614
+ if (row)
615
+ return ` ${p.bold(row[1])}${row[2]}${row[3]}`;
616
+ if (line.startsWith("Safety:"))
617
+ return p.dim(line);
618
+ return line;
619
+ });
620
+ return [p.dim(`fullstackgtm ${info.version} · profile ${info.profile}`), "", ...lines].join("\n");
621
+ }
622
+ // Focused help for a single command. Falls back to shortUsage() for anything
623
+ // unknown so `--help` never dead-ends on the full wall.
624
+ export function commandHelp(command) {
625
+ const entry = HELP[command];
626
+ if (!entry)
627
+ return shortUsage();
628
+ const lines = [`fullstackgtm ${command} — ${entry.summary}`, "", "Usage:"];
629
+ for (const s of entry.synopsis)
630
+ lines.push(` ${s}`);
631
+ if (entry.detail)
632
+ lines.push("", entry.detail);
633
+ if (entry.options?.length) {
634
+ lines.push("", "Options:");
635
+ const pad = Math.max(...entry.options.map(([f]) => f.length)) + 2;
636
+ for (const [flag, desc] of entry.options)
637
+ lines.push(` ${flag.padEnd(pad)}${desc}`);
638
+ }
639
+ lines.push("", `Lifecycle phase: ${entry.phase}`);
640
+ if (entry.seeAlso?.length)
641
+ lines.push(`See also: ${entry.seeAlso.join(", ")}`);
642
+ if (BESPOKE_HELP.includes(command))
643
+ lines.push("", `Run \`fullstackgtm ${command} --help\` for the full subcommand reference.`);
644
+ lines.push("", "Full reference: fullstackgtm help --full");
645
+ return lines.join("\n");
646
+ }