salesprompter-cli 0.1.70 → 0.1.71

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +101 -0
  3. 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"
@@ -179,6 +180,7 @@ the app's CLI imports view.
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 repairs stalled 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
@@ -298,6 +298,21 @@ const AffiliateOutreachHealthSchema = z.object({
298
298
  const AffiliateOutreachLiveStatusResponseSchema = AffiliateOutreachResponseSchema.extend({
299
299
  health: AffiliateOutreachHealthSchema
300
300
  });
301
+ const AffiliateVerificationRepairResponseSchema = AffiliateOutreachResponseSchema.extend({
302
+ repair: z.object({
303
+ checkedAt: DatabaseTimestampSchema,
304
+ beforeCount: z.number().int().nonnegative(),
305
+ afterCount: z.number().int().nonnegative(),
306
+ restoredMissing: z.number().int().nonnegative(),
307
+ requeuedPending: z.number().int().nonnegative(),
308
+ quarantinedPending: z.number().int().nonnegative(),
309
+ prunedInvalid: z.number().int().nonnegative(),
310
+ pendingAfter: z.number().int().nonnegative(),
311
+ invalidAfter: z.number().int().nonnegative(),
312
+ verifiedAfter: z.number().int().nonnegative(),
313
+ dataPreparationAccepted: z.boolean()
314
+ })
315
+ });
301
316
  const AffiliateOutreachSummarySchema = AffiliateOutreachRunBaseSchema;
302
317
  const AffiliateAudienceSummarySchema = z.object({
303
318
  public_token: z.string().min(1),
@@ -710,6 +725,7 @@ const cliPacks = [
710
725
  "affiliate:run",
711
726
  "affiliate:list",
712
727
  "affiliate:status",
728
+ "affiliate:finish",
713
729
  "affiliate:analytics",
714
730
  "affiliate:regenerate-sequence",
715
731
  "affiliate:enrich",
@@ -770,6 +786,7 @@ const helpVisibleCommandNames = new Set([
770
786
  "affiliate:run",
771
787
  "affiliate:list",
772
788
  "affiliate:status",
789
+ "affiliate:finish",
773
790
  "affiliate:analytics",
774
791
  "affiliate:regenerate-sequence",
775
792
  "affiliate:enrich",
@@ -6828,6 +6845,62 @@ async function getAffiliateOutreachLiveStatusViaApp(session, audienceRunId) {
6828
6845
  }, AffiliateOutreachLiveStatusResponseSchema);
6829
6846
  return value;
6830
6847
  }
6848
+ async function repairAffiliateOutreachVerificationViaApp(session, audienceRunId, options) {
6849
+ const { value } = await fetchCliJson(session, (currentSession) => fetch(`${currentSession.apiBaseUrl}/api/cli/affiliate-outreach/${encodeURIComponent(audienceRunId)}/repair-verification`, {
6850
+ method: "POST",
6851
+ headers: {
6852
+ "Content-Type": "application/json",
6853
+ Authorization: `Bearer ${currentSession.accessToken}`
6854
+ },
6855
+ body: JSON.stringify({
6856
+ ...options,
6857
+ pruneInvalid: true
6858
+ })
6859
+ }), AffiliateVerificationRepairResponseSchema);
6860
+ return value;
6861
+ }
6862
+ async function finishAffiliateOutreachViaApp(session, audienceRunId, options) {
6863
+ let live = await getAffiliateOutreachLiveStatusViaApp(session, audienceRunId);
6864
+ const repairs = [];
6865
+ for (let batch = 0; batch < options.maxRepairBatches; batch += 1) {
6866
+ if (live.outreach.status === "active" || live.health.activationReady) {
6867
+ break;
6868
+ }
6869
+ if (live.outreach.status !== "ready" || !live.health.liveChecked || !live.health.leads) {
6870
+ throw new Error(describeAffiliateOutreachHealth(live.health));
6871
+ }
6872
+ const beforeUnsafe = live.health.leads.unsafe;
6873
+ const needsRepair = beforeUnsafe > 0 || !live.health.dataPreparationComplete;
6874
+ if (!needsRepair) {
6875
+ break;
6876
+ }
6877
+ const repaired = await repairAffiliateOutreachVerificationViaApp(session, audienceRunId, {
6878
+ staleAfterMinutes: options.staleAfterMinutes,
6879
+ maxPendingRequeues: 1_000,
6880
+ quarantinePending: !options.requeuePending,
6881
+ acceptIncompleteRecovery: true
6882
+ });
6883
+ repairs.push(repaired.repair);
6884
+ live = await getAffiliateOutreachLiveStatusViaApp(session, audienceRunId);
6885
+ const afterUnsafe = live.health.leads?.unsafe ?? beforeUnsafe;
6886
+ if (!live.health.activationReady &&
6887
+ afterUnsafe >= beforeUnsafe &&
6888
+ live.health.dataPreparationComplete) {
6889
+ throw new Error(`${describeAffiliateOutreachHealth(live.health)} Pending leads may not be old enough for safe automatic repair yet.`);
6890
+ }
6891
+ }
6892
+ if (live.outreach.status !== "active") {
6893
+ if (!live.health.activationReady) {
6894
+ throw new Error(`${describeAffiliateOutreachHealth(live.health)} The safe activation gate remains closed.`);
6895
+ }
6896
+ await activateAffiliateOutreachViaApp(session, audienceRunId);
6897
+ live = await getAffiliateOutreachLiveStatusViaApp(session, audienceRunId);
6898
+ }
6899
+ if (!live.health.running || !live.health.safeToSend) {
6900
+ throw new Error(`Activation could not be verified safely. ${describeAffiliateOutreachHealth(live.health)}`);
6901
+ }
6902
+ return { live, repairs };
6903
+ }
6831
6904
  function describeAffiliateOutreachHealth(health) {
6832
6905
  const leads = health.leads;
6833
6906
  if (health.done && leads) {
@@ -15201,6 +15274,34 @@ program
15201
15274
  outreach: result.outreach
15202
15275
  });
15203
15276
  });
15277
+ program
15278
+ .command("affiliate:finish <run-id>")
15279
+ .description("Repair stalled verification, activate safely, and verify the live campaign.")
15280
+ .option("--stale-after-minutes <number>", "Age before a pending verification can be repaired or quarantined", "15")
15281
+ .option("--max-repair-batches <number>", "Maximum 1,000-lead repair batches", "125")
15282
+ .option("--requeue-pending", "Retry stalled verification instead of quarantining it outside the sending campaign", false)
15283
+ .action(async (runId, options) => {
15284
+ const session = await requireAuthSession();
15285
+ const audienceRunId = z.string().uuid().parse(runId);
15286
+ const result = await finishAffiliateOutreachViaApp(session, audienceRunId, {
15287
+ staleAfterMinutes: z.coerce.number().int().min(15).max(30 * 24 * 60).parse(options.staleAfterMinutes),
15288
+ maxRepairBatches: z.coerce.number().int().min(1).max(250).parse(options.maxRepairBatches),
15289
+ requeuePending: Boolean(options.requeuePending)
15290
+ });
15291
+ const leads = result.live.health.leads;
15292
+ printOutput({
15293
+ status: "ok",
15294
+ running: result.live.health.running,
15295
+ safeToSend: result.live.health.safeToSend,
15296
+ done: result.live.health.done,
15297
+ repairs: result.repairs,
15298
+ health: result.live.health,
15299
+ outreach: result.live.outreach,
15300
+ message: result.live.health.done
15301
+ ? describeAffiliateOutreachHealth(result.live.health)
15302
+ : `The campaign is active and safe with ${leads?.verified ?? 0}/${leads?.total ?? 0} verified leads; sending will begin in its configured schedule.`
15303
+ });
15304
+ });
15204
15305
  program
15205
15306
  .command("affiliate:analytics <run-id>")
15206
15307
  .description("Show safe campaign and step/variant performance metrics for an affiliate run.")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.70",
3
+ "version": "0.1.71",
4
4
  "description": "Sales workflow CLI for guided lead generation, enrichment, scoring, and sync.",
5
5
  "author": "Daniel Sinewe <hello@danielsinewe.com>",
6
6
  "type": "module",