salesprompter-cli 0.1.33 → 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 +224 -19
  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(),
@@ -2564,6 +2590,37 @@ function resolveSessionOrgId(session) {
2564
2590
  const orgId = session.user.orgId?.trim();
2565
2591
  return orgId && orgId.length > 0 ? orgId : null;
2566
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
+ }
2567
2624
  function writeSessionSummary(session) {
2568
2625
  const identity = session.user.name?.trim()
2569
2626
  ? `${session.user.name} (${session.user.email})`
@@ -2748,6 +2805,21 @@ async function promptChoice(rl, prompt, options, defaultValue) {
2748
2805
  writeWizardLine();
2749
2806
  }
2750
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
+ }
2751
2823
  async function promptText(rl, prompt, options = {}) {
2752
2824
  while (true) {
2753
2825
  const suffix = options.defaultValue !== undefined ? ` [${options.defaultValue}]` : "";
@@ -2828,7 +2900,7 @@ async function confirmWizardWorkspace(rl, session, options) {
2828
2900
  ? hasNamedOrg
2829
2901
  ? "Current cached CLI workspace"
2830
2902
  : "Workspace name is not available in this cached token"
2831
- : "Choose another workspace in the browser if this account belongs to more than one";
2903
+ : "Select from your Salesprompter organizations in this terminal";
2832
2904
  const workspaceChoice = await promptChoice(rl, "Which workspace should I use?", [
2833
2905
  {
2834
2906
  value: "current",
@@ -2838,8 +2910,8 @@ async function confirmWizardWorkspace(rl, session, options) {
2838
2910
  },
2839
2911
  {
2840
2912
  value: "browser",
2841
- label: "Choose another workspace in the browser",
2842
- 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",
2843
2915
  aliases: ["browser", "choose another", "switch workspace", "select organization"]
2844
2916
  }
2845
2917
  ], "current");
@@ -2848,12 +2920,10 @@ async function confirmWizardWorkspace(rl, session, options) {
2848
2920
  return session;
2849
2921
  }
2850
2922
  writeWizardLine();
2851
- writeWizardLine("Choose the workspace for this CLI session in the browser.");
2852
- writeWizardLine();
2853
- await clearAuthSession();
2854
- const result = await performLogin({
2923
+ const result = await switchWorkspaceInCli({
2855
2924
  apiUrl: options?.apiUrl ?? session.apiBaseUrl,
2856
- timeoutSeconds: options?.timeoutSeconds ?? 180
2925
+ timeoutSeconds: options?.timeoutSeconds,
2926
+ rl
2857
2927
  });
2858
2928
  writeSessionSummary(result.session);
2859
2929
  writeWizardLine();
@@ -2866,6 +2936,134 @@ async function switchWorkspaceWithBrowser(options) {
2866
2936
  timeoutSeconds: options?.timeoutSeconds ?? 180
2867
2937
  })).session;
2868
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
+ }
2869
3067
  async function resolveLlmAuthReadiness() {
2870
3068
  const apiBaseUrl = process.env.SALESPROMPTER_API_BASE_URL?.trim() || "https://salesprompter.ai";
2871
3069
  const envToken = resolveNonInteractiveAuthToken(process.env);
@@ -5485,10 +5683,11 @@ async function runWizard(options) {
5485
5683
  ], "product-market");
5486
5684
  writeWizardLine();
5487
5685
  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);
5686
+ const result = await switchWorkspaceInCli({
5687
+ ...options,
5688
+ rl
5689
+ });
5690
+ writeSessionSummary(result.session);
5492
5691
  writeWizardLine();
5493
5692
  continue;
5494
5693
  }
@@ -5829,19 +6028,25 @@ program
5829
6028
  .alias("auth:switch")
5830
6029
  .description("Switch the active Salesprompter workspace for this CLI session.")
5831
6030
  .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")
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")
5833
6035
  .action(async (options) => {
5834
6036
  const timeoutSeconds = z.coerce.number().int().min(30).max(1800).parse(options.timeoutSeconds);
5835
- const session = await switchWorkspaceWithBrowser({
6037
+ const result = await switchWorkspaceInCli({
5836
6038
  apiUrl: options.apiUrl,
5837
- timeoutSeconds
6039
+ timeoutSeconds,
6040
+ orgId: options.orgId,
6041
+ workspace: options.workspace,
6042
+ browser: Boolean(options.browser)
5838
6043
  });
5839
6044
  printOutput({
5840
6045
  status: "ok",
5841
- method: "browser",
5842
- apiBaseUrl: session.apiBaseUrl,
5843
- user: session.user,
5844
- expiresAt: session.expiresAt ?? null
6046
+ method: result.method,
6047
+ apiBaseUrl: result.session.apiBaseUrl,
6048
+ user: result.session.user,
6049
+ expiresAt: result.session.expiresAt ?? null
5845
6050
  });
5846
6051
  });
5847
6052
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.33",
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",