fullstackgtm 0.45.0 → 0.47.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 (95) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/INSTALL_FOR_AGENTS.md +13 -11
  3. package/README.md +27 -18
  4. package/dist/backfill.d.ts +86 -0
  5. package/dist/backfill.js +251 -0
  6. package/dist/bin.js +4 -1
  7. package/dist/cli/auth.d.ts +2 -0
  8. package/dist/cli/auth.js +119 -12
  9. package/dist/cli/backfill.d.ts +1 -0
  10. package/dist/cli/backfill.js +125 -0
  11. package/dist/cli/backfillRuns.d.ts +1 -0
  12. package/dist/cli/backfillRuns.js +187 -0
  13. package/dist/cli/call.js +23 -9
  14. package/dist/cli/enrich.js +28 -10
  15. package/dist/cli/fix.js +9 -7
  16. package/dist/cli/help.js +60 -23
  17. package/dist/cli/plans.js +13 -11
  18. package/dist/cli/shared.d.ts +4 -3
  19. package/dist/cli/shared.js +31 -20
  20. package/dist/cli/ui.d.ts +21 -0
  21. package/dist/cli/ui.js +53 -1
  22. package/dist/cli.js +37 -41
  23. package/dist/connector.d.ts +8 -0
  24. package/dist/connector.js +104 -21
  25. package/dist/connectors/hubspot.d.ts +8 -1
  26. package/dist/connectors/hubspot.js +406 -13
  27. package/dist/connectors/hubspotAuth.js +5 -1
  28. package/dist/connectors/linkedin.js +14 -14
  29. package/dist/connectors/salesforce.d.ts +8 -1
  30. package/dist/connectors/salesforce.js +163 -2
  31. package/dist/connectors/stripe.d.ts +37 -0
  32. package/dist/connectors/stripe.js +103 -31
  33. package/dist/enrich.d.ts +7 -0
  34. package/dist/enrich.js +7 -0
  35. package/dist/health.d.ts +11 -69
  36. package/dist/health.js +4 -134
  37. package/dist/healthScore.d.ts +71 -0
  38. package/dist/healthScore.js +143 -0
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.js +3 -1
  41. package/dist/llm.d.ts +29 -0
  42. package/dist/llm.js +206 -0
  43. package/dist/market.d.ts +39 -1
  44. package/dist/market.js +116 -44
  45. package/dist/marketClassify.d.ts +9 -1
  46. package/dist/marketClassify.js +10 -1
  47. package/dist/marketTaxonomy.d.ts +6 -1
  48. package/dist/marketTaxonomy.js +4 -2
  49. package/dist/mcp-bin.js +29 -0
  50. package/dist/mcp.js +117 -4
  51. package/dist/progress.d.ts +96 -0
  52. package/dist/progress.js +142 -0
  53. package/dist/runReport.d.ts +24 -0
  54. package/dist/runReport.js +139 -4
  55. package/dist/types.d.ts +33 -1
  56. package/docs/api.md +4 -2
  57. package/docs/architecture.md +2 -0
  58. package/docs/linkedin-connector-spec.md +1 -1
  59. package/llms.txt +3 -3
  60. package/package.json +10 -3
  61. package/skills/fullstackgtm/SKILL.md +1 -0
  62. package/src/backfill.ts +340 -0
  63. package/src/bin.ts +5 -1
  64. package/src/cli/auth.ts +135 -15
  65. package/src/cli/backfill.ts +156 -0
  66. package/src/cli/backfillRuns.ts +198 -0
  67. package/src/cli/call.ts +26 -10
  68. package/src/cli/enrich.ts +33 -10
  69. package/src/cli/fix.ts +11 -10
  70. package/src/cli/help.ts +61 -23
  71. package/src/cli/plans.ts +15 -14
  72. package/src/cli/shared.ts +44 -27
  73. package/src/cli/ui.ts +72 -1
  74. package/src/cli.ts +38 -41
  75. package/src/connector.ts +110 -16
  76. package/src/connectors/hubspot.ts +423 -14
  77. package/src/connectors/hubspotAuth.ts +5 -1
  78. package/src/connectors/linkedin.ts +14 -14
  79. package/src/connectors/salesforce.ts +168 -3
  80. package/src/connectors/stripe.ts +154 -34
  81. package/src/enrich.ts +13 -0
  82. package/src/health.ts +14 -213
  83. package/src/healthScore.ts +223 -0
  84. package/src/index.ts +28 -1
  85. package/src/llm.ts +239 -0
  86. package/src/market.ts +157 -44
  87. package/src/marketClassify.ts +18 -1
  88. package/src/marketTaxonomy.ts +10 -2
  89. package/src/mcp-bin.ts +32 -0
  90. package/src/mcp.ts +140 -6
  91. package/src/progress.ts +197 -0
  92. package/src/runReport.ts +159 -4
  93. package/src/types.ts +35 -2
  94. package/docs/dx-punch-list.md +0 -87
  95. package/docs/roadmap-to-1.0.md +0 -211
