salesprompter-cli 0.1.74 → 0.1.75

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 CHANGED
@@ -42,6 +42,8 @@ For headless or automation use, generate a CLI token in the app and run `salespr
42
42
 
43
43
  `leads:at-companies` researches Director, Head-of, VP and C-level contacts across functions. It exports a shortlist for review without starting email enrichment or outreach.
44
44
 
45
+ If LinkedIn rejects the saved session, the command tries a different local extension-synced session once and keeps completed searches. If none works, reconnect the extension or use `--browser-relay-port` with a signed-in browser worker. Rate limits stop the run without switching sessions.
46
+
45
47
  Create a JSON brief with verified numeric LinkedIn company IDs. Use each subsidiary's own ID; names alone are not an employer match. Missing IDs stay in the coverage report as unresolved.
46
48
 
47
49
  ```json
package/dist/cli.js CHANGED
@@ -16,6 +16,7 @@ import { Command } from "commander";
16
16
  import { z } from "zod";
17
17
  import { AffiliateCopyReviewSchema, renderAffiliateCopyReview } from "./affiliate-copy.js";
18
18
  import { CompanyBriefSchema, planCompanySearches, runCompanyResearch } from "./company-leads.js";
19
+ import { createSessionRecovery } from "./session-recovery.js";
19
20
  import { clearAuthSession, loginWithBrowserConnect, loginWithDeviceFlow, loginWithToken, readAuthSession, requireAuthSession, shouldBypassAuth, verifySession, writeAuthSession } from "./auth.js";
20
21
  import { buildBigQueryLeadLookupSql, executeBigQuerySql, normalizeBigQueryLeadRows, runBigQueryQuery, runBigQueryRows } from "./bigquery.js";
21
22
  import { AccountProfileSchema, EnrichedLeadSchema, IcpSchema, LeadSchema, ScoredLeadSchema, SyncTargetSchema } from "./domain.js";
@@ -15157,6 +15158,16 @@ program
15157
15158
  const port = options.browserRelayPort == null ? null : z.coerce.number().int().min(1).max(65535).parse(options.browserRelayPort);
15158
15159
  const relay = port == null ? null : await createLocalAccountSearchBrowserRelay(port);
15159
15160
  const config = relay ? null : await readLinkedInDirectLookupConfig();
15161
+ const recoverSession = createSessionRecovery({
15162
+ initial: config,
15163
+ isAuthError: error => error instanceof LocalSalesNavigatorHttpError && (error.status === 401 || error.status === 403),
15164
+ refresh: async () => relay || shouldDisableLinkedInDirectLookupAutodiscovery() ? null : readLocalLinkedInExtensionDirectLookupConfig(),
15165
+ sameSession: (a, b) => a?.cookie === b?.cookie && a?.csrfToken === b?.csrfToken,
15166
+ beforeRetry: async () => {
15167
+ writeProgress("Saved LinkedIn session was rejected; trying the latest extension-synced session once.");
15168
+ await delay(randomIntegerBetween(5000, 8000));
15169
+ },
15170
+ });
15160
15171
  let startedSearches = 0;
15161
15172
  try {
15162
15173
  const report = await runCompanyResearch({ brief, outDir: path.resolve(options.outDir), scope: `${session.apiBaseUrl}:${orgId}`, maxSearches,
@@ -15164,13 +15175,15 @@ program
15164
15175
  if (startedSearches++ > 0)
15165
15176
  await delay(randomIntegerBetween(5000, 8000));
15166
15177
  process.stderr.write(`Researching ${job.company.name} — ${job.department.name}\n`);
15167
- const request = relay
15168
- ? { url: buildSalesNavigatorLeadApiUrlFromSearchUrl(job.queryUrl, 100), headers: {} }
15169
- : buildSalesNavigatorApiRequestFromSearchUrl(job.queryUrl, config, 100);
15170
- return fetchAllLocalSalesNavigatorPeople(request, {
15171
- requestedProfiles: brief.candidatesPerDepartment, pageSize: 100, pageDelayMinMs: 5000, pageDelayMaxMs: 8000,
15172
- retry: { maxRetries: 2, retryBaseDelayMs: 2000, retryMaxDelayMs: 10000 },
15173
- executeRequest: relay ? request => relay.request(request) : undefined,
15178
+ return recoverSession(async (activeConfig) => {
15179
+ const request = relay
15180
+ ? { url: buildSalesNavigatorLeadApiUrlFromSearchUrl(job.queryUrl, 100), headers: {} }
15181
+ : buildSalesNavigatorApiRequestFromSearchUrl(job.queryUrl, activeConfig, 100);
15182
+ return fetchAllLocalSalesNavigatorPeople(request, {
15183
+ requestedProfiles: brief.candidatesPerDepartment, pageSize: 100, pageDelayMinMs: 5000, pageDelayMaxMs: 8000,
15184
+ retry: { maxRetries: 2, retryBaseDelayMs: 2000, retryMaxDelayMs: 10000 },
15185
+ executeRequest: relay ? request => relay.request(request) : undefined,
15186
+ });
15174
15187
  });
15175
15188
  },
15176
15189
  });
@@ -0,0 +1,31 @@
1
+ /** One credential refresh per run; never rotate on rate limits or other failures. */
2
+ export function createSessionRecovery(options) {
3
+ let current = options.initial;
4
+ let attempted = false;
5
+ const unavailable = () => new Error("Sales Navigator rejected the available LinkedIn session. Completed company searches are preserved. Reconnect the Salesprompter extension, or rerun with --browser-relay-port and a signed-in browser worker. Signing into Salesprompter alone does not refresh LinkedIn.");
6
+ return async function run(collect) {
7
+ try {
8
+ return await collect(current);
9
+ }
10
+ catch (error) {
11
+ if (!options.isAuthError(error))
12
+ throw error;
13
+ if (attempted)
14
+ throw unavailable();
15
+ attempted = true;
16
+ const next = await options.refresh();
17
+ if (!next || options.sameSession(current, next))
18
+ throw unavailable();
19
+ current = next;
20
+ await options.beforeRetry();
21
+ try {
22
+ return await collect(current);
23
+ }
24
+ catch (retryError) {
25
+ if (options.isAuthError(retryError))
26
+ throw unavailable();
27
+ throw retryError;
28
+ }
29
+ }
30
+ };
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.74",
3
+ "version": "0.1.75",
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",