fullstackgtm 0.43.0 → 0.45.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 (130) hide show
  1. package/CHANGELOG.md +378 -1
  2. package/README.md +37 -14
  3. package/dist/audit.d.ts +3 -1
  4. package/dist/audit.js +29 -2
  5. package/dist/cli/audit.d.ts +15 -0
  6. package/dist/cli/audit.js +392 -0
  7. package/dist/cli/auth.d.ts +80 -0
  8. package/dist/cli/auth.js +500 -0
  9. package/dist/cli/call.d.ts +1 -0
  10. package/dist/cli/call.js +373 -0
  11. package/dist/cli/capabilities.d.ts +30 -0
  12. package/dist/cli/capabilities.js +197 -0
  13. package/dist/cli/draft.d.ts +7 -0
  14. package/dist/cli/draft.js +87 -0
  15. package/dist/cli/enrich.d.ts +8 -0
  16. package/dist/cli/enrich.js +788 -0
  17. package/dist/cli/fix.d.ts +33 -0
  18. package/dist/cli/fix.js +344 -0
  19. package/dist/cli/help.d.ts +25 -0
  20. package/dist/cli/help.js +609 -0
  21. package/dist/cli/icp.d.ts +7 -0
  22. package/dist/cli/icp.js +229 -0
  23. package/dist/cli/init.d.ts +7 -0
  24. package/dist/cli/init.js +58 -0
  25. package/dist/cli/market.d.ts +1 -0
  26. package/dist/cli/market.js +391 -0
  27. package/dist/cli/plans.d.ts +5 -0
  28. package/dist/cli/plans.js +454 -0
  29. package/dist/cli/schedule.d.ts +8 -0
  30. package/dist/cli/schedule.js +479 -0
  31. package/dist/cli/shared.d.ts +70 -0
  32. package/dist/cli/shared.js +331 -0
  33. package/dist/cli/signals.d.ts +7 -0
  34. package/dist/cli/signals.js +412 -0
  35. package/dist/cli/suggest.d.ts +49 -0
  36. package/dist/cli/suggest.js +135 -0
  37. package/dist/cli/tam.d.ts +9 -0
  38. package/dist/cli/tam.js +387 -0
  39. package/dist/cli/ui.d.ts +121 -0
  40. package/dist/cli/ui.js +375 -0
  41. package/dist/cli.d.ts +1 -57
  42. package/dist/cli.js +104 -4556
  43. package/dist/connector.d.ts +15 -0
  44. package/dist/connector.js +35 -0
  45. package/dist/connectors/hubspot.d.ts +3 -1
  46. package/dist/connectors/hubspot.js +10 -3
  47. package/dist/connectors/outboxChannel.d.ts +64 -0
  48. package/dist/connectors/outboxChannel.js +170 -0
  49. package/dist/connectors/prospectSources.d.ts +22 -0
  50. package/dist/connectors/prospectSources.js +43 -0
  51. package/dist/connectors/salesforce.d.ts +3 -1
  52. package/dist/connectors/salesforce.js +12 -5
  53. package/dist/connectors/signalSources.d.ts +105 -0
  54. package/dist/connectors/signalSources.js +264 -0
  55. package/dist/connectors/theirstack.d.ts +84 -0
  56. package/dist/connectors/theirstack.js +125 -0
  57. package/dist/icp.d.ts +52 -4
  58. package/dist/icp.js +112 -35
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.js +1 -0
  61. package/dist/init.js +3 -0
  62. package/dist/judge.d.ts +2 -0
  63. package/dist/judge.js +6 -0
  64. package/dist/judgeEval.d.ts +7 -0
  65. package/dist/judgeEval.js +8 -1
  66. package/dist/marketClassify.d.ts +2 -0
  67. package/dist/marketClassify.js +7 -1
  68. package/dist/mcp-bin.js +2 -2
  69. package/dist/mcp.js +259 -166
  70. package/dist/runReport.d.ts +26 -0
  71. package/dist/runReport.js +15 -0
  72. package/dist/schedule.d.ts +80 -2
  73. package/dist/schedule.js +272 -5
  74. package/dist/signals.d.ts +54 -0
  75. package/dist/signals.js +64 -0
  76. package/dist/spoolFiles.d.ts +44 -0
  77. package/dist/spoolFiles.js +114 -0
  78. package/dist/tam.d.ts +225 -0
  79. package/dist/tam.js +470 -0
  80. package/dist/types.d.ts +11 -0
  81. package/docs/api.md +91 -11
  82. package/docs/outbox-format.md +92 -0
  83. package/docs/recipes.md +37 -0
  84. package/docs/roadmap-to-1.0.md +31 -1
  85. package/docs/signal-spool-format.md +175 -0
  86. package/docs/tam.md +195 -0
  87. package/llms.txt +83 -11
  88. package/package.json +1 -1
  89. package/skills/fullstackgtm/SKILL.md +4 -3
  90. package/src/audit.ts +27 -1
  91. package/src/cli/audit.ts +447 -0
  92. package/src/cli/auth.ts +549 -0
  93. package/src/cli/call.ts +398 -0
  94. package/src/cli/capabilities.ts +215 -0
  95. package/src/cli/draft.ts +101 -0
  96. package/src/cli/enrich.ts +885 -0
  97. package/src/cli/fix.ts +372 -0
  98. package/src/cli/help.ts +664 -0
  99. package/src/cli/icp.ts +265 -0
  100. package/src/cli/init.ts +65 -0
  101. package/src/cli/market.ts +423 -0
  102. package/src/cli/plans.ts +523 -0
  103. package/src/cli/schedule.ts +526 -0
  104. package/src/cli/shared.ts +375 -0
  105. package/src/cli/signals.ts +434 -0
  106. package/src/cli/suggest.ts +151 -0
  107. package/src/cli/tam.ts +435 -0
  108. package/src/cli/ui.ts +426 -0
  109. package/src/cli.ts +103 -5170
  110. package/src/connector.ts +46 -0
  111. package/src/connectors/hubspot.ts +14 -2
  112. package/src/connectors/outboxChannel.ts +202 -0
  113. package/src/connectors/prospectSources.ts +57 -0
  114. package/src/connectors/salesforce.ts +18 -2
  115. package/src/connectors/signalSources.ts +314 -0
  116. package/src/connectors/theirstack.ts +170 -0
  117. package/src/icp.ts +120 -34
  118. package/src/index.ts +32 -0
  119. package/src/init.ts +3 -0
  120. package/src/judge.ts +7 -0
  121. package/src/judgeEval.ts +8 -1
  122. package/src/marketClassify.ts +8 -1
  123. package/src/mcp-bin.ts +2 -2
  124. package/src/mcp.ts +130 -57
  125. package/src/runReport.ts +39 -0
  126. package/src/schedule.ts +330 -7
  127. package/src/signals.ts +90 -0
  128. package/src/spoolFiles.ts +116 -0
  129. package/src/tam.ts +654 -0
  130. package/src/types.ts +12 -0
