salesprompter-cli 0.1.32 → 0.1.34

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 (2) hide show
  1. package/dist/cli.js +490 -46
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -42,6 +42,32 @@ const runtimeOutputOptions = {
42
42
  quiet: false
43
43
  };
44
44
  const nullableOptionalString = z.string().min(1).nullish().transform((value) => value ?? undefined);
45
+ const CliWorkspaceSchema = z.object({
46
+ id: z.string().min(1),
47
+ name: nullableOptionalString,
48
+ slug: nullableOptionalString,
49
+ workspaceClientId: nullableOptionalString,
50
+ workspaceClientName: nullableOptionalString
51
+ });
52
+ const CliWorkspaceListResponseSchema = z.object({
53
+ workspaces: z.array(CliWorkspaceSchema),
54
+ currentOrgId: nullableOptionalString
55
+ });
56
+ const CliAuthUserSchema = z.object({
57
+ id: z.string().min(1),
58
+ email: z.string().email(),
59
+ name: nullableOptionalString,
60
+ orgId: nullableOptionalString,
61
+ orgName: nullableOptionalString,
62
+ orgSlug: nullableOptionalString,
63
+ workspaceClientId: nullableOptionalString,
64
+ workspaceClientName: nullableOptionalString
65
+ });
66
+ const CliWorkspaceSwitchResponseSchema = z.object({
67
+ token: z.string().min(1),
68
+ expiresAt: z.string().datetime().optional(),
69
+ user: CliAuthUserSchema
70
+ });
45
71
  const LinkedInCompanyBackfillClientIdStateSchema = z
