prompts-gpt 0.3.2 → 0.3.3

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
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, statSync, readdirSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { parseArgs } from "node:util";
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, normalizeConcreteProvider, 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, captureGitBranch, resolveModelWithWarning, getModelCostTier, estimateTokenCost, } 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, normalizeConcreteProvider, 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, captureGitBranch, resolveModelWithWarning, getModelCostTier, estimateTokenCost, DEFAULT_MODELS, } from "./index.js";
7
7
  const CLI_EXIT_CODES = {
8
8
  success: 0,
9
9
  general: 1,
@@ -1229,6 +1229,14 @@ async function runCommand(command, flags) {
1229
1229
  flags["non-interactive"] = true;
1230
1230
  }
1231
1231
  const sweepPromptFile = getStringFlag(flags, "prompt-file");
1232
+ const sweepFilesFlag = getStringFlag(flags, "sweep-files");
1233
+ const allSweepsFlag = Boolean(flags["all-sweeps"]);
1234
+ if (sweepPromptFile && (sweepFilesFlag || allSweepsFlag)) {
1235
+ throw new CliError("Use either --prompt-file for one sweep, --sweep-files for selected sweeps, or --all-sweeps for every sweep file.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
1236
+ }
1237
+ if (sweepFilesFlag && allSweepsFlag) {
1238
+ throw new CliError("Use either --sweep-files or --all-sweeps, not both.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
1239
+ }
1232
1240
  if (sweepPromptFile && !existsSync(path.resolve(cwd, sweepPromptFile))) {
1233
1241
  throw new CliError(`Sweep file not found: ${sweepPromptFile}\n\nCheck the path and try again. Run \`prompts-gpt list\` to see available sweep files.`, CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
1234
1242
  }
@@ -1255,7 +1263,7 @@ async function runCommand(command, flags) {
1255
1263
  }
1256
1264
  throw new CliError("No supported provider CLI was found on PATH. Install Codex, Cursor Agent, Claude Code, or Copilot CLI, then run `prompts-gpt doctor`.", CLI_EXIT_CODES.validation, { helpCommand: "providers" });
1257
1265
  }
1258
- if (!sweepPromptFile && !Boolean(flags.json)) {
1266
+ if (!sweepPromptFile && !sweepFilesFlag && !allSweepsFlag && !Boolean(flags.json)) {
1259
1267
  const assets = await discoverWorkspaceAssets(cwd);
1260
1268
  if (assets.sweeps.length === 1) {
1261
1269
  const autoFile = assets.sweeps[0].file;
@@ -1656,6 +1664,159 @@ async function runCommand(command, flags) {
1656
1664
  evaluatorModel: getStringFlag(flags, "eval-model") ?? getStringFlag(flags, "evaluator-model"),
1657
1665
  criteria: getStringFlag(flags, "eval-criteria")?.split(",").map((c) => c.trim()),
1658
1666
  } : undefined;
1667
+ const requestedSweepFiles = await resolveSweepFileSelection(cwd, flags);
1668
+ if (requestedSweepFiles.length > 0) {
1669
+ const rawSweepStrategy = getStringFlag(flags, "sweep-strategy") ?? getStringFlag(flags, "files-mode") ?? "sequential";
1670
+ if (rawSweepStrategy !== "sequential" && rawSweepStrategy !== "parallel") {
1671
+ throw new CliError("--sweep-strategy must be either sequential or parallel.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
1672
+ }
1673
+ const rawFileConcurrency = parsePositiveIntFlag(getStringFlag(flags, "file-concurrency"), "file-concurrency");
1674
+ const MAX_FILE_CONCURRENCY = 12;
1675
+ const fileConcurrency = rawSweepStrategy === "parallel"
1676
+ ? Math.min(rawFileConcurrency ?? requestedSweepFiles.length, MAX_FILE_CONCURRENCY, requestedSweepFiles.length)
1677
+ : 1;
1678
+ if (rawFileConcurrency && rawFileConcurrency > MAX_FILE_CONCURRENCY) {
1679
+ console.log(`Warning: --file-concurrency ${rawFileConcurrency} capped to ${MAX_FILE_CONCURRENCY} to prevent resource exhaustion.`);
1680
+ }
1681
+ const rawParallelCount = parsePositiveIntFlag(getStringFlag(flags, "parallel"), "parallel");
1682
+ const MAX_PARALLEL_SWEEPS = 16;
1683
+ const parallelCount = rawParallelCount ? Math.min(rawParallelCount, MAX_PARALLEL_SWEEPS) : undefined;
1684
+ if (rawParallelCount && rawParallelCount > MAX_PARALLEL_SWEEPS) {
1685
+ console.log(`Warning: --parallel ${rawParallelCount} capped to ${MAX_PARALLEL_SWEEPS} to prevent resource exhaustion.`);
1686
+ }
1687
+ const explicitIterations = parsePositiveIntFlag(getStringFlag(flags, "iterations"), "iterations");
1688
+ const runBaseId = getStringFlag(flags, "run-id") || `sweep-plan-${new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14)}`;
1689
+ const planStartMs = Date.now();
1690
+ async function resolveIterationsForFile(file) {
1691
+ if (explicitIterations)
1692
+ return explicitIterations;
1693
+ return await readSweepIterationsFromFrontmatter(path.resolve(cwd, file)) ?? 1;
1694
+ }
1695
+ async function runSweepFile(file, index) {
1696
+ const iterationsForFile = await resolveIterationsForFile(file);
1697
+ const fileSlug = path.basename(file, ".md").replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || `file-${index + 1}`;
1698
+ const fileRunId = `${runBaseId}-${String(index + 1).padStart(2, "0")}-${fileSlug}`;
1699
+ if (parallelCount && parallelCount > 1) {
1700
+ const batchSize = Math.min(parallelCount, iterationsForFile);
1701
+ if (!silent) {
1702
+ console.log(`\n${path.basename(file)}: running ${iterationsForFile} independent iteration${iterationsForFile === 1 ? "" : "s"} with parallelism=${batchSize}`);
1703
+ }
1704
+ const allResults = [];
1705
+ for (let batchStart = 0; batchStart < iterationsForFile; batchStart += batchSize) {
1706
+ const batchEnd = Math.min(batchStart + batchSize, iterationsForFile);
1707
+ const batchPromises = [];
1708
+ for (let i = batchStart; i < batchEnd; i++) {
1709
+ batchPromises.push(sweepPrompt({
1710
+ cwd,
1711
+ promptFile: file,
1712
+ agent,
1713
+ model: getStringFlag(flags, "model"),
1714
+ iterations: 1,
1715
+ iterationTimeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout"),
1716
+ maxRetries: parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries"),
1717
+ artifactsDir: getStringFlag(flags, "artifacts-dir"),
1718
+ runId: `${fileRunId}-iter-${i + 1}`,
1719
+ approveMcps: !Boolean(flags["no-approve-mcps"]),
1720
+ sandboxMode: getStringFlag(flags, "sandbox"),
1721
+ phase: getStringFlag(flags, "phase"),
1722
+ dryRun: Boolean(flags["dry-run"]),
1723
+ maxRunDirs: parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs"),
1724
+ summaryLines: parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines"),
1725
+ background: Boolean(flags.background),
1726
+ permissionMode: getStringFlag(flags, "permission-mode"),
1727
+ evalAfterEachIteration: evalConfig,
1728
+ onProgress,
1729
+ }));
1730
+ }
1731
+ const batchResults = await Promise.allSettled(batchPromises);
1732
+ for (const batchResult of batchResults) {
1733
+ if (batchResult.status === "fulfilled")
1734
+ allResults.push(batchResult.value);
1735
+ else if (!Boolean(flags.json))
1736
+ console.error(`Sweep failed for ${file}: ${batchResult.reason instanceof Error ? batchResult.reason.message : String(batchResult.reason)}`);
1737
+ }
1738
+ }
1739
+ const succeeded = allResults.reduce((sum, result) => sum + result.succeeded, 0);
1740
+ const failed = allResults.reduce((sum, result) => sum + result.failed, 0) + (iterationsForFile - allResults.length);
1741
+ return { file, iterations: iterationsForFile, succeeded, failed, results: allResults };
1742
+ }
1743
+ const result = await sweepPrompt({
1744
+ cwd,
1745
+ promptFile: file,
1746
+ agent,
1747
+ model: getStringFlag(flags, "model"),
1748
+ iterations: iterationsForFile,
1749
+ iterationTimeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout"),
1750
+ maxRetries: parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries"),
1751
+ artifactsDir: getStringFlag(flags, "artifacts-dir"),
1752
+ runId: fileRunId,
1753
+ approveMcps: !Boolean(flags["no-approve-mcps"]),
1754
+ sandboxMode: getStringFlag(flags, "sandbox"),
1755
+ phase: getStringFlag(flags, "phase"),
1756
+ dryRun: Boolean(flags["dry-run"]),
1757
+ maxRunDirs: parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs"),
1758
+ summaryLines: parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines"),
1759
+ background: Boolean(flags.background),
1760
+ permissionMode: getStringFlag(flags, "permission-mode"),
1761
+ evalAfterEachIteration: evalConfig,
1762
+ onProgress,
1763
+ });
1764
+ return { file, iterations: result.totalIterations, succeeded: result.succeeded, failed: result.failed, results: [result] };
1765
+ }
1766
+ if (!Boolean(flags.json)) {
1767
+ console.log(`Running ${requestedSweepFiles.length} sweep file${requestedSweepFiles.length === 1 ? "" : "s"} with ${rawSweepStrategy} file execution${rawSweepStrategy === "parallel" ? ` (file concurrency=${fileConcurrency})` : ""}.`);
1768
+ if (parallelCount && parallelCount > 1) {
1769
+ console.log(`Each file runs its iterations independently with --parallel ${parallelCount}.`);
1770
+ }
1771
+ }
1772
+ const plannedResults = [];
1773
+ if (rawSweepStrategy === "parallel") {
1774
+ for (let batchStart = 0; batchStart < requestedSweepFiles.length; batchStart += fileConcurrency) {
1775
+ const batch = requestedSweepFiles.slice(batchStart, batchStart + fileConcurrency);
1776
+ const settled = await Promise.allSettled(batch.map((file, offset) => runSweepFile(file, batchStart + offset)));
1777
+ for (const result of settled) {
1778
+ if (result.status === "fulfilled")
1779
+ plannedResults.push(result.value);
1780
+ else if (!Boolean(flags.json))
1781
+ console.error(result.reason instanceof Error ? result.reason.message : String(result.reason));
1782
+ }
1783
+ }
1784
+ }
1785
+ else {
1786
+ for (let index = 0; index < requestedSweepFiles.length; index++) {
1787
+ plannedResults.push(await runSweepFile(requestedSweepFiles[index], index));
1788
+ }
1789
+ }
1790
+ const totalSucceeded = plannedResults.reduce((sum, result) => sum + result.succeeded, 0);
1791
+ const totalFailed = plannedResults.reduce((sum, result) => sum + result.failed, 0);
1792
+ const totalIterations = plannedResults.reduce((sum, result) => sum + result.iterations, 0);
1793
+ const totalDuration = Date.now() - planStartMs;
1794
+ if (Boolean(flags.json)) {
1795
+ console.log(JSON.stringify({
1796
+ multiFile: true,
1797
+ sweepStrategy: rawSweepStrategy,
1798
+ fileConcurrency,
1799
+ files: plannedResults,
1800
+ totalFiles: requestedSweepFiles.length,
1801
+ totalIterations,
1802
+ totalSucceeded,
1803
+ totalFailed,
1804
+ totalDurationMs: totalDuration,
1805
+ }, null, 2));
1806
+ }
1807
+ else {
1808
+ console.log(`\nSweep plan complete: ${totalSucceeded}/${totalIterations} iterations succeeded across ${requestedSweepFiles.length} file${requestedSweepFiles.length === 1 ? "" : "s"}.`);
1809
+ if (totalFailed > 0)
1810
+ console.log(`Failed iterations: ${totalFailed}`);
1811
+ console.log(`Total wall-clock time: ${formatDuration(totalDuration)}`);
1812
+ for (const result of plannedResults) {
1813
+ console.log(` ${result.file}: ${result.succeeded}/${result.iterations} succeeded`);
1814
+ }
1815
+ }
1816
+ if (totalFailed > 0 || plannedResults.length < requestedSweepFiles.length)
1817
+ process.exitCode = 1;
1818
+ return;
1819
+ }
1659
1820
  // Parallel sweep — run N iterations concurrently
1660
1821
  const rawParallelCount = parsePositiveIntFlag(getStringFlag(flags, "parallel"), "parallel");
1661
1822
  const MAX_PARALLEL_SWEEPS = 16;
@@ -1912,12 +2073,34 @@ async function runCommand(command, flags) {
1912
2073
  }
1913
2074
  else if (event.type === "provider_start") {
1914
2075
  const stepLabel = event.step ? ` [${event.step}]` : "";
1915
- console.log(` ${orchUc ? "" : ">"} ${event.provider}${stepLabel} starting...`);
2076
+ const modelLabel = event.model ? colorize(` (${event.model})`, "\x1b[2m") : "";
2077
+ const promptLabel = event.promptFile ? colorize(` ${event.promptFile}`, "\x1b[2m") : "";
2078
+ console.log(` ${orchUc ? "▶" : ">"} ${event.provider}${stepLabel}${modelLabel} starting...${promptLabel}`);
2079
+ }
2080
+ else if (event.type === "step_context") {
2081
+ console.log(` ${colorize(orchUc ? "↳" : "->", "\x1b[36m")} Receiving ${formatContextSize(event.contextChars)} context from "${event.source}"`);
2082
+ if (event.contextPreview) {
2083
+ console.log(` ${colorize(truncatePreview(event.contextPreview, 120), "\x1b[2m")}`);
2084
+ }
1916
2085
  }
1917
2086
  else if (event.type === "provider_end") {
1918
2087
  const icon = event.exitCode === 0 ? colorize(orchUc ? "✓" : "OK", "\x1b[32m") : colorize(orchUc ? "✗" : "FAIL", "\x1b[31m");
1919
2088
  console.log(` ${icon} ${event.provider} finished (${formatDuration(event.durationMs)}, exit=${event.exitCode})`);
1920
2089
  }
2090
+ else if (event.type === "step_summary") {
2091
+ if (event.filesChanged.length > 0) {
2092
+ console.log(` ${colorize(orchUc ? "📝" : "[files]", "\x1b[36m")} ${event.filesChanged.length} file(s) changed:`);
2093
+ for (const f of event.filesChanged.slice(0, 10)) {
2094
+ console.log(` ${colorize(f, "\x1b[2m")}`);
2095
+ }
2096
+ if (event.filesChanged.length > 10) {
2097
+ console.log(` ${colorize(`...and ${event.filesChanged.length - 10} more`, "\x1b[2m")}`);
2098
+ }
2099
+ }
2100
+ if (event.outputChars > 0) {
2101
+ console.log(` ${colorize(orchUc ? "💬" : "[out]", "\x1b[36m")} Output: ${formatContextSize(event.outputChars)} ${colorize(`→ passed to next step`, "\x1b[2m")}`);
2102
+ }
2103
+ }
1921
2104
  else if (event.type === "eval_start") {
1922
2105
  console.log(` ${orchUc && supportsUnicode() ? "🧪" : "[eval]"} Evaluating with ${event.evaluator}...`);
1923
2106
  }
@@ -1927,8 +2110,12 @@ async function runCommand(command, flags) {
1927
2110
  else if (event.type === "warning") {
1928
2111
  console.log(` ${colorize(orchUc ? "⚠" : "[warn]", "\x1b[33m")} ${event.text}`);
1929
2112
  }
2113
+ else if (event.type === "info") {
2114
+ console.log(` ${colorize(orchUc ? "ℹ" : "[info]", "\x1b[36m")} ${event.text}`);
2115
+ }
1930
2116
  else if (event.type === "orchestrate_end") {
1931
- console.log(`\n${colorize(orchBar, "\x1b[35m")} Done in ${formatDuration(event.totalDurationMs)}${event.winner ? ` | winner=${event.winner}` : ""} ${colorize(orchBar, "\x1b[35m")}\n`);
2117
+ const artLabel = event.artifactsDir ? ` | logs: ${path.relative(cwd, event.artifactsDir)}` : "";
2118
+ console.log(`\n${colorize(orchBar, "\x1b[35m")} Done in ${formatDuration(event.totalDurationMs)}${event.winner ? ` | winner=${event.winner}` : ""}${artLabel} ${colorize(orchBar, "\x1b[35m")}\n`);
1932
2119
  }
1933
2120
  };
1934
2121
  if (mode === "parallel") {
@@ -1991,20 +2178,25 @@ async function runCommand(command, flags) {
1991
2178
  throw new CliError(`Pipeline steps file not found: ${resolvedStepsFile}`, CLI_EXIT_CODES.usage);
1992
2179
  }
1993
2180
  let stepsJson;
2181
+ let pipelineDefaults = {};
1994
2182
  try {
1995
2183
  const parsed = JSON.parse(readFileSync(resolvedStepsFile, "utf8"));
1996
2184
  if (Array.isArray(parsed)) {
1997
2185
  stepsJson = parsed;
1998
2186
  }
1999
2187
  else if (parsed && typeof parsed === "object" && Array.isArray(parsed.steps)) {
2000
- stepsJson = parsed.steps;
2188
+ const wrapper = parsed;
2189
+ stepsJson = wrapper.steps;
2190
+ if (wrapper.defaults && typeof wrapper.defaults === "object") {
2191
+ pipelineDefaults = wrapper.defaults;
2192
+ }
2001
2193
  }
2002
2194
  else {
2003
- throw new Error("Pipeline steps config must be either an array of steps or an object with a steps array.");
2195
+ throw new Error("Pipeline config must be an array of steps or an object with { defaults?, steps }.");
2004
2196
  }
2005
2197
  }
2006
2198
  catch (error) {
2007
- if (error instanceof Error && error.message.includes("Pipeline steps config must be either")) {
2199
+ if (error instanceof Error && error.message.includes("Pipeline config must be")) {
2008
2200
  throw new CliError(error.message, CLI_EXIT_CODES.validation);
2009
2201
  }
2010
2202
  throw new CliError(`Pipeline steps file contains invalid JSON: ${resolvedStepsFile}`, CLI_EXIT_CODES.validation);
@@ -2012,8 +2204,11 @@ async function runCommand(command, flags) {
2012
2204
  if (!Array.isArray(stepsJson) || stepsJson.length === 0) {
2013
2205
  throw new CliError("Pipeline steps file must contain a non-empty JSON array", CLI_EXIT_CODES.usage);
2014
2206
  }
2015
- const defaultPipelineAgent = resolveRunAgent(flags, config.defaultAgent);
2016
- const globalPipelineModel = getStringFlag(flags, "model");
2207
+ const defaultPipelineAgent = pipelineDefaults.agent?.trim() || resolveRunAgent(flags, config.defaultAgent);
2208
+ const globalPipelineModel = getStringFlag(flags, "model") || pipelineDefaults.model?.trim();
2209
+ const globalPipelineTimeout = pipelineDefaults.timeout;
2210
+ const globalPipelineRetries = pipelineDefaults.retries;
2211
+ const globalPipelineSandbox = pipelineDefaults.sandboxMode;
2017
2212
  for (const step of stepsJson) {
2018
2213
  if (!step || typeof step.name !== "string" || typeof step.promptFile !== "string") {
2019
2214
  throw new CliError(`Pipeline step entries must include name and promptFile fields: ${resolvedStepsFile}`, CLI_EXIT_CODES.validation);
@@ -2030,6 +2225,15 @@ async function runCommand(command, flags) {
2030
2225
  if (!step.model && globalPipelineModel) {
2031
2226
  step.model = globalPipelineModel;
2032
2227
  }
2228
+ if (!step.timeout && globalPipelineTimeout) {
2229
+ step.timeout = globalPipelineTimeout;
2230
+ }
2231
+ if (typeof step.retries !== "number" && typeof globalPipelineRetries === "number") {
2232
+ step.retries = globalPipelineRetries;
2233
+ }
2234
+ if (!step.sandboxMode && globalPipelineSandbox) {
2235
+ step.sandboxMode = globalPipelineSandbox;
2236
+ }
2033
2237
  if (step.model) {
2034
2238
  const stepProvider = resolveRunProvider(normalizeOrchestrationAgent(step.agent), providers, config.providerOrder);
2035
2239
  const modelCheck = validateModelForProvider(step.model, stepProvider);
@@ -2038,11 +2242,40 @@ async function runCommand(command, flags) {
2038
2242
  }
2039
2243
  }
2040
2244
  }
2245
+ if (isTTYInteractive(flags) && !jsonOutput && !globalPipelineModel) {
2246
+ for (const step of stepsJson) {
2247
+ if (!step.model) {
2248
+ const stepProvider = resolveRunProvider(normalizeOrchestrationAgent(step.agent ?? defaultPipelineAgent), providers, config.providerOrder);
2249
+ const modelChoices = getModelChoicesForProvider(stepProvider, config);
2250
+ const defaultModel = config.modelOverrides[stepProvider] || DEFAULT_MODELS[stepProvider] || "auto";
2251
+ modelChoices.unshift({ label: `${defaultModel} (default)`, value: defaultModel });
2252
+ const pickedModel = await interactiveSelect(`Model for step "${step.name}" (${stepProvider}):`, modelChoices.filter((c, i, arr) => arr.findIndex((x) => x.value === c.value) === i));
2253
+ if (pickedModel === "__custom__") {
2254
+ step.model = await interactiveInput("Enter custom model name", "");
2255
+ }
2256
+ else {
2257
+ step.model = pickedModel;
2258
+ }
2259
+ }
2260
+ }
2261
+ }
2262
+ if (!jsonOutput && !dryRun) {
2263
+ console.log(`\n Pipeline Plan (${stepsJson.length} steps):`);
2264
+ for (let idx = 0; idx < stepsJson.length; idx++) {
2265
+ const s = stepsJson[idx];
2266
+ const sp = resolveRunProvider(normalizeOrchestrationAgent(s.agent ?? defaultPipelineAgent), providers, config.providerOrder);
2267
+ const sm = s.model?.trim() || config.modelOverrides[sp] || DEFAULT_MODELS[sp] || "auto";
2268
+ console.log(` ${idx + 1}. ${s.name} ${colorize(`(${sp} / ${sm})`, "\x1b[2m")}`);
2269
+ }
2270
+ console.log();
2271
+ }
2272
+ const continueOnError = Boolean(flags["continue-on-error"]);
2041
2273
  const result = await orchestratePipeline({
2042
2274
  cwd,
2043
2275
  steps: stepsJson,
2044
2276
  timeoutSeconds,
2045
2277
  dryRun,
2278
+ continueOnError,
2046
2279
  onProgress,
2047
2280
  });
2048
2281
  if (jsonOutput) {
@@ -2052,12 +2285,25 @@ async function runCommand(command, flags) {
2052
2285
  console.log("Pipeline Results:");
2053
2286
  for (const step of result.steps) {
2054
2287
  const icon = step.result.exitCode === 0 ? sym("✓", "+") : sym("✗", "x");
2055
- const model = step.result.model && step.result.model !== "dry-run" ? ` model=${step.result.model}` : "";
2056
- console.log(` ${icon} ${step.name} (${step.provider}${model}) — ${formatDuration(step.durationMs)}`);
2288
+ const modelStr = step.result.model && step.result.model !== "dry-run" ? colorize(` model=${step.result.model}`, "\x1b[2m") : "";
2289
+ console.log(` ${icon} ${step.name} (${step.provider}${modelStr}) — ${formatDuration(step.durationMs)}`);
2290
+ if (step.result.summaryFile && existsSync(step.result.summaryFile)) {
2291
+ const summary = readFileSync(step.result.summaryFile, "utf8").trim();
2292
+ if (summary) {
2293
+ const summaryPreview = truncatePreview(summary.replace(/\n/g, " "), 150);
2294
+ console.log(` ${colorize(summaryPreview, "\x1b[2m")}`);
2295
+ }
2296
+ }
2057
2297
  }
2058
2298
  const successCount = result.steps.filter((s) => s.result.exitCode === 0).length;
2059
2299
  const failCount = result.steps.filter((s) => s.result.exitCode !== 0).length;
2060
2300
  console.log(`\n Total: ${formatDuration(result.totalDurationMs)} | ${successCount} passed${failCount > 0 ? `, ${failCount} failed` : ""}`);
2301
+ if (result.artifactsDir) {
2302
+ const relativeArtifacts = path.relative(cwd, result.artifactsDir) || result.artifactsDir;
2303
+ console.log(` Artifacts: ${colorize(relativeArtifacts, "\x1b[2m")}`);
2304
+ console.log(`\n Inspect step details:`);
2305
+ console.log(` prompts-gpt diff ${relativeArtifacts}`);
2306
+ }
2061
2307
  }
2062
2308
  return;
2063
2309
  }
@@ -2704,20 +2950,90 @@ async function runCommand(command, flags) {
2704
2950
  if (existsSync(filePath) && !Boolean(flags.overwrite)) {
2705
2951
  throw new CliError(`Orchestration file already exists: ${filePath}\nUse --overwrite to replace it.`, CLI_EXIT_CODES.validation);
2706
2952
  }
2707
- const content = isPipelineTemplate
2708
- ? PIPELINE_TEMPLATE
2709
- .replace(/\{goal\}/g, goal.trim())
2710
- : ORCHESTRATION_TEMPLATE
2953
+ if (isPipelineTemplate) {
2954
+ const stepsDir = path.join(orchDir, "pipeline-steps");
2955
+ await fsMkdir(stepsDir, { recursive: true });
2956
+ let numSteps = 3;
2957
+ let stepDefs = [
2958
+ { name: "research", description: `Research and analyze requirements for: ${goal.trim()}`, agent: "codex" },
2959
+ { name: "implement", description: `Implement the solution based on the research analysis`, agent: "claude" },
2960
+ { name: "test-and-review", description: `Test, review, and validate the implementation`, agent: "cursor" },
2961
+ ];
2962
+ if (isTTYInteractive()) {
2963
+ const stepCountChoice = await interactiveSelect("How many pipeline steps?", [
2964
+ { label: "2 steps (research + implement)", value: "2" },
2965
+ { label: "3 steps (research + implement + review)", value: "3" },
2966
+ { label: "4 steps (research + implement + test + review)", value: "4" },
2967
+ ]);
2968
+ numSteps = parseInt(stepCountChoice, 10);
2969
+ if (numSteps === 2) {
2970
+ stepDefs = [
2971
+ { name: "research", description: `Research and analyze: ${goal.trim()}`, agent: "codex" },
2972
+ { name: "implement", description: `Implement based on research`, agent: "claude" },
2973
+ ];
2974
+ }
2975
+ else if (numSteps === 4) {
2976
+ stepDefs = [
2977
+ { name: "research", description: `Research and analyze: ${goal.trim()}`, agent: "codex" },
2978
+ { name: "implement", description: `Implement the solution`, agent: "claude" },
2979
+ { name: "test", description: `Run tests and fix failures`, agent: "cursor" },
2980
+ { name: "review", description: `Code review and final validation`, agent: "codex" },
2981
+ ];
2982
+ }
2983
+ for (let si = 0; si < stepDefs.length; si++) {
2984
+ const s = stepDefs[si];
2985
+ const customName = await interactiveInput(`Step ${si + 1} name`, s.name);
2986
+ s.name = customName || s.name;
2987
+ const agentPick = await interactiveSelect(`Step ${si + 1} ("${s.name}") agent:`, [
2988
+ { label: "codex — OpenAI Codex CLI", value: "codex" },
2989
+ { label: "claude — Anthropic Claude Code", value: "claude" },
2990
+ { label: "cursor — Cursor Agent", value: "cursor" },
2991
+ { label: "copilot — GitHub Copilot", value: "copilot" },
2992
+ ]);
2993
+ s.agent = agentPick;
2994
+ }
2995
+ }
2996
+ const stepFilePaths = [];
2997
+ for (const s of stepDefs) {
2998
+ const stepFileName = `${s.name.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase()}.md`;
2999
+ const stepFilePath = path.join(stepsDir, stepFileName);
3000
+ stepFilePaths.push(path.relative(cwd, stepFilePath));
3001
+ if (!existsSync(stepFilePath)) {
3002
+ const stepContent = buildPipelineStepPrompt(s.name, s.description, stepDefs.indexOf(s), stepDefs.length);
3003
+ await fsWriteFile(stepFilePath, stepContent);
3004
+ }
3005
+ }
3006
+ const pipelineConfig = {
3007
+ defaults: { timeout: 900 },
3008
+ steps: stepDefs.map((s, si) => ({
3009
+ name: s.name,
3010
+ promptFile: stepFilePaths[si],
3011
+ agent: s.agent,
3012
+ })),
3013
+ };
3014
+ await fsWriteFile(filePath, JSON.stringify(pipelineConfig, null, 2) + "\n");
3015
+ console.log(`${sym("\u2713", "+")} Created pipeline orchestration:`);
3016
+ console.log(` Config: ${path.relative(cwd, filePath)}`);
3017
+ for (const fp of stepFilePaths) {
3018
+ console.log(` Prompt: ${fp}`);
3019
+ }
3020
+ console.log(`\nRun it with:`);
3021
+ console.log(` prompts-gpt orchestrate --mode pipeline --steps ${path.relative(cwd, filePath)}`);
3022
+ console.log(`\nEdit the step prompt files to customize behavior, then run the pipeline.`);
3023
+ }
3024
+ else {
3025
+ const content = ORCHESTRATION_TEMPLATE
2711
3026
  .replace(/\{title\}/g, title.trim())
2712
3027
  .replace(/\{description\}/g, (description || title).trim())
2713
3028
  .replace('"parallel"', `"${mode}"`)
2714
3029
  .replace("{goal}", goal.trim())
2715
3030
  .replace("{step1}", `Analyze the requirements for: ${goal.trim()}`)
2716
3031
  .replace("{step2}", `Implement the solution based on the analysis`);
2717
- await fsWriteFile(filePath, content);
2718
- console.log(`${sym("\u2713", "+")} Created orchestration file: ${filePath}`);
2719
- console.log(`\nRun it with:`);
2720
- console.log(` prompts-gpt orchestrate --mode ${mode}${isPipelineTemplate ? ` --steps ${path.relative(cwd, filePath)}` : ` -f ${path.relative(cwd, filePath)}`}`);
3032
+ await fsWriteFile(filePath, content);
3033
+ console.log(`${sym("\u2713", "+")} Created orchestration file: ${filePath}`);
3034
+ console.log(`\nRun it with:`);
3035
+ console.log(` prompts-gpt orchestrate --mode ${mode} -f ${path.relative(cwd, filePath)}`);
3036
+ }
2721
3037
  return;
2722
3038
  }
2723
3039
  if (command === "sync") {
@@ -3124,6 +3440,11 @@ function getCommandOptions(command) {
3124
3440
  quiet: { type: "boolean", short: "q" },
3125
3441
  silent: { type: "boolean" },
3126
3442
  parallel: { type: "string" },
3443
+ "all-sweeps": { type: "boolean" },
3444
+ "sweep-files": { type: "string" },
3445
+ "sweep-strategy": { type: "string" },
3446
+ "files-mode": { type: "string" },
3447
+ "file-concurrency": { type: "string" },
3127
3448
  eval: { type: "boolean" },
3128
3449
  "eval-criteria": { type: "string" },
3129
3450
  "eval-agent": { type: "string" },
@@ -3151,6 +3472,7 @@ function getCommandOptions(command) {
3151
3472
  model: { type: "string" },
3152
3473
  timeout: { type: "string" },
3153
3474
  "dry-run": { type: "boolean" },
3475
+ "continue-on-error": { type: "boolean" },
3154
3476
  "non-interactive": { type: "boolean" },
3155
3477
  "list-models": { type: "boolean" },
3156
3478
  };
@@ -3604,6 +3926,19 @@ function formatDuration(ms) {
3604
3926
  return `${minutes}m ${seconds}s`;
3605
3927
  return `${seconds}s`;
3606
3928
  }
3929
+ function formatContextSize(chars) {
3930
+ if (chars < 1000)
3931
+ return `${chars} chars`;
3932
+ if (chars < 100_000)
3933
+ return `${(chars / 1000).toFixed(1)}k chars`;
3934
+ return `${(chars / 1_000_000).toFixed(2)}M chars`;
3935
+ }
3936
+ function truncatePreview(text, maxLen) {
3937
+ const clean = text.replace(/\s+/g, " ").trim();
3938
+ if (clean.length <= maxLen)
3939
+ return clean;
3940
+ return clean.slice(0, maxLen - 3) + "...";
3941
+ }
3607
3942
  function formatList(values) {
3608
3943
  if (!Array.isArray(values) || values.length === 0)
3609
3944
  return "None";
@@ -4342,9 +4677,14 @@ Usage:
4342
4677
  prompts-gpt generate-orchestration [--title <text>] [--goal <text>] [--description <text>] [--mode <parallel|pipeline|eval>] [--overwrite] [--cwd <path>]
4343
4678
 
4344
4679
  Why use it:
4345
- Scaffolds a new orchestration template locally for multi-agent workflows.
4346
- Pipeline mode emits a runnable JSON steps file; parallel and eval modes emit Markdown prompt templates.
4347
- Run interactively (no flags) for a guided experience, or pass all flags for scripting.
4680
+ Scaffolds a new orchestration locally. Pipeline mode creates a JSON config AND step prompt files
4681
+ so you can immediately run the pipeline. Parallel and eval modes create Markdown prompt templates.
4682
+ Run interactively (no flags) for a guided wizard that lets you pick steps, agents, and models.
4683
+
4684
+ What it creates (pipeline mode):
4685
+ .prompts-gpt/orchestrations/<name>.json — Pipeline config with steps
4686
+ .prompts-gpt/orchestrations/pipeline-steps/ — Step prompt Markdown files
4687
+ research.md, implement.md, test-and-review.md
4348
4688
 
4349
4689
  Options:
4350
4690
  --title <text> Required. The orchestration title (used as filename).
@@ -4356,8 +4696,15 @@ Options:
4356
4696
  --help Show this command help.
4357
4697
 
4358
4698
  Examples:
4699
+ # Interactive wizard — pick steps, agents, models
4359
4700
  prompts-gpt generate-orchestration
4701
+
4702
+ # Non-interactive pipeline scaffolding
4360
4703
  prompts-gpt generate-orchestration --title "Full Stack Feature" --goal "Add user auth" --mode pipeline
4704
+
4705
+ # Then customize the generated step prompts and run
4706
+ # Edit: .prompts-gpt/orchestrations/pipeline-steps/*.md
4707
+ prompts-gpt orchestrate --mode pipeline --steps .prompts-gpt/orchestrations/full-stack-feature.json
4361
4708
  `;
4362
4709
  }
4363
4710
  if (command === "completions") {
@@ -4485,6 +4832,8 @@ Examples:
4485
4832
 
4486
4833
  Usage:
4487
4834
  prompts-gpt sweep [-f <path>] [-n <count>] [--agent <name>] [--model <name>] [--dry-run]
4835
+ prompts-gpt sweep --all-sweeps [--sweep-strategy sequential|parallel] [-n <count>]
4836
+ prompts-gpt sweep --sweep-files <a,b,c> [--sweep-strategy sequential|parallel] [-n <count>]
4488
4837
 
4489
4838
  Why use it:
4490
4839
  Runs the same prompt N times, feeding each iteration's summary into the next.
@@ -4496,6 +4845,11 @@ Why use it:
4496
4845
  Options:
4497
4846
  -f, --prompt-file <path> Prompt file to sweep. Auto-detects local sweeps if omitted.
4498
4847
  -n, --iterations <n> Number of iterations. Interactive default: 1.
4848
+ --all-sweeps Run every file in .prompts-gpt/sweeps.
4849
+ --sweep-files <list> Comma-separated sweep paths, filenames, or sweep names.
4850
+ --sweep-strategy <mode> File execution mode: sequential or parallel. Default: sequential.
4851
+ --files-mode <mode> Alias for --sweep-strategy.
4852
+ --file-concurrency <n> Max sweep files to run at once in parallel mode.
4499
4853
  --agent <name> Orchestration profile. Default from config or router.
4500
4854
  --model <name> Model override for the selected provider.
4501
4855
  --iteration-timeout <secs> Timeout per iteration in seconds. Default: 5400 (90 min)
@@ -4510,7 +4864,7 @@ Options:
4510
4864
  --max-run-dirs <n> Max artifact directories to keep. Default: 20
4511
4865
  --summary-lines <n> Lines of summary to extract per iteration. Default: 40
4512
4866
  --dry-run Preview what the sweep would do without executing.
4513
- --parallel <n> Run iterations in parallel batches (experimental).
4867
+ --parallel <n> Run iterations in parallel batches inside each selected file (experimental).
4514
4868
  --quiet, -q Suppress live tool/message logs (keep iteration headers).
4515
4869
  --silent Suppress all output except errors and final result.
4516
4870
  --list-models List available models for the selected provider.
@@ -4534,6 +4888,11 @@ Examples:
4534
4888
  prompts-gpt sweep # interactive picker
4535
4889
  prompts-gpt sweep -f .prompts-gpt/sweeps/sdk-hardening.md # explicit file
4536
4890
  prompts-gpt sweep -f .prompts-gpt/sweeps/design.md -n 5 # 5 iterations
4891
+ prompts-gpt sweep --all-sweeps --sweep-strategy sequential # every sweep file, one after another
4892
+ prompts-gpt sweep --all-sweeps --sweep-strategy parallel -n 2 # every sweep file in parallel, 2 iterations each
4893
+ prompts-gpt sweep --sweep-files design,research --sweep-strategy parallel -n 2
4894
+ prompts-gpt sweep --sweep-files .prompts-gpt/sweeps/a.md,.prompts-gpt/sweeps/b.md --sweep-strategy sequential
4895
+ prompts-gpt sweep -f .prompts-gpt/sweeps/design.md -n 6 --parallel 3
4537
4896
  prompts-gpt sweep --model claude-sonnet-4-6 # specify model
4538
4897
  prompts-gpt sweep --quiet # suppress live logs
4539
4898
  prompts-gpt sweep --list-models --agent codex # see codex models
@@ -4677,7 +5036,7 @@ Options:
4677
5036
  return `prompts-gpt orchestrate
4678
5037
 
4679
5038
  Usage:
4680
- prompts-gpt orchestrate --mode <parallel|pipeline|eval> [--prompt-file <path>] [--providers <list>] [--agent <name>] [--model <name>] [--timeout <secs>] [--evaluator <name>] [--evaluator-model <name>] [--criteria <list>] [--eval-criteria <list>] [--steps <file>] [--dry-run] [--json] [--cwd <path>]
5039
+ prompts-gpt orchestrate --mode <parallel|pipeline|eval> [--prompt-file <path>] [--providers <list>] [--agent <name>] [--model <name>] [--timeout <secs>] [--evaluator <name>] [--evaluator-model <name>] [--criteria <list>] [--eval-criteria <list>] [--steps <file>] [--continue-on-error] [--dry-run] [--json] [--cwd <path>]
4681
5040
 
4682
5041
  Why use it:
4683
5042
  Run multi-agent orchestration — execute the same prompt across multiple providers in parallel,
@@ -4700,6 +5059,7 @@ Options:
4700
5059
  --eval-criteria <list> Alias for --criteria in eval mode and sweep self-evaluation.
4701
5060
  --steps <file> JSON step file for pipeline mode.
4702
5061
  --timeout <secs> Timeout per provider in seconds.
5062
+ --continue-on-error Continue running remaining pipeline steps even if one fails.
4703
5063
  --dry-run Preview what the orchestration would do.
4704
5064
  --list-models Show model choices for the resolved provider(s).
4705
5065
  --non-interactive Skip all interactive prompts.
@@ -5284,6 +5644,48 @@ async function readSweepFrontmatter(filePath) {
5284
5644
  async function readSweepIterationsFromFrontmatter(filePath) {
5285
5645
  return (await readSweepFrontmatter(filePath)).iterations;
5286
5646
  }
5647
+ async function resolveSweepFileSelection(cwd, flags) {
5648
+ const allSweeps = Boolean(flags["all-sweeps"]);
5649
+ const rawSelected = getStringFlag(flags, "sweep-files");
5650
+ if (!allSweeps && !rawSelected)
5651
+ return [];
5652
+ const assets = await discoverWorkspaceAssets(cwd);
5653
+ if (assets.sweeps.length === 0) {
5654
+ throw new CliError("No sweep files found in .prompts-gpt/sweeps.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
5655
+ }
5656
+ if (allSweeps) {
5657
+ return assets.sweeps.map((sweep) => sweep.file);
5658
+ }
5659
+ const requested = rawSelected
5660
+ .split(",")
5661
+ .map((item) => item.trim())
5662
+ .filter(Boolean);
5663
+ if (requested.length === 0) {
5664
+ throw new CliError("--sweep-files must include at least one file path, filename, or sweep name.", CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
5665
+ }
5666
+ const resolved = [];
5667
+ const seen = new Set();
5668
+ for (const item of requested) {
5669
+ const directPath = path.resolve(cwd, item);
5670
+ let matched = existsSync(directPath) ? item : undefined;
5671
+ if (!matched) {
5672
+ const normalizedItem = item.replace(/\.md$/i, "");
5673
+ const asset = assets.sweeps.find((sweep) => {
5674
+ const basename = path.basename(sweep.file, ".md");
5675
+ return sweep.file === item || basename === item || basename === normalizedItem || sweep.name === item || sweep.name === normalizedItem;
5676
+ });
5677
+ matched = asset?.file;
5678
+ }
5679
+ if (!matched || !existsSync(path.resolve(cwd, matched))) {
5680
+ throw new CliError(`Sweep file not found: ${item}\n\nUse --sweep-files with paths, filenames, or names from \`prompts-gpt list\`.`, CLI_EXIT_CODES.validation, { helpCommand: "sweep" });
5681
+ }
5682
+ if (!seen.has(matched)) {
5683
+ seen.add(matched);
5684
+ resolved.push(matched);
5685
+ }
5686
+ }
5687
+ return resolved;
5688
+ }
5287
5689
  function buildGenerateInput(flags) {
5288
5690
  return {
5289
5691
  goal: getStringFlag(flags, "goal") || "",
@@ -5347,7 +5749,7 @@ function printCompletionScript(shell) {
5347
5749
  const commands = [...COMMANDS].sort().join(" ");
5348
5750
  const sweepFlags = "--prompt-file --agent --model --iterations --timeout --retries --json --verbose --dry-run --non-interactive --cwd --help --quiet --list-models";
5349
5751
  const runFlags = "--prompt-file --agent --model --timeout --verbose --dry-run --json --non-interactive --cwd --help --list-models";
5350
- const orchFlags = "--mode --prompt-file --providers --agent --model --timeout --evaluator --evaluator-model --criteria --eval-criteria --steps --dry-run --json --non-interactive --cwd --help --list-models";
5752
+ const orchFlags = "--mode --prompt-file --providers --agent --model --timeout --evaluator --evaluator-model --criteria --eval-criteria --steps --continue-on-error --dry-run --json --non-interactive --cwd --help --list-models";
5351
5753
  if (shell === "zsh") {
5352
5754
  console.log(`# Zsh completions for prompts-gpt
5353
5755
  # Add to ~/.zshrc: eval "$(prompts-gpt completions zsh)"
@@ -5455,19 +5857,29 @@ mode: "parallel"
5455
5857
  - Quality: Is the output production-ready?
5456
5858
  - Correctness: Are there any bugs or issues?
5457
5859
  `;
5458
- const PIPELINE_TEMPLATE = `[
5459
- {
5460
- "name": "analyze",
5461
- "promptFile": ".prompts-gpt/review.md",
5462
- "agent": "codex"
5463
- },
5464
- {
5465
- "name": "implement",
5466
- "promptFile": ".prompts-gpt/review.md",
5467
- "agent": "claude"
5468
- }
5469
- ]
5470
- `;
5860
+ function buildPipelineStepPrompt(stepName, description, stepIndex, totalSteps) {
5861
+ const isFirstStep = stepIndex === 0;
5862
+ const isLastStep = stepIndex === totalSteps - 1;
5863
+ const sections = [
5864
+ `You are the **{{ step_name }}** step ({{ step_index }}/{{ total_steps }}) of a multi-step pipeline.`,
5865
+ "",
5866
+ `## Goal`,
5867
+ "",
5868
+ description,
5869
+ "",
5870
+ ];
5871
+ if (isFirstStep) {
5872
+ sections.push("## Instructions", "", "1. Analyze the codebase structure and understand the existing patterns", "2. Identify the best approach for the task", "3. Produce a structured plan with specific file paths and changes", "", "## Output Format", "", "Provide a structured plan with:", "- Architecture decisions", "- Implementation checklist (numbered, with file paths)", "- Risk assessment", "- Existing patterns to follow", "");
5873
+ }
5874
+ else if (isLastStep) {
5875
+ sections.push("## Instructions", "", "1. Review the output from the previous step(s)", "2. Validate correctness and completeness", "3. Run any available tests: `npm run lint && npm run build`", "4. Fix any issues found", "", "## Output Format", "", "Provide:", "- Files reviewed (pass/fail per file)", "- Tests run and results", "- Issues found and fixed", "- Final status", "");
5876
+ }
5877
+ else {
5878
+ sections.push("## Instructions", "", "1. Read the plan/context from the previous step", "2. Implement each item systematically", "3. Follow existing patterns identified in the research", "4. Handle error states, edge cases, and validation", "", "## Output Format", "", "List every file created or modified with a one-line description.", "");
5879
+ }
5880
+ sections.push("{{ previous_output }}", "");
5881
+ return sections.join("\n");
5882
+ }
5471
5883
  main().catch((error) => {
5472
5884
  if (error instanceof CliError) {
5473
5885
  try {