@prompts-gpt/client 0.2.5 → 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") {
@@ -141,13 +165,42 @@ async function runCommand(command, flags) {
141
165
  console.log("⚠ No provider CLIs detected. Install codex, cursor agent, claude, or copilot.");
142
166
  }
143
167
  const setupAssets = await discoverWorkspaceAssets(cwd);
144
- console.log("Next steps:");
145
- console.log(` prompts-gpt run${result.sourceSummary.defaultPromptFile ? "" : " --prompt-file <path>"}`);
146
- if (setupAssets.sweeps.length > 0) {
147
- 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");
148
203
  }
149
- console.log(" prompts-gpt list — see all runnable assets");
150
- console.log(" prompts-gpt status — check workspace readiness");
151
204
  return;
152
205
  }
153
206
  if (command === "doctor") {
@@ -157,17 +210,38 @@ async function runCommand(command, flags) {
157
210
  console.log(JSON.stringify(report, null, 2));
158
211
  return;
159
212
  }
160
- console.log(`Workspace: ${report.cwd}`);
161
- console.log(`Node: ${report.nodeVersion} | OS: ${report.osPlatform}/${report.osArch} | CPUs: ${report.cpuCount}`);
162
- 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:");
163
221
  for (const provider of report.providers) {
164
- 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})` : ""}`);
165
224
  if (!provider.available) {
166
- console.log(` hint: ${provider.installHint}`);
225
+ console.log(` ${provider.installHint}`);
167
226
  }
168
227
  }
169
- for (const note of report.notes) {
170
- 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}`);
171
245
  }
172
246
  return;
173
247
  }
@@ -205,9 +279,23 @@ async function runCommand(command, flags) {
205
279
  const iterCount = await readSweepIterationsFromFrontmatter(path.resolve(cwd, s.file));
206
280
  const iterHint = iterCount ? ` (${iterCount} iterations)` : "";
207
281
  const iterFlag = iterCount ? ` --iterations ${iterCount}` : "";
208
- 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}`);
209
297
  console.log(` file: ${s.file}`);
210
- console.log(` sweep: prompts-gpt sweep --prompt-file ${s.file}${iterFlag}`);
298
+ console.log(` sweep: prompts-gpt sweep -f ${s.file}${iterFlag}`);
211
299
  }
212
300
  console.log("");
213
301
  }
@@ -250,7 +338,8 @@ async function runCommand(command, flags) {
250
338
  const cmd = action === "sweep" ? "sweep" : "run";
251
339
  console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
252
340
  const { spawnSync: spSync } = await import("node:child_process");
253
- const result = spSync(process.execPath, [process.argv[1], cmd, "-f", file], { stdio: "inherit", cwd });
341
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
342
+ const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
254
343
  process.exitCode = result.status ?? 1;
255
344
  }
256
345
  }
@@ -287,7 +376,22 @@ async function runCommand(command, flags) {
287
376
  }
288
377
  }
289
378
  else {
290
- 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
+ }
291
395
  }
292
396
  console.log("");
293
397
  if (assets.prompts.length > 0 && availableProviders.length > 0) {
@@ -331,6 +435,20 @@ async function runCommand(command, flags) {
331
435
  for (const w of result.warnings) {
332
436
  console.log(` warning: ${w}`);
333
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
+ }
334
452
  if (result.valid && result.errors.length === 0 && result.warnings.length === 0) {
335
453
  console.log(" No issues found.");
336
454
  }
@@ -374,12 +492,32 @@ async function runCommand(command, flags) {
374
492
  const runProviders = await detectProviders(cwd);
375
493
  const runAvailable = runProviders.filter((p) => p.available);
376
494
  if (!getStringFlag(flags, "agent") && isTTYInteractive(flags) && !Boolean(flags.json) && runAvailable.length > 1) {
495
+ const lastRunProvider = await getLastUsedProvider(cwd);
377
496
  const providerOpts = runAvailable.map((p) => ({
378
- label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})`,
497
+ label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})${p.provider === lastRunProvider ? " ★" : ""}`,
379
498
  value: p.provider,
380
499
  }));
381
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
+ }
382
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
+ }
383
521
  }
384
522
  const agent = resolveRunAgent(flags, config.defaultAgent);
