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 +2 -0
- package/dist/cli.js +20 -7
- package/dist/session-recovery.js +31 -0
- package/package.json +1 -1
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
|
-
|
|
15168
|
-
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
|
|
15172
|
-
|
|
15173
|
-
|
|
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