prompts-gpt 0.2.8
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/LICENSE +75 -0
- package/README.md +202 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3650 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1119 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +225 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1490 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sweep.d.ts +180 -0
- package/dist/sweep.d.ts.map +1 -0
- package/dist/sweep.js +765 -0
- package/dist/sweep.js.map +1 -0
- package/package.json +66 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3650 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { hasTokenUsage, DEFAULT_PROMPTS_GPT_API_URL, DEFAULT_PROMPTS_GPT_OUT_DIR, DEFAULT_RUN_CONFIG_PATH, PROMPTS_GPT_CREDENTIALS_FILE, PromptsGptApiError, PromptsGptClient, doctor, initRunConfig, loadRunConfig, normalizeOrchestrationAgent, ORCHESTRATION_AGENT_PROFILES, runBatch, runPrompt, resolveRunProvider, warnModelProviderMismatch, sweepPrompt, validateRunConfig, discoverWorkspaceAssets, SUPPORTED_AGENT_TARGETS, detectProviders, loadLocalCredentials, saveLocalCredentials, syncPrompts, writeAgentFiles, writePromptManifest, writePromptMarkdownFiles, ensureGitignoreEntry, } from "./index.js";
|
|
6
|
+
const CLI_EXIT_CODES = {
|
|
7
|
+
success: 0,
|
|
8
|
+
general: 1,
|
|
9
|
+
auth: 2,
|
|
10
|
+
validation: 3,
|
|
11
|
+
rateLimit: 4,
|
|
12
|
+
usage: 64,
|
|
13
|
+
};
|
|
14
|
+
const VALID_TOOLS = ["Codex", "Claude Code", "Cursor", "GitHub Copilot", "ChatGPT", "Gemini", "Perplexity", "Grok", "DeepSeek", "Claude"];
|
|
15
|
+
const COMMANDS = ["setup", "init", "project", "pull", "generate", "sync", "run", "run-batch", "sweep", "list", "status", "validate", "providers", "models", "sync-models", "doctor", "load-config", "quickstart", "version", "help"];
|
|
16
|
+
const MAX_STDIN_TOKEN_LENGTH = 4_096;
|
|
17
|
+
class CliError extends Error {
|
|
18
|
+
exitCode;
|
|
19
|
+
helpCommand;
|
|
20
|
+
constructor(message, exitCode, options) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "CliError";
|
|
23
|
+
this.exitCode = exitCode;
|
|
24
|
+
this.helpCommand = options?.helpCommand;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function main() {
|
|
28
|
+
const argv = process.argv.slice(2);
|
|
29
|
+
if (argv.length === 0) {
|
|
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
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const first = argv[0];
|
|
59
|
+
if (first === "--help") {
|
|
60
|
+
printHelp();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (first === "--version") {
|
|
64
|
+
await printVersion();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (first === "help") {
|
|
68
|
+
handleHelpCommand(argv.slice(1));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (first === "version") {
|
|
72
|
+
if (argv.length > 1) {
|
|
73
|
+
throw new CliError("The `version` command does not accept additional arguments.", CLI_EXIT_CODES.usage, {
|
|
74
|
+
helpCommand: "version",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
await printVersion();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const command = asCommandName(first);
|
|
81
|
+
if (!command) {
|
|
82
|
+
throw new CliError(`Unknown command: ${first}.`, CLI_EXIT_CODES.usage);
|
|
83
|
+
}
|
|
84
|
+
if (!isRunnableCommand(command)) {
|
|
85
|
+
throw new CliError(`Unknown command: ${first}.`, CLI_EXIT_CODES.usage, {
|
|
86
|
+
helpCommand: "help",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const flags = parseCommandFlags(command, argv.slice(1));
|
|
90
|
+
if (Boolean(flags.help)) {
|
|
91
|
+
printHelp(command);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await runCommand(command, flags);
|
|
95
|
+
}
|
|
96
|
+
async function runCommand(command, flags) {
|
|
97
|
+
if (command === "providers") {
|
|
98
|
+
const cwd = getResolvedCwd(flags);
|
|
99
|
+
const providers = await detectProviders(cwd);
|
|
100
|
+
if (Boolean(flags.json)) {
|
|
101
|
+
console.log(JSON.stringify({ cwd, providers }, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log(`Workspace: ${cwd}\n`);
|
|
105
|
+
for (const provider of providers) {
|
|
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}` : ""}`);
|
|
108
|
+
if (!provider.available) {
|
|
109
|
+
console.log(` → ${provider.installHint}`);
|
|
110
|
+
}
|
|
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
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (command === "setup") {
|
|
122
|
+
const cwd = getResolvedCwd(flags);
|
|
123
|
+
const providerOrder = getStringFlag(flags, "provider-order")
|
|
124
|
+
?.split(",")
|
|
125
|
+
.map((item) => item.trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
validateProviderOrderFlag(providerOrder);
|
|
128
|
+
const promptFile = getStringFlag(flags, "prompt-file");
|
|
129
|
+
const promptFiles = getStringFlag(flags, "prompt-files")
|
|
130
|
+
?.split(",")
|
|
131
|
+
.map((item) => item.trim())
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
const result = await initRunConfig({
|
|
134
|
+
cwd,
|
|
135
|
+
promptFile,
|
|
136
|
+
manifestPath: getStringFlag(flags, "manifest"),
|
|
137
|
+
promptFiles,
|
|
138
|
+
promptDir: getStringFlag(flags, "prompt-dir"),
|
|
139
|
+
defaultAgent: resolveRunAgent(flags, "router"),
|
|
140
|
+
providerOrder,
|
|
141
|
+
artifactsDir: getStringFlag(flags, "artifacts-dir"),
|
|
142
|
+
timeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "timeout"), "timeout"),
|
|
143
|
+
retryCount: parseNonNegativeIntFlag(getStringFlag(flags, "retry-count"), "retry-count"),
|
|
144
|
+
disallowDestructiveGit: Boolean(flags["allow-destructive-git"]) ? false : true,
|
|
145
|
+
modelOverrides: {
|
|
146
|
+
codex: getStringFlag(flags, "codex-model"),
|
|
147
|
+
cursor: getStringFlag(flags, "cursor-model"),
|
|
148
|
+
claude: getStringFlag(flags, "claude-model"),
|
|
149
|
+
copilot: getStringFlag(flags, "copilot-model"),
|
|
150
|
+
},
|
|
151
|
+
overwrite: Boolean(flags.overwrite),
|
|
152
|
+
});
|
|
153
|
+
if (Boolean(flags.json)) {
|
|
154
|
+
console.log(JSON.stringify(result, null, 2));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
console.log(`Config: ${result.configPath}`);
|
|
158
|
+
console.log(`Agent: ${result.config.defaultAgent ?? "router"} | Providers: ${(result.config.providerOrder ?? []).join(", ")}`);
|
|
159
|
+
if (result.sourceSummary.defaultPromptFile) {
|
|
160
|
+
console.log(`Default prompt: ${result.sourceSummary.defaultPromptFile}`);
|
|
161
|
+
}
|
|
162
|
+
const availableCount = result.providerSummary.filter((p) => p.available).length;
|
|
163
|
+
console.log(`Providers: ${availableCount}/${result.providerSummary.length} available`);
|
|
164
|
+
if (availableCount === 0) {
|
|
165
|
+
console.log("⚠ No provider CLIs detected. Install codex, cursor agent, claude, or copilot.");
|
|
166
|
+
}
|
|
167
|
+
const setupAssets = await discoverWorkspaceAssets(cwd);
|
|
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");
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (command === "models") {
|
|
207
|
+
const cwd = getResolvedCwd(flags);
|
|
208
|
+
const config = await loadRunConfig(cwd);
|
|
209
|
+
const providerFilter = getStringFlag(flags, "provider") || getStringFlag(flags, "agent");
|
|
210
|
+
const providers = providerFilter ? [providerFilter] : Object.keys(PROVIDER_MODELS);
|
|
211
|
+
if (Boolean(flags.json)) {
|
|
212
|
+
const result = {};
|
|
213
|
+
for (const p of providers) {
|
|
214
|
+
const entries = PROVIDER_MODELS[p] ?? [];
|
|
215
|
+
const custom = config.customModels?.[p] ?? [];
|
|
216
|
+
result[p] = [
|
|
217
|
+
...entries.map((e) => ({ value: e.value, tier: e.tier })),
|
|
218
|
+
...custom.map((c) => ({ value: c, tier: "custom" })),
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
console.log(JSON.stringify(result, null, 2));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
for (const p of providers) {
|
|
225
|
+
const entries = PROVIDER_MODELS[p] ?? [];
|
|
226
|
+
const custom = config.customModels?.[p] ?? [];
|
|
227
|
+
const cachedModels = loadCachedModelRegistry(p, cwd);
|
|
228
|
+
const allNames = [...entries.map((e) => e.value), ...custom, ...cachedModels];
|
|
229
|
+
const nameW = Math.max(30, ...allNames.map((n) => n.length + 2));
|
|
230
|
+
const uc = supportsColor();
|
|
231
|
+
console.log(colorize(`\n${p}:`, "\x1b[1;36m"));
|
|
232
|
+
for (const e of entries) {
|
|
233
|
+
const tierBadge = e.tier === "frontier"
|
|
234
|
+
? (uc ? " ⭐" : " [F]")
|
|
235
|
+
: e.tier === "budget" ? (uc ? " 💰" : " [$]") : e.tier === "fast" ? (uc ? " ⚡" : " [>]") : "";
|
|
236
|
+
console.log(` ${e.value.padEnd(nameW)} ${e.tier.padEnd(10)}${tierBadge}`);
|
|
237
|
+
}
|
|
238
|
+
if (custom.length > 0) {
|
|
239
|
+
console.log(colorize(" Custom:", "\x1b[33m"));
|
|
240
|
+
for (const c of custom)
|
|
241
|
+
console.log(` ${c.padEnd(nameW)} custom`);
|
|
242
|
+
}
|
|
243
|
+
if (cachedModels.length > 0) {
|
|
244
|
+
console.log(colorize(" Synced:", "\x1b[32m"));
|
|
245
|
+
for (const c of cachedModels)
|
|
246
|
+
console.log(` ${c.padEnd(nameW)} synced`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const addFlag = getStringFlag(flags, "add");
|
|
250
|
+
if (addFlag && providerFilter) {
|
|
251
|
+
const modelNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
252
|
+
const addModels = addFlag.split(",").map((m) => m.trim()).filter(Boolean);
|
|
253
|
+
const invalidModels = addModels.filter((m) => !modelNamePattern.test(m));
|
|
254
|
+
if (invalidModels.length > 0) {
|
|
255
|
+
console.error(`Invalid model name(s): ${invalidModels.join(", ")}. Model names must be alphanumeric with dots, hyphens, or underscores.`);
|
|
256
|
+
process.exitCode = CLI_EXIT_CODES.validation;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (addModels.length > 0) {
|
|
260
|
+
const configPath = config.configPath;
|
|
261
|
+
try {
|
|
262
|
+
const { readFile: fsRead, writeFile: fsWrite } = await import("node:fs/promises");
|
|
263
|
+
const rawConfig = JSON.parse(await fsRead(configPath, "utf8"));
|
|
264
|
+
const existing = rawConfig.customModels ?? {};
|
|
265
|
+
const providerList = existing[providerFilter] ?? [];
|
|
266
|
+
for (const m of addModels) {
|
|
267
|
+
if (!providerList.includes(m))
|
|
268
|
+
providerList.push(m);
|
|
269
|
+
}
|
|
270
|
+
existing[providerFilter] = providerList;
|
|
271
|
+
rawConfig.customModels = existing;
|
|
272
|
+
await fsWrite(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
273
|
+
console.log(`\nAdded ${addModels.join(", ")} to ${providerFilter} custom models.`);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
console.log(`Failed to update config: ${err instanceof Error ? err.message : String(err)}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const removeFlag = getStringFlag(flags, "remove");
|
|
281
|
+
if (removeFlag && providerFilter) {
|
|
282
|
+
const removeModels = removeFlag.split(",").map((m) => m.trim()).filter(Boolean);
|
|
283
|
+
if (removeModels.length > 0) {
|
|
284
|
+
const configPath = config.configPath;
|
|
285
|
+
try {
|
|
286
|
+
const { readFile: fsRead, writeFile: fsWrite } = await import("node:fs/promises");
|
|
287
|
+
const rawConfig = JSON.parse(await fsRead(configPath, "utf8"));
|
|
288
|
+
const existing = rawConfig.customModels ?? {};
|
|
289
|
+
const currentList = existing[providerFilter] ?? [];
|
|
290
|
+
const notFound = removeModels.filter((m) => !currentList.includes(m));
|
|
291
|
+
if (notFound.length > 0) {
|
|
292
|
+
console.log(`Note: ${notFound.join(", ")} not found in ${providerFilter} custom models.`);
|
|
293
|
+
}
|
|
294
|
+
const providerList = currentList.filter((m) => !removeModels.includes(m));
|
|
295
|
+
existing[providerFilter] = providerList;
|
|
296
|
+
rawConfig.customModels = existing;
|
|
297
|
+
await fsWrite(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
298
|
+
const removed = removeModels.filter((m) => !notFound.includes(m));
|
|
299
|
+
if (removed.length > 0) {
|
|
300
|
+
console.log(`\nRemoved ${removed.join(", ")} from ${providerFilter} custom models.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
console.error(`Failed to update config: ${err instanceof Error ? err.message : String(err)}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
console.log("\nAliases:");
|
|
309
|
+
for (const [alias, target] of Object.entries(MODEL_ALIASES)) {
|
|
310
|
+
console.log(` ${alias.padEnd(15)} → ${target}`);
|
|
311
|
+
}
|
|
312
|
+
console.log("\nManage models:");
|
|
313
|
+
console.log(" prompts-gpt models --provider codex --add my-custom-model");
|
|
314
|
+
console.log(" prompts-gpt models --provider codex --remove gpt-4.1");
|
|
315
|
+
console.log(" prompts-gpt sync-models");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (command === "sync-models") {
|
|
319
|
+
const cwd = getResolvedCwd(flags);
|
|
320
|
+
try {
|
|
321
|
+
const client = await createClientForCommand("sync-models", flags);
|
|
322
|
+
console.log("Fetching model registry from prompts-gpt.com...");
|
|
323
|
+
let models;
|
|
324
|
+
try {
|
|
325
|
+
models = await client.fetchModels();
|
|
326
|
+
}
|
|
327
|
+
catch (fetchErr) {
|
|
328
|
+
if (fetchErr instanceof PromptsGptApiError && fetchErr.status === 404) {
|
|
329
|
+
console.log("Model registry API not yet available on your server. Using built-in model lists.");
|
|
330
|
+
console.log("This feature requires a server update. Check https://prompts-gpt.com/docs/sdk/models");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
throw fetchErr;
|
|
334
|
+
}
|
|
335
|
+
if (Object.keys(models).length === 0) {
|
|
336
|
+
console.log("No models returned from API. Using built-in model lists.");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const { writeFile: fsWrite, mkdir: fsMkdir } = await import("node:fs/promises");
|
|
340
|
+
const outDir = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR);
|
|
341
|
+
await fsMkdir(outDir, { recursive: true });
|
|
342
|
+
const registry = {};
|
|
343
|
+
for (const [provider, modelList] of Object.entries(models)) {
|
|
344
|
+
registry[provider] = { models: modelList, syncedAt: new Date().toISOString() };
|
|
345
|
+
console.log(` ${provider}: ${modelList.length} models`);
|
|
346
|
+
}
|
|
347
|
+
await fsWrite(path.resolve(outDir, ".models.json"), JSON.stringify(registry, null, 2) + "\n");
|
|
348
|
+
await ensureGitignoreEntry(cwd, `${DEFAULT_PROMPTS_GPT_OUT_DIR}/.models.json`);
|
|
349
|
+
console.log(`Model registry saved to ${DEFAULT_PROMPTS_GPT_OUT_DIR}/.models.json`);
|
|
350
|
+
console.log("\nSchedule regular syncs:");
|
|
351
|
+
console.log(` crontab: 0 6 * * * cd ${cwd} && npx prompts-gpt sync-models`);
|
|
352
|
+
console.log(" GitHub Actions: schedule a weekly workflow calling 'npx prompts-gpt sync-models'");
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
if (err instanceof PromptsGptApiError) {
|
|
356
|
+
if (err.status === 401 || err.code === "AUTH_ERROR") {
|
|
357
|
+
throw new CliError(`Authentication failed: ${err.message}. Run 'prompts-gpt init' to set up credentials.`, CLI_EXIT_CODES.auth);
|
|
358
|
+
}
|
|
359
|
+
console.error(`API error: ${err.message}`);
|
|
360
|
+
console.log("Using built-in model lists.");
|
|
361
|
+
}
|
|
362
|
+
else if (err instanceof CliError) {
|
|
363
|
+
throw err;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.error(`Failed to sync models: ${err instanceof Error ? err.message : String(err)}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (command === "doctor") {
|
|
372
|
+
const cwd = getResolvedCwd(flags);
|
|
373
|
+
const report = await doctor(cwd);
|
|
374
|
+
if (Boolean(flags.json)) {
|
|
375
|
+
console.log(JSON.stringify(report, null, 2));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
console.log("Prompts-GPT Doctor");
|
|
379
|
+
console.log("==================\n");
|
|
380
|
+
console.log("System:");
|
|
381
|
+
console.log(` ${report.nodeVersion.startsWith("v18") || report.nodeVersion.startsWith("v2") ? "✓" : "⚠"} Node: ${report.nodeVersion}`);
|
|
382
|
+
console.log(` ✓ OS: ${report.osPlatform}/${report.osArch} | CPUs: ${report.cpuCount}`);
|
|
383
|
+
console.log(` ${report.configFound ? "✓" : "✗"} Config: ${report.configFound ? report.configPath : "not found — run \`prompts-gpt setup\`"}`);
|
|
384
|
+
console.log(`\n Workspace: ${report.cwd}\n`);
|
|
385
|
+
console.log("Providers:");
|
|
386
|
+
for (const provider of report.providers) {
|
|
387
|
+
const icon = provider.available ? "✓" : "✗";
|
|
388
|
+
console.log(` ${icon} ${provider.provider}: ${provider.available ? `${provider.bin}` : "not found"}${provider.version ? ` (${provider.version})` : ""}`);
|
|
389
|
+
if (!provider.available) {
|
|
390
|
+
console.log(` → ${provider.installHint}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const configNotes = report.notes.filter((n) => n.includes("config") || n.includes("Config") || n.includes("provider order") || n.includes("Router"));
|
|
394
|
+
const sweepNotes = report.notes.filter((n) => n.includes("weep") || n.includes("lock"));
|
|
395
|
+
const otherNotes = report.notes.filter((n) => !configNotes.includes(n) && !sweepNotes.includes(n));
|
|
396
|
+
if (configNotes.length > 0) {
|
|
397
|
+
console.log("\nConfiguration:");
|
|
398
|
+
for (const n of configNotes)
|
|
399
|
+
console.log(` ℹ ${n}`);
|
|
400
|
+
}
|
|
401
|
+
if (sweepNotes.length > 0) {
|
|
402
|
+
console.log("\nSweep:");
|
|
403
|
+
for (const n of sweepNotes)
|
|
404
|
+
console.log(` ℹ ${n}`);
|
|
405
|
+
}
|
|
406
|
+
if (otherNotes.length > 0) {
|
|
407
|
+
console.log("\nNotes:");
|
|
408
|
+
for (const n of otherNotes)
|
|
409
|
+
console.log(` ℹ ${n}`);
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const config = await loadRunConfig(cwd);
|
|
413
|
+
const modelIssues = [];
|
|
414
|
+
for (const [provider, model] of Object.entries(config.modelOverrides)) {
|
|
415
|
+
if (model) {
|
|
416
|
+
const check = validateModelForProvider(model, provider);
|
|
417
|
+
if (!check.valid) {
|
|
418
|
+
modelIssues.push(`modelOverrides.${provider} = "${model}" is not a known model${check.suggestion ? ` (did you mean "${check.suggestion}"?)` : ""}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (modelIssues.length > 0) {
|
|
423
|
+
console.log("\nModel Overrides:");
|
|
424
|
+
for (const issue of modelIssues)
|
|
425
|
+
console.log(` ⚠ ${issue}`);
|
|
426
|
+
}
|
|
427
|
+
const cachedModelsPath = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".models.json");
|
|
428
|
+
if (existsSync(cachedModelsPath)) {
|
|
429
|
+
try {
|
|
430
|
+
const data = JSON.parse(readFileSync(cachedModelsPath, "utf8"));
|
|
431
|
+
const firstKey = Object.keys(data)[0];
|
|
432
|
+
const syncedAt = firstKey ? data[firstKey]?.syncedAt : undefined;
|
|
433
|
+
console.log(`\nModel Registry: synced${syncedAt ? ` (${syncedAt})` : ""}`);
|
|
434
|
+
console.log(" Run 'prompts-gpt sync-models' to refresh");
|
|
435
|
+
}
|
|
436
|
+
catch { /* skip */ }
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
console.log("\nModel Registry: not synced");
|
|
440
|
+
console.log(" Run 'prompts-gpt sync-models' to fetch latest models");
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch { /* skip config check */ }
|
|
444
|
+
console.log("\nAPI Connectivity:");
|
|
445
|
+
if (typeof globalThis.fetch !== "function") {
|
|
446
|
+
console.log(" ⚠ fetch is not available (Node.js 18.18+ required). Skipping API check.");
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
try {
|
|
450
|
+
const healthRes = await Promise.race([
|
|
451
|
+
globalThis.fetch(`${DEFAULT_PROMPTS_GPT_API_URL}/api/health`, { method: "HEAD" }),
|
|
452
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 5000)),
|
|
453
|
+
]);
|
|
454
|
+
if (healthRes instanceof Response && healthRes.ok) {
|
|
455
|
+
console.log(` ✓ prompts-gpt.com reachable`);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
console.log(` ⚠ prompts-gpt.com responded with status ${healthRes.status}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch (err) {
|
|
462
|
+
const msg = err instanceof Error ? err.message : "";
|
|
463
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND")) {
|
|
464
|
+
console.log(" ✗ prompts-gpt.com unreachable — check network or proxy settings (HTTPS_PROXY)");
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
console.log(" ✗ prompts-gpt.com unreachable — check network connection");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
const pkgData = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
473
|
+
console.log(`\nSDK Version: ${pkgData.version ?? "unknown"}`);
|
|
474
|
+
}
|
|
475
|
+
catch { /* skip */ }
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (command === "list") {
|
|
479
|
+
const cwd = getResolvedCwd(flags);
|
|
480
|
+
const assets = await discoverWorkspaceAssets(cwd);
|
|
481
|
+
if (Boolean(flags.json)) {
|
|
482
|
+
console.log(JSON.stringify(assets, null, 2));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
console.log(`Workspace: ${cwd}`);
|
|
486
|
+
console.log("");
|
|
487
|
+
const providers = await detectProviders(cwd);
|
|
488
|
+
const availableProviderNames = providers.filter((p) => p.available).map((p) => p.provider);
|
|
489
|
+
if (assets.prompts.length > 0) {
|
|
490
|
+
console.log(`Prompt packs (${assets.prompts.length}):`);
|
|
491
|
+
for (const p of assets.prompts) {
|
|
492
|
+
const providerHint = availableProviderNames.length > 0
|
|
493
|
+
? ` (run with: ${availableProviderNames[0]})`
|
|
494
|
+
: "";
|
|
495
|
+
console.log(` ${p.slug} — ${p.title} [${p.source}]${providerHint}`);
|
|
496
|
+
console.log(` file: .prompts-gpt/${p.file}`);
|
|
497
|
+
console.log(` run: prompts-gpt run --prompt-file .prompts-gpt/${p.file}`);
|
|
498
|
+
}
|
|
499
|
+
console.log("");
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
console.log("Prompt packs: none found");
|
|
503
|
+
console.log(" Run `prompts-gpt sync` to pull prompt packs, or `prompts-gpt pull` to download them.");
|
|
504
|
+
console.log("");
|
|
505
|
+
}
|
|
506
|
+
if (assets.sweeps.length > 0) {
|
|
507
|
+
console.log(`Sweep prompts (${assets.sweeps.length}):`);
|
|
508
|
+
const listConfig = await loadRunConfig(cwd).catch(() => null);
|
|
509
|
+
const artifactsDir = path.resolve(cwd, listConfig?.artifactsDir ?? ".scripts/runs");
|
|
510
|
+
let lastRunDate = null;
|
|
511
|
+
try {
|
|
512
|
+
if (existsSync(artifactsDir)) {
|
|
513
|
+
const { readdir: rd, stat: fsStat } = await import("node:fs/promises");
|
|
514
|
+
const dirs = await rd(artifactsDir);
|
|
515
|
+
const sweepDirs = dirs.filter((d) => d.includes("sweep")).sort().reverse();
|
|
516
|
+
if (sweepDirs[0]) {
|
|
517
|
+
const s2 = await fsStat(path.join(artifactsDir, sweepDirs[0]));
|
|
518
|
+
lastRunDate = s2.mtime.toLocaleDateString();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch { /* skip */ }
|
|
523
|
+
for (const s of assets.sweeps) {
|
|
524
|
+
const iterCount = await readSweepIterationsFromFrontmatter(path.resolve(cwd, s.file));
|
|
525
|
+
const iterHint = iterCount ? ` (${iterCount} iterations)` : "";
|
|
526
|
+
const iterFlag = iterCount ? ` --iterations ${iterCount}` : "";
|
|
527
|
+
const lastRun = lastRunDate ? ` | last run: ${lastRunDate}` : "";
|
|
528
|
+
console.log(` ${s.name}${iterHint}${lastRun}`);
|
|
529
|
+
console.log(` file: ${s.file}`);
|
|
530
|
+
console.log(` sweep: prompts-gpt sweep -f ${s.file}${iterFlag}`);
|
|
531
|
+
}
|
|
532
|
+
console.log("");
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
console.log("Sweep prompts: none found");
|
|
536
|
+
console.log(" Create .prompts-gpt/sweeps/<name>.md to add sweep prompts.");
|
|
537
|
+
console.log("");
|
|
538
|
+
}
|
|
539
|
+
if (assets.agents.length > 0) {
|
|
540
|
+
console.log(`Agent integrations (${assets.agents.length}):`);
|
|
541
|
+
for (const a of assets.agents) {
|
|
542
|
+
console.log(` ${a.target} — ${a.file}`);
|
|
543
|
+
}
|
|
544
|
+
console.log("");
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
console.log("Agent integrations: none synced");
|
|
548
|
+
console.log(" Run `prompts-gpt sync` to generate agent files.");
|
|
549
|
+
console.log("");
|
|
550
|
+
}
|
|
551
|
+
console.log(`Config: ${assets.configFound ? "found" : "not found — run `prompts-gpt setup`"}`);
|
|
552
|
+
console.log(`Manifest: ${assets.manifestFound ? "found" : "not found — run `prompts-gpt sync`"}`);
|
|
553
|
+
console.log(`Credentials: ${assets.credentialsFound ? "found" : "not found — run `prompts-gpt init --token <token>`"}`);
|
|
554
|
+
if (isTTYInteractive() && availableProviderNames.length > 0) {
|
|
555
|
+
const runnableOptions = [];
|
|
556
|
+
for (const p of assets.prompts) {
|
|
557
|
+
runnableOptions.push({ label: `run: ${p.slug} — ${p.title}`, value: `run:.prompts-gpt/${p.file}` });
|
|
558
|
+
}
|
|
559
|
+
for (const s of assets.sweeps) {
|
|
560
|
+
runnableOptions.push({ label: `sweep: ${s.name}`, value: `sweep:${s.file}` });
|
|
561
|
+
}
|
|
562
|
+
if (runnableOptions.length > 1) {
|
|
563
|
+
console.log("");
|
|
564
|
+
const picked = await interactiveSelect("Run something now?", [
|
|
565
|
+
...runnableOptions,
|
|
566
|
+
{ label: "(exit)", value: "exit" },
|
|
567
|
+
]);
|
|
568
|
+
if (picked !== "exit") {
|
|
569
|
+
const [action, file] = picked.split(":", 2);
|
|
570
|
+
const cmd = action === "sweep" ? "sweep" : "run";
|
|
571
|
+
console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
|
|
572
|
+
const { spawnSync: spSync } = await import("node:child_process");
|
|
573
|
+
const cliEntry = process.argv[1] || require.resolve("./cli.js");
|
|
574
|
+
const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
|
|
575
|
+
process.exitCode = result.status ?? 1;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (command === "status") {
|
|
582
|
+
const cwd = getResolvedCwd(flags);
|
|
583
|
+
const assets = await discoverWorkspaceAssets(cwd);
|
|
584
|
+
const providers = await detectProviders(cwd);
|
|
585
|
+
const availableProviders = providers.filter((p) => p.available);
|
|
586
|
+
if (Boolean(flags.json)) {
|
|
587
|
+
console.log(JSON.stringify({ cwd, assets, providers }, null, 2));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
console.log(`Workspace: ${cwd}`);
|
|
591
|
+
console.log("");
|
|
592
|
+
console.log("Readiness:");
|
|
593
|
+
console.log(` Credentials: ${assets.credentialsFound ? "✓" : "✗ — run \`prompts-gpt init --token <token>\`"}`);
|
|
594
|
+
console.log(` Config: ${assets.configFound ? "✓" : "✗ — run \`prompts-gpt setup\`"}`);
|
|
595
|
+
console.log(` Manifest: ${assets.manifestFound ? "✓" : "✗ — run \`prompts-gpt sync\`"}`);
|
|
596
|
+
const cloudCount = assets.prompts.filter((p) => p.source === "library" || p.source === "generated").length;
|
|
597
|
+
const localCount = assets.prompts.filter((p) => p.source === "local").length;
|
|
598
|
+
const promptSummary = assets.prompts.length > 0
|
|
599
|
+
? `✓ (${assets.prompts.length}${cloudCount > 0 ? `, ${cloudCount} synced` : ""}${localCount > 0 ? `, ${localCount} local-only` : ""})`
|
|
600
|
+
: "✗ — none found";
|
|
601
|
+
console.log(` Prompts: ${promptSummary}`);
|
|
602
|
+
console.log(` Sweeps: ${assets.sweeps.length > 0 ? `✓ (${assets.sweeps.length})` : "— none found"}`);
|
|
603
|
+
console.log(` Agents: ${assets.agents.length > 0 ? `✓ (${assets.agents.length} targets)` : "✗ — run \`prompts-gpt sync\`"}`);
|
|
604
|
+
if (availableProviders.length > 0) {
|
|
605
|
+
console.log(` Providers: ✓`);
|
|
606
|
+
for (const p of availableProviders) {
|
|
607
|
+
console.log(` ${p.provider}: ${p.bin} | model: ${p.modelDefault}${p.version ? ` | ${p.version}` : ""}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
console.log(" Providers: ✗ — no CLI found. Install one:");
|
|
612
|
+
console.log(" npm install -g @openai/codex # Codex");
|
|
613
|
+
console.log(" npm install -g @anthropic-ai/claude-code # Claude Code");
|
|
614
|
+
console.log(" # Cursor: install Cursor IDE (includes agent CLI)");
|
|
615
|
+
}
|
|
616
|
+
console.log("");
|
|
617
|
+
const statusModelCache = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".models.json");
|
|
618
|
+
if (existsSync(statusModelCache)) {
|
|
619
|
+
try {
|
|
620
|
+
const fs = require("node:fs");
|
|
621
|
+
const mStat = fs.statSync(statusModelCache);
|
|
622
|
+
const ageMs = Date.now() - mStat.mtimeMs;
|
|
623
|
+
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
|
|
624
|
+
console.log(` Models: ${ageDays <= 7 ? "✓" : "⚠"} synced ${ageDays === 0 ? "today" : `${ageDays}d ago`}${ageDays > 7 ? " — run 'prompts-gpt sync-models'" : ""}`);
|
|
625
|
+
}
|
|
626
|
+
catch { /* skip */ }
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
console.log(" Models: — not synced. Run 'prompts-gpt sync-models'");
|
|
630
|
+
}
|
|
631
|
+
console.log("");
|
|
632
|
+
const lockFile = path.resolve(cwd, ".sweep.lock");
|
|
633
|
+
if (existsSync(lockFile)) {
|
|
634
|
+
try {
|
|
635
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
636
|
+
const lockData = JSON.parse(await fsRead(lockFile, "utf8"));
|
|
637
|
+
console.log(` Sweep lock: ⚠ active (PID ${lockData.pid}, started ${lockData.startedAt})`);
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
console.log(" Sweep lock: ⚠ found but unreadable");
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
console.log("");
|
|
644
|
+
if (assets.prompts.length > 0 && availableProviders.length > 0) {
|
|
645
|
+
console.log("Ready to run:");
|
|
646
|
+
console.log(` prompts-gpt run --prompt-file .prompts-gpt/${assets.prompts[0].file}`);
|
|
647
|
+
if (assets.sweeps.length > 0) {
|
|
648
|
+
for (const s of assets.sweeps) {
|
|
649
|
+
console.log(` prompts-gpt sweep --prompt-file ${s.file}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
console.log("Not ready yet. Quick fix:");
|
|
655
|
+
console.log(" prompts-gpt quickstart");
|
|
656
|
+
console.log("");
|
|
657
|
+
console.log("Or step by step:");
|
|
658
|
+
let step = 1;
|
|
659
|
+
if (!assets.credentialsFound)
|
|
660
|
+
console.log(` ${step++}. prompts-gpt init --token <project-token>`);
|
|
661
|
+
if (assets.prompts.length === 0)
|
|
662
|
+
console.log(` ${step++}. prompts-gpt sync`);
|
|
663
|
+
if (availableProviders.length === 0)
|
|
664
|
+
console.log(` ${step++}. Install a provider CLI (codex, cursor agent, claude, copilot)`);
|
|
665
|
+
if (!assets.configFound)
|
|
666
|
+
console.log(` ${step++}. prompts-gpt setup`);
|
|
667
|
+
}
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (command === "validate") {
|
|
671
|
+
const cwd = getResolvedCwd(flags);
|
|
672
|
+
const result = await validateRunConfig(cwd);
|
|
673
|
+
if (Boolean(flags.json)) {
|
|
674
|
+
console.log(JSON.stringify(result, null, 2));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
console.log(`Config: ${result.configPath}`);
|
|
678
|
+
console.log(`Valid: ${result.valid ? "yes" : "no"}`);
|
|
679
|
+
for (const e of result.errors) {
|
|
680
|
+
console.log(` error: ${e}`);
|
|
681
|
+
}
|
|
682
|
+
for (const w of result.warnings) {
|
|
683
|
+
console.log(` warning: ${w}`);
|
|
684
|
+
}
|
|
685
|
+
const validAssets = await discoverWorkspaceAssets(cwd);
|
|
686
|
+
if (validAssets.sweeps.length > 0) {
|
|
687
|
+
console.log(`\nSweep files (${validAssets.sweeps.length}):`);
|
|
688
|
+
for (const s of validAssets.sweeps) {
|
|
689
|
+
const fm = await readSweepFrontmatter(path.resolve(cwd, s.file));
|
|
690
|
+
const issues = [];
|
|
691
|
+
if (!fm.iterations)
|
|
692
|
+
issues.push("no iterations in frontmatter");
|
|
693
|
+
if (!fm.title)
|
|
694
|
+
issues.push("no title heading");
|
|
695
|
+
const icon = issues.length === 0 ? "✓" : "⚠";
|
|
696
|
+
console.log(` ${icon} ${s.name}${issues.length > 0 ? ` — ${issues.join(", ")}` : ""}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const modelCachePath = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".models.json");
|
|
700
|
+
try {
|
|
701
|
+
const { statSync } = await import("node:fs");
|
|
702
|
+
const mStat = statSync(modelCachePath);
|
|
703
|
+
const ageMs = Date.now() - mStat.mtimeMs;
|
|
704
|
+
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
|
|
705
|
+
if (ageDays > 7) {
|
|
706
|
+
console.log(`\n⚠ Model registry is ${ageDays} days old. Run 'prompts-gpt sync-models' to refresh.`);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
console.log(`\n✓ Model registry: synced ${ageDays === 0 ? "today" : `${ageDays}d ago`}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
console.log("\n⚠ Model registry not synced. Run 'prompts-gpt sync-models'.");
|
|
714
|
+
}
|
|
715
|
+
if (result.valid && result.errors.length === 0 && result.warnings.length === 0) {
|
|
716
|
+
console.log(" No issues found.");
|
|
717
|
+
}
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (command === "run") {
|
|
721
|
+
const cwd = getResolvedCwd(flags);
|
|
722
|
+
const promptFile = getStringFlag(flags, "prompt-file");
|
|
723
|
+
const config = await loadRunConfig(cwd);
|
|
724
|
+
warnOnConfigIssues(config);
|
|
725
|
+
if (Boolean(flags["list-models"])) {
|
|
726
|
+
const agentStr = getStringFlag(flags, "agent") || config.defaultAgent;
|
|
727
|
+
const targetProvider = resolveRunAgent({ agent: agentStr }, config.defaultAgent);
|
|
728
|
+
const choices = getModelChoicesForProvider(targetProvider, config);
|
|
729
|
+
console.log(`Models for ${targetProvider}:\n`);
|
|
730
|
+
for (const c of choices) {
|
|
731
|
+
if (c.value !== "__custom__")
|
|
732
|
+
console.log(` ${c.value.padEnd(30)} ${c.label}`);
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (!promptFile && !Boolean(flags.json)) {
|
|
737
|
+
if (!config.defaultPromptFile && config.batchDefaults.promptFiles.length === 0 && !config.batchDefaults.manifestPath) {
|
|
738
|
+
const assets = await discoverWorkspaceAssets(cwd);
|
|
739
|
+
if (assets.prompts.length > 0 && isTTYInteractive(flags)) {
|
|
740
|
+
const promptOptions = assets.prompts.map((p) => ({
|
|
741
|
+
label: `${p.slug} — ${p.title}`,
|
|
742
|
+
value: `.prompts-gpt/${p.file}`,
|
|
743
|
+
}));
|
|
744
|
+
const picked = await interactiveSelect("Select a prompt to run:", promptOptions);
|
|
745
|
+
flags["prompt-file"] = picked;
|
|
746
|
+
}
|
|
747
|
+
else if (assets.prompts.length > 0 || assets.sweeps.length > 0) {
|
|
748
|
+
console.log("No default prompt configured. Available options:\n");
|
|
749
|
+
if (assets.prompts.length > 0) {
|
|
750
|
+
console.log("Prompt packs:");
|
|
751
|
+
for (const p of assets.prompts) {
|
|
752
|
+
console.log(` prompts-gpt run -f .prompts-gpt/${p.file}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (assets.sweeps.length > 0) {
|
|
756
|
+
console.log("\nSweep prompts (use `sweep` command):");
|
|
757
|
+
for (const s of assets.sweeps) {
|
|
758
|
+
console.log(` prompts-gpt sweep -f ${s.file}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
console.log("\nOr set a default: prompts-gpt setup --prompt-file <path>");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const runProviders = await detectProviders(cwd);
|
|
767
|
+
const runAvailable = runProviders.filter((p) => p.available);
|
|
768
|
+
if (!getStringFlag(flags, "agent") && isTTYInteractive(flags) && !Boolean(flags.json) && runAvailable.length > 1) {
|
|
769
|
+
const lastRunProvider = await getLastUsedProvider(cwd);
|
|
770
|
+
const providerOpts = runAvailable.map((p) => ({
|
|
771
|
+
label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})${p.provider === lastRunProvider ? " ★" : ""}`,
|
|
772
|
+
value: p.provider,
|
|
773
|
+
}));
|
|
774
|
+
providerOpts.push({ label: "router (auto-select)", value: "router" });
|
|
775
|
+
if (lastRunProvider) {
|
|
776
|
+
const idx = providerOpts.findIndex((o) => o.value === lastRunProvider);
|
|
777
|
+
if (idx > 0) {
|
|
778
|
+
const [item] = providerOpts.splice(idx, 1);
|
|
779
|
+
providerOpts.unshift(item);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
flags.agent = await interactiveSelect("Select a provider:", providerOpts);
|
|
783
|
+
if (flags.agent !== "router") {
|
|
784
|
+
await saveLastUsedProvider(cwd, flags.agent);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (!getStringFlag(flags, "model") && isTTYInteractive(flags) && !Boolean(flags.json)) {
|
|
788
|
+
const currentAgent = resolveRunAgent(flags, config.defaultAgent);
|
|
789
|
+
if (currentAgent !== "router") {
|
|
790
|
+
const modelChoices = getModelChoicesForProvider(currentAgent, config);
|
|
791
|
+
if (modelChoices.length > 0) {
|
|
792
|
+
const lastModel = await getLastUsedModel(cwd, currentAgent);
|
|
793
|
+
if (lastModel) {
|
|
794
|
+
const idx = modelChoices.findIndex((c) => c.value === lastModel);
|
|
795
|
+
if (idx > 0) {
|
|
796
|
+
const [item] = modelChoices.splice(idx, 1);
|
|
797
|
+
modelChoices.unshift({ label: `${item.label} ★`, value: item.value });
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
let picked = await interactiveSelect("Select a model:", modelChoices);
|
|
801
|
+
if (picked === "__custom__") {
|
|
802
|
+
picked = await interactiveInput("Enter model name", "");
|
|
803
|
+
if (!picked.trim()) {
|
|
804
|
+
console.log("No model entered, using default.");
|
|
805
|
+
picked = "";
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (picked) {
|
|
809
|
+
flags.model = resolveModelAlias(picked);
|
|
810
|
+
await saveLastUsedModel(cwd, currentAgent, flags.model);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const agent = resolveRunAgent(flags, config.defaultAgent);
|
|
816
|
+
if (Boolean(flags["dry-run"])) {
|
|
817
|
+
const providers = await detectProviders(cwd);
|
|
818
|
+
const resolvedProvider = resolveRunProvider(agent, providers, config.providerOrder);
|
|
819
|
+
const resolvedModel = getStringFlag(flags, "model")?.trim() || config.modelOverrides[resolvedProvider]?.trim() || "";
|
|
820
|
+
let resolvedPrompt = promptFile || getStringFlag(flags, "prompt-file") || config.defaultPromptFile || null;
|
|
821
|
+
if (!resolvedPrompt) {
|
|
822
|
+
try {
|
|
823
|
+
const { resolveDefaultPromptFile: resolvePf } = await import("./index.js");
|
|
824
|
+
resolvedPrompt = await resolvePf(cwd, config);
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
resolvedPrompt = null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const agentStr = getStringFlag(flags, "agent") ? agent : `${agent} (auto-selected)`;
|
|
831
|
+
console.log("[dry-run] Would execute:");
|
|
832
|
+
console.log(` Agent: ${agentStr}`);
|
|
833
|
+
console.log(` Provider: ${resolvedProvider}`);
|
|
834
|
+
console.log(` Model: ${resolvedModel || "(default)"}`);
|
|
835
|
+
console.log(` Prompt: ${resolvedPrompt || "(none found — pass --prompt-file)"}`);
|
|
836
|
+
console.log(` Timeout: ${getStringFlag(flags, "timeout") || config.timeoutSeconds}s`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (isTTYInteractive(flags) && !Boolean(flags.json) && !Boolean(flags["dry-run"])) {
|
|
840
|
+
const previewFile = getStringFlag(flags, "prompt-file");
|
|
841
|
+
if (previewFile && existsSync(path.resolve(cwd, previewFile))) {
|
|
842
|
+
try {
|
|
843
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
844
|
+
const previewContent = await fsRead(path.resolve(cwd, previewFile), "utf8");
|
|
845
|
+
const previewLines = previewContent.split("\n").slice(0, 5);
|
|
846
|
+
console.log(`\nPrompt preview (${path.basename(previewFile)}):`);
|
|
847
|
+
for (const line of previewLines)
|
|
848
|
+
console.log(` ${line}`);
|
|
849
|
+
if (previewContent.split("\n").length > 5)
|
|
850
|
+
console.log(` ... (${previewContent.split("\n").length - 5} more lines)`);
|
|
851
|
+
console.log("");
|
|
852
|
+
}
|
|
853
|
+
catch { /* skip preview */ }
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const modelFlag = getStringFlag(flags, "model");
|
|
857
|
+
if (modelFlag && !Boolean(flags.json) && agent !== "router") {
|
|
858
|
+
const providers = await detectProviders(cwd);
|
|
859
|
+
const resolvedProvider = resolveRunProvider(agent, providers, config.providerOrder);
|
|
860
|
+
const mismatchWarning = warnModelProviderMismatch(resolvedProvider, modelFlag);
|
|
861
|
+
if (mismatchWarning) {
|
|
862
|
+
console.error(`[warning] ${mismatchWarning}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const result = await runPrompt({
|
|
866
|
+
cwd,
|
|
867
|
+
promptFile,
|
|
868
|
+
agent,
|
|
869
|
+
model: modelFlag,
|
|
870
|
+
timeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "timeout"), "timeout"),
|
|
871
|
+
artifactsDir: getStringFlag(flags, "artifacts-dir"),
|
|
872
|
+
runId: getStringFlag(flags, "run-id"),
|
|
873
|
+
approveMcps: !Boolean(flags["no-approve-mcps"]),
|
|
874
|
+
sandboxMode: getStringFlag(flags, "sandbox"),
|
|
875
|
+
background: Boolean(flags.background),
|
|
876
|
+
permissionMode: getStringFlag(flags, "permission-mode"),
|
|
877
|
+
});
|
|
878
|
+
if (Boolean(flags.json)) {
|
|
879
|
+
console.log(JSON.stringify(result, null, 2));
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const agentLabel = getStringFlag(flags, "agent") ? result.provider : `${result.provider} (auto — first available from provider order)`;
|
|
883
|
+
console.log(`Run ID: ${result.runId}`);
|
|
884
|
+
console.log(`Provider: ${agentLabel} | Model: ${result.model}`);
|
|
885
|
+
console.log(`Exit code: ${result.exitCode}`);
|
|
886
|
+
console.log(`Duration: ${formatDuration(result.durationMs)}`);
|
|
887
|
+
if (hasTokenUsage(result.tokenUsage)) {
|
|
888
|
+
console.log(`Tokens: ${formatTokenUsage(result.tokenUsage)}`);
|
|
889
|
+
}
|
|
890
|
+
console.log(`Run dir: ${result.runDir}`);
|
|
891
|
+
console.log(`Summary: ${result.summaryFile}`);
|
|
892
|
+
console.log(`Log: ${result.logFile}`);
|
|
893
|
+
if (result.exitCode === 0) {
|
|
894
|
+
try {
|
|
895
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
896
|
+
const deltaContent = await fsRead(result.worktreeDeltaFile, "utf8");
|
|
897
|
+
if (!deltaContent.includes("No worktree delta")) {
|
|
898
|
+
const afterLines = deltaContent.split("\n").filter((l) => l.startsWith("=== AFTER ===") ? false : true);
|
|
899
|
+
const changedFiles = afterLines.filter((l) => /^\s*[MADRCU?!]/.test(l)).length;
|
|
900
|
+
if (changedFiles > 0) {
|
|
901
|
+
console.log(`Files changed: ${changedFiles}`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch { /* skip */ }
|
|
906
|
+
console.log("");
|
|
907
|
+
console.log(`View results: cat ${result.summaryFile}`);
|
|
908
|
+
}
|
|
909
|
+
if (Boolean(flags.open) && result.summaryFile) {
|
|
910
|
+
try {
|
|
911
|
+
const { spawn: openSpawn } = await import("node:child_process");
|
|
912
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "notepad" : "xdg-open";
|
|
913
|
+
openSpawn(openCmd, [result.summaryFile], { detached: true, stdio: "ignore" }).unref();
|
|
914
|
+
}
|
|
915
|
+
catch { /* ignore */ }
|
|
916
|
+
}
|
|
917
|
+
if (result.exitCode !== 0) {
|
|
918
|
+
const diagnostics = await extractRunDiagnostics(result.logFile, result.provider, result.model);
|
|
919
|
+
if (diagnostics.length > 0) {
|
|
920
|
+
console.log("");
|
|
921
|
+
console.log("Diagnostics:");
|
|
922
|
+
for (const d of diagnostics) {
|
|
923
|
+
console.log(` ${d}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
if (command === "run-batch") {
|
|
930
|
+
const cwd = getResolvedCwd(flags);
|
|
931
|
+
const promptFiles = getStringFlag(flags, "prompt-files")
|
|
932
|
+
?.split(",")
|
|
933
|
+
.map((item) => item.trim())
|
|
934
|
+
.filter(Boolean);
|
|
935
|
+
const config = await loadRunConfig(cwd);
|
|
936
|
+
warnOnConfigIssues(config);
|
|
937
|
+
if (!promptFiles && !getStringFlag(flags, "manifest") && !Boolean(flags.json)) {
|
|
938
|
+
if (!config.batchDefaults.manifestPath && config.batchDefaults.promptFiles.length === 0) {
|
|
939
|
+
const assets = await discoverWorkspaceAssets(cwd);
|
|
940
|
+
if (assets.prompts.length > 0) {
|
|
941
|
+
console.log("No batch source configured. Available prompt packs:\n");
|
|
942
|
+
const fileList = assets.prompts.map((p) => `.prompts-gpt/${p.file}`).join(",");
|
|
943
|
+
console.log(` prompts-gpt run-batch --prompt-files ${fileList}`);
|
|
944
|
+
if (assets.manifestFound) {
|
|
945
|
+
console.log(` prompts-gpt run-batch --manifest .prompts-gpt/manifest.json`);
|
|
946
|
+
}
|
|
947
|
+
console.log("\nOr configure batch defaults: prompts-gpt setup");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
const agent = resolveRunAgent(flags, config.defaultAgent);
|
|
953
|
+
const result = await runBatch({
|
|
954
|
+
cwd,
|
|
955
|
+
manifestPath: getStringFlag(flags, "manifest"),
|
|
956
|
+
promptFiles,
|
|
957
|
+
agent,
|
|
958
|
+
model: getStringFlag(flags, "model"),
|
|
959
|
+
timeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "timeout"), "timeout"),
|
|
960
|
+
artifactsDir: getStringFlag(flags, "artifacts-dir"),
|
|
961
|
+
});
|
|
962
|
+
if (Boolean(flags.json)) {
|
|
963
|
+
console.log(JSON.stringify(result, null, 2));
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
console.log(`Batch complete: total=${result.total} success=${result.success} failed=${result.failed}`);
|
|
967
|
+
if (hasTokenUsage(result.tokenUsage)) {
|
|
968
|
+
console.log(`Batch tokens: ${formatTokenUsage(result.tokenUsage)}`);
|
|
969
|
+
}
|
|
970
|
+
for (const [idx, run] of result.results.entries()) {
|
|
971
|
+
const status = run.exitCode === 0 ? "ok" : "FAIL";
|
|
972
|
+
const usageStr = hasTokenUsage(run.tokenUsage) ? `, tokens=${run.tokenUsage.totalTokens.toLocaleString()}` : "";
|
|
973
|
+
console.log(` [${idx + 1}/${result.total}] ${status} ${path.basename(run.promptFile)} -> ${run.provider} (exit=${run.exitCode}, ${formatDuration(run.durationMs)}${usageStr})`);
|
|
974
|
+
}
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (command === "sweep") {
|
|
978
|
+
const cwd = getResolvedCwd(flags);
|
|
979
|
+
const sweepPromptFile = getStringFlag(flags, "prompt-file");
|
|
980
|
+
const config = await loadRunConfig(cwd);
|
|
981
|
+
warnOnConfigIssues(config);
|
|
982
|
+
if (Boolean(flags["list-models"])) {
|
|
983
|
+
const agentStr = getStringFlag(flags, "agent") || config.defaultAgent;
|
|
984
|
+
const targetProvider = resolveRunAgent({ agent: agentStr }, config.defaultAgent);
|
|
985
|
+
const choices = getModelChoicesForProvider(targetProvider, config);
|
|
986
|
+
console.log(`Models for ${targetProvider}:\n`);
|
|
987
|
+
for (const c of choices) {
|
|
988
|
+
if (c.value !== "__custom__")
|
|
989
|
+
console.log(` ${c.value.padEnd(30)} ${c.label}`);
|
|
990
|
+
}
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (!sweepPromptFile && !Boolean(flags.json)) {
|
|
994
|
+
const assets = await discoverWorkspaceAssets(cwd);
|
|
995
|
+
if (assets.sweeps.length === 1) {
|
|
996
|
+
const autoFile = assets.sweeps[0].file;
|
|
997
|
+
const iterFromFm = await readSweepIterationsFromFrontmatter(path.resolve(cwd, autoFile));
|
|
998
|
+
console.log(`Auto-selected sweep: ${path.basename(autoFile)}${iterFromFm ? ` (${iterFromFm} iterations from frontmatter)` : ""}`);
|
|
999
|
+
flags["prompt-file"] = autoFile;
|
|
1000
|
+
if (iterFromFm && !getStringFlag(flags, "iterations")) {
|
|
1001
|
+
flags.iterations = String(iterFromFm);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
else if (assets.sweeps.length > 1) {
|
|
1005
|
+
if (isTTYInteractive(flags)) {
|
|
1006
|
+
const sweepOptions = await Promise.all(assets.sweeps.map(async (s) => {
|
|
1007
|
+
const fm = await readSweepFrontmatter(path.resolve(cwd, s.file));
|
|
1008
|
+
const iterLabel = fm.iterations ? ` (${fm.iterations} iterations)` : "";
|
|
1009
|
+
const titleLabel = fm.title ? ` — ${fm.title}` : "";
|
|
1010
|
+
return { label: `${path.basename(s.file, ".md")}${titleLabel}${iterLabel}`, value: s.file };
|
|
1011
|
+
}));
|
|
1012
|
+
const picked = await interactiveSelect("Select a sweep file:", sweepOptions);
|
|
1013
|
+
flags["prompt-file"] = picked;
|
|
1014
|
+
const iterFromFm = await readSweepIterationsFromFrontmatter(path.resolve(cwd, picked));
|
|
1015
|
+
if (iterFromFm && !getStringFlag(flags, "iterations")) {
|
|
1016
|
+
flags.iterations = String(iterFromFm);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
console.log(`${assets.sweeps.length} sweep files found. Pick one with --prompt-file:\n`);
|
|
1021
|
+
for (const s of assets.sweeps) {
|
|
1022
|
+
const iterCount = await readSweepIterationsFromFrontmatter(path.resolve(cwd, s.file));
|
|
1023
|
+
const iterFlag = iterCount ? ` --iterations ${iterCount}` : "";
|
|
1024
|
+
console.log(` prompts-gpt sweep -f ${s.file}${iterFlag}`);
|
|
1025
|
+
}
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
console.log("No sweep files found.");
|
|
1031
|
+
console.log(" Create .prompts-gpt/sweeps/<name>.md to add sweep prompts.");
|
|
1032
|
+
console.log(" Run `prompts-gpt list` to see what's available.");
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (!getStringFlag(flags, "iterations") && getStringFlag(flags, "prompt-file")) {
|
|
1037
|
+
const iterFromFm = await readSweepIterationsFromFrontmatter(path.resolve(cwd, getStringFlag(flags, "prompt-file")));
|
|
1038
|
+
if (iterFromFm) {
|
|
1039
|
+
flags.iterations = String(iterFromFm);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
const providers = await detectProviders(cwd);
|
|
1043
|
+
const availableProviders = providers.filter((p) => p.available);
|
|
1044
|
+
if (!getStringFlag(flags, "agent") && isTTYInteractive(flags) && !Boolean(flags.json) && availableProviders.length > 1) {
|
|
1045
|
+
const lastProvider = await getLastUsedProvider(cwd);
|
|
1046
|
+
const providerOptions = availableProviders.map((p) => ({
|
|
1047
|
+
label: `${p.provider} (${p.modelDefault}${p.version ? `, ${p.version}` : ""})${p.provider === lastProvider ? " ★" : ""}`,
|
|
1048
|
+
value: p.provider,
|
|
1049
|
+
}));
|
|
1050
|
+
providerOptions.push({ label: "router (auto-select best available)", value: "router" });
|
|
1051
|
+
if (lastProvider) {
|
|
1052
|
+
const idx = providerOptions.findIndex((o) => o.value === lastProvider);
|
|
1053
|
+
if (idx > 0) {
|
|
1054
|
+
const [item] = providerOptions.splice(idx, 1);
|
|
1055
|
+
providerOptions.unshift(item);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
const picked = await interactiveSelect("Select a provider:", providerOptions);
|
|
1059
|
+
flags.agent = picked;
|
|
1060
|
+
if (picked !== "router") {
|
|
1061
|
+
await saveLastUsedProvider(cwd, picked);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (!getStringFlag(flags, "model") && isTTYInteractive(flags) && !Boolean(flags.json)) {
|
|
1065
|
+
const currentAgent = resolveRunAgent(flags, config.defaultAgent);
|
|
1066
|
+
if (currentAgent !== "router") {
|
|
1067
|
+
const modelChoices = getModelChoicesForProvider(currentAgent, config);
|
|
1068
|
+
if (modelChoices.length > 0) {
|
|
1069
|
+
const lastModel = await getLastUsedModel(cwd, currentAgent);
|
|
1070
|
+
if (lastModel) {
|
|
1071
|
+
const idx = modelChoices.findIndex((c) => c.value === lastModel);
|
|
1072
|
+
if (idx > 0) {
|
|
1073
|
+
const [item] = modelChoices.splice(idx, 1);
|
|
1074
|
+
modelChoices.unshift({ label: `${item.label} ★`, value: item.value });
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
let picked = await interactiveSelect("Select a model:", modelChoices);
|
|
1078
|
+
if (picked === "__custom__") {
|
|
1079
|
+
picked = await interactiveInput("Enter model name", "");
|
|
1080
|
+
if (!picked.trim()) {
|
|
1081
|
+
console.log("No model entered, using default.");
|
|
1082
|
+
picked = "";
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (picked) {
|
|
1086
|
+
flags.model = resolveModelAlias(picked);
|
|
1087
|
+
await saveLastUsedModel(cwd, currentAgent, flags.model);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (!getStringFlag(flags, "iterations") && isTTYInteractive(flags) && !Boolean(flags.json) && getStringFlag(flags, "prompt-file")) {
|
|
1093
|
+
const fmIter = await readSweepIterationsFromFrontmatter(path.resolve(cwd, getStringFlag(flags, "prompt-file")));
|
|
1094
|
+
const defaultIter = fmIter ? String(fmIter) : "1";
|
|
1095
|
+
const iterOptions = [
|
|
1096
|
+
{ label: `${defaultIter} (default)`, value: defaultIter },
|
|
1097
|
+
...(defaultIter !== "1" ? [{ label: "1", value: "1" }] : []),
|
|
1098
|
+
...(defaultIter !== "2" ? [{ label: "2", value: "2" }] : []),
|
|
1099
|
+
...(defaultIter !== "3" ? [{ label: "3", value: "3" }] : []),
|
|
1100
|
+
...(defaultIter !== "5" ? [{ label: "5", value: "5" }] : []),
|
|
1101
|
+
];
|
|
1102
|
+
flags.iterations = await interactiveSelect("Select iterations:", iterOptions);
|
|
1103
|
+
}
|
|
1104
|
+
const agent = resolveRunAgent(flags, config.defaultAgent);
|
|
1105
|
+
const sweepModelFlag = getStringFlag(flags, "model");
|
|
1106
|
+
if (sweepModelFlag && agent !== "router" && !Boolean(flags.json)) {
|
|
1107
|
+
const resolvedP = resolveRunProvider(agent, providers, config.providerOrder);
|
|
1108
|
+
const check = validateModelForProvider(sweepModelFlag, resolvedP);
|
|
1109
|
+
if (!check.valid) {
|
|
1110
|
+
console.log(`⚠ Model "${sweepModelFlag}" may not be available for ${resolvedP}.${check.suggestion ? ` Did you mean "${check.suggestion}"?` : ""}`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (isTTYInteractive(flags) && !Boolean(flags.json) && !Boolean(flags["dry-run"])) {
|
|
1114
|
+
const resolvedProvider = resolveRunProvider(agent, providers, config.providerOrder);
|
|
1115
|
+
const resolvedModel = getStringFlag(flags, "model")?.trim() || config.modelOverrides[resolvedProvider]?.trim() || availableProviders.find((p) => p.provider === resolvedProvider)?.modelDefault || "auto";
|
|
1116
|
+
const resolvedIter = getStringFlag(flags, "iterations") || "1";
|
|
1117
|
+
const sweepFile = getStringFlag(flags, "prompt-file") || "(default)";
|
|
1118
|
+
const iterTimeout = parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout") ?? 5400;
|
|
1119
|
+
const estMaxMs = parseInt(resolvedIter, 10) * iterTimeout * 1000;
|
|
1120
|
+
const maxRetries = parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries") ?? 2;
|
|
1121
|
+
console.log("");
|
|
1122
|
+
const modelTier = (PROVIDER_MODELS[resolvedProvider] ?? []).find((m) => m.value === resolvedModel)?.tier ?? "standard";
|
|
1123
|
+
const tierBadge = modelTier === "frontier" ? "⭐ frontier" : modelTier === "fast" ? "⚡ fast" : modelTier === "budget" ? "💰 budget" : "standard";
|
|
1124
|
+
const boxValues = [
|
|
1125
|
+
path.basename(sweepFile), resolvedProvider, resolvedModel, resolvedIter,
|
|
1126
|
+
`${formatDuration(iterTimeout * 1000)} per iteration`, `~${formatDuration(estMaxMs)}`,
|
|
1127
|
+
String(maxRetries), tierBadge,
|
|
1128
|
+
];
|
|
1129
|
+
const longestVal = Math.max(...boxValues.map((v) => v.length), 30);
|
|
1130
|
+
const innerW = longestVal + 16;
|
|
1131
|
+
const bar = "═".repeat(innerW);
|
|
1132
|
+
const pad = (v) => v.padEnd(longestVal);
|
|
1133
|
+
const row = (label, val) => `${colorize("║", "\x1b[36m")} ${label.padEnd(12)} ${pad(val)} ${colorize("║", "\x1b[36m")}`;
|
|
1134
|
+
const titleText = "Prompts-GPT Sweep";
|
|
1135
|
+
const titlePad = Math.max(0, Math.floor((innerW - titleText.length) / 2));
|
|
1136
|
+
console.log(colorize(`╔${bar}╗`, "\x1b[36m"));
|
|
1137
|
+
console.log(colorize(`║${" ".repeat(titlePad)}${titleText}${" ".repeat(innerW - titlePad - titleText.length)}║`, "\x1b[36m"));
|
|
1138
|
+
console.log(colorize(`╠${bar}╣`, "\x1b[36m"));
|
|
1139
|
+
console.log(row("Sweep:", path.basename(sweepFile)));
|
|
1140
|
+
console.log(row("Provider:", resolvedProvider));
|
|
1141
|
+
console.log(row("Model:", resolvedModel));
|
|
1142
|
+
console.log(row("Iterations:", resolvedIter));
|
|
1143
|
+
console.log(row("Timeout:", `${formatDuration(iterTimeout * 1000)} per iteration`));
|
|
1144
|
+
console.log(row("Max time:", `~${formatDuration(estMaxMs)}`));
|
|
1145
|
+
console.log(row("Retries:", String(maxRetries)));
|
|
1146
|
+
console.log(row("Tier:", tierBadge));
|
|
1147
|
+
if (modelTier === "frontier") {
|
|
1148
|
+
console.log(`${colorize("║", "\x1b[36m")} ${colorize("⚠ Frontier model — higher cost per token", "\x1b[33m")}${" ".repeat(Math.max(0, innerW - 43))} ${colorize("║", "\x1b[36m")}`);
|
|
1149
|
+
}
|
|
1150
|
+
console.log(colorize(`╚${bar}╝`, "\x1b[36m"));
|
|
1151
|
+
console.log("");
|
|
1152
|
+
const confirmOpts = [
|
|
1153
|
+
{ label: "Yes, run this sweep", value: "y" },
|
|
1154
|
+
{ label: "Cancel", value: "n" },
|
|
1155
|
+
];
|
|
1156
|
+
const confirm = await interactiveSelect("Run this sweep?", confirmOpts);
|
|
1157
|
+
if (confirm === "n") {
|
|
1158
|
+
console.log("Cancelled.");
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
const quiet = Boolean(flags.quiet);
|
|
1163
|
+
const summaryLineCount = parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines") ?? 40;
|
|
1164
|
+
const sweepDurations = [];
|
|
1165
|
+
const onProgress = Boolean(flags.json)
|
|
1166
|
+
? undefined
|
|
1167
|
+
: (event) => {
|
|
1168
|
+
if (event.type === "preflight") {
|
|
1169
|
+
const report = JSON.parse(event.message);
|
|
1170
|
+
console.log(`Provider: ${report.provider} | Model: ${report.model} | Iterations: ${report.iterations}`);
|
|
1171
|
+
console.log(`Branch: ${report.gitBranch} | Dirty files: ${report.gitDirtyFiles} | Free disk: ${report.diskFreeMb}MB`);
|
|
1172
|
+
console.log(`Prompt: ${path.basename(report.promptFile)}`);
|
|
1173
|
+
try {
|
|
1174
|
+
const fs = require("node:fs");
|
|
1175
|
+
const skillDir = path.resolve(cwd, ".agents", "skills");
|
|
1176
|
+
const ruleDir = path.resolve(cwd, ".cursor", "rules");
|
|
1177
|
+
const skillCount = fs.existsSync(skillDir) ? fs.readdirSync(skillDir, { recursive: true }).filter((f) => String(f).endsWith("SKILL.md")).length : 0;
|
|
1178
|
+
const ruleCount = fs.existsSync(ruleDir) ? fs.readdirSync(ruleDir).filter((f) => String(f).endsWith(".mdc")).length : 0;
|
|
1179
|
+
const mcpConfig = fs.existsSync(path.resolve(cwd, ".cursor", "mcp.json")) ? "found" : "none";
|
|
1180
|
+
console.log(`Skills: ${skillCount} | Rules: ${ruleCount} | MCP config: ${mcpConfig}`);
|
|
1181
|
+
}
|
|
1182
|
+
catch { /* skip */ }
|
|
1183
|
+
const maxRunDirs = parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs") ?? 20;
|
|
1184
|
+
console.log(`Log rotation: keep ${maxRunDirs} runs`);
|
|
1185
|
+
for (const w of report.warnings)
|
|
1186
|
+
console.log(`⚠ ${w}`);
|
|
1187
|
+
}
|
|
1188
|
+
else if (event.type === "iteration_start") {
|
|
1189
|
+
console.log(`\n${colorize("══════════════════════════════════════════════════════════════", "\x1b[35m")}`);
|
|
1190
|
+
console.log(colorize(`[${new Date().toLocaleTimeString()}] Iteration ${event.iteration}/${event.total}: ${event.provider} (${event.model})`, "\x1b[1;35m"));
|
|
1191
|
+
console.log(colorize("══════════════════════════════════════════════════════════════", "\x1b[35m"));
|
|
1192
|
+
}
|
|
1193
|
+
else if (event.type === "message") {
|
|
1194
|
+
if (!quiet) {
|
|
1195
|
+
const truncText = event.text.length > 120 ? `${event.text.slice(0, 117)}...` : event.text;
|
|
1196
|
+
console.log(` ${colorize(`[${event.elapsed}]`, "\x1b[36m")} 💬 ${truncText}`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
else if (event.type === "tool") {
|
|
1200
|
+
if (!quiet) {
|
|
1201
|
+
const c = event.counts;
|
|
1202
|
+
let icon = "🔧";
|
|
1203
|
+
let friendlyAction = event.action;
|
|
1204
|
+
if (event.action.includes("read")) {
|
|
1205
|
+
icon = "📖";
|
|
1206
|
+
friendlyAction = "read";
|
|
1207
|
+
}
|
|
1208
|
+
else if (event.action.includes("write") || event.action.includes("edit") || event.action === "str_replace") {
|
|
1209
|
+
icon = "✏️ ";
|
|
1210
|
+
friendlyAction = "write";
|
|
1211
|
+
}
|
|
1212
|
+
else if (event.action.includes("shell") || event.action === "bash") {
|
|
1213
|
+
icon = "⚡";
|
|
1214
|
+
friendlyAction = "shell";
|
|
1215
|
+
}
|
|
1216
|
+
else if (event.action.includes("search") || event.action.includes("grep") || event.action.includes("glob")) {
|
|
1217
|
+
icon = "🔍";
|
|
1218
|
+
friendlyAction = "search";
|
|
1219
|
+
}
|
|
1220
|
+
else if (event.action.includes("todo")) {
|
|
1221
|
+
icon = "📋";
|
|
1222
|
+
friendlyAction = "todo";
|
|
1223
|
+
}
|
|
1224
|
+
else if (event.action.includes("web") || event.action.includes("fetch")) {
|
|
1225
|
+
icon = "🌐";
|
|
1226
|
+
friendlyAction = "web";
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
friendlyAction = event.action.replace(/ToolCall$/i, "").replace(/Tool$/i, "");
|
|
1230
|
+
}
|
|
1231
|
+
const fileStr = event.file ? ` ${colorize(event.file, "\x1b[90m")}` : "";
|
|
1232
|
+
const countStr = colorize(`(R:${c.reads} W:${c.writes} S:${c.shells} 🔍:${c.searches})`, "\x1b[90m");
|
|
1233
|
+
console.log(` ${icon} ${friendlyAction}${fileStr} ${countStr}`);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
else if (event.type === "iteration_end") {
|
|
1237
|
+
const elapsed = formatDuration(event.durationMs);
|
|
1238
|
+
const icon = event.status === "success" ? colorize("✓", "\x1b[32m") : colorize("✗", "\x1b[31m");
|
|
1239
|
+
sweepDurations.push(event.durationMs);
|
|
1240
|
+
const remaining = ((parseInt(String(getStringFlag(flags, "iterations"))) || 1) - event.iteration);
|
|
1241
|
+
let etaStr = "";
|
|
1242
|
+
if (remaining > 0 && sweepDurations.length > 0) {
|
|
1243
|
+
const avgMs = sweepDurations.reduce((a, b) => a + b, 0) / sweepDurations.length;
|
|
1244
|
+
etaStr = ` | ETA: ~${formatDuration(avgMs * remaining)}`;
|
|
1245
|
+
}
|
|
1246
|
+
console.log(`${icon} Iteration ${event.iteration} ${event.status} (${elapsed})${etaStr}`);
|
|
1247
|
+
}
|
|
1248
|
+
else if (event.type === "attempt_retry") {
|
|
1249
|
+
console.log(colorize(` ⟳ Retry ${event.attempt}/${event.maxAttempts} for iteration ${event.iteration}, backoff ${event.backoffMs}ms`, "\x1b[33m"));
|
|
1250
|
+
}
|
|
1251
|
+
else if (event.type === "summary") {
|
|
1252
|
+
if (summaryLineCount > 0) {
|
|
1253
|
+
const preview = event.lines.slice(0, summaryLineCount);
|
|
1254
|
+
console.log(`\n── Summary (iteration ${event.iteration}, ${preview.length}/${event.lines.length} lines) ──`);
|
|
1255
|
+
for (const line of preview)
|
|
1256
|
+
console.log(` ${line}`);
|
|
1257
|
+
if (event.lines.length > summaryLineCount) {
|
|
1258
|
+
console.log(` ... (${event.lines.length - summaryLineCount} more lines)`);
|
|
1259
|
+
}
|
|
1260
|
+
console.log("── End summary ──");
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
else if (event.type === "sweep_end") {
|
|
1264
|
+
const r = event.result;
|
|
1265
|
+
const cW = 62;
|
|
1266
|
+
const cBar = "═".repeat(cW);
|
|
1267
|
+
const cRow = (label, val) => {
|
|
1268
|
+
const plain = ` ${label.padEnd(12)} ${val}`;
|
|
1269
|
+
console.log(`${colorize("║", "\x1b[36m")}${plain.padEnd(cW)}${colorize("║", "\x1b[36m")}`);
|
|
1270
|
+
};
|
|
1271
|
+
console.log("");
|
|
1272
|
+
console.log(colorize(`╔${cBar}╗`, "\x1b[36m"));
|
|
1273
|
+
const cTitle = "Sweep Complete";
|
|
1274
|
+
const cTitlePad = Math.floor((cW - cTitle.length) / 2);
|
|
1275
|
+
console.log(colorize(`║${" ".repeat(cTitlePad)}${cTitle}${" ".repeat(cW - cTitlePad - cTitle.length)}║`, "\x1b[36m"));
|
|
1276
|
+
console.log(colorize(`╠${cBar}╣`, "\x1b[36m"));
|
|
1277
|
+
cRow("Model:", r.model);
|
|
1278
|
+
cRow("Duration:", formatDuration(r.totalDurationMs));
|
|
1279
|
+
cRow("Iterations:", String(r.totalIterations));
|
|
1280
|
+
cRow("Succeeded:", String(r.succeeded));
|
|
1281
|
+
if (r.failed > 0) {
|
|
1282
|
+
cRow("Failed:", String(r.failed));
|
|
1283
|
+
}
|
|
1284
|
+
console.log(colorize(`╠${cBar}╣`, "\x1b[36m"));
|
|
1285
|
+
for (const iter of r.iterations) {
|
|
1286
|
+
const statusIcon = iter.status === "success" ? "✓" : "✗";
|
|
1287
|
+
const plain = ` ${statusIcon} Iter ${iter.iteration}: ${iter.status.padEnd(16)} ${formatDuration(iter.durationMs)}`;
|
|
1288
|
+
console.log(`${colorize("║", "\x1b[36m")}${plain.padEnd(cW)}${colorize("║", "\x1b[36m")}`);
|
|
1289
|
+
}
|
|
1290
|
+
console.log(colorize(`╚${cBar}╝`, "\x1b[36m"));
|
|
1291
|
+
if (hasTokenUsage(r.tokenUsage)) {
|
|
1292
|
+
const tu = r.tokenUsage;
|
|
1293
|
+
console.log(`Tokens: ${formatTokenUsage(tu)}`);
|
|
1294
|
+
const estCost = ((tu.inputTokens || 0) * 0.003 + (tu.outputTokens || 0) * 0.015) / 1000;
|
|
1295
|
+
if (estCost > 0.001) {
|
|
1296
|
+
console.log(`Estimated cost: ~$${estCost.toFixed(2)}`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
const result = await sweepPrompt({
|
|
1302
|
+
cwd,
|
|
1303
|
+
promptFile: getStringFlag(flags, "prompt-file"),
|
|
1304
|
+
agent,
|
|
1305
|
+
model: getStringFlag(flags, "model"),
|
|
1306
|
+
iterations: parsePositiveIntFlag(getStringFlag(flags, "iterations"), "iterations"),
|
|
1307
|
+
iterationTimeoutSeconds: parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout"),
|
|
1308
|
+
maxRetries: parseNonNegativeIntFlag(getStringFlag(flags, "max-retries"), "max-retries"),
|
|
1309
|
+
artifactsDir: getStringFlag(flags, "artifacts-dir"),
|
|
1310
|
+
runId: getStringFlag(flags, "run-id"),
|
|
1311
|
+
approveMcps: !Boolean(flags["no-approve-mcps"]),
|
|
1312
|
+
sandboxMode: getStringFlag(flags, "sandbox"),
|
|
1313
|
+
phase: getStringFlag(flags, "phase"),
|
|
1314
|
+
dryRun: Boolean(flags["dry-run"]),
|
|
1315
|
+
maxRunDirs: parsePositiveIntFlag(getStringFlag(flags, "max-run-dirs"), "max-run-dirs"),
|
|
1316
|
+
summaryLines: parsePositiveIntFlag(getStringFlag(flags, "summary-lines"), "summary-lines"),
|
|
1317
|
+
background: Boolean(flags.background),
|
|
1318
|
+
permissionMode: getStringFlag(flags, "permission-mode"),
|
|
1319
|
+
onProgress,
|
|
1320
|
+
});
|
|
1321
|
+
if (Boolean(flags.json)) {
|
|
1322
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
if (result.dryRun) {
|
|
1326
|
+
const iterTimeout = parsePositiveIntFlag(getStringFlag(flags, "iteration-timeout"), "iteration-timeout") ?? 5400;
|
|
1327
|
+
const estMaxDuration = result.totalIterations * iterTimeout;
|
|
1328
|
+
console.log("[dry-run] Would execute sweep with:");
|
|
1329
|
+
console.log(` Provider: ${result.provider}`);
|
|
1330
|
+
console.log(` Model: ${result.model}`);
|
|
1331
|
+
console.log(` Iterations: ${result.totalIterations}`);
|
|
1332
|
+
console.log(` Prompt: ${result.promptFile}`);
|
|
1333
|
+
console.log(` Max duration: ~${formatDuration(estMaxDuration * 1000)} (${result.totalIterations} x ${formatDuration(iterTimeout * 1000)})`);
|
|
1334
|
+
try {
|
|
1335
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
1336
|
+
const previewText = await fsRead(result.promptFile, "utf8");
|
|
1337
|
+
const previewLines = previewText.split("\n").slice(0, 5);
|
|
1338
|
+
console.log(` Prompt preview:`);
|
|
1339
|
+
for (const line of previewLines)
|
|
1340
|
+
console.log(` ${line}`);
|
|
1341
|
+
if (previewText.split("\n").length > 5)
|
|
1342
|
+
console.log(` ... (${previewText.split("\n").length - 5} more lines)`);
|
|
1343
|
+
}
|
|
1344
|
+
catch { /* skip preview */ }
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
console.log(`Run ID: ${result.runId}`);
|
|
1348
|
+
console.log(`Provider: ${result.provider} | Model: ${result.model}`);
|
|
1349
|
+
console.log(`Prompt: ${result.promptFile}`);
|
|
1350
|
+
console.log(`Iterations: ${result.succeeded}/${result.totalIterations} succeeded`);
|
|
1351
|
+
console.log(`Duration: ${formatDuration(result.totalDurationMs)}`);
|
|
1352
|
+
if (hasTokenUsage(result.tokenUsage)) {
|
|
1353
|
+
console.log(`Tokens: ${formatTokenUsage(result.tokenUsage)}`);
|
|
1354
|
+
}
|
|
1355
|
+
console.log(`Run dir: ${result.runDir}`);
|
|
1356
|
+
console.log(`Manifest: ${result.manifestFile}`);
|
|
1357
|
+
if (result.iterations.length > 1) {
|
|
1358
|
+
console.log("");
|
|
1359
|
+
console.log("Per-iteration results:");
|
|
1360
|
+
for (const iter of result.iterations) {
|
|
1361
|
+
const tools = iter.toolCounts;
|
|
1362
|
+
const toolStr = tools.total > 0 ? ` | tools: ${tools.total} (R:${tools.reads} W:${tools.writes} S:${tools.shells})` : "";
|
|
1363
|
+
const usageStr = hasTokenUsage(iter.tokenUsage) ? ` | tokens: ${iter.tokenUsage.totalTokens.toLocaleString()}` : "";
|
|
1364
|
+
console.log(` [${iter.iteration}/${result.totalIterations}] ${iter.status} (${formatDuration(iter.durationMs)})${toolStr}${usageStr}`);
|
|
1365
|
+
}
|
|
1366
|
+
const totalTools = result.iterations.reduce((acc, i) => ({
|
|
1367
|
+
reads: acc.reads + i.toolCounts.reads, writes: acc.writes + i.toolCounts.writes,
|
|
1368
|
+
shells: acc.shells + i.toolCounts.shells, searches: acc.searches + i.toolCounts.searches,
|
|
1369
|
+
webSearches: acc.webSearches + i.toolCounts.webSearches, total: acc.total + i.toolCounts.total,
|
|
1370
|
+
}), { reads: 0, writes: 0, shells: 0, searches: 0, webSearches: 0, total: 0 });
|
|
1371
|
+
if (totalTools.total > 0) {
|
|
1372
|
+
console.log(` Total tools: ${totalTools.total} (reads: ${totalTools.reads}, writes: ${totalTools.writes}, shells: ${totalTools.shells}, searches: ${totalTools.searches})`);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
console.log("");
|
|
1376
|
+
console.log("Inspect results:");
|
|
1377
|
+
console.log(` cat ${result.manifestFile}`);
|
|
1378
|
+
console.log(` ls ${result.runDir}`);
|
|
1379
|
+
if (Boolean(flags.open) && result.manifestFile) {
|
|
1380
|
+
try {
|
|
1381
|
+
const { spawn: openSpawn } = await import("node:child_process");
|
|
1382
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "notepad" : "xdg-open";
|
|
1383
|
+
openSpawn(openCmd, [result.manifestFile], { detached: true, stdio: "ignore" }).unref();
|
|
1384
|
+
}
|
|
1385
|
+
catch { /* ignore */ }
|
|
1386
|
+
}
|
|
1387
|
+
if (result.failed > 0) {
|
|
1388
|
+
const failedIters = result.iterations.filter((it) => it.status !== "success");
|
|
1389
|
+
for (const fi of failedIters) {
|
|
1390
|
+
try {
|
|
1391
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
1392
|
+
const logText = await fsRead(fi.logFile, "utf8");
|
|
1393
|
+
const tailLines = logText.split("\n").slice(-80);
|
|
1394
|
+
if (tailLines.length > 0) {
|
|
1395
|
+
console.log(`\n── Last 80 lines of iteration ${fi.iteration} log ──`);
|
|
1396
|
+
for (const line of tailLines)
|
|
1397
|
+
console.log(` ${line}`);
|
|
1398
|
+
console.log("── End log tail ──");
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
catch { /* skip */ }
|
|
1402
|
+
}
|
|
1403
|
+
process.exitCode = 1;
|
|
1404
|
+
}
|
|
1405
|
+
console.log("\nNext steps:");
|
|
1406
|
+
console.log(" 1. Review iteration summaries in the run directory");
|
|
1407
|
+
if (result.failed > 0) {
|
|
1408
|
+
console.log(` 2. Re-run failed: prompts-gpt sweep -f ${getStringFlag(flags, "prompt-file")} -n ${result.failed}`);
|
|
1409
|
+
}
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (command === "quickstart") {
|
|
1413
|
+
const cwd = getResolvedCwd(flags);
|
|
1414
|
+
const assets = await discoverWorkspaceAssets(cwd);
|
|
1415
|
+
const providers = await detectProviders(cwd);
|
|
1416
|
+
const availableProviders = providers.filter((p) => p.available);
|
|
1417
|
+
console.log("Prompts-GPT Quickstart");
|
|
1418
|
+
console.log("======================");
|
|
1419
|
+
console.log("");
|
|
1420
|
+
const { spawnSync: gitCheck } = await import("node:child_process");
|
|
1421
|
+
const gitResult = gitCheck("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
1422
|
+
if (gitResult.status !== 0) {
|
|
1423
|
+
console.log("⚠ Not inside a git repository. Prompts-GPT works best in a git repo for worktree tracking.");
|
|
1424
|
+
console.log(" Run: git init\n");
|
|
1425
|
+
}
|
|
1426
|
+
if (!assets.credentialsFound) {
|
|
1427
|
+
let networkOk = true;
|
|
1428
|
+
try {
|
|
1429
|
+
const netCheck = await Promise.race([
|
|
1430
|
+
fetch("https://prompts-gpt.com/api/health", { method: "HEAD" }),
|
|
1431
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 5000)),
|
|
1432
|
+
]);
|
|
1433
|
+
networkOk = netCheck instanceof Response;
|
|
1434
|
+
}
|
|
1435
|
+
catch {
|
|
1436
|
+
networkOk = false;
|
|
1437
|
+
}
|
|
1438
|
+
if (!networkOk) {
|
|
1439
|
+
console.log("⚠ Network unavailable. Quickstart requires internet for token validation.\n");
|
|
1440
|
+
console.log("You can still use local sweep files without a network connection:");
|
|
1441
|
+
console.log(" 1. Create .prompts-gpt/sweeps/<name>.md with your sweep prompt");
|
|
1442
|
+
console.log(" 2. Run: prompts-gpt sweep");
|
|
1443
|
+
console.log("\nWhen online, run `prompts-gpt quickstart` again.");
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
console.log("Step 1: Save your project token");
|
|
1447
|
+
console.log(" prompts-gpt init --token-prompt");
|
|
1448
|
+
console.log("");
|
|
1449
|
+
console.log(" Get your token from: https://prompts-gpt.com/studio/projects");
|
|
1450
|
+
console.log("");
|
|
1451
|
+
console.log("Run `prompts-gpt quickstart` again after saving your token.");
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
console.log("✓ Credentials found");
|
|
1455
|
+
if (!assets.configFound) {
|
|
1456
|
+
console.log("→ Setting up config...");
|
|
1457
|
+
try {
|
|
1458
|
+
await initRunConfig({ cwd, overwrite: false });
|
|
1459
|
+
console.log("✓ Config created");
|
|
1460
|
+
}
|
|
1461
|
+
catch {
|
|
1462
|
+
console.log("✓ Config already exists");
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
else {
|
|
1466
|
+
console.log("✓ Config found");
|
|
1467
|
+
}
|
|
1468
|
+
if (availableProviders.length === 0) {
|
|
1469
|
+
console.log("");
|
|
1470
|
+
console.log("✗ No provider CLIs found. Install at least one:");
|
|
1471
|
+
console.log(" - codex: npm install -g @openai/codex");
|
|
1472
|
+
console.log(" - claude: npm install -g @anthropic-ai/claude-code");
|
|
1473
|
+
console.log(" - cursor: Install Cursor IDE (includes `agent` CLI)");
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
console.log(`✓ Providers: ${availableProviders.map((p) => p.provider).join(", ")}`);
|
|
1477
|
+
if (assets.prompts.length === 0 && assets.sweeps.length === 0) {
|
|
1478
|
+
console.log("");
|
|
1479
|
+
console.log("→ No prompts found. Syncing from Prompts Studio...");
|
|
1480
|
+
try {
|
|
1481
|
+
const clientOpts = await resolveClientOptions("quickstart", flags).catch(() => null);
|
|
1482
|
+
if (clientOpts) {
|
|
1483
|
+
const qsClient = new PromptsGptClient(clientOpts);
|
|
1484
|
+
const pulledPrompts = await qsClient.pullPrompts();
|
|
1485
|
+
if (pulledPrompts.length > 0) {
|
|
1486
|
+
const syncResult = await syncPrompts(pulledPrompts, { cwd, agent: "all" });
|
|
1487
|
+
console.log(`✓ Synced ${syncResult.markdown.written.length} prompt(s) and ${syncResult.agents.written.length} agent file(s)`);
|
|
1488
|
+
const refreshed = await discoverWorkspaceAssets(cwd);
|
|
1489
|
+
if (refreshed.prompts.length > 0 || refreshed.sweeps.length > 0) {
|
|
1490
|
+
console.log(`✓ Prompts: ${refreshed.prompts.length} prompt packs, ${refreshed.sweeps.length} sweeps`);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
else {
|
|
1494
|
+
console.log("No prompts found in your Prompts Studio library.");
|
|
1495
|
+
console.log(" Configure prompts at: https://prompts-gpt.com/studio/projects");
|
|
1496
|
+
console.log(" Or run: prompts-gpt generate --goal \"Your task\"");
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
console.log("✗ Could not sync (credentials issue). Run manually:");
|
|
1502
|
+
console.log(" prompts-gpt sync");
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
catch (syncErr) {
|
|
1507
|
+
const syncMsg = syncErr instanceof Error ? syncErr.message : String(syncErr);
|
|
1508
|
+
if (syncErr instanceof PromptsGptApiError && (syncErr.status === 401 || syncErr.status === 403)) {
|
|
1509
|
+
console.log(`✗ Authentication failed: ${syncMsg}`);
|
|
1510
|
+
console.log(" Your token may be invalid or expired. Re-run:");
|
|
1511
|
+
console.log(" prompts-gpt init --token-prompt");
|
|
1512
|
+
}
|
|
1513
|
+
else if (syncMsg.includes("ENOTFOUND") || syncMsg.includes("ECONNREFUSED") || syncMsg.includes("fetch") || syncMsg.includes("network")) {
|
|
1514
|
+
console.log(`✗ Network error: ${syncMsg}`);
|
|
1515
|
+
console.log(" Check your internet connection and try again.");
|
|
1516
|
+
console.log(" You can still use local sweep files without network access.");
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
console.log(`✗ Sync failed: ${syncMsg}`);
|
|
1520
|
+
console.log(" Run manually: prompts-gpt sync");
|
|
1521
|
+
}
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
const refreshedAssets = await discoverWorkspaceAssets(cwd);
|
|
1525
|
+
if (refreshedAssets.prompts.length === 0 && refreshedAssets.sweeps.length === 0) {
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
console.log(`✓ Prompts: ${assets.prompts.length} prompt packs, ${assets.sweeps.length} sweeps`);
|
|
1530
|
+
console.log("");
|
|
1531
|
+
console.log("You're ready!");
|
|
1532
|
+
if (isTTYInteractive() && (assets.prompts.length > 0 || assets.sweeps.length > 0)) {
|
|
1533
|
+
const qsOptions = [];
|
|
1534
|
+
for (const s of assets.sweeps) {
|
|
1535
|
+
qsOptions.push({ label: `sweep: ${s.name}`, value: `sweep:${s.file}` });
|
|
1536
|
+
}
|
|
1537
|
+
for (const p of assets.prompts.slice(0, 5)) {
|
|
1538
|
+
qsOptions.push({ label: `run: ${p.title}`, value: `run:.prompts-gpt/${p.file}` });
|
|
1539
|
+
}
|
|
1540
|
+
qsOptions.push({ label: "(show commands and exit)", value: "exit" });
|
|
1541
|
+
console.log("");
|
|
1542
|
+
const qsPicked = await interactiveSelect("Run something now?", qsOptions);
|
|
1543
|
+
if (qsPicked !== "exit") {
|
|
1544
|
+
const [action, file] = qsPicked.split(":", 2);
|
|
1545
|
+
const cmd = action === "sweep" ? "sweep" : "run";
|
|
1546
|
+
console.log(`\nRunning: prompts-gpt ${cmd} -f ${file}\n`);
|
|
1547
|
+
const { spawnSync: spSync } = await import("node:child_process");
|
|
1548
|
+
const cliEntry = process.argv[1] || require.resolve("./cli.js");
|
|
1549
|
+
const result = spSync(process.execPath, [cliEntry, cmd, "-f", file], { stdio: "inherit", cwd });
|
|
1550
|
+
process.exitCode = result.status ?? 1;
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
console.log("");
|
|
1555
|
+
console.log("Try these commands:");
|
|
1556
|
+
console.log("");
|
|
1557
|
+
if (assets.prompts.length > 0) {
|
|
1558
|
+
console.log(` prompts-gpt run -f .prompts-gpt/${assets.prompts[0].file}`);
|
|
1559
|
+
}
|
|
1560
|
+
if (assets.sweeps.length > 0) {
|
|
1561
|
+
console.log(` prompts-gpt sweep -f ${assets.sweeps[0].file}`);
|
|
1562
|
+
}
|
|
1563
|
+
console.log(" prompts-gpt list — see all available prompts and sweeps");
|
|
1564
|
+
console.log(" prompts-gpt status — check workspace readiness");
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (command === "load-config") {
|
|
1568
|
+
const cwd = getResolvedCwd(flags);
|
|
1569
|
+
const client = await createClientForCommand(command, flags);
|
|
1570
|
+
const project = await client.getProject();
|
|
1571
|
+
const prompts = await client.pullPrompts();
|
|
1572
|
+
if (prompts.length === 0) {
|
|
1573
|
+
console.log("No prompt packs found in the project library. Configure prompts in Prompts Studio first.");
|
|
1574
|
+
console.log(` Open: https://prompts-gpt.com/studio/projects`);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const result = await syncPrompts(prompts, {
|
|
1578
|
+
cwd,
|
|
1579
|
+
outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
|
|
1580
|
+
overwrite: true,
|
|
1581
|
+
agent: getStringFlag(flags, "agent") || "all",
|
|
1582
|
+
});
|
|
1583
|
+
const providers = await detectProviders(cwd);
|
|
1584
|
+
const availableProviders = providers.filter((p) => p.available);
|
|
1585
|
+
let configError = null;
|
|
1586
|
+
let doOverwrite = true;
|
|
1587
|
+
const existingConfig = existsSync(path.resolve(cwd, DEFAULT_RUN_CONFIG_PATH));
|
|
1588
|
+
if (existingConfig && isTTYInteractive() && !Boolean(flags.json)) {
|
|
1589
|
+
const confirmOverwrite = await interactiveInput("Existing run config found. Overwrite? [Y/n]", "Y");
|
|
1590
|
+
if (confirmOverwrite.toLowerCase() === "n" || confirmOverwrite.toLowerCase() === "no") {
|
|
1591
|
+
doOverwrite = false;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
const configResult = doOverwrite ? await initRunConfig({
|
|
1595
|
+
cwd,
|
|
1596
|
+
overwrite: true,
|
|
1597
|
+
}).catch((err) => {
|
|
1598
|
+
configError = err.message;
|
|
1599
|
+
return null;
|
|
1600
|
+
}) : null;
|
|
1601
|
+
if (Boolean(flags.json)) {
|
|
1602
|
+
console.log(JSON.stringify({
|
|
1603
|
+
project: { brandName: project.brandName, websiteUrl: project.websiteUrl },
|
|
1604
|
+
synced: { prompts: result.markdown.written.length, agents: result.agents.written.length },
|
|
1605
|
+
providers: availableProviders.map((p) => p.provider),
|
|
1606
|
+
config: configResult ? "created" : "skipped",
|
|
1607
|
+
}, null, 2));
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
console.log(`Loaded configuration from Prompts Studio for: ${project.brandName}`);
|
|
1611
|
+
console.log(`Synced ${result.markdown.written.length} prompt pack(s) to ${result.markdown.outDir}`);
|
|
1612
|
+
console.log(`Synced ${result.agents.written.length} agent file(s): ${result.agents.targets.join(", ")}`);
|
|
1613
|
+
console.log(`Manifest: ${result.manifest.manifestPath}`);
|
|
1614
|
+
if (configResult) {
|
|
1615
|
+
console.log(`Config: ${configResult.configPath}`);
|
|
1616
|
+
}
|
|
1617
|
+
else if (configError) {
|
|
1618
|
+
console.error(`[config] Skipped: ${configError}`);
|
|
1619
|
+
}
|
|
1620
|
+
if (availableProviders.length > 0 && result.manifest.manifest.prompts.length > 0) {
|
|
1621
|
+
const firstFile = result.manifest.manifest.prompts[0]?.file;
|
|
1622
|
+
if (firstFile) {
|
|
1623
|
+
console.log(`\nReady to run:`);
|
|
1624
|
+
console.log(` prompts-gpt run --prompt-file .prompts-gpt/${firstFile}`);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
const lcAssets = await discoverWorkspaceAssets(cwd);
|
|
1628
|
+
if (lcAssets.sweeps.length > 0) {
|
|
1629
|
+
console.log(`\nSweep files available (${lcAssets.sweeps.length}):`);
|
|
1630
|
+
for (const s of lcAssets.sweeps) {
|
|
1631
|
+
console.log(` prompts-gpt sweep --prompt-file ${s.file}`);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (command === "init") {
|
|
1637
|
+
let token = await resolveTokenInput(flags, { command });
|
|
1638
|
+
if (!token && process.stdin.isTTY && process.stdout.isTTY) {
|
|
1639
|
+
console.log("No token flag provided — prompting interactively.");
|
|
1640
|
+
console.log("Get your token from: https://prompts-gpt.com/studio/projects");
|
|
1641
|
+
console.log("Token format: pgpt_xxxxxxxxxxxxxxxx (starts with 'pgpt_')\n");
|
|
1642
|
+
token = await readTokenFromPrompt(command);
|
|
1643
|
+
}
|
|
1644
|
+
if (!token) {
|
|
1645
|
+
throw new CliError("Run `prompts-gpt init --token <project-token>`, `--token-stdin`, or `--token-prompt`.", CLI_EXIT_CODES.validation, {
|
|
1646
|
+
helpCommand: "init",
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
if (token.length > 256) {
|
|
1650
|
+
throw new CliError("Token value is invalid or too long.", CLI_EXIT_CODES.validation, {
|
|
1651
|
+
helpCommand: "init",
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
if (!token.startsWith("pgpt_")) {
|
|
1655
|
+
throw new CliError("Token must start with `pgpt_`. Copy the full token from the Prompts-GPT dashboard.", CLI_EXIT_CODES.validation, {
|
|
1656
|
+
helpCommand: "init",
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
const cwd = getResolvedCwd(flags);
|
|
1660
|
+
const apiUrlFlag = getStringFlag(flags, "api-url");
|
|
1661
|
+
if (apiUrlFlag) {
|
|
1662
|
+
try {
|
|
1663
|
+
const parsed = new URL(apiUrlFlag);
|
|
1664
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
1665
|
+
throw new CliError("--api-url must use https or http.", CLI_EXIT_CODES.validation, { helpCommand: "init" });
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
catch (e) {
|
|
1669
|
+
if (e instanceof CliError)
|
|
1670
|
+
throw e;
|
|
1671
|
+
throw new CliError(`--api-url is not a valid URL: ${apiUrlFlag}`, CLI_EXIT_CODES.validation, { helpCommand: "init" });
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
const result = await saveLocalCredentials({
|
|
1675
|
+
token,
|
|
1676
|
+
apiUrl: apiUrlFlag || DEFAULT_PROMPTS_GPT_API_URL,
|
|
1677
|
+
cwd,
|
|
1678
|
+
});
|
|
1679
|
+
console.log(`Saved Prompts-GPT credentials to ${result.credentialsPath}`);
|
|
1680
|
+
console.log("The credentials file is added to .gitignore.");
|
|
1681
|
+
if (isTTYInteractive()) {
|
|
1682
|
+
console.log("");
|
|
1683
|
+
const runQs = await interactiveInput("Run quickstart now? [Y/n]", "Y");
|
|
1684
|
+
if (runQs.toLowerCase() !== "n" && runQs.toLowerCase() !== "no") {
|
|
1685
|
+
const { spawnSync: spSync } = await import("node:child_process");
|
|
1686
|
+
const cliEntry = process.argv[1] || require.resolve("./cli.js");
|
|
1687
|
+
const qsResult = spSync(process.execPath, [cliEntry, "quickstart", "--cwd", cwd], { stdio: "inherit", cwd });
|
|
1688
|
+
process.exitCode = qsResult.status ?? 1;
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
console.log("");
|
|
1693
|
+
console.log("Next steps:");
|
|
1694
|
+
console.log(" prompts-gpt quickstart — interactive setup (recommended)");
|
|
1695
|
+
console.log(" prompts-gpt sync — pull prompts, agent files, and model registry");
|
|
1696
|
+
console.log(" prompts-gpt sync-models — sync latest model registry from prompts-gpt.com");
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (command === "pull") {
|
|
1700
|
+
const parsedLimit = parseLimitFlag(getStringFlag(flags, "limit"));
|
|
1701
|
+
const client = await createClientForCommand(command, flags);
|
|
1702
|
+
const prompts = await client.pullPrompts(buildPullQuery(flags, parsedLimit));
|
|
1703
|
+
if (prompts.length === 0) {
|
|
1704
|
+
console.log("No prompts matched the query. Try different filters or check the project prompt library.");
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
const pullCwd = getResolvedCwd(flags);
|
|
1708
|
+
const pullOutDir = getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR;
|
|
1709
|
+
const result = await writePromptMarkdownFiles(prompts, {
|
|
1710
|
+
cwd: pullCwd,
|
|
1711
|
+
outDir: pullOutDir,
|
|
1712
|
+
overwrite: Boolean(flags.overwrite),
|
|
1713
|
+
});
|
|
1714
|
+
const pullManifest = await writePromptManifest(prompts, { cwd: pullCwd, outDir: pullOutDir });
|
|
1715
|
+
console.log(`Wrote ${result.written.length} prompt file(s) to ${result.outDir}.`);
|
|
1716
|
+
console.log(`Updated manifest: ${pullManifest.manifestPath}`);
|
|
1717
|
+
if (Boolean(flags.overwrite)) {
|
|
1718
|
+
const agentResult = await writeAgentFiles(prompts, { cwd: pullCwd, agent: "all", overwriteAgentFiles: true });
|
|
1719
|
+
if (agentResult.written.length > 0) {
|
|
1720
|
+
console.log(`Updated ${agentResult.written.length} agent file(s): ${agentResult.targets.join(", ")}`);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
if (result.skipped.length) {
|
|
1724
|
+
console.log(`Skipped ${result.skipped.length} existing file(s). Use --overwrite to replace them.`);
|
|
1725
|
+
}
|
|
1726
|
+
if (result.written.length > 0) {
|
|
1727
|
+
console.log("");
|
|
1728
|
+
console.log("Run a pulled prompt:");
|
|
1729
|
+
console.log(` prompts-gpt run -f ${result.written[0]}`);
|
|
1730
|
+
console.log(" prompts-gpt list — see all available prompts");
|
|
1731
|
+
}
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
if (command === "generate") {
|
|
1735
|
+
validateToolFlag(getStringFlag(flags, "tool"));
|
|
1736
|
+
const goal = getStringFlag(flags, "goal");
|
|
1737
|
+
if (!goal) {
|
|
1738
|
+
throw new CliError("Prompt generation requires `--goal`.\n\n" +
|
|
1739
|
+
"Example:\n" +
|
|
1740
|
+
" prompts-gpt generate --goal \"Review pull requests for security issues\"\n" +
|
|
1741
|
+
" prompts-gpt generate --goal \"Write unit tests\" --sync-agents\n\n" +
|
|
1742
|
+
"Use --sync-agents to also write agent files (AGENTS.md, .cursor/rules, etc.).", CLI_EXIT_CODES.validation, { helpCommand: "generate" });
|
|
1743
|
+
}
|
|
1744
|
+
validateAgentFlag(getStringFlag(flags, "agent"));
|
|
1745
|
+
if (!Boolean(flags.json)) {
|
|
1746
|
+
printDataTransmissionNotice("generate", { goal, context: getStringFlag(flags, "context"), constraints: getStringFlag(flags, "constraints") });
|
|
1747
|
+
}
|
|
1748
|
+
const client = await createClientForCommand(command, flags);
|
|
1749
|
+
const prompt = await client.generatePrompt(buildGenerateInput(flags));
|
|
1750
|
+
const cwd = getResolvedCwd(flags);
|
|
1751
|
+
const result = await writePromptMarkdownFiles([prompt], {
|
|
1752
|
+
cwd,
|
|
1753
|
+
outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
|
|
1754
|
+
overwrite: Boolean(flags.overwrite),
|
|
1755
|
+
});
|
|
1756
|
+
const shouldSyncAgents = Boolean(flags["sync-agents"]) || typeof getStringFlag(flags, "agent") === "string";
|
|
1757
|
+
if (shouldSyncAgents) {
|
|
1758
|
+
const target = getStringFlag(flags, "agent") || "all";
|
|
1759
|
+
const pulled = await client.pullPrompts().catch((err) => {
|
|
1760
|
+
if (!Boolean(flags.json)) {
|
|
1761
|
+
console.error(`⚠ Could not pull existing prompts for agent sync: ${err.message}`);
|
|
1762
|
+
console.error(" Agent files will only include the newly generated prompt.");
|
|
1763
|
+
console.error(" Run \`prompts-gpt sync\` afterward to include all library prompts.");
|
|
1764
|
+
}
|
|
1765
|
+
return [];
|
|
1766
|
+
});
|
|
1767
|
+
const syncedPrompts = mergePromptPacks([prompt, ...pulled]);
|
|
1768
|
+
const agentResult = await writeAgentFiles(syncedPrompts, {
|
|
1769
|
+
cwd,
|
|
1770
|
+
agent: target,
|
|
1771
|
+
overwriteAgentFiles: true,
|
|
1772
|
+
});
|
|
1773
|
+
const manifestResult = await writePromptManifest(syncedPrompts, {
|
|
1774
|
+
cwd,
|
|
1775
|
+
outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
|
|
1776
|
+
});
|
|
1777
|
+
console.log(`Synced ${agentResult.written.length} agent file(s) for ${agentResult.targets.join(", ")}.`);
|
|
1778
|
+
console.log(`Updated manifest: ${manifestResult.manifestPath}`);
|
|
1779
|
+
}
|
|
1780
|
+
console.log(`Generated: ${prompt.title}`);
|
|
1781
|
+
console.log(`Wrote to ${result.written[0] ?? result.outDir}.`);
|
|
1782
|
+
if (result.skipped.length) {
|
|
1783
|
+
console.log("Skipped existing generated prompt. Use --overwrite to replace it.");
|
|
1784
|
+
}
|
|
1785
|
+
const genFile = result.written[0];
|
|
1786
|
+
if (genFile && isTTYInteractive()) {
|
|
1787
|
+
const genProviders = await detectProviders(cwd);
|
|
1788
|
+
if (genProviders.some((p) => p.available)) {
|
|
1789
|
+
console.log("");
|
|
1790
|
+
const runNow = await interactiveInput("Run this prompt now? [Y/n]", "Y");
|
|
1791
|
+
if (runNow.toLowerCase() !== "n" && runNow.toLowerCase() !== "no") {
|
|
1792
|
+
console.log(`\nRunning: prompts-gpt run -f ${genFile}\n`);
|
|
1793
|
+
const { spawnSync: spSync } = await import("node:child_process");
|
|
1794
|
+
const cliEntry = process.argv[1] || require.resolve("./cli.js");
|
|
1795
|
+
const genResult = spSync(process.execPath, [cliEntry, "run", "-f", genFile], { stdio: "inherit", cwd });
|
|
1796
|
+
process.exitCode = genResult.status ?? 1;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (command === "sync") {
|
|
1803
|
+
validateToolFlag(getStringFlag(flags, "tool"));
|
|
1804
|
+
validateAgentFlag(getStringFlag(flags, "agent"));
|
|
1805
|
+
const parsedLimit = parseLimitFlag(getStringFlag(flags, "limit"));
|
|
1806
|
+
const prompts = [];
|
|
1807
|
+
const goal = getStringFlag(flags, "goal");
|
|
1808
|
+
const generatedOnly = Boolean(flags["generated-only"]);
|
|
1809
|
+
let client;
|
|
1810
|
+
try {
|
|
1811
|
+
client = await createClientForCommand(command, flags);
|
|
1812
|
+
}
|
|
1813
|
+
catch (clientErr) {
|
|
1814
|
+
if (clientErr instanceof CliError && clientErr.exitCode === CLI_EXIT_CODES.auth) {
|
|
1815
|
+
throw new CliError(`Authentication failed. Your token may be invalid, expired, or lack the required scope.\n\n` +
|
|
1816
|
+
`Re-run: prompts-gpt init --token-prompt\n` +
|
|
1817
|
+
`Get a new token: https://prompts-gpt.com/studio/projects`, CLI_EXIT_CODES.auth, { helpCommand: "sync" });
|
|
1818
|
+
}
|
|
1819
|
+
throw clientErr;
|
|
1820
|
+
}
|
|
1821
|
+
if (goal && !Boolean(flags.json)) {
|
|
1822
|
+
printDataTransmissionNotice("sync", { goal, context: getStringFlag(flags, "context"), constraints: getStringFlag(flags, "constraints") });
|
|
1823
|
+
}
|
|
1824
|
+
if (goal) {
|
|
1825
|
+
prompts.push(await client.generatePrompt(buildGenerateInput(flags)));
|
|
1826
|
+
}
|
|
1827
|
+
if (!generatedOnly) {
|
|
1828
|
+
prompts.push(...await client.pullPrompts(buildPullQuery(flags, parsedLimit)));
|
|
1829
|
+
}
|
|
1830
|
+
if (prompts.length === 0) {
|
|
1831
|
+
throw new CliError("No prompts to sync. Provide `--goal` or remove `--generated-only`.", CLI_EXIT_CODES.validation, {
|
|
1832
|
+
helpCommand: "sync",
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
if (Boolean(flags["dry-run"])) {
|
|
1836
|
+
const agentTarget = getStringFlag(flags, "agent") || "all";
|
|
1837
|
+
console.log(`[dry-run] Would sync ${prompts.length} prompt(s) for agent target: ${agentTarget}`);
|
|
1838
|
+
for (const p of prompts)
|
|
1839
|
+
console.log(` - ${p.title} (${p.source})`);
|
|
1840
|
+
const targetList = agentTarget === "all" ? SUPPORTED_AGENT_TARGETS.join(", ") : agentTarget;
|
|
1841
|
+
console.log(`\n Agent file targets: ${targetList}`);
|
|
1842
|
+
console.log(` Output dir: ${getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR}`);
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
const result = await syncPrompts(prompts, {
|
|
1846
|
+
cwd: getResolvedCwd(flags),
|
|
1847
|
+
outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
|
|
1848
|
+
overwrite: Boolean(flags.overwrite),
|
|
1849
|
+
agent: getStringFlag(flags, "agent") || "all",
|
|
1850
|
+
});
|
|
1851
|
+
console.log(`Synced ${result.markdown.written.length} new prompt file(s) to ${result.markdown.outDir}.`);
|
|
1852
|
+
if (result.markdown.skipped.length) {
|
|
1853
|
+
console.log(`Kept ${result.markdown.skipped.length} existing file(s) unchanged. Use --overwrite to replace.`);
|
|
1854
|
+
}
|
|
1855
|
+
console.log(`Synced ${result.agents.written.length} agent file(s): ${result.agents.targets.join(", ")}.`);
|
|
1856
|
+
console.log(`Manifest: ${result.manifest.manifestPath}`);
|
|
1857
|
+
const syncCwd = getResolvedCwd(flags);
|
|
1858
|
+
try {
|
|
1859
|
+
const syncedModels = await client.fetchModels();
|
|
1860
|
+
if (Object.keys(syncedModels).length > 0) {
|
|
1861
|
+
const { writeFile: fsWrite, mkdir: fsMkdir } = await import("node:fs/promises");
|
|
1862
|
+
const modelsDir = path.resolve(syncCwd, DEFAULT_PROMPTS_GPT_OUT_DIR);
|
|
1863
|
+
await fsMkdir(modelsDir, { recursive: true });
|
|
1864
|
+
const modelsRegistry = {};
|
|
1865
|
+
for (const [p, ml] of Object.entries(syncedModels)) {
|
|
1866
|
+
modelsRegistry[p] = { models: ml, syncedAt: new Date().toISOString() };
|
|
1867
|
+
}
|
|
1868
|
+
await fsWrite(path.resolve(modelsDir, ".models.json"), JSON.stringify(modelsRegistry, null, 2) + "\n");
|
|
1869
|
+
console.log(`Synced model registry (${Object.keys(syncedModels).length} providers).`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
catch { /* model sync is best-effort */ }
|
|
1873
|
+
const postSyncAssets = await discoverWorkspaceAssets(syncCwd);
|
|
1874
|
+
if (postSyncAssets.sweeps.length > 0) {
|
|
1875
|
+
console.log(`Sweeps: ${postSyncAssets.sweeps.length} sweep file(s) available.`);
|
|
1876
|
+
}
|
|
1877
|
+
const postProviders = await detectProviders(syncCwd);
|
|
1878
|
+
const postAvailable = postProviders.filter((p) => p.available);
|
|
1879
|
+
if (postAvailable.length > 0 && postSyncAssets.prompts.length > 0) {
|
|
1880
|
+
console.log("");
|
|
1881
|
+
console.log("Next steps:");
|
|
1882
|
+
console.log(` prompts-gpt run --prompt-file .prompts-gpt/${postSyncAssets.prompts[0].file}`);
|
|
1883
|
+
if (postSyncAssets.sweeps.length > 0) {
|
|
1884
|
+
console.log(` prompts-gpt sweep --prompt-file ${postSyncAssets.sweeps[0].file}`);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
if (command === "project") {
|
|
1890
|
+
const clientOptions = await resolveClientOptions(command, flags);
|
|
1891
|
+
const client = new PromptsGptClient(clientOptions);
|
|
1892
|
+
const project = await client.getProject();
|
|
1893
|
+
const apiUrl = clientOptions.apiUrl;
|
|
1894
|
+
if (Boolean(flags.json)) {
|
|
1895
|
+
console.log(JSON.stringify({
|
|
1896
|
+
project,
|
|
1897
|
+
apiUrl,
|
|
1898
|
+
}, null, 2));
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
console.log(`Project: ${project.brandName}`);
|
|
1902
|
+
console.log(`Website: ${project.websiteUrl}`);
|
|
1903
|
+
console.log(`Industry: ${project.industryCategory}`);
|
|
1904
|
+
console.log(`Language: ${project.targetLanguage}`);
|
|
1905
|
+
console.log(`Countries: ${formatList(project.targetCountries)}`);
|
|
1906
|
+
console.log(`Aliases: ${formatList(project.brandAliases)}`);
|
|
1907
|
+
console.log(`Keywords: ${formatList(project.productKeywords)}`);
|
|
1908
|
+
console.log(`Personas: ${formatList(project.targetPersonas)}`);
|
|
1909
|
+
console.log(`Competitors: ${formatCompetitors(project.competitors)}`);
|
|
1910
|
+
console.log(`API URL: ${apiUrl}`);
|
|
1911
|
+
if (isTTYInteractive()) {
|
|
1912
|
+
console.log("");
|
|
1913
|
+
const syncNow = await interactiveInput("Sync prompts from this project? [Y/n]", "Y");
|
|
1914
|
+
if (syncNow.toLowerCase() !== "n" && syncNow.toLowerCase() !== "no") {
|
|
1915
|
+
console.log("\nSyncing...");
|
|
1916
|
+
const { spawnSync: spSync } = await import("node:child_process");
|
|
1917
|
+
const cliEntry = process.argv[1] || require.resolve("./cli.js");
|
|
1918
|
+
const projCwd = getResolvedCwd(flags);
|
|
1919
|
+
const syncResult = spSync(process.execPath, [cliEntry, "sync", "--cwd", projCwd], { stdio: "inherit", cwd: projCwd });
|
|
1920
|
+
process.exitCode = syncResult.status ?? 1;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
async function createClientForCommand(command, flags) {
|
|
1927
|
+
return new PromptsGptClient(await resolveClientOptions(command, flags));
|
|
1928
|
+
}
|
|
1929
|
+
async function resolveClientOptions(command, flags) {
|
|
1930
|
+
const cwd = getResolvedCwd(flags);
|
|
1931
|
+
const explicitToken = await resolveTokenInput(flags, { command });
|
|
1932
|
+
const explicitApiUrl = getStringFlag(flags, "api-url");
|
|
1933
|
+
const credentialsPath = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, PROMPTS_GPT_CREDENTIALS_FILE);
|
|
1934
|
+
const hasCredentialsFile = existsSync(credentialsPath);
|
|
1935
|
+
const credentials = await loadLocalCredentials(cwd);
|
|
1936
|
+
if (hasCredentialsFile && !credentials && !explicitToken) {
|
|
1937
|
+
throw new CliError(`Could not read local credentials at ${credentialsPath}. Re-run \`prompts-gpt init --token <project-token>\` to replace the file.`, CLI_EXIT_CODES.auth, { helpCommand: command });
|
|
1938
|
+
}
|
|
1939
|
+
const token = explicitToken || credentials?.token || "";
|
|
1940
|
+
if (!token && !process.stdin.isTTY) {
|
|
1941
|
+
// CI fallback — never read process.env in the importable SDK, only in the CLI entrypoint
|
|
1942
|
+
const envToken = process.env.PROMPTS_GPT_TOKEN?.trim();
|
|
1943
|
+
if (envToken) {
|
|
1944
|
+
if (!envToken.startsWith("pgpt_")) {
|
|
1945
|
+
throw new CliError("PROMPTS_GPT_TOKEN env var must start with 'pgpt_'. Check the value.", CLI_EXIT_CODES.auth);
|
|
1946
|
+
}
|
|
1947
|
+
return {
|
|
1948
|
+
token: envToken,
|
|
1949
|
+
apiUrl: explicitApiUrl || credentials?.apiUrl || DEFAULT_PROMPTS_GPT_API_URL,
|
|
1950
|
+
fetch,
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
if (!token) {
|
|
1955
|
+
const cloudCommands = new Set(["sync", "generate", "pull", "load-config", "project", "sync-models"]);
|
|
1956
|
+
const extra = cloudCommands.has(command)
|
|
1957
|
+
? `\n\nThis command connects to prompts-gpt.com. Set up credentials first:\n prompts-gpt init --token <project-token>\n prompts-gpt quickstart`
|
|
1958
|
+
: "";
|
|
1959
|
+
throw new CliError(`Project token is missing. Run \`prompts-gpt init --token <project-token>\` or pass \`--token\`, \`--token-stdin\`, or \`--token-prompt\` for this command.${extra}`, CLI_EXIT_CODES.auth, { helpCommand: command });
|
|
1960
|
+
}
|
|
1961
|
+
return {
|
|
1962
|
+
token,
|
|
1963
|
+
apiUrl: explicitApiUrl || credentials?.apiUrl || DEFAULT_PROMPTS_GPT_API_URL,
|
|
1964
|
+
fetch,
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
function buildPullQuery(flags, limit) {
|
|
1968
|
+
return {
|
|
1969
|
+
q: getStringFlag(flags, "query") || getStringFlag(flags, "q"),
|
|
1970
|
+
category: getStringFlag(flags, "category"),
|
|
1971
|
+
tool: getStringFlag(flags, "tool"),
|
|
1972
|
+
outputType: getStringFlag(flags, "output-type"),
|
|
1973
|
+
limit,
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function parseCommandFlags(command, argv) {
|
|
1977
|
+
const options = getCommandOptions(command);
|
|
1978
|
+
try {
|
|
1979
|
+
const result = parseArgs({
|
|
1980
|
+
args: argv,
|
|
1981
|
+
options,
|
|
1982
|
+
allowPositionals: false,
|
|
1983
|
+
strict: true,
|
|
1984
|
+
});
|
|
1985
|
+
const values = result.values;
|
|
1986
|
+
validateFlagConflicts(command, values);
|
|
1987
|
+
return values;
|
|
1988
|
+
}
|
|
1989
|
+
catch (error) {
|
|
1990
|
+
throw toCliParseError(error, command);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
function handleHelpCommand(argv) {
|
|
1994
|
+
try {
|
|
1995
|
+
const { positionals, values } = parseArgs({
|
|
1996
|
+
args: argv,
|
|
1997
|
+
options: { help: { type: "boolean" } },
|
|
1998
|
+
allowPositionals: true,
|
|
1999
|
+
strict: true,
|
|
2000
|
+
});
|
|
2001
|
+
if (values.help) {
|
|
2002
|
+
printHelp();
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
if (positionals.length > 1) {
|
|
2006
|
+
throw new CliError("The `help` command accepts at most one command name.", CLI_EXIT_CODES.usage, {
|
|
2007
|
+
helpCommand: "help",
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
if (positionals.length === 0) {
|
|
2011
|
+
printHelp();
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
const topic = asCommandName(positionals[0]);
|
|
2015
|
+
if (!topic) {
|
|
2016
|
+
throw new CliError(`Unknown help topic: ${positionals[0]}.`, CLI_EXIT_CODES.usage, {
|
|
2017
|
+
helpCommand: "help",
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
printHelp(topic);
|
|
2021
|
+
}
|
|
2022
|
+
catch (error) {
|
|
2023
|
+
if (error instanceof CliError)
|
|
2024
|
+
throw error;
|
|
2025
|
+
throw toCliParseError(error, "help");
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
function getCommandOptions(command) {
|
|
2029
|
+
const common = {
|
|
2030
|
+
help: { type: "boolean" },
|
|
2031
|
+
token: { type: "string" },
|
|
2032
|
+
"token-stdin": { type: "boolean" },
|
|
2033
|
+
"token-prompt": { type: "boolean" },
|
|
2034
|
+
"api-url": { type: "string" },
|
|
2035
|
+
cwd: { type: "string" },
|
|
2036
|
+
};
|
|
2037
|
+
if (command === "init") {
|
|
2038
|
+
return common;
|
|
2039
|
+
}
|
|
2040
|
+
if (command === "setup") {
|
|
2041
|
+
return {
|
|
2042
|
+
help: { type: "boolean" },
|
|
2043
|
+
cwd: { type: "string" },
|
|
2044
|
+
json: { type: "boolean" },
|
|
2045
|
+
overwrite: { type: "boolean" },
|
|
2046
|
+
"prompt-file": { type: "string" },
|
|
2047
|
+
"prompt-dir": { type: "string" },
|
|
2048
|
+
manifest: { type: "string" },
|
|
2049
|
+
"prompt-files": { type: "string" },
|
|
2050
|
+
agent: { type: "string" },
|
|
2051
|
+
"provider-order": { type: "string" },
|
|
2052
|
+
timeout: { type: "string" },
|
|
2053
|
+
"retry-count": { type: "string" },
|
|
2054
|
+
"artifacts-dir": { type: "string" },
|
|
2055
|
+
"allow-destructive-git": { type: "boolean" },
|
|
2056
|
+
"codex-model": { type: "string" },
|
|
2057
|
+
"cursor-model": { type: "string" },
|
|
2058
|
+
"claude-model": { type: "string" },
|
|
2059
|
+
"copilot-model": { type: "string" },
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
if (command === "project") {
|
|
2063
|
+
return {
|
|
2064
|
+
...common,
|
|
2065
|
+
json: { type: "boolean" },
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
if (command === "pull") {
|
|
2069
|
+
return {
|
|
2070
|
+
...common,
|
|
2071
|
+
query: { type: "string" },
|
|
2072
|
+
q: { type: "string" },
|
|
2073
|
+
category: { type: "string" },
|
|
2074
|
+
tool: { type: "string" },
|
|
2075
|
+
"output-type": { type: "string" },
|
|
2076
|
+
limit: { type: "string" },
|
|
2077
|
+
out: { type: "string" },
|
|
2078
|
+
overwrite: { type: "boolean" },
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
if (command === "generate") {
|
|
2082
|
+
return {
|
|
2083
|
+
...common,
|
|
2084
|
+
goal: { type: "string" },
|
|
2085
|
+
context: { type: "string" },
|
|
2086
|
+
constraints: { type: "string" },
|
|
2087
|
+
"desired-output": { type: "string" },
|
|
2088
|
+
tool: { type: "string" },
|
|
2089
|
+
mode: { type: "string" },
|
|
2090
|
+
"artifact-type": { type: "string" },
|
|
2091
|
+
"web-search": { type: "boolean" },
|
|
2092
|
+
out: { type: "string" },
|
|
2093
|
+
overwrite: { type: "boolean" },
|
|
2094
|
+
agent: { type: "string" },
|
|
2095
|
+
"sync-agents": { type: "boolean" },
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
if (command === "sync") {
|
|
2099
|
+
return {
|
|
2100
|
+
...common,
|
|
2101
|
+
goal: { type: "string" },
|
|
2102
|
+
context: { type: "string" },
|
|
2103
|
+
constraints: { type: "string" },
|
|
2104
|
+
"desired-output": { type: "string" },
|
|
2105
|
+
tool: { type: "string" },
|
|
2106
|
+
mode: { type: "string" },
|
|
2107
|
+
"artifact-type": { type: "string" },
|
|
2108
|
+
"web-search": { type: "boolean" },
|
|
2109
|
+
query: { type: "string" },
|
|
2110
|
+
q: { type: "string" },
|
|
2111
|
+
category: { type: "string" },
|
|
2112
|
+
"output-type": { type: "string" },
|
|
2113
|
+
limit: { type: "string" },
|
|
2114
|
+
out: { type: "string" },
|
|
2115
|
+
overwrite: { type: "boolean" },
|
|
2116
|
+
agent: { type: "string" },
|
|
2117
|
+
"generated-only": { type: "boolean" },
|
|
2118
|
+
"dry-run": { type: "boolean" },
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
if (command === "run") {
|
|
2122
|
+
return {
|
|
2123
|
+
help: { type: "boolean" },
|
|
2124
|
+
cwd: { type: "string" },
|
|
2125
|
+
json: { type: "boolean" },
|
|
2126
|
+
"prompt-file": { type: "string", short: "f" },
|
|
2127
|
+
agent: { type: "string" },
|
|
2128
|
+
model: { type: "string" },
|
|
2129
|
+
timeout: { type: "string" },
|
|
2130
|
+
"artifacts-dir": { type: "string" },
|
|
2131
|
+
"run-id": { type: "string" },
|
|
2132
|
+
sandbox: { type: "string" },
|
|
2133
|
+
"no-approve-mcps": { type: "boolean" },
|
|
2134
|
+
background: { type: "boolean" },
|
|
2135
|
+
"permission-mode": { type: "string" },
|
|
2136
|
+
"dry-run": { type: "boolean" },
|
|
2137
|
+
"non-interactive": { type: "boolean" },
|
|
2138
|
+
verbose: { type: "boolean", short: "v" },
|
|
2139
|
+
open: { type: "boolean" },
|
|
2140
|
+
"list-models": { type: "boolean" },
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
if (command === "sweep") {
|
|
2144
|
+
return {
|
|
2145
|
+
help: { type: "boolean" },
|
|
2146
|
+
cwd: { type: "string" },
|
|
2147
|
+
json: { type: "boolean" },
|
|
2148
|
+
"prompt-file": { type: "string", short: "f" },
|
|
2149
|
+
agent: { type: "string" },
|
|
2150
|
+
model: { type: "string" },
|
|
2151
|
+
iterations: { type: "string", short: "n" },
|
|
2152
|
+
"iteration-timeout": { type: "string" },
|
|
2153
|
+
"max-retries": { type: "string" },
|
|
2154
|
+
"artifacts-dir": { type: "string" },
|
|
2155
|
+
"run-id": { type: "string" },
|
|
2156
|
+
sandbox: { type: "string" },
|
|
2157
|
+
"no-approve-mcps": { type: "boolean" },
|
|
2158
|
+
phase: { type: "string" },
|
|
2159
|
+
"dry-run": { type: "boolean" },
|
|
2160
|
+
"max-run-dirs": { type: "string" },
|
|
2161
|
+
"summary-lines": { type: "string" },
|
|
2162
|
+
background: { type: "boolean" },
|
|
2163
|
+
"permission-mode": { type: "string" },
|
|
2164
|
+
"non-interactive": { type: "boolean" },
|
|
2165
|
+
verbose: { type: "boolean", short: "v" },
|
|
2166
|
+
open: { type: "boolean" },
|
|
2167
|
+
quiet: { type: "boolean", short: "q" },
|
|
2168
|
+
"list-models": { type: "boolean" },
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
if (command === "models") {
|
|
2172
|
+
return {
|
|
2173
|
+
...common,
|
|
2174
|
+
provider: { type: "string" },
|
|
2175
|
+
agent: { type: "string" },
|
|
2176
|
+
add: { type: "string" },
|
|
2177
|
+
remove: { type: "string" },
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
if (command === "sync-models") {
|
|
2181
|
+
return common;
|
|
2182
|
+
}
|
|
2183
|
+
if (command === "run-batch") {
|
|
2184
|
+
return {
|
|
2185
|
+
help: { type: "boolean" },
|
|
2186
|
+
cwd: { type: "string" },
|
|
2187
|
+
json: { type: "boolean" },
|
|
2188
|
+
manifest: { type: "string" },
|
|
2189
|
+
"prompt-files": { type: "string" },
|
|
2190
|
+
agent: { type: "string" },
|
|
2191
|
+
model: { type: "string" },
|
|
2192
|
+
timeout: { type: "string" },
|
|
2193
|
+
"artifacts-dir": { type: "string" },
|
|
2194
|
+
"non-interactive": { type: "boolean" },
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
if (command === "load-config") {
|
|
2198
|
+
return {
|
|
2199
|
+
...common,
|
|
2200
|
+
json: { type: "boolean" },
|
|
2201
|
+
out: { type: "string" },
|
|
2202
|
+
agent: { type: "string" },
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
if (command === "quickstart" || command === "list" || command === "status" || command === "validate" || command === "providers" || command === "doctor") {
|
|
2206
|
+
return {
|
|
2207
|
+
help: { type: "boolean" },
|
|
2208
|
+
cwd: { type: "string" },
|
|
2209
|
+
json: { type: "boolean" },
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
throw new CliError(`Unsupported command: ${command}.`, CLI_EXIT_CODES.usage, {
|
|
2213
|
+
helpCommand: "help",
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
function toCliParseError(error, command) {
|
|
2217
|
+
const runnableCmd = isRunnableCommand(command) ? command : undefined;
|
|
2218
|
+
const message = normalizeParseErrorMessage(error instanceof Error ? error.message : String(error), runnableCmd);
|
|
2219
|
+
return new CliError(message, CLI_EXIT_CODES.usage, {
|
|
2220
|
+
helpCommand: command,
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
function normalizeParseErrorMessage(message, command) {
|
|
2224
|
+
const trimmed = message.trim();
|
|
2225
|
+
if (trimmed.startsWith("Unknown option")) {
|
|
2226
|
+
const match = trimmed.match(/Unknown option '(--[^']+)'/);
|
|
2227
|
+
const unknownFlag = match?.[1];
|
|
2228
|
+
const base = trimmed.endsWith(".") ? trimmed : `${trimmed}.`;
|
|
2229
|
+
if (unknownFlag && command) {
|
|
2230
|
+
const suggestion = suggestClosestFlag(unknownFlag, command);
|
|
2231
|
+
if (suggestion) {
|
|
2232
|
+
return `${base} Did you mean ${suggestion}?\nRun \`prompts-gpt help ${command}\` to see all available flags.`;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return `${base}\nRun \`prompts-gpt help ${command ?? ""}\` to see all available flags.`;
|
|
2236
|
+
}
|
|
2237
|
+
return trimmed;
|
|
2238
|
+
}
|
|
2239
|
+
function suggestClosestFlag(unknown, command) {
|
|
2240
|
+
const options = getCommandOptions(command);
|
|
2241
|
+
const flagName = unknown.replace(/^--/, "");
|
|
2242
|
+
const candidates = Object.keys(options);
|
|
2243
|
+
let best = null;
|
|
2244
|
+
let bestScore = 0;
|
|
2245
|
+
for (const candidate of candidates) {
|
|
2246
|
+
const score = computeFlagSimilarity(flagName, candidate);
|
|
2247
|
+
if (score > bestScore && score >= 0.4) {
|
|
2248
|
+
bestScore = score;
|
|
2249
|
+
best = candidate;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return best ? `--${best}` : null;
|
|
2253
|
+
}
|
|
2254
|
+
function computeFlagSimilarity(a, b) {
|
|
2255
|
+
if (a === b)
|
|
2256
|
+
return 1;
|
|
2257
|
+
const shorter = a.length < b.length ? a : b;
|
|
2258
|
+
const longer = a.length < b.length ? b : a;
|
|
2259
|
+
if (longer.startsWith(shorter) || longer.endsWith(shorter))
|
|
2260
|
+
return 0.8;
|
|
2261
|
+
if (longer.includes(shorter) && shorter.length >= 3)
|
|
2262
|
+
return 0.6;
|
|
2263
|
+
if (shorter.length === 0)
|
|
2264
|
+
return 0;
|
|
2265
|
+
let matches = 0;
|
|
2266
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
2267
|
+
if (a[i] === b[i])
|
|
2268
|
+
matches++;
|
|
2269
|
+
}
|
|
2270
|
+
return matches / Math.max(a.length, b.length);
|
|
2271
|
+
}
|
|
2272
|
+
function validateFlagConflicts(command, flags) {
|
|
2273
|
+
const tokenSourceCount = [Boolean(flags.token), Boolean(flags["token-stdin"]), Boolean(flags["token-prompt"])].filter(Boolean).length;
|
|
2274
|
+
if (tokenSourceCount > 1) {
|
|
2275
|
+
throw new CliError("Use only one token source: `--token`, `--token-stdin`, or `--token-prompt`.", CLI_EXIT_CODES.usage, {
|
|
2276
|
+
helpCommand: command,
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
if (flags.query && flags.q) {
|
|
2280
|
+
throw new CliError("Use either `--query` or `--q`, not both.", CLI_EXIT_CODES.usage, {
|
|
2281
|
+
helpCommand: command,
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
const promptSourceCount = [
|
|
2285
|
+
Boolean(flags["prompt-file"]),
|
|
2286
|
+
Boolean(flags["prompt-files"]),
|
|
2287
|
+
Boolean(flags.manifest),
|
|
2288
|
+
Boolean(flags["prompt-dir"]),
|
|
2289
|
+
].filter(Boolean).length;
|
|
2290
|
+
if (command === "setup" && promptSourceCount > 1) {
|
|
2291
|
+
throw new CliError("Use only one prompt source for `setup`: `--prompt-file`, `--prompt-files`, `--manifest`, or `--prompt-dir`.", CLI_EXIT_CODES.usage, {
|
|
2292
|
+
helpCommand: command,
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
if (command === "run-batch" && Boolean(flags["prompt-files"]) && Boolean(flags.manifest)) {
|
|
2296
|
+
throw new CliError("Use either `--prompt-files` or `--manifest`, not both.", CLI_EXIT_CODES.usage, {
|
|
2297
|
+
helpCommand: command,
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
async function resolveTokenInput(flags, options) {
|
|
2302
|
+
const explicitToken = getStringFlag(flags, "token")?.trim();
|
|
2303
|
+
if (explicitToken) {
|
|
2304
|
+
return explicitToken;
|
|
2305
|
+
}
|
|
2306
|
+
if (flags["token-stdin"]) {
|
|
2307
|
+
const token = await readTokenFromStdin();
|
|
2308
|
+
if (!token) {
|
|
2309
|
+
throw new CliError("No token was received on stdin. Pipe a project token into `--token-stdin`.", CLI_EXIT_CODES.validation, {
|
|
2310
|
+
helpCommand: options.command,
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
return token;
|
|
2314
|
+
}
|
|
2315
|
+
if (flags["token-prompt"]) {
|
|
2316
|
+
return readTokenFromPrompt(options.command);
|
|
2317
|
+
}
|
|
2318
|
+
return undefined;
|
|
2319
|
+
}
|
|
2320
|
+
async function readTokenFromStdin() {
|
|
2321
|
+
if (process.stdin.isTTY) {
|
|
2322
|
+
throw new CliError("`--token-stdin` expects piped input. Use `--token-prompt` for an interactive terminal.", CLI_EXIT_CODES.usage);
|
|
2323
|
+
}
|
|
2324
|
+
const chunks = [];
|
|
2325
|
+
let totalLength = 0;
|
|
2326
|
+
for await (const chunk of process.stdin) {
|
|
2327
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
2328
|
+
totalLength += buffer.length;
|
|
2329
|
+
if (totalLength > MAX_STDIN_TOKEN_LENGTH) {
|
|
2330
|
+
throw new CliError("Token input from stdin is too long.", CLI_EXIT_CODES.validation);
|
|
2331
|
+
}
|
|
2332
|
+
chunks.push(buffer);
|
|
2333
|
+
}
|
|
2334
|
+
const combined = Buffer.concat(chunks);
|
|
2335
|
+
const result = combined.toString("utf8").trim();
|
|
2336
|
+
combined.fill(0);
|
|
2337
|
+
for (const chunk of chunks)
|
|
2338
|
+
chunk.fill(0);
|
|
2339
|
+
return result;
|
|
2340
|
+
}
|
|
2341
|
+
function readTokenFromPrompt(command) {
|
|
2342
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2343
|
+
throw new CliError("`--token-prompt` requires an interactive terminal. Use `--token-stdin` for CI or piped input.", CLI_EXIT_CODES.usage, {
|
|
2344
|
+
helpCommand: command,
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
return new Promise((resolve, reject) => {
|
|
2348
|
+
const stdin = process.stdin;
|
|
2349
|
+
const stdout = process.stdout;
|
|
2350
|
+
const buf = Buffer.alloc(MAX_STDIN_TOKEN_LENGTH);
|
|
2351
|
+
let bufLen = 0;
|
|
2352
|
+
const cleanup = () => {
|
|
2353
|
+
stdin.removeListener("data", onData);
|
|
2354
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
2355
|
+
process.removeListener("SIGINT", onSigint);
|
|
2356
|
+
stdin.setRawMode?.(false);
|
|
2357
|
+
stdin.pause();
|
|
2358
|
+
};
|
|
2359
|
+
const extractAndWipe = () => {
|
|
2360
|
+
const result = buf.toString("utf8", 0, bufLen).trim();
|
|
2361
|
+
buf.fill(0, 0, bufLen);
|
|
2362
|
+
bufLen = 0;
|
|
2363
|
+
return result;
|
|
2364
|
+
};
|
|
2365
|
+
const finish = (callback) => {
|
|
2366
|
+
stdout.write("\n");
|
|
2367
|
+
cleanup();
|
|
2368
|
+
callback();
|
|
2369
|
+
};
|
|
2370
|
+
const onSigterm = () => {
|
|
2371
|
+
buf.fill(0, 0, bufLen);
|
|
2372
|
+
cleanup();
|
|
2373
|
+
process.exit(130);
|
|
2374
|
+
};
|
|
2375
|
+
const onSigint = () => {
|
|
2376
|
+
buf.fill(0, 0, bufLen);
|
|
2377
|
+
cleanup();
|
|
2378
|
+
process.exit(130);
|
|
2379
|
+
};
|
|
2380
|
+
process.on("SIGTERM", onSigterm);
|
|
2381
|
+
process.on("SIGINT", onSigint);
|
|
2382
|
+
const onData = (chunk) => {
|
|
2383
|
+
const value = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2384
|
+
for (const char of value) {
|
|
2385
|
+
if (char === "\u0003") {
|
|
2386
|
+
buf.fill(0, 0, bufLen);
|
|
2387
|
+
finish(() => reject(new CliError("Token entry cancelled.", CLI_EXIT_CODES.general, { helpCommand: command })));
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
if (char === "\r" || char === "\n") {
|
|
2391
|
+
const trimmed = extractAndWipe();
|
|
2392
|
+
if (!trimmed) {
|
|
2393
|
+
finish(() => reject(new CliError("Project token is required.", CLI_EXIT_CODES.validation, { helpCommand: command })));
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
finish(() => resolve(trimmed));
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
if (char === "\u007f" || char === "\b") {
|
|
2400
|
+
if (bufLen > 0) {
|
|
2401
|
+
bufLen--;
|
|
2402
|
+
buf[bufLen] = 0;
|
|
2403
|
+
}
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
if (char >= " ") {
|
|
2407
|
+
const charBytes = Buffer.byteLength(char, "utf8");
|
|
2408
|
+
if (bufLen + charBytes > MAX_STDIN_TOKEN_LENGTH) {
|
|
2409
|
+
buf.fill(0, 0, bufLen);
|
|
2410
|
+
finish(() => reject(new CliError("Token input is too long.", CLI_EXIT_CODES.validation, { helpCommand: command })));
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
buf.write(char, bufLen, "utf8");
|
|
2414
|
+
bufLen += charBytes;
|
|
2415
|
+
stdout.write("*");
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
stdout.write("Project token: ");
|
|
2420
|
+
stdin.setEncoding("utf8");
|
|
2421
|
+
try {
|
|
2422
|
+
stdin.setRawMode?.(true);
|
|
2423
|
+
}
|
|
2424
|
+
catch {
|
|
2425
|
+
reject(new CliError("Cannot enable raw mode for token prompt. Use --token-stdin instead.", CLI_EXIT_CODES.usage, { helpCommand: command }));
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
stdin.resume();
|
|
2429
|
+
stdin.on("data", onData);
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
function parseLimitFlag(raw) {
|
|
2433
|
+
if (raw === undefined)
|
|
2434
|
+
return undefined;
|
|
2435
|
+
if (!/^\d+$/.test(raw)) {
|
|
2436
|
+
throw new CliError("`--limit` must be a positive integer between 1 and 100.", CLI_EXIT_CODES.validation);
|
|
2437
|
+
}
|
|
2438
|
+
const value = Number.parseInt(raw, 10);
|
|
2439
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 100) {
|
|
2440
|
+
throw new CliError("`--limit` must be a positive integer between 1 and 100.", CLI_EXIT_CODES.validation);
|
|
2441
|
+
}
|
|
2442
|
+
return value;
|
|
2443
|
+
}
|
|
2444
|
+
function validateToolFlag(tool) {
|
|
2445
|
+
if (!tool)
|
|
2446
|
+
return;
|
|
2447
|
+
const validSet = new Set(VALID_TOOLS);
|
|
2448
|
+
if (!validSet.has(tool)) {
|
|
2449
|
+
throw new CliError(`Invalid --tool "${tool}". Valid tools: ${VALID_TOOLS.join(", ")}.`, CLI_EXIT_CODES.validation);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
function validateAgentFlag(agent) {
|
|
2453
|
+
if (!agent)
|
|
2454
|
+
return;
|
|
2455
|
+
const targets = agent.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
2456
|
+
const validSet = new Set([...SUPPORTED_AGENT_TARGETS, "all"]);
|
|
2457
|
+
const invalid = [...new Set(targets.filter((target) => !validSet.has(target)))];
|
|
2458
|
+
if (invalid.length) {
|
|
2459
|
+
throw new CliError(`Invalid --agent target: ${invalid.join(", ")}. Use ${SUPPORTED_AGENT_TARGETS.join(", ")}, or all.`, CLI_EXIT_CODES.validation);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
function validateProviderOrderFlag(values) {
|
|
2463
|
+
if (!values?.length)
|
|
2464
|
+
return;
|
|
2465
|
+
const invalid = values.filter((value) => {
|
|
2466
|
+
const raw = value.trim().toLowerCase();
|
|
2467
|
+
const normalized = normalizeOrchestrationAgent(raw);
|
|
2468
|
+
return !raw || normalized === "router";
|
|
2469
|
+
});
|
|
2470
|
+
if (invalid.length > 0) {
|
|
2471
|
+
throw new CliError(`Invalid --provider-order value: ${invalid.join(", ")}. Use codex, cursor, claude, or copilot.`, CLI_EXIT_CODES.validation);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
function getResolvedCwd(flags) {
|
|
2475
|
+
return path.resolve(getStringFlag(flags, "cwd") || process.cwd());
|
|
2476
|
+
}
|
|
2477
|
+
function getStringFlag(flags, name) {
|
|
2478
|
+
const value = flags[name];
|
|
2479
|
+
return typeof value === "string" ? value : undefined;
|
|
2480
|
+
}
|
|
2481
|
+
function formatDuration(ms) {
|
|
2482
|
+
if (ms < 1000)
|
|
2483
|
+
return ms < 1 ? "<1ms" : `${Math.round(ms)}ms`;
|
|
2484
|
+
const totalSeconds = Math.round(ms / 1000);
|
|
2485
|
+
if (totalSeconds === 0)
|
|
2486
|
+
return "<1s";
|
|
2487
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
2488
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
2489
|
+
const seconds = totalSeconds % 60;
|
|
2490
|
+
if (hours > 0)
|
|
2491
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
2492
|
+
if (minutes > 0)
|
|
2493
|
+
return `${minutes}m ${seconds}s`;
|
|
2494
|
+
return `${seconds}s`;
|
|
2495
|
+
}
|
|
2496
|
+
function formatList(values) {
|
|
2497
|
+
if (!Array.isArray(values) || values.length === 0)
|
|
2498
|
+
return "None";
|
|
2499
|
+
return values.join(", ");
|
|
2500
|
+
}
|
|
2501
|
+
function formatCompetitors(values) {
|
|
2502
|
+
if (!Array.isArray(values) || values.length === 0)
|
|
2503
|
+
return "None";
|
|
2504
|
+
return values.map((competitor) => {
|
|
2505
|
+
const aliases = competitor.aliases?.length ? ` [aliases: ${competitor.aliases.join(", ")}]` : "";
|
|
2506
|
+
return competitor.websiteUrl ? `${competitor.name} (${competitor.websiteUrl})${aliases}` : `${competitor.name}${aliases}`;
|
|
2507
|
+
}).join("; ");
|
|
2508
|
+
}
|
|
2509
|
+
function mergePromptPacks(prompts) {
|
|
2510
|
+
const deduped = new Map();
|
|
2511
|
+
for (const prompt of prompts) {
|
|
2512
|
+
deduped.set(normalizePromptKey(prompt), prompt);
|
|
2513
|
+
}
|
|
2514
|
+
return [...deduped.values()];
|
|
2515
|
+
}
|
|
2516
|
+
function normalizePromptKey(prompt) {
|
|
2517
|
+
const raw = String(prompt.slug || prompt.title || "prompt").trim().toLowerCase();
|
|
2518
|
+
const normalized = raw.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2519
|
+
return normalized || "prompt";
|
|
2520
|
+
}
|
|
2521
|
+
function asCommandName(value) {
|
|
2522
|
+
if (!value)
|
|
2523
|
+
return undefined;
|
|
2524
|
+
return COMMANDS.find((command) => command === value);
|
|
2525
|
+
}
|
|
2526
|
+
function isRunnableCommand(command) {
|
|
2527
|
+
return command !== "help" && command !== "version";
|
|
2528
|
+
}
|
|
2529
|
+
async function printVersion() {
|
|
2530
|
+
const version = await getCliVersion();
|
|
2531
|
+
const apiUrl = process.env.PROMPTS_GPT_API_URL?.trim() || DEFAULT_PROMPTS_GPT_API_URL;
|
|
2532
|
+
console.log(`prompts-gpt v${version}`);
|
|
2533
|
+
console.log(` Node: ${process.version}`);
|
|
2534
|
+
console.log(` Platform: ${process.platform}/${process.arch}`);
|
|
2535
|
+
console.log(` API: ${apiUrl}`);
|
|
2536
|
+
}
|
|
2537
|
+
async function getCliVersion() {
|
|
2538
|
+
try {
|
|
2539
|
+
const { readFile } = await import("node:fs/promises");
|
|
2540
|
+
const pkg = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
2541
|
+
return pkg.version ?? "0.0.0";
|
|
2542
|
+
}
|
|
2543
|
+
catch {
|
|
2544
|
+
return "0.0.0";
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
function printHelp(command) {
|
|
2548
|
+
if (command) {
|
|
2549
|
+
console.log(getCommandHelp(command));
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
console.log(`Prompts-GPT CLI
|
|
2553
|
+
|
|
2554
|
+
Usage:
|
|
2555
|
+
prompts-gpt <command> [options]
|
|
2556
|
+
prompts-gpt help [command]
|
|
2557
|
+
prompts-gpt version
|
|
2558
|
+
|
|
2559
|
+
Setup:
|
|
2560
|
+
quickstart Interactive setup — credentials, config, and first run
|
|
2561
|
+
init Save a project token
|
|
2562
|
+
setup Scaffold local orchestration config
|
|
2563
|
+
status Show workspace readiness and next steps
|
|
2564
|
+
doctor Validate prerequisites and config
|
|
2565
|
+
|
|
2566
|
+
Sync & Generate:
|
|
2567
|
+
sync Pull prompt packs and sync agent files
|
|
2568
|
+
pull Download prompt packs as Markdown files
|
|
2569
|
+
generate Generate a prompt pack from a goal
|
|
2570
|
+
load-config Pull config and prompts from Prompts Studio
|
|
2571
|
+
|
|
2572
|
+
Run:
|
|
2573
|
+
run Execute one prompt with a local agent (-f <file>)
|
|
2574
|
+
run-batch Execute multiple prompts
|
|
2575
|
+
sweep Multi-iteration execution (-f <file> -n <count>)
|
|
2576
|
+
|
|
2577
|
+
Inspect:
|
|
2578
|
+
list Show prompts, sweeps, and agent integrations
|
|
2579
|
+
validate Check config for errors
|
|
2580
|
+
providers Show detected provider CLIs
|
|
2581
|
+
models List/add/remove models per provider
|
|
2582
|
+
sync-models Sync model registry from prompts-gpt.com
|
|
2583
|
+
project Show project linked to the token
|
|
2584
|
+
|
|
2585
|
+
Global options:
|
|
2586
|
+
--help Show help
|
|
2587
|
+
--version Show the current CLI version
|
|
2588
|
+
--quiet Suppress live tool/message logs during sweep
|
|
2589
|
+
--list-models List available models for the selected provider
|
|
2590
|
+
--json Output structured JSON (for run, sweep, list, etc.)
|
|
2591
|
+
|
|
2592
|
+
Environment variables:
|
|
2593
|
+
PROMPTS_GPT_TOKEN Project token (overrides stored credentials)
|
|
2594
|
+
PROMPTS_GPT_API_URL API URL (default: https://prompts-gpt.com)
|
|
2595
|
+
PROMPTS_GPT_MODEL Global model override (applied to all providers)
|
|
2596
|
+
PROMPTS_GPT_NON_INTERACTIVE=1 Disable interactive prompts
|
|
2597
|
+
NO_COLOR=1 Disable colored output
|
|
2598
|
+
|
|
2599
|
+
Providers: ${ORCHESTRATION_AGENT_PROFILES.filter((p) => p !== "router").join(", ")} (or router for auto-select)
|
|
2600
|
+
Agent targets: ${SUPPORTED_AGENT_TARGETS.join(", ")}
|
|
2601
|
+
`);
|
|
2602
|
+
}
|
|
2603
|
+
function parsePositiveIntFlag(raw, flagName) {
|
|
2604
|
+
if (raw === undefined)
|
|
2605
|
+
return undefined;
|
|
2606
|
+
const label = flagName ? `--${flagName}` : "Value";
|
|
2607
|
+
if (!/^\d+$/.test(raw)) {
|
|
2608
|
+
throw new CliError(`${label} must be a positive integer, got "${raw}".`, CLI_EXIT_CODES.validation);
|
|
2609
|
+
}
|
|
2610
|
+
const value = Number.parseInt(raw, 10);
|
|
2611
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
2612
|
+
throw new CliError(`${label} must be a positive integer, got "${raw}".`, CLI_EXIT_CODES.validation);
|
|
2613
|
+
}
|
|
2614
|
+
return value;
|
|
2615
|
+
}
|
|
2616
|
+
function parseNonNegativeIntFlag(raw, flagName) {
|
|
2617
|
+
if (raw === undefined)
|
|
2618
|
+
return undefined;
|
|
2619
|
+
const label = flagName ? `--${flagName}` : "Value";
|
|
2620
|
+
if (!/^\d+$/.test(raw)) {
|
|
2621
|
+
throw new CliError(`${label} must be a non-negative integer, got "${raw}".`, CLI_EXIT_CODES.validation);
|
|
2622
|
+
}
|
|
2623
|
+
const value = Number.parseInt(raw, 10);
|
|
2624
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
2625
|
+
throw new CliError(`${label} must be a non-negative integer, got "${raw}".`, CLI_EXIT_CODES.validation);
|
|
2626
|
+
}
|
|
2627
|
+
return value;
|
|
2628
|
+
}
|
|
2629
|
+
async function getLastUsedProvider(cwd) {
|
|
2630
|
+
try {
|
|
2631
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
2632
|
+
const data = JSON.parse(await fsRead(path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".last-provider"), "utf8"));
|
|
2633
|
+
return data.provider ?? null;
|
|
2634
|
+
}
|
|
2635
|
+
catch {
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
async function saveLastUsedProvider(cwd, provider) {
|
|
2640
|
+
try {
|
|
2641
|
+
const { writeFile: fsWrite, mkdir: fsMkdir } = await import("node:fs/promises");
|
|
2642
|
+
const dir = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR);
|
|
2643
|
+
await fsMkdir(dir, { recursive: true });
|
|
2644
|
+
await fsWrite(path.resolve(dir, ".last-provider"), JSON.stringify({ provider, updatedAt: new Date().toISOString() }));
|
|
2645
|
+
}
|
|
2646
|
+
catch { /* ignore */ }
|
|
2647
|
+
}
|
|
2648
|
+
async function getLastUsedModel(cwd, provider) {
|
|
2649
|
+
try {
|
|
2650
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
2651
|
+
const data = JSON.parse(await fsRead(path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, ".last-models"), "utf8"));
|
|
2652
|
+
return data[provider] ?? null;
|
|
2653
|
+
}
|
|
2654
|
+
catch {
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
async function saveLastUsedModel(cwd, provider, model) {
|
|
2659
|
+
try {
|
|
2660
|
+
const { readFile: fsRead, writeFile: fsWrite, mkdir: fsMkdir, rename } = await import("node:fs/promises");
|
|
2661
|
+
const dir = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR);
|
|
2662
|
+
await fsMkdir(dir, { recursive: true });
|
|
2663
|
+
const filePath = path.resolve(dir, ".last-models");
|
|
2664
|
+
let existing = {};
|
|
2665
|
+
try {
|
|
2666
|
+
existing = JSON.parse(await fsRead(filePath, "utf8"));
|
|
2667
|
+
}
|
|
2668
|
+
catch { /* new file */ }
|
|
2669
|
+
existing[provider] = model;
|
|
2670
|
+
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
2671
|
+
await fsWrite(tmpPath, JSON.stringify(existing, null, 2));
|
|
2672
|
+
await rename(tmpPath, filePath);
|
|
2673
|
+
}
|
|
2674
|
+
catch { /* ignore */ }
|
|
2675
|
+
}
|
|
2676
|
+
const PROVIDER_MODELS = Object.freeze({
|
|
2677
|
+
codex: [
|
|
2678
|
+
{ value: "gpt-5.5", label: "gpt-5.5 — frontier coding & reasoning", tier: "frontier" },
|
|
2679
|
+
{ value: "gpt-5.5-pro", label: "gpt-5.5-pro — smarter, more precise", tier: "frontier" },
|
|
2680
|
+
{ value: "gpt-5.4", label: "gpt-5.4 — strong coding model", tier: "standard" },
|
|
2681
|
+
{ value: "gpt-5.4-pro", label: "gpt-5.4-pro — enhanced responses", tier: "standard" },
|
|
2682
|
+
{ value: "gpt-5.4-mini", label: "gpt-5.4-mini — fast coding & subagents", tier: "fast" },
|
|
2683
|
+
{ value: "gpt-5.4-nano", label: "gpt-5.4-nano — cheapest high-volume", tier: "budget" },
|
|
2684
|
+
{ value: "gpt-5.3-codex", label: "gpt-5.3-codex — previous gen codex", tier: "standard" },
|
|
2685
|
+
{ value: "gpt-5.3-codex-spark", label: "gpt-5.3-codex-spark — 15x faster gen", tier: "fast" },
|
|
2686
|
+
{ value: "o4-mini", label: "o4-mini — fast reasoning", tier: "fast" },
|
|
2687
|
+
{ value: "o3", label: "o3 — advanced reasoning", tier: "frontier" },
|
|
2688
|
+
{ value: "gpt-5.1-codex", label: "gpt-5.1-codex — legacy codex", tier: "budget" },
|
|
2689
|
+
{ value: "gpt-4.1", label: "gpt-4.1 — legacy", tier: "budget" },
|
|
2690
|
+
],
|
|
2691
|
+
claude: [
|
|
2692
|
+
{ value: "claude-opus-4-7", label: "claude-opus-4-7 — most capable, complex reasoning", tier: "frontier" },
|
|
2693
|
+
{ value: "claude-sonnet-4-6", label: "claude-sonnet-4-6 — speed + intelligence", tier: "standard" },
|
|
2694
|
+
{ value: "claude-haiku-4-5", label: "claude-haiku-4-5 — fastest near-frontier", tier: "fast" },
|
|
2695
|
+
{ value: "claude-4.6-opus-high", label: "claude-4.6-opus-high — high-thinking opus", tier: "frontier" },
|
|
2696
|
+
{ value: "claude-4.6-sonnet-high", label: "claude-4.6-sonnet-high — high-thinking sonnet", tier: "standard" },
|
|
2697
|
+
{ value: "claude-4.5-opus", label: "claude-4.5-opus — previous gen opus", tier: "standard" },
|
|
2698
|
+
],
|
|
2699
|
+
cursor: [
|
|
2700
|
+
{ value: "auto", label: "auto — Cursor auto-selects best", tier: "standard" },
|
|
2701
|
+
{ value: "claude-4.6-opus-high", label: "claude-4.6-opus-high — frontier reasoning", tier: "frontier" },
|
|
2702
|
+
{ value: "claude-4.6-sonnet-high", label: "claude-4.6-sonnet-high — fast + smart", tier: "standard" },
|
|
2703
|
+
{ value: "gpt-5.5-medium", label: "gpt-5.5-medium — GPT frontier", tier: "frontier" },
|
|
2704
|
+
{ value: "composer-2", label: "composer-2 — balanced multi-file", tier: "standard" },
|
|
2705
|
+
{ value: "composer-2-fast", label: "composer-2-fast — speed optimized", tier: "fast" },
|
|
2706
|
+
{ value: "gpt-5.3-codex", label: "gpt-5.3-codex — OpenAI codex", tier: "standard" },
|
|
2707
|
+
{ value: "gpt-5.4-mini", label: "gpt-5.4-mini — fast & affordable", tier: "fast" },
|
|
2708
|
+
{ value: "gemini-3.1-pro", label: "gemini-3.1-pro — Google frontier", tier: "standard" },
|
|
2709
|
+
{ value: "claude-4.5-opus", label: "claude-4.5-opus — previous gen opus", tier: "standard" },
|
|
2710
|
+
],
|
|
2711
|
+
copilot: [
|
|
2712
|
+
{ value: "auto", label: "auto — Copilot auto-selects", tier: "standard" },
|
|
2713
|
+
{ value: "gpt-5.5", label: "gpt-5.5 — frontier", tier: "frontier" },
|
|
2714
|
+
{ value: "gpt-5.4-mini", label: "gpt-5.4-mini — fast", tier: "fast" },
|
|
2715
|
+
{ value: "claude-sonnet-4-6", label: "claude-sonnet-4-6 — Anthropic", tier: "standard" },
|
|
2716
|
+
{ value: "claude-opus-4-7", label: "claude-opus-4-7 — Anthropic frontier", tier: "frontier" },
|
|
2717
|
+
],
|
|
2718
|
+
});
|
|
2719
|
+
const MODEL_ALIASES = {
|
|
2720
|
+
opus: "claude-4.6-opus-high",
|
|
2721
|
+
sonnet: "claude-sonnet-4-6",
|
|
2722
|
+
haiku: "claude-haiku-4-5",
|
|
2723
|
+
codex: "gpt-5.3-codex",
|
|
2724
|
+
gpt: "gpt-5.5",
|
|
2725
|
+
mini: "gpt-5.4-mini",
|
|
2726
|
+
nano: "gpt-5.4-nano",
|
|
2727
|
+
composer: "composer-2",
|
|
2728
|
+
fast: "composer-2-fast",
|
|
2729
|
+
gemini: "gemini-3.1-pro",
|
|
2730
|
+
};
|
|
2731
|
+
function resolveModelAlias(model) {
|
|
2732
|
+
return MODEL_ALIASES[model.toLowerCase().trim()] ?? model;
|
|
2733
|
+
}
|
|
2734
|
+
function validateModelForProvider(model, provider) {
|
|
2735
|
+
const providerModels = PROVIDER_MODELS[provider] ?? [];
|
|
2736
|
+
const known = providerModels.map((m) => m.value);
|
|
2737
|
+
if (known.length === 0)
|
|
2738
|
+
return { valid: true, suggestion: "" };
|
|
2739
|
+
const resolved = resolveModelAlias(model);
|
|
2740
|
+
if (known.includes(resolved))
|
|
2741
|
+
return { valid: true, suggestion: "" };
|
|
2742
|
+
const close = known.find((k) => k.startsWith(resolved.slice(0, 5)));
|
|
2743
|
+
return { valid: false, suggestion: close ?? known[0] ?? "" };
|
|
2744
|
+
}
|
|
2745
|
+
function getModelChoicesForProvider(provider, config) {
|
|
2746
|
+
const builtIn = PROVIDER_MODELS[provider] ?? [];
|
|
2747
|
+
const unicode = supportsColor();
|
|
2748
|
+
const choices = builtIn.map((m) => {
|
|
2749
|
+
const tierBadge = m.tier === "frontier"
|
|
2750
|
+
? (unicode ? " ⭐" : " [F]")
|
|
2751
|
+
: m.tier === "budget" ? (unicode ? " 💰" : " [$]") : m.tier === "fast" ? (unicode ? " ⚡" : " [>]") : "";
|
|
2752
|
+
return { label: `${m.label}${tierBadge}`, value: m.value };
|
|
2753
|
+
});
|
|
2754
|
+
const customList = config.customModels?.[provider] ?? [];
|
|
2755
|
+
for (const cm of customList) {
|
|
2756
|
+
if (!choices.some((c) => c.value === cm)) {
|
|
2757
|
+
choices.push({ label: `${cm} (custom)`, value: cm });
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
const cachedModels = loadCachedModelRegistry(provider);
|
|
2761
|
+
for (const cm of cachedModels) {
|
|
2762
|
+
if (!choices.some((c) => c.value === cm)) {
|
|
2763
|
+
choices.push({ label: `${cm} (synced)`, value: cm });
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
const override = config.modelOverrides[provider]?.trim();
|
|
2767
|
+
if (override && !choices.some((c) => c.value === override)) {
|
|
2768
|
+
choices.unshift({ label: `${override} (configured)`, value: override });
|
|
2769
|
+
}
|
|
2770
|
+
choices.push({ label: "Enter custom model...", value: "__custom__" });
|
|
2771
|
+
return choices;
|
|
2772
|
+
}
|
|
2773
|
+
function loadCachedModelRegistry(provider, cwd) {
|
|
2774
|
+
try {
|
|
2775
|
+
const base = cwd ?? process.cwd();
|
|
2776
|
+
const modelsPath = path.resolve(base, DEFAULT_PROMPTS_GPT_OUT_DIR, ".models.json");
|
|
2777
|
+
if (!existsSync(modelsPath))
|
|
2778
|
+
return [];
|
|
2779
|
+
const data = JSON.parse(readFileSync(modelsPath, "utf8"));
|
|
2780
|
+
return data[provider]?.models ?? [];
|
|
2781
|
+
}
|
|
2782
|
+
catch {
|
|
2783
|
+
return [];
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
function resolveRunAgent(flags, fallback) {
|
|
2787
|
+
const raw = getStringFlag(flags, "agent");
|
|
2788
|
+
if (!raw)
|
|
2789
|
+
return fallback;
|
|
2790
|
+
const sanitized = raw.replace(/[;&|`$(){}!<>]/g, "").trim();
|
|
2791
|
+
const normalizedRaw = sanitized.toLowerCase();
|
|
2792
|
+
const normalized = normalizeOrchestrationAgent(sanitized);
|
|
2793
|
+
if (normalized === "router" && normalizedRaw !== "router") {
|
|
2794
|
+
const profiles = ORCHESTRATION_AGENT_PROFILES.join(", ");
|
|
2795
|
+
const hint = ORCHESTRATION_AGENT_PROFILES.find((p) => p.startsWith(normalizedRaw.slice(0, 3)));
|
|
2796
|
+
const suggestion = hint ? ` Did you mean "${hint}"?` : "";
|
|
2797
|
+
throw new CliError(`Invalid --agent target: ${sanitized}. Use ${profiles}.${suggestion}`, CLI_EXIT_CODES.validation);
|
|
2798
|
+
}
|
|
2799
|
+
return normalized;
|
|
2800
|
+
}
|
|
2801
|
+
function getCommandHelp(command) {
|
|
2802
|
+
if (command === "init") {
|
|
2803
|
+
return `prompts-gpt init
|
|
2804
|
+
|
|
2805
|
+
Usage:
|
|
2806
|
+
prompts-gpt init [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
|
|
2807
|
+
|
|
2808
|
+
Why use it:
|
|
2809
|
+
Stores a project token locally so you don't have to pass it on every command.
|
|
2810
|
+
If run without flags in an interactive terminal, it will prompt for the token.
|
|
2811
|
+
|
|
2812
|
+
Options:
|
|
2813
|
+
--token <token> Project API token. Must start with pgpt_.
|
|
2814
|
+
--token-stdin Read the project token from stdin (for CI/CD pipelines).
|
|
2815
|
+
--token-prompt Prompt for the project token without echoing it.
|
|
2816
|
+
--api-url <url> Custom API base URL for self-hosted instances.
|
|
2817
|
+
--cwd <path> Target project directory.
|
|
2818
|
+
--help Show this command help.
|
|
2819
|
+
|
|
2820
|
+
Examples:
|
|
2821
|
+
prompts-gpt init # interactive prompt
|
|
2822
|
+
prompts-gpt init --token pgpt_abc123 # direct token
|
|
2823
|
+
printf '%s' "$TOKEN" | prompts-gpt init --token-stdin # CI/CD
|
|
2824
|
+
`;
|
|
2825
|
+
}
|
|
2826
|
+
if (command === "setup") {
|
|
2827
|
+
return `prompts-gpt setup
|
|
2828
|
+
|
|
2829
|
+
Usage:
|
|
2830
|
+
prompts-gpt setup [--prompt-file <path> | --prompt-files <a,b,c> | --manifest <path> | --prompt-dir <path>] [--agent codex|cursor|claude|copilot|router] [--provider-order <a,b,c>] [--timeout <seconds>] [--retry-count <n>] [--artifacts-dir <path>] [--codex-model <name>] [--cursor-model <name>] [--claude-model <name>] [--copilot-model <name>] [--allow-destructive-git] [--overwrite] [--json] [--cwd <path>]
|
|
2831
|
+
|
|
2832
|
+
Why use it:
|
|
2833
|
+
Scaffolds a generic local orchestration config so a repo can run prompts through Codex, Cursor, Claude Code, or Copilot with minimal repeated CLI flags.
|
|
2834
|
+
|
|
2835
|
+
Options:
|
|
2836
|
+
--prompt-file <path> Default single prompt file for \`prompts-gpt run\`.
|
|
2837
|
+
--prompt-files <list> Default batch prompt files for \`prompts-gpt run-batch\`.
|
|
2838
|
+
--manifest <path> Use this manifest as the batch source. Default: auto-detect .prompts-gpt/manifest.json
|
|
2839
|
+
--prompt-dir <path> Scan a prompt directory when you do not use .prompts-gpt.
|
|
2840
|
+
--agent <name> Default orchestration profile. Default: router
|
|
2841
|
+
--provider-order <list> Comma-separated router order such as codex,cursor,claude,copilot
|
|
2842
|
+
--timeout <seconds> Default timeout in seconds. Default: 900
|
|
2843
|
+
--retry-count <n> Retry count for spawn and timeout failures. Default: 0
|
|
2844
|
+
--artifacts-dir <path> Run artifact directory. Default: .scripts/runs
|
|
2845
|
+
--codex-model <name> Default model override for codex.
|
|
2846
|
+
--cursor-model <name> Default model override for cursor.
|
|
2847
|
+
--claude-model <name> Default model override for claude.
|
|
2848
|
+
--copilot-model <name> Default model override for copilot.
|
|
2849
|
+
--allow-destructive-git Write config with destructive git protection disabled.
|
|
2850
|
+
--overwrite Replace an existing config file.
|
|
2851
|
+
--json Print the generated config payload as JSON.
|
|
2852
|
+
--cwd <path> Project directory.
|
|
2853
|
+
--help Show this command help.
|
|
2854
|
+
`;
|
|
2855
|
+
}
|
|
2856
|
+
if (command === "project") {
|
|
2857
|
+
return `prompts-gpt project
|
|
2858
|
+
|
|
2859
|
+
Usage:
|
|
2860
|
+
prompts-gpt project [--json] [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
|
|
2861
|
+
|
|
2862
|
+
Why use it:
|
|
2863
|
+
Verifies that the current local token resolves to the expected Prompts-GPT project before syncing files.
|
|
2864
|
+
|
|
2865
|
+
Options:
|
|
2866
|
+
--json Print the full project payload as JSON.
|
|
2867
|
+
--token <token> Override the saved local token for this command only.
|
|
2868
|
+
--token-stdin Read the override token from stdin for this command only.
|
|
2869
|
+
--token-prompt Prompt for the override token without echoing it.
|
|
2870
|
+
--api-url <url> Override the saved API base URL for this command only.
|
|
2871
|
+
--cwd <path> Project directory to inspect for local credentials.
|
|
2872
|
+
--help Show this command help.
|
|
2873
|
+
`;
|
|
2874
|
+
}
|
|
2875
|
+
if (command === "pull") {
|
|
2876
|
+
return `prompts-gpt pull
|
|
2877
|
+
|
|
2878
|
+
Usage:
|
|
2879
|
+
prompts-gpt pull [--query <text> | --q <text>] [--category <name>] [--tool <name>] [--output-type <name>] [--limit <n>] [--out <dir>] [--overwrite] [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
|
|
2880
|
+
|
|
2881
|
+
Why use it:
|
|
2882
|
+
Pulls prompt packs into local Markdown files so teams can review and use the same prompt assets inside their repository.
|
|
2883
|
+
|
|
2884
|
+
Data privacy:
|
|
2885
|
+
Downloads prompts from your prompts-gpt.com project library.
|
|
2886
|
+
No local files or repo content are uploaded.
|
|
2887
|
+
|
|
2888
|
+
Options:
|
|
2889
|
+
--query <text> Search query for the project prompt library.
|
|
2890
|
+
--q <text> Short alias for --query.
|
|
2891
|
+
--category <name> Filter prompts by category.
|
|
2892
|
+
--tool <name> Filter prompts by supported tool.
|
|
2893
|
+
--output-type <name> Filter prompts by output type.
|
|
2894
|
+
--limit <n> Positive integer limit for returned prompts.
|
|
2895
|
+
--out <dir> Output directory inside the current project. Default: .prompts-gpt
|
|
2896
|
+
--overwrite Replace existing Markdown files instead of skipping them.
|
|
2897
|
+
--token <token> Override the saved local token for this command only.
|
|
2898
|
+
--token-stdin Read the override token from stdin for this command only.
|
|
2899
|
+
--token-prompt Prompt for the override token without echoing it.
|
|
2900
|
+
--api-url <url> Override the saved API base URL for this command only.
|
|
2901
|
+
--cwd <path> Project directory to inspect for local credentials and output files.
|
|
2902
|
+
--help Show this command help.
|
|
2903
|
+
`;
|
|
2904
|
+
}
|
|
2905
|
+
if (command === "generate") {
|
|
2906
|
+
return `prompts-gpt generate
|
|
2907
|
+
|
|
2908
|
+
Usage:
|
|
2909
|
+
prompts-gpt generate --goal <text> [--context <text>] [--constraints <text>] [--desired-output <text>] [--tool <name>] [--mode <name>] [--artifact-type <name>] [--web-search] [--out <dir>] [--overwrite] [--agent <targets>] [--sync-agents] [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
|
|
2910
|
+
|
|
2911
|
+
Why use it:
|
|
2912
|
+
Creates a new reusable prompt pack from a concrete developer goal, then optionally installs agent-readable files immediately.
|
|
2913
|
+
|
|
2914
|
+
Data privacy:
|
|
2915
|
+
The text you pass via --goal, --context, and --constraints is sent to prompts-gpt.com
|
|
2916
|
+
for AI-powered prompt generation. No local files or repo content are uploaded.
|
|
2917
|
+
Do not include PII, secrets, or confidential data in these flags.
|
|
2918
|
+
|
|
2919
|
+
Options:
|
|
2920
|
+
--goal <text> Required. The task to generate a prompt pack for.
|
|
2921
|
+
--context <text> Extra project or stack context for the generator.
|
|
2922
|
+
--constraints <text> Constraints that the generated prompt must honor.
|
|
2923
|
+
--desired-output <text> Preferred output shape for the generated prompt pack.
|
|
2924
|
+
--tool <name> Target tool. Default: Codex
|
|
2925
|
+
--mode <name> Prompt generation mode. Default: implement
|
|
2926
|
+
--artifact-type <name> Artifact type. Default: prompt-file
|
|
2927
|
+
--web-search Allow server-side web research when generating the prompt.
|
|
2928
|
+
--out <dir> Output directory inside the current project. Default: .prompts-gpt
|
|
2929
|
+
--overwrite Replace an existing generated Markdown file.
|
|
2930
|
+
--agent <targets> Sync agent files for specific targets after generation.
|
|
2931
|
+
When set, also writes agent-specific files (AGENTS.md, .cursor/rules, etc.).
|
|
2932
|
+
--sync-agents Sync all agent files after generation (same as --agent all).
|
|
2933
|
+
--token <token> Override the saved local token for this command only.
|
|
2934
|
+
--token-stdin Read the override token from stdin for this command only.
|
|
2935
|
+
--token-prompt Prompt for the override token without echoing it.
|
|
2936
|
+
--api-url <url> Override the saved API base URL for this command only.
|
|
2937
|
+
--cwd <path> Project directory to inspect for local credentials and output files.
|
|
2938
|
+
--help Show this command help.
|
|
2939
|
+
|
|
2940
|
+
Examples:
|
|
2941
|
+
prompts-gpt generate --goal "Review PRs for security issues"
|
|
2942
|
+
prompts-gpt generate --goal "Write unit tests" --sync-agents
|
|
2943
|
+
prompts-gpt generate --goal "Add error handling" --agent cursor,codex
|
|
2944
|
+
`;
|
|
2945
|
+
}
|
|
2946
|
+
if (command === "sync") {
|
|
2947
|
+
return `prompts-gpt sync
|
|
2948
|
+
|
|
2949
|
+
Usage:
|
|
2950
|
+
prompts-gpt sync [--goal <text>] [--generated-only] [--query <text> | --q <text>] [--category <name>] [--tool <name>] [--output-type <name>] [--limit <n>] [--context <text>] [--constraints <text>] [--desired-output <text>] [--mode <name>] [--artifact-type <name>] [--web-search] [--agent <targets>] [--out <dir>] [--overwrite] [--dry-run] [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
|
|
2951
|
+
|
|
2952
|
+
Why use it:
|
|
2953
|
+
Gives teams a one-command path to refresh Markdown prompts, agent files, and the local manifest from the same source of truth.
|
|
2954
|
+
|
|
2955
|
+
Data privacy:
|
|
2956
|
+
Prompts are downloaded from your prompts-gpt.com project library.
|
|
2957
|
+
When --goal is used, the text you pass is sent to prompts-gpt.com for AI generation.
|
|
2958
|
+
No local files or repo content are uploaded — only explicit flag values are transmitted.
|
|
2959
|
+
Do not include PII, secrets, or confidential data in --goal, --context, or --constraints.
|
|
2960
|
+
Use --dry-run to preview what would be synced without sending or writing anything.
|
|
2961
|
+
|
|
2962
|
+
Options:
|
|
2963
|
+
--goal <text> Generate one prompt pack before syncing.
|
|
2964
|
+
--generated-only Skip library pull and sync only the generated prompt pack from --goal.
|
|
2965
|
+
--query <text> Search query for pulled prompt packs.
|
|
2966
|
+
--q <text> Short alias for --query.
|
|
2967
|
+
--category <name> Filter pulled prompt packs by category.
|
|
2968
|
+
--tool <name> Filter pulled prompt packs and generated output by tool.
|
|
2969
|
+
--output-type <name> Filter pulled prompt packs by output type.
|
|
2970
|
+
--limit <n> Positive integer limit for pulled prompt packs.
|
|
2971
|
+
--context <text> Extra project or stack context for generated prompt packs.
|
|
2972
|
+
--constraints <text> Constraints for generated prompt packs.
|
|
2973
|
+
--desired-output <text> Preferred output shape for generated prompt packs.
|
|
2974
|
+
--mode <name> Prompt generation mode. Default: implement
|
|
2975
|
+
--artifact-type <name> Artifact type. Default: prompt-file
|
|
2976
|
+
--web-search Allow server-side web research when generating a prompt pack.
|
|
2977
|
+
--agent <targets> Comma-separated agent targets. Default: all
|
|
2978
|
+
--out <dir> Output directory inside the current project. Default: .prompts-gpt
|
|
2979
|
+
--overwrite Replace existing Markdown files instead of skipping them.
|
|
2980
|
+
--dry-run Preview what would be synced without writing any files.
|
|
2981
|
+
--token <token> Override the saved local token for this command only.
|
|
2982
|
+
--token-stdin Read the override token from stdin for this command only.
|
|
2983
|
+
--token-prompt Prompt for the override token without echoing it.
|
|
2984
|
+
--api-url <url> Override the saved API base URL for this command only.
|
|
2985
|
+
--cwd <path> Project directory to inspect for local credentials and output files.
|
|
2986
|
+
--help Show this command help.
|
|
2987
|
+
|
|
2988
|
+
Examples:
|
|
2989
|
+
prompts-gpt sync # pull all prompts + sync agent files
|
|
2990
|
+
prompts-gpt sync --goal "Add error handling" # generate + pull + sync
|
|
2991
|
+
prompts-gpt sync --dry-run # preview without writing
|
|
2992
|
+
prompts-gpt sync --agent cursor,codex # sync only specific agents
|
|
2993
|
+
`;
|
|
2994
|
+
}
|
|
2995
|
+
if (command === "run") {
|
|
2996
|
+
return `prompts-gpt run
|
|
2997
|
+
|
|
2998
|
+
Usage:
|
|
2999
|
+
prompts-gpt run [-f <path>] [--agent <name>] [--model <name>] [--timeout <seconds>] [--dry-run]
|
|
3000
|
+
|
|
3001
|
+
Why use it:
|
|
3002
|
+
Executes one local prompt file with the built-in provider adapter runtime and writes run artifacts.
|
|
3003
|
+
If \`--prompt-file\` is omitted and only one prompt exists locally, it is auto-selected.
|
|
3004
|
+
|
|
3005
|
+
Options:
|
|
3006
|
+
-f, --prompt-file <path> Optional explicit prompt Markdown or text file.
|
|
3007
|
+
--agent <name> Orchestration profile. Default from ${DEFAULT_RUN_CONFIG_PATH} or router.
|
|
3008
|
+
--model <name> Optional model override for the selected provider.
|
|
3009
|
+
--timeout <seconds> Execution timeout in seconds.
|
|
3010
|
+
--artifacts-dir <path> Override run artifacts directory.
|
|
3011
|
+
--run-id <id> Explicit run ID for artifact folder naming.
|
|
3012
|
+
--sandbox <mode> Codex sandbox mode. Default: workspace-write.
|
|
3013
|
+
--no-approve-mcps Disable Cursor MCP auto-approval flag.
|
|
3014
|
+
--background Run as a background agent (Cursor cloud agent mode).
|
|
3015
|
+
--permission-mode <mode> Claude Code permission mode. Default: acceptEdits.
|
|
3016
|
+
--dry-run Preview the execution parameters without actually running.
|
|
3017
|
+
--non-interactive Skip all interactive prompts (same as CI mode).
|
|
3018
|
+
--json Print machine-readable JSON output.
|
|
3019
|
+
--cwd <path> Project directory.
|
|
3020
|
+
--help Show this command help.
|
|
3021
|
+
`;
|
|
3022
|
+
}
|
|
3023
|
+
if (command === "run-batch") {
|
|
3024
|
+
return `prompts-gpt run-batch
|
|
3025
|
+
|
|
3026
|
+
Usage:
|
|
3027
|
+
prompts-gpt run-batch [--manifest <path> | --prompt-files <a,b,c>] [--agent <name>] [--model <name>] [--timeout <seconds>]
|
|
3028
|
+
|
|
3029
|
+
Why use it:
|
|
3030
|
+
Executes multiple prompt files in sequence using the configured prompt source, a manifest, or an explicit file list.
|
|
3031
|
+
|
|
3032
|
+
Options:
|
|
3033
|
+
--manifest <path> Prompt manifest path. Overrides config batch defaults.
|
|
3034
|
+
--prompt-files <list> Comma-separated prompt files to run.
|
|
3035
|
+
--agent <name> Orchestration profile. Default from ${DEFAULT_RUN_CONFIG_PATH} or router.
|
|
3036
|
+
--model <name> Optional model override for all runs.
|
|
3037
|
+
--timeout <seconds> Execution timeout in seconds per prompt.
|
|
3038
|
+
--artifacts-dir <path> Override run artifacts directory.
|
|
3039
|
+
--json Print machine-readable JSON output.
|
|
3040
|
+
--cwd <path> Project directory.
|
|
3041
|
+
--help Show this command help.
|
|
3042
|
+
|
|
3043
|
+
Examples:
|
|
3044
|
+
prompts-gpt run-batch --manifest .prompts-gpt/manifest.json
|
|
3045
|
+
prompts-gpt run-batch --prompt-files .prompts-gpt/review.md,.prompts-gpt/tests.md
|
|
3046
|
+
`;
|
|
3047
|
+
}
|
|
3048
|
+
if (command === "sweep") {
|
|
3049
|
+
return `prompts-gpt sweep
|
|
3050
|
+
|
|
3051
|
+
Usage:
|
|
3052
|
+
prompts-gpt sweep [-f <path>] [-n <count>] [--agent <name>] [--model <name>] [--dry-run]
|
|
3053
|
+
|
|
3054
|
+
Why use it:
|
|
3055
|
+
Runs the same prompt N times, feeding each iteration's summary into the next.
|
|
3056
|
+
Includes pre-flight checks, safety guards, SIGTERM handling, and progress monitoring.
|
|
3057
|
+
|
|
3058
|
+
When --prompt-file is omitted and only one .prompts-gpt/sweeps/*.md file exists,
|
|
3059
|
+
it is auto-selected. The iteration count defaults to the \`iterations:\` value
|
|
3060
|
+
in the sweep file's YAML frontmatter (or 1 if not specified).
|
|
3061
|
+
|
|
3062
|
+
Options:
|
|
3063
|
+
-f, --prompt-file <path> Prompt file to sweep. Auto-detects local sweeps if omitted.
|
|
3064
|
+
-n, --iterations <n> Number of iterations. Default: from frontmatter or 1.
|
|
3065
|
+
--agent <name> Orchestration profile. Default from config or router.
|
|
3066
|
+
--model <name> Model override for the selected provider.
|
|
3067
|
+
--iteration-timeout <secs> Timeout per iteration in seconds. Default: 5400 (90 min)
|
|
3068
|
+
--max-retries <n> Max retries per iteration on spawn/timeout. Default: 2
|
|
3069
|
+
--phase <name> Optional phase label injected into the prompt.
|
|
3070
|
+
--artifacts-dir <path> Override run artifacts directory.
|
|
3071
|
+
--run-id <id> Explicit run ID for artifact folder naming.
|
|
3072
|
+
--sandbox <mode> Codex sandbox mode. Default: workspace-write
|
|
3073
|
+
--no-approve-mcps Disable Cursor MCP auto-approval flag.
|
|
3074
|
+
--background Run as a background agent (Cursor cloud agent mode).
|
|
3075
|
+
--permission-mode <mode> Claude Code permission mode. Default: acceptEdits
|
|
3076
|
+
--max-run-dirs <n> Max artifact directories to keep. Default: 20
|
|
3077
|
+
--summary-lines <n> Lines of summary to extract per iteration. Default: 40
|
|
3078
|
+
--dry-run Preview what the sweep would do without executing.
|
|
3079
|
+
--quiet, -q Suppress live tool/message logs (keep iteration headers).
|
|
3080
|
+
--list-models List available models for the selected provider.
|
|
3081
|
+
--non-interactive Skip all interactive prompts (same as CI mode).
|
|
3082
|
+
--json Print machine-readable JSON output.
|
|
3083
|
+
--cwd <path> Project directory.
|
|
3084
|
+
--help Show this command help.
|
|
3085
|
+
|
|
3086
|
+
Environment:
|
|
3087
|
+
PROMPTS_GPT_TOKEN Override project token.
|
|
3088
|
+
PROMPTS_GPT_MODEL Global model override.
|
|
3089
|
+
PROMPTS_GPT_NON_INTERACTIVE=1 Skip interactive prompts.
|
|
3090
|
+
NO_COLOR=1 Disable colored output.
|
|
3091
|
+
|
|
3092
|
+
Model aliases: ${Object.entries(MODEL_ALIASES).map(([k, v]) => `${k} → ${v}`).join(", ")}
|
|
3093
|
+
|
|
3094
|
+
Examples:
|
|
3095
|
+
prompts-gpt sweep # interactive picker
|
|
3096
|
+
prompts-gpt sweep -f .prompts-gpt/sweeps/sdk-hardening.md # explicit file
|
|
3097
|
+
prompts-gpt sweep -f .prompts-gpt/sweeps/design.md -n 5 # 5 iterations
|
|
3098
|
+
prompts-gpt sweep --model opus # use model alias
|
|
3099
|
+
prompts-gpt sweep --quiet # suppress live logs
|
|
3100
|
+
prompts-gpt sweep --list-models --agent codex # see codex models
|
|
3101
|
+
prompts-gpt sweep --dry-run # preview without running
|
|
3102
|
+
prompts-gpt sweep --non-interactive # skip interactive prompts
|
|
3103
|
+
`;
|
|
3104
|
+
}
|
|
3105
|
+
if (command === "list") {
|
|
3106
|
+
return `prompts-gpt list
|
|
3107
|
+
|
|
3108
|
+
Usage:
|
|
3109
|
+
prompts-gpt list [--json] [--cwd <path>]
|
|
3110
|
+
|
|
3111
|
+
Why use it:
|
|
3112
|
+
Shows all prompt packs, sweep files, and agent integrations available in the workspace.
|
|
3113
|
+
Gives runnable commands for each entry so you can copy-paste them directly.
|
|
3114
|
+
|
|
3115
|
+
Options:
|
|
3116
|
+
--json Print as JSON.
|
|
3117
|
+
--cwd <path> Project directory.
|
|
3118
|
+
--help Show this command help.
|
|
3119
|
+
`;
|
|
3120
|
+
}
|
|
3121
|
+
if (command === "status") {
|
|
3122
|
+
return `prompts-gpt status
|
|
3123
|
+
|
|
3124
|
+
Usage:
|
|
3125
|
+
prompts-gpt status [--json] [--cwd <path>]
|
|
3126
|
+
|
|
3127
|
+
Why use it:
|
|
3128
|
+
Shows workspace readiness at a glance: credentials, config, manifest,
|
|
3129
|
+
prompts, sweeps, agent integrations, and available providers.
|
|
3130
|
+
|
|
3131
|
+
Options:
|
|
3132
|
+
--json Print as JSON.
|
|
3133
|
+
--cwd <path> Project directory.
|
|
3134
|
+
--help Show this command help.
|
|
3135
|
+
`;
|
|
3136
|
+
}
|
|
3137
|
+
if (command === "validate") {
|
|
3138
|
+
return `prompts-gpt validate
|
|
3139
|
+
|
|
3140
|
+
Usage:
|
|
3141
|
+
prompts-gpt validate [--json] [--cwd <path>]
|
|
3142
|
+
|
|
3143
|
+
Why use it:
|
|
3144
|
+
Validates the .prompts-gpt/config.json file and reports errors and
|
|
3145
|
+
warnings without running any commands.
|
|
3146
|
+
|
|
3147
|
+
Options:
|
|
3148
|
+
--json Print as JSON.
|
|
3149
|
+
--cwd <path> Project directory.
|
|
3150
|
+
--help Show this command help.
|
|
3151
|
+
`;
|
|
3152
|
+
}
|
|
3153
|
+
if (command === "providers") {
|
|
3154
|
+
return `prompts-gpt providers
|
|
3155
|
+
|
|
3156
|
+
Usage:
|
|
3157
|
+
prompts-gpt providers [--json] [--cwd <path>]
|
|
3158
|
+
|
|
3159
|
+
Why use it:
|
|
3160
|
+
Shows provider CLI detection details for codex, cursor, claude, and copilot so you can decide the best router order before running setup.
|
|
3161
|
+
`;
|
|
3162
|
+
}
|
|
3163
|
+
if (command === "models") {
|
|
3164
|
+
return `prompts-gpt models
|
|
3165
|
+
|
|
3166
|
+
Usage:
|
|
3167
|
+
prompts-gpt models [--provider <name>] [--add <model>] [--remove <model>] [--json] [--cwd <path>]
|
|
3168
|
+
|
|
3169
|
+
Why use it:
|
|
3170
|
+
Lists available models per provider with tier badges (frontier/standard/fast/budget).
|
|
3171
|
+
Add or remove custom models stored in your config. Shows model aliases and synced models.
|
|
3172
|
+
|
|
3173
|
+
Examples:
|
|
3174
|
+
prompts-gpt models — list all models
|
|
3175
|
+
prompts-gpt models --provider codex — list codex models only
|
|
3176
|
+
prompts-gpt models --provider cursor --add my-model — add custom model
|
|
3177
|
+
prompts-gpt models --provider codex --remove gpt-4.1 — remove a custom model
|
|
3178
|
+
`;
|
|
3179
|
+
}
|
|
3180
|
+
if (command === "sync-models") {
|
|
3181
|
+
return `prompts-gpt sync-models
|
|
3182
|
+
|
|
3183
|
+
Usage:
|
|
3184
|
+
prompts-gpt sync-models [--token <project-token>] [--cwd <path>]
|
|
3185
|
+
|
|
3186
|
+
Why use it:
|
|
3187
|
+
Fetches the latest model registry from prompts-gpt.com and caches it locally.
|
|
3188
|
+
Models synced from the API appear as "(synced)" in the interactive selector.
|
|
3189
|
+
|
|
3190
|
+
Schedule regular syncs with cron:
|
|
3191
|
+
0 6 * * * cd /path/to/project && npx prompts-gpt sync-models
|
|
3192
|
+
`;
|
|
3193
|
+
}
|
|
3194
|
+
if (command === "doctor") {
|
|
3195
|
+
return `prompts-gpt doctor
|
|
3196
|
+
|
|
3197
|
+
Usage:
|
|
3198
|
+
prompts-gpt doctor [--json] [--cwd <path>]
|
|
3199
|
+
|
|
3200
|
+
Why use it:
|
|
3201
|
+
Checks runtime prerequisites, provider availability, prompt-source readiness, and run config health.
|
|
3202
|
+
`;
|
|
3203
|
+
}
|
|
3204
|
+
if (command === "load-config") {
|
|
3205
|
+
return `prompts-gpt load-config
|
|
3206
|
+
|
|
3207
|
+
Usage:
|
|
3208
|
+
prompts-gpt load-config [--agent <targets>] [--out <dir>] [--json] [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
|
|
3209
|
+
|
|
3210
|
+
Why use it:
|
|
3211
|
+
One command to pull all prompts, agent files, and config from Prompts Studio
|
|
3212
|
+
into your local workspace. Replaces the multi-step init + sync + setup flow
|
|
3213
|
+
for users who manage their prompt library in the Prompts Studio web app.
|
|
3214
|
+
|
|
3215
|
+
Data privacy:
|
|
3216
|
+
Downloads prompts and project config from your prompts-gpt.com project.
|
|
3217
|
+
No local files or repo content are uploaded.
|
|
3218
|
+
|
|
3219
|
+
Options:
|
|
3220
|
+
--agent <targets> Comma-separated agent targets. Default: all
|
|
3221
|
+
--out <dir> Output directory. Default: .prompts-gpt
|
|
3222
|
+
--json Print machine-readable JSON output.
|
|
3223
|
+
--token <token> Override saved token.
|
|
3224
|
+
--token-stdin Read token from stdin.
|
|
3225
|
+
--token-prompt Prompt for token.
|
|
3226
|
+
--api-url <url> Override API URL.
|
|
3227
|
+
--cwd <path> Project directory.
|
|
3228
|
+
--help Show this command help.
|
|
3229
|
+
`;
|
|
3230
|
+
}
|
|
3231
|
+
if (command === "quickstart") {
|
|
3232
|
+
return `prompts-gpt quickstart
|
|
3233
|
+
|
|
3234
|
+
Usage:
|
|
3235
|
+
prompts-gpt quickstart [--json] [--cwd <path>]
|
|
3236
|
+
|
|
3237
|
+
Why use it:
|
|
3238
|
+
Walks through setup in one command — checks credentials, creates config, detects providers,
|
|
3239
|
+
and shows runnable commands. The fastest path from install to first run.
|
|
3240
|
+
|
|
3241
|
+
Options:
|
|
3242
|
+
--json Print machine-readable JSON output.
|
|
3243
|
+
--cwd <path> Project directory.
|
|
3244
|
+
--help Show this command help.
|
|
3245
|
+
`;
|
|
3246
|
+
}
|
|
3247
|
+
if (command === "version") {
|
|
3248
|
+
return `prompts-gpt version
|
|
3249
|
+
|
|
3250
|
+
Usage:
|
|
3251
|
+
prompts-gpt version
|
|
3252
|
+
|
|
3253
|
+
Why use it:
|
|
3254
|
+
Confirms the installed CLI version when debugging package resolution or publish issues.
|
|
3255
|
+
`;
|
|
3256
|
+
}
|
|
3257
|
+
return `prompts-gpt help
|
|
3258
|
+
|
|
3259
|
+
Usage:
|
|
3260
|
+
prompts-gpt help [command]
|
|
3261
|
+
|
|
3262
|
+
Why use it:
|
|
3263
|
+
Shows focused usage for the exact command you are trying to run so shell users do not have to scan the full command list.
|
|
3264
|
+
`;
|
|
3265
|
+
}
|
|
3266
|
+
// ── Interactive TTY selection helpers ─────────────────────────────────────────
|
|
3267
|
+
function supportsColor() {
|
|
3268
|
+
if (process.env.NO_COLOR || process.env.TERM === "dumb")
|
|
3269
|
+
return false;
|
|
3270
|
+
if (process.env.FORCE_COLOR)
|
|
3271
|
+
return true;
|
|
3272
|
+
if (process.platform === "win32") {
|
|
3273
|
+
if (process.env.WT_SESSION || process.env.TERM_PROGRAM || process.env.ConEmuANSI === "ON" || process.env.ANSICON)
|
|
3274
|
+
return true;
|
|
3275
|
+
return Boolean(process.stdout.isTTY && (process.stdout.hasColors?.() ?? false));
|
|
3276
|
+
}
|
|
3277
|
+
return Boolean(process.stdout.isTTY);
|
|
3278
|
+
}
|
|
3279
|
+
function colorize(text, code) {
|
|
3280
|
+
return supportsColor() ? `${code}${text}\x1b[0m` : text;
|
|
3281
|
+
}
|
|
3282
|
+
function isTTYInteractive(flags) {
|
|
3283
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
3284
|
+
return false;
|
|
3285
|
+
if (flags && Boolean(flags["non-interactive"]))
|
|
3286
|
+
return false;
|
|
3287
|
+
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS ||
|
|
3288
|
+
process.env.GITLAB_CI || process.env.CIRCLECI || process.env.JENKINS_URL ||
|
|
3289
|
+
process.env.BUILDKITE || process.env.TRAVIS || process.env.TF_BUILD ||
|
|
3290
|
+
process.env.CODEBUILD_BUILD_ID)
|
|
3291
|
+
return false;
|
|
3292
|
+
if (process.env.PROMPTS_GPT_NON_INTERACTIVE === "1" || process.env.PROMPTS_GPT_NON_INTERACTIVE === "true")
|
|
3293
|
+
return false;
|
|
3294
|
+
return true;
|
|
3295
|
+
}
|
|
3296
|
+
function interactiveSelect(prompt, options) {
|
|
3297
|
+
if (options.length === 0) {
|
|
3298
|
+
return Promise.reject(new CliError("No options available for selection.", CLI_EXIT_CODES.usage));
|
|
3299
|
+
}
|
|
3300
|
+
if (options.length === 1) {
|
|
3301
|
+
return Promise.resolve(options[0].value);
|
|
3302
|
+
}
|
|
3303
|
+
return new Promise((resolve, reject) => {
|
|
3304
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3305
|
+
reject(new CliError("Interactive selection requires a TTY.", CLI_EXIT_CODES.usage));
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
const stdin = process.stdin;
|
|
3309
|
+
const stdout = process.stdout;
|
|
3310
|
+
let cursor = 0;
|
|
3311
|
+
let escBuf = "";
|
|
3312
|
+
let filterText = "";
|
|
3313
|
+
const termRows = typeof process.stdout.rows === "number" && process.stdout.rows > 0 ? process.stdout.rows : 24;
|
|
3314
|
+
const maxVisible = Math.max(3, Math.min(options.length, termRows - 4));
|
|
3315
|
+
const useUnicode = supportsColor();
|
|
3316
|
+
const pointer = useUnicode ? "❯" : ">";
|
|
3317
|
+
const formatLine = (i, selected) => {
|
|
3318
|
+
const num = i < 9 ? `${i + 1}` : " ";
|
|
3319
|
+
const prefix = selected ? colorize(pointer, "\x1b[36m") : " ";
|
|
3320
|
+
const label = selected ? colorize(options[i].label, "\x1b[1m") : options[i].label;
|
|
3321
|
+
return ` ${prefix} ${num}. ${label}`;
|
|
3322
|
+
};
|
|
3323
|
+
const hints = colorize(" ↑↓ navigate · 1-9 jump · Enter select · type to filter", "\x1b[90m");
|
|
3324
|
+
const writeInitial = () => {
|
|
3325
|
+
stdout.write(`${prompt}\n`);
|
|
3326
|
+
for (let i = 0; i < maxVisible; i++) {
|
|
3327
|
+
stdout.write(`${formatLine(i, i === cursor)}\n`);
|
|
3328
|
+
}
|
|
3329
|
+
if (options.length > maxVisible) {
|
|
3330
|
+
stdout.write(` ... ${options.length - maxVisible} more (scroll with arrows)\n`);
|
|
3331
|
+
}
|
|
3332
|
+
stdout.write(`${hints}\n`);
|
|
3333
|
+
};
|
|
3334
|
+
const render = () => {
|
|
3335
|
+
const footerLines = 1 + (options.length > maxVisible ? 1 : 0) + 1;
|
|
3336
|
+
const lines = maxVisible + footerLines;
|
|
3337
|
+
if (supportsColor()) {
|
|
3338
|
+
stdout.write(`\x1b[${lines}A\x1b[J`);
|
|
3339
|
+
}
|
|
3340
|
+
else {
|
|
3341
|
+
stdout.write("\n".repeat(2));
|
|
3342
|
+
}
|
|
3343
|
+
const posLabel = options.length > 1 ? ` (${cursor + 1}/${options.length})` : "";
|
|
3344
|
+
const filterLabel = filterText ? colorize(` filter: "${filterText}"`, "\x1b[33m") : "";
|
|
3345
|
+
stdout.write(`${prompt}${posLabel}${filterLabel}\n`);
|
|
3346
|
+
const scrollStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), options.length - maxVisible));
|
|
3347
|
+
for (let vi = 0; vi < maxVisible; vi++) {
|
|
3348
|
+
const i = scrollStart + vi;
|
|
3349
|
+
stdout.write(`${formatLine(i, i === cursor)}\n`);
|
|
3350
|
+
}
|
|
3351
|
+
if (options.length > maxVisible) {
|
|
3352
|
+
stdout.write(` ... showing ${scrollStart + 1}-${scrollStart + maxVisible} of ${options.length}\n`);
|
|
3353
|
+
}
|
|
3354
|
+
stdout.write(`${hints}\n`);
|
|
3355
|
+
};
|
|
3356
|
+
writeInitial();
|
|
3357
|
+
stdin.setEncoding("utf8");
|
|
3358
|
+
try {
|
|
3359
|
+
if (typeof stdin.setRawMode === "function")
|
|
3360
|
+
stdin.setRawMode(true);
|
|
3361
|
+
}
|
|
3362
|
+
catch {
|
|
3363
|
+
reject(new CliError("Cannot enable raw mode for interactive selection. Use --non-interactive or pass flags directly.", CLI_EXIT_CODES.usage));
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
stdin.resume();
|
|
3367
|
+
const cleanup = () => {
|
|
3368
|
+
try {
|
|
3369
|
+
stdin.removeListener("data", onData);
|
|
3370
|
+
}
|
|
3371
|
+
catch { /* already destroyed */ }
|
|
3372
|
+
try {
|
|
3373
|
+
if (typeof stdin.setRawMode === "function")
|
|
3374
|
+
stdin.setRawMode(false);
|
|
3375
|
+
}
|
|
3376
|
+
catch { /* ignore */ }
|
|
3377
|
+
try {
|
|
3378
|
+
if (!stdin.destroyed)
|
|
3379
|
+
stdin.pause();
|
|
3380
|
+
}
|
|
3381
|
+
catch { /* ignore */ }
|
|
3382
|
+
};
|
|
3383
|
+
const onData = (data) => {
|
|
3384
|
+
for (let ci = 0; ci < data.length; ci++) {
|
|
3385
|
+
const ch = data[ci];
|
|
3386
|
+
if (escBuf.length > 0) {
|
|
3387
|
+
escBuf += ch;
|
|
3388
|
+
if (escBuf.length === 2 && ch === "[")
|
|
3389
|
+
continue;
|
|
3390
|
+
if (escBuf.length >= 3) {
|
|
3391
|
+
if (escBuf === "\x1b[A" || escBuf === "\x1b[k") {
|
|
3392
|
+
cursor = cursor > 0 ? cursor - 1 : options.length - 1;
|
|
3393
|
+
filterText = "";
|
|
3394
|
+
render();
|
|
3395
|
+
}
|
|
3396
|
+
else if (escBuf === "\x1b[B" || escBuf === "\x1b[j") {
|
|
3397
|
+
cursor = cursor < options.length - 1 ? cursor + 1 : 0;
|
|
3398
|
+
filterText = "";
|
|
3399
|
+
render();
|
|
3400
|
+
}
|
|
3401
|
+
else if (escBuf === "\x1b[H") {
|
|
3402
|
+
cursor = 0;
|
|
3403
|
+
render();
|
|
3404
|
+
}
|
|
3405
|
+
else if (escBuf === "\x1b[F") {
|
|
3406
|
+
cursor = options.length - 1;
|
|
3407
|
+
render();
|
|
3408
|
+
}
|
|
3409
|
+
escBuf = "";
|
|
3410
|
+
}
|
|
3411
|
+
continue;
|
|
3412
|
+
}
|
|
3413
|
+
if (ch === "\x1b") {
|
|
3414
|
+
escBuf = "\x1b";
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
if (ch === "\x03") {
|
|
3418
|
+
cleanup();
|
|
3419
|
+
stdout.write("\n");
|
|
3420
|
+
process.exit(130);
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
if (ch === "\r" || ch === "\n") {
|
|
3424
|
+
cleanup();
|
|
3425
|
+
filterText = "";
|
|
3426
|
+
stdout.write("\n");
|
|
3427
|
+
resolve(options[cursor].value);
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
if (ch === "\x7f" || ch === "\b") {
|
|
3431
|
+
if (filterText.length > 0) {
|
|
3432
|
+
filterText = filterText.slice(0, -1);
|
|
3433
|
+
if (filterText) {
|
|
3434
|
+
const match = options.findIndex((o) => o.label.toLowerCase().includes(filterText.toLowerCase()));
|
|
3435
|
+
if (match >= 0)
|
|
3436
|
+
cursor = match;
|
|
3437
|
+
}
|
|
3438
|
+
render();
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
else if (ch === "k" && !filterText) {
|
|
3442
|
+
cursor = cursor > 0 ? cursor - 1 : options.length - 1;
|
|
3443
|
+
render();
|
|
3444
|
+
}
|
|
3445
|
+
else if (ch === "j" && !filterText) {
|
|
3446
|
+
cursor = cursor < options.length - 1 ? cursor + 1 : 0;
|
|
3447
|
+
render();
|
|
3448
|
+
}
|
|
3449
|
+
else if (ch >= "1" && ch <= "9" && !filterText) {
|
|
3450
|
+
const idx = parseInt(ch, 10) - 1;
|
|
3451
|
+
if (idx < options.length && idx < 9) {
|
|
3452
|
+
cursor = idx;
|
|
3453
|
+
cleanup();
|
|
3454
|
+
stdout.write("\n");
|
|
3455
|
+
resolve(options[idx].value);
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
else if (ch >= " " && ch <= "~") {
|
|
3460
|
+
filterText += ch;
|
|
3461
|
+
const match = options.findIndex((o) => o.label.toLowerCase().includes(filterText.toLowerCase()));
|
|
3462
|
+
if (match >= 0)
|
|
3463
|
+
cursor = match;
|
|
3464
|
+
render();
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
};
|
|
3468
|
+
stdin.on("data", onData);
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
async function interactiveInput(prompt, defaultValue) {
|
|
3472
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3473
|
+
throw new CliError("Interactive input requires a TTY.", CLI_EXIT_CODES.usage);
|
|
3474
|
+
}
|
|
3475
|
+
const { createInterface } = await import("node:readline");
|
|
3476
|
+
return new Promise((resolve) => {
|
|
3477
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3478
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
3479
|
+
rl.question(`${prompt}${suffix}: `, (answer) => {
|
|
3480
|
+
rl.close();
|
|
3481
|
+
rl.removeAllListeners();
|
|
3482
|
+
resolve((answer.trim() || defaultValue || "").slice(0, 1024));
|
|
3483
|
+
});
|
|
3484
|
+
rl.on("close", () => resolve(defaultValue || ""));
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
async function readSweepFrontmatter(filePath) {
|
|
3488
|
+
try {
|
|
3489
|
+
const { readFile: fsRead } = await import("node:fs/promises");
|
|
3490
|
+
const content = await fsRead(filePath, "utf8");
|
|
3491
|
+
let iterations = null;
|
|
3492
|
+
let title = null;
|
|
3493
|
+
const iterMatch = content.match(/iterations:\s*(\d+)/i);
|
|
3494
|
+
if (iterMatch) {
|
|
3495
|
+
const val = parseInt(iterMatch[1], 10);
|
|
3496
|
+
if (val > 0 && val <= 50)
|
|
3497
|
+
iterations = val;
|
|
3498
|
+
}
|
|
3499
|
+
const fmEnd = content.indexOf("---", content.indexOf("---") + 3);
|
|
3500
|
+
const bodyStart = fmEnd > 0 ? fmEnd + 3 : 0;
|
|
3501
|
+
const titleMatch = content.slice(bodyStart).match(/^#\s+(.+)/m);
|
|
3502
|
+
if (titleMatch) {
|
|
3503
|
+
title = titleMatch[1].trim();
|
|
3504
|
+
}
|
|
3505
|
+
return { iterations, title };
|
|
3506
|
+
}
|
|
3507
|
+
catch { /* skip */ }
|
|
3508
|
+
return { iterations: null, title: null };
|
|
3509
|
+
}
|
|
3510
|
+
async function readSweepIterationsFromFrontmatter(filePath) {
|
|
3511
|
+
return (await readSweepFrontmatter(filePath)).iterations;
|
|
3512
|
+
}
|
|
3513
|
+
function buildGenerateInput(flags) {
|
|
3514
|
+
return {
|
|
3515
|
+
goal: getStringFlag(flags, "goal") || "",
|
|
3516
|
+
context: sanitizeGenerateInput(getStringFlag(flags, "context") || ""),
|
|
3517
|
+
constraints: sanitizeGenerateInput(getStringFlag(flags, "constraints") || ""),
|
|
3518
|
+
desiredOutput: getStringFlag(flags, "desired-output") || "A reusable prompt pack saved as Markdown.",
|
|
3519
|
+
tool: getStringFlag(flags, "tool") || "Codex",
|
|
3520
|
+
mode: getStringFlag(flags, "mode") || "implement",
|
|
3521
|
+
artifactType: getStringFlag(flags, "artifact-type") || "prompt-file",
|
|
3522
|
+
includeWebSearch: Boolean(flags["web-search"]),
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
function sanitizeGenerateInput(value) {
|
|
3526
|
+
return value
|
|
3527
|
+
.replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***")
|
|
3528
|
+
.replace(/sk-[a-zA-Z0-9]{20,}/g, "sk-***")
|
|
3529
|
+
.replace(/ghp_[a-zA-Z0-9]{36,}/g, "ghp_***")
|
|
3530
|
+
.replace(/ghu_[a-zA-Z0-9]{36,}/g, "ghu_***")
|
|
3531
|
+
.replace(/Bearer\s+[a-zA-Z0-9_.-]{20,}/gi, "Bearer ***")
|
|
3532
|
+
.replace(/xai-[a-zA-Z0-9]{20,}/g, "xai-***")
|
|
3533
|
+
.replace(/ANTHROPIC_API_KEY=[^\s]+/gi, "ANTHROPIC_API_KEY=***")
|
|
3534
|
+
.replace(/OPENAI_API_KEY=[^\s]+/gi, "OPENAI_API_KEY=***");
|
|
3535
|
+
}
|
|
3536
|
+
function printDataTransmissionNotice(command, input) {
|
|
3537
|
+
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.`);
|
|
3538
|
+
console.log("[notice] Do not include PII, secrets, or confidential data in these flags.");
|
|
3539
|
+
console.log(`[notice] No local files or repo content are uploaded. Only the explicit flag values for \`${command}\` are transmitted.`);
|
|
3540
|
+
console.log("");
|
|
3541
|
+
}
|
|
3542
|
+
main().catch((error) => {
|
|
3543
|
+
if (error instanceof CliError) {
|
|
3544
|
+
console.error(error.message);
|
|
3545
|
+
if (error.helpCommand) {
|
|
3546
|
+
console.error(`Run \`prompts-gpt help${error.helpCommand ? ` ${error.helpCommand}` : ""}\` for usage.`);
|
|
3547
|
+
}
|
|
3548
|
+
process.exitCode = error.exitCode;
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
if (error instanceof PromptsGptApiError) {
|
|
3552
|
+
console.error(formatApiError(error));
|
|
3553
|
+
process.exitCode = error.status === 401 || error.status === 403
|
|
3554
|
+
? CLI_EXIT_CODES.auth
|
|
3555
|
+
: error.status === 429
|
|
3556
|
+
? CLI_EXIT_CODES.rateLimit
|
|
3557
|
+
: error.code === "VALIDATION_ERROR"
|
|
3558
|
+
? CLI_EXIT_CODES.validation
|
|
3559
|
+
: CLI_EXIT_CODES.general;
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3563
|
+
console.error(msg.replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***"));
|
|
3564
|
+
if (process.env.PROMPTS_GPT_DEBUG === "1" && error instanceof Error && error.stack) {
|
|
3565
|
+
console.error(error.stack.replace(/pgpt_[a-zA-Z0-9]{4,}/g, "pgpt_***"));
|
|
3566
|
+
}
|
|
3567
|
+
process.exitCode = CLI_EXIT_CODES.general;
|
|
3568
|
+
});
|
|
3569
|
+
async function extractRunDiagnostics(logFile, provider, model) {
|
|
3570
|
+
const diagnostics = [];
|
|
3571
|
+
try {
|
|
3572
|
+
const { readFile: fsReadFile } = await import("node:fs/promises");
|
|
3573
|
+
const log = await fsReadFile(logFile, "utf8");
|
|
3574
|
+
const errorLines = log.split("\n").filter((line) => line.startsWith("ERROR:") || line.includes('"error"'));
|
|
3575
|
+
for (const line of errorLines.slice(0, 5)) {
|
|
3576
|
+
if (line.includes("not supported")) {
|
|
3577
|
+
const modelMatch = line.match(/The '([^']+)' model is not supported/);
|
|
3578
|
+
const badModel = modelMatch?.[1] ?? model;
|
|
3579
|
+
diagnostics.push(`Model "${badModel}" is not supported by ${provider} with your account.`);
|
|
3580
|
+
diagnostics.push(`Fix: prompts-gpt run --model o4-mini`);
|
|
3581
|
+
diagnostics.push(`Or: prompts-gpt run --agent claude`);
|
|
3582
|
+
break;
|
|
3583
|
+
}
|
|
3584
|
+
if (line.includes("unauthorized") || line.includes("authentication")) {
|
|
3585
|
+
diagnostics.push(`Authentication failed for ${provider}. Check your ${provider} account login.`);
|
|
3586
|
+
diagnostics.push(`Fix: Run the ${provider} CLI directly to verify auth: ${provider} --version`);
|
|
3587
|
+
break;
|
|
3588
|
+
}
|
|
3589
|
+
if (line.includes("rate_limit") || line.includes("429")) {
|
|
3590
|
+
diagnostics.push(`Rate limited by ${provider}. Wait and try again.`);
|
|
3591
|
+
break;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
if (diagnostics.length === 0 && errorLines.length > 0) {
|
|
3595
|
+
const firstError = errorLines[0].slice(0, 200);
|
|
3596
|
+
diagnostics.push(`Provider error: ${firstError}`);
|
|
3597
|
+
}
|
|
3598
|
+
if (diagnostics.length === 0) {
|
|
3599
|
+
diagnostics.push(`${provider} exited with code 1. Check the log for details:`);
|
|
3600
|
+
diagnostics.push(` cat ${logFile}`);
|
|
3601
|
+
diagnostics.push(`Try a different provider: prompts-gpt run --agent claude`);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
catch {
|
|
3605
|
+
diagnostics.push(`Could not read log file. Check: ${logFile}`);
|
|
3606
|
+
}
|
|
3607
|
+
return diagnostics;
|
|
3608
|
+
}
|
|
3609
|
+
function warnOnConfigIssues(config) {
|
|
3610
|
+
for (const w of config.configWarnings) {
|
|
3611
|
+
console.error(`[config warning] ${w}`);
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
function formatApiError(error) {
|
|
3615
|
+
const lines = [error.message];
|
|
3616
|
+
if (error.code === "NETWORK_ERROR" || error.code === "TIMEOUT") {
|
|
3617
|
+
lines.push("Run `prompts-gpt doctor` to diagnose connectivity issues.");
|
|
3618
|
+
}
|
|
3619
|
+
for (const [field, messages] of Object.entries(error.fieldErrors ?? {})) {
|
|
3620
|
+
const msgList = Array.isArray(messages) ? messages : [];
|
|
3621
|
+
for (const message of msgList) {
|
|
3622
|
+
lines.push(`- ${field}: ${String(message)}`);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
if (error.requestId) {
|
|
3626
|
+
lines.push(`Request ID: ${error.requestId}`);
|
|
3627
|
+
}
|
|
3628
|
+
if (error.recovery) {
|
|
3629
|
+
lines.push(error.recovery);
|
|
3630
|
+
}
|
|
3631
|
+
return lines.join("\n");
|
|
3632
|
+
}
|
|
3633
|
+
function formatTokenUsage(usage) {
|
|
3634
|
+
const parts = [
|
|
3635
|
+
`total=${usage.totalTokens.toLocaleString()}`,
|
|
3636
|
+
`input=${usage.inputTokens.toLocaleString()}`,
|
|
3637
|
+
`output=${usage.outputTokens.toLocaleString()}`,
|
|
3638
|
+
];
|
|
3639
|
+
if (usage.reasoningTokens > 0) {
|
|
3640
|
+
parts.push(`reasoning=${usage.reasoningTokens.toLocaleString()}`);
|
|
3641
|
+
}
|
|
3642
|
+
if (usage.cacheReadTokens > 0) {
|
|
3643
|
+
parts.push(`cache-read=${usage.cacheReadTokens.toLocaleString()}`);
|
|
3644
|
+
}
|
|
3645
|
+
if (usage.cacheWriteTokens > 0) {
|
|
3646
|
+
parts.push(`cache-write=${usage.cacheWriteTokens.toLocaleString()}`);
|
|
3647
|
+
}
|
|
3648
|
+
return parts.join(" | ");
|
|
3649
|
+
}
|
|
3650
|
+
//# sourceMappingURL=cli.js.map
|