salesprompter-cli 0.1.6 → 0.1.7
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 +12 -1
- package/dist/cli.js +413 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,9 +19,16 @@ It is built for two users at the same time:
|
|
|
19
19
|
|
|
20
20
|
## Start Here
|
|
21
21
|
|
|
22
|
-
If someone discovers Salesprompter from a vague prompt,
|
|
22
|
+
If someone discovers Salesprompter from a vague prompt, give them the shortest working path for their context.
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
+
## Human-friendly guided path
|
|
26
|
+
npx -y salesprompter-cli@latest
|
|
27
|
+
|
|
28
|
+
## Explicit guided path
|
|
29
|
+
npx -y salesprompter-cli@latest wizard
|
|
30
|
+
|
|
31
|
+
## Raw command surface for agents and scripts
|
|
25
32
|
npx salesprompter-cli@latest --help
|
|
26
33
|
```
|
|
27
34
|
|
|
@@ -29,9 +36,13 @@ Or install it globally:
|
|
|
29
36
|
|
|
30
37
|
```bash
|
|
31
38
|
npm install -g salesprompter-cli
|
|
39
|
+
salesprompter
|
|
40
|
+
salesprompter wizard
|
|
32
41
|
salesprompter --help
|
|
33
42
|
```
|
|
34
43
|
|
|
44
|
+
Bare `salesprompter` now opens a guided wizard in an interactive terminal. Keep using explicit subcommands for agents, CI, and copy-paste docs.
|
|
45
|
+
|
|
35
46
|
## Prompt To Command
|
|
36
47
|
|
|
37
48
|
If the user says something like "I need to determine the ICP of deel.com", there are two different meanings.
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
import { clearAuthSession, loginWithBrowserConnect, loginWithDeviceFlow, loginWithToken, requireAuthSession, shouldBypassAuth, verifySession } from "./auth.js";
|
|
@@ -89,6 +90,386 @@ function writeBrowserLoginInstructions(info) {
|
|
|
89
90
|
process.stderr.write("Opened the browser for you.\n");
|
|
90
91
|
}
|
|
91
92
|
}
|
|
93
|
+
async function performLogin(options) {
|
|
94
|
+
const token = typeof options.token === "string" ? options.token.trim() : "";
|
|
95
|
+
if (token.length > 0) {
|
|
96
|
+
const session = await loginWithToken(token, options.apiUrl);
|
|
97
|
+
return {
|
|
98
|
+
method: "token",
|
|
99
|
+
session
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const startedAt = new Date().toISOString();
|
|
103
|
+
try {
|
|
104
|
+
const session = await loginWithBrowserConnect({
|
|
105
|
+
apiBaseUrl: options.apiUrl,
|
|
106
|
+
timeoutSeconds: options.timeoutSeconds,
|
|
107
|
+
onConnectStart: writeBrowserLoginInstructions
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
method: "browser",
|
|
111
|
+
startedAt,
|
|
112
|
+
session
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
if (!message.includes("timed out waiting for browser login") &&
|
|
118
|
+
!message.includes("invalid localhost callback response") &&
|
|
119
|
+
!message.includes("request failed")) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
if (!/request failed \((401|403|404|405|500|501|502|503|504)\)/.test(message)) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const result = await loginWithDeviceFlow({
|
|
127
|
+
apiBaseUrl: options.apiUrl,
|
|
128
|
+
timeoutSeconds: options.timeoutSeconds,
|
|
129
|
+
onDeviceStart: writeDeviceLoginInstructions
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
method: "device",
|
|
133
|
+
startedAt,
|
|
134
|
+
verificationUrl: result.verificationUrl,
|
|
135
|
+
userCode: result.userCode,
|
|
136
|
+
session: result.session
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function shellQuote(value) {
|
|
140
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) {
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
144
|
+
}
|
|
145
|
+
function buildCommandLine(args) {
|
|
146
|
+
return args.map((arg) => shellQuote(arg)).join(" ");
|
|
147
|
+
}
|
|
148
|
+
function slugify(value) {
|
|
149
|
+
return value
|
|
150
|
+
.trim()
|
|
151
|
+
.toLowerCase()
|
|
152
|
+
.replace(/^https?:\/\//, "")
|
|
153
|
+
.replace(/^www\./, "")
|
|
154
|
+
.split("/")[0]
|
|
155
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
156
|
+
.replace(/^-+|-+$/g, "")
|
|
157
|
+
.replace(/-{2,}/g, "-");
|
|
158
|
+
}
|
|
159
|
+
function normalizeDomainInput(value) {
|
|
160
|
+
return value
|
|
161
|
+
.trim()
|
|
162
|
+
.toLowerCase()
|
|
163
|
+
.replace(/^https?:\/\//, "")
|
|
164
|
+
.replace(/^www\./, "")
|
|
165
|
+
.split("/")[0] ?? "";
|
|
166
|
+
}
|
|
167
|
+
function deriveCompanyNameFromDomain(domain) {
|
|
168
|
+
const hostname = normalizeDomainInput(domain).split(".")[0] ?? domain;
|
|
169
|
+
return hostname
|
|
170
|
+
.split(/[-_]/)
|
|
171
|
+
.filter((part) => part.length > 0)
|
|
172
|
+
.map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
|
|
173
|
+
.join(" ");
|
|
174
|
+
}
|
|
175
|
+
function writeWizardLine(message = "") {
|
|
176
|
+
process.stdout.write(`${message}\n`);
|
|
177
|
+
}
|
|
178
|
+
async function promptChoice(rl, prompt, options, defaultValue) {
|
|
179
|
+
const defaultIndex = options.findIndex((option) => option.value === defaultValue);
|
|
180
|
+
if (defaultIndex === -1) {
|
|
181
|
+
throw new Error(`wizard default option is invalid for ${prompt}`);
|
|
182
|
+
}
|
|
183
|
+
while (true) {
|
|
184
|
+
writeWizardLine(prompt);
|
|
185
|
+
for (const [index, option] of options.entries()) {
|
|
186
|
+
const description = option.description ? ` - ${option.description}` : "";
|
|
187
|
+
writeWizardLine(` ${index + 1}. ${option.label}${description}`);
|
|
188
|
+
}
|
|
189
|
+
const answer = (await rl.question(`Choose [1-${options.length}] (default ${defaultIndex + 1}): `)).trim();
|
|
190
|
+
if (answer.length === 0) {
|
|
191
|
+
return defaultValue;
|
|
192
|
+
}
|
|
193
|
+
const numeric = Number(answer);
|
|
194
|
+
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= options.length) {
|
|
195
|
+
const selected = options[numeric - 1];
|
|
196
|
+
if (!selected) {
|
|
197
|
+
throw new Error("wizard selection invariant violated");
|
|
198
|
+
}
|
|
199
|
+
return selected.value;
|
|
200
|
+
}
|
|
201
|
+
const matched = options.find((option) => option.value.toLowerCase() === answer.toLowerCase());
|
|
202
|
+
if (matched) {
|
|
203
|
+
return matched.value;
|
|
204
|
+
}
|
|
205
|
+
writeWizardLine("Please choose one of the numbered options.");
|
|
206
|
+
writeWizardLine();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function promptText(rl, prompt, options = {}) {
|
|
210
|
+
while (true) {
|
|
211
|
+
const suffix = options.defaultValue !== undefined ? ` [${options.defaultValue}]` : "";
|
|
212
|
+
const answer = (await rl.question(`${prompt}${suffix}: `)).trim();
|
|
213
|
+
if (answer.length > 0) {
|
|
214
|
+
return answer;
|
|
215
|
+
}
|
|
216
|
+
if (options.defaultValue !== undefined) {
|
|
217
|
+
return options.defaultValue;
|
|
218
|
+
}
|
|
219
|
+
if (!options.required) {
|
|
220
|
+
return "";
|
|
221
|
+
}
|
|
222
|
+
writeWizardLine("This field is required.");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function promptYesNo(rl, prompt, defaultValue) {
|
|
226
|
+
while (true) {
|
|
227
|
+
const answer = (await rl.question(`${prompt} [${defaultValue ? "Y/n" : "y/N"}]: `)).trim().toLowerCase();
|
|
228
|
+
if (answer.length === 0) {
|
|
229
|
+
return defaultValue;
|
|
230
|
+
}
|
|
231
|
+
if (["y", "yes"].includes(answer)) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
if (["n", "no"].includes(answer)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
writeWizardLine("Please answer yes or no.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async function ensureWizardSession(options) {
|
|
241
|
+
if (shouldBypassAuth()) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const session = await requireAuthSession();
|
|
246
|
+
writeWizardLine(`Signed in as ${session.user.email}.`);
|
|
247
|
+
writeWizardLine();
|
|
248
|
+
return session;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
252
|
+
if (!message.includes("not logged in") && !message.includes("session expired")) {
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
writeWizardLine("No active Salesprompter session found. Starting login...");
|
|
257
|
+
writeWizardLine();
|
|
258
|
+
const result = await performLogin({
|
|
259
|
+
apiUrl: options?.apiUrl,
|
|
260
|
+
timeoutSeconds: options?.timeoutSeconds ?? 180
|
|
261
|
+
});
|
|
262
|
+
writeWizardLine(`Signed in as ${result.session.user.email}.`);
|
|
263
|
+
writeWizardLine();
|
|
264
|
+
return result.session;
|
|
265
|
+
}
|
|
266
|
+
async function runVendorIcpWizard(rl) {
|
|
267
|
+
const vendor = await promptChoice(rl, "Which vendor template do you want?", [{ value: "deel", label: "Deel", description: "Global payroll, EOR, and contractor workflows" }], "deel");
|
|
268
|
+
writeWizardLine();
|
|
269
|
+
const market = await promptChoice(rl, "Which market should this ICP target?", [
|
|
270
|
+
{ value: "dach", label: "DACH", description: "Germany, Austria, Switzerland" },
|
|
271
|
+
{ value: "europe", label: "Europe" },
|
|
272
|
+
{ value: "global", label: "Global" }
|
|
273
|
+
], "dach");
|
|
274
|
+
writeWizardLine();
|
|
275
|
+
const outPath = await promptText(rl, "Where should I save the ICP JSON?", {
|
|
276
|
+
defaultValue: `./data/${slugify(vendor)}-icp-${market}.json`,
|
|
277
|
+
required: true
|
|
278
|
+
});
|
|
279
|
+
writeWizardLine();
|
|
280
|
+
const icp = buildVendorIcp(vendor, market);
|
|
281
|
+
await writeJsonFile(outPath, icp);
|
|
282
|
+
writeWizardLine(`Created ${icp.name}.`);
|
|
283
|
+
writeWizardLine(`Saved ICP to ${outPath}.`);
|
|
284
|
+
writeWizardLine();
|
|
285
|
+
writeWizardLine("Equivalent raw command:");
|
|
286
|
+
writeWizardLine(` ${buildCommandLine(["salesprompter", "icp:vendor", "--vendor", vendor, "--market", market, "--out", outPath])}`);
|
|
287
|
+
writeWizardLine();
|
|
288
|
+
writeWizardLine("Next suggested command:");
|
|
289
|
+
writeWizardLine(` ${buildCommandLine([
|
|
290
|
+
"salesprompter",
|
|
291
|
+
"leads:lookup:bq",
|
|
292
|
+
"--icp",
|
|
293
|
+
outPath,
|
|
294
|
+
"--limit",
|
|
295
|
+
"100",
|
|
296
|
+
"--lead-out",
|
|
297
|
+
`./data/${slugify(vendor)}-leads.json`
|
|
298
|
+
])}`);
|
|
299
|
+
}
|
|
300
|
+
async function runTargetAccountWizard(rl) {
|
|
301
|
+
const domain = normalizeDomainInput(await promptText(rl, "Which company domain are you targeting?", { required: true }));
|
|
302
|
+
writeWizardLine();
|
|
303
|
+
const companyName = await promptText(rl, "Company name override (optional)");
|
|
304
|
+
const displayName = companyName || deriveCompanyNameFromDomain(domain);
|
|
305
|
+
const leadCount = z.coerce.number().int().min(1).max(1000).parse(await promptText(rl, "How many leads should I generate?", { defaultValue: "5", required: true }));
|
|
306
|
+
const region = await promptText(rl, "Primary region hint", { defaultValue: "Global", required: true });
|
|
307
|
+
const industries = await promptText(rl, "Industry hint (optional, comma-separated)");
|
|
308
|
+
const titles = await promptText(rl, "Target titles (optional, comma-separated)");
|
|
309
|
+
writeWizardLine();
|
|
310
|
+
const slug = slugify(domain);
|
|
311
|
+
const icpPath = await promptText(rl, "Where should I save the ad-hoc ICP JSON?", {
|
|
312
|
+
defaultValue: `./data/${slug}-target-icp.json`,
|
|
313
|
+
required: true
|
|
314
|
+
});
|
|
315
|
+
const leadsPath = await promptText(rl, "Where should I save the generated leads JSON?", {
|
|
316
|
+
defaultValue: `./data/${slug}-leads.json`,
|
|
317
|
+
required: true
|
|
318
|
+
});
|
|
319
|
+
writeWizardLine();
|
|
320
|
+
const icp = IcpSchema.parse({
|
|
321
|
+
name: `${displayName} target account`,
|
|
322
|
+
regions: region.length > 0 ? [region] : [],
|
|
323
|
+
industries: splitCsv(industries),
|
|
324
|
+
titles: splitCsv(titles)
|
|
325
|
+
});
|
|
326
|
+
await writeJsonFile(icpPath, icp);
|
|
327
|
+
const result = await leadProvider.generateLeads(icp, leadCount, {
|
|
328
|
+
companyDomain: domain,
|
|
329
|
+
companyName: companyName || undefined
|
|
330
|
+
});
|
|
331
|
+
await writeJsonFile(leadsPath, result.leads);
|
|
332
|
+
writeWizardLine(`Generated ${result.leads.length} leads for ${result.account.companyName} (${result.account.domain}).`);
|
|
333
|
+
writeWizardLine(`Saved ad-hoc ICP to ${icpPath}.`);
|
|
334
|
+
writeWizardLine(`Saved leads to ${leadsPath}.`);
|
|
335
|
+
if (result.warnings.length > 0) {
|
|
336
|
+
writeWizardLine();
|
|
337
|
+
writeWizardLine(`Warning: ${result.warnings.join(" ")}`);
|
|
338
|
+
}
|
|
339
|
+
writeWizardLine();
|
|
340
|
+
writeWizardLine("Equivalent raw commands:");
|
|
341
|
+
const defineArgs = ["salesprompter", "icp:define", "--name", icp.name];
|
|
342
|
+
if (region.length > 0) {
|
|
343
|
+
defineArgs.push("--regions", region);
|
|
344
|
+
}
|
|
345
|
+
if (industries.trim().length > 0) {
|
|
346
|
+
defineArgs.push("--industries", industries);
|
|
347
|
+
}
|
|
348
|
+
if (titles.trim().length > 0) {
|
|
349
|
+
defineArgs.push("--titles", titles);
|
|
350
|
+
}
|
|
351
|
+
defineArgs.push("--out", icpPath);
|
|
352
|
+
writeWizardLine(` ${buildCommandLine(defineArgs)}`);
|
|
353
|
+
const leadArgs = ["salesprompter", "leads:generate", "--icp", icpPath, "--count", String(leadCount), "--domain", domain];
|
|
354
|
+
if (companyName.trim().length > 0) {
|
|
355
|
+
leadArgs.push("--company-name", companyName);
|
|
356
|
+
}
|
|
357
|
+
leadArgs.push("--out", leadsPath);
|
|
358
|
+
writeWizardLine(` ${buildCommandLine(leadArgs)}`);
|
|
359
|
+
}
|
|
360
|
+
async function runVendorLookupWizard(rl) {
|
|
361
|
+
const vendor = await promptChoice(rl, "Which vendor template do you want to use?", [{ value: "deel", label: "Deel", description: "Global payroll, EOR, and contractor workflows" }], "deel");
|
|
362
|
+
writeWizardLine();
|
|
363
|
+
const market = await promptChoice(rl, "Which market should the BigQuery lookup target?", [
|
|
364
|
+
{ value: "dach", label: "DACH", description: "Germany, Austria, Switzerland" },
|
|
365
|
+
{ value: "europe", label: "Europe" },
|
|
366
|
+
{ value: "global", label: "Global" }
|
|
367
|
+
], "dach");
|
|
368
|
+
const limit = z.coerce.number().int().min(1).max(5000).parse(await promptText(rl, "How many rows should the lookup return?", { defaultValue: "100", required: true }));
|
|
369
|
+
const execute = await promptYesNo(rl, "Execute the BigQuery lookup now?", false);
|
|
370
|
+
writeWizardLine();
|
|
371
|
+
const slug = slugify(vendor);
|
|
372
|
+
const icpPath = await promptText(rl, "Where should I save the ICP JSON?", {
|
|
373
|
+
defaultValue: `./data/${slug}-icp-${market}.json`,
|
|
374
|
+
required: true
|
|
375
|
+
});
|
|
376
|
+
const sqlPath = await promptText(rl, "Where should I save the generated SQL?", {
|
|
377
|
+
defaultValue: `./data/${slug}-lookup-${market}.sql`,
|
|
378
|
+
required: true
|
|
379
|
+
});
|
|
380
|
+
const rawPath = execute
|
|
381
|
+
? await promptText(rl, "Where should I save raw BigQuery rows?", {
|
|
382
|
+
defaultValue: `./data/${slug}-leads-raw-${market}.json`,
|
|
383
|
+
required: true
|
|
384
|
+
})
|
|
385
|
+
: "";
|
|
386
|
+
const leadPath = execute
|
|
387
|
+
? await promptText(rl, "Where should I save normalized leads?", {
|
|
388
|
+
defaultValue: `./data/${slug}-leads-${market}.json`,
|
|
389
|
+
required: true
|
|
390
|
+
})
|
|
391
|
+
: "";
|
|
392
|
+
writeWizardLine();
|
|
393
|
+
const icp = buildVendorIcp(vendor, market);
|
|
394
|
+
await writeJsonFile(icpPath, icp);
|
|
395
|
+
const sql = buildBigQueryLeadLookupSql(icp, {
|
|
396
|
+
table: "icpidentifier.SalesGPT.leadPool_new",
|
|
397
|
+
companyField: "companyName",
|
|
398
|
+
domainField: "domain",
|
|
399
|
+
regionField: undefined,
|
|
400
|
+
keywordFields: splitCsv("companyName,industry,description,tagline,specialties"),
|
|
401
|
+
titleField: "jobTitle",
|
|
402
|
+
industryField: "industry",
|
|
403
|
+
companySizeField: "companySize",
|
|
404
|
+
countryField: "company_countryCode",
|
|
405
|
+
firstNameField: "firstName",
|
|
406
|
+
lastNameField: "lastName",
|
|
407
|
+
emailField: "email",
|
|
408
|
+
limit,
|
|
409
|
+
additionalWhere: undefined,
|
|
410
|
+
useSalesprompterGuards: true
|
|
411
|
+
});
|
|
412
|
+
await writeTextFile(sqlPath, `${sql}\n`);
|
|
413
|
+
let executedRowCount = null;
|
|
414
|
+
if (execute) {
|
|
415
|
+
const rows = await runBigQueryQuery(sql, { maxRows: limit });
|
|
416
|
+
const parsedRows = z.array(z.record(z.string(), z.unknown())).parse(rows);
|
|
417
|
+
await writeJsonFile(rawPath, parsedRows);
|
|
418
|
+
const normalizedLeads = normalizeBigQueryLeadRows(parsedRows);
|
|
419
|
+
await writeJsonFile(leadPath, normalizedLeads);
|
|
420
|
+
executedRowCount = parsedRows.length;
|
|
421
|
+
}
|
|
422
|
+
writeWizardLine(`Saved vendor ICP to ${icpPath}.`);
|
|
423
|
+
writeWizardLine(`Saved lookup SQL to ${sqlPath}.`);
|
|
424
|
+
if (execute) {
|
|
425
|
+
writeWizardLine(`Saved ${executedRowCount ?? 0} raw rows to ${rawPath}.`);
|
|
426
|
+
writeWizardLine(`Saved normalized leads to ${leadPath}.`);
|
|
427
|
+
}
|
|
428
|
+
writeWizardLine();
|
|
429
|
+
writeWizardLine("Equivalent raw commands:");
|
|
430
|
+
writeWizardLine(` ${buildCommandLine(["salesprompter", "icp:vendor", "--vendor", vendor, "--market", market, "--out", icpPath])}`);
|
|
431
|
+
const lookupArgs = ["salesprompter", "leads:lookup:bq", "--icp", icpPath, "--limit", String(limit), "--sql-out", sqlPath];
|
|
432
|
+
if (execute) {
|
|
433
|
+
lookupArgs.push("--execute", "--out", rawPath, "--lead-out", leadPath);
|
|
434
|
+
}
|
|
435
|
+
writeWizardLine(` ${buildCommandLine(lookupArgs)}`);
|
|
436
|
+
}
|
|
437
|
+
async function runWizard(options) {
|
|
438
|
+
if (runtimeOutputOptions.json || runtimeOutputOptions.quiet) {
|
|
439
|
+
throw new Error("wizard does not support --json or --quiet.");
|
|
440
|
+
}
|
|
441
|
+
writeWizardLine("Salesprompter Wizard");
|
|
442
|
+
writeWizardLine("Choose a workflow and I will map it to the underlying CLI commands.");
|
|
443
|
+
writeWizardLine();
|
|
444
|
+
await ensureWizardSession(options);
|
|
445
|
+
const rl = createInterface({
|
|
446
|
+
input: process.stdin,
|
|
447
|
+
output: process.stdout
|
|
448
|
+
});
|
|
449
|
+
try {
|
|
450
|
+
const flow = await promptChoice(rl, "What do you want to do?", [
|
|
451
|
+
{ value: "vendor-icp", label: "Create a vendor ICP template", description: "Best for prompts like \"determine Deel's ICP\"" },
|
|
452
|
+
{ value: "target-account", label: "Generate leads for a target company", description: "Best for prompts like \"find contacts at deel.com\"" },
|
|
453
|
+
{ value: "vendor-lookup", label: "Create a vendor ICP and prepare a BigQuery lookup", description: "Best for warehouse-backed lead discovery" }
|
|
454
|
+
], "vendor-icp");
|
|
455
|
+
writeWizardLine();
|
|
456
|
+
if (flow === "vendor-icp") {
|
|
457
|
+
await runVendorIcpWizard(rl);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (flow === "target-account") {
|
|
461
|
+
await runTargetAccountWizard(rl);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
await runVendorLookupWizard(rl);
|
|
465
|
+
}
|
|
466
|
+
finally {
|
|
467
|
+
rl.close();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function shouldAutoRunWizard(argv) {
|
|
471
|
+
return argv.length <= 2 && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
472
|
+
}
|
|
92
473
|
function buildCliError(error) {
|
|
93
474
|
if (error instanceof z.ZodError) {
|
|
94
475
|
return {
|
|
@@ -234,53 +615,15 @@ program
|
|
|
234
615
|
.option("--timeout-seconds <number>", "Device flow wait timeout in seconds", "180")
|
|
235
616
|
.action(async (options) => {
|
|
236
617
|
const timeoutSeconds = z.coerce.number().int().min(30).max(1800).parse(options.timeoutSeconds);
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
status: "ok",
|
|
242
|
-
method: "token",
|
|
243
|
-
apiBaseUrl: session.apiBaseUrl,
|
|
244
|
-
user: session.user,
|
|
245
|
-
expiresAt: session.expiresAt ?? null
|
|
246
|
-
});
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
const startedAt = new Date().toISOString();
|
|
250
|
-
try {
|
|
251
|
-
const session = await loginWithBrowserConnect({
|
|
252
|
-
apiBaseUrl: options.apiUrl,
|
|
253
|
-
timeoutSeconds,
|
|
254
|
-
onConnectStart: writeBrowserLoginInstructions
|
|
255
|
-
});
|
|
256
|
-
printOutput({
|
|
257
|
-
status: "ok",
|
|
258
|
-
method: "browser",
|
|
259
|
-
startedAt,
|
|
260
|
-
apiBaseUrl: session.apiBaseUrl,
|
|
261
|
-
user: session.user,
|
|
262
|
-
expiresAt: session.expiresAt ?? null
|
|
263
|
-
});
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
268
|
-
if (!message.includes("timed out waiting for browser login") && !message.includes("invalid localhost callback response") && !message.includes("request failed")) {
|
|
269
|
-
throw error;
|
|
270
|
-
}
|
|
271
|
-
if (!/request failed \((401|403|404|405|500|501|502|503|504)\)/.test(message)) {
|
|
272
|
-
throw error;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
const result = await loginWithDeviceFlow({
|
|
276
|
-
apiBaseUrl: options.apiUrl,
|
|
277
|
-
timeoutSeconds,
|
|
278
|
-
onDeviceStart: writeDeviceLoginInstructions
|
|
618
|
+
const result = await performLogin({
|
|
619
|
+
token: options.token,
|
|
620
|
+
apiUrl: options.apiUrl,
|
|
621
|
+
timeoutSeconds
|
|
279
622
|
});
|
|
280
623
|
printOutput({
|
|
281
624
|
status: "ok",
|
|
282
|
-
method:
|
|
283
|
-
startedAt,
|
|
625
|
+
method: result.method,
|
|
626
|
+
startedAt: result.startedAt,
|
|
284
627
|
verificationUrl: result.verificationUrl,
|
|
285
628
|
userCode: result.userCode,
|
|
286
629
|
apiBaseUrl: result.session.apiBaseUrl,
|
|
@@ -288,6 +631,19 @@ program
|
|
|
288
631
|
expiresAt: result.session.expiresAt ?? null
|
|
289
632
|
});
|
|
290
633
|
});
|
|
634
|
+
program
|
|
635
|
+
.command("wizard")
|
|
636
|
+
.alias("start")
|
|
637
|
+
.description("Run an interactive guided workflow for common Salesprompter tasks.")
|
|
638
|
+
.option("--api-url <url>", "Salesprompter API base URL, defaults to SALESPROMPTER_API_BASE_URL or salesprompter.ai")
|
|
639
|
+
.option("--timeout-seconds <number>", "Auth login timeout in seconds when the wizard needs to sign in", "180")
|
|
640
|
+
.action(async (options) => {
|
|
641
|
+
const timeoutSeconds = z.coerce.number().int().min(30).max(1800).parse(options.timeoutSeconds);
|
|
642
|
+
await runWizard({
|
|
643
|
+
apiUrl: options.apiUrl,
|
|
644
|
+
timeoutSeconds
|
|
645
|
+
});
|
|
646
|
+
});
|
|
291
647
|
program
|
|
292
648
|
.command("auth:whoami")
|
|
293
649
|
.description("Show current authenticated user and session status.")
|
|
@@ -313,7 +669,7 @@ program
|
|
|
313
669
|
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
314
670
|
applyGlobalOutputOptions(actionCommand);
|
|
315
671
|
const commandName = actionCommand.name();
|
|
316
|
-
if (commandName.startsWith("auth:")) {
|
|
672
|
+
if (commandName.startsWith("auth:") || commandName === "wizard") {
|
|
317
673
|
return;
|
|
318
674
|
}
|
|
319
675
|
if (shouldBypassAuth()) {
|
|
@@ -980,7 +1336,18 @@ program
|
|
|
980
1336
|
execution
|
|
981
1337
|
});
|
|
982
1338
|
});
|
|
983
|
-
|
|
1339
|
+
async function main() {
|
|
1340
|
+
if (shouldAutoRunWizard(process.argv)) {
|
|
1341
|
+
await runWizard();
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (process.argv.length <= 2) {
|
|
1345
|
+
program.outputHelp();
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
await program.parseAsync(process.argv);
|
|
1349
|
+
}
|
|
1350
|
+
main().catch((error) => {
|
|
984
1351
|
const cliError = buildCliError(error);
|
|
985
1352
|
const space = runtimeOutputOptions.json ? undefined : 2;
|
|
986
1353
|
if (runtimeOutputOptions.json) {
|
package/package.json
CHANGED