385
523
  if (Boolean(flags["dry-run"])) {
@@ -405,6 +543,23 @@ async function runCommand(command, flags) {
405
543
  console.log(` Timeout: ${getStringFlag(flags, "timeout") || config.timeoutSeconds}s`);
406
544
  return;
407
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
+ }
408
563
  const modelFlag = getStringFlag(flags, "model");
409
564
  if (modelFlag && !Boolean(flags.json) && agent !== "router") {
410
565
  const providers = await detectProviders(cwd);
@@ -443,9 +598,29 @@ async function runCommand(command, flags) {
443
598
  console.log(`Summary: ${result.summaryFile}`);
444
599
  console.log(`Log: ${result.logFile}`);
445
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 */ }
446
613
  console.log("");
447
614
  console.log(`View results: cat ${result.summaryFile}`);
448
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
+ }
449
624
  if (result.exitCode !== 0) {
450
625
  const diagnostics = await extractRunDiagnostics(result.logFile, result.provider, result.model);
451
626
  if (diagnostics.length > 0) {
@@ -563,35 +738,55 @@ async function runCommand(command, flags) {
563
738
  const providers = await detectProviders(cwd);
564
739
  const availableProviders = providers.filter((p) => p.available);
565
740
  if (!getStringFlag(flags, "agent") && isTTYInteractive(flags) && !Boolean(flags.json) && availableProviders.length > 1) {
741
+ const lastProvider = await getLastUsedProvider(cwd);
566
742
  const providerOptions = availableProviders.map((p) => ({
567
- label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})`,
743
+ label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})${p.provider === lastProvider ? " ★" : ""}`,
568
744
  value: p.provider,
569
745
  }));
570
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
+ }
571
754
  const picked = await interactiveSelect("Select a provider:", providerOptions);
572
755
  flags.agent = picked;
756
+ if (picked !== "router") {
757
+ await saveLastUsedProvider(cwd, picked);
758
+ }
573
759
  }
574
760
  if (!getStringFlag(flags, "model") && isTTYInteractive(flags) && !Boolean(flags.json)) {
575
761
  const currentAgent = resolveRunAgent(flags, config.defaultAgent);
576
762
  if (currentAgent !== "router") {
577
- const defaultModel = config.modelOverrides[currentAgent]?.trim() || "";
578
- const providerDefault = availableProviders.find((p) => p.provider === currentAgent)?.modelDefault || "";
579
- const hint = defaultModel || providerDefault;
580
- const modelInput = await interactiveInput("Model", hint);
581
- if (modelInput && modelInput !== hint) {
582
- flags.model = modelInput;
763
+ const modelChoices = getModelChoicesForProvider(currentAgent, config);
764
+ if (modelChoices.length > 0) {
765
+ flags.model = await interactiveSelect("Select a model:", modelChoices);
583
766
  }
584
767
  }
585
768
  }
586
769
  if (!getStringFlag(flags, "iterations") && isTTYInteractive(flags) && !Boolean(flags.json)) {
587
770
  const fmIter = await readSweepIterationsFromFrontmatter(path.resolve(cwd, getStringFlag(flags, "prompt-file")));
588
771
  const defaultIter = fmIter ? String(fmIter) : "1";
589
- const iterInput = await interactiveInput("Iterations", defaultIter);
590
- if (iterInput) {
591
- flags.iterations = iterInput;
592
- }
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);
593
780
  }
594
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
+ }
595
790
  if (isTTYInteractive(flags) && !Boolean(flags.json) && !Boolean(flags["dry-run"])) {
596
791
  const resolvedProvider = resolveRunProvider(agent, providers, config.providerOrder);
597
792
  const resolvedModel = getStringFlag(flags, "model")?.trim() || config.modelOverrides[resolvedProvider]?.trim() || availableProviders.find((p) => p.provider === resolvedProvider)?.modelDefault || "auto";
@@ -599,19 +794,32 @@ async function runCommand(command, flags) {
599
794
  const sweepFile = getStringFlag(flags, "prompt-file") || "(default)";
600
795
  const iterTimeout = parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout") ?? 5400;
601
796
  const estMaxMs = parseInt(resolvedIter, 10) * iterTimeout * 1000;
797
+ const maxRetries = parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries") ?? 2;
602
798
  console.log("");
603
- console.log(` Sweep: ${path.basename(sweepFile)}`);
604
- console.log(` Provider: ${resolvedProvider}`);
605
- console.log(` Model: ${resolvedModel}`);
606
- console.log(` Iterations: ${resolvedIter}`);
607
- console.log(` Max time: ~${formatDuration(estMaxMs)}`);
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"));
608
810
  console.log("");
609
- const confirm = await interactiveInput("Run this sweep? [Y/n]", "Y");
610
- if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
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") {
611
817
  console.log("Cancelled.");
612
818
  return;
613
819
  }
614
820
  }
