fullstackgtm 0.22.0 → 0.23.1
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 +57 -0
- package/README.md +15 -0
- package/dist/cli.js +456 -4
- package/dist/enrich.d.ts +220 -0
- package/dist/enrich.js +724 -0
- package/dist/enrichApollo.d.ts +59 -0
- package/dist/enrichApollo.js +192 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/marketReport.js +5 -5
- package/llms.txt +15 -0
- package/package.json +1 -1
- package/src/cli.ts +525 -4
- package/src/enrich.ts +1016 -0
- package/src/enrichApollo.ts +250 -0
- package/src/index.ts +45 -0
- package/src/marketReport.ts +9 -9
package/dist/enrich.d.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { CanonicalGtmSnapshot, PatchPlan } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The enrich layer: governed append/refresh of third-party data into the CRM.
|
|
4
|
+
*
|
|
5
|
+
* Every enrichment vendor ships fire-and-forget writeback — data lands without
|
|
6
|
+
* a diff, without approval, over whatever a human typed. This layer inverts
|
|
7
|
+
* that: a source (Apollo pull, Clay ingest) feeds a deterministic matcher,
|
|
8
|
+
* the matcher feeds a fill-blanks-only patch plan, and the plan goes through
|
|
9
|
+
* the existing dry-run → approval → apply contract. Every proposed value is
|
|
10
|
+
* traceable to the source payload that produced it (`GtmEvidence` on the
|
|
11
|
+
* plan), and every write carries a `beforeValue` for apply-time
|
|
12
|
+
* compare-and-set.
|
|
13
|
+
*
|
|
14
|
+
* State lives in a profile-scoped, append-only run store
|
|
15
|
+
* (`~/.fullstackgtm/profiles/<profile>/enrich/runs/`) that is checkpoint,
|
|
16
|
+
* staleness ledger, and observability surface in one. The CLI never writes
|
|
17
|
+
* `fsgtm_enriched_at`-style custom properties into the customer's portal.
|
|
18
|
+
*
|
|
19
|
+
* Recurring execution belongs to the horizontal scheduler (docs/schedule.md);
|
|
20
|
+
* enrich owns no cron logic.
|
|
21
|
+
*/
|
|
22
|
+
export type EnrichObjectType = "company" | "contact";
|
|
23
|
+
export type EnrichSourceKind = "api" | "ingest";
|
|
24
|
+
export type EnrichSourceConfig = {
|
|
25
|
+
kind: EnrichSourceKind;
|
|
26
|
+
/** Ingest staging format; csv (column headers) or json (dotted paths). */
|
|
27
|
+
format?: "csv" | "json";
|
|
28
|
+
};
|
|
29
|
+
export type EnrichAmbiguousPolicy = "skip" | "suggest";
|
|
30
|
+
export type EnrichMatchConfig = {
|
|
31
|
+
/** Ordered match keys, evaluated against the snapshot. */
|
|
32
|
+
keys: string[];
|
|
33
|
+
/** Multi-hit behavior; default skip. */
|
|
34
|
+
onAmbiguous?: EnrichAmbiguousPolicy;
|
|
35
|
+
};
|
|
36
|
+
export type EnrichFieldConfig = {
|
|
37
|
+
/** CRM property: canonical field name, or a default HubSpot property name. */
|
|
38
|
+
crm: string;
|
|
39
|
+
/** sourceId → dotted JSON path (api/json) or column header (ingest csv). */
|
|
40
|
+
from: Record<string, string>;
|
|
41
|
+
/** Opt into `enrich refresh`; fields without it are set once, never revisited. */
|
|
42
|
+
refresh?: boolean;
|
|
43
|
+
/** Staleness window for refresh; falls back to policy.defaultStaleDays. */
|
|
44
|
+
staleDays?: number;
|
|
45
|
+
/** Per-field conflict policy override. MVP: only "never". */
|
|
46
|
+
policy?: "never";
|
|
47
|
+
};
|
|
48
|
+
export type EnrichPolicyConfig = {
|
|
49
|
+
/** Conflict policy ladder. MVP ships "never" (fill blanks only). */
|
|
50
|
+
overwrite: "never";
|
|
51
|
+
defaultStaleDays?: number;
|
|
52
|
+
};
|
|
53
|
+
export type EnrichConfig = {
|
|
54
|
+
sources: Record<string, EnrichSourceConfig>;
|
|
55
|
+
match: Partial<Record<EnrichObjectType, EnrichMatchConfig>>;
|
|
56
|
+
fields: Partial<Record<EnrichObjectType, EnrichFieldConfig[]>>;
|
|
57
|
+
policy: EnrichPolicyConfig;
|
|
58
|
+
};
|
|
59
|
+
export declare const ENRICH_CONFIG_FILE_NAME = "enrich.config.json";
|
|
60
|
+
export declare const DEFAULT_STALE_DAYS = 90;
|
|
61
|
+
/** API source ids the MVP can pull from. */
|
|
62
|
+
export declare const SUPPORTED_API_SOURCES: string[];
|
|
63
|
+
/** Resolve a config `crm` field name to the canonical snapshot field. */
|
|
64
|
+
export declare function resolveCrmField(objectType: EnrichObjectType, name: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Strict, up-front validation (the 0.18 lesson: a config crash mid-run is
|
|
67
|
+
* worse than a refused config). Every problem names the offending entry and
|
|
68
|
+
* the accepted values.
|
|
69
|
+
*/
|
|
70
|
+
export declare function parseEnrichConfig(raw: string): EnrichConfig;
|
|
71
|
+
export declare function loadEnrichConfig(path: string): EnrichConfig;
|
|
72
|
+
export declare function parseCsv(text: string): Array<Record<string, string>>;
|
|
73
|
+
export type EnrichSourceRecord = {
|
|
74
|
+
/** e.g. "apollo:org_abc", "clay:row-3". Lands on stamps as sourceRecordId. */
|
|
75
|
+
id: string;
|
|
76
|
+
objectType: EnrichObjectType;
|
|
77
|
+
/** Match-key values (key name → raw value), extracted by the source adapter. */
|
|
78
|
+
keys: Record<string, string | undefined>;
|
|
79
|
+
/** Raw source payload; field paths and evidence excerpts read from it. */
|
|
80
|
+
payload: Record<string, unknown>;
|
|
81
|
+
};
|
|
82
|
+
/** Read a value from a payload: exact key first (CSV headers), then dotted path. */
|
|
83
|
+
export declare function sourceValueAt(payload: Record<string, unknown>, path: string): unknown;
|
|
84
|
+
/** Case-insensitive header lookup for ingest rows ("Email" matches key "email"). */
|
|
85
|
+
export declare function ingestKeyValue(row: Record<string, unknown>, key: string): string | undefined;
|
|
86
|
+
export type MatchOutcome = {
|
|
87
|
+
status: "matched";
|
|
88
|
+
recordId: string;
|
|
89
|
+
matchedKey: string;
|
|
90
|
+
} | {
|
|
91
|
+
status: "unmatched";
|
|
92
|
+
} | {
|
|
93
|
+
status: "ambiguous";
|
|
94
|
+
key: string;
|
|
95
|
+
candidateIds: string[];
|
|
96
|
+
};
|
|
97
|
+
export declare function matchSourceRecord(snapshot: CanonicalGtmSnapshot, objectType: EnrichObjectType, keys: string[], sourceKeys: Record<string, string | undefined>): MatchOutcome;
|
|
98
|
+
export type EnrichMode = "append" | "refresh";
|
|
99
|
+
export type EnrichCounts = {
|
|
100
|
+
fetched: number;
|
|
101
|
+
matched: number;
|
|
102
|
+
unmatched: number;
|
|
103
|
+
ambiguous: number;
|
|
104
|
+
opsEmitted: number;
|
|
105
|
+
};
|
|
106
|
+
export type EnrichStamp = {
|
|
107
|
+
objectType: EnrichObjectType;
|
|
108
|
+
objectId: string;
|
|
109
|
+
/** Canonical field name. */
|
|
110
|
+
field: string;
|
|
111
|
+
enrichedAt: string;
|
|
112
|
+
sourceRecordId: string;
|
|
113
|
+
/** Source value at stamp time (refresh change-detection observability). */
|
|
114
|
+
value?: unknown;
|
|
115
|
+
};
|
|
116
|
+
export type EnrichAmbiguity = {
|
|
117
|
+
sourceRecordId: string;
|
|
118
|
+
key: string;
|
|
119
|
+
candidateIds: string[];
|
|
120
|
+
};
|
|
121
|
+
export type EnrichWorkItem = {
|
|
122
|
+
objectType: EnrichObjectType;
|
|
123
|
+
objectId: string;
|
|
124
|
+
/** Canonical field name. */
|
|
125
|
+
field: string;
|
|
126
|
+
};
|
|
127
|
+
export type BuildEnrichPlanOptions = {
|
|
128
|
+
config: EnrichConfig;
|
|
129
|
+
source: string;
|
|
130
|
+
mode: EnrichMode;
|
|
131
|
+
snapshot: CanonicalGtmSnapshot;
|
|
132
|
+
records: EnrichSourceRecord[];
|
|
133
|
+
/**
|
|
134
|
+
* Refresh only: the stale (record, field) work set computed from run-store
|
|
135
|
+
* stamps. Refresh proposes writes ONLY for work-set cells — fields the
|
|
136
|
+
* ledger proves enrich itself stamped — so policy "never" still never
|
|
137
|
+
* overwrites a value enrich did not put there.
|
|
138
|
+
*/
|
|
139
|
+
workSet?: EnrichWorkItem[];
|
|
140
|
+
now?: () => Date;
|
|
141
|
+
runLabel: string;
|
|
142
|
+
};
|
|
143
|
+
export type EnrichPlanResult = {
|
|
144
|
+
plan: PatchPlan;
|
|
145
|
+
counts: EnrichCounts;
|
|
146
|
+
stamps: EnrichStamp[];
|
|
147
|
+
ambiguities: EnrichAmbiguity[];
|
|
148
|
+
unmatchedSourceIds: string[];
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Match source records against the snapshot and emit a patch plan under the
|
|
152
|
+
* conflict policy. Append fills blanks only; refresh proposes updates for
|
|
153
|
+
* stale stamped fields whose source value actually changed (beforeValue =
|
|
154
|
+
* current CRM value → apply-time compare-and-set rejects drifted records).
|
|
155
|
+
*/
|
|
156
|
+
export declare function buildEnrichPlan(options: BuildEnrichPlanOptions): EnrichPlanResult;
|
|
157
|
+
/** Latest stamp per (objectType, objectId, field) across a source's runs. */
|
|
158
|
+
export declare function latestStamps(runs: EnrichRun[], source: string): Map<string, EnrichStamp>;
|
|
159
|
+
export declare function staleDaysFor(config: EnrichConfig, objectType: EnrichObjectType, field: string): number;
|
|
160
|
+
/**
|
|
161
|
+
* Stale (record, field) cells: stamped by this source, refresh-eligible in
|
|
162
|
+
* the config, and older than the staleness window (per-field staleDays →
|
|
163
|
+
* policy.defaultStaleDays → 90; --stale-days overrides all).
|
|
164
|
+
*/
|
|
165
|
+
export declare function selectStaleWork(config: EnrichConfig, runs: EnrichRun[], source: string, options?: {
|
|
166
|
+
now?: () => Date;
|
|
167
|
+
staleDaysOverride?: number;
|
|
168
|
+
}): EnrichWorkItem[];
|
|
169
|
+
export type EnrichRunMode = EnrichMode | "ingest";
|
|
170
|
+
export type EnrichRun = {
|
|
171
|
+
id: string;
|
|
172
|
+
runLabel: string;
|
|
173
|
+
source: string;
|
|
174
|
+
mode: EnrichRunMode;
|
|
175
|
+
startedAt: string;
|
|
176
|
+
/** null while in progress — `status` surfaces it as an interrupted run. */
|
|
177
|
+
completedAt: string | null;
|
|
178
|
+
/** Resume point for an interrupted pull (last processed pull key). */
|
|
179
|
+
cursor: string | null;
|
|
180
|
+
counts: EnrichCounts;
|
|
181
|
+
planIds: string[];
|
|
182
|
+
stamps: EnrichStamp[];
|
|
183
|
+
/** Staged source rows (ingest mode only), consumed by append/refresh. */
|
|
184
|
+
staged?: Array<Record<string, unknown>>;
|
|
185
|
+
/** Object type of the staged rows (ingest mode only). */
|
|
186
|
+
stagedObjectType?: EnrichObjectType;
|
|
187
|
+
/**
|
|
188
|
+
* Source records pulled so far (api pulls with --save). Together with
|
|
189
|
+
* `cursor` this makes the checkpoint complete: a resumed run replays the
|
|
190
|
+
* already-paid-for payloads instead of re-fetching them.
|
|
191
|
+
*/
|
|
192
|
+
pulled?: EnrichSourceRecord[];
|
|
193
|
+
/** Pull keys the source returned no data for (api pulls with --save). */
|
|
194
|
+
missedKeys?: string[];
|
|
195
|
+
/** Match collisions recorded for review (candidate ids included). */
|
|
196
|
+
ambiguities?: EnrichAmbiguity[];
|
|
197
|
+
};
|
|
198
|
+
export declare function enrichRunId(source: string, runLabel: string): string;
|
|
199
|
+
export declare function enrichRunsDir(baseDir?: string): string;
|
|
200
|
+
export interface EnrichRunStore {
|
|
201
|
+
/** Append a new run; refuses an existing label (runs are append-only). */
|
|
202
|
+
append(run: EnrichRun): Promise<EnrichRun>;
|
|
203
|
+
/** Update an in-progress run (cursor checkpoint, finalization) in place. */
|
|
204
|
+
update(run: EnrichRun): Promise<EnrichRun>;
|
|
205
|
+
get(runLabel: string): Promise<EnrichRun | null>;
|
|
206
|
+
list(): Promise<EnrichRun[]>;
|
|
207
|
+
latest(filter?: {
|
|
208
|
+
source?: string;
|
|
209
|
+
mode?: EnrichRunMode;
|
|
210
|
+
}): Promise<EnrichRun | null>;
|
|
211
|
+
}
|
|
212
|
+
export declare function createFileEnrichRunStore(directory?: string): EnrichRunStore;
|
|
213
|
+
/**
|
|
214
|
+
* Infer the object type of staged rows from the configured match keys: the
|
|
215
|
+
* type whose key columns actually appear on the rows. Exactly one hit wins;
|
|
216
|
+
* zero or two is an error asking for --objects.
|
|
217
|
+
*/
|
|
218
|
+
export declare function inferIngestObjectType(config: EnrichConfig, source: string, rows: Array<Record<string, unknown>>): EnrichObjectType;
|
|
219
|
+
/** Turn staged ingest rows into source records for the matcher. */
|
|
220
|
+
export declare function stagedSourceRecords(config: EnrichConfig, source: string, run: EnrichRun): EnrichSourceRecord[];
|