salesprompter-cli 0.1.14 → 0.1.15

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 +116 -0
  2. package/package.json +1 -1
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 { emitKeypressEvents } from "node:readline";
4
5
  import { createInterface } from "node:readline/promises";
5
6
  import { Command } from "commander";
6
7
  import { z } from "zod";
@@ -198,11 +199,121 @@ function writeSessionSummary(session) {
198
199
  function normalizeChoiceText(value) {
199
200
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
200
201
  }
202
+ function supportsInteractiveChoice() {
203
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function");
204
+ }
205
+ function renderInteractiveChoiceLines(prompt, options, activeIndex) {
206
+ const lines = [prompt];
207
+ for (const [index, option] of options.entries()) {
208
+ const description = option.description ? ` - ${option.description}` : "";
209
+ const text = `${index + 1}. ${option.label}${description}`;
210
+ if (index === activeIndex) {
211
+ lines.push(`\x1b[7m> ${text}\x1b[0m`);
212
+ }
213
+ else {
214
+ lines.push(` ${text}`);
215
+ }
216
+ }
217
+ lines.push("\x1b[2mUse Up/Down or number keys. Press Enter to continue.\x1b[0m");
218
+ return lines;
219
+ }
220
+ function redrawInteractiveChoice(lines, previousLineCount) {
221
+ if (previousLineCount > 0) {
222
+ for (let index = 0; index < previousLineCount; index += 1) {
223
+ process.stdout.write("\x1b[1A\x1b[2K\r");
224
+ }
225
+ }
226
+ for (const line of lines) {
227
+ process.stdout.write(`${line}\n`);
228
+ }
229
+ }
230
+ async function promptChoiceInteractive(prompt, options, defaultIndex) {
231
+ const stdin = process.stdin;
232
+ let activeIndex = defaultIndex;
233
+ let renderedLineCount = 0;
234
+ const render = () => {
235
+ const lines = renderInteractiveChoiceLines(prompt, options, activeIndex);
236
+ redrawInteractiveChoice(lines, renderedLineCount);
237
+ renderedLineCount = lines.length;
238
+ };
239
+ const cleanup = () => {
240
+ stdin.removeListener("keypress", onKeypress);
241
+ process.removeListener("SIGINT", onSigint);
242
+ if (stdin.isTTY) {
243
+ stdin.setRawMode(false);
244
+ }
245
+ stdin.pause();
246
+ };
247
+ const finalize = (index, resolve) => {
248
+ const selected = options[index];
249
+ if (!selected) {
250
+ throw new Error("wizard selection invariant violated");
251
+ }
252
+ const summary = `${prompt} ${selected.label}`;
253
+ redrawInteractiveChoice([summary], renderedLineCount);
254
+ renderedLineCount = 1;
255
+ process.stdout.write("\n");
256
+ cleanup();
257
+ resolve(selected.value);
258
+ };
259
+ const cancel = (reject) => {
260
+ cleanup();
261
+ process.stdout.write("\n");
262
+ reject(new Error("prompt cancelled"));
263
+ };
264
+ const onSigint = () => {
265
+ cancel(rejectPromise);
266
+ };
267
+ const onKeypress = (_character, key) => {
268
+ if (key.ctrl && key.name === "c") {
269
+ return cancel(rejectPromise);
270
+ }
271
+ if (key.name === "up") {
272
+ activeIndex = activeIndex === 0 ? options.length - 1 : activeIndex - 1;
273
+ render();
274
+ return;
275
+ }
276
+ if (key.name === "down") {
277
+ activeIndex = activeIndex === options.length - 1 ? 0 : activeIndex + 1;
278
+ render();
279
+ return;
280
+ }
281
+ if (key.name === "return" || key.name === "enter") {
282
+ finalize(activeIndex, resolvePromise);
283
+ return;
284
+ }
285
+ const digit = Number(key.sequence ?? "");
286
+ if (Number.isInteger(digit) && digit >= 1 && digit <= options.length) {
287
+ finalize(digit - 1, resolvePromise);
288
+ }
289
+ };
290
+ let resolvePromise;
291
+ let rejectPromise;
292
+ return await new Promise((resolve, reject) => {
293
+ resolvePromise = resolve;
294
+ rejectPromise = reject;
295
+ emitKeypressEvents(stdin);
296
+ stdin.setRawMode(true);
297
+ stdin.resume();
298
+ stdin.on("keypress", onKeypress);
299
+ process.on("SIGINT", onSigint);
300
+ render();
301
+ });
302
+ }
201
303
  async function promptChoice(rl, prompt, options, defaultValue) {
202
304
  const defaultIndex = options.findIndex((option) => option.value === defaultValue);
203
305
  if (defaultIndex === -1) {
204
306
  throw new Error(`wizard default option is invalid for ${prompt}`);
205
307
  }
308
+ if (supportsInteractiveChoice()) {
309
+ rl.pause?.();
310
+ try {
311
+ return await promptChoiceInteractive(prompt, options, defaultIndex);
312
+ }
313
+ finally {
314
+ rl.resume?.();
315
+ }
316
+ }
206
317
  while (true) {
207
318
  writeWizardLine(prompt);
208
319
  for (const [index, option] of options.entries()) {
@@ -1551,6 +1662,11 @@ async function main() {
1551
1662
  await program.parseAsync(process.argv);
1552
1663
  }
1553
1664
  main().catch((error) => {
1665
+ if (error instanceof Error &&
1666
+ (error.message === "prompt cancelled" || error.message === "readline was closed")) {
1667
+ process.exitCode = 130;
1668
+ return;
1669
+ }
1554
1670
  const cliError = buildCliError(error);
1555
1671
  const space = runtimeOutputOptions.json ? undefined : 2;
1556
1672
  if (runtimeOutputOptions.json) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "JSON-first sales prospecting CLI for ICP definition, lead generation, enrichment, scoring, and CRM/outreach sync.",
5
5
  "type": "module",
6
6
  "bin": {