821
+ const quiet = Boolean(flags.quiet);
822
+ const summaryLineCount = parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines") ?? 40;
615
823
  const onProgress = Boolean(flags.json)
616
824
  ? undefined
617
825
  : (event) => {
@@ -620,43 +828,107 @@ async function runCommand(command, flags) {
620
828
  console.log(`Provider: ${report.provider} | Model: ${report.model} | Iterations: ${report.iterations}`);
621
829
  console.log(`Branch: ${report.gitBranch} | Dirty files: ${report.gitDirtyFiles} | Free disk: ${report.diskFreeMb}MB`);
622
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`);
623
843
  for (const w of report.warnings)
624
844
  console.log(`⚠ ${w}`);
625
845
  }
626
846
  else if (event.type === "iteration_start") {
627
- console.log(`\n--- Iteration ${event.iteration}/${event.total} ---`);
628
- if (process.stdout.isTTY) {
629
- const frames = ["", "", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
630
- let fi = 0;
631
- const startMs = Date.now();
632
- const spinner = setInterval(() => {
633
- const elapsed = formatDuration(Date.now() - startMs);
634
- process.stdout.write(`\r${frames[fi++ % frames.length]} Running ${event.provider}... ${elapsed} `);
635
- }, 120);
636
- spinner.unref?.();
637
- const clearSpinner = () => { clearInterval(spinner); process.stdout.write("\r\x1b[K"); };
638
- globalThis.__sweepSpinner = clearSpinner;
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)}`);
639
854
  }
640
855
  }