package/dist/icp.js CHANGED
@@ -55,46 +55,128 @@ export function icpToExploriumFilters(icp) {
55
55
  return f;
56
56
  }
57
57
  /**
58
- * Crustdata / LinkedIn seniority vocab. ICP job levels are normalized lowercase;
59
- * Crustdata expects these exact capitalized strings (verified: "CXO",
60
- * "Vice President", "Director" appear in Crustdata's docs/examples).
58
+ * Explorium /v1/businesses filters from the ICP the COMPANY (account) side, for
59
+ * sizing the account universe (TAM). Firmographics only, no persona: the count is
60
+ * of matching companies. Field names differ from /v1/prospects (verified live):
61
+ * `country_code` (not company_country_code), `company_size` (same employee bands),
62
+ * `naics_category`. `/v1/businesses` total_results is a real count, capped at
63
+ * 60,000 (see EXPLORIUM_BUSINESS_COUNT_CAP in the connector).
61
64
  */
62
- const CRUSTDATA_SENIORITY = {
63
- cxo: "CXO",
64
- "c-suite": "CXO",
65
- c_suite: "CXO",
66
- founder: "Owner",
67
- owner: "Owner",
68
- partner: "Partner",
69
- vp: "Vice President",
70
- "vice president": "Vice President",
71
- head: "Director",
72
- director: "Director",
73
- manager: "Manager",
74
- senior: "Senior",
75
- entry: "Entry",
76
- };
65
+ export function icpToExploriumBusinessFilters(icp) {
66
+ const f = {};
67
+ if (icp.firmographics.geos?.length)
68
+ f.country_code = { values: icp.firmographics.geos };
69
+ if (icp.firmographics.employeeBands?.length)
70
+ f.company_size = { values: icp.firmographics.employeeBands };
71
+ if (icp.firmographics.naics?.length)
72
+ f.naics_category = { values: icp.firmographics.naics };
73
+ return f;
74
+ }
75
+ /**
76
+ * Collapse provider-agnostic employee bands ("51-200","10001+") into a single
77
+ * {min,max} envelope for APIs that take integer bounds (TheirStack). An open
78
+ * top band ("10001+") leaves max undefined.
79
+ */
80
+ export function employeeBandsToRange(bands) {
81
+ if (!bands?.length)
82
+ return {};
83
+ let min;
84
+ let max;
85
+ let openTop = false;
86
+ for (const band of bands) {
87
+ const plus = /^(\d+)\+$/.exec(band.trim());
88
+ if (plus) {
89
+ const lo = Number(plus[1]);
90
+ if (min === undefined || lo < min)
91
+ min = lo;
92
+ openTop = true;
93
+ continue;
94
+ }
95
+ const range = /^(\d+)\s*-\s*(\d+)$/.exec(band.trim());
96
+ if (range) {
97
+ const lo = Number(range[1]);
98
+ const hi = Number(range[2]);
99
+ if (min === undefined || lo < min)
100
+ min = lo;
101
+ if (max === undefined || hi > max)
102
+ max = hi;
103
+ }
104
+ }
105
+ return { min, max: openTop ? undefined : max };
106
+ }
77
107
  /**
78
- * ICP industry keyword LinkedIn industry names Crustdata filters on. Includes
79
- * the current LinkedIn-v2 names; for software we send the cluster (dev + IT
80
- * services + internet) so an OR match isn't overly narrow.
108
+ * TheirStack company-search filter from the ICP: the technographic targeting that
109
+ * Explorium can't do. `company_technology_slug_or` is the CRM/MAP buying signal
110
+ * (firmographics.technologies); employee bands become min/max bounds; geos become
111
+ * ISO2 codes (uppercased).
112
+ */
113
+ export function icpToTheirStackFilters(icp) {
114
+ const f = {};
115
+ if (icp.firmographics.technologies?.length) {
116
+ f.company_technology_slug_or = icp.firmographics.technologies.map((t) => t.trim().toLowerCase());
117
+ }
118
+ if (icp.firmographics.geos?.length) {
119
+ f.company_country_code_or = icp.firmographics.geos.map((g) => g.trim().toUpperCase());
120
+ }
121
+ const range = employeeBandsToRange(icp.firmographics.employeeBands);
122
+ if (range.min !== undefined)
123
+ f.min_employee_count = range.min;
124
+ if (range.max !== undefined)
125
+ f.max_employee_count = range.max;
126
+ return f;
127
+ }
128
+ // NOTE: a CRUSTDATA_SENIORITY vocab table lived here until 2026-07-02 — the
129
+ // `current_seniority_levels` filter is broken upstream (see the live findings
130
+ // on icpToCrustdataFilters below), so job levels are no longer sent to the
131
+ // provider at all. Resurrect the table from git history if pipe0 fixes it.
132
+ /**
133
+ * ICP industry keyword → LinkedIn industry names Crustdata filters on.
134
+ *
135
+ * LinkedIn renamed its industry taxonomy (v1 → v2, ~2022), and data vendors
136
+ * normalize to one generation or the other. Sending ONLY the v2 names risks a
137
+ * zero-match when Crustdata stores v1 (and vice-versa) — observed live: a RevOps
138
+ * ICP whose only firmographic constraint was the v2 names returned 0 even though
139
+ * the title-only search returned plenty. So each cluster sends BOTH generations
140
+ * (OR-matched within the field): v2 ("Software Development", "IT Services and IT
141
+ * Consulting", "Technology, Information and Internet") AND v1 ("Computer
142
+ * Software", "Information Technology & Services", "Internet").
81
143
  */
