prompts-gpt 0.2.14 → 0.2.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.
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, readFileSync, statSync, readdirSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { parseArgs } from "node:util";
5
- import { hasTokenUsage, DEFAULT_PROMPTS_GPT_API_URL, DEFAULT_PROMPTS_GPT_OUT_DIR, DEFAULT_RUN_CONFIG_PATH, PROMPTS_GPT_CREDENTIALS_FILE, PromptsGptApiError, PromptsGptClient, doctor, initRunConfig, loadRunConfig, normalizeOrchestrationAgent, ORCHESTRATION_AGENT_PROFILES, runBatch, runPrompt, resolveRunProvider, warnModelProviderMismatch, sweepPrompt, validateRunConfig, discoverWorkspaceAssets, SUPPORTED_AGENT_TARGETS, detectProviders, loadLocalCredentials, saveLocalCredentials, syncPrompts, writeAgentFiles, writePromptManifest, writePromptMarkdownFiles, ensureGitignoreEntry, } from "./index.js";
6
+ import { hasTokenUsage, DEFAULT_PROMPTS_GPT_API_URL, DEFAULT_PROMPTS_GPT_OUT_DIR, DEFAULT_RUN_CONFIG_PATH, PROMPTS_GPT_CREDENTIALS_FILE, PromptsGptApiError, PromptsGptClient, doctor, initRunConfig, loadRunConfig, normalizeOrchestrationAgent, ORCHESTRATION_AGENT_PROFILES, runBatch, runPrompt, resolveRunProvider, warnModelProviderMismatch, sweepPrompt, validateRunConfig, discoverWorkspaceAssets, SUPPORTED_AGENT_TARGETS, detectProviders, loadLocalCredentials, saveLocalCredentials, syncPrompts, writeAgentFiles, writePromptManifest, writePromptMarkdownFiles, ensureGitignoreEntry, isCI, orchestrateParallel, orchestratePipeline, orchestrateEval, } from "./index.js";
6
7
  const CLI_EXIT_CODES = {
7
8
  success: 0,
8
9
  general: 1,
@@ -12,7 +13,7 @@ const CLI_EXIT_CODES = {
12
13
  usage: 64,
13
14
  };
14
15
  const VALID_TOOLS = ["Codex", "Claude Code", "Cursor", "GitHub Copilot", "ChatGPT", "Gemini", "Perplexity", "Grok", "DeepSeek", "Claude"];
15
- const COMMANDS = ["setup", "init", "project", "pull", "generate", "sync", "run", "run-batch", "sweep", "list", "status", "validate", "providers", "models", "sync-models", "doctor", "load-config", "quickstart", "version", "help"];
16
+ const COMMANDS = ["setup", "init", "project", "pull", "generate", "sync", "run", "run-batch", "sweep", "orchestrate", "diff", "list", "status", "validate", "providers", "models", "sync-models", "doctor", "load-config", "quickstart", "version", "help"];
16
17
  const MAX_STDIN_TOKEN_LENGTH = 4_096;
17
18
  class CliError extends Error {
18
19
  exitCode;
@@ -79,7 +80,7 @@ async function main() {
79
80
  }
80
81
  const command = asCommandName(first);
81
82
  if (!command) {
82
- const suggestion = COMMANDS.find((c) => c.startsWith(first.slice(0, 3)));
83
+ const suggestion = findClosestCommand(first);
83
84
  const hint = suggestion ? ` Did you mean \`prompts-gpt ${suggestion}\`?` : "";
84
85
  const installHint = "\nIf you installed from npm, ensure you have the latest version: npm install -g prompts-gpt@latest";
85
86
  throw new CliError(`Unknown command: ${first}.${hint}${installHint}`, CLI_EXIT_CODES.usage);
@@ -106,10 +107,11 @@ async function runCommand(command, flags) {
106
107
  }
107
108
  console.log(`Workspace: ${cwd}\n`);
108
109
  for (const provider of providers) {
109
- const icon = provider.available ? "✓" : "✗";
110
- console.log(`${icon} ${provider.provider}: ${provider.available ? "available" : "missing"} | bin=${provider.bin} | model=${provider.modelDefault}${provider.version ? ` | ${provider.version}` : ""}`);
110
+ const icon = provider.available ? sym("✓", "+") : sym("✗", "x");
111
+ const statusColor = provider.available ? "\x1b[32m" : "\x1b[31m";
112
+ console.log(`${colorize(icon, statusColor)} ${provider.provider}: ${provider.available ? "available" : "missing"} | bin=${provider.bin} | model=${provider.modelDefault}${provider.version ? ` | ${provider.version}` : ""}`);
111
113
  if (!provider.available) {
112
- console.log(` → ${provider.installHint}`);
114
+ console.log(` ${sym("", "->")} ${provider.installHint}`);
113
115
  }
114
116
  }
115
117
  const noneAvailable = providers.every((p) => !p.available);
@@ -165,7 +167,7 @@ async function runCommand(command, flags) {
165
167
  const availableCount = result.providerSummary.filter((p) => p.available).length;
166
168
  console.log(`Providers: ${availableCount}/${result.providerSummary.length} available`);
167
169
  if (availableCount === 0) {
168
- console.log("⚠ No provider CLIs detected. Install codex, cursor agent, claude, or copilot.");
170
+ console.log(`${sym("⚠", "!")} No provider CLIs detected. Install codex, cursor agent, claude, or copilot.`);
169
171
  }
170
172
  const setupAssets = await discoverWorkspaceAssets(cwd);
171
173
  if (isTTYInteractive() && (setupAssets.sweeps.length > 0 || setupAssets.prompts.length > 0)) {
@@ -208,9 +210,24 @@ async function runCommand(command, flags) {
208
210
  }
209
211
  if (command === "models") {
210
212
  const cwd = getResolvedCwd(flags);
211
- const config = await loadRunConfig(cwd);
213
+ let config;
214
+ try {
215
+ config = await loadRunConfig(cwd);
216
+ }
217
+ catch (configErr) {
218
+ console.error(`Failed to load config: ${configErr instanceof Error ? configErr.message : String(configErr)}`);
219
+ console.error(`Fix or delete ${cwd}/.prompts-gpt/config.json and retry.`);
220
+ process.exitCode = CLI_EXIT_CODES.general;
221
+ return;
222
+ }
212
223
  const providerFilter = getStringFlag(flags, "provider") || getStringFlag(flags, "agent");
213
- const providers = providerFilter ? [providerFilter] : Object.keys(PROVIDER_MODELS);
224
+ const validProviders = Object.keys(PROVIDER_MODELS);
225
+ if (providerFilter && !validProviders.includes(providerFilter)) {
226
+ console.error(`Unknown provider "${providerFilter}". Valid providers: ${validProviders.join(", ")}`);
227
+ process.exitCode = CLI_EXIT_CODES.validation;
228
+ return;
229
+ }
230
+ const providers = providerFilter ? [providerFilter] : validProviders;
214
231
  if (Boolean(flags.json)) {
215
232
  const result = {};
216
233
  for (const p of providers) {
@@ -253,7 +270,8 @@ async function runCommand(command, flags) {
253
270
  if (addFlag && providerFilter) {
254
271
  const modelNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
255
272
  const addModels = addFlag.split(",").map((m) => m.trim()).filter(Boolean);
256
- const invalidModels = addModels.filter((m) => !modelNamePattern.test(m));
273
+ const dangerousKeys = new Set(["__proto__", "constructor", "prototype"]);
274
+ const invalidModels = addModels.filter((m) => !modelNamePattern.test(m) || dangerousKeys.has(m));
257
275
  if (invalidModels.length > 0) {
258
276
  console.error(`Invalid model name(s): ${invalidModels.join(", ")}. Model names must be alphanumeric with dots, hyphens, or underscores.`);
259
277
  process.exitCode = CLI_EXIT_CODES.validation;
@@ -381,16 +399,16 @@ async function runCommand(command, flags) {
381
399
  console.log("Prompts-GPT Doctor");
382
400
  console.log("==================\n");
383
401
  console.log("System:");
384
- console.log(` ${report.nodeVersion.startsWith("v18") || report.nodeVersion.startsWith("v2") ? "✓" : "⚠"} Node: ${report.nodeVersion}`);
385
- console.log(` ✓ OS: ${report.osPlatform}/${report.osArch} | CPUs: ${report.cpuCount}`);
386
- console.log(` ${report.configFound ? "✓" : "✗"} Config: ${report.configFound ? report.configPath : "not found — run \`prompts-gpt setup\`"}`);
402
+ console.log(` ${report.nodeVersion.startsWith("v18") || report.nodeVersion.startsWith("v2") ? sym("✓", "+") : sym("⚠", "!")} Node: ${report.nodeVersion}`);
403
+ console.log(` ${sym("", "+")} OS: ${report.osPlatform}/${report.osArch} | CPUs: ${report.cpuCount}`);
404
+ console.log(` ${report.configFound ? sym("✓", "+") : sym("✗", "x")} Config: ${report.configFound ? report.configPath : "not found — run \`prompts-gpt setup\`"}`);
387
405
  console.log(`\n Workspace: ${report.cwd}\n`);
388
406
  console.log("Providers:");
389
407
  for (const provider of report.providers) {
390
- const icon = provider.available ? "✓" : "✗";
408
+ const icon = provider.available ? sym("✓", "+") : sym("✗", "x");
391
409
  console.log(` ${icon} ${provider.provider}: ${provider.available ? `${provider.bin}` : "not found"}${provider.version ? ` (${provider.version})` : ""}`);
392
410
  if (!provider.available) {
393
- console.log(` → ${provider.installHint}`);
411
+ console.log(` ${sym("", "->")} ${provider.installHint}`);
394
412
  }
395
413
  }
396
414
  const configNotes = report.notes.filter((n) => n.includes("config") || n.includes("Config") || n.includes("provider order") || n.includes("Router"));
@@ -399,17 +417,17 @@ async function runCommand(command, flags) {
399
417
  if (configNotes.length > 0) {
400
418
  console.log("\nConfiguration:");
401
419
  for (const n of configNotes)
402
- console.log(` ℹ ${n}`);
420
+ console.log(` ${sym("", "i")} ${n}`);
403
421
  }
404
422
  if (sweepNotes.length > 0) {
405
423
  console.log("\nSweep:");
406
424
  for (const n of sweepNotes)
407
- console.log(` ℹ ${n}`);
425
+ console.log(` ${sym("", "i")} ${n}`);
408
426
  }
409
427
  if (otherNotes.length > 0) {
410
428
  console.log("\nNotes:");
411
429
  for (const n of otherNotes)
412
- console.log(` ℹ ${n}`);
430
+ console.log(` ${sym("", "i")} ${n}`);
413
431
  }
414
432
  try {
415
433
  const config = await loadRunConfig(cwd);
@@ -425,7 +443,7 @@ async function runCommand(command, flags) {
425
443
  if (modelIssues.length > 0) {
426
444
  console.log("\nModel Overrides:");
427
445
  for (const issue of modelIssues)
428
- console.log(` ⚠ ${issue}`);
446
+ console.log(` ${sym("", "!")} ${issue}`);
429
447
  }
430
448
  const cachedModelsPath = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".models.json");
431
449
  if (existsSync(cachedModelsPath)) {
@@ -446,25 +464,25 @@ async function runCommand(command, flags) {
446
464
  catch { /* skip config check */ }
447
465
  console.log("\nAPI Connectivity:");
448
466
  if (typeof globalThis.fetch !== "function") {
449
- console.log(" ⚠ fetch is not available (Node.js 18.18+ required). Skipping API check.");
467
+ console.log(` ${sym("⚠", "!")} fetch is not available (Node.js 18.18+ required). Skipping API check.`);
450
468
  }
451
469
  else {
452
470
  try {
453
471
  const siteCheck = await checkPromptsGptSiteReachable(DEFAULT_PROMPTS_GPT_API_URL);
454
472
  if (siteCheck.ok) {
455
- console.log(` ✓ prompts-gpt.com reachable`);
473
+ console.log(` ${sym("", "+")} prompts-gpt.com reachable`);
456
474
  }
457
475
  else {
458
- console.log(` ⚠ prompts-gpt.com responded with status ${siteCheck.status ?? "unknown"}`);
476
+ console.log(` ${sym("", "!")} prompts-gpt.com responded with status ${siteCheck.status ?? "unknown"}`);
459
477
  }
460
478
  }
461
479
  catch (err) {
462
480
  const msg = err instanceof Error ? err.message : "";
463
481
  if (msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND")) {
464
- console.log(" ✗ prompts-gpt.com unreachable — check network or proxy settings (HTTPS_PROXY)");
482
+ console.log(` ${sym("✗", "x")} prompts-gpt.com unreachable — check network or proxy settings (HTTPS_PROXY)`);
465
483
  }
466
484
  else {
467
- console.log(" ✗ prompts-gpt.com unreachable — check network connection");
485
+ console.log(` ${sym("✗", "x")} prompts-gpt.com unreachable — check network connection`);
468
486
  }
469
487
  }
470
488
  }
@@ -473,6 +491,50 @@ async function runCommand(command, flags) {
473
491
  console.log(`\nSDK Version: ${pkgData.version ?? "unknown"}`);
474
492
  }
475
493
  catch { /* skip */ }
494
+ // --fix mode: auto-fix common issues (idempotent — safe to run repeatedly)
495
+ if (Boolean(flags.fix)) {
496
+ const { mkdir: fsMkdir } = await import("node:fs/promises");
497
+ const uc = supportsColor();
498
+ const ok = uc ? "✓" : "[ok]";
499
+ const warn = uc ? "⚠" : "[!]";
500
+ const info = uc ? "ℹ" : "[i]";
501
+ console.log("\nAuto-fix:");
502
+ let fixCount = 0;
503
+ if (!report.configFound) {
504
+ try {
505
+ await initRunConfig({ cwd, overwrite: false });
506
+ console.log(` ${ok} Created default config`);
507
+ fixCount++;
508
+ }
509
+ catch {
510
+ console.log(` ${warn} Could not create config (may already exist)`);
511
+ }
512
+ }
513
+ const promptsDir = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR);
514
+ if (!existsSync(promptsDir)) {
515
+ try {
516
+ await fsMkdir(promptsDir, { recursive: true });
517
+ console.log(` ${ok} Created ${DEFAULT_PROMPTS_GPT_OUT_DIR}/ directory`);
518
+ fixCount++;
519
+ }
520
+ catch {
521
+ console.log(` ${warn} Could not create ${DEFAULT_PROMPTS_GPT_OUT_DIR}/ directory`);
522
+ }
523
+ }
524
+ try {
525
+ await ensureGitignoreEntry(cwd, ".prompts-gpt/.credentials.json");
526
+ await ensureGitignoreEntry(cwd, ".prompts-gpt/.models.json");
527
+ console.log(` ${ok} Updated .gitignore with sensitive file patterns`);
528
+ fixCount++;
529
+ }
530
+ catch { /* gitignore update is best-effort */ }
531
+ if (report.providers.every((p) => !p.available)) {
532
+ console.log(` ${info} No providers found. Install one:`);
533
+ console.log(" npm install -g @openai/codex");
534
+ console.log(" npm install -g @anthropic-ai/claude-code");
535
+ }
536
+ console.log(`\n ${fixCount} fix${fixCount === 1 ? "" : "es"} applied.`);
537
+ }
476
538
  return;
477
539
  }
478
540
  if (command === "list") {
@@ -589,26 +651,30 @@ async function runCommand(command, flags) {
589
651
  }
590
652
  console.log(`Workspace: ${cwd}`);
591
653
  console.log("");
654
+ const docUc = supportsColor();
655
+ const docOk = docUc ? "✓" : "[ok]";
656
+ const docNo = docUc ? "✗" : "[x]";
657
+ const docDash = docUc ? "—" : "-";
592
658
  console.log("Readiness:");
593
- console.log(` Credentials: ${assets.credentialsFound ? "✓" : "✗ run \`prompts-gpt init --token <token>\`"}`);
594
- console.log(` Config: ${assets.configFound ? "✓" : "✗ run \`prompts-gpt setup\`"}`);
595
- console.log(` Manifest: ${assets.manifestFound ? "✓" : "✗ run \`prompts-gpt sync\`"}`);
659
+ console.log(` Credentials: ${assets.credentialsFound ? docOk : `${docNo} ${docDash} run \`prompts-gpt init --token <token>\``}`);
660
+ console.log(` Config: ${assets.configFound ? docOk : `${docNo} ${docDash} run \`prompts-gpt setup\``}`);
661
+ console.log(` Manifest: ${assets.manifestFound ? docOk : `${docNo} ${docDash} run \`prompts-gpt sync\``}`);
596
662
  const cloudCount = assets.prompts.filter((p) => p.source === "library" || p.source === "generated").length;
597
663
  const localCount = assets.prompts.filter((p) => p.source === "local").length;
598
664
  const promptSummary = assets.prompts.length > 0
599
- ? `✓ (${assets.prompts.length}${cloudCount > 0 ? `, ${cloudCount} synced` : ""}${localCount > 0 ? `, ${localCount} local-only` : ""})`
600
- : "✗ none found";
665
+ ? `${docOk} (${assets.prompts.length}${cloudCount > 0 ? `, ${cloudCount} synced` : ""}${localCount > 0 ? `, ${localCount} local-only` : ""})`
666
+ : `${docNo} ${docDash} none found`;
601
667
  console.log(` Prompts: ${promptSummary}`);
602
- console.log(` Sweeps: ${assets.sweeps.length > 0 ? `✓ (${assets.sweeps.length})` : "— none found"}`);
603
- console.log(` Agents: ${assets.agents.length > 0 ? `✓ (${assets.agents.length} targets)` : "✗ run \`prompts-gpt sync\`"}`);
668
+ console.log(` Sweeps: ${assets.sweeps.length > 0 ? `${docOk} (${assets.sweeps.length})` : `${docDash} none found`}`);
669
+ console.log(` Agents: ${assets.agents.length > 0 ? `${docOk} (${assets.agents.length} targets)` : `${docNo} ${docDash} run \`prompts-gpt sync\``}`);
604
670
  if (availableProviders.length > 0) {
605
- console.log(` Providers: ✓`);
671
+ console.log(` Providers: ${docOk}`);
606
672
  for (const p of availableProviders) {
607
673
  console.log(` ${p.provider}: ${p.bin} | model: ${p.modelDefault}${p.version ? ` | ${p.version}` : ""}`);
608
674
  }
609
675
  }
610
676
  else {
611
- console.log(" Providers: no CLI found. Install one:");
677
+ console.log(` Providers: ${docNo} ${docDash} no CLI found. Install one:`);
612
678
  console.log(" npm install -g @openai/codex # Codex");
613
679
  console.log(" npm install -g @anthropic-ai/claude-code # Claude Code");
614
680
  console.log(" # Cursor: install Cursor IDE (includes agent CLI)");
@@ -620,7 +686,7 @@ async function runCommand(command, flags) {
620
686
  const mStat = statSync(statusModelCache);
621
687
  const ageMs = Date.now() - mStat.mtimeMs;
622
688
  const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
623
- console.log(` Models: ${ageDays <= 7 ? "" : ""} synced ${ageDays === 0 ? "today" : `${ageDays}d ago`}${ageDays > 7 ? " run 'prompts-gpt sync-models'" : ""}`);
689
+ console.log(` Models: ${ageDays <= 7 ? docOk : (docUc ? "" : "[!]")} synced ${ageDays === 0 ? "today" : `${ageDays}d ago`}${ageDays > 7 ? ` ${docDash} run 'prompts-gpt sync-models'` : ""}`);
624
690
  }
625
691
  catch { /* skip */ }
626
692
  }
@@ -679,8 +745,10 @@ async function runCommand(command, flags) {
679
745
  issues.push("no iterations in frontmatter");
680
746
  if (!fm.title)
681
747
  issues.push("no title heading");
682
- const icon = issues.length === 0 ? "✓" : "⚠";
683
- console.log(` ${icon} ${s.name}${issues.length > 0 ? ` ${issues.join(", ")}` : ""}`);
748
+ const valUc = supportsColor();
749
+ const icon = issues.length === 0 ? (valUc ? "✓" : "[ok]") : (valUc ? "" : "[!]");
750
+ const sep = issues.length > 0 ? (valUc ? " — " : " - ") : "";
751
+ console.log(` ${icon} ${s.name}${issues.length > 0 ? `${sep}${issues.join(", ")}` : ""}`);
684
752
  }
685
753
  }
686
754
  const modelCachePath = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".models.json");
@@ -690,14 +758,14 @@ async function runCommand(command, flags) {
690
758
  const ageMs = Date.now() - mStat.mtimeMs;
691
759
  const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
692
760
  if (ageDays > 7) {
693
- console.log(`\n⚠ Model registry is ${ageDays} days old. Run 'prompts-gpt sync-models' to refresh.`);
761
+ console.log(`\n${sym("", "!")} Model registry is ${ageDays} days old. Run 'prompts-gpt sync-models' to refresh.`);
694
762
  }
695
763
  else {
696
- console.log(`\n✓ Model registry: synced ${ageDays === 0 ? "today" : `${ageDays}d ago`}`);
764
+ console.log(`\n${sym("", "+")} Model registry: synced ${ageDays === 0 ? "today" : `${ageDays}d ago`}`);
697
765
  }
698
766
  }
699
767
  catch {
700
- console.log("\n⚠ Model registry not synced. Run 'prompts-gpt sync-models'.");
768
+ console.log(`\n${sym("⚠", "!")} Model registry not synced. Run 'prompts-gpt sync-models'.`);
701
769
  }
702
770
  if (result.valid && result.errors.length === 0 && result.warnings.length === 0) {
703
771
  console.log(" No issues found.");
@@ -862,6 +930,86 @@ async function runCommand(command, flags) {
862
930
  console.error(`[warning] ${mismatchWarning}`);
863
931
  }
864
932
  }
933
+ // Watch mode — re-run on file change
934
+ if (Boolean(flags.watch)) {
935
+ const watchTarget = getStringFlag(flags, "prompt-file") || config.defaultPromptFile;
936
+ if (!watchTarget) {
937
+ throw new CliError("--watch requires a prompt file (use --prompt-file or set a default)", CLI_EXIT_CODES.usage);
938
+ }
939
+ const resolvedWatch = path.resolve(cwd, watchTarget);
940
+ console.log(`Watching ${path.basename(resolvedWatch)} for changes... (Ctrl+C to stop)\n`);
941
+ const { watch: fsWatch } = await import("node:fs");
942
+ let running = false;
943
+ const doRun = async () => {
944
+ if (running)
945
+ return;
946
+ running = true;
947
+ console.log(`\n${colorize("▶ Running...", "\x1b[36m")} (${new Date().toLocaleTimeString()})`);
948
+ try {
949
+ const r = await runPrompt({
950
+ cwd,
951
+ promptFile: watchTarget,
952
+ agent,
953
+ model: modelFlag,
954
+ timeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "timeout"), "timeout"),
955
+ artifactsDir: getStringFlag(flags, "artifacts-dir"),
956
+ approveMcps: !Boolean(flags["no-approve-mcps"]),
957
+ sandboxMode: getStringFlag(flags, "sandbox"),
958
+ permissionMode: getStringFlag(flags, "permission-mode"),
959
+ });
960
+ const icon = r.exitCode === 0 ? colorize("✓", "\x1b[32m") : colorize("✗", "\x1b[31m");
961
+ console.log(`${icon} ${r.provider} exit=${r.exitCode} (${formatDuration(r.durationMs)})`);
962
+ }
963
+ catch (err) {
964
+ console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);
965
+ }
966
+ finally {
967
+ running = false;
968
+ console.log(`\n Watching for changes...`);
969
+ }
970
+ };
971
+ await doRun();
972
+ let debounce = null;
973
+ const watcher = fsWatch(resolvedWatch, () => {
974
+ if (debounce)
975
+ clearTimeout(debounce);
976
+ debounce = setTimeout(doRun, 500);
977
+ debounce.unref?.();
978
+ });
979
+ watcher.on("error", (err) => {
980
+ if (debounce) {
981
+ clearTimeout(debounce);
982
+ debounce = null;
983
+ }
984
+ console.error(`Watch error: ${err instanceof Error ? err.message : String(err)}`);
985
+ console.log("File may have been deleted. Exiting watch mode.");
986
+ watcher.close();
987
+ });
988
+ await new Promise((resolve) => {
989
+ let settled = false;
990
+ const settle = () => { if (settled)
991
+ return; settled = true; watcher.close(); if (debounce) {
992
+ clearTimeout(debounce);
993
+ debounce = null;
994
+ } resolve(); };
995
+ const onSignal = () => { settle(); };
996
+ process.on("SIGINT", onSignal);
997
+ process.on("SIGTERM", onSignal);
998
+ watcher.on("close", () => {
999
+ process.removeListener("SIGINT", onSignal);
1000
+ process.removeListener("SIGTERM", onSignal);
1001
+ settle();
1002
+ });
1003
+ });
1004
+ return;
1005
+ }
1006
+ // B4: --verbose shows detailed execution info
1007
+ if (Boolean(flags.verbose) && !Boolean(flags.json)) {
1008
+ const providers = await detectProviders(cwd);
1009
+ const resolved = resolveRunProvider(agent, providers, config.providerOrder);
1010
+ const resolvedModel = modelFlag?.trim() || config.modelOverrides[resolved]?.trim() || "(default)";
1011
+ console.log(colorize("[verbose]", "\x1b[90m") + ` provider=${resolved} model=${resolvedModel} agent=${agent} timeout=${getStringFlag(flags, "timeout") || config.timeoutSeconds}s`);
1012
+ }
865
1013
  const result = await runPrompt({
866
1014
  cwd,
867
1015
  promptFile,
@@ -879,8 +1027,8 @@ async function runCommand(command, flags) {
879
1027
  console.log(JSON.stringify(result, null, 2));
880
1028
  return;
881
1029
  }
882
- const agentLabel = getStringFlag(flags, "agent") ? result.provider : `${result.provider} (auto — first available from provider order)`;
883
- console.log(`Run ID: ${result.runId}`);
1030
+ const agentLabel = getStringFlag(flags, "agent") ? result.provider : `${result.provider} (auto-selected)`;
1031
+ console.log(`\n${sym("▸", ">")} Run ID: ${result.runId}`);
884
1032
  console.log(`Provider: ${agentLabel} | Model: ${result.model}`);
885
1033
  console.log(`Exit code: ${result.exitCode}`);
886
1034
  console.log(`Duration: ${formatDuration(result.durationMs)}`);
@@ -890,6 +1038,7 @@ async function runCommand(command, flags) {
890
1038
  console.log(`Run dir: ${result.runDir}`);
891
1039
  console.log(`Summary: ${result.summaryFile}`);
892
1040
  console.log(`Log: ${result.logFile}`);
1041
+ console.log(`Diff: prompts-gpt diff ${result.runId}`);
893
1042
  if (result.exitCode === 0) {
894
1043
  try {
895
1044
  const { readFile: fsRead } = await import("node:fs/promises");
@@ -981,6 +1130,22 @@ async function runCommand(command, flags) {
981
1130
  }
982
1131
  if (command === "sweep") {
983
1132
  const cwd = getResolvedCwd(flags);
1133
+ // B1: Handle --force-unlock to clear stale sweep locks
1134
+ if (Boolean(flags["force-unlock"])) {
1135
+ const { forceReleaseSweepLock } = await import("./index.js");
1136
+ const released = await forceReleaseSweepLock(cwd);
1137
+ if (released) {
1138
+ console.log("Sweep lock forcefully released.");
1139
+ }
1140
+ else {
1141
+ console.log("No sweep lock found.");
1142
+ }
1143
+ return;
1144
+ }
1145
+ // Auto-detect CI and force non-interactive mode
1146
+ if (isCI() && !flags["non-interactive"]) {
1147
+ flags["non-interactive"] = true;
1148
+ }
984
1149
  const sweepPromptFile = getStringFlag(flags, "prompt-file");
985
1150
  const config = await loadRunConfig(cwd);
986
1151
  warnOnConfigIssues(config);
@@ -1188,8 +1353,14 @@ async function runCommand(command, flags) {
1188
1353
  const quiet = Boolean(flags.quiet);
1189
1354
  const summaryLineCount = parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines") ?? 40;
1190
1355
  const sweepDurations = [];
1356
+ const sweepUc = supportsColor();
1357
+ // B11: When --json, emit NDJSON progress lines instead of silencing
1191
1358
  const onProgress = Boolean(flags.json)
1192
- ? undefined
1359
+ ? (event) => {
1360
+ if (event.type !== "message" && event.type !== "tool") {
1361
+ console.log(JSON.stringify(event));
1362
+ }
1363
+ }
1193
1364
  : (event) => {
1194
1365
  if (event.type === "preflight") {
1195
1366
  const report = JSON.parse(event.message);
@@ -1208,97 +1379,118 @@ async function runCommand(command, flags) {
1208
1379
  const maxRunDirs = parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs") ?? 20;
1209
1380
  console.log(`Log rotation: keep ${maxRunDirs} runs`);
1210
1381
  for (const w of report.warnings)
1211
- console.log(`⚠ ${w}`);
1382
+ console.log(`${sweepUc ? "⚠" : "[!]"} ${w}`);
1212
1383
  }
1213
1384
  else if (event.type === "iteration_start") {
1214
- console.log(`\n${colorize("══════════════════════════════════════════════════════════════", "\x1b[35m")}`);
1385
+ const progressBar = buildProgressBar(event.iteration - 1, event.total, 40);
1386
+ const divider = supportsColor() ? "\u2550".repeat(62) : "=".repeat(62);
1387
+ console.log(`\n${colorize(divider, "\x1b[35m")}`);
1215
1388
  console.log(colorize(`[${new Date().toLocaleTimeString()}] Iteration ${event.iteration}/${event.total}: ${event.provider} (${event.model})`, "\x1b[1;35m"));
1216
- console.log(colorize("══════════════════════════════════════════════════════════════", "\x1b[35m"));
1389
+ console.log(` ${progressBar}`);
1390
+ console.log(colorize(divider, "\x1b[35m"));
1217
1391
  }
1218
1392
  else if (event.type === "message") {
1219
1393
  if (!quiet) {
1220
1394
  const truncText = event.text.length > 120 ? `${event.text.slice(0, 117)}...` : event.text;
1221
- console.log(` ${colorize(`[${event.elapsed}]`, "\x1b[36m")} 💬 ${truncText}`);
1395
+ console.log(` ${colorize(`[${event.elapsed}]`, "\x1b[36m")} ${sweepUc ? "💬" : "[msg]"} ${truncText}`);
1222
1396
  }
1223
1397
  }
1224
1398
  else if (event.type === "tool") {
1225
1399
  if (!quiet) {
1226
1400
  const c = event.counts;
1227
- let icon = "🔧";
1401
+ let icon = sweepUc ? "🔧" : "[tool]";
1228
1402
  let friendlyAction = event.action;
1229
1403
  if (event.action.includes("read")) {
1230
- icon = "📖";
1404
+ icon = sweepUc ? "📖" : "[read]";
1231
1405
  friendlyAction = "read";
1232
1406
  }
1233
1407
  else if (event.action.includes("write") || event.action.includes("edit") || event.action === "str_replace") {
1234
- icon = "✏️ ";
1408
+ icon = sweepUc ? "✏️ " : "[write]";
1235
1409
  friendlyAction = "write";
1236
1410
  }
1237
1411
  else if (event.action.includes("shell") || event.action === "bash") {
1238
- icon = "⚡";
1412
+ icon = sweepUc ? "⚡" : "[shell]";
1239
1413
  friendlyAction = "shell";
1240
1414
  }
1241
1415
  else if (event.action.includes("search") || event.action.includes("grep") || event.action.includes("glob")) {
1242
- icon = "🔍";
1416
+ icon = sweepUc ? "🔍" : "[find]";
1243
1417
  friendlyAction = "search";
1244
1418
  }
1245
1419
  else if (event.action.includes("todo")) {
1246
- icon = "📋";
1420
+ icon = sweepUc ? "📋" : "[todo]";
1247
1421
  friendlyAction = "todo";
1248
1422
  }
1249
1423
  else if (event.action.includes("web") || event.action.includes("fetch")) {
1250
- icon = "🌐";
1424
+ icon = sweepUc ? "🌐" : "[web]";
1251
1425
  friendlyAction = "web";
1252
1426
  }
1253
1427
  else {
1254
1428
  friendlyAction = event.action.replace(/ToolCall$/i, "").replace(/Tool$/i, "");
1255
1429
  }
1256
1430
  const fileStr = event.file ? ` ${colorize(event.file, "\x1b[90m")}` : "";
1257
- const countStr = colorize(`(R:${c.reads} W:${c.writes} S:${c.shells} 🔍:${c.searches})`, "\x1b[90m");
1431
+ const searchIcon = sweepUc ? "🔍" : "Q";
1432
+ const countStr = colorize(`(R:${c.reads} W:${c.writes} S:${c.shells} ${searchIcon}:${c.searches})`, "\x1b[90m");
1258
1433
  console.log(` ${icon} ${friendlyAction}${fileStr} ${countStr}`);
1259
1434
  }
1260
1435
  }
1261
1436
  else if (event.type === "iteration_end") {
1262
1437
  const elapsed = formatDuration(event.durationMs);
1263
- const icon = event.status === "success" ? colorize("✓", "\x1b[32m") : colorize("✗", "\x1b[31m");
1438
+ const icon = event.status === "success" ? colorize(sweepUc ? "✓" : "OK", "\x1b[32m") : colorize(sweepUc ? "✗" : "FAIL", "\x1b[31m");
1264
1439
  sweepDurations.push(event.durationMs);
1265
1440
  const remaining = ((parseInt(String(getStringFlag(flags, "iterations"))) || 1) - event.iteration);
1266
1441
  let etaStr = "";
1267
1442
  if (remaining > 0 && sweepDurations.length > 0) {
1268
- const avgMs = sweepDurations.reduce((a, b) => a + b, 0) / sweepDurations.length;
1443
+ // Use exponentially weighted moving average for better ETA (recent iterations weigh more)
1444
+ const n = sweepDurations.length;
1445
+ let avgMs;
1446
+ if (n <= 2) {
1447
+ avgMs = sweepDurations.reduce((a, b) => a + b, 0) / n;
1448
+ }
1449
+ else {
1450
+ const recentWeight = 0.7;
1451
+ const recentCount = Math.max(1, Math.floor(n / 2));
1452
+ const recentAvg = sweepDurations.slice(-recentCount).reduce((a, b) => a + b, 0) / recentCount;
1453
+ const overallAvg = sweepDurations.reduce((a, b) => a + b, 0) / n;
1454
+ avgMs = recentAvg * recentWeight + overallAvg * (1 - recentWeight);
1455
+ }
1269
1456
  etaStr = ` | ETA: ~${formatDuration(avgMs * remaining)}`;
1270
1457
  }
1271
1458
  console.log(`${icon} Iteration ${event.iteration} ${event.status} (${elapsed})${etaStr}`);
1272
1459
  }
1273
1460
  else if (event.type === "attempt_retry") {
1274
- console.log(colorize(` ⟳ Retry ${event.attempt}/${event.maxAttempts} for iteration ${event.iteration}, backoff ${event.backoffMs}ms`, "\x1b[33m"));
1461
+ console.log(colorize(` ${sweepUc ? "" : "[retry]"} Retry ${event.attempt}/${event.maxAttempts} for iteration ${event.iteration}, backoff ${event.backoffMs}ms`, "\x1b[33m"));
1275
1462
  }
1276
1463
  else if (event.type === "summary") {
1277
1464
  if (summaryLineCount > 0) {
1278
1465
  const preview = event.lines.slice(0, summaryLineCount);
1279
- console.log(`\n── Summary (iteration ${event.iteration}, ${preview.length}/${event.lines.length} lines) ──`);
1466
+ const sumDash = sweepUc ? "──" : "--";
1467
+ console.log(`\n${sumDash} Summary (iteration ${event.iteration}, ${preview.length}/${event.lines.length} lines) ${sumDash}`);
1280
1468
  for (const line of preview)
1281
1469
  console.log(` ${line}`);
1282
1470
  if (event.lines.length > summaryLineCount) {
1283
1471
  console.log(` ... (${event.lines.length - summaryLineCount} more lines)`);
1284
1472
  }
1285
- console.log("── End summary ──");
1473
+ console.log(`${sumDash} End summary ${sumDash}`);
1286
1474
  }
1287
1475
  }
1288
1476
  else if (event.type === "sweep_end") {
1289
1477
  const r = event.result;
1290
1478
  const cW = 62;
1291
- const cBar = "═".repeat(cW);
1479
+ const uc = supportsColor();
1480
+ const [boxTL, boxH, boxTR, boxV, boxML, boxMR, boxBL, boxBR] = uc
1481
+ ? ["\u2554", "\u2550", "\u2557", "\u2551", "\u2560", "\u2563", "\u255A", "\u255D"]
1482
+ : ["+", "=", "+", "|", "+", "+", "+", "+"];
1483
+ const cBar = boxH.repeat(cW);
1292
1484
  const cRow = (label, val) => {
1293
1485
  const plain = ` ${label.padEnd(12)} ${val}`;
1294
- console.log(`${colorize("║", "\x1b[36m")}${plain.padEnd(cW)}${colorize("║", "\x1b[36m")}`);
1486
+ console.log(`${colorize(boxV, "\x1b[36m")}${plain.padEnd(cW)}${colorize(boxV, "\x1b[36m")}`);
1295
1487
  };
1296
1488
  console.log("");
1297
- console.log(colorize(`╔${cBar}╗`, "\x1b[36m"));
1489
+ console.log(colorize(`${boxTL}${cBar}${boxTR}`, "\x1b[36m"));
1298
1490
  const cTitle = "Sweep Complete";
1299
1491
  const cTitlePad = Math.floor((cW - cTitle.length) / 2);
1300
- console.log(colorize(`║${" ".repeat(cTitlePad)}${cTitle}${" ".repeat(cW - cTitlePad - cTitle.length)}║`, "\x1b[36m"));
1301
- console.log(colorize(`╠${cBar}╣`, "\x1b[36m"));
1492
+ console.log(colorize(`${boxV}${" ".repeat(cTitlePad)}${cTitle}${" ".repeat(cW - cTitlePad - cTitle.length)}${boxV}`, "\x1b[36m"));
1493
+ console.log(colorize(`${boxML}${cBar}${boxMR}`, "\x1b[36m"));
1302
1494
  cRow("Model:", r.model);
1303
1495
  cRow("Duration:", formatDuration(r.totalDurationMs));
1304
1496
  cRow("Iterations:", String(r.totalIterations));
@@ -1306,13 +1498,13 @@ async function runCommand(command, flags) {
1306
1498
  if (r.failed > 0) {
1307
1499
  cRow("Failed:", String(r.failed));
1308
1500
  }
1309
- console.log(colorize(`╠${cBar}╣`, "\x1b[36m"));
1501
+ console.log(colorize(`${boxML}${cBar}${boxMR}`, "\x1b[36m"));
1310
1502
  for (const iter of r.iterations) {
1311
- const statusIcon = iter.status === "success" ? "" : "";
1503
+ const statusIcon = iter.status === "success" ? (uc ? "\u2713" : "+") : (uc ? "\u2717" : "x");
1312
1504
  const plain = ` ${statusIcon} Iter ${iter.iteration}: ${iter.status.padEnd(16)} ${formatDuration(iter.durationMs)}`;
1313
- console.log(`${colorize("║", "\x1b[36m")}${plain.padEnd(cW)}${colorize("║", "\x1b[36m")}`);
1505
+ console.log(`${colorize(boxV, "\x1b[36m")}${plain.padEnd(cW)}${colorize(boxV, "\x1b[36m")}`);
1314
1506
  }
1315
- console.log(colorize(`╚${cBar}╝`, "\x1b[36m"));
1507
+ console.log(colorize(`${boxBL}${cBar}${boxBR}`, "\x1b[36m"));
1316
1508
  if (hasTokenUsage(r.tokenUsage)) {
1317
1509
  const tu = r.tokenUsage;
1318
1510
  console.log(`Tokens: ${formatTokenUsage(tu)}`);
@@ -1323,6 +1515,72 @@ async function runCommand(command, flags) {
1323
1515
  }
1324
1516
  }
1325
1517
  };
1518
+ // Build eval config if --eval flag is set
1519
+ const evalConfig = Boolean(flags.eval) ? {
1520
+ enabled: true,
1521
+ evaluatorAgent: getStringFlag(flags, "eval-agent"),
1522
+ criteria: getStringFlag(flags, "eval-criteria")?.split(",").map((c) => c.trim()),
1523
+ } : undefined;
1524
+ // Parallel sweep — run N iterations concurrently
1525
+ const parallelCount = parsePositiveIntFlag(getStringFlag(flags, "parallel"), "parallel");
1526
+ if (parallelCount && parallelCount > 1) {
1527
+ const totalIterations = parsePositiveIntFlag(getStringFlag(flags, "iterations"), "iterations") ?? 1;
1528
+ const batchSize = Math.min(parallelCount, totalIterations);
1529
+ if (totalIterations > 1) {
1530
+ console.log(`Note: Parallel sweep runs independent iterations (no inter-iteration summary chaining).`);
1531
+ }
1532
+ console.log(`Running ${totalIterations} iterations with parallelism=${batchSize}...`);
1533
+ const allResults = [];
1534
+ const parallelWallClockStart = Date.now();
1535
+ for (let batchStart = 0; batchStart < totalIterations; batchStart += batchSize) {
1536
+ const batchEnd = Math.min(batchStart + batchSize, totalIterations);
1537
+ const batchPromises = [];
1538
+ for (let i = batchStart; i < batchEnd; i++) {
1539
+ batchPromises.push(sweepPrompt({
1540
+ cwd,
1541
+ promptFile: getStringFlag(flags, "prompt-file"),
1542
+ agent,
1543
+ model: getStringFlag(flags, "model"),
1544
+ iterations: 1,
1545
+ iterationTimeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout"),
1546
+ maxRetries: parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries"),
1547
+ artifactsDir: getStringFlag(flags, "artifacts-dir"),
1548
+ runId: `${getStringFlag(flags, "run-id") || "sweep"}-parallel-${i + 1}`,
1549
+ approveMcps: !Boolean(flags["no-approve-mcps"]),
1550
+ sandboxMode: getStringFlag(flags, "sandbox"),
1551
+ phase: getStringFlag(flags, "phase"),
1552
+ dryRun: Boolean(flags["dry-run"]),
1553
+ maxRunDirs: parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs"),
1554
+ summaryLines: parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines"),
1555
+ background: Boolean(flags.background),
1556
+ permissionMode: getStringFlag(flags, "permission-mode"),
1557
+ evalAfterEachIteration: evalConfig,
1558
+ }));
1559
+ }
1560
+ const batchResults = await Promise.allSettled(batchPromises);
1561
+ for (const br of batchResults) {
1562
+ if (br.status === "fulfilled")
1563
+ allResults.push(br.value);
1564
+ }
1565
+ }
1566
+ const totalSucceeded = allResults.reduce((s, r) => s + r.succeeded, 0);
1567
+ const totalFailed = allResults.reduce((s, r) => s + r.failed, 0);
1568
+ const totalDuration = Date.now() - parallelWallClockStart;
1569
+ if (Boolean(flags.json)) {
1570
+ const jsonResults = allResults.map((r) => ({
1571
+ ...r,
1572
+ iterations: r.iterations.map((it) => ({ ...it, toolCounts: it.toolCounts ?? null })),
1573
+ }));
1574
+ console.log(JSON.stringify({ parallel: true, results: jsonResults, totalSucceeded, totalFailed, totalDurationMs: totalDuration }, null, 2));
1575
+ }
1576
+ else {
1577
+ console.log(`\nParallel sweep complete: ${totalSucceeded}/${totalIterations} succeeded, ${totalFailed} failed`);
1578
+ console.log(`Total wall-clock time: ${formatDuration(totalDuration)}`);
1579
+ }
1580
+ if (totalFailed > 0)
1581
+ process.exitCode = 1;
1582
+ return;
1583
+ }
1326
1584
  const result = await sweepPrompt({
1327
1585
  cwd,
1328
1586
  promptFile: getStringFlag(flags, "prompt-file"),
@@ -1341,6 +1599,7 @@ async function runCommand(command, flags) {
1341
1599
  summaryLines: parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines"),
1342
1600
  background: Boolean(flags.background),
1343
1601
  permissionMode: getStringFlag(flags, "permission-mode"),
1602
+ evalAfterEachIteration: evalConfig,
1344
1603
  onProgress,
1345
1604
  });
1346
1605
  if (Boolean(flags.json)) {
@@ -1439,6 +1698,223 @@ async function runCommand(command, flags) {
1439
1698
  }
1440
1699
  return;
1441
1700
  }
1701
+ if (command === "orchestrate") {
1702
+ const cwd = getResolvedCwd(flags);
1703
+ const mode = getStringFlag(flags, "mode");
1704
+ if (!mode || !["parallel", "pipeline", "eval"].includes(mode)) {
1705
+ throw new CliError("The --mode flag is required and must be one of: parallel, pipeline, eval", CLI_EXIT_CODES.usage);
1706
+ }
1707
+ const dryRun = Boolean(flags["dry-run"]);
1708
+ const jsonOutput = Boolean(flags.json);
1709
+ const timeoutSeconds = parsePositiveIntFlag(getStringFlag(flags, "timeout"), "timeout");
1710
+ const orchUc = supportsColor();
1711
+ const orchBar = orchUc ? "═══" : "===";
1712
+ const onProgress = jsonOutput
1713
+ ? undefined
1714
+ : (event) => {
1715
+ if (event.type === "orchestrate_start") {
1716
+ console.log(`\n${colorize(`${orchBar} Orchestration`, "\x1b[35m")} mode=${event.mode}${event.providers ? ` providers=${event.providers.join(",")}` : ""} ${colorize(orchBar, "\x1b[35m")}`);
1717
+ }
1718
+ else if (event.type === "provider_start") {
1719
+ const stepLabel = event.step ? ` [${event.step}]` : "";
1720
+ console.log(` ${orchUc ? "▶" : ">"} ${event.provider}${stepLabel} starting...`);
1721
+ }
1722
+ else if (event.type === "provider_end") {
1723
+ const icon = event.exitCode === 0 ? colorize(orchUc ? "✓" : "OK", "\x1b[32m") : colorize(orchUc ? "✗" : "FAIL", "\x1b[31m");
1724
+ console.log(` ${icon} ${event.provider} finished (${formatDuration(event.durationMs)}, exit=${event.exitCode})`);
1725
+ }
1726
+ else if (event.type === "eval_start") {
1727
+ console.log(` ${orchUc ? "🧪" : "[eval]"} Evaluating with ${event.evaluator}...`);
1728
+ }
1729
+ else if (event.type === "eval_end") {
1730
+ console.log(` ${orchUc ? "📊" : "[score]"} Score: ${event.score}/100`);
1731
+ }
1732
+ else if (event.type === "orchestrate_end") {
1733
+ console.log(`\n${colorize(orchBar, "\x1b[35m")} Done in ${formatDuration(event.totalDurationMs)}${event.winner ? ` | Winner: ${event.winner}` : ""} ${colorize(orchBar, "\x1b[35m")}\n`);
1734
+ }
1735
+ };
1736
+ if (mode === "parallel") {
1737
+ const providersStr = getStringFlag(flags, "providers");
1738
+ if (!providersStr) {
1739
+ throw new CliError("--providers is required for parallel mode (e.g. --providers codex,claude,cursor)", CLI_EXIT_CODES.usage);
1740
+ }
1741
+ const providers = providersStr.split(",").map((p) => p.trim()).filter(Boolean);
1742
+ const promptFile = getStringFlag(flags, "prompt-file");
1743
+ if (!promptFile) {
1744
+ throw new CliError("--prompt-file is required for orchestrate", CLI_EXIT_CODES.usage);
1745
+ }
1746
+ const criteriaStr = getStringFlag(flags, "criteria");
1747
+ const criteria = criteriaStr ? criteriaStr.split(",").map((c) => c.trim()) : undefined;
1748
+ const evaluator = getStringFlag(flags, "evaluator");
1749
+ const result = await orchestrateParallel({
1750
+ cwd,
1751
+ promptFile,
1752
+ providers,
1753
+ scoringCriteria: criteria,
1754
+ evaluatorProvider: evaluator,
1755
+ timeoutSeconds,
1756
+ dryRun,
1757
+ onProgress,
1758
+ });
1759
+ if (jsonOutput) {
1760
+ console.log(JSON.stringify(result, null, 2));
1761
+ }
1762
+ else {
1763
+ console.log("Results:");
1764
+ for (const run of result.runs) {
1765
+ const icon = run.result.exitCode === 0 ? sym("✓", "+") : sym("✗", "x");
1766
+ console.log(` ${icon} ${run.provider}: ${run.score}/100 — ${run.reasoning}`);
1767
+ }
1768
+ if (result.winner) {
1769
+ console.log(`\n ${sym("🏆", "[W]")} Winner: ${result.winner.provider} (${result.winner.score}/100)`);
1770
+ }
1771
+ }
1772
+ return;
1773
+ }
1774
+ if (mode === "pipeline") {
1775
+ const stepsFile = getStringFlag(flags, "steps");
1776
+ if (!stepsFile) {
1777
+ throw new CliError("--steps <pipeline-config.json> is required for pipeline mode", CLI_EXIT_CODES.usage);
1778
+ }
1779
+ if (!existsSync(stepsFile)) {
1780
+ throw new CliError(`Pipeline steps file not found: ${stepsFile}`, CLI_EXIT_CODES.usage);
1781
+ }
1782
+ const stepsJson = JSON.parse(readFileSync(stepsFile, "utf8"));
1783
+ if (!Array.isArray(stepsJson) || stepsJson.length === 0) {
1784
+ throw new CliError("Pipeline steps file must contain a non-empty JSON array", CLI_EXIT_CODES.usage);
1785
+ }
1786
+ for (const step of stepsJson) {
1787
+ if (step.promptFile && !existsSync(path.resolve(cwd, step.promptFile))) {
1788
+ throw new CliError(`Pipeline step "${step.name}" references missing prompt file: ${step.promptFile}`, CLI_EXIT_CODES.usage);
1789
+ }
1790
+ }
1791
+ const result = await orchestratePipeline({
1792
+ cwd,
1793
+ steps: stepsJson,
1794
+ timeoutSeconds,
1795
+ dryRun,
1796
+ onProgress,
1797
+ });
1798
+ if (jsonOutput) {
1799
+ console.log(JSON.stringify(result, null, 2));
1800
+ }
1801
+ else {
1802
+ console.log("Pipeline Results:");
1803
+ for (const step of result.steps) {
1804
+ const icon = step.result.exitCode === 0 ? sym("✓", "+") : sym("✗", "x");
1805
+ console.log(` ${icon} ${step.name} (${step.provider}) — ${formatDuration(step.durationMs)}`);
1806
+ }
1807
+ console.log(`\n Total: ${formatDuration(result.totalDurationMs)}`);
1808
+ }
1809
+ return;
1810
+ }
1811
+ if (mode === "eval") {
1812
+ const promptFile = getStringFlag(flags, "prompt-file");
1813
+ if (!promptFile) {
1814
+ throw new CliError("--prompt-file is required for orchestrate eval mode", CLI_EXIT_CODES.usage);
1815
+ }
1816
+ const agent = getStringFlag(flags, "agent");
1817
+ const evaluator = getStringFlag(flags, "evaluator");
1818
+ const criteriaStr = getStringFlag(flags, "criteria");
1819
+ const criteria = criteriaStr ? criteriaStr.split(",").map((c) => c.trim()) : undefined;
1820
+ const result = await orchestrateEval({
1821
+ cwd,
1822
+ promptFile,
1823
+ agent,
1824
+ evaluatorAgent: evaluator,
1825
+ criteria,
1826
+ timeoutSeconds,
1827
+ dryRun,
1828
+ onProgress,
1829
+ });
1830
+ if (jsonOutput) {
1831
+ console.log(JSON.stringify(result, null, 2));
1832
+ }
1833
+ else {
1834
+ console.log(`Run: exit=${result.run.exitCode ?? "?"} provider=${result.run.provider ?? "?"}`);
1835
+ console.log(`\nEvaluation:`);
1836
+ console.log(` Score: ${result.evaluation.score}/${result.evaluation.maxScore}`);
1837
+ console.log(` Reasoning: ${result.evaluation.reasoning}`);
1838
+ if (Object.keys(result.evaluation.criteriaScores).length > 0) {
1839
+ console.log(` Criteria:`);
1840
+ for (const [key, value] of Object.entries(result.evaluation.criteriaScores)) {
1841
+ console.log(` ${key}: ${value}/10`);
1842
+ }
1843
+ }
1844
+ if (result.evaluation.suggestions.length > 0) {
1845
+ console.log(` Suggestions:`);
1846
+ for (const s of result.evaluation.suggestions) {
1847
+ console.log(` - ${s}`);
1848
+ }
1849
+ }
1850
+ }
1851
+ return;
1852
+ }
1853
+ return;
1854
+ }
1855
+ if (command === "diff") {
1856
+ const cwd = getResolvedCwd(flags);
1857
+ const runId = getStringFlag(flags, "run-id");
1858
+ const config = await loadRunConfig(cwd);
1859
+ const artifactsDir = path.resolve(cwd, config.artifactsDir);
1860
+ if (!runId) {
1861
+ if (!existsSync(artifactsDir)) {
1862
+ throw new CliError("No artifacts directory found. Run a sweep or prompt first.", CLI_EXIT_CODES.usage);
1863
+ }
1864
+ const { readdirSync: listDir } = await import("node:fs");
1865
+ const dirs = listDir(artifactsDir).filter((d) => {
1866
+ try {
1867
+ return statSync(path.join(artifactsDir, d)).isDirectory();
1868
+ }
1869
+ catch {
1870
+ return false;
1871
+ }
1872
+ }).sort().reverse().slice(0, 10);
1873
+ if (dirs.length === 0) {
1874
+ throw new CliError("No run directories found in artifacts.", CLI_EXIT_CODES.usage);
1875
+ }
1876
+ console.log("Recent runs:");
1877
+ for (const d of dirs)
1878
+ console.log(` ${d}`);
1879
+ console.log(`\nUsage: prompts-gpt diff <run-id>`);
1880
+ return;
1881
+ }
1882
+ const runDir = path.resolve(artifactsDir, runId);
1883
+ const caseInsensitive = process.platform === "win32";
1884
+ const runDirCheck = caseInsensitive ? runDir.toLowerCase() : runDir;
1885
+ const artDirCheck = caseInsensitive ? artifactsDir.toLowerCase() : artifactsDir;
1886
+ if (!runDirCheck.startsWith(artDirCheck + path.sep) && runDirCheck !== artDirCheck) {
1887
+ throw new CliError("Invalid run-id: path escapes the artifacts directory.", CLI_EXIT_CODES.usage);
1888
+ }
1889
+ if (!existsSync(runDir)) {
1890
+ throw new CliError(`Run directory not found: ${runDir}`, CLI_EXIT_CODES.usage);
1891
+ }
1892
+ const { readFile: fsRead } = await import("node:fs/promises");
1893
+ const deltaFile = path.join(runDir, "worktree-delta.diff");
1894
+ if (existsSync(deltaFile)) {
1895
+ const delta = await fsRead(deltaFile, "utf8");
1896
+ if (delta.trim()) {
1897
+ console.log(delta);
1898
+ }
1899
+ else {
1900
+ console.log("No worktree changes recorded for this run.");
1901
+ }
1902
+ }
1903
+ else {
1904
+ const beforeFile = path.join(runDir, "worktree-before.txt");
1905
+ const afterFile = path.join(runDir, "worktree-after.txt");
1906
+ if (existsSync(beforeFile) && existsSync(afterFile)) {
1907
+ const before = await fsRead(beforeFile, "utf8");
1908
+ const after = await fsRead(afterFile, "utf8");
1909
+ console.log("Before:\n" + before.slice(0, 2000));
1910
+ console.log("\nAfter:\n" + after.slice(0, 2000));
1911
+ }
1912
+ else {
1913
+ console.log("No diff data available for this run.");
1914
+ }
1915
+ }
1916
+ return;
1917
+ }
1442
1918
  if (command === "quickstart") {
1443
1919
  const cwd = getResolvedCwd(flags);
1444
1920
  const assets = await discoverWorkspaceAssets(cwd);
@@ -1450,7 +1926,7 @@ async function runCommand(command, flags) {
1450
1926
  const { spawnSync: gitCheck } = await import("node:child_process");
1451
1927
  const gitResult = gitCheck("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8", timeout: 5000, windowsHide: true });
1452
1928
  if (gitResult.status !== 0) {
1453
- console.log("⚠ Not inside a git repository. Prompts-GPT works best in a git repo for worktree tracking.");
1929
+ console.log(`${sym("⚠", "!")} Not inside a git repository. Prompts-GPT works best in a git repo for worktree tracking.`);
1454
1930
  console.log(" Run: git init\n");
1455
1931
  }
1456
1932
  if (!assets.credentialsFound) {
@@ -1462,7 +1938,7 @@ async function runCommand(command, flags) {
1462
1938
  networkOk = false;
1463
1939
  }
1464
1940
  if (!networkOk) {
1465
- console.log("⚠ Network unavailable. Quickstart requires internet for token validation.\n");
1941
+ console.log(`${sym("⚠", "!")} Network unavailable. Quickstart requires internet for token validation.\n`);
1466
1942
  console.log("You can still use local sweep files without a network connection:");
1467
1943
  console.log(" 1. Create .prompts-gpt/sweeps/<name>.md with your sweep prompt");
1468
1944
  console.log(" 2. Run: prompts-gpt sweep");
@@ -1477,29 +1953,29 @@ async function runCommand(command, flags) {
1477
1953
  console.log("Run `prompts-gpt quickstart` again after saving your token.");
1478
1954
  return;
1479
1955
  }
1480
- console.log("✓ Credentials found");
1956
+ console.log(`${sym("✓", "+")} Credentials found`);
1481
1957
  if (!assets.configFound) {
1482
- console.log("→ Setting up config...");
1958
+ console.log(`${sym("→", "->")} Setting up config...`);
1483
1959
  try {
1484
1960
  await initRunConfig({ cwd, overwrite: false });
1485
- console.log("✓ Config created");
1961
+ console.log(`${sym("✓", "+")} Config created`);
1486
1962
  }
1487
1963
  catch {
1488
- console.log("✓ Config already exists");
1964
+ console.log(`${sym("✓", "+")} Config already exists`);
1489
1965
  }
1490
1966
  }
1491
1967
  else {
1492
- console.log("✓ Config found");
1968
+ console.log(`${sym("✓", "+")} Config found`);
1493
1969
  }
1494
1970
  if (availableProviders.length === 0) {
1495
1971
  console.log("");
1496
- console.log("✗ No provider CLIs found. Install at least one:");
1972
+ console.log(`${sym("✗", "x")} No provider CLIs found. Install at least one:`);
1497
1973
  console.log(" - codex: npm install -g @openai/codex");
1498
1974
  console.log(" - claude: npm install -g @anthropic-ai/claude-code");
1499
1975
  console.log(" - cursor: Install Cursor IDE (includes `agent` CLI)");
1500
1976
  return;
1501
1977
  }
1502
- console.log(`✓ Providers: ${availableProviders.map((p) => p.provider).join(", ")}`);
1978
+ console.log(`${sym("✓", "+")} Providers: ${availableProviders.map((p) => p.provider).join(", ")}`);
1503
1979
  if (assets.prompts.length === 0 && assets.sweeps.length === 0) {
1504
1980
  console.log("");
1505
1981
  console.log("→ No prompts found. Syncing from Prompts Studio...");
@@ -2163,6 +2639,7 @@ function getCommandOptions(command) {
2163
2639
  "non-interactive": { type: "boolean" },
2164
2640
  verbose: { type: "boolean", short: "v" },
2165
2641
  open: { type: "boolean" },
2642
+ watch: { type: "boolean", short: "w" },
2166
2643
  "list-models": { type: "boolean" },
2167
2644
  };
2168
2645
  }
@@ -2191,7 +2668,38 @@ function getCommandOptions(command) {
2191
2668
  verbose: { type: "boolean", short: "v" },
2192
2669
  open: { type: "boolean" },
2193
2670
  quiet: { type: "boolean", short: "q" },
2671
+ parallel: { type: "string" },
2672
+ eval: { type: "boolean" },
2673
+ "eval-criteria": { type: "string" },
2674
+ "eval-agent": { type: "string" },
2194
2675
  "list-models": { type: "boolean" },
2676
+ "force-unlock": { type: "boolean" },
2677
+ "allow-destructive-git": { type: "boolean" },
2678
+ };
2679
+ }
2680
+ if (command === "orchestrate") {
2681
+ return {
2682
+ help: { type: "boolean" },
2683
+ cwd: { type: "string" },
2684
+ json: { type: "boolean" },
2685
+ mode: { type: "string" },
2686
+ "prompt-file": { type: "string", short: "f" },
2687
+ providers: { type: "string" },
2688
+ evaluator: { type: "string" },
2689
+ criteria: { type: "string" },
2690
+ steps: { type: "string" },
2691
+ agent: { type: "string" },
2692
+ model: { type: "string" },
2693
+ timeout: { type: "string" },
2694
+ "dry-run": { type: "boolean" },
2695
+ "non-interactive": { type: "boolean" },
2696
+ };
2697
+ }
2698
+ if (command === "diff") {
2699
+ return {
2700
+ help: { type: "boolean" },
2701
+ cwd: { type: "string" },
2702
+ "run-id": { type: "string" },
2195
2703
  };
2196
2704
  }
2197
2705
  if (command === "models") {
@@ -2228,7 +2736,15 @@ function getCommandOptions(command) {
2228
2736
  agent: { type: "string" },
2229
2737
  };
2230
2738
  }
2231
- if (command === "quickstart" || command === "list" || command === "status" || command === "validate" || command === "providers" || command === "doctor") {
2739
+ if (command === "doctor") {
2740
+ return {
2741
+ help: { type: "boolean" },
2742
+ cwd: { type: "string" },
2743
+ json: { type: "boolean" },
2744
+ fix: { type: "boolean" },
2745
+ };
2746
+ }
2747
+ if (command === "quickstart" || command === "list" || command === "status" || command === "validate" || command === "providers") {
2232
2748
  return {
2233
2749
  help: { type: "boolean" },
2234
2750
  cwd: { type: "string" },
@@ -2498,7 +3014,10 @@ function validateProviderOrderFlag(values) {
2498
3014
  }
2499
3015
  }
2500
3016
  function getResolvedCwd(flags) {
2501
- return path.resolve(getStringFlag(flags, "cwd") || process.cwd());
3017
+ const raw = getStringFlag(flags, "cwd")?.trim();
3018
+ // B19: Strip surrounding quotes that may appear when shell doesn't consume them
3019
+ const cleaned = raw?.replace(/^["']|["']$/g, "");
3020
+ return path.resolve(cleaned || process.cwd());
2502
3021
  }
2503
3022
  function getStringFlag(flags, name) {
2504
3023
  const value = flags[name];
@@ -2618,11 +3137,22 @@ Global options:
2618
3137
  --json Output structured JSON (for run, sweep, list, etc.)
2619
3138
 
2620
3139
  Environment variables:
2621
- PROMPTS_GPT_TOKEN Project token (overrides stored credentials)
2622
- PROMPTS_GPT_API_URL API URL (default: https://prompts-gpt.com)
2623
- PROMPTS_GPT_MODEL Global model override (applied to all providers)
2624
- PROMPTS_GPT_NON_INTERACTIVE=1 Disable interactive prompts
2625
- NO_COLOR=1 Disable colored output
3140
+ PROMPTS_GPT_TOKEN Project token (overrides stored credentials)
3141
+ PROMPTS_GPT_API_URL API URL (default: https://prompts-gpt.com)
3142
+ PROMPTS_GPT_MODEL Global model override (all providers)
3143
+ PROMPTS_GPT_RUN_MODEL_CODEX Model override for codex provider
3144
+ PROMPTS_GPT_RUN_MODEL_CLAUDE Model override for claude provider
3145
+ PROMPTS_GPT_RUN_MODEL_CURSOR Model override for cursor provider
3146
+ PROMPTS_GPT_RUN_MODEL_COPILOT Model override for copilot provider
3147
+ PROMPTS_GPT_RUN_AGENT Default agent (codex|claude|cursor|copilot|router)
3148
+ PROMPTS_GPT_RUN_PROVIDER_ORDER Provider priority (comma-separated)
3149
+ PROMPTS_GPT_RUN_TIMEOUT_SECONDS Default timeout for runs
3150
+ PROMPTS_GPT_RUN_RETRY_COUNT Number of retries on transient failures
3151
+ PROMPTS_GPT_RUN_ARTIFACTS_DIR Custom artifacts directory
3152
+ PROMPTS_GPT_RUN_DISALLOW_DESTRUCTIVE_GIT Block git stash/reset (true/false)
3153
+ PROMPTS_GPT_DEBUG=1 Enable debug logging
3154
+ PROMPTS_GPT_NON_INTERACTIVE=1 Disable interactive prompts
3155
+ NO_COLOR=1 Disable colored output
2626
3156
 
2627
3157
  Providers: ${ORCHESTRATION_AGENT_PROFILES.filter((p) => p !== "router").join(", ")} (or router for auto-select)
2628
3158
  Agent targets: ${SUPPORTED_AGENT_TARGETS.join(", ")}
@@ -2707,24 +3237,29 @@ const PROVIDER_MODELS = Object.freeze({
2707
3237
  { value: "gpt-5.4-pro", label: "gpt-5.4-pro — enhanced responses", tier: "standard" },
2708
3238
  { value: "gpt-5.4-mini", label: "gpt-5.4-mini — fast coding & subagents", tier: "fast" },
2709
3239
  { value: "gpt-5.4-nano", label: "gpt-5.4-nano — cheapest high-volume", tier: "budget" },
2710
- { value: "gpt-5.3-codex", label: "gpt-5.3-codex — previous gen codex", tier: "standard" },
3240
+ { value: "gpt-5.3-codex", label: "gpt-5.3-codex — merged GPT-5 + Codex", tier: "standard" },
2711
3241
  { value: "gpt-5.3-codex-spark", label: "gpt-5.3-codex-spark — 15x faster gen", tier: "fast" },
3242
+ { value: "gpt-5.2-codex", label: "gpt-5.2-codex — API key auth recommended", tier: "standard" },
2712
3243
  { value: "o4-mini", label: "o4-mini — fast reasoning", tier: "fast" },
2713
3244
  { value: "o3", label: "o3 — advanced reasoning", tier: "frontier" },
2714
3245
  { value: "gpt-5.1-codex", label: "gpt-5.1-codex — legacy codex", tier: "budget" },
3246
+ { value: "gpt-5.1-codex-mini", label: "gpt-5.1-codex-mini — small legacy", tier: "budget" },
2715
3247
  { value: "gpt-4.1", label: "gpt-4.1 — legacy", tier: "budget" },
2716
3248
  ],
2717
3249
  claude: [
3250
+ { value: "claude-opus-4-7", label: "claude-opus-4-7 — most capable model", tier: "frontier" },
2718
3251
  { value: "claude-sonnet-4-6", label: "claude-sonnet-4-6 — speed + intelligence ★", tier: "standard" },
2719
- { value: "claude-opus-4-5", label: "claude-opus-4-5 — previous gen opus", tier: "frontier" },
3252
+ { value: "claude-opus-4-6", label: "claude-opus-4-6 — previous gen frontier", tier: "frontier" },
3253
+ { value: "claude-opus-4-5", label: "claude-opus-4-5 — legacy opus", tier: "standard" },
2720
3254
  { value: "claude-haiku-4-5", label: "claude-haiku-4-5 — fastest near-frontier", tier: "fast" },
2721
3255
  ],
2722
3256
  cursor: [
2723
3257
  { value: "auto", label: "auto — Cursor auto-selects best", tier: "standard" },
3258
+ { value: "opus-4.7", label: "opus-4.7 — Claude Opus latest", tier: "frontier" },
2724
3259
  { value: "claude-4.6-opus-high", label: "claude-4.6-opus-high — frontier reasoning", tier: "frontier" },
2725
3260
  { value: "claude-4.6-opus-high-thinking", label: "claude-4.6-opus-high-thinking — reasoning + thinking", tier: "frontier" },
2726
3261
  { value: "claude-4.6-sonnet-high", label: "claude-4.6-sonnet-high — fast + smart", tier: "standard" },
2727
- { value: "gpt-5.5-medium", label: "gpt-5.5-medium — GPT frontier", tier: "frontier" },
3262
+ { value: "gpt-5.5-high-fast", label: "gpt-5.5-high-fast — GPT frontier", tier: "frontier" },
2728
3263
  { value: "composer-2", label: "composer-2 — balanced multi-file", tier: "standard" },
2729
3264
  { value: "composer-2-fast", label: "composer-2-fast — speed optimized", tier: "fast" },
2730
3265
  { value: "gpt-5.3-codex", label: "gpt-5.3-codex — OpenAI codex", tier: "standard" },
@@ -2735,14 +3270,21 @@ const PROVIDER_MODELS = Object.freeze({
2735
3270
  ],
2736
3271
  copilot: [
2737
3272
  { value: "auto", label: "auto — Copilot auto-selects", tier: "standard" },
2738
- { value: "gpt-5.5", label: "gpt-5.5 — frontier", tier: "frontier" },
2739
- { value: "gpt-5.4-mini", label: "gpt-5.4-minifast", tier: "fast" },
2740
- { value: "claude-sonnet-4-6", label: "claude-sonnet-4-6 — Anthropic", tier: "standard" },
2741
- { value: "claude-opus-4-5", label: "claude-opus-4-5 — Anthropic frontier", tier: "frontier" },
3273
+ { value: "claude-opus-4-6", label: "claude-opus-4-6Anthropic frontier", tier: "frontier" },
3274
+ { value: "claude-sonnet-4-6", label: "claude-sonnet-4-6Anthropic balanced", tier: "standard" },
3275
+ { value: "claude-sonnet-4-5", label: "claude-sonnet-4-5 — Anthropic previous gen", tier: "standard" },
3276
+ { value: "claude-opus-4-5", label: "claude-opus-4-5 — Anthropic legacy opus", tier: "standard" },
3277
+ { value: "gpt-5.3-codex", label: "gpt-5.3-codex — OpenAI coding", tier: "standard" },
3278
+ { value: "gpt-5.2-codex", label: "gpt-5.2-codex — OpenAI API key auth", tier: "standard" },
3279
+ { value: "gpt-5.1-codex-max", label: "gpt-5.1-codex-max — extended compute", tier: "frontier" },
3280
+ { value: "gemini-3-pro", label: "gemini-3-pro — Google frontier", tier: "frontier" },
3281
+ { value: "gpt-5-mini", label: "gpt-5-mini — fast included", tier: "fast" },
3282
+ { value: "gpt-4.1", label: "gpt-4.1 — included with subscription", tier: "budget" },
3283
+ { value: "claude-haiku-4-5", label: "claude-haiku-4-5 — fastest", tier: "fast" },
2742
3284
  ],
2743
3285
  });
2744
3286
  const MODEL_ALIASES = {
2745
- opus: "claude-opus-4-5",
3287
+ opus: "claude-opus-4-7",
2746
3288
  sonnet: "claude-sonnet-4-6",
2747
3289
  haiku: "claude-haiku-4-5",
2748
3290
  codex: "gpt-5.3-codex",
@@ -3106,6 +3648,8 @@ Options:
3106
3648
  --quiet, -q Suppress live tool/message logs (keep iteration headers).
3107
3649
  --list-models List available models for the selected provider.
3108
3650
  --non-interactive Skip all interactive prompts (same as CI mode).
3651
+ --force-unlock Force-release a stale sweep lock file.
3652
+ --allow-destructive-git Allow agents to run git stash/reset/checkout.
3109
3653
  --json Print machine-readable JSON output.
3110
3654
  --cwd <path> Project directory.
3111
3655
  --help Show this command help.
@@ -3115,6 +3659,7 @@ Environment:
3115
3659
  PROMPTS_GPT_MODEL Global model override.
3116
3660
  PROMPTS_GPT_NON_INTERACTIVE=1 Skip interactive prompts.
3117
3661
  NO_COLOR=1 Disable colored output.
3662
+ PROMPTS_GPT_DEBUG=1 Enable debug logging.
3118
3663
 
3119
3664
  Model aliases: ${Object.entries(MODEL_ALIASES).map(([k, v]) => `${k} → ${v}`).join(", ")}
3120
3665
 
@@ -3306,13 +3851,56 @@ function supportsColor() {
3306
3851
  function colorize(text, code) {
3307
3852
  return supportsColor() ? `${code}${text}\x1b[0m` : text;
3308
3853
  }
3854
+ function supportsUnicode() {
3855
+ if (process.platform === "win32") {
3856
+ return Boolean(process.env.WT_SESSION || process.env.TERM_PROGRAM);
3857
+ }
3858
+ return true;
3859
+ }
3860
+ function sym(unicode, ascii) {
3861
+ return supportsUnicode() ? unicode : ascii;
3862
+ }
3863
+ function buildProgressBar(completed, total, width) {
3864
+ const safeTotal = Math.max(total, 1);
3865
+ const safeCompleted = Math.max(0, Math.min(completed, safeTotal));
3866
+ const fraction = safeCompleted / safeTotal;
3867
+ const filled = Math.round(fraction * width);
3868
+ const empty = width - filled;
3869
+ const pct = Math.round(fraction * 100);
3870
+ if (supportsColor()) {
3871
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
3872
+ return `\x1b[32m${bar}\x1b[0m ${pct}% (${safeCompleted}/${safeTotal})`;
3873
+ }
3874
+ const bar = "#".repeat(filled) + "-".repeat(empty);
3875
+ return `[${bar}] ${pct}% (${safeCompleted}/${safeTotal})`;
3876
+ }
3877
+ // B2: Simple spinner for long-running operations
3878
+ function createSpinner(label) {
3879
+ if (!process.stdout.isTTY || process.env.NO_COLOR || process.env.CI) {
3880
+ process.stdout.write(`${label}...\n`);
3881
+ return { stop: (text) => { if (text)
3882
+ console.log(text); } };
3883
+ }
3884
+ const frames = supportsUnicode() ? ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] : ["-", "\\", "|", "/"];
3885
+ let i = 0;
3886
+ const timer = setInterval(() => {
3887
+ process.stdout.write(`\r${frames[i++ % frames.length]} ${label}`);
3888
+ }, 80);
3889
+ return {
3890
+ stop: (finalText) => {
3891
+ clearInterval(timer);
3892
+ process.stdout.write(`\r${" ".repeat(label.length + 4)}\r`);
3893
+ if (finalText)
3894
+ console.log(finalText);
3895
+ },
3896
+ };
3897
+ }
3309
3898
  const viewFileCmd = process.platform === "win32" ? "type" : "cat";
3310
3899
  const listDirCmd = process.platform === "win32" ? "dir" : "ls";
3311
3900
  function resolveCliEntry() {
3312
3901
  if (process.argv[1])
3313
3902
  return process.argv[1];
3314
3903
  try {
3315
- const { fileURLToPath } = require("node:url");
3316
3904
  return fileURLToPath(import.meta.url);
3317
3905
  }
3318
3906
  catch {
@@ -3355,7 +3943,8 @@ function interactiveSelect(prompt, options) {
3355
3943
  let cursor = 0;
3356
3944
  let escBuf = "";
3357
3945
  let filterText = "";
3358
- const termRows = typeof process.stdout.rows === "number" && process.stdout.rows > 2 ? process.stdout.rows : 24;
3946
+ const rawRows = typeof process.stdout.rows === "number" ? process.stdout.rows : 0;
3947
+ const termRows = rawRows > 2 ? rawRows : 24;
3359
3948
  const maxVisible = Math.min(options.length, Math.max(3, termRows - 4));
3360
3949
  const useUnicode = supportsColor();
3361
3950
  const pointer = useUnicode ? "❯" : ">";
@@ -3581,13 +4170,24 @@ async function interactiveInput(prompt, defaultValue) {
3581
4170
  resolved = true;
3582
4171
  resolve(defaultValue || "");
3583
4172
  });
4173
+ rl.on("SIGINT", () => {
4174
+ if (resolved)
4175
+ return;
4176
+ resolved = true;
4177
+ rl.close();
4178
+ rl.removeAllListeners();
4179
+ resolve(defaultValue || "");
4180
+ });
3584
4181
  });
3585
4182
  }
3586
4183
  async function checkPromptsGptSiteReachable(apiUrl) {
3587
4184
  const target = new URL("/", apiUrl).toString();
3588
4185
  const request = (method) => Promise.race([
3589
4186
  globalThis.fetch(target, { method }),
3590
- new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 5000)),
4187
+ new Promise((_, rej) => {
4188
+ const timer = setTimeout(() => rej(new Error("timeout")), 5000);
4189
+ timer.unref?.();
4190
+ }),
3591
4191
  ]);
3592
4192
  let response = await request("HEAD");
3593
4193
  if (response.status === 405) {
@@ -3633,7 +4233,7 @@ function buildGenerateInput(flags) {
3633
4233
  includeWebSearch: Boolean(flags["web-search"]),
3634
4234
  };
3635
4235
  }
3636
- function sanitizeGenerateInput(value) {
4236
+ function redactSecrets(value) {
3637
4237
  return value
3638
4238
  .replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***")
3639
4239
  .replace(/sk-[a-zA-Z0-9]{20,}/g, "sk-***")
@@ -3644,6 +4244,9 @@ function sanitizeGenerateInput(value) {
3644
4244
  .replace(/ANTHROPIC_API_KEY=[^\s]+/gi, "ANTHROPIC_API_KEY=***")
3645
4245
  .replace(/OPENAI_API_KEY=[^\s]+/gi, "OPENAI_API_KEY=***");
3646
4246
  }
4247
+ function sanitizeGenerateInput(value) {
4248
+ return redactSecrets(value);
4249
+ }
3647
4250
  function printDataTransmissionNotice(command, input) {
3648
4251
  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.`);
3649
4252
  console.log("[notice] Do not include PII, secrets, or confidential data in these flags.");
@@ -3680,9 +4283,9 @@ main().catch((error) => {
3680
4283
  }
3681
4284
  const msg = error instanceof Error ? error.message : String(error);
3682
4285
  try {
3683
- console.error(msg.replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***"));
4286
+ console.error(redactSecrets(msg));
3684
4287
  if (process.env.PROMPTS_GPT_DEBUG === "1" && error instanceof Error && error.stack) {
3685
- console.error(error.stack.replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***"));
4288
+ console.error(redactSecrets(error.stack));
3686
4289
  }
3687
4290
  }
3688
4291
  catch { /* stderr closed or broken pipe */ }
@@ -3769,4 +4372,42 @@ function formatTokenUsage(usage) {
3769
4372
  }
3770
4373
  return parts.join(" | ");
3771
4374
  }
4375
+ function findClosestCommand(input) {
4376
+ const lower = input.toLowerCase();
4377
+ // Exact prefix match
4378
+ const prefixMatch = COMMANDS.find((c) => c.startsWith(lower.slice(0, 3)));
4379
+ if (prefixMatch)
4380
+ return prefixMatch;
4381
+ // Levenshtein-like: find command within edit distance 2
4382
+ let bestMatch = null;
4383
+ let bestDist = 3;
4384
+ for (const cmd of COMMANDS) {
4385
+ const dist = editDistance(lower, cmd);
4386
+ if (dist < bestDist) {
4387
+ bestDist = dist;
4388
+ bestMatch = cmd;
4389
+ }
4390
+ }
4391
+ return bestMatch;
4392
+ }
4393
+ function editDistance(a, b) {
4394
+ if (a.length === 0)
4395
+ return b.length;
4396
+ if (b.length === 0)
4397
+ return a.length;
4398
+ const matrix = [];
4399
+ for (let i = 0; i <= a.length; i++) {
4400
+ matrix[i] = [i];
4401
+ }
4402
+ for (let j = 0; j <= b.length; j++) {
4403
+ matrix[0][j] = j;
4404
+ }
4405
+ for (let i = 1; i <= a.length; i++) {
4406
+ for (let j = 1; j <= b.length; j++) {
4407
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
4408
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
4409
+ }
4410
+ }
4411
+ return matrix[a.length][b.length];
4412
+ }
3772
4413
  //# sourceMappingURL=cli.js.map