package/dist/cli.js CHANGED
@@ -1,22 +1,14 @@
1
1
  import { activeProfile, listProfiles, setActiveProfile } from "./credentials.js";
2
- import { audit, healthCommand, reportCommand, rulesCommand, snapshotCommand } from "./cli/audit.js";
3
- import { doctorCommand, login, logout } from "./cli/auth.js";
4
2
  import { capabilitiesCommand, printCommandHelpJson, robotDocsCommand, unknownCommandEnvelope } from "./cli/capabilities.js";
5
- import { callCommand } from "./cli/call.js";
6
- import { draftCommand } from "./cli/draft.js";
7
- import { enrichCommand } from "./cli/enrich.js";
8
- import { bulkUpdateCommand, dedupeCommand, fixCommand, reassignCommand, resolveCommand, suggest } from "./cli/fix.js";
9
3
  import { BESPOKE_HELP, commandHelp, HELP, shortUsage, stylizeShortUsage, usage } from "./cli/help.js";
10
- import { icpCommand } from "./cli/icp.js";
11
- import { initCommand } from "./cli/init.js";
12
- import { marketCommand } from "./cli/market.js";
13
- import { apply, auditLogCommand, diffCommand, mergeCommand, plansCommand } from "./cli/plans.js";
14
- import { scheduleCommand } from "./cli/schedule.js";
15
4
  import { readPackageInfo } from "./cli/shared.js";
16
5
  import { correctedCommand, detectFlagTypo, suggestCommand, unknownFlagEnvelope } from "./cli/suggest.js";
17
6
  import { colorEnabled, paint } from "./cli/ui.js";