82
144
  const CRUSTDATA_INDUSTRY = {
83
- software: ["Software Development", "Information Technology and Services", "Internet"],
84
- saas: ["Software Development", "Information Technology and Services"],
85
- "information technology & services": ["Information Technology and Services"],
86
- "information technology and services": ["Information Technology and Services"],
87
- internet: ["Internet"],
145
+ software: [
146
+ "Software Development",
147
+ "Computer Software",
148
+ "IT Services and IT Consulting",
149
+ "Information Technology & Services",
150
+ "Information Technology and Services",
151
+ "Technology, Information and Internet",
152
+ "Internet",
153
+ ],
154
+ saas: ["Software Development", "Computer Software", "IT Services and IT Consulting", "Information Technology & Services"],
155
+ "information technology & services": ["Information Technology & Services", "IT Services and IT Consulting"],
156
+ "information technology and services": ["Information Technology & Services", "IT Services and IT Consulting"],
157
+ internet: ["Internet", "Technology, Information and Internet"],
88
158
  fintech: ["Financial Services"],
89
159
  "financial services": ["Financial Services"],
90
160
  };
91
161
  /**
92
162
  * pipe0 Crustdata people-search config.filters from the ICP. `current_job_titles`
93
163
  * matches real LinkedIn title strings (case-sensitive), so keywords are Title
94
- * Cased. Seniority + industry are mapped to Crustdata's controlled vocab via the
95
- * tables above. NOTE: the exact pipe0→Crustdata value set could not be re-run
96
- * live (pipe0 credits were exhausted) validate when credits refill; fit
97
- * scoring is the safety net for persona precision regardless.
164
+ * Cased. Industry is mapped to Crustdata's controlled vocab via the table above.
165
+ *
166
+ * LIVE FINDINGS (2026-06-26, re-confirmed 2026-07-02 with fresh credits):
167
+ * `current_job_titles` works; `current_title` is rejected (422). The culprit
168
+ * that zeroed the full ICP filter is `current_seniority_levels`: ANY non-empty
169
+ * `include` returns 0 results through pipe0's people:profiles:crustdata@1 —
170
+ * including Crustdata's own documented values ("CXO", "Director",
171
+ * "Vice President"), lowercase, and SNAKE_CASE variants — while the identical
172
+ * search without it returns results. (An array instead of the
173
+ * {include, exclude} object is a 422, so the shape was right; the filter is
174
+ * broken upstream.) So job levels are NOT sent to the provider: persona
175
+ * seniority is enforced by fit scoring (scoreProspectAgainstIcp weights
176
+ * jobLevels), which was always the precision backstop. `locations` and
177
+ * `current_employers_linkedin_industries` (both-taxonomy hedge) are live-
178
+ * verified working in combination with titles. Note fit-scoring backs up
179
+ * PERSONA precision but NOT industry, so the industry filter is load-bearing.
98
180
  */
