salesprompter-cli 0.1.33 → 0.1.35
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 +306 -20
- 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}]` : "";
|
|
@@ -2764,6 +2836,87 @@ async function promptText(rl, prompt, options = {}) {
|
|
|
2764
2836
|
writeWizardLine("This field is required.");
|
|
2765
2837
|
}
|
|
2766
2838
|
}
|
|
2839
|
+
async function promptLongPastedText(rl, prompt, options = {}) {
|
|
2840
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
2841
|
+
return await promptText(rl, prompt, options);
|
|
2842
|
+
}
|
|
2843
|
+
const suffix = options.defaultValue !== undefined ? ` [${options.defaultValue}]` : "";
|
|
2844
|
+
while (true) {
|
|
2845
|
+
const answer = await new Promise((resolve, reject) => {
|
|
2846
|
+
const stdin = process.stdin;
|
|
2847
|
+
let value = "";
|
|
2848
|
+
let finished = false;
|
|
2849
|
+
const cleanup = () => {
|
|
2850
|
+
if (finished) {
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
finished = true;
|
|
2854
|
+
stdin.off("data", onData);
|
|
2855
|
+
process.off("SIGINT", onSigint);
|
|
2856
|
+
if (stdin.isTTY) {
|
|
2857
|
+
stdin.setRawMode(false);
|
|
2858
|
+
}
|
|
2859
|
+
stdin.pause();
|
|
2860
|
+
rl.resume?.();
|
|
2861
|
+
};
|
|
2862
|
+
const finish = () => {
|
|
2863
|
+
cleanup();
|
|
2864
|
+
process.stdout.write("\n");
|
|
2865
|
+
resolve(value.trim());
|
|
2866
|
+
};
|
|
2867
|
+
const cancel = () => {
|
|
2868
|
+
cleanup();
|
|
2869
|
+
process.stdout.write("\n");
|
|
2870
|
+
reject(new Error("prompt cancelled"));
|
|
2871
|
+
};
|
|
2872
|
+
const onSigint = () => {
|
|
2873
|
+
cancel();
|
|
2874
|
+
};
|
|
2875
|
+
const onData = (chunk) => {
|
|
2876
|
+
for (const byte of chunk) {
|
|
2877
|
+
if (byte === 3) {
|
|
2878
|
+
cancel();
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
if (byte === 13 || byte === 10) {
|
|
2882
|
+
finish();
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
if (byte === 127 || byte === 8) {
|
|
2886
|
+
if (value.length > 0) {
|
|
2887
|
+
value = value.slice(0, -1);
|
|
2888
|
+
process.stdout.write("\b \b");
|
|
2889
|
+
}
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2892
|
+
// Ignore escape sequences for arrows and other terminal controls.
|
|
2893
|
+
if (byte === 27) {
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
const character = Buffer.from([byte]).toString("utf8");
|
|
2897
|
+
value += character;
|
|
2898
|
+
process.stdout.write(character);
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
rl.pause?.();
|
|
2902
|
+
process.stdout.write(`${prompt}${suffix}: `);
|
|
2903
|
+
stdin.setRawMode(true);
|
|
2904
|
+
stdin.resume();
|
|
2905
|
+
stdin.on("data", onData);
|
|
2906
|
+
process.on("SIGINT", onSigint);
|
|
2907
|
+
});
|
|
2908
|
+
if (answer.length > 0) {
|
|
2909
|
+
return answer;
|
|
2910
|
+
}
|
|
2911
|
+
if (options.defaultValue !== undefined) {
|
|
2912
|
+
return options.defaultValue;
|
|
2913
|
+
}
|
|
2914
|
+
if (!options.required) {
|
|
2915
|
+
return "";
|
|
2916
|
+
}
|
|
2917
|
+
writeWizardLine("This field is required.");
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2767
2920
|
async function promptYesNo(rl, prompt, defaultValue) {
|
|
2768
2921
|
while (true) {
|
|
2769
2922
|
const answer = (await rl.question(`${prompt} [${defaultValue ? "Y/n" : "y/N"}]: `)).trim().toLowerCase();
|
|
@@ -2828,7 +2981,7 @@ async function confirmWizardWorkspace(rl, session, options) {
|
|
|
2828
2981
|
? hasNamedOrg
|
|
2829
2982
|
? "Current cached CLI workspace"
|
|
2830
2983
|
: "Workspace name is not available in this cached token"
|
|
2831
|
-
: "
|
|
2984
|
+
: "Select from your Salesprompter organizations in this terminal";
|
|
2832
2985
|
const workspaceChoice = await promptChoice(rl, "Which workspace should I use?", [
|
|
2833
2986
|
{
|
|
2834
2987
|
value: "current",
|
|
@@ -2838,8 +2991,8 @@ async function confirmWizardWorkspace(rl, session, options) {
|
|
|
2838
2991
|
},
|
|
2839
2992
|
{
|
|
2840
2993
|
value: "browser",
|
|
2841
|
-
label: "Choose another workspace
|
|
2842
|
-
description: "
|
|
2994
|
+
label: "Choose another workspace",
|
|
2995
|
+
description: "Select from your Salesprompter organizations in this terminal",
|
|
2843
2996
|
aliases: ["browser", "choose another", "switch workspace", "select organization"]
|
|
2844
2997
|
}
|
|
2845
2998
|
], "current");
|
|
@@ -2848,12 +3001,10 @@ async function confirmWizardWorkspace(rl, session, options) {
|
|
|
2848
3001
|
return session;
|
|
2849
3002
|
}
|
|
2850
3003
|
writeWizardLine();
|
|
2851
|
-
|
|
2852
|
-
writeWizardLine();
|
|
2853
|
-
await clearAuthSession();
|
|
2854
|
-
const result = await performLogin({
|
|
3004
|
+
const result = await switchWorkspaceInCli({
|
|
2855
3005
|
apiUrl: options?.apiUrl ?? session.apiBaseUrl,
|
|
2856
|
-
timeoutSeconds: options?.timeoutSeconds
|
|
3006
|
+
timeoutSeconds: options?.timeoutSeconds,
|
|
3007
|
+
rl
|
|
2857
3008
|
});
|
|
2858
3009
|
writeSessionSummary(result.session);
|
|
2859
3010
|
writeWizardLine();
|
|
@@ -2866,6 +3017,134 @@ async function switchWorkspaceWithBrowser(options) {
|
|
|
2866
3017
|
timeoutSeconds: options?.timeoutSeconds ?? 180
|
|
2867
3018
|
})).session;
|
|
2868
3019
|
}
|
|
3020
|
+
async function listCliWorkspaces(session) {
|
|
3021
|
+
const { session: refreshedSession, value } = await fetchCliJson(session, async (currentSession) => await fetch(`${normalizeCliApiBaseUrl(currentSession.apiBaseUrl)}/api/cli/auth/workspaces`, {
|
|
3022
|
+
method: "GET",
|
|
3023
|
+
headers: {
|
|
3024
|
+
Authorization: `Bearer ${currentSession.accessToken}`,
|
|
3025
|
+
"X-Salesprompter-Client": "salesprompter-cli/0.2"
|
|
3026
|
+
}
|
|
3027
|
+
}), CliWorkspaceListResponseSchema);
|
|
3028
|
+
return {
|
|
3029
|
+
session: refreshedSession,
|
|
3030
|
+
workspaces: value.workspaces,
|
|
3031
|
+
currentOrgId: value.currentOrgId ?? null
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
async function selectCliWorkspace(session, orgId) {
|
|
3035
|
+
const { value } = await fetchCliJson(session, async (currentSession) => await fetch(`${normalizeCliApiBaseUrl(currentSession.apiBaseUrl)}/api/cli/auth/workspaces`, {
|
|
3036
|
+
method: "POST",
|
|
3037
|
+
headers: {
|
|
3038
|
+
Authorization: `Bearer ${currentSession.accessToken}`,
|
|
3039
|
+
"Content-Type": "application/json",
|
|
3040
|
+
"X-Salesprompter-Client": "salesprompter-cli/0.2"
|
|
3041
|
+
},
|
|
3042
|
+
body: JSON.stringify({ orgId })
|
|
3043
|
+
}), CliWorkspaceSwitchResponseSchema);
|
|
3044
|
+
const nextSession = {
|
|
3045
|
+
accessToken: value.token,
|
|
3046
|
+
refreshToken: session.refreshToken,
|
|
3047
|
+
apiBaseUrl: session.apiBaseUrl,
|
|
3048
|
+
user: value.user,
|
|
3049
|
+
expiresAt: value.expiresAt,
|
|
3050
|
+
createdAt: new Date().toISOString()
|
|
3051
|
+
};
|
|
3052
|
+
await writeAuthSession(nextSession);
|
|
3053
|
+
return nextSession;
|
|
3054
|
+
}
|
|
3055
|
+
async function promptForCliWorkspace(rl, workspaces, currentOrgId, requestedWorkspace) {
|
|
3056
|
+
const requested = compactOptionalText(requestedWorkspace);
|
|
3057
|
+
if (requested) {
|
|
3058
|
+
const matched = workspaces.find((workspace) => workspaceMatchesInput(workspace, requested));
|
|
3059
|
+
if (!matched) {
|
|
3060
|
+
throw new Error(`workspace not found for this account: ${requested}`);
|
|
3061
|
+
}
|
|
3062
|
+
return matched.id;
|
|
3063
|
+
}
|
|
3064
|
+
const current = currentOrgId ? workspaces.find((workspace) => workspace.id === currentOrgId) : undefined;
|
|
3065
|
+
const options = workspaces.map((workspace) => ({
|
|
3066
|
+
value: workspace.id,
|
|
3067
|
+
label: formatCliWorkspaceLabel(workspace),
|
|
3068
|
+
description: workspace.id === currentOrgId ? "Current cached CLI workspace" : undefined,
|
|
3069
|
+
aliases: [workspace.name, workspace.slug, workspace.workspaceClientId, workspace.workspaceClientName].filter((value) => Boolean(compactOptionalText(value)))
|
|
3070
|
+
}));
|
|
3071
|
+
options.push({
|
|
3072
|
+
value: "browser",
|
|
3073
|
+
label: "Use browser chooser",
|
|
3074
|
+
description: "Fallback if this terminal list is missing a workspace",
|
|
3075
|
+
aliases: ["browser", "open browser", "web"]
|
|
3076
|
+
});
|
|
3077
|
+
return await promptChoice(rl, "Which workspace should I use?", options, current?.id ?? options[0]?.value ?? "browser");
|
|
3078
|
+
}
|
|
3079
|
+
async function switchWorkspaceInCli(options = {}) {
|
|
3080
|
+
if (options.browser) {
|
|
3081
|
+
return {
|
|
3082
|
+
session: await switchWorkspaceWithBrowser(options),
|
|
3083
|
+
method: "browser"
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
let session = null;
|
|
3087
|
+
try {
|
|
3088
|
+
session = await requireAuthSession();
|
|
3089
|
+
if (options.apiUrl) {
|
|
3090
|
+
session = {
|
|
3091
|
+
...session,
|
|
3092
|
+
apiBaseUrl: normalizeCliApiBaseUrl(options.apiUrl)
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
catch {
|
|
3097
|
+
if (!runtimeOutputOptions.quiet) {
|
|
3098
|
+
writeWizardLine("No cached CLI session found. Starting browser login flow.");
|
|
3099
|
+
writeWizardLine();
|
|
3100
|
+
}
|
|
3101
|
+
return {
|
|
3102
|
+
session: await switchWorkspaceWithBrowser(options),
|
|
3103
|
+
method: "browser"
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
let workspaces;
|
|
3107
|
+
let currentOrgId;
|
|
3108
|
+
try {
|
|
3109
|
+
const listed = await listCliWorkspaces(session);
|
|
3110
|
+
session = listed.session;
|
|
3111
|
+
workspaces = listed.workspaces;
|
|
3112
|
+
currentOrgId = listed.currentOrgId;
|
|
3113
|
+
}
|
|
3114
|
+
catch (error) {
|
|
3115
|
+
if (!runtimeOutputOptions.quiet) {
|
|
3116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3117
|
+
writeWizardLine(`Terminal workspace selection is unavailable (${message}). Starting browser chooser.`);
|
|
3118
|
+
writeWizardLine();
|
|
3119
|
+
}
|
|
3120
|
+
return {
|
|
3121
|
+
session: await switchWorkspaceWithBrowser(options),
|
|
3122
|
+
method: "browser"
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
if (workspaces.length === 0) {
|
|
3126
|
+
if (!runtimeOutputOptions.quiet) {
|
|
3127
|
+
writeWizardLine("No Salesprompter workspaces were returned for this account. Starting browser chooser.");
|
|
3128
|
+
writeWizardLine();
|
|
3129
|
+
}
|
|
3130
|
+
return {
|
|
3131
|
+
session: await switchWorkspaceWithBrowser(options),
|
|
3132
|
+
method: "browser"
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
const selectedOrgId = options.orgId ?? (await withPromptReader(options.rl, async (rl) => await promptForCliWorkspace(rl, workspaces, currentOrgId, options.workspace)));
|
|
3136
|
+
if (selectedOrgId === "browser") {
|
|
3137
|
+
return {
|
|
3138
|
+
session: await switchWorkspaceWithBrowser(options),
|
|
3139
|
+
method: "browser"
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
const nextSession = await selectCliWorkspace(session, selectedOrgId);
|
|
3143
|
+
return {
|
|
3144
|
+
session: nextSession,
|
|
3145
|
+
method: "terminal"
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
2869
3148
|
async function resolveLlmAuthReadiness() {
|
|
2870
3149
|
const apiBaseUrl = process.env.SALESPROMPTER_API_BASE_URL?.trim() || "https://salesprompter.ai";
|
|
2871
3150
|
const envToken = resolveNonInteractiveAuthToken(process.env);
|
|
@@ -5234,7 +5513,7 @@ async function runDirectSalesNavigatorSearchWizard(input) {
|
|
|
5234
5513
|
}
|
|
5235
5514
|
async function runProductMarketWizard(rl) {
|
|
5236
5515
|
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.");
|
|
5237
|
-
const input = await
|
|
5516
|
+
const input = await promptLongPastedText(rl, "What company website or LinkedIn page should I start from?", {
|
|
5238
5517
|
required: true
|
|
5239
5518
|
});
|
|
5240
5519
|
if (isSalesNavigatorPeopleSearchUrl(input)) {
|
|
@@ -5485,10 +5764,11 @@ async function runWizard(options) {
|
|
|
5485
5764
|
], "product-market");
|
|
5486
5765
|
writeWizardLine();
|
|
5487
5766
|
if (flow === "switch-workspace") {
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5767
|
+
const result = await switchWorkspaceInCli({
|
|
5768
|
+
...options,
|
|
5769
|
+
rl
|
|
5770
|
+
});
|
|
5771
|
+
writeSessionSummary(result.session);
|
|
5492
5772
|
writeWizardLine();
|
|
5493
5773
|
continue;
|
|
5494
5774
|
}
|
|
@@ -5829,19 +6109,25 @@ program
|
|
|
5829
6109
|
.alias("auth:switch")
|
|
5830
6110
|
.description("Switch the active Salesprompter workspace for this CLI session.")
|
|
5831
6111
|
.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")
|
|
6112
|
+
.option("--timeout-seconds <number>", "Browser fallback login timeout in seconds", "180")
|
|
6113
|
+
.option("--org-id <id>", "Switch directly to a Clerk organization id")
|
|
6114
|
+
.option("--workspace <nameOrSlug>", "Switch directly to a workspace by name, slug, client id, or org id")
|
|
6115
|
+
.option("--browser", "Use the browser chooser instead of terminal workspace selection")
|
|
5833
6116
|
.action(async (options) => {
|
|
5834
6117
|
const timeoutSeconds = z.coerce.number().int().min(30).max(1800).parse(options.timeoutSeconds);
|
|
5835
|
-
const
|
|
6118
|
+
const result = await switchWorkspaceInCli({
|
|
5836
6119
|
apiUrl: options.apiUrl,
|
|
5837
|
-
timeoutSeconds
|
|
6120
|
+
timeoutSeconds,
|
|
6121
|
+
orgId: options.orgId,
|
|
6122
|
+
workspace: options.workspace,
|
|
6123
|
+
browser: Boolean(options.browser)
|
|
5838
6124
|
});
|
|
5839
6125
|
printOutput({
|
|
5840
6126
|
status: "ok",
|
|
5841
|
-
method:
|
|
5842
|
-
apiBaseUrl: session.apiBaseUrl,
|
|
5843
|
-
user: session.user,
|
|
5844
|
-
expiresAt: session.expiresAt ?? null
|
|
6127
|
+
method: result.method,
|
|
6128
|
+
apiBaseUrl: result.session.apiBaseUrl,
|
|
6129
|
+
user: result.session.user,
|
|
6130
|
+
expiresAt: result.session.expiresAt ?? null
|
|
5845
6131
|
});
|
|
5846
6132
|
});
|
|
5847
6133
|
program
|
package/package.json
CHANGED