641
- else if (event.type === "iteration_end") {
642
- const clearFn = globalThis.__sweepSpinner;
643
- if (clearFn) {
644
- clearFn();
645
- delete globalThis.__sweepSpinner;
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}`);
646
873
  }
874
+ }
875
+ else if (event.type === "iteration_end") {
647
876
  const elapsed = formatDuration(event.durationMs);
648
- const icon = event.status === "success" ? "✓" : "✗";
649
- console.log(`${icon} 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}`);
650
888
  }
651
889
  else if (event.type === "attempt_retry") {
652
- 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"));
653
891
  }
654
892
  else if (event.type === "summary") {
655
- const preview = event.lines.slice(0, 5).join("\n");
656
- 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
+ }
657
903
  }
658
904
  else if (event.type === "sweep_end") {
659
- 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
+ }
660
932
  }
661
933
  };
662
934
  const result = await sweepPrompt({
@@ -737,9 +1009,37 @@ async function runCommand(command, flags) {
737
1009
  console.log("Inspect results:");
738
1010
  console.log(` cat ${result.manifestFile}`);
739
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
+ }
740
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
+ }
741
1036
  process.exitCode = 1;
742
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
+ }
743
1043
  return;
744
1044
  }
745
1045
  if (command === "quickstart") {
@@ -750,7 +1050,32 @@ async function runCommand(command, flags) {
750
1050
  console.log("Prompts-GPT Quickstart");
751
1051
  console.log("======================");
752
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
+ }
753
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
+ }
754
1079
  console.log("Step 1: Save your project token");
755
1080
  console.log(" prompts-gpt init --token-prompt");
756
1081
  console.log("");
@@ -812,8 +1137,21 @@ async function runCommand(command, flags) {
812
1137
  }
813
1138
  }
814
1139
  catch (syncErr) {
815
- console.log(`✗ Sync failed: ${syncErr instanceof Error ? syncErr.message : String(syncErr)}`);
816
- 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
+ }
817
1155
  return;
818
1156
  }
819
1157
  const refreshedAssets = await discoverWorkspaceAssets(cwd);
@@ -840,7 +1178,8 @@ async function runCommand(command, flags) {
840
1178
  const cmd = action === "sweep" ? "sweep" : "run";
841
1179
  console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
842
1180
  const { spawnSync: spSync } = await import("node:child_process");
843
- const result = spSync(process.execPath, [process.argv[1], cmd, "-f", file], { stdio: "inherit", cwd });
1181
+ const cliEntry = process.argv[1] || require.resolve("./cli.js");
1182
+ const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
844
1183
  process.exitCode = result.status ?? 1;
845
1184
  return;
846
1185
  }
@@ -877,13 +1216,21 @@ async function runCommand(command, flags) {
877
1216
  const providers = await detectProviders(cwd);
878
1217
  const availableProviders = providers.filter((p) => p.available);
879
1218
  let configError = null;
880
- 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({
881
1228
  cwd,
882
1229
  overwrite: true,
883
1230
  }).catch((err) => {
884
1231
  configError = err.message;
885
1232
  return null;
886
- });
1233
+ }) : null;
887
1234
  if (Boolean(flags.json)) {
888
1235
  console.log(JSON.stringify({
889
1236
  project: { brandName: project.brandName, websiteUrl: project.websiteUrl },
@@ -923,7 +1270,8 @@ async function runCommand(command, flags) {
923
1270
  let token = await resolveTokenInput(flags, { command });
924
1271
  if (!token && process.stdin.isTTY && process.stdout.isTTY) {
925
1272
  console.log("No token flag provided — prompting interactively.");
926
- 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");
927
1275
  token = await readTokenFromPrompt(command);
928
1276
  }
929
1277
  if (!token) {
@@ -963,6 +1311,17 @@ async function runCommand(command, flags) {
963
1311
  });
964
1312
  console.log(`Saved Prompts-GPT credentials to ${result.credentialsPath}`);
965
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
+ }
966
1325
  console.log("");
967
1326
  console.log("Next steps:");
968
1327
  console.log(" prompts-gpt quickstart — interactive setup (recommended)");
@@ -977,19 +1336,29 @@ async function runCommand(command, flags) {
977
1336
  console.log("No prompts matched the query. Try different filters or check the project prompt library.");
978
1337
  return;
979
1338
  }
1339
+ const pullCwd = getResolvedCwd(flags);
1340
+ const pullOutDir = getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR;
980
1341
  const result = await writePromptMarkdownFiles(prompts, {
981
- cwd: getResolvedCwd(flags),
982
- outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
1342
+ cwd: pullCwd,
1343
+ outDir: pullOutDir,
983
1344
  overwrite: Boolean(flags.overwrite),
984
1345
  });
1346
+ const pullManifest = await writePromptManifest(prompts, { cwd: pullCwd, outDir: pullOutDir });
985
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
+ }
986
1355
  if (result.skipped.length) {
987
1356
  console.log(`Skipped ${result.skipped.length} existing file(s). Use --overwrite to replace them.`);
988
1357
  }
989
1358
  if (result.written.length > 0) {
990
1359
  console.log("");
991
1360
  console.log("Run a pulled prompt:");
992
- console.log(` prompts-gpt run --prompt-file ${result.written[0]}`);
1361
+ console.log(` prompts-gpt run -f ${result.written[0]}`);
993
1362
  console.log(" prompts-gpt list — see all available prompts");
994
1363
  }
995
1364
  return;
@@ -1021,7 +1390,9 @@ async function runCommand(command, flags) {
1021
1390
  const target = getStringFlag(flags, "agent") || "all";
1022
1391
  const pulled = await client.pullPrompts().catch((err) => {
1023
1392
  if (!Boolean(flags.json)) {
1024
- 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.");
1025
1396
  }
1026
1397
  return [];
1027
1398
  });
@@ -1043,6 +1414,21 @@ async function runCommand(command, flags) {
1043
1414
  if (result.skipped.length) {
1044
1415
  console.log("Skipped existing generated prompt. Use --overwrite to replace it.");
1045
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
+ }
1046
1432
  return;
1047
1433
  }
1048
1434
  if (command === "sync") {
@@ -1052,7 +1438,18 @@ async function runCommand(command, flags) {
1052
1438
  const prompts = [];
1053
1439
  const goal = getStringFlag(flags, "goal");
1054
1440
  const generatedOnly = Boolean(flags["generated-only"]);
1055
- 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
+ }
1056
1453
  if (goal && !Boolean(flags.json)) {
1057
1454
  printDataTransmissionNotice("sync", { goal, context: getStringFlag(flags, "context"), constraints: getStringFlag(flags, "constraints") });
1058
1455
  }
@@ -1068,9 +1465,13 @@ async function runCommand(command, flags) {
1068
1465
  });
1069
1466
  }
1070
1467
  if (Boolean(flags["dry-run"])) {
1071
- 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}`);
1072
1470
  for (const p of prompts)
