salesprompter-cli 0.1.70 → 0.1.72
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/README.md +3 -1
- package/dist/cli.js +123 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,7 @@ salesprompter affiliate:grow \
|
|
|
112
112
|
# Verify live campaign state, lead safety, and contact activity, then activate explicitly
|
|
113
113
|
salesprompter affiliate:list
|
|
114
114
|
salesprompter affiliate:status "$AFFILIATE_RUN_ID"
|
|
115
|
+
salesprompter affiliate:finish "$AFFILIATE_RUN_ID"
|
|
115
116
|
salesprompter affiliate:analytics "$AFFILIATE_RUN_ID"
|
|
116
117
|
salesprompter affiliate:regenerate-sequence "$AFFILIATE_RUN_ID"
|
|
117
118
|
salesprompter affiliate:enrich "$AFFILIATE_RUN_ID"
|
|
@@ -174,11 +175,12 @@ the app's CLI imports view.
|
|
|
174
175
|
- Respect provider terms and customer data boundaries.
|
|
175
176
|
- `affiliate:grow` is an alias for `affiliate:run`. It excludes repeated `--seed-profile` customers, removes profiles already stored in prior workspace affiliate audiences, and verifies the final persisted count before preparing Instantly.
|
|
176
177
|
- If every collected profile is known or excluded, no audience or Instantly campaign is created. A stale Sales Navigator session also fails before persistence with a safe retry instruction.
|
|
177
|
-
- Affiliate outreach uses
|
|
178
|
+
- Affiliate outreach uses Hunter first, requires Hunter's `valid` verdict before import, and sends recovered addresses through Hunter Email Verifier before creating a draft Instantly campaign. Catch-all, disposable, webmail, invalid, unknown, claimed, and unresolved addresses are excluded. Instantly verification remains enabled as a second gate.
|
|
178
179
|
- Affiliate preparation uses exactly three emails with three observable variants per step; `affiliate:regenerate-sequence` rebuilds and syncs them through the Salesprompter app.
|
|
179
180
|
- `affiliate:list` shows safe workspace summaries. `affiliate:analytics` shows aggregate and step/variant outcomes without exposing lead data; unsupported per-step meeting and won fields render as `n/a`.
|
|
180
181
|
- `affiliate:run` defaults to `--timing-mode custom`, so its daily limit and weekday sending window are honored. Use `--timing-mode auto` only when capacity-derived scheduling is intended.
|
|
181
182
|
- `affiliate:enrich` starts durable email recovery and adds only newly found addresses to an existing campaign. It returns a background-processing status for large audiences instead of blocking the terminal. Pass `--source-audience-run-id` to merge a newer compatible audience into that campaign without creating a duplicate campaign.
|
|
183
|
+
- `affiliate:finish` closes the loop: it requires persisted Hunter-valid evidence for new campaigns, repairs stalled Instantly verification in 1,000-lead batches, quarantines stale pending or invalid leads outside the sending campaign, accepts an explicitly audited recovery fallback, activates only a fully verified campaign, and reads the live state back. Use `--requeue-pending` only after restoring Instantly verification capacity.
|
|
182
184
|
- A draft campaign does not send until `affiliate:activate` is run.
|
|
183
185
|
- The CLI is designed for interactive users and agent-assisted workflows.
|
|
184
186
|
- A repo-local Chrome extension compatibility copy is available in `chrome-extension/` for local LinkedIn session sync and popup copy/debug flows.
|
package/dist/cli.js
CHANGED
|
@@ -283,6 +283,8 @@ const AffiliateOutreachHealthSchema = z.object({
|
|
|
283
283
|
pendingVerification: z.number().int().nonnegative(),
|
|
284
284
|
invalid: z.number().int().nonnegative(),
|
|
285
285
|
unsafe: z.number().int().nonnegative(),
|
|
286
|
+
hunterVerified: z.number().int().nonnegative().default(0),
|
|
287
|
+
hunterEvidenceMissing: z.number().int().nonnegative().default(0),
|
|
286
288
|
contacted: z.number().int().nonnegative(),
|
|
287
289
|
replied: z.number().int().nonnegative(),
|
|
288
290
|
statusCounts: z.record(z.string(), z.number().int().nonnegative()),
|
|
@@ -298,6 +300,21 @@ const AffiliateOutreachHealthSchema = z.object({
|
|
|
298
300
|
const AffiliateOutreachLiveStatusResponseSchema = AffiliateOutreachResponseSchema.extend({
|
|
299
301
|
health: AffiliateOutreachHealthSchema
|
|
300
302
|
});
|
|
303
|
+
const AffiliateVerificationRepairResponseSchema = AffiliateOutreachResponseSchema.extend({
|
|
304
|
+
repair: z.object({
|
|
305
|
+
checkedAt: DatabaseTimestampSchema,
|
|
306
|
+
beforeCount: z.number().int().nonnegative(),
|
|
307
|
+
afterCount: z.number().int().nonnegative(),
|
|
308
|
+
restoredMissing: z.number().int().nonnegative(),
|
|
309
|
+
requeuedPending: z.number().int().nonnegative(),
|
|
310
|
+
quarantinedPending: z.number().int().nonnegative(),
|
|
311
|
+
prunedInvalid: z.number().int().nonnegative(),
|
|
312
|
+
pendingAfter: z.number().int().nonnegative(),
|
|
313
|
+
invalidAfter: z.number().int().nonnegative(),
|
|
314
|
+
verifiedAfter: z.number().int().nonnegative(),
|
|
315
|
+
dataPreparationAccepted: z.boolean()
|
|
316
|
+
})
|
|
317
|
+
});
|
|
301
318
|
const AffiliateOutreachSummarySchema = AffiliateOutreachRunBaseSchema;
|
|
302
319
|
const AffiliateAudienceSummarySchema = z.object({
|
|
303
320
|
public_token: z.string().min(1),
|
|
@@ -710,6 +727,7 @@ const cliPacks = [
|
|
|
710
727
|
"affiliate:run",
|
|
711
728
|
"affiliate:list",
|
|
712
729
|
"affiliate:status",
|
|
730
|
+
"affiliate:finish",
|
|
713
731
|
"affiliate:analytics",
|
|
714
732
|
"affiliate:regenerate-sequence",
|
|
715
733
|
"affiliate:enrich",
|
|
@@ -770,6 +788,7 @@ const helpVisibleCommandNames = new Set([
|
|
|
770
788
|
"affiliate:run",
|
|
771
789
|
"affiliate:list",
|
|
772
790
|
"affiliate:status",
|
|
791
|
+
"affiliate:finish",
|
|
773
792
|
"affiliate:analytics",
|
|
774
793
|
"affiliate:regenerate-sequence",
|
|
775
794
|
"affiliate:enrich",
|
|
@@ -6828,20 +6847,81 @@ async function getAffiliateOutreachLiveStatusViaApp(session, audienceRunId) {
|
|
|
6828
6847
|
}, AffiliateOutreachLiveStatusResponseSchema);
|
|
6829
6848
|
return value;
|
|
6830
6849
|
}
|
|
6850
|
+
async function repairAffiliateOutreachVerificationViaApp(session, audienceRunId, options) {
|
|
6851
|
+
const { value } = await fetchCliJson(session, (currentSession) => fetch(`${currentSession.apiBaseUrl}/api/cli/affiliate-outreach/${encodeURIComponent(audienceRunId)}/repair-verification`, {
|
|
6852
|
+
method: "POST",
|
|
6853
|
+
headers: {
|
|
6854
|
+
"Content-Type": "application/json",
|
|
6855
|
+
Authorization: `Bearer ${currentSession.accessToken}`
|
|
6856
|
+
},
|
|
6857
|
+
body: JSON.stringify({
|
|
6858
|
+
...options,
|
|
6859
|
+
pruneInvalid: true
|
|
6860
|
+
})
|
|
6861
|
+
}), AffiliateVerificationRepairResponseSchema);
|
|
6862
|
+
return value;
|
|
6863
|
+
}
|
|
6864
|
+
async function finishAffiliateOutreachViaApp(session, audienceRunId, options) {
|
|
6865
|
+
let live = await getAffiliateOutreachLiveStatusViaApp(session, audienceRunId);
|
|
6866
|
+
const repairs = [];
|
|
6867
|
+
for (let batch = 0; batch < options.maxRepairBatches; batch += 1) {
|
|
6868
|
+
if (live.outreach.status === "active" || live.health.activationReady) {
|
|
6869
|
+
break;
|
|
6870
|
+
}
|
|
6871
|
+
if (live.outreach.status !== "ready" || !live.health.liveChecked || !live.health.leads) {
|
|
6872
|
+
throw new Error(describeAffiliateOutreachHealth(live.health));
|
|
6873
|
+
}
|
|
6874
|
+
const beforeUnsafe = live.health.leads.unsafe;
|
|
6875
|
+
const needsRepair = beforeUnsafe > 0 || !live.health.dataPreparationComplete;
|
|
6876
|
+
if (!needsRepair) {
|
|
6877
|
+
break;
|
|
6878
|
+
}
|
|
6879
|
+
const repaired = await repairAffiliateOutreachVerificationViaApp(session, audienceRunId, {
|
|
6880
|
+
staleAfterMinutes: options.staleAfterMinutes,
|
|
6881
|
+
maxPendingRequeues: 1_000,
|
|
6882
|
+
quarantinePending: !options.requeuePending,
|
|
6883
|
+
acceptIncompleteRecovery: true
|
|
6884
|
+
});
|
|
6885
|
+
repairs.push(repaired.repair);
|
|
6886
|
+
live = await getAffiliateOutreachLiveStatusViaApp(session, audienceRunId);
|
|
6887
|
+
const afterUnsafe = live.health.leads?.unsafe ?? beforeUnsafe;
|
|
6888
|
+
if (!live.health.activationReady &&
|
|
6889
|
+
afterUnsafe >= beforeUnsafe &&
|
|
6890
|
+
live.health.dataPreparationComplete) {
|
|
6891
|
+
throw new Error(`${describeAffiliateOutreachHealth(live.health)} Pending leads may not be old enough for safe automatic repair yet.`);
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
if (live.outreach.status !== "active") {
|
|
6895
|
+
if (!live.health.activationReady) {
|
|
6896
|
+
throw new Error(`${describeAffiliateOutreachHealth(live.health)} The safe activation gate remains closed.`);
|
|
6897
|
+
}
|
|
6898
|
+
await activateAffiliateOutreachViaApp(session, audienceRunId);
|
|
6899
|
+
live = await getAffiliateOutreachLiveStatusViaApp(session, audienceRunId);
|
|
6900
|
+
}
|
|
6901
|
+
if (!live.health.running || !live.health.safeToSend) {
|
|
6902
|
+
throw new Error(`Activation could not be verified safely. ${describeAffiliateOutreachHealth(live.health)}`);
|
|
6903
|
+
}
|
|
6904
|
+
return { live, repairs };
|
|
6905
|
+
}
|
|
6831
6906
|
function describeAffiliateOutreachHealth(health) {
|
|
6832
6907
|
const leads = health.leads;
|
|
6908
|
+
const hunterSummary = leads
|
|
6909
|
+
? leads.hunterEvidenceMissing > 0
|
|
6910
|
+
? `Hunter evidence recorded for ${leads.hunterVerified}/${leads.total} leads (${leads.hunterEvidenceMissing} legacy or missing)`
|
|
6911
|
+
: `${leads.hunterVerified}/${leads.total} Hunter-valid`
|
|
6912
|
+
: null;
|
|
6833
6913
|
if (health.done && leads) {
|
|
6834
|
-
return `Done: the campaign is running with ${leads.verified}/${leads.total} verified leads.`;
|
|
6914
|
+
return `Done: the campaign is running with ${leads.verified}/${leads.total} Instantly-verified leads; ${hunterSummary}.`;
|
|
6835
6915
|
}
|
|
6836
6916
|
if (health.verdict === "unsafe_running" && leads) {
|
|
6837
6917
|
return `Attention: the campaign is running with ${leads.unsafe} unsafe leads.`;
|
|
6838
6918
|
}
|
|
6839
6919
|
if (health.verdict === "ready_to_activate" && leads) {
|
|
6840
|
-
return `Ready to activate: ${leads.verified}/${leads.total}
|
|
6920
|
+
return `Ready to activate: ${leads.verified}/${leads.total} Instantly-verified; ${hunterSummary}.`;
|
|
6841
6921
|
}
|
|
6842
6922
|
if (health.liveChecked && leads) {
|
|
6843
6923
|
const campaignState = health.campaign?.statusLabel ?? "unknown";
|
|
6844
|
-
return `Not done: the campaign is ${campaignState}; ${leads.verified}/${leads.total}
|
|
6924
|
+
return `Not done: the campaign is ${campaignState}; ${leads.verified}/${leads.total} Instantly-verified; ${hunterSummary}; ${leads.contacted} contacted.`;
|
|
6845
6925
|
}
|
|
6846
6926
|
return health.blockers[0]?.message ?? "Live campaign status could not be verified.";
|
|
6847
6927
|
}
|
|
@@ -13912,6 +13992,9 @@ function parseAffiliateOutreachCommandOptions(options) {
|
|
|
13912
13992
|
if (!parsedTimingMode.success) {
|
|
13913
13993
|
throw new Error("--timing-mode must be either auto or custom.");
|
|
13914
13994
|
}
|
|
13995
|
+
if (options.allowAcceptAll) {
|
|
13996
|
+
throw new Error("--allow-accept-all is no longer supported. Affiliate outreach requires Hunter status valid.");
|
|
13997
|
+
}
|
|
13915
13998
|
const trimmedCampaignName = options.campaignName?.trim();
|
|
13916
13999
|
const campaignName = trimmedCampaignName
|
|
13917
14000
|
? z.string().max(120, "--campaign-name must be 120 characters or fewer.").parse(trimmedCampaignName)
|
|
@@ -13921,7 +14004,7 @@ function parseAffiliateOutreachCommandOptions(options) {
|
|
|
13921
14004
|
language: z.string().trim().min(1).max(40).parse(options.language),
|
|
13922
14005
|
numberOfSteps: 3,
|
|
13923
14006
|
minEmailScore: z.coerce.number().int().min(0).max(100).parse(options.minEmailScore),
|
|
13924
|
-
allowAcceptAll:
|
|
14007
|
+
allowAcceptAll: false,
|
|
13925
14008
|
timingMode: parsedTimingMode.data,
|
|
13926
14009
|
dailyLimit: z.coerce.number().int().min(1).max(1000).parse(options.dailyLimit),
|
|
13927
14010
|
timezone: z.string().trim().min(1).max(100).parse(options.timezone),
|
|
@@ -15096,7 +15179,7 @@ addAffiliateAudienceOptions(program
|
|
|
15096
15179
|
.option("--language <language>", "Sequence language", "English")
|
|
15097
15180
|
.option("--steps <number>", "Exactly 3 sequence emails (fixed)", "3")
|
|
15098
15181
|
.option("--min-email-score <number>", "Minimum Hunter confidence score", "80")
|
|
15099
|
-
.option("--allow-accept-all", "
|
|
15182
|
+
.option("--allow-accept-all", "Deprecated: catch-all emails are rejected", false)
|
|
15100
15183
|
.option("--timing-mode <mode>", "Schedule mode: custom honors the daily limit and sending window; auto derives them from capacity", "custom")
|
|
15101
15184
|
.option("--daily-limit <number>", "Maximum new leads contacted per day", "25")
|
|
15102
15185
|
.option("--timezone <timezone>", "Instantly sending timezone", "America/Detroit")
|
|
@@ -15170,6 +15253,13 @@ addAffiliateAudienceOptions(program
|
|
|
15170
15253
|
? "complete"
|
|
15171
15254
|
: "not_reported",
|
|
15172
15255
|
persistence: "verified",
|
|
15256
|
+
emailVerification: {
|
|
15257
|
+
provider: "hunter",
|
|
15258
|
+
policy: "valid_only",
|
|
15259
|
+
verified: Number(finalOutreach.stats.hunterVerifiedEmails ?? 0),
|
|
15260
|
+
rejected: Number(finalOutreach.stats.hunterRejectedEmails ?? 0),
|
|
15261
|
+
secondaryProvider: "instantly"
|
|
15262
|
+
},
|
|
15173
15263
|
outreach: finalOutreach.status,
|
|
15174
15264
|
activation: finalOutreach.status === "active" ? "complete" : "not_requested",
|
|
15175
15265
|
},
|
|
@@ -15201,6 +15291,34 @@ program
|
|
|
15201
15291
|
outreach: result.outreach
|
|
15202
15292
|
});
|
|
15203
15293
|
});
|
|
15294
|
+
program
|
|
15295
|
+
.command("affiliate:finish <run-id>")
|
|
15296
|
+
.description("Repair stalled verification, activate safely, and verify the live campaign.")
|
|
15297
|
+
.option("--stale-after-minutes <number>", "Age before a pending verification can be repaired or quarantined", "15")
|
|
15298
|
+
.option("--max-repair-batches <number>", "Maximum 1,000-lead repair batches", "125")
|
|
15299
|
+
.option("--requeue-pending", "Retry stalled verification instead of quarantining it outside the sending campaign", false)
|
|
15300
|
+
.action(async (runId, options) => {
|
|
15301
|
+
const session = await requireAuthSession();
|
|
15302
|
+
const audienceRunId = z.string().uuid().parse(runId);
|
|
15303
|
+
const result = await finishAffiliateOutreachViaApp(session, audienceRunId, {
|
|
15304
|
+
staleAfterMinutes: z.coerce.number().int().min(15).max(30 * 24 * 60).parse(options.staleAfterMinutes),
|
|
15305
|
+
maxRepairBatches: z.coerce.number().int().min(1).max(250).parse(options.maxRepairBatches),
|
|
15306
|
+
requeuePending: Boolean(options.requeuePending)
|
|
15307
|
+
});
|
|
15308
|
+
const leads = result.live.health.leads;
|
|
15309
|
+
printOutput({
|
|
15310
|
+
status: "ok",
|
|
15311
|
+
running: result.live.health.running,
|
|
15312
|
+
safeToSend: result.live.health.safeToSend,
|
|
15313
|
+
done: result.live.health.done,
|
|
15314
|
+
repairs: result.repairs,
|
|
15315
|
+
health: result.live.health,
|
|
15316
|
+
outreach: result.live.outreach,
|
|
15317
|
+
message: result.live.health.done
|
|
15318
|
+
? describeAffiliateOutreachHealth(result.live.health)
|
|
15319
|
+
: `The campaign is active and safe with ${leads?.verified ?? 0}/${leads?.total ?? 0} verified leads; sending will begin in its configured schedule.`
|
|
15320
|
+
});
|
|
15321
|
+
});
|
|
15204
15322
|
program
|
|
15205
15323
|
.command("affiliate:analytics <run-id>")
|
|
15206
15324
|
.description("Show safe campaign and step/variant performance metrics for an affiliate run.")
|
package/package.json
CHANGED