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/README.md +52 -0
- package/dist/cli.js +450 -38
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrate.d.ts +57 -0
- package/dist/orchestrate.d.ts.map +1 -1
- package/dist/orchestrate.js +185 -35
- package/dist/orchestrate.js.map +1 -1
- package/dist/sweep.d.ts +2 -0
- package/dist/sweep.d.ts.map +1 -1
- package/dist/sweep.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2056
|
-
console.log(` ${icon} ${step.name} (${step.provider}${
|
|
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
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
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
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
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
|
|
4346
|
-
|
|
4347
|
-
Run interactively (no flags) for a guided
|
|
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
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
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 {
|