salesprompter-cli 0.1.32 → 0.1.33
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/dist/cli.js +277 -38
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -311,6 +311,7 @@ const helpVisibleCommandNames = new Set([
|
|
|
311
311
|
"packs:add",
|
|
312
312
|
"upgrade",
|
|
313
313
|
"auth:login",
|
|
314
|
+
"auth:workspace",
|
|
314
315
|
"wizard",
|
|
315
316
|
"auth:whoami",
|
|
316
317
|
"llm:ready",
|
|
@@ -2858,6 +2859,13 @@ async function confirmWizardWorkspace(rl, session, options) {
|
|
|
2858
2859
|
writeWizardLine();
|
|
2859
2860
|
return result.session;
|
|
2860
2861
|
}
|
|
2862
|
+
async function switchWorkspaceWithBrowser(options) {
|
|
2863
|
+
await clearAuthSession();
|
|
2864
|
+
return (await performLogin({
|
|
2865
|
+
apiUrl: options?.apiUrl,
|
|
2866
|
+
timeoutSeconds: options?.timeoutSeconds ?? 180
|
|
2867
|
+
})).session;
|
|
2868
|
+
}
|
|
2861
2869
|
async function resolveLlmAuthReadiness() {
|
|
2862
2870
|
const apiBaseUrl = process.env.SALESPROMPTER_API_BASE_URL?.trim() || "https://salesprompter.ai";
|
|
2863
2871
|
const envToken = resolveNonInteractiveAuthToken(process.env);
|
|
@@ -2942,6 +2950,19 @@ function buildSalesNavigatorCrawlLogPath(input) {
|
|
|
2942
2950
|
const slug = slugify(input) || "salesnav-crawl";
|
|
2943
2951
|
return `./data/${slug}-crawl.log.jsonl`;
|
|
2944
2952
|
}
|
|
2953
|
+
function buildSalesNavigatorCrawlOutputPath(input) {
|
|
2954
|
+
const slug = slugify(input) || "salesnav-crawl";
|
|
2955
|
+
return `./data/${slug}-crawl.json`;
|
|
2956
|
+
}
|
|
2957
|
+
function isSalesNavigatorPeopleSearchUrl(input) {
|
|
2958
|
+
try {
|
|
2959
|
+
const parsed = new URL(input);
|
|
2960
|
+
return /(^|\.)linkedin\.com$/i.test(parsed.hostname) && parsed.pathname.replace(/\/+$/, "") === "/sales/search/people";
|
|
2961
|
+
}
|
|
2962
|
+
catch {
|
|
2963
|
+
return false;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2945
2966
|
function decodeSalesNavigatorQueryParam(url) {
|
|
2946
2967
|
try {
|
|
2947
2968
|
const encoded = new URL(url).searchParams.get("query");
|
|
@@ -5035,11 +5056,192 @@ async function searchTargetCompanyLeads(reference, limit) {
|
|
|
5035
5056
|
limit
|
|
5036
5057
|
});
|
|
5037
5058
|
}
|
|
5059
|
+
async function runDirectSalesNavigatorSearchWizard(input) {
|
|
5060
|
+
const maxResultsPerSearch = 2500;
|
|
5061
|
+
const numberOfProfiles = 2500;
|
|
5062
|
+
const slicePreset = "wizard-salesnav-search";
|
|
5063
|
+
const maxSplitDepth = DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS.length;
|
|
5064
|
+
const maxSlices = 1000;
|
|
5065
|
+
const maxRetries = 3;
|
|
5066
|
+
const probeProfiles = 100;
|
|
5067
|
+
const agentBusyWaitSeconds = 30;
|
|
5068
|
+
const agentBusyMaxWaits = 20;
|
|
5069
|
+
const idlePollSeconds = 10;
|
|
5070
|
+
const idleMaxPolls = 180;
|
|
5071
|
+
const parallelExports = 3;
|
|
5072
|
+
const outPath = buildSalesNavigatorCrawlOutputPath(input);
|
|
5073
|
+
const dryRun = shouldBypassAuth();
|
|
5074
|
+
const logger = await createWorkflowLogger({
|
|
5075
|
+
logPath: buildSalesNavigatorCrawlLogPath(input)
|
|
5076
|
+
});
|
|
5077
|
+
writeWizardLine("Detected a Sales Navigator people search. I will process this search directly.");
|
|
5078
|
+
if (dryRun) {
|
|
5079
|
+
writeWizardLine("Auth bypass is enabled, so I will preview the crawl plan instead of launching it.");
|
|
5080
|
+
}
|
|
5081
|
+
writeWizardLine();
|
|
5082
|
+
await logger.log("salesnav.crawl.command.started", {
|
|
5083
|
+
queryUrl: input,
|
|
5084
|
+
jobId: null,
|
|
5085
|
+
maxResultsPerSearch,
|
|
5086
|
+
numberOfProfiles,
|
|
5087
|
+
slicePreset,
|
|
5088
|
+
maxSplitDepth,
|
|
5089
|
+
maxSlices,
|
|
5090
|
+
maxRetries,
|
|
5091
|
+
probeProfiles,
|
|
5092
|
+
agentBusyWaitSeconds,
|
|
5093
|
+
agentBusyMaxWaits,
|
|
5094
|
+
idlePollSeconds,
|
|
5095
|
+
idleMaxPolls,
|
|
5096
|
+
parallelExports,
|
|
5097
|
+
dryRun
|
|
5098
|
+
});
|
|
5099
|
+
if (dryRun) {
|
|
5100
|
+
const preview = buildSalesNavigatorCrawlPreview({
|
|
5101
|
+
sourceQueryUrl: input,
|
|
5102
|
+
maxResultsPerSearch,
|
|
5103
|
+
numberOfProfiles,
|
|
5104
|
+
slicePreset
|
|
5105
|
+
});
|
|
5106
|
+
const payload = {
|
|
5107
|
+
status: "ok",
|
|
5108
|
+
dryRun: true,
|
|
5109
|
+
mode: "adaptive",
|
|
5110
|
+
traceId: logger.traceId,
|
|
5111
|
+
logPath: logger.logPath,
|
|
5112
|
+
sourceQueryUrl: input,
|
|
5113
|
+
rootQueryUrl: preview.root.slicedQueryUrl,
|
|
5114
|
+
rootAppliedFilters: preview.root.appliedFilters,
|
|
5115
|
+
dimensionOrder: preview.dimensions.map((dimension) => ({
|
|
5116
|
+
key: dimension.key,
|
|
5117
|
+
filterType: dimension.filterType,
|
|
5118
|
+
valueCount: dimension.values.length
|
|
5119
|
+
})),
|
|
5120
|
+
firstSplitQueries: preview.firstSplit.map((attempt) => ({
|
|
5121
|
+
slicedQueryUrl: attempt.slicedQueryUrl,
|
|
5122
|
+
appliedFilters: attempt.appliedFilters,
|
|
5123
|
+
splitTrail: attempt.splitTrail.map((entry) => ({
|
|
5124
|
+
key: entry.key,
|
|
5125
|
+
filterType: entry.filterType,
|
|
5126
|
+
valueText: entry.value.text
|
|
5127
|
+
}))
|
|
5128
|
+
}))
|
|
5129
|
+
};
|
|
5130
|
+
await logger.log("salesnav.crawl.dry-run.preview", {
|
|
5131
|
+
sourceQueryUrl: input,
|
|
5132
|
+
root: summarizeSalesNavigatorQuery(payload.rootQueryUrl, payload.rootAppliedFilters),
|
|
5133
|
+
dimensionOrder: payload.dimensionOrder,
|
|
5134
|
+
firstSplitQueries: payload.firstSplitQueries.map((attempt) => ({
|
|
5135
|
+
splitTrail: attempt.splitTrail,
|
|
5136
|
+
...summarizeSalesNavigatorQuery(attempt.slicedQueryUrl, attempt.appliedFilters)
|
|
5137
|
+
}))
|
|
5138
|
+
});
|
|
5139
|
+
await writeJsonFile(outPath, payload);
|
|
5140
|
+
writeWizardLine(`Saved Sales Navigator crawl preview to ${outPath}.`);
|
|
5141
|
+
writeWizardLine(`Saved logs to ${logger.logPath}.`);
|
|
5142
|
+
return;
|
|
5143
|
+
}
|
|
5144
|
+
let session = await requireAuthSession();
|
|
5145
|
+
const sessionOrgId = resolveSessionOrgId(session);
|
|
5146
|
+
if (sessionOrgId) {
|
|
5147
|
+
logger.setEventStore(await createSalesNavigatorCrawlEventStore({ orgId: sessionOrgId }));
|
|
5148
|
+
}
|
|
5149
|
+
const seed = createSalesNavigatorCrawlSeed({
|
|
5150
|
+
sourceQueryUrl: input,
|
|
5151
|
+
maxResultsPerSearch,
|
|
5152
|
+
numberOfProfiles,
|
|
5153
|
+
slicePreset
|
|
5154
|
+
});
|
|
5155
|
+
const created = await createOrResumeSalesNavigatorCrawlJob(session, {
|
|
5156
|
+
sourceQueryUrl: input,
|
|
5157
|
+
slicePreset,
|
|
5158
|
+
maxResultsPerSearch,
|
|
5159
|
+
numberOfProfiles,
|
|
5160
|
+
rawPayload: {
|
|
5161
|
+
workflow: "wizard:salesnav-search",
|
|
5162
|
+
traceId: logger.traceId,
|
|
5163
|
+
command: {
|
|
5164
|
+
sourceQueryUrl: input,
|
|
5165
|
+
slicePreset,
|
|
5166
|
+
maxResultsPerSearch,
|
|
5167
|
+
numberOfProfiles,
|
|
5168
|
+
maxSplitDepth,
|
|
5169
|
+
maxSlices,
|
|
5170
|
+
maxRetries,
|
|
5171
|
+
probeProfiles,
|
|
5172
|
+
agentBusyWaitSeconds,
|
|
5173
|
+
agentBusyMaxWaits,
|
|
5174
|
+
idlePollSeconds,
|
|
5175
|
+
idleMaxPolls,
|
|
5176
|
+
parallelExports
|
|
5177
|
+
}
|
|
5178
|
+
},
|
|
5179
|
+
rootSlice: {
|
|
5180
|
+
slicedQueryUrl: seed.slicedQueryUrl,
|
|
5181
|
+
appliedFilters: seed.appliedFilters,
|
|
5182
|
+
depth: seed.depth,
|
|
5183
|
+
splitTrail: seed.splitTrail,
|
|
5184
|
+
rawPayload: {
|
|
5185
|
+
workflow: "wizard:salesnav-search",
|
|
5186
|
+
traceId: logger.traceId
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
}, logger.traceId);
|
|
5190
|
+
session = created.session;
|
|
5191
|
+
const jobId = created.value.job.id;
|
|
5192
|
+
writeWizardLine(`${created.value.resumed ? "Resumed" : "Started"} Sales Navigator crawl ${jobId}. Processing the search now...`);
|
|
5193
|
+
const crawl = await executeSalesNavigatorCrawlJob(session, jobId, {
|
|
5194
|
+
maxSplitDepth,
|
|
5195
|
+
maxSlices,
|
|
5196
|
+
maxRetries,
|
|
5197
|
+
probeProfiles,
|
|
5198
|
+
agentBusyWaitSeconds,
|
|
5199
|
+
agentBusyMaxWaits,
|
|
5200
|
+
idlePollSeconds,
|
|
5201
|
+
idleMaxPolls,
|
|
5202
|
+
parallelExports,
|
|
5203
|
+
traceId: logger.traceId,
|
|
5204
|
+
logger
|
|
5205
|
+
});
|
|
5206
|
+
const payload = {
|
|
5207
|
+
status: "ok",
|
|
5208
|
+
dryRun: false,
|
|
5209
|
+
mode: "durable",
|
|
5210
|
+
traceId: logger.traceId,
|
|
5211
|
+
logPath: logger.logPath,
|
|
5212
|
+
jobId,
|
|
5213
|
+
resumed: created.value.resumed,
|
|
5214
|
+
sourceQueryUrl: crawl.job.sourceQueryUrl,
|
|
5215
|
+
slicePreset: crawl.job.slicePreset,
|
|
5216
|
+
maxResultsPerSearch: crawl.job.maxResultsPerSearch,
|
|
5217
|
+
numberOfProfiles: crawl.job.numberOfProfiles,
|
|
5218
|
+
claimedSlices: crawl.claimedSlices,
|
|
5219
|
+
truncated: crawl.truncated,
|
|
5220
|
+
job: crawl.job,
|
|
5221
|
+
lastOutcome: crawl.lastOutcome
|
|
5222
|
+
};
|
|
5223
|
+
await writeJsonFile(outPath, payload);
|
|
5224
|
+
writeWizardLine(`Sales Navigator crawl status: ${crawl.job.status}.`);
|
|
5225
|
+
writeWizardLine(`Imported ${crawl.job.importedPeople} people across ${crawl.job.exportedSlices} exported slice${crawl.job.exportedSlices === 1 ? "" : "s"}.`);
|
|
5226
|
+
if (crawl.job.failedSlices > 0 || crawl.truncated) {
|
|
5227
|
+
writeWizardLine(`Some work still needs attention: ${crawl.job.failedSlices} failed slice${crawl.job.failedSlices === 1 ? "" : "s"}, truncated=${crawl.truncated}.`);
|
|
5228
|
+
}
|
|
5229
|
+
writeWizardLine(`Saved crawl summary to ${outPath}.`);
|
|
5230
|
+
writeWizardLine(`Saved logs to ${logger.logPath}.`);
|
|
5231
|
+
writeWizardLine();
|
|
5232
|
+
writeWizardLine("Equivalent raw command:");
|
|
5233
|
+
writeWizardLine(` ${buildCommandLine(["salesprompter", "salesnav:crawl", "--query-url", input])}`);
|
|
5234
|
+
}
|
|
5038
5235
|
async function runProductMarketWizard(rl) {
|
|
5039
5236
|
writeWizardSection("Find leads from a product market", "Start from a company website, LinkedIn company page, product page, or category page. I will turn that into intended job titles and durable Sales Navigator crawls.");
|
|
5040
5237
|
const input = await promptText(rl, "What company website or LinkedIn page should I start from?", {
|
|
5041
5238
|
required: true
|
|
5042
5239
|
});
|
|
5240
|
+
if (isSalesNavigatorPeopleSearchUrl(input)) {
|
|
5241
|
+
writeWizardLine();
|
|
5242
|
+
await runDirectSalesNavigatorSearchWizard(input);
|
|
5243
|
+
return;
|
|
5244
|
+
}
|
|
5043
5245
|
const productLimit = z.coerce.number().int().min(1).max(5000).parse(await promptText(rl, "How many products should I inspect?", { defaultValue: "25", required: true }));
|
|
5044
5246
|
const titleLimit = z.coerce.number().int().min(1).max(1000).parse(await promptText(rl, "How many job titles should I turn into Sales Navigator crawls?", {
|
|
5045
5247
|
defaultValue: "5",
|
|
@@ -5237,7 +5439,7 @@ async function runWizard(options) {
|
|
|
5237
5439
|
throw new Error("wizard does not support --json or --quiet.");
|
|
5238
5440
|
}
|
|
5239
5441
|
writeWizardLine("Salesprompter");
|
|
5240
|
-
writeWizardLine("Start with a company website, LinkedIn product page, or
|
|
5442
|
+
writeWizardLine("Start with a company website, LinkedIn product page, category URL, or Sales Navigator search. I will guide you from there.");
|
|
5241
5443
|
writeWizardLine();
|
|
5242
5444
|
const rl = createInterface({
|
|
5243
5445
|
input: process.stdin,
|
|
@@ -5248,46 +5450,63 @@ async function runWizard(options) {
|
|
|
5248
5450
|
if (wizardSession.session && wizardSession.restoredFromCache) {
|
|
5249
5451
|
await confirmWizardWorkspace(rl, wizardSession.session, options);
|
|
5250
5452
|
}
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5453
|
+
for (;;) {
|
|
5454
|
+
const flow = await promptChoice(rl, "What do you want help with?", [
|
|
5455
|
+
{
|
|
5456
|
+
value: "product-market",
|
|
5457
|
+
label: "Find leads from a product market",
|
|
5458
|
+
description: "Start from a company, product, or LinkedIn category and crawl Sales Navigator",
|
|
5459
|
+
aliases: ["product market", "linkedin products", "category", "sales navigator", "crawl"]
|
|
5460
|
+
},
|
|
5461
|
+
{
|
|
5462
|
+
value: "reference-company",
|
|
5463
|
+
label: "Use a built-in vendor shortcut",
|
|
5464
|
+
description: "Generate the saved vendor ICP and search workspace leads",
|
|
5465
|
+
aliases: ["vendor", "shortcut", "vendor template", "quick template"]
|
|
5466
|
+
},
|
|
5467
|
+
{
|
|
5468
|
+
value: "target-company",
|
|
5469
|
+
label: "Find people at a specific company",
|
|
5470
|
+
description: "Example: find people at company.com",
|
|
5471
|
+
aliases: ["target company", "company", "find people", "people at a company", "lead generation"]
|
|
5472
|
+
},
|
|
5473
|
+
{
|
|
5474
|
+
value: "switch-workspace",
|
|
5475
|
+
label: "Switch workspace",
|
|
5476
|
+
description: "Choose Gojiberry, SelectLine, or another Salesprompter workspace",
|
|
5477
|
+
aliases: ["switch workspace", "change workspace", "gojiberry", "organization", "org"]
|
|
5478
|
+
},
|
|
5479
|
+
{
|
|
5480
|
+
value: "outreach-sync",
|
|
5481
|
+
label: "Push qualified leads to Instantly",
|
|
5482
|
+
description: "Use a saved leads file to fill an Instantly campaign",
|
|
5483
|
+
aliases: ["instantly", "outreach", "send leads", "campaign"]
|
|
5484
|
+
}
|
|
5485
|
+
], "product-market");
|
|
5486
|
+
writeWizardLine();
|
|
5487
|
+
if (flow === "switch-workspace") {
|
|
5488
|
+
writeWizardLine("Choose the workspace for this CLI session in the browser.");
|
|
5489
|
+
writeWizardLine();
|
|
5490
|
+
const session = await switchWorkspaceWithBrowser(options);
|
|
5491
|
+
writeSessionSummary(session);
|
|
5492
|
+
writeWizardLine();
|
|
5493
|
+
continue;
|
|
5275
5494
|
}
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5495
|
+
if (flow === "product-market") {
|
|
5496
|
+
await runProductMarketWizard(rl);
|
|
5497
|
+
return;
|
|
5498
|
+
}
|
|
5499
|
+
if (flow === "reference-company") {
|
|
5500
|
+
await runVendorShortcutWizard(rl);
|
|
5501
|
+
return;
|
|
5502
|
+
}
|
|
5503
|
+
if (flow === "target-company") {
|
|
5504
|
+
await runTargetCompanyWizard(rl);
|
|
5505
|
+
return;
|
|
5506
|
+
}
|
|
5507
|
+
await runOutreachSyncWizard(rl);
|
|
5288
5508
|
return;
|
|
5289
5509
|
}
|
|
5290
|
-
await runOutreachSyncWizard(rl);
|
|
5291
5510
|
}
|
|
5292
5511
|
finally {
|
|
5293
5512
|
rl.close();
|
|
@@ -5605,6 +5824,26 @@ program
|
|
|
5605
5824
|
expiresAt: result.session.expiresAt ?? null
|
|
5606
5825
|
});
|
|
5607
5826
|
});
|
|
5827
|
+
program
|
|
5828
|
+
.command("auth:workspace")
|
|
5829
|
+
.alias("auth:switch")
|
|
5830
|
+
.description("Switch the active Salesprompter workspace for this CLI session.")
|
|
5831
|
+
.option("--api-url <url>", "Salesprompter API base URL, defaults to SALESPROMPTER_API_BASE_URL or salesprompter.ai")
|
|
5832
|
+
.option("--timeout-seconds <number>", "Browser login timeout in seconds", "180")
|
|
5833
|
+
.action(async (options) => {
|
|
5834
|
+
const timeoutSeconds = z.coerce.number().int().min(30).max(1800).parse(options.timeoutSeconds);
|
|
5835
|
+
const session = await switchWorkspaceWithBrowser({
|
|
5836
|
+
apiUrl: options.apiUrl,
|
|
5837
|
+
timeoutSeconds
|
|
5838
|
+
});
|
|
5839
|
+
printOutput({
|
|
5840
|
+
status: "ok",
|
|
5841
|
+
method: "browser",
|
|
5842
|
+
apiBaseUrl: session.apiBaseUrl,
|
|
5843
|
+
user: session.user,
|
|
5844
|
+
expiresAt: session.expiresAt ?? null
|
|
5845
|
+
});
|
|
5846
|
+
});
|
|
5608
5847
|
program
|
|
5609
5848
|
.command("wizard")
|
|
5610
5849
|
.alias("start")
|
package/package.json
CHANGED