99
181
  export function icpToCrustdataFilters(icp) {
100
182
  const f = {};
@@ -103,11 +185,6 @@ export function icpToCrustdataFilters(icp) {
103
185
  if (icp.firmographics.geos?.length) {
104
186
  f.locations = icp.firmographics.geos.map((g) => COUNTRY_NAMES[g.toLowerCase()] ?? g);
105
187
  }
106
- if (icp.persona.jobLevels?.length) {
107
- const include = [...new Set(icp.persona.jobLevels.map((l) => CRUSTDATA_SENIORITY[l.toLowerCase()]).filter(Boolean))];
108
- if (include.length)
109
- f.current_seniority_levels = { include, exclude: [] };
110
- }
111
188
  if (icp.firmographics.industries?.length) {
112
189
  const inds = [
113
190
  ...new Set(icp.firmographics.industries.flatMap((i) => CRUSTDATA_INDUSTRY[i.toLowerCase()] ?? [titleCase(i)])),
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export { activeProfile, credentialsDir, credentialsPath, DEFAULT_PROFILE, delete
14
14
  export { generateDemoSnapshot, type DemoSnapshotOptions } from "./demo.ts";
15
15
  export { buildEnrichPlan, createFileEnrichRunStore, DEFAULT_STALE_DAYS, ENRICH_CONFIG_FILE_NAME, enrichRunId, inferIngestObjectType, ingestKeyValue, latestStamps, loadEnrichConfig, matchSourceRecord, parseCsv, parseEnrichConfig, resolveCrmField, selectStaleWork, sourceValueAt, stagedSourceRecords, staleDaysFor, type BuildEnrichPlanOptions, type EnrichAmbiguity, type EnrichConfig, type EnrichCounts, type EnrichFieldConfig, type EnrichMatchConfig, type EnrichMode, type EnrichObjectType, type EnrichPlanResult, type EnrichRun, type EnrichRunStore, type EnrichSourceConfig, type EnrichSourceRecord, type EnrichStamp, type EnrichWorkItem, type MatchOutcome, } from "./enrich.ts";
16
16
  export { scaffoldWorkspace, starterEnrichConfig, starterIcp, starterPlaybook, type InitProvider, type InitSource, type ScaffoldFile, type ScaffoldOptions, } from "./init.ts";
17
+ export { appendCoverage, classifyAccount, classifyCoverage, computeCoverage, coverageCountsFromSnapshot, coveredAccounts, coverageToText, crmCheckableCriteria, deriveAcvFromClosedWon, deriveBuyersPerAccount, estimateTam, loadTamModel, projectEta, readCoverageTimeline, saveTamModel, tamDir, tamReportToMarkdown, type AccountTamClass, type AcvBasis, type DerivedAcv, type DerivedBuyers, type EstimateTamInput, type TamClassified, type TamCoverage, type TamCoverageCounts, type TamCrossCheck, type TamEta, type TamModel, type TamTargeting, type TamUniverse, } from "./tam.ts";
17
18
  export { apolloPullKeysForAppend, apolloPullKeysForRefresh, createApolloClient, pullApolloRecords, type ApolloClient, type ApolloClientOptions, type ApolloPullKey, type ApolloPullResult, } from "./enrichApollo.ts";
18
19
  export { diffFindings, diffSnapshots, diffToMarkdown, type CollectionDiff, type FieldChange, type FindingsDrift, type RecordChange, type SnapshotDiff, } from "./diff.ts";
19
20
  export { mergeSnapshots, type MergeConflict, type MergeMatch, type MergeReport, type MergeSuggestion, } from "./merge.ts";
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export { activeProfile, credentialsDir, credentialsPath, DEFAULT_PROFILE, delete
14
14
  export { generateDemoSnapshot } from "./demo.js";
15
15
  export { buildEnrichPlan, createFileEnrichRunStore, DEFAULT_STALE_DAYS, ENRICH_CONFIG_FILE_NAME, enrichRunId, inferIngestObjectType, ingestKeyValue, latestStamps, loadEnrichConfig, matchSourceRecord, parseCsv, parseEnrichConfig, resolveCrmField, selectStaleWork, sourceValueAt, stagedSourceRecords, staleDaysFor, } from "./enrich.js";
16
16
  export { scaffoldWorkspace, starterEnrichConfig, starterIcp, starterPlaybook, } from "./init.js";
17
+ export { appendCoverage, classifyAccount, classifyCoverage, computeCoverage, coverageCountsFromSnapshot, coveredAccounts, coverageToText, crmCheckableCriteria, deriveAcvFromClosedWon, deriveBuyersPerAccount, estimateTam, loadTamModel, projectEta, readCoverageTimeline, saveTamModel, tamDir, tamReportToMarkdown, } from "./tam.js";
17
18
  export { apolloPullKeysForAppend, apolloPullKeysForRefresh, createApolloClient, pullApolloRecords, } from "./enrichApollo.js";
18
19
  export { diffFindings, diffSnapshots, diffToMarkdown, } from "./diff.js";
19
20
  export { mergeSnapshots, } from "./merge.js";
package/dist/init.js CHANGED
@@ -28,6 +28,9 @@ export function starterIcp() {
28
28
  industries: ["software", "saas"],
29
29
  employeeBands: ["51-200", "201-500", "501-1000"],
30
30
  geos: ["us"],
31
+ // Technographic targeting (the real RevOps signal): companies that USE a
32
+ // CRM/MAP. Drives `tam estimate|accounts --source theirstack`.
33
+ technologies: ["salesforce", "hubspot", "pipedrive"],
31
34
  },
32
35
  persona: {
33
36
  jobLevels: ["vp", "director", "manager"],
package/dist/judge.d.ts CHANGED
@@ -230,6 +230,8 @@ export declare function judgeSignals(opts: {
230
230
  promptTemplate?: string;
231
231
  llm?: LlmCallOptions;
232
232
  now?: Date;
233
+ /** Per-account progress (presentation only — a throwing callback never fails the run). */
234
+ onAccount?: (done: number, total: number, domain: string) => void;
233
235
  }): Promise<JudgeDecision[]>;
234
236
  export declare function judgeDir(baseDir?: string): string;
235
237
  export interface JudgeStore {
package/dist/judge.js CHANGED
@@ -438,6 +438,12 @@ export async function judgeSignals(opts) {
438
438
  }
439
439
  const decisions = [];
440
440
  for (const [domain, signals] of byAccount) {
441
+ try {
442
+ opts.onAccount?.(decisions.length, byAccount.size, domain);
443
+ }
444
+ catch {
445
+ // progress is presentation-only
446
+ }
441
447
  // Memory + fit stay gated on --with-history (scoring semantics unchanged).
442
448
  const recentlyTouched = opts.withHistory && opts.snapshot ? accountRecentlyTouched(domain, opts.snapshot, now) : false;
443
449
  const bestContact = opts.withHistory && opts.icp && opts.snapshot ? bestContactForAccount(domain, opts.snapshot) : undefined;
@@ -110,6 +110,13 @@ export declare function defaultJudgeFn(opts?: {
110
110
  icp?: Icp;
111
111
  now?: Date;
112
112
  }): EvalJudgeFn;
113
+ /**
114
+ * The default golden set's clock. Every `firstSeen` below is relative to this
115
+ * instant, so grading MUST pin `now: new Date(DEFAULT_GOLDEN_NOW_ISO)` —
116
+ * grading against wall time lets freshness decay rot the "fresh → send" rows,
117
+ * and the gate starts failing on a calendar date instead of a code change.
118
+ */
119
+ export declare const DEFAULT_GOLDEN_NOW_ISO = "2026-06-23T12:00:00.000Z";
113
120
  /**
114
121
  * A starter labeled set covering the four rubric corners, so `icp eval --golden
115
122
  * default` runs offline and the deterministic baseline passes it (>= the default
package/dist/judgeEval.js CHANGED
@@ -85,7 +85,14 @@ export function defaultJudgeFn(opts = {}) {
85
85
  }
86
86
  // ---------------------------------------------------------------------------
87
87
  // The default golden set (ships inline so `icp eval --golden default` is free)
88
- const GOLD_NOW_ISO = "2026-06-23T12:00:00.000Z";
88
+ /**
89
+ * The default golden set's clock. Every `firstSeen` below is relative to this
90
+ * instant, so grading MUST pin `now: new Date(DEFAULT_GOLDEN_NOW_ISO)` —
91
+ * grading against wall time lets freshness decay rot the "fresh → send" rows,
92
+ * and the gate starts failing on a calendar date instead of a code change.
93
+ */
94
+ export const DEFAULT_GOLDEN_NOW_ISO = "2026-06-23T12:00:00.000Z";
95
+ const GOLD_NOW_ISO = DEFAULT_GOLDEN_NOW_ISO;
89
96
  function goldSignal(over) {
90
97
  const domain = normalizeAccountDomain(over.accountDomain);
91
98
  return {
@@ -11,6 +11,8 @@ export type ClassifyMarketOptions = {
11
11
  /** Captures directory override (tests); defaults to the profile market home. */
12
12
  capturesDir?: string;
13
13
  now?: () => Date;
14
+ /** Per-vendor progress (presentation only — a throwing callback never fails the run). */
15
+ onVendor?: (done: number, total: number, vendorId: string) => void;
14
16
  };
15
17
  export type ClassifyMarketResult = {
16
18
  set: ObservationSet;
@@ -92,7 +92,13 @@ export async function classifyMarket(config, options) {
92
92
  const claimIds = config.claims.map((claim) => claim.id);
93
93
  const observations = [];
94
94
  const retriedVendorIds = [];
95
- for (const vendorId of vendorIds) {
95
+ for (const [vendorIndex, vendorId] of vendorIds.entries()) {
96
+ try {
97
+ options.onVendor?.(vendorIndex, vendorIds.length, vendorId);
98
+ }
99
+ catch {
100
+ // progress is presentation-only
101
+ }
96
102
  const vendor = config.vendors.find((candidate) => candidate.id === vendorId);
97
103
  if (!vendor)
98
104
  throw new Error(`Unknown vendor "${vendorId}"`);
package/dist/mcp-bin.js CHANGED
@@ -7,8 +7,8 @@ const MISSING_PEER_HELP = `The MCP server needs the optional peer dependencies @
7
7
  In a project:
8
8
  npm install fullstackgtm @modelcontextprotocol/sdk zod
9
9
 
10
- Zero-install:
11
- npx -p fullstackgtm -p @modelcontextprotocol/sdk -p zod fullstackgtm-mcp`;
10
+ Zero-install (the fullstackgtm-mcp wrapper package bundles the peers):
11
+ npx -y fullstackgtm-mcp`;
12
12
  function isMissingPeerError(error) {
13
13
  if (!(error instanceof Error))
14
14
  return false;