18
- import { signalsCommand } from "./cli/signals.js";
19
- import { tamCommand } from "./cli/tam.js";
7
+ // Verb modules load lazily inside their dispatch branch below. The dispatcher
8
+ // used to import all of them eagerly, so `--version` compiled the full
9
+ // 78-module graph (audit, market, enrich, signals, schedule, …) before
10
+ // printing one line. Each branch now `await import()`s only its own module;
11
+ // help/typo/error paths and the exports above stay eager and byte-identical.
20
12
  /**
21
13
  * Pull the global `--profile <name>` flag out of argv (it may appear before
22
14
  * or after the command) and activate it. Stripping it keeps positional
@@ -126,95 +118,99 @@ export async function runCli(argv) {
126
118
  return;
127
119
  }
128
120
  if (command === "login") {
129
- await login(args);
121
+ await (await import("./cli/auth.js")).login(args);
130
122
  return;
131
123
  }
132
124
  if (command === "logout") {
133
- logout(args);
125
+ (await import("./cli/auth.js")).logout(args);
134
126
  return;
135
127
  }
136
128
  if (command === "snapshot") {
137
- await snapshotCommand(args);
129
+ await (await import("./cli/audit.js")).snapshotCommand(args);
138
130
  return;
139
131
  }
140
132
  if (command === "audit") {
141
- await audit(args);
133
+ await (await import("./cli/audit.js")).audit(args);
142
134
  return;
143
135
  }
144
136
  if (command === "report") {
145
- await reportCommand(args);
137
+ await (await import("./cli/audit.js")).reportCommand(args);
146
138
  return;
147
139
  }
148
140
  if (command === "rules") {
149
- await rulesCommand(args);
141
+ await (await import("./cli/audit.js")).rulesCommand(args);
150
142
  return;
151
143
  }
152
144
  if (command === "doctor") {
153
- await doctorCommand(args);
145
+ await (await import("./cli/auth.js")).doctorCommand(args);
154
146
  return;
155
147
  }
156
148
  if (command === "health") {
157
- healthCommand(args);
149
+ (await import("./cli/audit.js")).healthCommand(args);
158
150
  return;
159
151
  }
160
152
  if (command === "suggest") {
161
- await suggest(args);
153
+ await (await import("./cli/fix.js")).suggest(args);
162
154
  return;
163
155
  }
164
156
  if (command === "call") {
165
- await callCommand(args);
157
+ await (await import("./cli/call.js")).callCommand(args);
166
158
  return;
167
159
  }
168
160
  if (command === "resolve") {
169
- await resolveCommand(args);
161
+ await (await import("./cli/fix.js")).resolveCommand(args);
170
162
  return;
171
163
  }
172
164
  if (command === "bulk-update") {
173
- await bulkUpdateCommand(args);
165
+ await (await import("./cli/fix.js")).bulkUpdateCommand(args);
174
166
  return;
175
167
  }
176
168
  if (command === "dedupe") {
177
- await dedupeCommand(args);
169
+ await (await import("./cli/fix.js")).dedupeCommand(args);
178
170
  return;
179
171
  }
180
172
  if (command === "reassign") {
181
- await reassignCommand(args);
173
+ await (await import("./cli/fix.js")).reassignCommand(args);
182
174
  return;
183
175
  }
184
176
  if (command === "fix") {
185
- await fixCommand(args);
177
+ await (await import("./cli/fix.js")).fixCommand(args);
186
178
  return;
187
179
  }
188
180
  if (command === "market") {
189
- await marketCommand(args);
181
+ await (await import("./cli/market.js")).marketCommand(args);
190
182
  return;
191
183
  }
192
184
  if (command === "enrich") {
193
- await enrichCommand(args);
185
+ await (await import("./cli/enrich.js")).enrichCommand(args);
186
+ return;
187
+ }
188
+ if (command === "backfill") {
189
+ await (await import("./cli/backfill.js")).backfillCommand(args);
194
190
  return;
195
191
  }
196
192
  if (command === "init") {
197
- initCommand(args);
193
+ (await import("./cli/init.js")).initCommand(args);
198
194
  return;
199
195
  }
200
196
  if (command === "tam") {
201
- await tamCommand(args);
197
+ await (await import("./cli/tam.js")).tamCommand(args);
202
198
  return;
203
199
  }
204
200
  if (command === "icp") {
205
- await icpCommand(args);
201
+ await (await import("./cli/icp.js")).icpCommand(args);
206
202
  return;
207
203
  }
208
204
  if (command === "signals") {
209
- await signalsCommand(args);
205
+ await (await import("./cli/signals.js")).signalsCommand(args);
210
206
  return;
211
207
  }
212
208
  if (command === "draft") {
213
- await draftCommand(args);
209
+ await (await import("./cli/draft.js")).draftCommand(args);
214
210
  return;
215
211
  }
216
212
  if (command === "schedule") {
217
- await scheduleCommand(args);
213
+ await (await import("./cli/schedule.js")).scheduleCommand(args);
218
214
  return;
219
215
  }
220
216
  if (command === "profiles") {
@@ -222,23 +218,23 @@ export async function runCli(argv) {
222
218
  return;
223
219
  }
224
220
  if (command === "diff") {
225
- await diffCommand(args);
221
+ await (await import("./cli/plans.js")).diffCommand(args);
226
222
  return;
227
223
  }
228
224
  if (command === "merge") {
229
- await mergeCommand(args);
225
+ await (await import("./cli/plans.js")).mergeCommand(args);
230
226
  return;
231
227
  }
232
228
  if (command === "plans") {
233
- await plansCommand(args);
229
+ await (await import("./cli/plans.js")).plansCommand(args);
234
230
  return;
235
231
  }
236
232
  if (command === "audit-log") {
237
- await auditLogCommand(args);
233
+ await (await import("./cli/plans.js")).auditLogCommand(args);
238
234
  return;
239
235
  }
240
236
  if (command === "apply") {
241
- await apply(args);
237
+ await (await import("./cli/plans.js")).apply(args);
242
238
  return;
243
239
  }
244
240
  // Machine callers get a structured envelope with a did-you-mean hint
@@ -1,3 +1,4 @@
1
+ import { type ProgressEmitter } from "./progress.ts";
1
2
  import type { GtmConnector, PatchPlan, PatchPlanRun } from "./types.ts";
2
3
  export type ApplyPatchPlanOptions = {
3
4
  /**
@@ -31,6 +32,13 @@ export type ApplyPatchPlanOptions = {
31
32
  * full operation count.
32
33
  */
