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