fullstackgtm 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +273 -0
- package/README.md +18 -7
- package/dist/calls.d.ts +13 -0
- package/dist/calls.js +14 -3
- package/dist/cli.js +718 -60
- package/dist/connectors/hubspot.js +58 -24
- package/dist/connectors/outboxChannel.d.ts +64 -0
- package/dist/connectors/outboxChannel.js +170 -0
- package/dist/connectors/prospectSources.d.ts +22 -0
- package/dist/connectors/prospectSources.js +43 -0
- package/dist/connectors/salesforce.js +40 -15
- package/dist/connectors/signalSources.d.ts +105 -0
- package/dist/connectors/signalSources.js +316 -0
- package/dist/connectors/theirstack.d.ts +84 -0
- package/dist/connectors/theirstack.js +125 -0
- package/dist/draft.js +27 -5
- package/dist/enrich.d.ts +6 -0
- package/dist/enrich.js +47 -1
- package/dist/health.d.ts +12 -0
- package/dist/health.js +29 -2
- package/dist/icp.d.ts +47 -3
- package/dist/icp.js +105 -11
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init.d.ts +47 -0
- package/dist/init.js +143 -0
- package/dist/judge.d.ts +36 -3
- package/dist/judge.js +46 -10
- package/dist/judgeEval.d.ts +7 -0
- package/dist/judgeEval.js +8 -1
- package/dist/runReport.d.ts +26 -0
- package/dist/runReport.js +15 -0
- package/dist/schedule.js +18 -4
- package/dist/signals.d.ts +58 -0
- package/dist/signals.js +65 -0
- package/dist/tam.d.ts +225 -0
- package/dist/tam.js +470 -0
- package/dist/types.d.ts +12 -0
- package/docs/api.md +26 -4
- package/docs/outbox-format.md +92 -0
- package/docs/recipes.md +195 -0
- package/docs/roadmap-to-1.0.md +31 -1
- package/docs/signal-spool-format.md +162 -0
- package/docs/tam.md +195 -0
- package/llms.txt +89 -1
- package/package.json +1 -1
- package/skills/fullstackgtm/SKILL.md +17 -1
- package/src/calls.ts +24 -3
- package/src/cli.ts +809 -55
- package/src/connectors/hubspot.ts +55 -23
- package/src/connectors/outboxChannel.ts +202 -0
- package/src/connectors/prospectSources.ts +57 -0
- package/src/connectors/salesforce.ts +39 -14
- package/src/connectors/signalSources.ts +363 -0
- package/src/connectors/theirstack.ts +170 -0
- package/src/draft.ts +26 -5
- package/src/enrich.ts +51 -1
- package/src/health.ts +47 -2
- package/src/icp.ts +113 -11
- package/src/index.ts +42 -0
- package/src/init.ts +166 -0
- package/src/judge.ts +85 -11
- package/src/judgeEval.ts +8 -1
- package/src/runReport.ts +39 -0
- package/src/schedule.ts +20 -4
- package/src/signals.ts +95 -0
- package/src/tam.ts +654 -0
- package/src/types.ts +12 -0
package/src/schedule.ts
CHANGED
|
@@ -84,7 +84,13 @@ const SCHEDULABLE: Record<string, string[] | null> = {
|
|
|
84
84
|
suggest: null,
|
|
85
85
|
report: null,
|
|
86
86
|
doctor: null,
|
|
87
|
-
enrich
|
|
87
|
+
// `enrich append|refresh` fill blanks; `enrich acquire` creates net-new
|
|
88
|
+
// leads — but ONLY as a needs_approval `create_record` plan (`--save`,
|
|
89
|
+
// required below), never a write. This is what lets `tam populate` chip away
|
|
90
|
+
// at a TAM unattended: each firing queues a fresh lead plan, the meter is
|
|
91
|
+
// charged only at apply, and apply stays `apply --plan-id` (re-checked
|
|
92
|
+
// approved). So scheduled acquire accumulates proposals, never surprise leads.
|
|
93
|
+
enrich: ["append", "refresh", "acquire"],
|
|
88
94
|
market: ["capture", "refresh"],
|
|
89
95
|
// The GTM brain. `signals fetch` is read-only re: CRM (--save persists only
|
|
90
96
|
// the local signal ledger). `icp judge`/`icp eval` are read-only/grade-only
|
|
@@ -98,9 +104,9 @@ const SCHEDULABLE: Record<string, string[] | null> = {
|
|
|
98
104
|
};
|
|
99
105
|
|
|
100
106
|
const ALLOWLIST_SUMMARY =
|
|
101
|
-
"audit, snapshot, enrich append|refresh,
|
|
102
|
-
"icp judge|eval, draft (stages a plan),
|
|
103
|
-
"plus apply --plan-id <id> (re-checked approved at every firing)";
|
|
107
|
+
"audit, snapshot, enrich append|refresh, enrich acquire --save (stages a lead plan), " +
|
|
108
|
+
"market capture|refresh, signals fetch, icp judge|eval, draft (stages a plan), " +
|
|
109
|
+
"suggest, report, doctor — plus apply --plan-id <id> (re-checked approved at every firing)";
|
|
104
110
|
|
|
105
111
|
/**
|
|
106
112
|
* Validate that an argv resolves to a schedulable fullstackgtm command.
|
|
@@ -158,6 +164,16 @@ export function validateSchedulableArgv(argv: string[]): void {
|
|
|
158
164
|
.join(", ")}.`,
|
|
159
165
|
);
|
|
160
166
|
}
|
|
167
|
+
// `enrich acquire` is schedulable ONLY in its plan-producing form: without
|
|
168
|
+
// --save it's a dry-run that writes nothing AND queues nothing, so an
|
|
169
|
+
// unattended firing would silently no-op. Require --save so a scheduled
|
|
170
|
+
// acquire always leaves a needs_approval plan behind (apply stays gated).
|
|
171
|
+
if (head === "enrich" && sub === "acquire" && !argv.includes("--save")) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Scheduled `enrich acquire` must include --save so each firing queues a needs_approval lead plan " +
|
|
174
|
+
"(without it the run produces nothing). Apply stays a separate human gate (`apply --plan-id <id>`).",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
179
|
|
package/src/signals.ts
CHANGED
|
@@ -65,6 +65,9 @@ export type SignalOutcome = {
|
|
|
65
65
|
id: string;
|
|
66
66
|
accountDomain: string;
|
|
67
67
|
touchId?: string;
|
|
68
|
+
/** The contact the touch went to (from the judge decision), when known — so an
|
|
69
|
+
* outcome credits the person reached, not just the account domain. */
|
|
70
|
+
contactId?: string;
|
|
68
71
|
result: SignalOutcomeResult;
|
|
69
72
|
recordedAt: string;
|
|
70
73
|
/** Signal ids credited (from the judge decision). */
|
|
@@ -413,6 +416,71 @@ function withinWindow(iso: string, now: Date, windowDays: number): boolean {
|
|
|
413
416
|
return now.getTime() - t <= windowDays * DAY_MS;
|
|
414
417
|
}
|
|
415
418
|
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Staged rows -> signals (shared by `--from` ingest and source connectors)
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* One staged signal row: the platform-agnostic intake shape every NON-job source
|
|
424
|
+
* produces (a source connector's output, or a `--from` JSON row). It carries the
|
|
425
|
+
* evidence anchor (`quote`) but none of the derived fields (`id`, `weight`,
|
|
426
|
+
* `source`) — `stagedRowToSignal` fills those so there is ONE place that gates
|
|
427
|
+
* evidence and stamps identity, whether the row came from a file or an API.
|
|
428
|
+
*/
|
|
429
|
+
export type StagedSignalRow = {
|
|
430
|
+
bucket: SignalBucket;
|
|
431
|
+
accountDomain: string;
|
|
432
|
+
trigger: string;
|
|
433
|
+
/** VERBATIM evidence — required, non-empty. */
|
|
434
|
+
quote: string;
|
|
435
|
+
sourceUrl?: string;
|
|
436
|
+
/** ISO 8601; defaults to run time when absent. */
|
|
437
|
+
firstSeen?: string;
|
|
438
|
+
/** Optional explicit weight; defaults to the bucket's configured weight. */
|
|
439
|
+
weight?: number;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Validate one loosely-typed staged row and turn it into a `Signal`, applying
|
|
444
|
+
* the verbatim-evidence gate (a row with no quote is rejected, never faked) and
|
|
445
|
+
* the same id/normalization logic the ATS path uses. `errorLabel` lets the
|
|
446
|
+
* caller produce a precise message ("--from f.json: row 3", "serpapi-news row 0")
|
|
447
|
+
* since both the `--from` ingest and the source-connector registry funnel here.
|
|
448
|
+
*
|
|
449
|
+
* Throws on a malformed row; returns the canonical `Signal` on success. Bucket
|
|
450
|
+
* FILTERING (skip rows outside a `--bucket` selection) stays with the caller —
|
|
451
|
+
* this function is per-row validation only.
|
|
452
|
+
*/
|
|
453
|
+
export function stagedRowToSignal(
|
|
454
|
+
entry: Record<string, unknown>,
|
|
455
|
+
opts: { now: Date; source: string; errorLabel: string },
|
|
456
|
+
): Signal {
|
|
457
|
+
const { now, source, errorLabel } = opts;
|
|
458
|
+
const bucket = String(entry.bucket ?? "");
|
|
459
|
+
if (!SIGNAL_BUCKETS.includes(bucket as SignalBucket)) {
|
|
460
|
+
throw new Error(`${errorLabel} has unknown bucket "${bucket}" (one of ${SIGNAL_BUCKETS.join(", ")}).`);
|
|
461
|
+
}
|
|
462
|
+
const accountDomain = normalizeAccountDomain(String(entry.accountDomain ?? entry.domain ?? ""));
|
|
463
|
+
if (!accountDomain) throw new Error(`${errorLabel} is missing accountDomain.`);
|
|
464
|
+
const trigger = String(entry.trigger ?? "").trim();
|
|
465
|
+
if (!trigger) throw new Error(`${errorLabel} is missing trigger.`);
|
|
466
|
+
const quote = String(entry.quote ?? "").trim();
|
|
467
|
+
if (!quote) throw new Error(`${errorLabel} is missing the verbatim quote (the evidence anchor).`);
|
|
468
|
+
const base = { accountDomain, bucket: bucket as SignalBucket, trigger };
|
|
469
|
+
const firstSeen = typeof entry.firstSeen === "string" && entry.firstSeen ? entry.firstSeen : now.toISOString();
|
|
470
|
+
return {
|
|
471
|
+
id: signalId(base),
|
|
472
|
+
accountDomain,
|
|
473
|
+
bucket: bucket as SignalBucket,
|
|
474
|
+
trigger,
|
|
475
|
+
quote,
|
|
476
|
+
sourceUrl: String(entry.sourceUrl ?? ""),
|
|
477
|
+
firstSeen,
|
|
478
|
+
weight: typeof entry.weight === "number" ? entry.weight : DEFAULT_SIGNALS_CONFIG.buckets[bucket as SignalBucket].weight,
|
|
479
|
+
source,
|
|
480
|
+
judgedBy: null,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
416
484
|
// ---------------------------------------------------------------------------
|
|
417
485
|
// Dedup
|
|
418
486
|
|
|
@@ -530,6 +598,31 @@ export function signalsDir(baseDir?: string): string {
|
|
|
530
598
|
return join(baseDir ?? credentialsDir(), "signals");
|
|
531
599
|
}
|
|
532
600
|
|
|
601
|
+
/**
|
|
602
|
+
* Conventional webhook landing zone: `<signals>/spool`, profile-scoped. A
|
|
603
|
+
* webhook receiver (hosted, or the operator's own glue) appends one JSONL row
|
|
604
|
+
* per event to a `*.jsonl` file here; `signals fetch --connector file` reads the
|
|
605
|
+
* whole directory when given no explicit path. The CLI never writes here — the
|
|
606
|
+
* receiver does — so this is just the agreed-upon location, not a managed store.
|
|
607
|
+
* Per-source files (`rb2b.jsonl`, `hubspot.jsonl`, …) coexist. See
|
|
608
|
+
* docs/signal-spool-format.md.
|
|
609
|
+
*/
|
|
610
|
+
export function signalsSpoolDir(baseDir?: string): string {
|
|
611
|
+
return join(signalsDir(baseDir), "spool");
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Conventional outbox: `<signals>/outbox`, profile-scoped — the SEND-side mirror
|
|
616
|
+
* of the spool. `apply --channel outbox` renders each APPROVED drafted opener to
|
|
617
|
+
* a `<channel>.jsonl` file here (one row per touch); a downstream sender (hosted,
|
|
618
|
+
* or the operator's own) drains it. The CLI WRITES governed, approved send
|
|
619
|
+
* intents here but TRANSMITS NOTHING — the "drafts everything, transmits nothing"
|
|
620
|
+
* invariant holds. See docs/outbox-format.md.
|
|
621
|
+
*/
|
|
622
|
+
export function signalsOutboxDir(baseDir?: string): string {
|
|
623
|
+
return join(signalsDir(baseDir), "outbox");
|
|
624
|
+
}
|
|
625
|
+
|
|
533
626
|
export type SignalRun = {
|
|
534
627
|
id: string;
|
|
535
628
|
runLabel: string;
|
|
@@ -668,6 +761,7 @@ export function createFileSignalStore(directory?: string): SignalStore {
|
|
|
668
761
|
export function makeOutcome(input: {
|
|
669
762
|
accountDomain: string;
|
|
670
763
|
touchId?: string;
|
|
764
|
+
contactId?: string;
|
|
671
765
|
result: SignalOutcomeResult;
|
|
672
766
|
creditedSignals?: string[];
|
|
673
767
|
recordedAt?: string;
|
|
@@ -678,6 +772,7 @@ export function makeOutcome(input: {
|
|
|
678
772
|
id: outcomeId({ accountDomain, touchId: input.touchId, result: input.result, recordedAt }),
|
|
679
773
|
accountDomain,
|
|
680
774
|
...(input.touchId !== undefined ? { touchId: input.touchId } : {}),
|
|
775
|
+
...(input.contactId !== undefined ? { contactId: input.contactId } : {}),
|
|
681
776
|
result: input.result,
|
|
682
777
|
recordedAt,
|
|
683
778
|
creditedSignals: input.creditedSignals ?? [],
|