33
34
  onOperation?: (progress: ApplyProgress) => void;
35
+ /**
36
+ * Shared progress vocabulary (src/progress.ts): the run emits the
37
+ * APPLY_STAGES (preflight → operations → results), one `opResult` per
38
+ * resolved operation (id + status only — no values), and `items` over the
39
+ * plan's operations. Additive alongside `onOperation`; presentation only.
40
+ */
41
+ progress?: ProgressEmitter;
34
42
  };
35
43
  export type ApplyProgress = {
36
44
  completed: number;
package/dist/connector.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { dedupeKey } from "./dedupe.js";
2
+ import { APPLY_STAGES } from "./progress.js";
2
3
  import { requiresHumanInput } from "./rules.js";
3
4
  const IRREVERSIBLE_OPERATIONS = new Set(["merge_records", "archive_record"]);
4
5
  const IDENTITY_KEY_BY_TYPE = {
@@ -70,6 +71,13 @@ function normalizeForComparison(value) {
70
71
  return null;
71
72
  return String(value);
72
73
  }
74
+ function isApplyBatchCandidate(operation) {
75
+ return (((operation.operation === "set_field" || operation.operation === "clear_field") && Boolean(operation.field)) ||
76
+ operation.operation === "archive_record");
77
+ }
78
+ function sameApplyBatchKind(a, b) {
79
+ return a.operation === b.operation && a.objectType === b.objectType;
80
+ }
73
81
  /**
74
82
  * Apply an approved subset of a patch plan through a connector.
75
83
  *
@@ -83,6 +91,8 @@ export async function applyPatchPlan(connector, plan, options) {
83
91
  throw new Error(`The ${connector.provider} connector is read-only.`);
84
92
  }
85
93
  const startedAt = new Date().toISOString();
94
+ const emitStage = (stage) => options.progress?.stage(stage, APPLY_STAGES.indexOf(stage), APPLY_STAGES.length);
95
+ emitStage("preflight");
86
96
  const approved = new Set(options.approvedOperationIds);
87
97
  const checkConflicts = options.checkConflicts ?? typeof connector.readField === "function";
88
98
  const results = [];
@@ -157,6 +167,7 @@ export async function applyPatchPlan(connector, plan, options) {
157
167
  : "Operation was not approved.",
158
168
  });
159
169
  }
170
+ emitStage("results");
160
171
  return {
161
172
  planId: plan.id,
162
173
  provider: connector.provider,
@@ -190,6 +201,11 @@ export async function applyPatchPlan(connector, plan, options) {
190
201
  for (const operation of plan.operations) {
191
202
  if (!approved.has(operation.id) || conflicts.has(operation.id))
192
203
  continue;
204
+ // Generic batch connectors (Salesforce) do their own batched CAS read for
205
+ // eligible field writes, so don't pre-read those one-by-one here.
206
+ if (connector.applyBatch && isApplyBatchCandidate(operation) && !operation.groupId && !operation.preconditions?.length) {
207
+ continue;
208
+ }
193
209
  let conflict = null;
194
210
  if (operation.field && FIELD_WRITE_OPERATIONS.has(operation.operation)) {
195
211
  const current = await connector.readField(operation.objectType, operation.objectId, operation.field);
@@ -260,30 +276,40 @@ export async function applyPatchPlan(connector, plan, options) {
260
276
  const resultsBefore = results.length;
261
277
  let lastNotified = results.length;
262
278
  const notifyProgress = () => {
263
- if (!options.onOperation || results.length === lastNotified)
279
+ if ((!options.onOperation && !options.progress) || results.length === lastNotified)
264
280
  return;
265
- lastNotified = results.length;
266
- const last = results[results.length - 1];
267
- if (last.status === "applied")
268
- progressCounts.applied += 1;
269
- else if (last.status === "failed")
270
- progressCounts.failed += 1;
271
- else if (last.status === "conflict")
272
- progressCounts.conflicts += 1;
273
- else
274
- progressCounts.skipped += 1;
275
- try {
276
- options.onOperation({
277
- completed: results.length - resultsBefore,
278
- total: plan.operations.length,
279
- ...progressCounts,
280
- });
281
- }
282
- catch {
283
- // progress is presentation-only
281
+ while (lastNotified < results.length) {
282
+ const last = results[lastNotified];
283
+ lastNotified += 1;
284
+ if (last.status === "applied")
285
+ progressCounts.applied += 1;
286
+ else if (last.status === "failed")
287
+ progressCounts.failed += 1;
288
+ else if (last.status === "conflict")
289
+ progressCounts.conflicts += 1;
290
+ else
291
+ progressCounts.skipped += 1;
292
+ // Shared vocabulary: operation id + status only — a conflict/failure
293
+ // detail can echo field values, which never leave the machine.
294
+ options.progress?.opResult(last.operationId, last.status);
295
+ options.progress?.items(results.length - resultsBefore, plan.operations.length);
296
+ if (options.onOperation) {
297
+ try {
298
+ options.onOperation({
299
+ completed: lastNotified - resultsBefore,
300
+ total: plan.operations.length,
301
+ ...progressCounts,
302
+ });
303
+ }
304
+ catch {
305
+ // progress is presentation-only
306
+ }
307
+ }
284
308
  }
285
309
  };
286
- for (const operation of plan.operations) {
310
+ emitStage("operations");
311
+ for (let opIndex = 0; opIndex < plan.operations.length; opIndex++) {
312
+ const operation = plan.operations[opIndex];
287
313
  // Report the previous iteration's result (guarded: no-op if it pushed
288
314
  // nothing). One-iteration lag keeps this a two-line hook instead of a
289
315
  // notify after all ten push sites; the loop-exit call below flushes the last.
@@ -351,6 +377,62 @@ export async function applyPatchPlan(connector, plan, options) {
351
377
  });
352
378
  continue;
353
379
  }
380
+ if (connector.applyBatch && isApplyBatchCandidate(operation) && (checkConflicts || operation.operation === "archive_record") && !operation.groupId && !operation.preconditions?.length) {
381
+ const chunk = [];
382
+ const applyBatchLimit = Math.max(1, connector.applyBatchLimit ?? 200);
383
+ for (let scan = opIndex; scan < plan.operations.length && chunk.length < applyBatchLimit; scan++) {
384
+ const candidate = plan.operations[scan];
385
+ if (!sameApplyBatchKind(operation, candidate) || !isApplyBatchCandidate(candidate))
386
+ break;
387
+ const candidateOverride = options.valueOverrides?.[candidate.id];
388
+ if (!approved.has(candidate.id) ||
389
+ (requiresHumanInput(candidate.afterValue) && candidateOverride === undefined) ||
390
+ conflicts.has(candidate.id) ||
391
+ Boolean(candidate.groupId) ||
392
+ Boolean(candidate.preconditions?.length) ||
393
+ (staleByFilter && staleByFilter.has(candidate.objectId)) ||
394
+ irreversibleStale.has(candidate.id)) {
395
+ break;
396
+ }
397
+ chunk.push(candidateOverride === undefined ? candidate : { ...candidate, afterValue: candidateOverride });
398
+ }
399
+ if (chunk.length > 1) {
400
+ try {
401
+ const batchResults = await connector.applyBatch(chunk);
402
+ const byOperationId = new Map(batchResults.map((result) => [result.operationId, result]));
403
+ for (const batchOperation of chunk) {
404
+ const result = byOperationId.get(batchOperation.id) ?? {
405
+ operationId: batchOperation.id,
406
+ status: "failed",
407
+ detail: "Batch connector did not return a result for this operation.",
408
+ };
409
+ results.push(result);
410
+ if (result.status === "applied" || result.status === "failed")
411
+ attempted += 1;
412
+ if (result.status === "applied")
413
+ applied += 1;
414
+ }
415
+ const appliedInBatch = batchResults.filter((result) => result.status === "applied").length;
416
+ appliedSinceRecheck += appliedInBatch;
417
+ if (appliedInBatch > 0 && (applied === appliedInBatch || appliedSinceRecheck >= recheckEvery)) {
418
+ appliedSinceRecheck = 0;
419
+ await refreshSnapshotChecks();
420
+ }
421
+ }
422
+ catch (error) {
423
+ for (const batchOperation of chunk) {
424
+ results.push({
425
+ operationId: batchOperation.id,
426
+ status: "failed",
427
+ detail: error instanceof Error ? error.message : String(error),
428
+ });
429
+ attempted += 1;
430
+ }
431
+ }
432
+ opIndex += chunk.length - 1;
433
+ continue;
434
+ }
435
+ }
354
436
  const resolved = override === undefined ? operation : { ...operation, afterValue: override };
355
437
  attempted += 1;
356
438
  try {
@@ -377,6 +459,7 @@ export async function applyPatchPlan(connector, plan, options) {
377
459
  }
378
460
  }
379
461
  notifyProgress();
462
+ emitStage("results");
380
463
  return {
381
464
  planId: plan.id,
382
465
  provider: connector.provider,
@@ -1,5 +1,6 @@
1
1
  import { type FieldMappings } from "../mappings.ts";
2
2
  import type { GtmConnector, SnapshotProgress } from "../types.ts";
3
+ import { type ProgressEmitter } from "../progress.ts";
3
4
  export type HubspotConnectorOptions = {
4
5
  /** Returns a HubSpot access token (private app token or OAuth access token). */
5
6
  getAccessToken: () => string | Promise<string>;
@@ -10,6 +11,12 @@ export type HubspotConnectorOptions = {
10
11
  fetchImpl?: typeof fetch;
11
12
  /** Per-page snapshot-pull progress (presentation only — errors are swallowed). */
12
13
  onProgress?: (progress: SnapshotProgress) => void;
14
+ /**
15
+ * Shared progress vocabulary (src/progress.ts): the snapshot pull emits a
16
+ * `stage` per object type and an `items` heartbeat per page. Additive
17
+ * alongside the legacy `onProgress` callback; both are presentation-only.
18
+ */
19
+ progress?: ProgressEmitter;
13
20
  };
14
21
  /**
15
22
  * Reference connector for HubSpot.
@@ -19,4 +26,4 @@ export type HubspotConnectorOptions = {
19
26
  * amountless deals — so audit rules can surface the gaps instead of hiding
20
27
  * them.
21
28
  */
22
- export declare function createHubspotConnector(options: HubspotConnectorOptions): Required<GtmConnector>;
29
+ export declare function createHubspotConnector(options: HubspotConnectorOptions): GtmConnector;