1073
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}`);
1074
1475
  return;
1075
1476
  }
1076
1477
  const result = await syncPrompts(prompts, {
@@ -1124,6 +1525,18 @@ async function runCommand(command, flags) {
1124
1525
  console.log(`Personas: ${formatList(project.targetPersonas)}`);
1125
1526
  console.log(`Competitors: ${formatCompetitors(project.competitors)}`);
1126
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
+ }
1127
1540
  return;
1128
1541
  }
1129
1542
  }
@@ -1145,6 +1558,9 @@ async function resolveClientOptions(command, flags) {
1145
1558
  // CI fallback — never read process.env in the importable SDK, only in the CLI entrypoint
1146
1559
  const envToken = process.env.PROMPTS_GPT_TOKEN?.trim();
1147
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
+ }
1148
1564
  return {
1149
1565
  token: envToken,
1150
1566
  apiUrl: explicitApiUrl || credentials?.apiUrl || DEFAULT_PROMPTS_GPT_API_URL,
@@ -1336,6 +1752,8 @@ function getCommandOptions(command) {
1336
1752
  "permission-mode": { type: "string" },
1337
1753
  "dry-run": { type: "boolean" },
1338
1754
  "non-interactive": { type: "boolean" },
1755
+ verbose: { type: "boolean", short: "v" },
1756
+ open: { type: "boolean" },
1339
1757
  };
1340
1758
  }
1341
1759
  if (command === "sweep") {
@@ -1360,6 +1778,9 @@ function getCommandOptions(command) {
1360
1778
  background: { type: "boolean" },
1361
1779
  "permission-mode": { type: "string" },
1362
1780
  "non-interactive": { type: "boolean" },
1781
+ verbose: { type: "boolean", short: "v" },
1782
+ open: { type: "boolean" },
1783
+ quiet: { type: "boolean", short: "q" },
1363
1784
  };
1364
1785
  }
1365
1786
  if (command === "run-batch") {
@@ -1373,6 +1794,7 @@ function getCommandOptions(command) {
1373
1794
  model: { type: "string" },
1374
1795
  timeout: { type: "string" },
1375
1796
  "artifacts-dir": { type: "string" },
1797
+ "non-interactive": { type: "boolean" },
1376
1798
  };
1377
1799
  }
1378
1800
  if (command === "load-config") {
@@ -1512,7 +1934,12 @@ async function readTokenFromStdin() {
1512
1934
  }
1513
1935
  chunks.push(buffer);
1514
1936
  }
1515
- 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;
1516
1943
  }
1517
1944
  function readTokenFromPrompt(command) {
1518
1945
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -1588,6 +2015,7 @@ function readTokenFromPrompt(command) {
1588
2015
  }
1589
2016
  buf.write(char, bufLen, "utf8");
1590
2017
  bufLen += charBytes;
2018
+ stdout.write("*");
1591
2019
  }
1592
2020
  }
1593
2021
  };
@@ -1783,17 +2211,86 @@ function parseNonNegativeIntFlag(raw, flagName) {
1783
2211
  }
1784
2212
  return value;
1785
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
+ }
1786
2282
  function resolveRunAgent(flags, fallback) {
1787
2283
  const raw = getStringFlag(flags, "agent");
1788
2284
  if (!raw)
1789
2285
  return fallback;
1790
- const normalizedRaw = raw.trim().toLowerCase();
1791
- const normalized = normalizeOrchestrationAgent(raw);
2286
+ const sanitized = raw.replace(/[;&|`$(){}!<>]/g, "").trim();
2287
+ const normalizedRaw = sanitized.toLowerCase();
2288
+ const normalized = normalizeOrchestrationAgent(sanitized);
1792
2289
  if (normalized === "router" && normalizedRaw !== "router") {
1793
2290
  const profiles = ORCHESTRATION_AGENT_PROFILES.join(", ");
1794
2291
  const hint = ORCHESTRATION_AGENT_PROFILES.find((p) => p.startsWith(normalizedRaw.slice(0, 3)));
1795
2292
  const suggestion = hint ? ` Did you mean "${hint}"?` : "";
1796
- 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);
1797
2294
  }