46
72
  .object({
47
73
  clientId: z.number().int().positive(),
@@ -311,6 +337,7 @@ const helpVisibleCommandNames = new Set([
311
337
  "packs:add",
312
338
  "upgrade",
313
339
  "auth:login",
340
+ "auth:workspace",
314
341
  "wizard",
315
342
  "auth:whoami",
316
343
  "llm:ready",
@@ -2563,6 +2590,37 @@ function resolveSessionOrgId(session) {
2563
2590
  const orgId = session.user.orgId?.trim();
2564
2591
  return orgId && orgId.length > 0 ? orgId : null;
2565
2592
  }
2593
+ function normalizeCliApiBaseUrl(value) {
2594
+ return value.trim().replace(/\/+$/, "");
2595
+ }
2596
+ function getWorkspaceDisplayName(workspace) {
2597
+ return (compactOptionalText(workspace.name) ??
2598
+ compactOptionalText(workspace.workspaceClientName) ??
2599
+ compactOptionalText(workspace.slug) ??
2600
+ workspace.id);
2601
+ }
2602
+ function formatCliWorkspaceLabel(workspace) {
2603
+ const name = getWorkspaceDisplayName(workspace);
2604
+ const details = [
2605
+ compactOptionalText(workspace.slug),
2606
+ compactOptionalText(workspace.workspaceClientId),
2607
+ workspace.id
2608
+ ].filter((value) => Boolean(value));
2609
+ return details.length > 0 ? `${name} (${details.join(", ")})` : name;
2610
+ }
2611
+ function workspaceMatchesInput(workspace, value) {
2612
+ const normalized = normalizeChoiceText(value);
2613
+ return [
2614
+ workspace.id,
2615
+ workspace.name,
2616
+ workspace.slug,
2617
+ workspace.workspaceClientId,
2618
+ workspace.workspaceClientName
2619
+ ].some((candidate) => {
2620
+ const compacted = compactOptionalText(candidate);
2621
+ return compacted ? normalizeChoiceText(compacted) === normalized : false;
2622
+ });
2623
+ }
2566
2624
  function writeSessionSummary(session) {
2567
2625
  const identity = session.user.name?.trim()
2568
2626
  ? `${session.user.name} (${session.user.email})`
@@ -2747,6 +2805,21 @@ async function promptChoice(rl, prompt, options, defaultValue) {
2747
2805
  writeWizardLine();
2748
2806
  }
2749
2807
  }
2808
+ async function withPromptReader(existingReader, run) {
2809
+ if (existingReader) {
2810
+ return await run(existingReader);
2811
+ }
2812
+ const rl = createInterface({
2813
+ input: process.stdin,
2814
+ output: process.stdout
2815
+ });
2816
+ try {
2817
+ return await run(rl);
2818
+ }
2819
+ finally {
2820
+ rl.close();
2821
+ }
2822
+ }
2750
2823
  async function promptText(rl, prompt, options = {}) {
2751
2824
  while (true) {
2752
2825
  const suffix = options.defaultValue !== undefined ? ` [${options.defaultValue}]` : "";
@@ -2827,7 +2900,7 @@ async function confirmWizardWorkspace(rl, session, options) {
2827
2900
  ? hasNamedOrg
2828
2901
  ? "Current cached CLI workspace"
2829
2902
  : "Workspace name is not available in this cached token"
2830
- : "Choose another workspace in the browser if this account belongs to more than one";
2903
+ : "Select from your Salesprompter organizations in this terminal";
2831
2904
  const workspaceChoice = await promptChoice(rl, "Which workspace should I use?", [
2832
2905
  {
2833
2906
  value: "current",
@@ -2837,8 +2910,8 @@ async function confirmWizardWorkspace(rl, session, options) {
2837
2910
  },
2838
2911
  {
2839
2912
  value: "browser",
2840
- label: "Choose another workspace in the browser",
2841
- description: "Opens Salesprompter so you can pick from your organizations",
2913
+ label: "Choose another workspace",
2914
+ description: "Select from your Salesprompter organizations in this terminal",
2842
2915
  aliases: ["browser", "choose another", "switch workspace", "select organization"]
2843
2916
  }
2844
2917
  ], "current");
@@ -2847,17 +2920,150 @@ async function confirmWizardWorkspace(rl, session, options) {
2847
2920
  return session;
2848
2921
  }
2849
2922
  writeWizardLine();
2850
- writeWizardLine("Choose the workspace for this CLI session in the browser.");
2851
- writeWizardLine();
2852
- await clearAuthSession();
2853
- const result = await performLogin({
2923
+ const result = await switchWorkspaceInCli({
2854
2924
  apiUrl: options?.apiUrl ?? session.apiBaseUrl,
2855
- timeoutSeconds: options?.timeoutSeconds ?? 180
2925
+ timeoutSeconds: options?.timeoutSeconds,
2926
+ rl
2856
2927
  });
2857
2928
  writeSessionSummary(result.session);
2858
2929
  writeWizardLine();
2859
2930
  return result.session;
2860
2931
  }
2932
+ async function switchWorkspaceWithBrowser(options) {
2933
+ await clearAuthSession();
2934
+ return (await performLogin({
2935
+ apiUrl: options?.apiUrl,
2936
+ timeoutSeconds: options?.timeoutSeconds ?? 180
2937
+ })).session;
2938
+ }
2939
+ async function listCliWorkspaces(session) {
2940
+ const { session: refreshedSession, value } = await fetchCliJson(session, async (currentSession) => await fetch(`${normalizeCliApiBaseUrl(currentSession.apiBaseUrl)}/api/cli/auth/workspaces`, {
2941
+ method: "GET",
2942
+ headers: {
2943
+ Authorization: `Bearer ${currentSession.accessToken}`,
2944
+ "X-Salesprompter-Client": "salesprompter-cli/0.2"
2945
+ }
2946
+ }), CliWorkspaceListResponseSchema);
2947
+ return {
2948
+ session: refreshedSession,
2949
+ workspaces: value.workspaces,
2950
+ currentOrgId: value.currentOrgId ?? null
2951
+ };
2952
+ }
2953
+ async function selectCliWorkspace(session, orgId) {
2954
+ const { value } = await fetchCliJson(session, async (currentSession) => await fetch(`${normalizeCliApiBaseUrl(currentSession.apiBaseUrl)}/api/cli/auth/workspaces`, {
2955
+ method: "POST",
2956
+ headers: {
2957
+ Authorization: `Bearer ${currentSession.accessToken}`,
2958
+ "Content-Type": "application/json",
2959
+ "X-Salesprompter-Client": "salesprompter-cli/0.2"
2960
+ },
2961
+ body: JSON.stringify({ orgId })
2962
+ }), CliWorkspaceSwitchResponseSchema);
2963
+ const nextSession = {
2964
+ accessToken: value.token,
2965
+ refreshToken: session.refreshToken,
2966
+ apiBaseUrl: session.apiBaseUrl,
2967
+ user: value.user,
2968
+ expiresAt: value.expiresAt,
2969
+ createdAt: new Date().toISOString()
2970
+ };
2971
+ await writeAuthSession(nextSession);
2972
+ return nextSession;
2973
+ }
2974
+ async function promptForCliWorkspace(rl, workspaces, currentOrgId, requestedWorkspace) {
2975
+ const requested = compactOptionalText(requestedWorkspace);
2976
+ if (requested) {
2977
+ const matched = workspaces.find((workspace) => workspaceMatchesInput(workspace, requested));
2978
+ if (!matched) {
2979
+ throw new Error(`workspace not found for this account: ${requested}`);
2980
+ }
2981
+ return matched.id;
2982
+ }
2983
+ const current = currentOrgId ? workspaces.find((workspace) => workspace.id === currentOrgId) : undefined;
2984
+ const options = workspaces.map((workspace) => ({
2985
+ value: workspace.id,
2986
+ label: formatCliWorkspaceLabel(workspace),
2987
+ description: workspace.id === currentOrgId ? "Current cached CLI workspace" : undefined,
2988
+ aliases: [workspace.name, workspace.slug, workspace.workspaceClientId, workspace.workspaceClientName].filter((value) => Boolean(compactOptionalText(value)))
2989
+ }));
2990
+ options.push({
2991
+ value: "browser",
2992
+ label: "Use browser chooser",
2993
+ description: "Fallback if this terminal list is missing a workspace",
2994
+ aliases: ["browser", "open browser", "web"]
2995
+ });
2996
+ return await promptChoice(rl, "Which workspace should I use?", options, current?.id ?? options[0]?.value ?? "browser");
2997
+ }
2998
+ async function switchWorkspaceInCli(options = {}) {
2999
+ if (options.browser) {
3000
+ return {
3001
+ session: await switchWorkspaceWithBrowser(options),
3002
+ method: "browser"
3003
+ };
3004
+ }
3005
+ let session = null;
3006
+ try {
3007
+ session = await requireAuthSession();
3008
+ if (options.apiUrl) {
3009
+ session = {
3010
+ ...session,
3011
+ apiBaseUrl: normalizeCliApiBaseUrl(options.apiUrl)
3012
+ };
3013
+ }
3014
+ }
3015
+ catch {
3016
+ if (!runtimeOutputOptions.quiet) {
3017
+ writeWizardLine("No cached CLI session found. Starting browser login flow.");
3018
+ writeWizardLine();
3019
+ }
3020
+ return {
3021
+ session: await switchWorkspaceWithBrowser(options),
3022
+ method: "browser"
3023
+ };
3024
+ }
3025
+ let workspaces;
3026
+ let currentOrgId;
3027
+ try {
3028
+ const listed = await listCliWorkspaces(session);
3029
+ session = listed.session;
3030
+ workspaces = listed.workspaces;
3031
+ currentOrgId = listed.currentOrgId;
3032
+ }
3033
+ catch (error) {
3034
+ if (!runtimeOutputOptions.quiet) {
3035
+ const message = error instanceof Error ? error.message : String(error);
3036
+ writeWizardLine(`Terminal workspace selection is unavailable (${message}). Starting browser chooser.`);
3037
+ writeWizardLine();
3038
+ }
3039
+ return {
3040
+ session: await switchWorkspaceWithBrowser(options),
3041
+ method: "browser"
3042
+ };
3043
+ }
3044
+ if (workspaces.length === 0) {
3045
+ if (!runtimeOutputOptions.quiet) {
3046
+ writeWizardLine("No Salesprompter workspaces were returned for this account. Starting browser chooser.");
3047
+ writeWizardLine();
3048
+ }
3049
+ return {
3050
+ session: await switchWorkspaceWithBrowser(options),
3051
+ method: "browser"
3052
+ };
3053
+ }
3054
+ const selectedOrgId = options.orgId ?? (await withPromptReader(options.rl, async (rl) => await promptForCliWorkspace(rl, workspaces, currentOrgId, options.workspace)));
3055
+ if (selectedOrgId === "browser") {
3056
+ return {
3057
+ session: await switchWorkspaceWithBrowser(options),
3058
+ method: "browser"
3059
+ };
3060
+ }
3061
+ const nextSession = await selectCliWorkspace(session, selectedOrgId);
3062
+ return {
3063
+ session: nextSession,
3064
+ method: "terminal"
3065
+ };
3066
+ }
2861
3067
  async function resolveLlmAuthReadiness() {
2862
3068
  const apiBaseUrl = process.env.SALESPROMPTER_API_BASE_URL?.trim() || "https://salesprompter.ai";
2863
3069
  const envToken = resolveNonInteractiveAuthToken(process.env);
@@ -2942,6 +3148,19 @@ function buildSalesNavigatorCrawlLogPath(input) {
2942
3148
  const slug = slugify(input) || "salesnav-crawl";
2943
3149
  return `./data/${slug}-crawl.log.jsonl`;
2944
3150
  }
3151
+ function buildSalesNavigatorCrawlOutputPath(input) {
3152
+ const slug = slugify(input) || "salesnav-crawl";
3153
+ return `./data/${slug}-crawl.json`;
3154
+ }
3155
+ function isSalesNavigatorPeopleSearchUrl(input) {
3156
+ try {
3157
+ const parsed = new URL(input);
3158
+ return /(^|\.)linkedin\.com$/i.test(parsed.hostname) && parsed.pathname.replace(/\/+$/, "") === "/sales/search/people";
3159
+ }
3160
+ catch {
3161
+ return false;
3162
+ }
3163
+ }
2945
3164
  function decodeSalesNavigatorQueryParam(url) {
2946
3165
  try {
2947
3166
  const encoded = new URL(url).searchParams.get("query");
@@ -5035,11 +5254,192 @@ async function searchTargetCompanyLeads(reference, limit) {
5035
5254
  limit
5036
5255
  });
5037
5256
  }
5257
+ async function runDirectSalesNavigatorSearchWizard(input) {
5258
+ const maxResultsPerSearch = 2500;
5259
+ const numberOfProfiles = 2500;
5260
+ const slicePreset = "wizard-salesnav-search";
5261
+ const maxSplitDepth = DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS.length;
5262
+ const maxSlices = 1000;
5263
+ const maxRetries = 3;
5264
+ const probeProfiles = 100;
5265
+ const agentBusyWaitSeconds = 30;
5266
+ const agentBusyMaxWaits = 20;
5267
+ const idlePollSeconds = 10;
5268
+ const idleMaxPolls = 180;
5269
+ const parallelExports = 3;
5270
+ const outPath = buildSalesNavigatorCrawlOutputPath(input);
5271
+ const dryRun = shouldBypassAuth();
5272
+ const logger = await createWorkflowLogger({
5273
+ logPath: buildSalesNavigatorCrawlLogPath(input)
5274
+ });
5275
+ writeWizardLine("Detected a Sales Navigator people search. I will process this search directly.");
5276
+ if (dryRun) {
5277
+ writeWizardLine("Auth bypass is enabled, so I will preview the crawl plan instead of launching it.");
5278
+ }
5279
+ writeWizardLine();
5280
+ await logger.log("salesnav.crawl.command.started", {
5281
+ queryUrl: input,
5282
+ jobId: null,
5283
+ maxResultsPerSearch,
5284
+ numberOfProfiles,
5285
+ slicePreset,
5286
+ maxSplitDepth,
5287
+ maxSlices,
5288
+ maxRetries,
5289
+ probeProfiles,
5290
+ agentBusyWaitSeconds,
5291
+ agentBusyMaxWaits,
5292
+ idlePollSeconds,
5293
+ idleMaxPolls,
5294
+ parallelExports,
5295
+ dryRun
5296
+ });
5297
+ if (dryRun) {
5298
+ const preview = buildSalesNavigatorCrawlPreview({
5299
+ sourceQueryUrl: input,
5300
+ maxResultsPerSearch,
5301
+ numberOfProfiles,
5302
+ slicePreset
5303
+ });
5304
+ const payload = {
5305
+ status: "ok",
5306
+ dryRun: true,
5307
+ mode: "adaptive",
5308
+ traceId: logger.traceId,
5309
+ logPath: logger.logPath,
5310
+ sourceQueryUrl: input,
5311
+ rootQueryUrl: preview.root.slicedQueryUrl,
5312
+ rootAppliedFilters: preview.root.appliedFilters,
5313
+ dimensionOrder: preview.dimensions.map((dimension) => ({
5314
+ key: dimension.key,
5315
+ filterType: dimension.filterType,
5316
+ valueCount: dimension.values.length
5317
+ })),
5318
+ firstSplitQueries: preview.firstSplit.map((attempt) => ({
5319
+ slicedQueryUrl: attempt.slicedQueryUrl,
5320
+ appliedFilters: attempt.appliedFilters,
5321
+ splitTrail: attempt.splitTrail.map((entry) => ({
5322
+ key: entry.key,
5323
+ filterType: entry.filterType,
5324
+ valueText: entry.value.text
5325
+ }))
5326
+ }))
5327
+ };
5328
+ await logger.log("salesnav.crawl.dry-run.preview", {
5329
+ sourceQueryUrl: input,
5330
+ root: summarizeSalesNavigatorQuery(payload.rootQueryUrl, payload.rootAppliedFilters),
5331
+ dimensionOrder: payload.dimensionOrder,
5332
+ firstSplitQueries: payload.firstSplitQueries.map((attempt) => ({
5333
+ splitTrail: attempt.splitTrail,
5334
+ ...summarizeSalesNavigatorQuery(attempt.slicedQueryUrl, attempt.appliedFilters)
5335
+ }))
5336
+ });
5337
+ await writeJsonFile(outPath, payload);
5338
+ writeWizardLine(`Saved Sales Navigator crawl preview to ${outPath}.`);
5339
+ writeWizardLine(`Saved logs to ${logger.logPath}.`);
5340
+ return;
5341
+ }
5342
+ let session = await requireAuthSession();
5343
+ const sessionOrgId = resolveSessionOrgId(session);
5344
+ if (sessionOrgId) {
5345
+ logger.setEventStore(await createSalesNavigatorCrawlEventStore({ orgId: sessionOrgId }));
5346
+ }
5347
+ const seed = createSalesNavigatorCrawlSeed({
5348
+ sourceQueryUrl: input,
5349
+ maxResultsPerSearch,
5350
+ numberOfProfiles,
5351
+ slicePreset
5352
+ });
5353
+ const created = await createOrResumeSalesNavigatorCrawlJob(session, {
5354
+ sourceQueryUrl: input,
5355
+ slicePreset,
5356
+ maxResultsPerSearch,
5357
+ numberOfProfiles,
5358
+ rawPayload: {
5359
+ workflow: "wizard:salesnav-search",
5360
+ traceId: logger.traceId,
5361
+ command: {
5362
+ sourceQueryUrl: input,
5363
+ slicePreset,
5364
+ maxResultsPerSearch,
5365
+ numberOfProfiles,
5366
+ maxSplitDepth,
5367
+ maxSlices,
5368
+ maxRetries,
5369
+ probeProfiles,
5370
+ agentBusyWaitSeconds,
5371
+ agentBusyMaxWaits,
5372
+ idlePollSeconds,
5373
+ idleMaxPolls,
5374
+ parallelExports
5375
+ }
5376
+ },
5377
+ rootSlice: {
5378
+ slicedQueryUrl: seed.slicedQueryUrl,
5379
+ appliedFilters: seed.appliedFilters,
5380
+ depth: seed.depth,
5381
+ splitTrail: seed.splitTrail,
5382
+ rawPayload: {
5383
+ workflow: "wizard:salesnav-search",
5384
+ traceId: logger.traceId
5385
+ }
5386
+ }
5387
+ }, logger.traceId);
5388
+ session = created.session;
5389
+ const jobId = created.value.job.id;
5390
+ writeWizardLine(`${created.value.resumed ? "Resumed" : "Started"} Sales Navigator crawl ${jobId}. Processing the search now...`);
5391
+ const crawl = await executeSalesNavigatorCrawlJob(session, jobId, {
5392
+ maxSplitDepth,
5393
+ maxSlices,
5394
+ maxRetries,
5395
+ probeProfiles,
5396
+ agentBusyWaitSeconds,
5397
+ agentBusyMaxWaits,
5398
+ idlePollSeconds,
5399
+ idleMaxPolls,
5400
+ parallelExports,
5401
+ traceId: logger.traceId,
5402
+ logger
5403
+ });
5404
+ const payload = {
5405
+ status: "ok",
5406
+ dryRun: false,
5407
+ mode: "durable",
5408
+ traceId: logger.traceId,
5409
+ logPath: logger.logPath,
5410
+ jobId,
5411
+ resumed: created.value.resumed,
5412
+ sourceQueryUrl: crawl.job.sourceQueryUrl,
5413
+ slicePreset: crawl.job.slicePreset,
5414
+ maxResultsPerSearch: crawl.job.maxResultsPerSearch,
5415
+ numberOfProfiles: crawl.job.numberOfProfiles,
5416
+ claimedSlices: crawl.claimedSlices,
5417
+ truncated: crawl.truncated,
5418
+ job: crawl.job,
5419
+ lastOutcome: crawl.lastOutcome
5420
+ };
5421
+ await writeJsonFile(outPath, payload);
5422
+ writeWizardLine(`Sales Navigator crawl status: ${crawl.job.status}.`);
5423
+ writeWizardLine(`Imported ${crawl.job.importedPeople} people across ${crawl.job.exportedSlices} exported slice${crawl.job.exportedSlices === 1 ? "" : "s"}.`);
5424
+ if (crawl.job.failedSlices > 0 || crawl.truncated) {
5425
+ writeWizardLine(`Some work still needs attention: ${crawl.job.failedSlices} failed slice${crawl.job.failedSlices === 1 ? "" : "s"}, truncated=${crawl.truncated}.`);
5426
+ }
5427
+ writeWizardLine(`Saved crawl summary to ${outPath}.`);
5428
+ writeWizardLine(`Saved logs to ${logger.logPath}.`);
5429
+ writeWizardLine();
5430
+ writeWizardLine("Equivalent raw command:");
5431
+ writeWizardLine(` ${buildCommandLine(["salesprompter", "salesnav:crawl", "--query-url", input])}`);
5432
+ }
5038
5433
  async function runProductMarketWizard(rl) {
5039
5434
  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
5435
  const input = await promptText(rl, "What company website or LinkedIn page should I start from?", {
5041
5436
  required: true
5042
5437
  });
5438
+ if (isSalesNavigatorPeopleSearchUrl(input)) {
5439
+ writeWizardLine();
5440
+ await runDirectSalesNavigatorSearchWizard(input);
5441
+ return;
5442
+ }
5043
5443
  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
5444
  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
5445
  defaultValue: "5",
@@ -5237,7 +5637,7 @@ async function runWizard(options) {
5237
5637
  throw new Error("wizard does not support --json or --quiet.");
5238
5638
  }
5239
5639
  writeWizardLine("Salesprompter");
5240
- writeWizardLine("Start with a company website, LinkedIn product page, or category URL. I will guide you from there.");
5640
+ writeWizardLine("Start with a company website, LinkedIn product page, category URL, or Sales Navigator search. I will guide you from there.");
5241
5641
  writeWizardLine();
5242
5642
  const rl = createInterface({
5243
5643
  input: process.stdin,
@@ -5248,46 +5648,64 @@ async function runWizard(options) {
5248
5648
  if (wizardSession.session && wizardSession.restoredFromCache) {
5249
5649
  await confirmWizardWorkspace(rl, wizardSession.session, options);
5250
5650
  }
5251
- const flow = await promptChoice(rl, "What do you want help with?", [
5252
- {
5253
- value: "product-market",
5254
- label: "Find leads from a product market",
5255
- description: "Start from a company, product, or LinkedIn category and crawl Sales Navigator",
5256
- aliases: ["product market", "linkedin products", "category", "sales navigator", "crawl"]
5257
- },
5258
- {
5259
- value: "reference-company",
5260
- label: "Use a built-in vendor shortcut",
5261
- description: "Generate the saved vendor ICP and search workspace leads",
5262
- aliases: ["vendor", "shortcut", "vendor template", "quick template"]
5263
- },
5264
- {
5265
- value: "target-company",
5266
- label: "Find people at a specific company",
5267
- description: "Example: find people at company.com",
5268
- aliases: ["target company", "company", "find people", "people at a company", "lead generation"]
5269
- },
5270
- {
5271
- value: "outreach-sync",
5272
- label: "Push qualified leads to Instantly",
5273
- description: "Use a saved leads file to fill an Instantly campaign",
5274
- aliases: ["instantly", "outreach", "send leads", "campaign"]
5651
+ for (;;) {
5652
+ const flow = await promptChoice(rl, "What do you want help with?", [
5653
+ {
5654
+ value: "product-market",
5655
+ label: "Find leads from a product market",
5656
+ description: "Start from a company, product, or LinkedIn category and crawl Sales Navigator",
5657
+ aliases: ["product market", "linkedin products", "category", "sales navigator", "crawl"]
5658
+ },
5659
+ {
5660
+ value: "reference-company",
5661
+ label: "Use a built-in vendor shortcut",
5662
+ description: "Generate the saved vendor ICP and search workspace leads",
5663
+ aliases: ["vendor", "shortcut", "vendor template", "quick template"]
5664
+ },
5665
+ {
5666
+ value: "target-company",
5667
+ label: "Find people at a specific company",
5668
+ description: "Example: find people at company.com",
5669
+ aliases: ["target company", "company", "find people", "people at a company", "lead generation"]
5670
+ },
5671
+ {
5672
+ value: "switch-workspace",
5673
+ label: "Switch workspace",
5674
+ description: "Choose Gojiberry, SelectLine, or another Salesprompter workspace",
5675
+ aliases: ["switch workspace", "change workspace", "gojiberry", "organization", "org"]
5676
+ },
5677
+ {
5678
+ value: "outreach-sync",
5679
+ label: "Push qualified leads to Instantly",
5680
+ description: "Use a saved leads file to fill an Instantly campaign",
5681
+ aliases: ["instantly", "outreach", "send leads", "campaign"]
5682
+ }
5683
+ ], "product-market");
5684
+ writeWizardLine();
5685
+ if (flow === "switch-workspace") {
5686
+ const result = await switchWorkspaceInCli({
5687
+ ...options,
5688
+ rl
5689
+ });
5690
+ writeSessionSummary(result.session);
5691
+ writeWizardLine();
5692
+ continue;
5275
5693
  }
5276
- ], "product-market");
5277
- writeWizardLine();
5278
- if (flow === "product-market") {
5279
- await runProductMarketWizard(rl);
5280
- return;
5281
- }
5282
- if (flow === "reference-company") {
5283
- await runVendorShortcutWizard(rl);
5284
- return;
5285
- }
5286
- if (flow === "target-company") {
5287
- await runTargetCompanyWizard(rl);
5694
+ if (flow === "product-market") {
5695
+ await runProductMarketWizard(rl);
5696
+ return;
5697
+ }
5698
+ if (flow === "reference-company") {
5699
+ await runVendorShortcutWizard(rl);
5700
+ return;
5701
+ }
5702
+ if (flow === "target-company") {
5703
+ await runTargetCompanyWizard(rl);
5704
+ return;
5705
+ }
5706
+ await runOutreachSyncWizard(rl);
5288
5707
  return;
5289
5708
  }
5290
- await runOutreachSyncWizard(rl);
5291
5709
  }
5292
5710
  finally {
5293
5711
  rl.close();
@@ -5605,6 +6023,32 @@ program
5605
6023
  expiresAt: result.session.expiresAt ?? null
5606
6024
  });
5607
6025
  });
6026
+ program
6027
+ .command("auth:workspace")
6028
+ .alias("auth:switch")
6029
+ .description("Switch the active Salesprompter workspace for this CLI session.")
6030
+ .option("--api-url <url>", "Salesprompter API base URL, defaults to SALESPROMPTER_API_BASE_URL or salesprompter.ai")
6031
+ .option("--timeout-seconds <number>", "Browser fallback login timeout in seconds", "180")
6032
+ .option("--org-id <id>", "Switch directly to a Clerk organization id")
6033
+ .option("--workspace <nameOrSlug>", "Switch directly to a workspace by name, slug, client id, or org id")
6034
+ .option("--browser", "Use the browser chooser instead of terminal workspace selection")
6035
+ .action(async (options) => {
6036
+ const timeoutSeconds = z.coerce.number().int().min(30).max(1800).parse(options.timeoutSeconds);
6037
+ const result = await switchWorkspaceInCli({
6038
+ apiUrl: options.apiUrl,
6039
+ timeoutSeconds,
6040
+ orgId: options.orgId,
6041
+ workspace: options.workspace,
6042
+ browser: Boolean(options.browser)
6043
+ });
6044
+ printOutput({
6045
+ status: "ok",
6046
+ method: result.method,
6047
+ apiBaseUrl: result.session.apiBaseUrl,
6048
+ user: result.session.user,
6049
+ expiresAt: result.session.expiresAt ?? null
6050
+ });
6051
+ });
5608
6052
  program
5609
6053
  .command("wizard")
5610
6054
  .alias("start")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
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",