@prompts-gpt/client 0.2.4 → 0.2.6

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 CHANGED
@@ -27,15 +27,32 @@ class CliError extends Error {
27
27
  async function main() {
28
28
  const argv = process.argv.slice(2);
29
29
  if (argv.length === 0) {
30
- console.log(`Prompts-GPT CLI sync and run AI prompt packs locally
31
-
32
- Get started:
33
- prompts-gpt quickstart setup credentials, config, and first run
34
- prompts-gpt list — see available prompts and sweeps
35
- prompts-gpt sweep — run a multi-iteration sweep (auto-detects local sweeps)
36
-
37
- Run \`prompts-gpt help\` for the full command list.
38
- `);
30
+ const assets = await discoverWorkspaceAssets(process.cwd()).catch(() => null);
31
+ const hasSetup = assets && (assets.credentialsFound || assets.configFound || assets.prompts.length > 0 || assets.sweeps.length > 0);
32
+ if (hasSetup && assets) {
33
+ console.log(`Prompts-GPT CLI ${assets.prompts.length} prompts, ${assets.sweeps.length} sweeps available\n`);
34
+ if (assets.sweeps.length > 0) {
35
+ console.log(" prompts-gpt sweep — run a sweep (interactive picker)");
36
+ }
37
+ if (assets.prompts.length > 0) {
38
+ console.log(" prompts-gpt run — run a prompt");
39
+ }
40
+ console.log(" prompts-gpt list — see all available assets");
41
+ console.log(" prompts-gpt sync — refresh prompts from studio");
42
+ console.log(" prompts-gpt status — check workspace readiness");
43
+ console.log(`\nRun \`prompts-gpt help\` for the full command list.\n`);
44
+ }
45
+ else {
46
+ console.log(`Prompts-GPT CLI — sync and run AI prompt packs locally\n`);
47
+ console.log("Get started:");
48
+ console.log(" prompts-gpt quickstart — setup credentials, config, and first run");
49
+ console.log(" prompts-gpt init --token-prompt — save your project token");
50
+ console.log("");
51
+ console.log("Already have sweep files?");
52
+ console.log(" prompts-gpt sweep — auto-detect and run local sweeps");
53
+ console.log(" prompts-gpt list — see available prompts and sweeps");
54
+ console.log(`\nRun \`prompts-gpt help\` for the full command list.\n`);
55
+ }
39
56
  return;
40
57
  }
41
58
  const first = argv[0];
@@ -84,14 +101,21 @@ async function runCommand(command, flags) {
84
101
  console.log(JSON.stringify({ cwd, providers }, null, 2));
85
102
  return;
86
103
  }
87
- console.log(`Workspace: ${cwd}`);
104
+ console.log(`Workspace: ${cwd}\n`);
88
105
  for (const provider of providers) {
89
- const status = provider.available ? "✓ available" : "✗ missing";
90
- console.log(`${provider.provider}: ${status} | bin=${provider.bin} | model=${provider.modelDefault}${provider.version ? ` | version=${provider.version}` : ""}`);
106
+ const icon = provider.available ? "✓" : "✗";
107
+ console.log(`${icon} ${provider.provider}: ${provider.available ? "available" : "missing"} | bin=${provider.bin} | model=${provider.modelDefault}${provider.version ? ` | ${provider.version}` : ""}`);
91
108
  if (!provider.available) {
92
- console.log(` install: ${provider.installHint}`);
109
+ console.log(` ${provider.installHint}`);
93
110
  }
94
111
  }
112
+ const noneAvailable = providers.every((p) => !p.available);
113
+ if (noneAvailable) {
114
+ console.log("\nNo providers installed. Quick install:");
115
+ console.log(" npm install -g @openai/codex # OpenAI Codex");
116
+ console.log(" npm install -g @anthropic-ai/claude-code # Claude Code");
117
+ console.log(" # Cursor: install Cursor IDE");
118
+ }
95
119
  return;
96
120
  }
97
121
  if (command === "setup") {
@@ -130,11 +154,9 @@ async function runCommand(command, flags) {
130
154
  console.log(JSON.stringify(result, null, 2));
131
155
  return;
132
156
  }
133
- console.log(`Created run config: ${result.configPath}`);
134
157
  console.log(`Config: ${result.configPath}`);
135
158
  console.log(`Agent: ${result.config.defaultAgent ?? "router"} | Providers: ${(result.config.providerOrder ?? []).join(", ")}`);
136
159
  if (result.sourceSummary.defaultPromptFile) {
137
- console.log(`Default prompt file: ${result.sourceSummary.defaultPromptFile}`);
138
160
  console.log(`Default prompt: ${result.sourceSummary.defaultPromptFile}`);
139
161
  }
140
162
  const availableCount = result.providerSummary.filter((p) => p.available).length;
@@ -143,14 +165,42 @@ async function runCommand(command, flags) {
143
165
  console.log("⚠ No provider CLIs detected. Install codex, cursor agent, claude, or copilot.");
144
166
  }
145
167
  const setupAssets = await discoverWorkspaceAssets(cwd);
146
- console.log("Next steps:");
147
- console.log(` prompts-gpt run${result.sourceSummary.defaultPromptFile ? "" : " --prompt-file <path>"}`);
148
- if (setupAssets.sweeps.length > 0) {
149
- console.log(` prompts-gpt sweep run a multi-iteration sweep (${setupAssets.sweeps.length} found)`);
168
+ if (isTTYInteractive() && (setupAssets.sweeps.length > 0 || setupAssets.prompts.length > 0)) {
169
+ const setupOptions = [];
170
+ for (const s of setupAssets.sweeps) {
171
+ setupOptions.push({ label: `sweep: ${s.name}`, value: `sweep:${s.file}` });
172
+ }
173
+ for (const p of setupAssets.prompts.slice(0, 3)) {
174
+ setupOptions.push({ label: `run: ${p.title}`, value: `run:.prompts-gpt/${p.file}` });
175
+ }
176
+ setupOptions.push({ label: "prompts-gpt list", value: "list" });
177
+ setupOptions.push({ label: "(done)", value: "done" });
178
+ console.log("");
179
+ const setupPicked = await interactiveSelect("What next?", setupOptions);
180
+ if (setupPicked === "list") {
181
+ const { spawnSync: spSync } = await import("node:child_process");
182
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
183
+ spSync(process.execPath, [cliEntry, "list"], { stdio: "inherit", cwd });
184
+ }
185
+ else if (setupPicked !== "done") {
186
+ const [action, file] = setupPicked.split(":", 2);
187
+ const cmd = action === "sweep" ? "sweep" : "run";
188
+ console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
189
+ const { spawnSync: spSync } = await import("node:child_process");
190
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
191
+ const setupResult = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
192
+ process.exitCode = setupResult.status ?? 1;
193
+ }
194
+ }
195
+ else {
196
+ console.log("Next steps:");
197
+ console.log(` prompts-gpt run${result.sourceSummary.defaultPromptFile ? "" : " --prompt-file <path>"}`);
198
+ if (setupAssets.sweeps.length > 0) {
199
+ console.log(` prompts-gpt sweep — run a multi-iteration sweep (${setupAssets.sweeps.length} found)`);
200
+ }
201
+ console.log(" prompts-gpt list — see all runnable assets");
202
+ console.log(" prompts-gpt status — check workspace readiness");
150
203
  }
151
- console.log(" prompts-gpt list — see all runnable assets");
152
- console.log(" prompts-gpt status — check workspace readiness");
153
- console.log(" prompts-gpt validate — validate your config");
154
204
  return;
155
205
  }
156
206
  if (command === "doctor") {
@@ -160,17 +210,38 @@ async function runCommand(command, flags) {
160
210
  console.log(JSON.stringify(report, null, 2));
161
211
  return;
162
212
  }
163
- console.log(`Workspace: ${report.cwd}`);
164
- console.log(`Node: ${report.nodeVersion} | OS: ${report.osPlatform}/${report.osArch} | CPUs: ${report.cpuCount}`);
165
- console.log(`Config: ${report.configFound ? "found" : "not found"} (${report.configPath})`);
213
+ console.log("Prompts-GPT Doctor");
214
+ console.log("==================\n");
215
+ console.log("System:");
216
+ console.log(` ${report.nodeVersion.startsWith("v18") || report.nodeVersion.startsWith("v2") ? "✓" : "⚠"} Node: ${report.nodeVersion}`);
217
+ console.log(` ✓ OS: ${report.osPlatform}/${report.osArch} | CPUs: ${report.cpuCount}`);
218
+ console.log(` ${report.configFound ? "✓" : "✗"} Config: ${report.configFound ? report.configPath : "not found — run \`prompts-gpt setup\`"}`);
219
+ console.log(`\n Workspace: ${report.cwd}\n`);
220
+ console.log("Providers:");
166
221
  for (const provider of report.providers) {
167
- console.log(`${provider.provider}: ${provider.available ? "available" : "missing"} | bin=${provider.bin}${provider.version ? ` | version=${provider.version}` : ""}`);
222
+ const icon = provider.available ? "" : "";
223
+ console.log(` ${icon} ${provider.provider}: ${provider.available ? `${provider.bin}` : "not found"}${provider.version ? ` (${provider.version})` : ""}`);
168
224
  if (!provider.available) {
169
- console.log(` hint: ${provider.installHint}`);
225
+ console.log(` ${provider.installHint}`);
170
226
  }
171
227
  }
172
- for (const note of report.notes) {
173
- console.log(`note: ${note}`);
228
+ const configNotes = report.notes.filter((n) => n.includes("config") || n.includes("Config") || n.includes("provider order") || n.includes("Router"));
229
+ const sweepNotes = report.notes.filter((n) => n.includes("weep") || n.includes("lock"));
230
+ const otherNotes = report.notes.filter((n) => !configNotes.includes(n) && !sweepNotes.includes(n));
231
+ if (configNotes.length > 0) {
232
+ console.log("\nConfiguration:");
233
+ for (const n of configNotes)
234
+ console.log(` ℹ ${n}`);
235
+ }
236
+ if (sweepNotes.length > 0) {
237
+ console.log("\nSweep:");
238
+ for (const n of sweepNotes)
239
+ console.log(` ℹ ${n}`);
240
+ }
241
+ if (otherNotes.length > 0) {
242
+ console.log("\nNotes:");
243
+ for (const n of otherNotes)
244
+ console.log(` ℹ ${n}`);
174
245
  }
175
246
  return;
176
247
  }
@@ -208,9 +279,23 @@ async function runCommand(command, flags) {
208
279
  const iterCount = await readSweepIterationsFromFrontmatter(path.resolve(cwd, s.file));
209
280
  const iterHint = iterCount ? ` (${iterCount} iterations)` : "";
210
281
  const iterFlag = iterCount ? ` --iterations ${iterCount}` : "";
211
- console.log(` ${s.name}${iterHint}`);
282
+ let lastRun = "";
283
+ try {
284
+ const artifactsDir = path.resolve(cwd, ".prompts-gpt-runs");
285
+ if (existsSync(artifactsDir)) {
286
+ const { readdir: rd, stat: fsStat } = await import("node:fs/promises");
287
+ const dirs = await rd(artifactsDir);
288
+ const sweepDirs = dirs.filter((d) => d.includes("sweep")).sort().reverse();
289
+ if (sweepDirs[0]) {
290
+ const s2 = await fsStat(path.join(artifactsDir, sweepDirs[0]));
291
+ lastRun = ` | last run: ${s2.mtime.toLocaleDateString()}`;
292
+ }
293
+ }
294
+ }
295
+ catch { /* skip */ }
296
+ console.log(` ${s.name}${iterHint}${lastRun}`);
212
297
  console.log(` file: ${s.file}`);
213
- console.log(` sweep: prompts-gpt sweep --prompt-file ${s.file}${iterFlag}`);
298
+ console.log(` sweep: prompts-gpt sweep -f ${s.file}${iterFlag}`);
214
299
  }
215
300
  console.log("");
216
301
  }
@@ -234,6 +319,31 @@ async function runCommand(command, flags) {
234
319
  console.log(`Config: ${assets.configFound ? "found" : "not found — run `prompts-gpt setup`"}`);
235
320
  console.log(`Manifest: ${assets.manifestFound ? "found" : "not found — run `prompts-gpt sync`"}`);
236
321
  console.log(`Credentials: ${assets.credentialsFound ? "found" : "not found — run `prompts-gpt init --token <token>`"}`);
322
+ if (isTTYInteractive() && availableProviderNames.length > 0) {
323
+ const runnableOptions = [];
324
+ for (const p of assets.prompts) {
325
+ runnableOptions.push({ label: `run: ${p.slug} — ${p.title}`, value: `run:.prompts-gpt/${p.file}` });
326
+ }
327
+ for (const s of assets.sweeps) {
328
+ runnableOptions.push({ label: `sweep: ${s.name}`, value: `sweep:${s.file}` });
329
+ }
330
+ if (runnableOptions.length > 1) {
331
+ console.log("");
332
+ const picked = await interactiveSelect("Run something now?", [
333
+ ...runnableOptions,
334
+ { label: "(exit)", value: "exit" },
335
+ ]);
336
+ if (picked !== "exit") {
337
+ const [action, file] = picked.split(":", 2);
338
+ const cmd = action === "sweep" ? "sweep" : "run";
339
+ console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
340
+ const { spawnSync: spSync } = await import("node:child_process");
341
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
342
+ const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
343
+ process.exitCode = result.status ?? 1;
344
+ }
345
+ }
346
+ }
237
347
  return;
238
348
  }
239
349
  if (command === "status") {
@@ -266,7 +376,22 @@ async function runCommand(command, flags) {
266
376
  }
267
377
  }
268
378
  else {
269
- console.log(" Providers: ✗ — no CLI found");
379
+ console.log(" Providers: ✗ — no CLI found. Install one:");
380
+ console.log(" npm install -g @openai/codex # Codex");
381
+ console.log(" npm install -g @anthropic-ai/claude-code # Claude Code");
382
+ console.log(" # Cursor: install Cursor IDE (includes agent CLI)");
383
+ }
384
+ console.log("");
385
+ const lockFile = path.resolve(cwd, ".sweep.lock");
386
+ if (existsSync(lockFile)) {
387
+ try {
388
+ const { readFile: fsRead } = await import("node:fs/promises");
389
+ const lockData = JSON.parse(await fsRead(lockFile, "utf8"));
390
+ console.log(` Sweep lock: ⚠ active (PID ${lockData.pid}, started ${lockData.startedAt})`);
391
+ }
392
+ catch {
393
+ console.log(" Sweep lock: ⚠ found but unreadable");
394
+ }
270
395
  }
271
396
  console.log("");
272
397
  if (assets.prompts.length > 0 && availableProviders.length > 0) {
@@ -310,6 +435,20 @@ async function runCommand(command, flags) {
310
435
  for (const w of result.warnings) {
311
436
  console.log(` warning: ${w}`);
312
437
  }
438
+ const validAssets = await discoverWorkspaceAssets(cwd);
439
+ if (validAssets.sweeps.length > 0) {
440
+ console.log(`\nSweep files (${validAssets.sweeps.length}):`);
441
+ for (const s of validAssets.sweeps) {
442
+ const fm = await readSweepFrontmatter(path.resolve(cwd, s.file));
443
+ const issues = [];
444
+ if (!fm.iterations)
445
+ issues.push("no iterations in frontmatter");
446
+ if (!fm.title)
447
+ issues.push("no title heading");
448
+ const icon = issues.length === 0 ? "✓" : "⚠";
449
+ console.log(` ${icon} ${s.name}${issues.length > 0 ? ` — ${issues.join(", ")}` : ""}`);
450
+ }
451
+ }
313
452
  if (result.valid && result.errors.length === 0 && result.warnings.length === 0) {
314
453
  console.log(" No issues found.");
315
454
  }
@@ -323,18 +462,26 @@ async function runCommand(command, flags) {
323
462
  if (!promptFile && !Boolean(flags.json)) {
324
463
  if (!config.defaultPromptFile && config.batchDefaults.promptFiles.length === 0 && !config.batchDefaults.manifestPath) {
325
464
  const assets = await discoverWorkspaceAssets(cwd);
326
- if (assets.prompts.length > 0 || assets.sweeps.length > 0) {
327
- console.log("No default prompt configured. No default prompt file configured. Available options:\n");
465
+ if (assets.prompts.length > 0 && isTTYInteractive(flags)) {
466
+ const promptOptions = assets.prompts.map((p) => ({
467
+ label: `${p.slug} — ${p.title}`,
468
+ value: `.prompts-gpt/${p.file}`,
469
+ }));
470
+ const picked = await interactiveSelect("Select a prompt to run:", promptOptions);
471
+ flags["prompt-file"] = picked;
472
+ }
473
+ else if (assets.prompts.length > 0 || assets.sweeps.length > 0) {
474
+ console.log("No default prompt configured. Available options:\n");
328
475
  if (assets.prompts.length > 0) {
329
476
  console.log("Prompt packs:");
330
477
  for (const p of assets.prompts) {
331
- console.log(` prompts-gpt run --prompt-file .prompts-gpt/${p.file}`);
478
+ console.log(` prompts-gpt run -f .prompts-gpt/${p.file}`);
332
479
  }
333
480
  }
334
481
  if (assets.sweeps.length > 0) {
335
482
  console.log("\nSweep prompts (use `sweep` command):");
336
483
  for (const s of assets.sweeps) {
337
- console.log(` prompts-gpt sweep --prompt-file ${s.file}`);
484
+ console.log(` prompts-gpt sweep -f ${s.file}`);
338
485
  }
339
486
  }
340
487
  console.log("\nOr set a default: prompts-gpt setup --prompt-file <path>");
@@ -342,6 +489,36 @@ async function runCommand(command, flags) {
342
489
  }
343
490
  }
344
491
  }
492
+ const runProviders = await detectProviders(cwd);
493
+ const runAvailable = runProviders.filter((p) => p.available);
494
+ if (!getStringFlag(flags, "agent") && isTTYInteractive(flags) && !Boolean(flags.json) && runAvailable.length > 1) {
495
+ const lastRunProvider = await getLastUsedProvider(cwd);
496
+ const providerOpts = runAvailable.map((p) => ({
497
+ label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})${p.provider === lastRunProvider ? " ★" : ""}`,
498
+ value: p.provider,
499
+ }));
500
+ providerOpts.push({ label: "router (auto-select)", value: "router" });
501
+ if (lastRunProvider) {
502
+ const idx = providerOpts.findIndex((o) => o.value === lastRunProvider);
503
+ if (idx > 0) {
504
+ const [item] = providerOpts.splice(idx, 1);
505
+ providerOpts.unshift(item);
506
+ }
507
+ }
508
+ flags.agent = await interactiveSelect("Select a provider:", providerOpts);
509
+ if (flags.agent !== "router") {
510
+ await saveLastUsedProvider(cwd, flags.agent);
511
+ }
512
+ }
513
+ if (!getStringFlag(flags, "model") && isTTYInteractive(flags) && !Boolean(flags.json)) {
514
+ const currentAgent = resolveRunAgent(flags, config.defaultAgent);
515
+ if (currentAgent !== "router") {
516
+ const modelChoices = getModelChoicesForProvider(currentAgent, config);
517
+ if (modelChoices.length > 0) {
518
+ flags.model = await interactiveSelect("Select a model:", modelChoices);
519
+ }
520
+ }
521
+ }
345
522
  const agent = resolveRunAgent(flags, config.defaultAgent);
346
523
  if (Boolean(flags["dry-run"])) {
347
524
  const providers = await detectProviders(cwd);
@@ -366,6 +543,23 @@ async function runCommand(command, flags) {
366
543
  console.log(` Timeout: ${getStringFlag(flags, "timeout") || config.timeoutSeconds}s`);
367
544
  return;
368
545
  }
546
+ if (isTTYInteractive(flags) && !Boolean(flags.json) && !Boolean(flags["dry-run"])) {
547
+ const previewFile = getStringFlag(flags, "prompt-file");
548
+ if (previewFile && existsSync(path.resolve(cwd, previewFile))) {
549
+ try {
550
+ const { readFile: fsRead } = await import("node:fs/promises");
551
+ const previewContent = await fsRead(path.resolve(cwd, previewFile), "utf8");
552
+ const previewLines = previewContent.split("\n").slice(0, 5);
553
+ console.log(`\nPrompt preview (${path.basename(previewFile)}):`);
554
+ for (const line of previewLines)
555
+ console.log(` ${line}`);
556
+ if (previewContent.split("\n").length > 5)
557
+ console.log(` ... (${previewContent.split("\n").length - 5} more lines)`);
558
+ console.log("");
559
+ }
560
+ catch { /* skip preview */ }
561
+ }
562
+ }
369
563
  const modelFlag = getStringFlag(flags, "model");
370
564
  if (modelFlag && !Boolean(flags.json) && agent !== "router") {
371
565
  const providers = await detectProviders(cwd);
@@ -404,9 +598,29 @@ async function runCommand(command, flags) {
404
598
  console.log(`Summary: ${result.summaryFile}`);
405
599
  console.log(`Log: ${result.logFile}`);
406
600
  if (result.exitCode === 0) {
601
+ try {
602
+ const { readFile: fsRead } = await import("node:fs/promises");
603
+ const deltaContent = await fsRead(result.worktreeDeltaFile, "utf8");
604
+ if (!deltaContent.includes("No worktree delta")) {
605
+ const afterLines = deltaContent.split("\n").filter((l) => l.startsWith("=== AFTER ===") ? false : true);
606
+ const changedFiles = afterLines.filter((l) => /^\s*[MADRCU?!]/.test(l)).length;
607
+ if (changedFiles > 0) {
608
+ console.log(`Files changed: ${changedFiles}`);
609
+ }
610
+ }
611
+ }
612
+ catch { /* skip */ }
407
613
  console.log("");
408
614
  console.log(`View results: cat ${result.summaryFile}`);
409
615
  }
616
+ if (Boolean(flags.open) && result.summaryFile) {
617
+ try {
618
+ const { spawn: openSpawn } = await import("node:child_process");
619
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "notepad" : "xdg-open";
620
+ openSpawn(openCmd, [result.summaryFile], { detached: true, stdio: "ignore" }).unref();
621
+ }
622
+ catch { /* ignore */ }
623
+ }
410
624
  if (result.exitCode !== 0) {
411
625
  const diagnostics = await extractRunDiagnostics(result.logFile, result.provider, result.model);
412
626
  if (diagnostics.length > 0) {
@@ -477,31 +691,40 @@ async function runCommand(command, flags) {
477
691
  if (assets.sweeps.length === 1) {
478
692
  const autoFile = assets.sweeps[0].file;
479
693
  const iterFromFm = await readSweepIterationsFromFrontmatter(path.resolve(cwd, autoFile));
480
- console.log(`Auto-selected sweep: ${autoFile}${iterFromFm ? ` (${iterFromFm} iterations from frontmatter)` : ""}`);
694
+ console.log(`Auto-selected sweep: ${path.basename(autoFile)}${iterFromFm ? ` (${iterFromFm} iterations from frontmatter)` : ""}`);
481
695
  flags["prompt-file"] = autoFile;
482
696
  if (iterFromFm && !getStringFlag(flags, "iterations")) {
483
697
  flags.iterations = String(iterFromFm);
484
698
  }
485
699
  }
486
700
  else if (assets.sweeps.length > 1) {
487
- console.log(`${assets.sweeps.length} sweep files found. Pick one with --prompt-file:\n`);
488
- for (const s of assets.sweeps) {
489
- const iterCount = await readSweepIterationsFromFrontmatter(path.resolve(cwd, s.file));
490
- const iterFlag = iterCount ? ` --iterations ${iterCount}` : "";
491
- console.log(` prompts-gpt sweep --prompt-file ${s.file}${iterFlag}`);
701
+ if (isTTYInteractive(flags)) {
702
+ const sweepOptions = await Promise.all(assets.sweeps.map(async (s) => {
703
+ const fm = await readSweepFrontmatter(path.resolve(cwd, s.file));
704
+ const iterLabel = fm.iterations ? ` (${fm.iterations} iterations)` : "";
705
+ const titleLabel = fm.title ? ` ${fm.title}` : "";
706
+ return { label: `${path.basename(s.file, ".md")}${titleLabel}${iterLabel}`, value: s.file };
707
+ }));
708
+ const picked = await interactiveSelect("Select a sweep file:", sweepOptions);
709
+ flags["prompt-file"] = picked;
710
+ const iterFromFm = await readSweepIterationsFromFrontmatter(path.resolve(cwd, picked));
711
+ if (iterFromFm && !getStringFlag(flags, "iterations")) {
712
+ flags.iterations = String(iterFromFm);
713
+ }
492
714
  }
493
- if (assets.prompts.length > 0) {
494
- console.log("\nOr run a single prompt:");
495
- for (const p of assets.prompts.slice(0, 3)) {
496
- console.log(` prompts-gpt run --prompt-file .prompts-gpt/${p.file}`);
715
+ else {
716
+ console.log(`${assets.sweeps.length} sweep files found. Pick one with --prompt-file:\n`);
717
+ for (const s of assets.sweeps) {
718
+ const iterCount = await readSweepIterationsFromFrontmatter(path.resolve(cwd, s.file));
719
+ const iterFlag = iterCount ? ` --iterations ${iterCount}` : "";
720
+ console.log(` prompts-gpt sweep -f ${s.file}${iterFlag}`);
497
721
  }
722
+ return;
498
723
  }
499
- return;
500
724
  }
501
725
  else {
502
726
  console.log("No sweep files found.");
503
727
  console.log(" Create .prompts-gpt/sweeps/<name>.md to add sweep prompts.");
504
- console.log(" Or set a default: prompts-gpt setup --prompt-file <path>");
505
728
  console.log(" Run `prompts-gpt list` to see what's available.");
506
729
  return;
507
730
  }
@@ -512,33 +735,200 @@ async function runCommand(command, flags) {
512
735
  flags.iterations = String(iterFromFm);
513
736
  }
514
737
  }
738
+ const providers = await detectProviders(cwd);
739
+ const availableProviders = providers.filter((p) => p.available);
740
+ if (!getStringFlag(flags, "agent") && isTTYInteractive(flags) && !Boolean(flags.json) && availableProviders.length > 1) {
741
+ const lastProvider = await getLastUsedProvider(cwd);
742
+ const providerOptions = availableProviders.map((p) => ({
743
+ label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})${p.provider === lastProvider ? " ★" : ""}`,
744
+ value: p.provider,
745
+ }));
746
+ providerOptions.push({ label: "router (auto-select best available)", value: "router" });
747
+ if (lastProvider) {
748
+ const idx = providerOptions.findIndex((o) => o.value === lastProvider);
749
+ if (idx > 0) {
750
+ const [item] = providerOptions.splice(idx, 1);
751
+ providerOptions.unshift(item);
752
+ }
753
+ }
754
+ const picked = await interactiveSelect("Select a provider:", providerOptions);
755
+ flags.agent = picked;
756
+ if (picked !== "router") {
757
+ await saveLastUsedProvider(cwd, picked);
758
+ }
759
+ }
760
+ if (!getStringFlag(flags, "model") && isTTYInteractive(flags) && !Boolean(flags.json)) {
761
+ const currentAgent = resolveRunAgent(flags, config.defaultAgent);
762
+ if (currentAgent !== "router") {
763
+ const modelChoices = getModelChoicesForProvider(currentAgent, config);
764
+ if (modelChoices.length > 0) {
765
+ flags.model = await interactiveSelect("Select a model:", modelChoices);
766
+ }
767
+ }
768
+ }
769
+ if (!getStringFlag(flags, "iterations") && isTTYInteractive(flags) && !Boolean(flags.json)) {
770
+ const fmIter = await readSweepIterationsFromFrontmatter(path.resolve(cwd, getStringFlag(flags, "prompt-file")));
771
+ const defaultIter = fmIter ? String(fmIter) : "1";
772
+ const iterOptions = [
773
+ { label: `${defaultIter} (default)`, value: defaultIter },
774
+ ...(defaultIter !== "1" ? [{ label: "1", value: "1" }] : []),
775
+ ...(defaultIter !== "2" ? [{ label: "2", value: "2" }] : []),
776
+ ...(defaultIter !== "3" ? [{ label: "3", value: "3" }] : []),
777
+ ...(defaultIter !== "5" ? [{ label: "5", value: "5" }] : []),
778
+ ];
779
+ flags.iterations = await interactiveSelect("Select iterations:", iterOptions);
780
+ }
515
781
  const agent = resolveRunAgent(flags, config.defaultAgent);
782
+ const sweepModelFlag = getStringFlag(flags, "model");
783
+ if (sweepModelFlag && agent !== "router" && !Boolean(flags.json)) {
784
+ const resolvedP = resolveRunProvider(agent, providers, config.providerOrder);
785
+ const check = validateModelForProvider(sweepModelFlag, resolvedP);
786
+ if (!check.valid) {
787
+ console.log(`⚠ Model "${sweepModelFlag}" may not be available for ${resolvedP}.${check.suggestion ? ` Did you mean "${check.suggestion}"?` : ""}`);
788
+ }
789
+ }
790
+ if (isTTYInteractive(flags) && !Boolean(flags.json) && !Boolean(flags["dry-run"])) {
791
+ const resolvedProvider = resolveRunProvider(agent, providers, config.providerOrder);
792
+ const resolvedModel = getStringFlag(flags, "model")?.trim() || config.modelOverrides[resolvedProvider]?.trim() || availableProviders.find((p) => p.provider === resolvedProvider)?.modelDefault || "auto";
793
+ const resolvedIter = getStringFlag(flags, "iterations") || "1";
794
+ const sweepFile = getStringFlag(flags, "prompt-file") || "(default)";
795
+ const iterTimeout = parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout") ?? 5400;
796
+ const estMaxMs = parseInt(resolvedIter, 10) * iterTimeout * 1000;
797
+ const maxRetries = parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries") ?? 2;
798
+ console.log("");
799
+ console.log(colorize("╔══════════════════════════════════════════════════════════════╗", "\x1b[36m"));
800
+ console.log(colorize("║ Prompts-GPT Sweep ║", "\x1b[36m"));
801
+ console.log(colorize("╠══════════════════════════════════════════════════════════════╣", "\x1b[36m"));
802
+ console.log(`${colorize("║", "\x1b[36m")} Sweep: ${path.basename(sweepFile).padEnd(46)} ${colorize("║", "\x1b[36m")}`);
803
+ console.log(`${colorize("║", "\x1b[36m")} Provider: ${resolvedProvider.padEnd(46)} ${colorize("║", "\x1b[36m")}`);
804
+ console.log(`${colorize("║", "\x1b[36m")} Model: ${resolvedModel.padEnd(46)} ${colorize("║", "\x1b[36m")}`);
805
+ console.log(`${colorize("║", "\x1b[36m")} Iterations: ${resolvedIter.padEnd(46)} ${colorize("║", "\x1b[36m")}`);
806
+ console.log(`${colorize("║", "\x1b[36m")} Timeout: ${`${formatDuration(iterTimeout * 1000)} per iteration`.padEnd(46)} ${colorize("║", "\x1b[36m")}`);
807
+ console.log(`${colorize("║", "\x1b[36m")} Max time: ${`~${formatDuration(estMaxMs)}`.padEnd(46)} ${colorize("║", "\x1b[36m")}`);
808
+ console.log(`${colorize("║", "\x1b[36m")} Retries: ${String(maxRetries).padEnd(46)} ${colorize("║", "\x1b[36m")}`);
809
+ console.log(colorize("╚══════════════════════════════════════════════════════════════╝", "\x1b[36m"));
810
+ console.log("");
811
+ const confirmOpts = [
812
+ { label: "Yes, run this sweep", value: "y" },
813
+ { label: "Cancel", value: "n" },
814
+ ];
815
+ const confirm = await interactiveSelect("Run this sweep?", confirmOpts);
816
+ if (confirm === "n") {
817
+ console.log("Cancelled.");
818
+ return;
819
+ }
820
+ }
821
+ const quiet = Boolean(flags.quiet);
822
+ const summaryLineCount = parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines") ?? 40;
516
823
  const onProgress = Boolean(flags.json)
517
824
  ? undefined
518
825
  : (event) => {
519
826
  if (event.type === "preflight") {
520
827
  const report = JSON.parse(event.message);
521
- console.log(`[preflight] provider=${report.provider} model=${report.model} branch=${report.gitBranch} dirty=${report.gitDirtyFiles} disk=${report.diskFreeMb}MB iterations=${report.iterations}`);
522
- console.log(`[preflight] prompt=${report.promptFile}`);
828
+ console.log(`Provider: ${report.provider} | Model: ${report.model} | Iterations: ${report.iterations}`);
829
+ console.log(`Branch: ${report.gitBranch} | Dirty files: ${report.gitDirtyFiles} | Free disk: ${report.diskFreeMb}MB`);
830
+ console.log(`Prompt: ${path.basename(report.promptFile)}`);
831
+ try {
832
+ const fs = require("node:fs");
833
+ const skillDir = path.resolve(cwd, ".agents", "skills");
834
+ const ruleDir = path.resolve(cwd, ".cursor", "rules");
835
+ const skillCount = fs.existsSync(skillDir) ? fs.readdirSync(skillDir, { recursive: true }).filter((f) => String(f).endsWith("SKILL.md")).length : 0;
836
+ const ruleCount = fs.existsSync(ruleDir) ? fs.readdirSync(ruleDir).filter((f) => String(f).endsWith(".mdc")).length : 0;
837
+ const mcpConfig = fs.existsSync(path.resolve(cwd, ".cursor", "mcp.json")) ? "found" : "none";
838
+ console.log(`Skills: ${skillCount} | Rules: ${ruleCount} | MCP config: ${mcpConfig}`);
839
+ }
840
+ catch { /* skip */ }
841
+ const maxRunDirs = parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs") ?? 20;
842
+ console.log(`Log rotation: keep ${maxRunDirs} runs`);
523
843
  for (const w of report.warnings)
524
- console.log(`[preflight] warning: ${w}`);
844
+ console.log(`⚠ ${w}`);
525
845
  }
526
846
  else if (event.type === "iteration_start") {
527
- console.log(`\n--- Iteration ${event.iteration}/${event.total} ---`);
847
+ console.log(`\n${colorize("══════════════════════════════════════════════════════════════", "\x1b[35m")}`);
848
+ console.log(colorize(`[${new Date().toLocaleTimeString()}] Iteration ${event.iteration}/${event.total}: ${event.provider} (${event.model})`, "\x1b[1;35m"));
849
+ console.log(colorize("══════════════════════════════════════════════════════════════", "\x1b[35m"));
850
+ }
851
+ else if (event.type === "message") {
852
+ if (!quiet) {
853
+ console.log(` ${colorize(`[${event.elapsed}]`, "\x1b[36m")} 💬 ${event.text.slice(0, 120)}`);
854
+ }
855
+ }
856
+ else if (event.type === "tool") {
857
+ if (!quiet) {
858
+ const c = event.counts;
859
+ let icon = "🔧";
860
+ if (event.action.includes("read"))
861
+ icon = "📖";
862
+ else if (event.action.includes("write") || event.action.includes("edit"))
863
+ icon = "✏️ ";
864
+ else if (event.action.includes("shell") || event.action === "bash")
865
+ icon = "⚡";
866
+ else if (event.action.includes("search") || event.action.includes("grep") || event.action.includes("glob"))
867
+ icon = "🔍";
868
+ else if (event.action.includes("todo"))
869
+ icon = "📋";
870
+ const fileStr = event.file ? ` ${colorize(event.file, "\x1b[90m")}` : "";
871
+ const countStr = colorize(`(R:${c.reads} W:${c.writes} S:${c.shells} 🔍:${c.searches})`, "\x1b[90m");
872
+ console.log(` ${icon} ${event.action}${fileStr} ${countStr}`);
873
+ }
528
874
  }
529
875
  else if (event.type === "iteration_end") {
530
876
  const elapsed = formatDuration(event.durationMs);
531
- console.log(`--- Iteration ${event.iteration} ${event.status} (${elapsed}) ---`);
877
+ const icon = event.status === "success" ? colorize("✓", "\x1b[32m") : colorize("✗", "\x1b[31m");
878
+ const durations = globalThis.__sweepDurations || [];
879
+ durations.push(event.durationMs);
880
+ globalThis.__sweepDurations = durations;
881
+ const remaining = ((parseInt(String(getStringFlag(flags, "iterations"))) || 1) - event.iteration);
882
+ let etaStr = "";
883
+ if (remaining > 0 && durations.length > 0) {
884
+ const avgMs = durations.reduce((a, b) => a + b, 0) / durations.length;
885
+ etaStr = ` | ETA: ~${formatDuration(avgMs * remaining)}`;
886
+ }
887
+ console.log(`${icon} Iteration ${event.iteration} ${event.status} (${elapsed})${etaStr}`);
532
888
  }
533
889
  else if (event.type === "attempt_retry") {
534
- console.log(`[retry] iteration ${event.iteration}, attempt ${event.attempt}/${event.maxAttempts}, backoff ${event.backoffMs}ms`);
890
+ console.log(colorize(` Retry ${event.attempt}/${event.maxAttempts} for iteration ${event.iteration}, backoff ${event.backoffMs}ms`, "\x1b[33m"));
535
891
  }
536
892
  else if (event.type === "summary") {
537
- const preview = event.lines.slice(0, 5).join("\n");
538
- console.log(`[summary] iteration ${event.iteration}:\n${preview}${event.lines.length > 5 ? `\n ... (${event.lines.length - 5} more lines)` : ""}`);
893
+ if (summaryLineCount > 0) {
894
+ const preview = event.lines.slice(0, summaryLineCount);
895
+ console.log(`\n── Summary (iteration ${event.iteration}, ${preview.length}/${event.lines.length} lines) ──`);
896
+ for (const line of preview)
897
+ console.log(` ${line}`);
898
+ if (event.lines.length > summaryLineCount) {
899
+ console.log(` ... (${event.lines.length - summaryLineCount} more lines)`);
900
+ }
901
+ console.log("── End summary ──");
902
+ }
539
903
  }
540
904
  else if (event.type === "sweep_end") {
541
- console.log(`\nSweep complete: ${event.result.succeeded}/${event.result.totalIterations} succeeded in ${formatDuration(event.result.totalDurationMs)}`);
905
+ const r = event.result;
906
+ console.log("");
907
+ console.log(colorize("╔══════════════════════════════════════════════════════════════╗", "\x1b[36m"));
908
+ console.log(colorize("║ Sweep Complete ║", "\x1b[36m"));
909
+ console.log(colorize("╠══════════════════════════════════════════════════════════════╣", "\x1b[36m"));
910
+ console.log(`${colorize("║", "\x1b[36m")} Model: ${r.model.padEnd(45)} ${colorize("║", "\x1b[36m")}`);
911
+ console.log(`${colorize("║", "\x1b[36m")} Duration: ${formatDuration(r.totalDurationMs).padEnd(45)} ${colorize("║", "\x1b[36m")}`);
912
+ console.log(`${colorize("║", "\x1b[36m")} Iterations: ${String(r.totalIterations).padEnd(45)} ${colorize("║", "\x1b[36m")}`);
913
+ console.log(`${colorize("║", "\x1b[36m")} Succeeded: ${colorize(String(r.succeeded), "\x1b[32m").padEnd(54)} ${colorize("║", "\x1b[36m")}`);
914
+ if (r.failed > 0) {
915
+ console.log(`${colorize("║", "\x1b[36m")} Failed: ${colorize(String(r.failed), "\x1b[31m").padEnd(54)} ${colorize("║", "\x1b[36m")}`);
916
+ }
917
+ console.log(colorize("╠══════════════════════════════════════════════════════════════╣", "\x1b[36m"));
918
+ for (const iter of r.iterations) {
919
+ const statusColor = iter.status === "success" ? "\x1b[32m" : "\x1b[31m";
920
+ const iterLine = ` Iter ${iter.iteration}: ${colorize(iter.status.padEnd(16), statusColor)} ${formatDuration(iter.durationMs)}`;
921
+ console.log(`${colorize("║", "\x1b[36m")}${iterLine.padEnd(60)} ${colorize("║", "\x1b[36m")}`);
922
+ }
923
+ console.log(colorize("╚══════════════════════════════════════════════════════════════╝", "\x1b[36m"));
924
+ if (hasTokenUsage(r.tokenUsage)) {
925
+ const tu = r.tokenUsage;
926
+ console.log(`Tokens: ${formatTokenUsage(tu)}`);
927
+ const estCost = ((tu.inputTokens || 0) * 0.003 + (tu.outputTokens || 0) * 0.015) / 1000;
928
+ if (estCost > 0.001) {
929
+ console.log(`Estimated cost: ~$${estCost.toFixed(2)}`);
930
+ }
931
+ }
542
932
  }
543
933
  };
544
934
  const result = await sweepPrompt({
@@ -619,9 +1009,37 @@ async function runCommand(command, flags) {
619
1009
  console.log("Inspect results:");
620
1010
  console.log(` cat ${result.manifestFile}`);
621
1011
  console.log(` ls ${result.runDir}`);
1012
+ if (Boolean(flags.open) && result.manifestFile) {
1013
+ try {
1014
+ const { spawn: openSpawn } = await import("node:child_process");
1015
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "notepad" : "xdg-open";
1016
+ openSpawn(openCmd, [result.manifestFile], { detached: true, stdio: "ignore" }).unref();
1017
+ }
1018
+ catch { /* ignore */ }
1019
+ }
622
1020
  if (result.failed > 0) {
1021
+ const failedIters = result.iterations.filter((it) => it.status !== "success");
1022
+ for (const fi of failedIters) {
1023
+ try {
1024
+ const { readFile: fsRead } = await import("node:fs/promises");
1025
+ const logText = await fsRead(fi.logFile, "utf8");
1026
+ const tailLines = logText.split("\n").slice(-80);
1027
+ if (tailLines.length > 0) {
1028
+ console.log(`\n── Last 80 lines of iteration ${fi.iteration} log ──`);
1029
+ for (const line of tailLines)
1030
+ console.log(` ${line}`);
1031
+ console.log("── End log tail ──");
1032
+ }
1033
+ }
1034
+ catch { /* skip */ }
1035
+ }
623
1036
  process.exitCode = 1;
624
1037
  }
1038
+ console.log("\nNext steps:");
1039
+ console.log(" 1. Review iteration summaries in the run directory");
1040
+ if (result.failed > 0) {
1041
+ console.log(` 2. Re-run failed: prompts-gpt sweep -f ${getStringFlag(flags, "prompt-file")} -n ${result.failed}`);
1042
+ }
625
1043
  return;
626
1044
  }
627
1045
  if (command === "quickstart") {
@@ -632,7 +1050,32 @@ async function runCommand(command, flags) {
632
1050
  console.log("Prompts-GPT Quickstart");
633
1051
  console.log("======================");
634
1052
  console.log("");
1053
+ const { spawnSync: gitCheck } = await import("node:child_process");
1054
+ const gitResult = gitCheck("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8", timeout: 5000, windowsHide: true });
1055
+ if (gitResult.status !== 0) {
1056
+ console.log("⚠ Not inside a git repository. Prompts-GPT works best in a git repo for worktree tracking.");
1057
+ console.log(" Run: git init\n");
1058
+ }
635
1059
  if (!assets.credentialsFound) {
1060
+ let networkOk = true;
1061
+ try {
1062
+ const netCheck = await Promise.race([
1063
+ fetch("https://prompts-gpt.com/api/health", { method: "HEAD" }),
1064
+ new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 5000)),
1065
+ ]);
1066
+ networkOk = netCheck instanceof Response;
1067
+ }
1068
+ catch {
1069
+ networkOk = false;
1070
+ }
1071
+ if (!networkOk) {
1072
+ console.log("⚠ Network unavailable. Quickstart requires internet for token validation.\n");
1073
+ console.log("You can still use local sweep files without a network connection:");
1074
+ console.log(" 1. Create .prompts-gpt/sweeps/<name>.md with your sweep prompt");
1075
+ console.log(" 2. Run: prompts-gpt sweep");
1076
+ console.log("\nWhen online, run `prompts-gpt quickstart` again.");
1077
+ return;
1078
+ }
636
1079
  console.log("Step 1: Save your project token");
637
1080
  console.log(" prompts-gpt init --token-prompt");
638
1081
  console.log("");
@@ -694,8 +1137,21 @@ async function runCommand(command, flags) {
694
1137
  }
695
1138
  }
696
1139
  catch (syncErr) {
697
- console.log(`✗ Sync failed: ${syncErr instanceof Error ? syncErr.message : String(syncErr)}`);
698
- console.log(" Run manually: prompts-gpt sync");
1140
+ const syncMsg = syncErr instanceof Error ? syncErr.message : String(syncErr);
1141
+ if (syncErr instanceof PromptsGptApiError && (syncErr.status === 401 || syncErr.status === 403)) {
1142
+ console.log(`✗ Authentication failed: ${syncMsg}`);
1143
+ console.log(" Your token may be invalid or expired. Re-run:");
1144
+ console.log(" prompts-gpt init --token-prompt");
1145
+ }
1146
+ else if (syncMsg.includes("ENOTFOUND") || syncMsg.includes("ECONNREFUSED") || syncMsg.includes("fetch") || syncMsg.includes("network")) {
1147
+ console.log(`✗ Network error: ${syncMsg}`);
1148
+ console.log(" Check your internet connection and try again.");
1149
+ console.log(" You can still use local sweep files without network access.");
1150
+ }
1151
+ else {
1152
+ console.log(`✗ Sync failed: ${syncMsg}`);
1153
+ console.log(" Run manually: prompts-gpt sync");
1154
+ }
699
1155
  return;
700
1156
  }
701
1157
  const refreshedAssets = await discoverWorkspaceAssets(cwd);
@@ -705,13 +1161,37 @@ async function runCommand(command, flags) {
705
1161
  }
706
1162
  console.log(`✓ Prompts: ${assets.prompts.length} prompt packs, ${assets.sweeps.length} sweeps`);
707
1163
  console.log("");
708
- console.log("You're ready! Try these commands:");
1164
+ console.log("You're ready!");
1165
+ if (isTTYInteractive() && (assets.prompts.length > 0 || assets.sweeps.length > 0)) {
1166
+ const qsOptions = [];
1167
+ for (const s of assets.sweeps) {
1168
+ qsOptions.push({ label: `sweep: ${s.name}`, value: `sweep:${s.file}` });
1169
+ }
1170
+ for (const p of assets.prompts.slice(0, 5)) {
1171
+ qsOptions.push({ label: `run: ${p.title}`, value: `run:.prompts-gpt/${p.file}` });
1172
+ }
1173
+ qsOptions.push({ label: "(show commands and exit)", value: "exit" });
1174
+ console.log("");
1175
+ const qsPicked = await interactiveSelect("Run something now?", qsOptions);
1176
+ if (qsPicked !== "exit") {
1177
+ const [action, file] = qsPicked.split(":", 2);
1178
+ const cmd = action === "sweep" ? "sweep" : "run";
1179
+ console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
1180
+ const { spawnSync: spSync } = await import("node:child_process");
1181
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
1182
+ const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
1183
+ process.exitCode = result.status ?? 1;
1184
+ return;
1185
+ }
1186
+ }
1187
+ console.log("");
1188
+ console.log("Try these commands:");
709
1189
  console.log("");
710
1190
  if (assets.prompts.length > 0) {
711
- console.log(` prompts-gpt run --prompt-file .prompts-gpt/${assets.prompts[0].file}`);
1191
+ console.log(` prompts-gpt run -f .prompts-gpt/${assets.prompts[0].file}`);
712
1192
  }
713
1193
  if (assets.sweeps.length > 0) {
714
- console.log(` prompts-gpt sweep --prompt-file ${assets.sweeps[0].file}`);
1194
+ console.log(` prompts-gpt sweep -f ${assets.sweeps[0].file}`);
715
1195
  }
716
1196
  console.log(" prompts-gpt list — see all available prompts and sweeps");
717
1197
  console.log(" prompts-gpt status — check workspace readiness");
@@ -736,13 +1216,21 @@ async function runCommand(command, flags) {
736
1216
  const providers = await detectProviders(cwd);
737
1217
  const availableProviders = providers.filter((p) => p.available);
738
1218
  let configError = null;
739
- const configResult = await initRunConfig({
1219
+ let doOverwrite = true;
1220
+ const existingConfig = existsSync(path.resolve(cwd, DEFAULT_RUN_CONFIG_PATH));
1221
+ if (existingConfig && isTTYInteractive() && !Boolean(flags.json)) {
1222
+ const confirmOverwrite = await interactiveInput("Existing run config found. Overwrite? [Y/n]", "Y");
1223
+ if (confirmOverwrite.toLowerCase() === "n" || confirmOverwrite.toLowerCase() === "no") {
1224
+ doOverwrite = false;
1225
+ }
1226
+ }
1227
+ const configResult = doOverwrite ? await initRunConfig({
740
1228
  cwd,
741
1229
  overwrite: true,
742
1230
  }).catch((err) => {
743
1231
  configError = err.message;
744
1232
  return null;
745
- });
1233
+ }) : null;
746
1234
  if (Boolean(flags.json)) {
747
1235
  console.log(JSON.stringify({
748
1236
  project: { brandName: project.brandName, websiteUrl: project.websiteUrl },
@@ -782,7 +1270,8 @@ async function runCommand(command, flags) {
782
1270
  let token = await resolveTokenInput(flags, { command });
783
1271
  if (!token && process.stdin.isTTY && process.stdout.isTTY) {
784
1272
  console.log("No token flag provided — prompting interactively.");
785
- console.log("Get your token from: https://prompts-gpt.com/studio/projects\n");
1273
+ console.log("Get your token from: https://prompts-gpt.com/studio/projects");
1274
+ console.log("Token format: pgpt_xxxxxxxxxxxxxxxx (starts with 'pgpt_')\n");
786
1275
  token = await readTokenFromPrompt(command);
787
1276
  }
788
1277
  if (!token) {
@@ -822,6 +1311,17 @@ async function runCommand(command, flags) {
822
1311
  });
823
1312
  console.log(`Saved Prompts-GPT credentials to ${result.credentialsPath}`);
824
1313
  console.log("The credentials file is added to .gitignore.");
1314
+ if (isTTYInteractive()) {
1315
+ console.log("");
1316
+ const runQs = await interactiveInput("Run quickstart now? [Y/n]", "Y");
1317
+ if (runQs.toLowerCase() !== "n" && runQs.toLowerCase() !== "no") {
1318
+ const { spawnSync: spSync } = await import("node:child_process");
1319
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
1320
+ const qsResult = spSync(process.execPath, [cliEntry, "quickstart", "--cwd", cwd], { stdio: "inherit", cwd });
1321
+ process.exitCode = qsResult.status ?? 1;
1322
+ return;
1323
+ }
1324
+ }
825
1325
  console.log("");
826
1326
  console.log("Next steps:");
827
1327
  console.log(" prompts-gpt quickstart — interactive setup (recommended)");
@@ -836,19 +1336,29 @@ async function runCommand(command, flags) {
836
1336
  console.log("No prompts matched the query. Try different filters or check the project prompt library.");
837
1337
  return;
838
1338
  }
1339
+ const pullCwd = getResolvedCwd(flags);
1340
+ const pullOutDir = getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR;
839
1341
  const result = await writePromptMarkdownFiles(prompts, {
840
- cwd: getResolvedCwd(flags),
841
- outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
1342
+ cwd: pullCwd,
1343
+ outDir: pullOutDir,
842
1344
  overwrite: Boolean(flags.overwrite),
843
1345
  });
1346
+ const pullManifest = await writePromptManifest(prompts, { cwd: pullCwd, outDir: pullOutDir });
844
1347
  console.log(`Wrote ${result.written.length} prompt file(s) to ${result.outDir}.`);
1348
+ console.log(`Updated manifest: ${pullManifest.manifestPath}`);
1349
+ if (Boolean(flags.overwrite)) {
1350
+ const agentResult = await writeAgentFiles(prompts, { cwd: pullCwd, agent: "all", overwriteAgentFiles: true });
1351
+ if (agentResult.written.length > 0) {
1352
+ console.log(`Updated ${agentResult.written.length} agent file(s): ${agentResult.targets.join(", ")}`);
1353
+ }
1354
+ }
845
1355
  if (result.skipped.length) {
846
1356
  console.log(`Skipped ${result.skipped.length} existing file(s). Use --overwrite to replace them.`);
847
1357
  }
848
1358
  if (result.written.length > 0) {
849
1359
  console.log("");
850
1360
  console.log("Run a pulled prompt:");
851
- console.log(` prompts-gpt run --prompt-file ${result.written[0]}`);
1361
+ console.log(` prompts-gpt run -f ${result.written[0]}`);
852
1362
  console.log(" prompts-gpt list — see all available prompts");
853
1363
  }
854
1364
  return;
@@ -880,7 +1390,9 @@ async function runCommand(command, flags) {
880
1390
  const target = getStringFlag(flags, "agent") || "all";
881
1391
  const pulled = await client.pullPrompts().catch((err) => {
882
1392
  if (!Boolean(flags.json)) {
883
- console.error(`[warning] Could not pull existing prompts for agent sync: ${err.message}`);
1393
+ console.error(`⚠ Could not pull existing prompts for agent sync: ${err.message}`);
1394
+ console.error(" Agent files will only include the newly generated prompt.");
1395
+ console.error(" Run \`prompts-gpt sync\` afterward to include all library prompts.");
884
1396
  }
885
1397
  return [];
886
1398
  });
@@ -902,6 +1414,21 @@ async function runCommand(command, flags) {
902
1414
  if (result.skipped.length) {
903
1415
  console.log("Skipped existing generated prompt. Use --overwrite to replace it.");
904
1416
  }
1417
+ const genFile = result.written[0];
1418
+ if (genFile && isTTYInteractive()) {
1419
+ const genProviders = await detectProviders(cwd);
1420
+ if (genProviders.some((p) => p.available)) {
1421
+ console.log("");
1422
+ const runNow = await interactiveInput("Run this prompt now? [Y/n]", "Y");
1423
+ if (runNow.toLowerCase() !== "n" && runNow.toLowerCase() !== "no") {
1424
+ console.log(`\nRunning: prompts-gpt run -f ${genFile}\n`);
1425
+ const { spawnSync: spSync } = await import("node:child_process");
1426
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
1427
+ const genResult = spSync(process.execPath, [cliEntry, "run", "-f", genFile], { stdio: "inherit", cwd });
1428
+ process.exitCode = genResult.status ?? 1;
1429
+ }
1430
+ }
1431
+ }
905
1432
  return;
906
1433
  }
907
1434
  if (command === "sync") {
@@ -911,7 +1438,18 @@ async function runCommand(command, flags) {
911
1438
  const prompts = [];
912
1439
  const goal = getStringFlag(flags, "goal");
913
1440
  const generatedOnly = Boolean(flags["generated-only"]);
914
- const client = await createClientForCommand(command, flags);
1441
+ let client;
1442
+ try {
1443
+ client = await createClientForCommand(command, flags);
1444
+ }
1445
+ catch (clientErr) {
1446
+ if (clientErr instanceof CliError && clientErr.exitCode === CLI_EXIT_CODES.auth) {
1447
+ throw new CliError(`Authentication failed. Your token may be invalid, expired, or lack the required scope.\n\n` +
1448
+ `Re-run: prompts-gpt init --token-prompt\n` +
1449
+ `Get a new token: https://prompts-gpt.com/studio/projects`, CLI_EXIT_CODES.auth, { helpCommand: "sync" });
1450
+ }
1451
+ throw clientErr;
1452
+ }
915
1453
  if (goal && !Boolean(flags.json)) {
916
1454
  printDataTransmissionNotice("sync", { goal, context: getStringFlag(flags, "context"), constraints: getStringFlag(flags, "constraints") });
917
1455
  }
@@ -927,9 +1465,13 @@ async function runCommand(command, flags) {
927
1465
  });
928
1466
  }
929
1467
  if (Boolean(flags["dry-run"])) {
930
- console.log(`[dry-run] Would sync ${prompts.length} prompt(s) for agent target: ${getStringFlag(flags, "agent") || "all"}.`);
1468
+ const agentTarget = getStringFlag(flags, "agent") || "all";
1469
+ console.log(`[dry-run] Would sync ${prompts.length} prompt(s) for agent target: ${agentTarget}`);
931
1470
  for (const p of prompts)
932
1471
  console.log(` - ${p.title} (${p.source})`);
1472
+ const targetList = agentTarget === "all" ? SUPPORTED_AGENT_TARGETS.join(", ") : agentTarget;
1473
+ console.log(`\n Agent file targets: ${targetList}`);
1474
+ console.log(` Output dir: ${getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR}`);
933
1475
  return;
934
1476
  }
935
1477
  const result = await syncPrompts(prompts, {
@@ -983,6 +1525,18 @@ async function runCommand(command, flags) {
983
1525
  console.log(`Personas: ${formatList(project.targetPersonas)}`);
984
1526
  console.log(`Competitors: ${formatCompetitors(project.competitors)}`);
985
1527
  console.log(`API URL: ${apiUrl}`);
1528
+ if (isTTYInteractive()) {
1529
+ console.log("");
1530
+ const syncNow = await interactiveInput("Sync prompts from this project? [Y/n]", "Y");
1531
+ if (syncNow.toLowerCase() !== "n" && syncNow.toLowerCase() !== "no") {
1532
+ console.log("\nSyncing...");
1533
+ const { spawnSync: spSync } = await import("node:child_process");
1534
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
1535
+ const projCwd = getResolvedCwd(flags);
1536
+ const syncResult = spSync(process.execPath, [cliEntry, "sync", "--cwd", projCwd], { stdio: "inherit", cwd: projCwd });
1537
+ process.exitCode = syncResult.status ?? 1;
1538
+ }
1539
+ }
986
1540
  return;
987
1541
  }
988
1542
  }
@@ -1004,6 +1558,9 @@ async function resolveClientOptions(command, flags) {
1004
1558
  // CI fallback — never read process.env in the importable SDK, only in the CLI entrypoint
1005
1559
  const envToken = process.env.PROMPTS_GPT_TOKEN?.trim();
1006
1560
  if (envToken) {
1561
+ if (!envToken.startsWith("pgpt_")) {
1562
+ throw new CliError("PROMPTS_GPT_TOKEN env var must start with 'pgpt_'. Check the value.", CLI_EXIT_CODES.auth);
1563
+ }
1007
1564
  return {
1008
1565
  token: envToken,
1009
1566
  apiUrl: explicitApiUrl || credentials?.apiUrl || DEFAULT_PROMPTS_GPT_API_URL,
@@ -1194,6 +1751,9 @@ function getCommandOptions(command) {
1194
1751
  background: { type: "boolean" },
1195
1752
  "permission-mode": { type: "string" },
1196
1753
  "dry-run": { type: "boolean" },
1754
+ "non-interactive": { type: "boolean" },
1755
+ verbose: { type: "boolean", short: "v" },
1756
+ open: { type: "boolean" },
1197
1757
  };
1198
1758
  }
1199
1759
  if (command === "sweep") {
@@ -1217,6 +1777,10 @@ function getCommandOptions(command) {
1217
1777
  "summary-lines": { type: "string" },
1218
1778
  background: { type: "boolean" },
1219
1779
  "permission-mode": { type: "string" },
1780
+ "non-interactive": { type: "boolean" },
1781
+ verbose: { type: "boolean", short: "v" },
1782
+ open: { type: "boolean" },
1783
+ quiet: { type: "boolean", short: "q" },
1220
1784
  };
1221
1785
  }
1222
1786
  if (command === "run-batch") {
@@ -1230,6 +1794,7 @@ function getCommandOptions(command) {
1230
1794
  model: { type: "string" },
1231
1795
  timeout: { type: "string" },
1232
1796
  "artifacts-dir": { type: "string" },
1797
+ "non-interactive": { type: "boolean" },
1233
1798
  };
1234
1799
  }
1235
1800
  if (command === "load-config") {
@@ -1369,7 +1934,12 @@ async function readTokenFromStdin() {
1369
1934
  }
1370
1935
  chunks.push(buffer);
1371
1936
  }
1372
- return Buffer.concat(chunks).toString("utf8").trim();
1937
+ const combined = Buffer.concat(chunks);
1938
+ const result = combined.toString("utf8").trim();
1939
+ combined.fill(0);
1940
+ for (const chunk of chunks)
1941
+ chunk.fill(0);
1942
+ return result;
1373
1943
  }
1374
1944
  function readTokenFromPrompt(command) {
1375
1945
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -1445,6 +2015,7 @@ function readTokenFromPrompt(command) {
1445
2015
  }
1446
2016
  buf.write(char, bufLen, "utf8");
1447
2017
  bufLen += charBytes;
2018
+ stdout.write("*");
1448
2019
  }
1449
2020
  }
1450
2021
  };
@@ -1640,17 +2211,86 @@ function parseNonNegativeIntFlag(raw, flagName) {
1640
2211
  }
1641
2212
  return value;
1642
2213
  }
2214
+ async function getLastUsedProvider(cwd) {
2215
+ try {
2216
+ const { readFile: fsRead } = await import("node:fs/promises");
2217
+ const data = JSON.parse(await fsRead(path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".last-provider"), "utf8"));
2218
+ return data.provider ?? null;
2219
+ }
2220
+ catch {
2221
+ return null;
2222
+ }
2223
+ }
2224
+ async function saveLastUsedProvider(cwd, provider) {
2225
+ try {
2226
+ const { writeFile: fsWrite, mkdir: fsMkdir } = await import("node:fs/promises");
2227
+ const dir = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR);
2228
+ await fsMkdir(dir, { recursive: true });
2229
+ await fsWrite(path.resolve(dir, ".last-provider"), JSON.stringify({ provider, updatedAt: new Date().toISOString() }));
2230
+ }
2231
+ catch { /* ignore */ }
2232
+ }
2233
+ function validateModelForProvider(model, provider) {
2234
+ const allKnown = {
2235
+ codex: ["gpt-5.4-mini", "o4-mini", "o3", "gpt-5.1-codex", "gpt-4.1"],
2236
+ claude: ["claude-sonnet-4-6", "claude-4.5-opus", "claude-4.6-sonnet", "claude-haiku-3.5", "claude-4.6-opus-high", "claude-4.6-sonnet-high"],
2237
+ cursor: ["auto", "claude-4.6-sonnet", "gpt-5.4-mini", "claude-4.5-opus", "claude-4.6-opus-high", "claude-4.6-sonnet-high", "composer-2", "composer-2-fast", "gpt-5.3-codex"],
2238
+ copilot: ["auto", "gpt-5.4-mini", "claude-4.6-sonnet"],
2239
+ };
2240
+ const known = allKnown[provider] ?? [];
2241
+ if (known.length === 0)
2242
+ return { valid: true, suggestion: "" };
2243
+ if (known.includes(model))
2244
+ return { valid: true, suggestion: "" };
2245
+ const close = known.find((k) => k.startsWith(model.slice(0, 5)));
2246
+ return { valid: false, suggestion: close ?? known[0] ?? "" };
2247
+ }
2248
+ function getModelChoicesForProvider(provider, config) {
2249
+ const KNOWN_MODELS = {
2250
+ codex: [
2251
+ { label: "gpt-5.4-mini (default)", value: "gpt-5.4-mini" },
2252
+ { label: "o4-mini", value: "o4-mini" },
2253
+ { label: "o3", value: "o3" },
2254
+ { label: "gpt-5.1-codex", value: "gpt-5.1-codex" },
2255
+ { label: "gpt-4.1", value: "gpt-4.1" },
2256
+ ],
2257
+ claude: [
2258
+ { label: "claude-sonnet-4-6 (default)", value: "claude-sonnet-4-6" },
2259
+ { label: "claude-4.5-opus", value: "claude-4.5-opus" },
2260
+ { label: "claude-4.6-sonnet", value: "claude-4.6-sonnet" },
2261
+ { label: "claude-haiku-3.5", value: "claude-haiku-3.5" },
2262
+ ],
2263
+ cursor: [
2264
+ { label: "auto (default)", value: "auto" },
2265
+ { label: "claude-4.6-sonnet", value: "claude-4.6-sonnet" },
2266
+ { label: "gpt-5.4-mini", value: "gpt-5.4-mini" },
2267
+ { label: "claude-4.5-opus", value: "claude-4.5-opus" },
2268
+ ],
2269
+ copilot: [
2270
+ { label: "auto (default)", value: "auto" },
2271
+ { label: "gpt-5.4-mini", value: "gpt-5.4-mini" },
2272
+ { label: "claude-4.6-sonnet", value: "claude-4.6-sonnet" },
2273
+ ],
2274
+ };
2275
+ const choices = KNOWN_MODELS[provider] ?? [];
2276
+ const override = config.modelOverrides[provider]?.trim();
2277
+ if (override && !choices.some((c) => c.value === override)) {
2278
+ choices.unshift({ label: `${override} (configured)`, value: override });
2279
+ }
2280
+ return choices;
2281
+ }
1643
2282
  function resolveRunAgent(flags, fallback) {
1644
2283
  const raw = getStringFlag(flags, "agent");
1645
2284
  if (!raw)
1646
2285
  return fallback;
1647
- const normalizedRaw = raw.trim().toLowerCase();
1648
- const normalized = normalizeOrchestrationAgent(raw);
2286
+ const sanitized = raw.replace(/[;&|`$(){}!<>]/g, "").trim();
2287
+ const normalizedRaw = sanitized.toLowerCase();
2288
+ const normalized = normalizeOrchestrationAgent(sanitized);
1649
2289
  if (normalized === "router" && normalizedRaw !== "router") {
1650
2290
  const profiles = ORCHESTRATION_AGENT_PROFILES.join(", ");
1651
2291
  const hint = ORCHESTRATION_AGENT_PROFILES.find((p) => p.startsWith(normalizedRaw.slice(0, 3)));
1652
2292
  const suggestion = hint ? ` Did you mean "${hint}"?` : "";
1653
- throw new CliError(`Invalid --agent target: ${raw}. Use ${profiles}.${suggestion}`, CLI_EXIT_CODES.validation);
2293
+ throw new CliError(`Invalid --agent target: ${sanitized}. Use ${profiles}.${suggestion}`, CLI_EXIT_CODES.validation);
1654
2294
  }
1655
2295
  return normalized;
1656
2296
  }
@@ -1870,6 +2510,7 @@ Options:
1870
2510
  --background Run as a background agent (Cursor cloud agent mode).
1871
2511
  --permission-mode <mode> Claude Code permission mode. Default: acceptEdits.
1872
2512
  --dry-run Preview the execution parameters without actually running.
2513
+ --non-interactive Skip all interactive prompts (same as CI mode).
1873
2514
  --json Print machine-readable JSON output.
1874
2515
  --cwd <path> Project directory.
1875
2516
  --help Show this command help.
@@ -1931,15 +2572,21 @@ Options:
1931
2572
  --max-run-dirs <n> Max artifact directories to keep. Default: 20
1932
2573
  --summary-lines <n> Lines of summary to extract per iteration. Default: 40
1933
2574
  --dry-run Preview what the sweep would do without executing.
2575
+ --non-interactive Skip all interactive prompts (same as CI mode).
1934
2576
  --json Print machine-readable JSON output.
1935
2577
  --cwd <path> Project directory.
1936
2578
  --help Show this command help.
1937
2579
 
2580
+ Environment:
2581
+ PROMPTS_GPT_NON_INTERACTIVE=1 Skip interactive prompts.
2582
+ NO_COLOR=1 Disable colored output.
2583
+
1938
2584
  Examples:
1939
- prompts-gpt sweep # auto-pick local sweep
2585
+ prompts-gpt sweep # interactive picker
1940
2586
  prompts-gpt sweep -f .prompts-gpt/sweeps/sdk-hardening.md # explicit file
1941
2587
  prompts-gpt sweep -f .prompts-gpt/sweeps/design.md -n 5 # 5 iterations
1942
2588
  prompts-gpt sweep --dry-run # preview without running
2589
+ prompts-gpt sweep --non-interactive # skip interactive prompts
1943
2590
  `;
1944
2591
  }
1945
2592
  if (command === "list") {
@@ -2072,19 +2719,222 @@ Why use it:
2072
2719
  Shows focused usage for the exact command you are trying to run so shell users do not have to scan the full command list.
2073
2720
  `;
2074
2721
  }
2075
- async function readSweepIterationsFromFrontmatter(filePath) {
2722
+ // ── Interactive TTY selection helpers ─────────────────────────────────────────
2723
+ function supportsColor() {
2724
+ if (process.env.NO_COLOR || process.env.TERM === "dumb")
2725
+ return false;
2726
+ if (process.env.FORCE_COLOR)
2727
+ return true;
2728
+ if (process.platform === "win32") {
2729
+ const osRelease = (process.env.OS_VERSION || "").toLowerCase();
2730
+ if (osRelease.includes("windows_nt") || process.env.WT_SESSION || process.env.TERM_PROGRAM)
2731
+ return true;
2732
+ return Boolean(process.stdout.isTTY && (process.stdout.hasColors?.() ?? false));
2733
+ }
2734
+ return Boolean(process.stdout.isTTY);
2735
+ }
2736
+ function colorize(text, code) {
2737
+ return supportsColor() ? `${code}${text}\x1b[0m` : text;
2738
+ }
2739
+ function isTTYInteractive(flags) {
2740
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
2741
+ return false;
2742
+ if (flags && Boolean(flags["non-interactive"]))
2743
+ return false;
2744
+ if (process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS ||
2745
+ process.env.GITLAB_CI || process.env.CIRCLECI || process.env.JENKINS_URL ||
2746
+ process.env.BUILDKITE || process.env.TRAVIS || process.env.TF_BUILD ||
2747
+ process.env.CODEBUILD_BUILD_ID)
2748
+ return false;
2749
+ if (process.env.PROMPTS_GPT_NON_INTERACTIVE === "1" || process.env.PROMPTS_GPT_NON_INTERACTIVE === "true")
2750
+ return false;
2751
+ return true;
2752
+ }
2753
+ function interactiveSelect(prompt, options) {
2754
+ if (options.length === 0) {
2755
+ return Promise.reject(new CliError("No options available for selection.", CLI_EXIT_CODES.usage));
2756
+ }
2757
+ if (options.length === 1) {
2758
+ return Promise.resolve(options[0].value);
2759
+ }
2760
+ return new Promise((resolve, reject) => {
2761
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
2762
+ reject(new CliError("Interactive selection requires a TTY.", CLI_EXIT_CODES.usage));
2763
+ return;
2764
+ }
2765
+ const stdin = process.stdin;
2766
+ const stdout = process.stdout;
2767
+ let cursor = 0;
2768
+ let escBuf = "";
2769
+ const maxVisible = Math.min(options.length, (process.stdout.rows ?? 24) - 3);
2770
+ const useUnicode = supportsColor();
2771
+ const pointer = useUnicode ? "❯" : ">";
2772
+ const formatLine = (i, selected) => {
2773
+ const num = i < 9 ? `${i + 1}` : " ";
2774
+ const prefix = selected ? colorize(pointer, "\x1b[36m") : " ";
2775
+ const label = selected ? colorize(options[i].label, "\x1b[1m") : options[i].label;
2776
+ return ` ${prefix} ${num}. ${label}`;
2777
+ };
2778
+ const writeInitial = () => {
2779
+ stdout.write(`${prompt}\n`);
2780
+ for (let i = 0; i < maxVisible; i++) {
2781
+ stdout.write(`${formatLine(i, i === cursor)}\n`);
2782
+ }
2783
+ if (options.length > maxVisible) {
2784
+ stdout.write(` ... ${options.length - maxVisible} more (scroll with arrows)\n`);
2785
+ }
2786
+ };
2787
+ const render = () => {
2788
+ const lines = maxVisible + 1 + (options.length > maxVisible ? 1 : 0);
2789
+ if (supportsColor()) {
2790
+ stdout.write(`\x1b[${lines}A\x1b[J`);
2791
+ }
2792
+ else {
2793
+ stdout.write("\n".repeat(2));
2794
+ }
2795
+ const posLabel = options.length > 1 ? ` (${cursor + 1}/${options.length})` : "";
2796
+ stdout.write(`${prompt}${posLabel}\n`);
2797
+ const scrollStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), options.length - maxVisible));
2798
+ for (let vi = 0; vi < maxVisible; vi++) {
2799
+ const i = scrollStart + vi;
2800
+ stdout.write(`${formatLine(i, i === cursor)}\n`);
2801
+ }
2802
+ if (options.length > maxVisible) {
2803
+ stdout.write(` ... showing ${scrollStart + 1}-${scrollStart + maxVisible} of ${options.length}\n`);
2804
+ }
2805
+ };
2806
+ writeInitial();
2807
+ stdin.setEncoding("utf8");
2808
+ try {
2809
+ if (typeof stdin.setRawMode === "function")
2810
+ stdin.setRawMode(true);
2811
+ }
2812
+ catch {
2813
+ reject(new CliError("Cannot enable raw mode for interactive selection. Use --non-interactive or pass flags directly.", CLI_EXIT_CODES.usage));
2814
+ return;
2815
+ }
2816
+ stdin.resume();
2817
+ const cleanup = () => {
2818
+ try {
2819
+ stdin.removeListener("data", onData);
2820
+ }
2821
+ catch { /* already destroyed */ }
2822
+ try {
2823
+ if (typeof stdin.setRawMode === "function")
2824
+ stdin.setRawMode(false);
2825
+ }
2826
+ catch { /* ignore */ }
2827
+ try {
2828
+ if (!stdin.destroyed)
2829
+ stdin.pause();
2830
+ }
2831
+ catch { /* ignore */ }
2832
+ };
2833
+ const onData = (data) => {
2834
+ for (let ci = 0; ci < data.length; ci++) {
2835
+ const ch = data[ci];
2836
+ if (escBuf.length > 0) {
2837
+ escBuf += ch;
2838
+ if (escBuf.length === 2 && ch === "[")
2839
+ continue;
2840
+ if (escBuf.length >= 3) {
2841
+ if (escBuf === "\x1b[A" || escBuf === "\x1b[k") {
2842
+ cursor = cursor > 0 ? cursor - 1 : options.length - 1;
2843
+ render();
2844
+ }
2845
+ else if (escBuf === "\x1b[B" || escBuf === "\x1b[j") {
2846
+ cursor = cursor < options.length - 1 ? cursor + 1 : 0;
2847
+ render();
2848
+ }
2849
+ else if (escBuf === "\x1b[H") {
2850
+ cursor = 0;
2851
+ render();
2852
+ }
2853
+ else if (escBuf === "\x1b[F") {
2854
+ cursor = options.length - 1;
2855
+ render();
2856
+ }
2857
+ escBuf = "";
2858
+ }
2859
+ continue;
2860
+ }
2861
+ if (ch === "\x1b") {
2862
+ escBuf = "\x1b";
2863
+ continue;
2864
+ }
2865
+ if (ch === "\x03") {
2866
+ cleanup();
2867
+ stdout.write("\n");
2868
+ process.exit(130);
2869
+ return;
2870
+ }
2871
+ if (ch === "\r" || ch === "\n") {
2872
+ cleanup();
2873
+ stdout.write("\n");
2874
+ resolve(options[cursor].value);
2875
+ return;
2876
+ }
2877
+ if (ch === "k") {
2878
+ cursor = cursor > 0 ? cursor - 1 : options.length - 1;
2879
+ render();
2880
+ }
2881
+ else if (ch === "j") {
2882
+ cursor = cursor < options.length - 1 ? cursor + 1 : 0;
2883
+ render();
2884
+ }
2885
+ else if (ch >= "1" && ch <= "9") {
2886
+ const idx = parseInt(ch, 10) - 1;
2887
+ if (idx < options.length) {
2888
+ cleanup();
2889
+ stdout.write("\n");
2890
+ resolve(options[idx].value);
2891
+ return;
2892
+ }
2893
+ }
2894
+ }
2895
+ };
2896
+ stdin.on("data", onData);
2897
+ });
2898
+ }
2899
+ async function interactiveInput(prompt, defaultValue) {
2900
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
2901
+ throw new CliError("Interactive input requires a TTY.", CLI_EXIT_CODES.usage);
2902
+ }
2903
+ const { createInterface } = await import("node:readline");
2904
+ return new Promise((resolve) => {
2905
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2906
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
2907
+ rl.question(`${prompt}${suffix}: `, (answer) => {
2908
+ rl.close();
2909
+ resolve(answer.trim() || defaultValue || "");
2910
+ });
2911
+ });
2912
+ }
2913
+ async function readSweepFrontmatter(filePath) {
2076
2914
  try {
2077
2915
  const { readFile: fsRead } = await import("node:fs/promises");
2078
2916
  const content = await fsRead(filePath, "utf8");
2079
- const match = content.match(/iterations:\s*(\d+)/i);
2080
- if (match) {
2081
- const val = parseInt(match[1], 10);
2917
+ let iterations = null;
2918
+ let title = null;
2919
+ const iterMatch = content.match(/iterations:\s*(\d+)/i);
2920
+ if (iterMatch) {
2921
+ const val = parseInt(iterMatch[1], 10);
2082
2922
  if (val > 0 && val <= 50)
2083
- return val;
2923
+ iterations = val;
2084
2924
  }
2925
+ const fmEnd = content.indexOf("---", content.indexOf("---") + 3);
2926
+ const bodyStart = fmEnd > 0 ? fmEnd + 3 : 0;
2927
+ const titleMatch = content.slice(bodyStart).match(/^#\s+(.+)/m);
2928
+ if (titleMatch) {
2929
+ title = titleMatch[1].trim();
2930
+ }
2931
+ return { iterations, title };
2085
2932
  }
2086
2933
  catch { /* skip */ }
2087
- return null;
2934
+ return { iterations: null, title: null };
2935
+ }
2936
+ async function readSweepIterationsFromFrontmatter(filePath) {
2937
+ return (await readSweepFrontmatter(filePath)).iterations;
2088
2938
  }
2089
2939
  function buildGenerateInput(flags) {
2090
2940
  return {
@@ -2099,7 +2949,15 @@ function buildGenerateInput(flags) {
2099
2949
  };
2100
2950
  }
2101
2951
  function sanitizeGenerateInput(value) {
2102
- return value.replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***").replace(/sk-[a-zA-Z0-9]{20,}/g, "sk-***").replace(/ghp_[a-zA-Z0-9]{36,}/g, "ghp_***");
2952
+ return value
2953
+ .replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***")
2954
+ .replace(/sk-[a-zA-Z0-9]{20,}/g, "sk-***")
2955
+ .replace(/ghp_[a-zA-Z0-9]{36,}/g, "ghp_***")
2956
+ .replace(/ghu_[a-zA-Z0-9]{36,}/g, "ghu_***")
2957
+ .replace(/Bearer\s+[a-zA-Z0-9_.-]{20,}/gi, "Bearer ***")
2958
+ .replace(/xai-[a-zA-Z0-9]{20,}/g, "xai-***")
2959
+ .replace(/ANTHROPIC_API_KEY=[^\s]+/gi, "ANTHROPIC_API_KEY=***")
2960
+ .replace(/OPENAI_API_KEY=[^\s]+/gi, "OPENAI_API_KEY=***");
2103
2961
  }
2104
2962
  function printDataTransmissionNotice(command, input) {
2105
2963
  console.log(`[notice] The --goal text${input.context ? ", --context" : ""}${input.constraints ? ", --constraints" : ""} you provided will be sent to prompts-gpt.com to generate a prompt.`);