1798
2295
  return normalized;
1799
2296
  }
@@ -2270,9 +2767,11 @@ function interactiveSelect(prompt, options) {
2270
2767
  let cursor = 0;
2271
2768
  let escBuf = "";
2272
2769
  const maxVisible = Math.min(options.length, (process.stdout.rows ?? 24) - 3);
2770
+ const useUnicode = supportsColor();
2771
+ const pointer = useUnicode ? "❯" : ">";
2273
2772
  const formatLine = (i, selected) => {
2274
2773
  const num = i < 9 ? `${i + 1}` : " ";
2275
- const prefix = selected ? colorize("❯", "\x1b[36m") : " ";
2774
+ const prefix = selected ? colorize(pointer, "\x1b[36m") : " ";
2276
2775
  const label = selected ? colorize(options[i].label, "\x1b[1m") : options[i].label;
2277
2776
  return ` ${prefix} ${num}. ${label}`;
2278
2777
  };
@@ -2293,7 +2792,8 @@ function interactiveSelect(prompt, options) {
2293
2792
  else {
2294
2793
  stdout.write("\n".repeat(2));
2295
2794
  }
2296
- stdout.write(`${prompt}\n`);
2795
+ const posLabel = options.length > 1 ? ` (${cursor + 1}/${options.length})` : "";
2796
+ stdout.write(`${prompt}${posLabel}\n`);
2297
2797
  const scrollStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), options.length - maxVisible));
2298
2798
  for (let vi = 0; vi < maxVisible; vi++) {
2299
2799
  const i = scrollStart + vi;
@@ -2315,13 +2815,20 @@ function interactiveSelect(prompt, options) {
2315
2815
  }
2316
2816
  stdin.resume();
2317
2817
  const cleanup = () => {
2318
- stdin.removeListener("data", onData);
2818
+ try {
2819
+ stdin.removeListener("data", onData);
2820
+ }
2821
+ catch { /* already destroyed */ }
2319
2822
  try {
2320
2823
  if (typeof stdin.setRawMode === "function")
2321
2824
  stdin.setRawMode(false);
2322
2825
  }
2323
2826
  catch { /* ignore */ }
2324
- stdin.pause();
2827
+ try {
2828
+ if (!stdin.destroyed)
2829
+ stdin.pause();
2830
+ }
2831
+ catch { /* ignore */ }
2325
2832
  };
2326
2833
  const onData = (data) => {
2327
2834
  for (let ci = 0; ci < data.length; ci++) {
@@ -2389,13 +2896,12 @@ function interactiveSelect(prompt, options) {
2389
2896
  stdin.on("data", onData);
2390
2897
  });
2391
2898
  }
2392
- function interactiveInput(prompt, defaultValue) {
2393
- return new Promise(async (resolve, reject) => {
2394
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
2395
- reject(new CliError("Interactive input requires a TTY.", CLI_EXIT_CODES.usage));
2396
- return;
2397
- }
2398
- const { createInterface } = await import("node:readline");
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) => {
2399
2905
  const rl = createInterface({ input: process.stdin, output: process.stdout });
2400
2906
  const suffix = defaultValue ? ` (${defaultValue})` : "";
2401
2907
  rl.question(`${prompt}${suffix}: `, (answer) => {
@@ -2416,7 +2922,9 @@ async function readSweepFrontmatter(filePath) {
2416
2922
  if (val > 0 && val <= 50)
2417
2923
  iterations = val;
2418
2924
  }
2419
- const titleMatch = content.match(/^#\s+(.+)/m);
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);
2420
2928
  if (titleMatch) {
2421
2929
  title = titleMatch[1].trim();
2422
2930
  }
@@ -2441,7 +2949,15 @@ function buildGenerateInput(flags) {
2441
2949
  };
2442
2950
  }
2443
2951
  function sanitizeGenerateInput(value) {
2444
- 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=***");
2445
2961
  }
2446
2962
  function printDataTransmissionNotice(command, input) {
2447
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.`);