fullstackgtm 0.49.0 → 0.50.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 +77 -0
- package/DATA-FLOWS.md +1 -0
- package/README.md +48 -0
- package/dist/acquireCheckpoint.d.ts +33 -0
- package/dist/acquireCheckpoint.js +149 -0
- package/dist/acquireLinkedIn.d.ts +2 -0
- package/dist/acquireLinkedIn.js +1 -1
- package/dist/cli/audit.js +6 -1
- package/dist/cli/auth.js +25 -0
- package/dist/cli/backfill.js +4 -0
- package/dist/cli/call.js +28 -6
- package/dist/cli/draft.js +11 -1
- package/dist/cli/enrich.js +286 -81
- package/dist/cli/fix.js +6 -3
- package/dist/cli/help.js +30 -26
- package/dist/cli/icp.js +15 -1
- package/dist/cli/market.js +14 -1
- package/dist/cli/planOutput.d.ts +5 -0
- package/dist/cli/planOutput.js +27 -0
- package/dist/cli/plans.js +269 -24
- package/dist/cli/schedule.js +22 -11
- package/dist/cli/shared.d.ts +3 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli/signals.js +22 -3
- package/dist/cli/tam.js +10 -1
- package/dist/cli/ui.d.ts +10 -5
- package/dist/cli/ui.js +28 -9
- package/dist/connectors/hubspot.d.ts +4 -0
- package/dist/connectors/hubspot.js +36 -18
- package/dist/connectors/linkedin.d.ts +2 -0
- package/dist/connectors/linkedin.js +5 -0
- package/dist/connectors/prospectSources.d.ts +23 -0
- package/dist/connectors/prospectSources.js +21 -3
- package/dist/enrich.d.ts +44 -1
- package/dist/hostedAcquireCheckpoint.d.ts +43 -0
- package/dist/hostedAcquireCheckpoint.js +129 -0
- package/dist/hostedPatchPlan.d.ts +87 -0
- package/dist/hostedPatchPlan.js +275 -0
- package/dist/icp.js +13 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/planStore.d.ts +14 -0
- package/dist/planStore.js +73 -0
- package/dist/progress.d.ts +2 -2
- package/dist/progress.js +2 -2
- package/docs/api.md +24 -0
- package/docs/architecture.md +9 -0
- package/package.json +1 -1
- package/skills/fullstackgtm/SKILL.md +1 -1
- package/src/acquireCheckpoint.ts +186 -0
- package/src/acquireLinkedIn.ts +3 -1
- package/src/cli/audit.ts +5 -1
- package/src/cli/auth.ts +24 -0
- package/src/cli/backfill.ts +3 -0
- package/src/cli/call.ts +25 -6
- package/src/cli/draft.ts +9 -1
- package/src/cli/enrich.ts +325 -80
- package/src/cli/fix.ts +5 -3
- package/src/cli/help.ts +31 -26
- package/src/cli/icp.ts +13 -1
- package/src/cli/market.ts +12 -1
- package/src/cli/planOutput.ts +30 -0
- package/src/cli/plans.ts +259 -25
- package/src/cli/schedule.ts +21 -15
- package/src/cli/shared.ts +2 -1
- package/src/cli/signals.ts +22 -3
- package/src/cli/tam.ts +8 -1
- package/src/cli/ui.ts +31 -13
- package/src/connectors/hubspot.ts +41 -17
- package/src/connectors/linkedin.ts +6 -0
- package/src/connectors/prospectSources.ts +46 -4
- package/src/enrich.ts +47 -1
- package/src/hostedAcquireCheckpoint.ts +156 -0
- package/src/hostedPatchPlan.ts +291 -0
- package/src/icp.ts +14 -2
- package/src/index.ts +20 -0
- package/src/planStore.ts +87 -0
- package/src/progress.ts +2 -2
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/** Broker-authenticated mirror for locally governed patch plans. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { getCredential } from "./credentials.js";
|
|
4
|
+
const ENDPOINT = "/api/cli/patch-plan";
|
|
5
|
+
const TIMEOUT_MS = 5000;
|
|
6
|
+
function broker() {
|
|
7
|
+
const value = getCredential("broker");
|
|
8
|
+
if (!value?.baseUrl || !value.accessToken)
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(value.baseUrl);
|
|
12
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
13
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && local))
|
|
14
|
+
return null;
|
|
15
|
+
return { baseUrl: url.href.replace(/\/+$/, ""), token: value.accessToken };
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Review document intentionally omits evidence/findings and all mutable store security state. */
|
|
22
|
+
export function hostedReviewDocument(plan) {
|
|
23
|
+
return {
|
|
24
|
+
id: plan.id,
|
|
25
|
+
title: plan.title,
|
|
26
|
+
createdAt: plan.createdAt,
|
|
27
|
+
status: "needs_approval",
|
|
28
|
+
dryRun: true,
|
|
29
|
+
summary: plan.summary,
|
|
30
|
+
operations: plan.operations,
|
|
31
|
+
...(plan.filter ? { filter: plan.filter } : {}),
|
|
32
|
+
...(plan.guards ? { guards: plan.guards } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function hostedPlanDigest(plan) {
|
|
36
|
+
return createHash("sha256").update(JSON.stringify(hostedReviewDocument(plan))).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
function mapOperation(op) {
|
|
39
|
+
return {
|
|
40
|
+
packageOpId: op.id,
|
|
41
|
+
objectType: op.objectType,
|
|
42
|
+
...(op.objectId ? { objectId: op.objectId } : {}),
|
|
43
|
+
fieldOrAction: op.field ?? op.operation,
|
|
44
|
+
beforeValue: op.beforeValue ?? null,
|
|
45
|
+
afterValue: op.afterValue ?? null,
|
|
46
|
+
...(op.reason ? { reason: op.reason } : {}),
|
|
47
|
+
...(op.sourceRuleOrPolicy ? { sourceRuleOrPolicy: op.sourceRuleOrPolicy } : {}),
|
|
48
|
+
riskLevel: op.riskLevel,
|
|
49
|
+
approvalRequired: op.approvalRequired,
|
|
50
|
+
...(op.rollback ? { rollback: op.rollback } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function parseState(value, baseUrl) {
|
|
54
|
+
if (!value || typeof value !== "object")
|
|
55
|
+
return null;
|
|
56
|
+
const row = value;
|
|
57
|
+
const id = typeof row.patchPlanId === "string" ? row.patchPlanId : null;
|
|
58
|
+
if (!id)
|
|
59
|
+
return null;
|
|
60
|
+
const approvedOperationIds = Array.isArray(row.approvedOperationIds) ? row.approvedOperationIds.filter((id) => typeof id === "string") : [];
|
|
61
|
+
const runRecord = parseRunRecord(row.runRecord, row.operationReceipts, row.externalPlanId, row.updatedAt);
|
|
62
|
+
return {
|
|
63
|
+
patchPlanId: id,
|
|
64
|
+
externalPlanId: typeof row.externalPlanId === "string" ? row.externalPlanId : "",
|
|
65
|
+
documentSha256: typeof row.documentSha256 === "string" ? row.documentSha256 : "",
|
|
66
|
+
status: typeof row.status === "string" ? row.status : "pending_approval",
|
|
67
|
+
approvedOperationIds,
|
|
68
|
+
valueOverrides: row.valueOverrides && typeof row.valueOverrides === "object" && !Array.isArray(row.valueOverrides) ? row.valueOverrides : {},
|
|
69
|
+
updatedAt: typeof row.updatedAt === "number" ? row.updatedAt : Date.now(),
|
|
70
|
+
...(runRecord ? { runRecord } : {}),
|
|
71
|
+
url: `${baseUrl}/dashboard/patch-plans?plan=${encodeURIComponent(id)}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function parseRunRecord(value, receiptsValue, externalPlanId, updatedAt) {
|
|
75
|
+
const row = value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
76
|
+
const planId = typeof row?.planId === "string" ? row.planId : typeof externalPlanId === "string" ? externalPlanId : undefined;
|
|
77
|
+
if (!planId)
|
|
78
|
+
return undefined;
|
|
79
|
+
const rawResults = Array.isArray(row?.results) ? row.results : Array.isArray(receiptsValue) ? receiptsValue : [];
|
|
80
|
+
const results = rawResults.flatMap((entry) => {
|
|
81
|
+
if (!entry || typeof entry !== "object")
|
|
82
|
+
return [];
|
|
83
|
+
const receipt = entry;
|
|
84
|
+
const operationId = typeof receipt.operationId === "string" ? receipt.operationId : typeof receipt.packageOpId === "string" ? receipt.packageOpId : undefined;
|
|
85
|
+
const rawStatus = receipt.status;
|
|
86
|
+
const status = rawStatus === "applied" || rawStatus === "failed" || rawStatus === "skipped" || rawStatus === "conflict" ? rawStatus : undefined;
|
|
87
|
+
if (!operationId || !status)
|
|
88
|
+
return [];
|
|
89
|
+
return [{ operationId, status, ...(typeof receipt.detail === "string" ? { detail: receipt.detail } : typeof receipt.error === "string" ? { detail: receipt.error } : {}), ...(receipt.providerData !== undefined ? { providerData: receipt.providerData } : {}) }];
|
|
90
|
+
});
|
|
91
|
+
if (results.length === 0)
|
|
92
|
+
return undefined;
|
|
93
|
+
const timestamp = typeof updatedAt === "number" ? new Date(updatedAt).toISOString() : new Date().toISOString();
|
|
94
|
+
const provider = typeof row?.provider === "string" ? row.provider : "mock";
|
|
95
|
+
const status = row?.status === "applied" || row?.status === "partial" || row?.status === "failed" || row?.status === "rejected"
|
|
96
|
+
? row.status : results.every((result) => result.status === "applied" || result.status === "skipped") ? "applied" : "partial";
|
|
97
|
+
return {
|
|
98
|
+
planId, provider,
|
|
99
|
+
startedAt: typeof row?.startedAt === "string" ? row.startedAt : timestamp,
|
|
100
|
+
finishedAt: typeof row?.finishedAt === "string" ? row.finishedAt : timestamp,
|
|
101
|
+
status,
|
|
102
|
+
results,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function failure(status) {
|
|
106
|
+
if (status === 409)
|
|
107
|
+
return { status: "conflict", reason: "hosted mirror has different immutable content" };
|
|
108
|
+
if (status === 401 || status === 403)
|
|
109
|
+
return { status: "unavailable", reason: "hosted authentication was rejected; pair the CLI again" };
|
|
110
|
+
return { status: "unavailable", reason: `hosted plan request failed (HTTP ${status})` };
|
|
111
|
+
}
|
|
112
|
+
export async function uploadHostedPatchPlan(stored, options = {}) {
|
|
113
|
+
const paired = broker();
|
|
114
|
+
if (!paired)
|
|
115
|
+
return { status: "unpaired" };
|
|
116
|
+
const document = hostedReviewDocument(stored.plan);
|
|
117
|
+
const digest = hostedPlanDigest(stored.plan);
|
|
118
|
+
const riskOrder = { low: 0, medium: 1, high: 2 };
|
|
119
|
+
const riskLevel = stored.plan.operations.reduce((risk, op) => riskOrder[op.riskLevel] > riskOrder[risk] ? op.riskLevel : risk, "low");
|
|
120
|
+
try {
|
|
121
|
+
const response = await (options.fetchImpl ?? fetch)(`${paired.baseUrl}${ENDPOINT}`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { Authorization: `Bearer ${paired.token}`, "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
externalPlanId: stored.plan.id, documentSha256: digest, externalRevision: stored.revision,
|
|
126
|
+
title: stored.plan.title, summary: stored.plan.summary, riskLevel, document,
|
|
127
|
+
operations: stored.plan.operations.map(mapOperation), createdAt: Date.parse(stored.plan.createdAt),
|
|
128
|
+
}),
|
|
129
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? TIMEOUT_MS),
|
|
130
|
+
});
|
|
131
|
+
if (!response.ok)
|
|
132
|
+
return failure(response.status);
|
|
133
|
+
const raw = await response.json();
|
|
134
|
+
const state = parseState({ ...raw, externalPlanId: stored.plan.id, documentSha256: digest, approvedOperationIds: [], valueOverrides: {}, updatedAt: Date.now() }, paired.baseUrl);
|
|
135
|
+
return state ? { status: "saved", state } : { status: "unavailable", reason: "hosted plan response was invalid" };
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return { status: "unavailable", reason: "hosted plan request was unavailable" };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function readHostedPatchPlan(planId, options = {}) {
|
|
142
|
+
const paired = broker();
|
|
143
|
+
if (!paired)
|
|
144
|
+
return { status: "unpaired" };
|
|
145
|
+
try {
|
|
146
|
+
const response = await (options.fetchImpl ?? fetch)(`${paired.baseUrl}${ENDPOINT}?externalPlanId=${encodeURIComponent(planId)}`, {
|
|
147
|
+
headers: { Authorization: `Bearer ${paired.token}`, Accept: "application/json" },
|
|
148
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? TIMEOUT_MS),
|
|
149
|
+
});
|
|
150
|
+
if (response.status === 404)
|
|
151
|
+
return { status: "missing" };
|
|
152
|
+
if (!response.ok)
|
|
153
|
+
return failure(response.status);
|
|
154
|
+
const state = parseState(await response.json(), paired.baseUrl);
|
|
155
|
+
return state ? { status: "found", state } : { status: "unavailable", reason: "hosted plan response was invalid" };
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return { status: "unavailable", reason: "hosted plan request was unavailable" };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** Pull a peer's newer lifecycle into the local replica without weakening local integrity gates. */
|
|
162
|
+
export async function reconcileHostedPatchPlan(store, stored, options = {}) {
|
|
163
|
+
const hosted = await readHostedPatchPlan(stored.plan.id, options);
|
|
164
|
+
if (hosted.status === "unpaired" || hosted.status === "missing")
|
|
165
|
+
return { status: hosted.status };
|
|
166
|
+
if (hosted.status === "unavailable" || hosted.status === "conflict")
|
|
167
|
+
return hosted;
|
|
168
|
+
if (hosted.state.documentSha256 !== hostedPlanDigest(stored.plan)) {
|
|
169
|
+
return { status: "conflict", reason: "hosted review content does not match the immutable local plan" };
|
|
170
|
+
}
|
|
171
|
+
if (!["approved", "rejected", "applied"].includes(hosted.state.status))
|
|
172
|
+
return { status: "unchanged" };
|
|
173
|
+
try {
|
|
174
|
+
const reconciled = await store.reconcileReplica(stored.plan.id, {
|
|
175
|
+
status: hosted.state.status,
|
|
176
|
+
approvedOperationIds: hosted.state.approvedOperationIds,
|
|
177
|
+
valueOverrides: hosted.state.valueOverrides,
|
|
178
|
+
...(hosted.state.runRecord ? { run: hosted.state.runRecord } : {}),
|
|
179
|
+
});
|
|
180
|
+
return reconciled.changed
|
|
181
|
+
? { status: "updated", stored: reconciled.stored, remoteStatus: hosted.state.status }
|
|
182
|
+
: { status: "unchanged" };
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return { status: "conflict", reason: error instanceof Error ? error.message : "replica reconciliation failed" };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export async function reportHostedPlanLifecycle(stored, options = {}) {
|
|
189
|
+
const paired = broker();
|
|
190
|
+
if (!paired)
|
|
191
|
+
return { status: "unpaired" };
|
|
192
|
+
const status = stored.status === "applied" ? "applied" : stored.status === "rejected" ? "rejected" : "approved";
|
|
193
|
+
const runRecord = status === "applied" ? stored.runs.at(-1) : undefined;
|
|
194
|
+
// The hosted terminal transition is authority-bound to the exact approved
|
|
195
|
+
// subset. Local runs also record excluded operations as `skipped` for a
|
|
196
|
+
// complete human audit trail; those are not execution receipts and must not
|
|
197
|
+
// be echoed as though they were authorized provider work.
|
|
198
|
+
const approved = new Set(stored.approvedOperationIds);
|
|
199
|
+
const operationReceipts = runRecord?.results.filter((result) => approved.has(result.operationId)).map((result) => ({
|
|
200
|
+
packageOpId: result.operationId,
|
|
201
|
+
status: result.status,
|
|
202
|
+
...(result.detail ? { error: result.detail.slice(0, 1000) } : {}),
|
|
203
|
+
...(result.providerData !== undefined ? { providerData: result.providerData } : {}),
|
|
204
|
+
updatedAt: Date.parse(runRecord.finishedAt),
|
|
205
|
+
}));
|
|
206
|
+
try {
|
|
207
|
+
const response = await (options.fetchImpl ?? fetch)(`${paired.baseUrl}${ENDPOINT}`, {
|
|
208
|
+
method: "POST", headers: { Authorization: `Bearer ${paired.token}`, "Content-Type": "application/json" },
|
|
209
|
+
body: JSON.stringify({ action: "lifecycle", externalPlanId: stored.plan.id, documentSha256: hostedPlanDigest(stored.plan), status, approvedOperationIds: stored.approvedOperationIds, ...(options.claimId ? { claimId: options.claimId } : {}), ...(runRecord ? { runRecord, operationReceipts } : {}) }),
|
|
210
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? TIMEOUT_MS),
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok)
|
|
213
|
+
return failure(response.status);
|
|
214
|
+
const raw = await response.json();
|
|
215
|
+
const state = parseState({ ...raw, externalPlanId: stored.plan.id, documentSha256: hostedPlanDigest(stored.plan), approvedOperationIds: stored.approvedOperationIds, valueOverrides: stored.valueOverrides, updatedAt: Date.now() }, paired.baseUrl);
|
|
216
|
+
return state ? { status: "saved", state } : { status: "unavailable", reason: "hosted lifecycle response was invalid" };
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return { status: "unavailable", reason: "hosted lifecycle request was unavailable" };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** Coordinate online replicas before provider I/O; provider idempotency remains the offline fallback. */
|
|
223
|
+
export async function claimHostedPlanApply(stored, options = {}) {
|
|
224
|
+
const paired = broker();
|
|
225
|
+
if (!paired)
|
|
226
|
+
return { status: "unpaired" };
|
|
227
|
+
try {
|
|
228
|
+
const response = await (options.fetchImpl ?? fetch)(`${paired.baseUrl}${ENDPOINT}`, {
|
|
229
|
+
method: "POST", headers: { Authorization: `Bearer ${paired.token}`, "Content-Type": "application/json" },
|
|
230
|
+
body: JSON.stringify({ action: "claim", externalPlanId: stored.plan.id, documentSha256: hostedPlanDigest(stored.plan) }),
|
|
231
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? TIMEOUT_MS),
|
|
232
|
+
});
|
|
233
|
+
if (!response.ok)
|
|
234
|
+
return failure(response.status);
|
|
235
|
+
const row = await response.json();
|
|
236
|
+
if (row.alreadyApplied === true || row.status === "applied")
|
|
237
|
+
return { status: "applied" };
|
|
238
|
+
if (row.status === "claimed" && typeof row.claimId === "string") {
|
|
239
|
+
return { status: "claimed", claimId: row.claimId, ...(typeof row.expiresAt === "number" ? { expiresAt: row.expiresAt } : {}) };
|
|
240
|
+
}
|
|
241
|
+
return { status: "unavailable", reason: "hosted apply claim response was invalid" };
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return { status: "unavailable", reason: "hosted apply claim request was unavailable" };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Release a shared CLI claim only when preflight aborts before provider I/O. */
|
|
248
|
+
export async function releaseHostedPlanApply(stored, claimId, reason, options = {}) {
|
|
249
|
+
const paired = broker();
|
|
250
|
+
if (!paired)
|
|
251
|
+
return { status: "unpaired" };
|
|
252
|
+
try {
|
|
253
|
+
const response = await (options.fetchImpl ?? fetch)(`${paired.baseUrl}${ENDPOINT}`, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: { Authorization: `Bearer ${paired.token}`, "Content-Type": "application/json" },
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
action: "release_claim",
|
|
258
|
+
externalPlanId: stored.plan.id,
|
|
259
|
+
documentSha256: hostedPlanDigest(stored.plan),
|
|
260
|
+
claimId,
|
|
261
|
+
reason,
|
|
262
|
+
}),
|
|
263
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? TIMEOUT_MS),
|
|
264
|
+
});
|
|
265
|
+
if (!response.ok)
|
|
266
|
+
return failure(response.status);
|
|
267
|
+
const row = await response.json();
|
|
268
|
+
return row.released === true
|
|
269
|
+
? { status: "released" }
|
|
270
|
+
: { status: "unavailable", reason: "hosted apply claim release response was invalid" };
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return { status: "unavailable", reason: "hosted apply claim release was unavailable" };
|
|
274
|
+
}
|
|
275
|
+
}
|
package/dist/icp.js
CHANGED
|
@@ -158,6 +158,14 @@ const CRUSTDATA_INDUSTRY = {
|
|
|
158
158
|
fintech: ["Financial Services"],
|
|
159
159
|
"financial services": ["Financial Services"],
|
|
160
160
|
};
|
|
161
|
+
// Common NAICS groups translated into Crustdata's LinkedIn industry vocabulary.
|
|
162
|
+
// This keeps an ICP authored with provider-neutral NAICS codes from silently
|
|
163
|
+
// dropping its industry constraint on the Pipe0 path.
|
|
164
|
+
const NAICS_CRUSTDATA_INDUSTRY = {
|
|
165
|
+
"5112": ["Software Development", "Computer Software"],
|
|
166
|
+
"5182": ["IT Services and IT Consulting", "Information Technology & Services"],
|
|
167
|
+
"5415": ["IT Services and IT Consulting", "Information Technology & Services"],
|
|
168
|
+
};
|
|
161
169
|
/**
|
|
162
170
|
* pipe0 Crustdata people-search config.filters from the ICP. `current_job_titles`
|
|
163
171
|
* matches real LinkedIn title strings (case-sensitive), so keywords are Title
|
|
@@ -185,9 +193,12 @@ export function icpToCrustdataFilters(icp) {
|
|
|
185
193
|
if (icp.firmographics.geos?.length) {
|
|
186
194
|
f.locations = icp.firmographics.geos.map((g) => COUNTRY_NAMES[g.toLowerCase()] ?? g);
|
|
187
195
|
}
|
|
188
|
-
if (icp.firmographics.industries?.length) {
|
|
196
|
+
if (icp.firmographics.industries?.length || icp.firmographics.naics?.length) {
|
|
189
197
|
const inds = [
|
|
190
|
-
...new Set(
|
|
198
|
+
...new Set([
|
|
199
|
+
...(icp.firmographics.industries ?? []).flatMap((i) => CRUSTDATA_INDUSTRY[i.toLowerCase()] ?? [titleCase(i)]),
|
|
200
|
+
...(icp.firmographics.naics ?? []).flatMap((code) => NAICS_CRUSTDATA_INDUSTRY[code] ?? []),
|
|
201
|
+
]),
|
|
191
202
|
];
|
|
192
203
|
if (inds.length)
|
|
193
204
|
f.current_employers_linkedin_industries = inds;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { auditSnapshot, defaultPolicy } from "./audit.ts";
|
|
2
|
+
export { acquireCheckpointId, acquireCheckpointsDir, createFileAcquireCheckpointStore, validateAcquireCheckpoint, validateAcquireCheckpointKey, type AcquireCheckpoint, type AcquireCheckpointKey, type AcquireCheckpointStore, type AcquireContinuation, } from "./acquireCheckpoint.ts";
|
|
2
3
|
export { matchesTerritory, parseAssignmentPolicy, resolveAssignment, type AssignmentContext, type AssignmentPolicy, type AssignmentResult, type AssignmentStrategy, type TerritoryRule, } from "./assign.ts";
|
|
3
4
|
export { buildBulkUpdatePlan, isFilterableField, parseWhere, type BulkUpdateOptions } from "./bulkUpdate.ts";
|
|
4
5
|
export { buildDedupePlan, dedupeKey, type DedupeOptions } from "./dedupe.ts";
|
|
@@ -17,7 +18,9 @@ export { createStripeConnector, fetchStripePaidInvoices, type StripeConnectorOpt
|
|
|
17
18
|
export { buildStripeBackfillPlan, DEFAULT_BACKFILL_MATCH_PROPERTY, type StripeBackfillCounts, type StripeBackfillOptions, type StripeBackfillResult, type StripeBackfillUnmatched, } from "./backfill.ts";
|
|
18
19
|
export { activeProfile, credentialsDir, credentialsPath, DEFAULT_PROFILE, deleteCredential, getCredential, listProfiles, resolveHubspotAccessToken, resolveHubspotConnection, setActiveProfile, storeCredential, type HubspotConnection, type StoredCredential, } from "./credentials.ts";
|
|
19
20
|
export { generateDemoSnapshot, type DemoSnapshotOptions } from "./demo.ts";
|
|
20
|
-
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";
|
|
21
|
+
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 AcquireDiscoveryCheckpoint, 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";
|
|
22
|
+
export { readHostedAcquireCheckpoint, writeHostedAcquireCheckpoint, type HostedAcquireCheckpoint, type HostedCheckpointReadResult, type HostedCheckpointWriteResult, } from "./hostedAcquireCheckpoint.ts";
|
|
23
|
+
export { claimHostedPlanApply, hostedPlanDigest, hostedReviewDocument, readHostedPatchPlan, reconcileHostedPatchPlan, releaseHostedPlanApply, reportHostedPlanLifecycle, uploadHostedPatchPlan } from "./hostedPatchPlan.ts";
|
|
21
24
|
export { scaffoldWorkspace, starterEnrichConfig, starterIcp, starterPlaybook, type InitProvider, type InitSource, type ScaffoldFile, type ScaffoldOptions, } from "./init.ts";
|
|
22
25
|
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";
|
|
23
26
|
export { apolloPullKeysForAppend, apolloPullKeysForRefresh, createApolloClient, pullApolloRecords, type ApolloClient, type ApolloClientOptions, type ApolloPullKey, type ApolloPullResult, } from "./enrichApollo.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { auditSnapshot, defaultPolicy } from "./audit.js";
|
|
2
|
+
export { acquireCheckpointId, acquireCheckpointsDir, createFileAcquireCheckpointStore, validateAcquireCheckpoint, validateAcquireCheckpointKey, } from "./acquireCheckpoint.js";
|
|
2
3
|
export { matchesTerritory, parseAssignmentPolicy, resolveAssignment, } from "./assign.js";
|
|
3
4
|
export { buildBulkUpdatePlan, isFilterableField, parseWhere } from "./bulkUpdate.js";
|
|
4
5
|
export { buildDedupePlan, dedupeKey } from "./dedupe.js";
|
|
@@ -18,6 +19,8 @@ export { buildStripeBackfillPlan, DEFAULT_BACKFILL_MATCH_PROPERTY, } from "./bac
|
|
|
18
19
|
export { activeProfile, credentialsDir, credentialsPath, DEFAULT_PROFILE, deleteCredential, getCredential, listProfiles, resolveHubspotAccessToken, resolveHubspotConnection, setActiveProfile, storeCredential, } from "./credentials.js";
|
|
19
20
|
export { generateDemoSnapshot } from "./demo.js";
|
|
20
21
|
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";
|
|
22
|
+
export { readHostedAcquireCheckpoint, writeHostedAcquireCheckpoint, } from "./hostedAcquireCheckpoint.js";
|
|
23
|
+
export { claimHostedPlanApply, hostedPlanDigest, hostedReviewDocument, readHostedPatchPlan, reconcileHostedPatchPlan, releaseHostedPlanApply, reportHostedPlanLifecycle, uploadHostedPatchPlan } from "./hostedPatchPlan.js";
|
|
21
24
|
export { scaffoldWorkspace, starterEnrichConfig, starterIcp, starterPlaybook, } from "./init.js";
|
|
22
25
|
export { appendCoverage, classifyAccount, classifyCoverage, computeCoverage, coverageCountsFromSnapshot, coveredAccounts, coverageToText, crmCheckableCriteria, deriveAcvFromClosedWon, deriveBuyersPerAccount, estimateTam, loadTamModel, projectEta, readCoverageTimeline, saveTamModel, tamDir, tamReportToMarkdown, } from "./tam.js";
|
|
23
26
|
export { apolloPullKeysForAppend, apolloPullKeysForRefresh, createApolloClient, pullApolloRecords, } from "./enrichApollo.js";
|
package/dist/planStore.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type StoredPlan = {
|
|
|
24
24
|
id: string;
|
|
25
25
|
claimedAt: string;
|
|
26
26
|
revision: number;
|
|
27
|
+
hostedClaimId?: string;
|
|
27
28
|
};
|
|
28
29
|
/** Durable journal of apply ownership. An unresolved entry must never be replayed automatically. */
|
|
29
30
|
applyAttempts?: ApplyAttempt[];
|
|
@@ -40,6 +41,7 @@ export type ApplyAttempt = {
|
|
|
40
41
|
resolvedAt?: string;
|
|
41
42
|
/** Deliberately generic: provider errors can contain CRM data or credentials. */
|
|
42
43
|
note?: string;
|
|
44
|
+
hostedClaimId?: string;
|
|
43
45
|
};
|
|
44
46
|
export interface PlanStore {
|
|
45
47
|
save(plan: PatchPlan): Promise<StoredPlan>;
|
|
@@ -51,11 +53,23 @@ export interface PlanStore {
|
|
|
51
53
|
stored: StoredPlan;
|
|
52
54
|
claimId: string;
|
|
53
55
|
}>;
|
|
56
|
+
recordHostedClaim(planId: string, claimId: string, hostedClaimId: string): Promise<StoredPlan>;
|
|
54
57
|
recordRun(planId: string, run: PatchPlanRun, claimId: string): Promise<StoredPlan>;
|
|
55
58
|
abortApplyPreflight(planId: string, claimId: string, note: string): Promise<StoredPlan>;
|
|
56
59
|
markApplyUncertain(planId: string, claimId: string): Promise<StoredPlan>;
|
|
57
60
|
recoverApply(planId: string): Promise<StoredPlan>;
|
|
61
|
+
reconcileReplica(planId: string, remote: ReplicaPlanState): Promise<{
|
|
62
|
+
stored: StoredPlan;
|
|
63
|
+
changed: boolean;
|
|
64
|
+
}>;
|
|
58
65
|
}
|
|
66
|
+
/** A validated lifecycle snapshot received from another plan replica. */
|
|
67
|
+
export type ReplicaPlanState = {
|
|
68
|
+
status: "approved" | "rejected" | "applied";
|
|
69
|
+
approvedOperationIds: string[];
|
|
70
|
+
valueOverrides?: Record<string, unknown>;
|
|
71
|
+
run?: PatchPlanRun;
|
|
72
|
+
};
|
|
59
73
|
/**
|
|
60
74
|
* Plans as JSON files in a directory (default `$FSGTM_HOME/plans`), one file
|
|
61
75
|
* per plan id. Filesystem-shaped on purpose: greppable, diffable, and any
|
package/dist/planStore.js
CHANGED
|
@@ -216,6 +216,19 @@ export function createFilePlanStore(directory) {
|
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
218
|
},
|
|
219
|
+
async recordHostedClaim(planId, claimId, hostedClaimId) {
|
|
220
|
+
return withPlanLock(planId, () => {
|
|
221
|
+
const stored = mustRead(planId);
|
|
222
|
+
if (stored.status !== "applying" || stored.applyClaim?.id !== claimId) {
|
|
223
|
+
throw new Error(`Refusing to attach hosted claim for plan ${planId}: local apply claim does not match.`);
|
|
224
|
+
}
|
|
225
|
+
return write({
|
|
226
|
+
...stored,
|
|
227
|
+
applyClaim: { ...stored.applyClaim, hostedClaimId },
|
|
228
|
+
applyAttempts: (stored.applyAttempts ?? []).map((attempt) => attempt.id === claimId ? { ...attempt, hostedClaimId } : attempt),
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
},
|
|
219
232
|
async abortApplyPreflight(planId, claimId, note) {
|
|
220
233
|
return withPlanLock(planId, () => {
|
|
221
234
|
const stored = mustRead(planId);
|
|
@@ -266,5 +279,65 @@ export function createFilePlanStore(directory) {
|
|
|
266
279
|
});
|
|
267
280
|
});
|
|
268
281
|
},
|
|
282
|
+
async reconcileReplica(planId, remote) {
|
|
283
|
+
return withPlanLock(planId, () => {
|
|
284
|
+
const stored = mustRead(planId);
|
|
285
|
+
if (stored.status === "applying" || stored.applyClaim) {
|
|
286
|
+
throw new Error(`Plan ${planId} is applying locally; replica reconciliation will retry after it finishes.`);
|
|
287
|
+
}
|
|
288
|
+
const known = new Set(stored.plan.operations.map((operation) => operation.id));
|
|
289
|
+
const approvedOperationIds = Array.from(new Set(remote.approvedOperationIds));
|
|
290
|
+
if (approvedOperationIds.some((id) => !known.has(id))) {
|
|
291
|
+
throw new Error(`Replica state for ${planId} contains an unknown operation.`);
|
|
292
|
+
}
|
|
293
|
+
// Terminal receipts are monotonic. A delayed approval or rejection can
|
|
294
|
+
// never roll an already-applied local replica backwards.
|
|
295
|
+
if (stored.status === "applied" && remote.status !== "applied") {
|
|
296
|
+
return { stored, changed: false };
|
|
297
|
+
}
|
|
298
|
+
if (remote.status === "rejected") {
|
|
299
|
+
if (stored.status === "rejected")
|
|
300
|
+
return { stored, changed: false };
|
|
301
|
+
return { stored: write({
|
|
302
|
+
...stored, status: "rejected", approvedOperationIds: [], valueOverrides: {},
|
|
303
|
+
approvalDigests: undefined, applyClaim: undefined,
|
|
304
|
+
}), changed: true };
|
|
305
|
+
}
|
|
306
|
+
if (approvedOperationIds.length === 0) {
|
|
307
|
+
throw new Error(`Replica ${remote.status} state for ${planId} has no approved operations.`);
|
|
308
|
+
}
|
|
309
|
+
const valueOverrides = remote.valueOverrides ?? {};
|
|
310
|
+
const approvalDigests = computeApprovalDigests(stored.plan.operations, approvedOperationIds, valueOverrides, loadOrCreateSigningKey());
|
|
311
|
+
if (remote.status === "approved") {
|
|
312
|
+
const unchanged = stored.status === "approved"
|
|
313
|
+
&& JSON.stringify(stored.approvedOperationIds) === JSON.stringify(approvedOperationIds)
|
|
314
|
+
&& JSON.stringify(stored.valueOverrides) === JSON.stringify(valueOverrides);
|
|
315
|
+
if (unchanged)
|
|
316
|
+
return { stored, changed: false };
|
|
317
|
+
return { stored: write({
|
|
318
|
+
...stored, status: "approved", approvedOperationIds, valueOverrides, approvalDigests,
|
|
319
|
+
}), changed: true };
|
|
320
|
+
}
|
|
321
|
+
if (!remote.run || remote.run.planId !== planId) {
|
|
322
|
+
throw new Error(`Replica applied state for ${planId} is missing a matching execution receipt.`);
|
|
323
|
+
}
|
|
324
|
+
if (remote.run.results.some((result) => !known.has(result.operationId))) {
|
|
325
|
+
throw new Error(`Replica execution receipt for ${planId} contains an unknown operation.`);
|
|
326
|
+
}
|
|
327
|
+
const runKey = (run) => `${run.startedAt}\0${run.finishedAt}\0${run.provider}`;
|
|
328
|
+
const alreadyRecorded = stored.runs.some((run) => runKey(run) === runKey(remote.run));
|
|
329
|
+
if (stored.status === "applied" && alreadyRecorded)
|
|
330
|
+
return { stored, changed: false };
|
|
331
|
+
return { stored: write({
|
|
332
|
+
...stored,
|
|
333
|
+
status: "applied",
|
|
334
|
+
approvedOperationIds,
|
|
335
|
+
valueOverrides,
|
|
336
|
+
approvalDigests,
|
|
337
|
+
applyClaim: undefined,
|
|
338
|
+
runs: alreadyRecorded ? stored.runs : [...stored.runs, remote.run],
|
|
339
|
+
}), changed: true };
|
|
340
|
+
});
|
|
341
|
+
},
|
|
269
342
|
};
|
|
270
343
|
}
|
package/dist/progress.d.ts
CHANGED
|
@@ -89,8 +89,8 @@ export declare const BACKFILL_STRIPE_STAGES: readonly ["invoices", "snapshot", "
|
|
|
89
89
|
/** `backfill runs` — replaying local plan runs + health history to the broker. */
|
|
90
90
|
export declare const RUNS_REPLAY_STAGES: readonly ["runs", "health"];
|
|
91
91
|
export declare const APPLY_STAGES: readonly ["preflight", "operations", "results"];
|
|
92
|
-
/** `enrich acquire` —
|
|
93
|
-
export declare const ACQUIRE_STAGES: readonly ["
|
|
92
|
+
/** `enrich acquire` — discovery through a governed create plan. */
|
|
93
|
+
export declare const ACQUIRE_STAGES: readonly ["discovery", "resolution", "plan"];
|
|
94
94
|
export declare const MARKET_CAPTURE_STAGES: readonly ["sources", "capture", "classify", "persist"];
|
|
95
95
|
/** No-op emitter for callers that don't care (keeps signatures simple). */
|
|
96
96
|
export declare function nullProgressEmitter(): ProgressEmitter;
|
package/dist/progress.js
CHANGED
|
@@ -133,8 +133,8 @@ export const BACKFILL_STRIPE_STAGES = ["invoices", "snapshot", "matching", "plan
|
|
|
133
133
|
/** `backfill runs` — replaying local plan runs + health history to the broker. */
|
|
134
134
|
export const RUNS_REPLAY_STAGES = ["runs", "health"];
|
|
135
135
|
export const APPLY_STAGES = ["preflight", "operations", "results"];
|
|
136
|
-
/** `enrich acquire` —
|
|
137
|
-
export const ACQUIRE_STAGES = ["
|
|
136
|
+
/** `enrich acquire` — discovery through a governed create plan. */
|
|
137
|
+
export const ACQUIRE_STAGES = ["discovery", "resolution", "plan"];
|
|
138
138
|
export const MARKET_CAPTURE_STAGES = ["sources", "capture", "classify", "persist"];
|
|
139
139
|
/** No-op emitter for callers that don't care (keeps signatures simple). */
|
|
140
140
|
export function nullProgressEmitter() {
|
package/docs/api.md
CHANGED
|
@@ -229,6 +229,15 @@ webhook landing-zone format (docs/signal-spool-format.md).
|
|
|
229
229
|
|
|
230
230
|
## Acquire (net-new lead generation)
|
|
231
231
|
|
|
232
|
+
API discovery is continuation-aware. Completed acquire runs persist a
|
|
233
|
+
provider/source/list/query-keyed Pipe0 cursor, Explorium page, or HeyReach list
|
|
234
|
+
offset, plus the discovered → qualified → deduped → resolved → proposed funnel.
|
|
235
|
+
Each list progresses independently. If a broker credential exists, GET/POST
|
|
236
|
+
`/api/cli/acquisition-checkpoint` synchronizes the opaque checkpoint inside the
|
|
237
|
+
token's organization using numeric compare-and-swap revisions; unpaired and
|
|
238
|
+
offline CLIs remain local-first. A changed ICP/list starts a new traversal. CLI
|
|
239
|
+
`--max` sets desired new leads while `--scan-limit` bounds raw candidates.
|
|
240
|
+
|
|
232
241
|
`buildAcquirePlan` turns sourced-but-unmatched prospects into `create_record`
|
|
233
242
|
operations (matched / ambiguous are skipped — resolve-first never creates over
|
|
234
243
|
a possible dup), capped by the meter's headroom. `builtinAcquirePreset` is the
|
|
@@ -308,6 +317,21 @@ operator reconciliation remains necessary after a timeout or crash.
|
|
|
308
317
|
|
|
309
318
|
## Schedule
|
|
310
319
|
|
|
320
|
+
## Hosted patch-plan mirror
|
|
321
|
+
|
|
322
|
+
`uploadHostedPatchPlan(stored)` mirrors a bounded review document when the
|
|
323
|
+
active profile has a secure broker pairing. `readHostedPatchPlan(planId)` reads
|
|
324
|
+
the org-scoped hosted decision, and `reportHostedPlanLifecycle(stored)` reflects
|
|
325
|
+
local approval/rejection/application. Upload is local-first and best-effort;
|
|
326
|
+
immutable hash conflicts fail closed. Hosted approval does not carry local
|
|
327
|
+
signing material: `apply --plan-id` verifies the hash and operation set before
|
|
328
|
+
`PlanStore.approveOperations` generates machine-local approval digests.
|
|
329
|
+
|
|
330
|
+
The hosted transport intentionally excludes findings, evidence, signing
|
|
331
|
+
digests/keys, apply claims and attempt notes, provider results, and raw run
|
|
332
|
+
errors. Operation before/after values are included because the paired
|
|
333
|
+
organization needs them to review the proposed CRM changes.
|
|
334
|
+
|
|
311
335
|
The horizontal scheduler: a declarative schedule-entry store, a
|
|
312
336
|
dependency-free 5-field cron parser, and the read/plan-side `SCHEDULABLE`
|
|
313
337
|
allowlist. `validateSchedulableArgv` enforces the allowlist at `schedule add`
|
package/docs/architecture.md
CHANGED
|
@@ -94,6 +94,15 @@ replays automatically: reconcile provider state, then `plans recover ...
|
|
|
94
94
|
translation, prospect fit scoring, and the agent-driven interview spec.
|
|
95
95
|
- `acquireMeter.ts` — per-profile windowed budget (records + spend) for acquire.
|
|
96
96
|
- `acquireSeen.ts` — cross-run "seen" cache so re-runs don't re-pay for dupes.
|
|
97
|
+
- `acquireCheckpoint.ts` — secure profile-scoped, provider/source/list/query-keyed
|
|
98
|
+
continuation store; each audience advances independently.
|
|
99
|
+
- `hostedAcquireCheckpoint.ts` — optional broker-authenticated checkpoint
|
|
100
|
+
transport. Organization identity is resolved server-side; numeric CAS
|
|
101
|
+
revisions prevent stale workers from overwriting newer continuation.
|
|
102
|
+
- `hostedPatchPlan.ts` — best-effort paired-CLI plan mirroring and decision
|
|
103
|
+
reconciliation. Review documents are immutable/hash-bound; hosted approval
|
|
104
|
+
becomes executable only after the CLI verifies it and generates local HMAC
|
|
105
|
+
signatures. Hosted never owns the apply lease for CLI-origin plans.
|
|
97
106
|
- `assign.ts` — `AssignmentPolicy` (fixed / round-robin / territory /
|
|
98
107
|
account-owner): the pure owner-routing rule `buildAcquirePlan` stamps onto new
|
|
99
108
|
leads (never born ownerless) and `reassign --assign-unowned` reuses to backfill.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fullstackgtm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.1",
|
|
4
4
|
"description": "Open-source agentic GTM ops framework: canonical GTM data model, pluggable deterministic audits, reviewable dry-run patch plans, approval-gated write-back with conflict detection, and cross-system entity resolution. HubSpot, Salesforce, and Stripe connectors included.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Full Stack GTM LLC <ryan@fullstackgtm.com> (https://fullstackgtm.com)",
|
|
@@ -63,7 +63,7 @@ credentials AND stored plans per client org.
|
|
|
63
63
|
| `backfill stripe\|runs [--since <iso>] [--pipeline <id\|label>]` | Paid Stripe invoices → proposed closed-won deals (amount = invoice total, close date = paid date, company matched by billing-email domain then name); deduped by a `stripe_invoice_id` deal property re-resolved at apply, so re-running never double-creates; a customer the CRM doesn't know gets a proposed account create in the same plan (freemail domains never used; `--skip-unmatched` = report-only); `--save` → approve → `apply`. `backfill runs` replays LOCAL run history + health timeline to the paired hosted app (idempotent; `--dry-run` to preview) |
|
|
64
64
|
| `call parse\|score\|link\|plan` | Transcripts → evidence-quoted insights, rubric scorecards, deal linking, governed next-step writes |
|
|
65
65
|
| `enrich append\|refresh\|ingest\|status` | Governed enrichment (Apollo pull / Clay ingest), fill-blanks-only plans |
|
|
66
|
-
| `enrich acquire [--source explorium\|pipe0\|linkedin]` | Net-new ICP-targeted lead gen:
|
|
66
|
+
| `enrich acquire [--source explorium\|pipe0\|linkedin] [--max <new>] [--scan-limit <raw>]` | Net-new ICP-targeted lead gen: independent per-provider/list/query checkpoints traverse full audiences instead of rereading page one; paired CLIs CAS-sync opaque, non-PII continuation to the hosted org; resolve-first deduped `create_record` plans, meter-capped and owner-stamped; `enrich status --runs` exposes funnel + continuation; never auto-writes |
|
|
67
67
|
| `signals fetch\|list\|outcome\|weights` | Detect-side timing layer: capture fresh buying triggers into a profile-scoped ledger (free Greenhouse/Lever/Ashby ATS in the box; `--from` staged files; `--connector file\|serpapi-news\|hubspot-forms` to pull connected platforms / read the webhook spool `<home>/signals/spool`, secrets via the credential ladder), ranked by learned weights. Writes NOTHING to the CRM; `outcome --account <domain> --contact <id> --result …` re-weights which triggers earn a touch |
|
|
68
68
|
| `icp interview\|set\|show\|judge\|eval` | Build the ICP, then `judge` ranks fresh signals into send/nurture/skip — pass a snapshot (`--provider`/`--input`) and each decision resolves its CRM target (`accountId` + the `contact`/`contacts` to reach). `eval` is the calibration gate (exit 2 below the bar) |
|
|
69
69
|
| `draft [--from-judge latest] [--channel email\|linkedin\|task]` | One trigger-grounded opener per hot account → a governed `create_task` targeting the resolved `contact.id` (or `accountId`); rejects a domain-only decision ("acquire it first"). **Never sends** — after `plans approve`, `apply --provider <crm>` logs it as a CRM task, or `apply --channel outbox` renders it to `<home>/signals/outbox/<channel>.jsonl` for a downstream sender to drain; the CLI transmits nothing |
|