@prompts-gpt/client 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,186 +1,591 @@
1
1
  #!/usr/bin/env node
2
- import { DEFAULT_PROMPTS_GPT_API_URL, DEFAULT_PROMPTS_GPT_OUT_DIR, PromptsGptApiError, PromptsGptClient, SUPPORTED_AGENT_TARGETS, loadLocalCredentials, saveLocalCredentials, syncPrompts, writeAgentFiles, writePromptMarkdownFiles, } from "./index.js";
3
- const CLI_EXIT_CODES = { success: 0, general: 1, auth: 2, validation: 3 };
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ import { DEFAULT_PROMPTS_GPT_API_URL, DEFAULT_PROMPTS_GPT_OUT_DIR, PROMPTS_GPT_CREDENTIALS_FILE, PromptsGptApiError, PromptsGptClient, SUPPORTED_AGENT_TARGETS, loadLocalCredentials, saveLocalCredentials, syncPrompts, writeAgentFiles, writePromptMarkdownFiles, } 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
+ };
4
14
  const VALID_TOOLS = ["Codex", "Claude Code", "Cursor", "GitHub Copilot", "ChatGPT", "Gemini", "Perplexity", "Grok", "DeepSeek", "Claude"];
15
+ const COMMANDS = ["init", "project", "pull", "generate", "sync", "install-agents", "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
+ }
5
27
  async function main() {
6
- const [command, ...args] = process.argv.slice(2);
7
- const flags = parseFlags(args);
8
- if (!command || command === "help" || flags.help) {
28
+ const argv = process.argv.slice(2);
29
+ if (argv.length === 0) {
9
30
  printHelp();
10
31
  return;
11
32
  }
12
- if (command === "version" || command === "--version" || flags.version) {
13
- const version = await getCliVersion();
14
- console.log(`@prompts-gpt/client v${version}`);
33
+ const normalizedArgv = normalizeLegacyFlags(argv);
34
+ const first = normalizedArgv[0];
35
+ if (first === "--help") {
36
+ printHelp();
37
+ return;
38
+ }
39
+ if (first === "--version") {
40
+ await printVersion();
41
+ return;
42
+ }
43
+ if (first === "help") {
44
+ handleHelpCommand(normalizedArgv.slice(1));
15
45
  return;
16
46
  }
47
+ if (first === "version") {
48
+ if (normalizedArgv.length > 1) {
49
+ throw new CliError("The `version` command does not accept additional arguments.", CLI_EXIT_CODES.usage, {
50
+ helpCommand: "version",
51
+ });
52
+ }
53
+ await printVersion();
54
+ return;
55
+ }
56
+ const command = asCommandName(first);
57
+ if (!command) {
58
+ throw new CliError(`Unknown command: ${first}.`, CLI_EXIT_CODES.usage);
59
+ }
60
+ if (!isRunnableCommand(command)) {
61
+ throw new CliError(`Unknown command: ${first}.`, CLI_EXIT_CODES.usage, {
62
+ helpCommand: "help",
63
+ });
64
+ }
65
+ const flags = parseCommandFlags(command, normalizedArgv.slice(1));
66
+ if (Boolean(flags.help)) {
67
+ printHelp(command);
68
+ return;
69
+ }
70
+ await runCommand(command, flags);
71
+ }
72
+ async function runCommand(command, flags) {
17
73
  if (command === "init") {
18
- const token = flags.token;
19
- if (!token)
20
- throw new Error("Run `prompts-gpt init --token <project-token>`.");
21
- if (typeof token !== "string" || token.length > 256)
22
- throw new Error("Token value is invalid or too long.");
23
- if (!token.startsWith("pgpt_"))
24
- throw new Error("Token must start with 'pgpt_'. Copy the full token from the Prompts-GPT dashboard.");
74
+ const token = await resolveTokenInput(flags, { command });
75
+ if (!token) {
76
+ throw new CliError("Run `prompts-gpt init --token <project-token>`, `--token-stdin`, or `--token-prompt`.", CLI_EXIT_CODES.validation, {
77
+ helpCommand: "init",
78
+ });
79
+ }
80
+ if (token.length > 256) {
81
+ throw new CliError("Token value is invalid or too long.", CLI_EXIT_CODES.validation, {
82
+ helpCommand: "init",
83
+ });
84
+ }
85
+ if (!token.startsWith("pgpt_")) {
86
+ throw new CliError("Token must start with `pgpt_`. Copy the full token from the Prompts-GPT dashboard.", CLI_EXIT_CODES.validation, {
87
+ helpCommand: "init",
88
+ });
89
+ }
90
+ const cwd = getResolvedCwd(flags);
25
91
  const result = await saveLocalCredentials({
26
92
  token,
27
- apiUrl: flags.apiUrl || DEFAULT_PROMPTS_GPT_API_URL,
28
- cwd: flags.cwd || process.cwd(),
93
+ apiUrl: getStringFlag(flags, "api-url") || DEFAULT_PROMPTS_GPT_API_URL,
94
+ cwd,
29
95
  });
30
96
  console.log(`Saved Prompts-GPT credentials to ${result.credentialsPath}`);
31
97
  console.log("The credentials file is added to .gitignore.");
32
98
  return;
33
99
  }
34
- const cwd = flags.cwd || process.cwd();
35
- const credentials = await loadLocalCredentials(cwd);
36
- const client = new PromptsGptClient({
37
- token: flags.token || credentials?.token || "",
38
- apiUrl: flags.apiUrl || credentials?.apiUrl || DEFAULT_PROMPTS_GPT_API_URL,
39
- fetch,
40
- });
41
- if (command === "project") {
42
- const project = await client.getProject();
43
- console.log(`${project.brandName} (${project.websiteUrl})`);
44
- return;
45
- }
46
100
  if (command === "pull") {
47
- const prompts = await client.pullPrompts(buildPullQuery(flags));
101
+ const parsedLimit = parseLimitFlag(getStringFlag(flags, "limit"));
102
+ const client = await createClientForCommand(command, flags);
103
+ const prompts = await client.pullPrompts(buildPullQuery(flags, parsedLimit));
48
104
  if (prompts.length === 0) {
49
105
  console.log("No prompts matched the query. Try different filters or check the project prompt library.");
50
106
  return;
51
107
  }
52
108
  const result = await writePromptMarkdownFiles(prompts, {
53
- cwd,
54
- outDir: flags.out || DEFAULT_PROMPTS_GPT_OUT_DIR,
109
+ cwd: getResolvedCwd(flags),
110
+ outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
55
111
  overwrite: Boolean(flags.overwrite),
56
112
  });
57
113
  console.log(`Wrote ${result.written.length} prompt file(s) to ${result.outDir}.`);
58
- if (result.skipped.length)
114
+ if (result.skipped.length) {
59
115
  console.log(`Skipped ${result.skipped.length} existing file(s). Use --overwrite to replace them.`);
116
+ }
60
117
  return;
61
118
  }
62
119
  if (command === "generate") {
63
- validateToolFlag(flags.tool);
64
- const prompt = await generatePromptFromFlags(client, flags);
120
+ validateToolFlag(getStringFlag(flags, "tool"));
121
+ const goal = getStringFlag(flags, "goal");
122
+ if (!goal) {
123
+ throw new CliError("Prompt generation requires `--goal`.", CLI_EXIT_CODES.validation, {
124
+ helpCommand: "generate",
125
+ });
126
+ }
127
+ validateAgentFlag(getStringFlag(flags, "agent"));
128
+ const client = await createClientForCommand(command, flags);
129
+ const prompt = await client.generatePrompt({
130
+ goal,
131
+ context: getStringFlag(flags, "context") || "",
132
+ constraints: getStringFlag(flags, "constraints") || "",
133
+ desiredOutput: getStringFlag(flags, "desired-output") || "A reusable prompt pack saved as Markdown.",
134
+ tool: getStringFlag(flags, "tool") || "Codex",
135
+ mode: getStringFlag(flags, "mode") || "implement",
136
+ artifactType: getStringFlag(flags, "artifact-type") || "prompt-file",
137
+ includeWebSearch: Boolean(flags["web-search"]),
138
+ });
139
+ const cwd = getResolvedCwd(flags);
65
140
  const result = await writePromptMarkdownFiles([prompt], {
66
141
  cwd,
67
- outDir: flags.out || DEFAULT_PROMPTS_GPT_OUT_DIR,
142
+ outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
68
143
  overwrite: Boolean(flags.overwrite),
69
144
  });
70
- const agentFlag = (flags.agent || flags.agents || flags.syncAgents);
71
- if (agentFlag) {
145
+ const shouldSyncAgents = Boolean(flags["sync-agents"]) || typeof getStringFlag(flags, "agent") === "string";
146
+ if (shouldSyncAgents) {
147
+ const target = getStringFlag(flags, "agent") || "all";
72
148
  const agentResult = await writeAgentFiles([prompt], {
73
149
  cwd,
74
- agent: typeof agentFlag === "string" ? agentFlag : "all",
150
+ agent: target,
75
151
  overwriteAgentFiles: true,
76
152
  });
77
153
  console.log(`Synced ${agentResult.written.length} agent file(s) for ${agentResult.targets.join(", ")}.`);
78
154
  }
79
155
  console.log(`Generated: ${prompt.title}`);
80
156
  console.log(`Wrote to ${result.written[0] ?? result.outDir}.`);
81
- if (result.skipped.length)
157
+ if (result.skipped.length) {
82
158
  console.log("Skipped existing generated prompt. Use --overwrite to replace it.");
159
+ }
83
160
  return;
84
161
  }
85
162
  if (command === "sync") {
86
- validateToolFlag(flags.tool);
163
+ validateToolFlag(getStringFlag(flags, "tool"));
164
+ validateAgentFlag(getStringFlag(flags, "agent"));
165
+ const parsedLimit = parseLimitFlag(getStringFlag(flags, "limit"));
87
166
  const prompts = [];
88
- if (flags.goal) {
89
- prompts.push(await generatePromptFromFlags(client, flags));
167
+ const goal = getStringFlag(flags, "goal");
168
+ const generatedOnly = Boolean(flags["generated-only"]);
169
+ const client = await createClientForCommand(command, flags);
170
+ if (goal) {
171
+ prompts.push(await client.generatePrompt({
172
+ goal,
173
+ context: getStringFlag(flags, "context") || "",
174
+ constraints: getStringFlag(flags, "constraints") || "",
175
+ desiredOutput: getStringFlag(flags, "desired-output") || "A reusable prompt pack saved as Markdown.",
176
+ tool: getStringFlag(flags, "tool") || "Codex",
177
+ mode: getStringFlag(flags, "mode") || "implement",
178
+ artifactType: getStringFlag(flags, "artifact-type") || "prompt-file",
179
+ includeWebSearch: Boolean(flags["web-search"]),
180
+ }));
181
+ }
182
+ if (!generatedOnly) {
183
+ prompts.push(...await client.pullPrompts(buildPullQuery(flags, parsedLimit)));
184
+ }
185
+ if (prompts.length === 0) {
186
+ throw new CliError("No prompts to sync. Provide `--goal` or remove `--generated-only`.", CLI_EXIT_CODES.validation, {
187
+ helpCommand: "sync",
188
+ });
90
189
  }
91
- if (!flags.generatedOnly) {
92
- prompts.push(...await client.pullPrompts(buildPullQuery(flags)));
190
+ if (Boolean(flags["dry-run"])) {
191
+ console.log(`[dry-run] Would sync ${prompts.length} prompt(s) for agent target: ${getStringFlag(flags, "agent") || "all"}.`);
192
+ for (const p of prompts)
193
+ console.log(` - ${p.title} (${p.source})`);
194
+ return;
93
195
  }
94
- if (prompts.length === 0)
95
- throw new Error("No prompts to sync. Provide --goal or remove --generated-only.");
96
196
  const result = await syncPrompts(prompts, {
97
- cwd,
98
- outDir: flags.out || DEFAULT_PROMPTS_GPT_OUT_DIR,
197
+ cwd: getResolvedCwd(flags),
198
+ outDir: getStringFlag(flags, "out") || DEFAULT_PROMPTS_GPT_OUT_DIR,
99
199
  overwrite: Boolean(flags.overwrite),
100
- agent: flags.agent || flags.agents || "all",
200
+ agent: getStringFlag(flags, "agent") || "all",
101
201
  });
102
202
  console.log(`Synced ${result.markdown.written.length} Markdown prompt file(s) to ${result.markdown.outDir}.`);
103
203
  console.log(`Synced ${result.agents.written.length} agent integration file(s): ${result.agents.targets.join(", ")}.`);
104
204
  console.log(`Updated manifest: ${result.manifest.manifestPath}`);
105
- if (result.markdown.skipped.length)
205
+ if (result.markdown.skipped.length) {
106
206
  console.log(`Skipped ${result.markdown.skipped.length} existing Markdown file(s). Use --overwrite to replace them.`);
207
+ }
107
208
  return;
108
209
  }
109
210
  if (command === "install-agents") {
110
- const prompts = await client.pullPrompts(buildPullQuery(flags));
211
+ validateAgentFlag(getStringFlag(flags, "agent"));
212
+ const parsedLimit = parseLimitFlag(getStringFlag(flags, "limit"));
213
+ const client = await createClientForCommand(command, flags);
214
+ const prompts = await client.pullPrompts(buildPullQuery(flags, parsedLimit));
215
+ if (prompts.length === 0) {
216
+ console.log("No prompts matched the query, so no agent files were written.");
217
+ return;
218
+ }
219
+ if (Boolean(flags["dry-run"])) {
220
+ console.log(`[dry-run] Would install agent files for ${prompts.length} prompt(s), target: ${getStringFlag(flags, "agent") || "all"}.`);
221
+ for (const p of prompts)
222
+ console.log(` - ${p.title}`);
223
+ return;
224
+ }
111
225
  const result = await writeAgentFiles(prompts, {
112
- cwd,
113
- agent: flags.agent || flags.agents || "all",
226
+ cwd: getResolvedCwd(flags),
227
+ agent: getStringFlag(flags, "agent") || "all",
114
228
  overwriteAgentFiles: true,
115
229
  });
116
230
  console.log(`Synced ${result.written.length} agent integration file(s): ${result.targets.join(", ")}.`);
117
231
  return;
118
232
  }
119
- throw new Error(`Unknown command: ${command}. Run \`prompts-gpt help\` for usage.`);
233
+ if (command === "project") {
234
+ const client = await createClientForCommand(command, flags);
235
+ const project = await client.getProject();
236
+ console.log(`${project.brandName} (${project.websiteUrl})`);
237
+ return;
238
+ }
239
+ }
240
+ async function createClientForCommand(command, flags) {
241
+ const cwd = getResolvedCwd(flags);
242
+ const explicitToken = await resolveTokenInput(flags, { command });
243
+ const explicitApiUrl = getStringFlag(flags, "api-url");
244
+ const credentialsPath = path.resolve(cwd, DEFAULT_PROMPTS_GPT_OUT_DIR, PROMPTS_GPT_CREDENTIALS_FILE);
245
+ const hasCredentialsFile = existsSync(credentialsPath);
246
+ const credentials = await loadLocalCredentials(cwd);
247
+ if (hasCredentialsFile && !credentials && !explicitToken) {
248
+ 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 });
249
+ }
250
+ const token = explicitToken || credentials?.token || "";
251
+ if (!token) {
252
+ 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.", CLI_EXIT_CODES.auth, { helpCommand: command });
253
+ }
254
+ return new PromptsGptClient({
255
+ token,
256
+ apiUrl: explicitApiUrl || credentials?.apiUrl || DEFAULT_PROMPTS_GPT_API_URL,
257
+ fetch,
258
+ });
259
+ }
260
+ function buildPullQuery(flags, limit) {
261
+ return {
262
+ q: getStringFlag(flags, "query") || getStringFlag(flags, "q"),
263
+ category: getStringFlag(flags, "category"),
264
+ tool: getStringFlag(flags, "tool"),
265
+ outputType: getStringFlag(flags, "output-type"),
266
+ limit,
267
+ };
268
+ }
269
+ function parseCommandFlags(command, argv) {
270
+ const options = getCommandOptions(command);
271
+ try {
272
+ const result = parseArgs({
273
+ args: argv,
274
+ options,
275
+ allowPositionals: false,
276
+ strict: true,
277
+ });
278
+ const values = result.values;
279
+ validateFlagConflicts(command, values);
280
+ return values;
281
+ }
282
+ catch (error) {
283
+ throw toCliParseError(error, command);
284
+ }
285
+ }
286
+ function handleHelpCommand(argv) {
287
+ try {
288
+ const { positionals, values } = parseArgs({
289
+ args: argv,
290
+ options: { help: { type: "boolean" } },
291
+ allowPositionals: true,
292
+ strict: true,
293
+ });
294
+ if (values.help) {
295
+ printHelp();
296
+ return;
297
+ }
298
+ if (positionals.length > 1) {
299
+ throw new CliError("The `help` command accepts at most one command name.", CLI_EXIT_CODES.usage, {
300
+ helpCommand: "help",
301
+ });
302
+ }
303
+ if (positionals.length === 0) {
304
+ printHelp();
305
+ return;
306
+ }
307
+ const topic = asCommandName(positionals[0]);
308
+ if (!topic) {
309
+ throw new CliError(`Unknown help topic: ${positionals[0]}.`, CLI_EXIT_CODES.usage, {
310
+ helpCommand: "help",
311
+ });
312
+ }
313
+ printHelp(topic);
314
+ }
315
+ catch (error) {
316
+ if (error instanceof CliError)
317
+ throw error;
318
+ throw toCliParseError(error, "help");
319
+ }
120
320
  }
121
- function buildPullQuery(flags) {
321
+ function getCommandOptions(command) {
322
+ const common = {
323
+ help: { type: "boolean" },
324
+ token: { type: "string" },
325
+ "token-stdin": { type: "boolean" },
326
+ "token-prompt": { type: "boolean" },
327
+ "api-url": { type: "string" },
328
+ cwd: { type: "string" },
329
+ };
330
+ if (command === "init") {
331
+ return common;
332
+ }
333
+ if (command === "project") {
334
+ return common;
335
+ }
336
+ if (command === "pull") {
337
+ return {
338
+ ...common,
339
+ query: { type: "string" },
340
+ q: { type: "string" },
341
+ category: { type: "string" },
342
+ tool: { type: "string" },
343
+ "output-type": { type: "string" },
344
+ limit: { type: "string" },
345
+ out: { type: "string" },
346
+ overwrite: { type: "boolean" },
347
+ };
348
+ }
349
+ if (command === "generate") {
350
+ return {
351
+ ...common,
352
+ goal: { type: "string" },
353
+ context: { type: "string" },
354
+ constraints: { type: "string" },
355
+ "desired-output": { type: "string" },
356
+ tool: { type: "string" },
357
+ mode: { type: "string" },
358
+ "artifact-type": { type: "string" },
359
+ "web-search": { type: "boolean" },
360
+ out: { type: "string" },
361
+ overwrite: { type: "boolean" },
362
+ agent: { type: "string" },
363
+ "sync-agents": { type: "boolean" },
364
+ };
365
+ }
366
+ if (command === "sync") {
367
+ return {
368
+ ...common,
369
+ goal: { type: "string" },
370
+ context: { type: "string" },
371
+ constraints: { type: "string" },
372
+ "desired-output": { type: "string" },
373
+ tool: { type: "string" },
374
+ mode: { type: "string" },
375
+ "artifact-type": { type: "string" },
376
+ "web-search": { type: "boolean" },
377
+ query: { type: "string" },
378
+ q: { type: "string" },
379
+ category: { type: "string" },
380
+ "output-type": { type: "string" },
381
+ limit: { type: "string" },
382
+ out: { type: "string" },
383
+ overwrite: { type: "boolean" },
384
+ agent: { type: "string" },
385
+ "generated-only": { type: "boolean" },
386
+ "dry-run": { type: "boolean" },
387
+ };
388
+ }
122
389
  return {
123
- q: (flags.query || flags.q),
124
- category: flags.category,
125
- tool: flags.tool,
126
- outputType: flags.outputType,
127
- limit: flags.limit,
390
+ ...common,
391
+ query: { type: "string" },
392
+ q: { type: "string" },
393
+ category: { type: "string" },
394
+ tool: { type: "string" },
395
+ "output-type": { type: "string" },
396
+ limit: { type: "string" },
397
+ agent: { type: "string" },
398
+ "dry-run": { type: "boolean" },
128
399
  };
129
400
  }
401
+ function normalizeLegacyFlags(argv) {
402
+ const aliases = new Map([
403
+ ["agents", "agent"],
404
+ ["apiUrl", "api-url"],
405
+ ["desiredOutput", "desired-output"],
406
+ ["artifactType", "artifact-type"],
407
+ ["outputType", "output-type"],
408
+ ["webSearch", "web-search"],
409
+ ["generatedOnly", "generated-only"],
410
+ ["syncAgents", "sync-agents"],
411
+ ["tokenStdin", "token-stdin"],
412
+ ["tokenPrompt", "token-prompt"],
413
+ ["dryRun", "dry-run"],
414
+ ]);
415
+ return argv.map((arg) => {
416
+ if (!arg.startsWith("--"))
417
+ return arg;
418
+ const eqIndex = arg.indexOf("=");
419
+ const rawKey = eqIndex === -1 ? arg.slice(2) : arg.slice(2, eqIndex);
420
+ const mappedKey = aliases.get(rawKey) ?? rawKey;
421
+ return eqIndex === -1 ? `--${mappedKey}` : `--${mappedKey}=${arg.slice(eqIndex + 1)}`;
422
+ });
423
+ }
424
+ function toCliParseError(error, command) {
425
+ const message = normalizeParseErrorMessage(error instanceof Error ? error.message : String(error));
426
+ return new CliError(message, CLI_EXIT_CODES.usage, {
427
+ helpCommand: command,
428
+ });
429
+ }
430
+ function normalizeParseErrorMessage(message) {
431
+ const trimmed = message.trim();
432
+ if (trimmed.startsWith("Unknown option")) {
433
+ return `${trimmed}.`;
434
+ }
435
+ return trimmed;
436
+ }
437
+ function validateFlagConflicts(command, flags) {
438
+ const tokenSourceCount = [Boolean(flags.token), Boolean(flags["token-stdin"]), Boolean(flags["token-prompt"])].filter(Boolean).length;
439
+ if (tokenSourceCount > 1) {
440
+ throw new CliError("Use only one token source: `--token`, `--token-stdin`, or `--token-prompt`.", CLI_EXIT_CODES.usage, {
441
+ helpCommand: command,
442
+ });
443
+ }
444
+ if (flags.query && flags.q) {
445
+ throw new CliError("Use either `--query` or `--q`, not both.", CLI_EXIT_CODES.usage, {
446
+ helpCommand: command,
447
+ });
448
+ }
449
+ }
450
+ async function resolveTokenInput(flags, options) {
451
+ const explicitToken = getStringFlag(flags, "token")?.trim();
452
+ if (explicitToken) {
453
+ return explicitToken;
454
+ }
455
+ if (flags["token-stdin"]) {
456
+ const token = await readTokenFromStdin();
457
+ if (!token) {
458
+ throw new CliError("No token was received on stdin. Pipe a project token into `--token-stdin`.", CLI_EXIT_CODES.validation, {
459
+ helpCommand: options.command,
460
+ });
461
+ }
462
+ return token;
463
+ }
464
+ if (flags["token-prompt"]) {
465
+ return readTokenFromPrompt(options.command);
466
+ }
467
+ return undefined;
468
+ }
469
+ async function readTokenFromStdin() {
470
+ if (process.stdin.isTTY) {
471
+ throw new CliError("`--token-stdin` expects piped input. Use `--token-prompt` for an interactive terminal.", CLI_EXIT_CODES.usage);
472
+ }
473
+ const chunks = [];
474
+ let totalLength = 0;
475
+ for await (const chunk of process.stdin) {
476
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
477
+ totalLength += buffer.length;
478
+ if (totalLength > MAX_STDIN_TOKEN_LENGTH) {
479
+ throw new CliError("Token input from stdin is too long.", CLI_EXIT_CODES.validation);
480
+ }
481
+ chunks.push(buffer);
482
+ }
483
+ return Buffer.concat(chunks).toString("utf8").trim();
484
+ }
485
+ function readTokenFromPrompt(command) {
486
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
487
+ throw new CliError("`--token-prompt` requires an interactive terminal. Use `--token-stdin` for CI or piped input.", CLI_EXIT_CODES.usage, {
488
+ helpCommand: command,
489
+ });
490
+ }
491
+ return new Promise((resolve, reject) => {
492
+ const stdin = process.stdin;
493
+ const stdout = process.stdout;
494
+ let token = "";
495
+ const cleanup = () => {
496
+ stdin.removeListener("data", onData);
497
+ stdin.setRawMode?.(false);
498
+ stdin.pause();
499
+ };
500
+ const finish = (callback) => {
501
+ stdout.write("\n");
502
+ cleanup();
503
+ callback();
504
+ };
505
+ const onData = (chunk) => {
506
+ const value = typeof chunk === "string" ? chunk : chunk.toString("utf8");
507
+ for (const char of value) {
508
+ if (char === "\u0003") {
509
+ finish(() => reject(new CliError("Token entry cancelled.", CLI_EXIT_CODES.general, { helpCommand: command })));
510
+ return;
511
+ }
512
+ if (char === "\r" || char === "\n") {
513
+ const trimmed = token.trim();
514
+ if (!trimmed) {
515
+ finish(() => reject(new CliError("Project token is required.", CLI_EXIT_CODES.validation, { helpCommand: command })));
516
+ return;
517
+ }
518
+ finish(() => resolve(trimmed));
519
+ return;
520
+ }
521
+ if (char === "\u007f" || char === "\b") {
522
+ token = token.slice(0, -1);
523
+ continue;
524
+ }
525
+ if (char >= " ") {
526
+ token += char;
527
+ if (token.length > MAX_STDIN_TOKEN_LENGTH) {
528
+ finish(() => reject(new CliError("Token input is too long.", CLI_EXIT_CODES.validation, { helpCommand: command })));
529
+ return;
530
+ }
531
+ }
532
+ }
533
+ };
534
+ stdout.write("Project token: ");
535
+ stdin.setEncoding("utf8");
536
+ stdin.setRawMode?.(true);
537
+ stdin.resume();
538
+ stdin.on("data", onData);
539
+ });
540
+ }
541
+ function parseLimitFlag(raw) {
542
+ if (raw === undefined)
543
+ return undefined;
544
+ if (!/^\d+$/.test(raw)) {
545
+ throw new CliError("`--limit` must be a positive integer.", CLI_EXIT_CODES.validation);
546
+ }
547
+ const value = Number.parseInt(raw, 10);
548
+ if (!Number.isSafeInteger(value) || value < 1) {
549
+ throw new CliError("`--limit` must be a positive integer.", CLI_EXIT_CODES.validation);
550
+ }
551
+ return value;
552
+ }
130
553
  function validateToolFlag(tool) {
131
554
  if (!tool)
132
555
  return;
133
556
  const validSet = new Set(VALID_TOOLS);
134
557
  if (!validSet.has(tool)) {
135
- throw new Error(`Invalid --tool "${tool}". Valid tools: ${VALID_TOOLS.join(", ")}.`);
136
- }
137
- }
138
- async function generatePromptFromFlags(client, flags) {
139
- const goal = flags.goal;
140
- if (!goal)
141
- throw new Error("Prompt generation requires --goal.");
142
- return client.generatePrompt({
143
- goal,
144
- context: flags.context || "",
145
- constraints: flags.constraints || "",
146
- desiredOutput: flags.desiredOutput || "A reusable prompt pack saved as Markdown.",
147
- tool: flags.tool || "Codex",
148
- mode: flags.mode || "implement",
149
- artifactType: flags.artifactType || "prompt-file",
150
- includeWebSearch: Boolean(flags.webSearch),
151
- });
558
+ throw new CliError(`Invalid --tool "${tool}". Valid tools: ${VALID_TOOLS.join(", ")}.`, CLI_EXIT_CODES.validation);
559
+ }
152
560
  }
153
- const MAX_FLAG_VALUE_LENGTH = 2000;
154
- function parseFlags(args) {
155
- const flags = {};
156
- for (let index = 0; index < args.length; index += 1) {
157
- const arg = args[index];
158
- if (!arg.startsWith("--"))
159
- continue;
160
- const eqIndex = arg.indexOf("=");
161
- if (eqIndex !== -1) {
162
- const key = normalizeFlag(arg.slice(2, eqIndex));
163
- if (!key || key.length > 64)
164
- continue;
165
- flags[key] = arg.slice(eqIndex + 1).slice(0, MAX_FLAG_VALUE_LENGTH);
166
- continue;
167
- }
168
- const key = normalizeFlag(arg.slice(2));
169
- if (!key || key.length > 64)
170
- continue;
171
- const next = args[index + 1];
172
- if (!next || next.startsWith("--")) {
173
- flags[key] = true;
174
- }
175
- else {
176
- flags[key] = String(next).slice(0, MAX_FLAG_VALUE_LENGTH);
177
- index += 1;
178
- }
561
+ function validateAgentFlag(agent) {
562
+ if (!agent)
563
+ return;
564
+ const targets = agent.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
565
+ const validSet = new Set([...SUPPORTED_AGENT_TARGETS, "all"]);
566
+ const invalid = [...new Set(targets.filter((target) => !validSet.has(target)))];
567
+ if (invalid.length) {
568
+ throw new CliError(`Invalid --agent target: ${invalid.join(", ")}. Use ${SUPPORTED_AGENT_TARGETS.join(", ")}, or all.`, CLI_EXIT_CODES.validation);
179
569
  }
180
- return flags;
181
570
  }
182
- function normalizeFlag(raw) {
183
- return raw.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
571
+ function getResolvedCwd(flags) {
572
+ return path.resolve(getStringFlag(flags, "cwd") || process.cwd());
573
+ }
574
+ function getStringFlag(flags, name) {
575
+ const value = flags[name];
576
+ return typeof value === "string" ? value : undefined;
577
+ }
578
+ function asCommandName(value) {
579
+ if (!value)
580
+ return undefined;
581
+ return COMMANDS.find((command) => command === value);
582
+ }
583
+ function isRunnableCommand(command) {
584
+ return command !== "help" && command !== "version";
585
+ }
586
+ async function printVersion() {
587
+ const version = await getCliVersion();
588
+ console.log(`@prompts-gpt/client v${version}`);
184
589
  }
185
590
  async function getCliVersion() {
186
591
  try {
@@ -192,47 +597,246 @@ async function getCliVersion() {
192
597
  return "0.0.0";
193
598
  }
194
599
  }
195
- function printHelp() {
600
+ function printHelp(command) {
601
+ if (command) {
602
+ console.log(getCommandHelp(command));
603
+ return;
604
+ }
196
605
  console.log(`Prompts-GPT CLI
197
606
 
198
- Commands:
199
- prompts-gpt init --token <token> [--api-url https://prompts-gpt.com] [--cwd /path/to/repo]
607
+ Usage:
608
+ prompts-gpt <command> [options]
609
+ prompts-gpt help [command]
200
610
  prompts-gpt version
201
- prompts-gpt project
202
- prompts-gpt pull [--query "repo audit"] [--category coding] [--tool Codex] [--limit 25] [--out .prompts-gpt] [--overwrite]
203
- prompts-gpt generate --goal "Review this diff" [--context "Next.js app"] [--agent codex,cursor,vscode]
204
- prompts-gpt sync [--goal "Review this diff"] [--limit 25] [--agent all] [--out .prompts-gpt] [--cwd /path/to/repo]
205
- prompts-gpt install-agents [--agent codex,cursor,vscode,copilot]
206
611
 
207
- Options:
208
- --token <token> Project API token
209
- --api-url <url> Custom API base URL for self-hosted instances
210
- --cwd <path> Target local project directory for config and generated files
211
- --agent <targets> Comma-separated agent targets: codex,cursor,vscode,copilot or all
212
- --help Show this help message
213
- --version Show the current CLI version
214
-
215
- Agent targets:
216
- codex writes AGENTS.md with prompt-pack links
217
- cursor writes .cursor/rules/prompts-gpt-*.mdc
218
- vscode writes .github/copilot-instructions.md and .vscode snippets
219
- copilot writes .github/prompts/*.prompt.md
612
+ Commands:
613
+ init Save a project token in .prompts-gpt/.credentials.json
614
+ project Show the current project linked to the token
615
+ pull Download prompt packs as Markdown files
616
+ generate Generate one prompt pack from a goal
617
+ sync Generate and/or pull prompt packs, then sync agent files
618
+ install-agents Refresh agent-specific instruction files from pulled prompts
619
+ version Show the current CLI version
620
+ help Show global or command-specific help
621
+
622
+ Global options:
623
+ --help Show help
624
+ --version Show the current CLI version
220
625
 
221
626
  Supported tools: ${VALID_TOOLS.join(", ")}
222
627
  Supported agent targets: ${SUPPORTED_AGENT_TARGETS.join(", ")}
223
628
  `);
224
629
  }
630
+ function getCommandHelp(command) {
631
+ if (command === "init") {
632
+ return `prompts-gpt init
633
+
634
+ Usage:
635
+ prompts-gpt init (--token <project-token> | --token-stdin | --token-prompt) [--api-url <url>] [--cwd <path>]
636
+
637
+ Why use it:
638
+ Stores a project token in a local credentials file so day-one CLI usage does not require re-pasting secrets on every command.
639
+
640
+ Options:
641
+ --token <token> Project API token. Must start with pgpt_.
642
+ --token-stdin Read the project token from stdin so it does not end up in shell history.
643
+ --token-prompt Prompt for the project token without echoing it.
644
+ --api-url <url> Custom API base URL for self-hosted instances.
645
+ --cwd <path> Target project directory that will receive .prompts-gpt/.credentials.json.
646
+ --help Show this command help.
647
+ `;
648
+ }
649
+ if (command === "project") {
650
+ return `prompts-gpt project
651
+
652
+ Usage:
653
+ prompts-gpt project [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
654
+
655
+ Why use it:
656
+ Verifies that the current local token resolves to the expected Prompts-GPT project before syncing files.
657
+
658
+ Options:
659
+ --token <token> Override the saved local token for this command only.
660
+ --token-stdin Read the override token from stdin for this command only.
661
+ --token-prompt Prompt for the override token without echoing it.
662
+ --api-url <url> Override the saved API base URL for this command only.
663
+ --cwd <path> Project directory to inspect for local credentials.
664
+ --help Show this command help.
665
+ `;
666
+ }
667
+ if (command === "pull") {
668
+ return `prompts-gpt pull
669
+
670
+ Usage:
671
+ 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>]
672
+
673
+ Why use it:
674
+ Pulls prompt packs into local Markdown files so teams can review and use the same prompt assets inside their repository.
675
+
676
+ Options:
677
+ --query <text> Search query for the project prompt library.
678
+ --q <text> Short alias for --query.
679
+ --category <name> Filter prompts by category.
680
+ --tool <name> Filter prompts by supported tool.
681
+ --output-type <name> Filter prompts by output type.
682
+ --limit <n> Positive integer limit for returned prompts.
683
+ --out <dir> Output directory inside the current project. Default: .prompts-gpt
684
+ --overwrite Replace existing Markdown files instead of skipping them.
685
+ --token <token> Override the saved local token for this command only.
686
+ --token-stdin Read the override token from stdin for this command only.
687
+ --token-prompt Prompt for the override token without echoing it.
688
+ --api-url <url> Override the saved API base URL for this command only.
689
+ --cwd <path> Project directory to inspect for local credentials and output files.
690
+ --help Show this command help.
691
+ `;
692
+ }
693
+ if (command === "generate") {
694
+ return `prompts-gpt generate
695
+
696
+ Usage:
697
+ 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>]
698
+
699
+ Why use it:
700
+ Creates a new reusable prompt pack from a concrete developer goal, then optionally installs agent-readable files immediately.
701
+
702
+ Options:
703
+ --goal <text> Required. The task to generate a prompt pack for.
704
+ --context <text> Extra project or stack context for the generator.
705
+ --constraints <text> Constraints that the generated prompt must honor.
706
+ --desired-output <text> Preferred output shape for the generated prompt pack.
707
+ --tool <name> Target tool. Default: Codex
708
+ --mode <name> Prompt generation mode. Default: implement
709
+ --artifact-type <name> Artifact type. Default: prompt-file
710
+ --web-search Allow server-side web research when generating the prompt.
711
+ --out <dir> Output directory inside the current project. Default: .prompts-gpt
712
+ --overwrite Replace an existing generated Markdown file.
713
+ --agent <targets> Sync agent files for specific targets after generation.
714
+ --sync-agents Sync all agent files after generation.
715
+ --token <token> Override the saved local token for this command only.
716
+ --token-stdin Read the override token from stdin for this command only.
717
+ --token-prompt Prompt for the override token without echoing it.
718
+ --api-url <url> Override the saved API base URL for this command only.
719
+ --cwd <path> Project directory to inspect for local credentials and output files.
720
+ --help Show this command help.
721
+ `;
722
+ }
723
+ if (command === "sync") {
724
+ return `prompts-gpt sync
725
+
726
+ Usage:
727
+ 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>]
728
+
729
+ Why use it:
730
+ Gives teams a one-command path to refresh Markdown prompts, agent files, and the local manifest from the same source of truth.
731
+
732
+ Options:
733
+ --goal <text> Generate one prompt pack before syncing.
734
+ --generated-only Skip library pull and sync only the generated prompt pack from --goal.
735
+ --query <text> Search query for pulled prompt packs.
736
+ --q <text> Short alias for --query.
737
+ --category <name> Filter pulled prompt packs by category.
738
+ --tool <name> Filter pulled prompt packs and generated output by tool.
739
+ --output-type <name> Filter pulled prompt packs by output type.
740
+ --limit <n> Positive integer limit for pulled prompt packs.
741
+ --context <text> Extra project or stack context for generated prompt packs.
742
+ --constraints <text> Constraints for generated prompt packs.
743
+ --desired-output <text> Preferred output shape for generated prompt packs.
744
+ --mode <name> Prompt generation mode. Default: implement
745
+ --artifact-type <name> Artifact type. Default: prompt-file
746
+ --web-search Allow server-side web research when generating a prompt pack.
747
+ --agent <targets> Comma-separated agent targets. Default: all
748
+ --out <dir> Output directory inside the current project. Default: .prompts-gpt
749
+ --overwrite Replace existing Markdown files instead of skipping them.
750
+ --dry-run Preview what would be synced without writing any files.
751
+ --token <token> Override the saved local token for this command only.
752
+ --token-stdin Read the override token from stdin for this command only.
753
+ --token-prompt Prompt for the override token without echoing it.
754
+ --api-url <url> Override the saved API base URL for this command only.
755
+ --cwd <path> Project directory to inspect for local credentials and output files.
756
+ --help Show this command help.
757
+ `;
758
+ }
759
+ if (command === "install-agents") {
760
+ return `prompts-gpt install-agents
761
+
762
+ Usage:
763
+ prompts-gpt install-agents [--query <text> | --q <text>] [--category <name>] [--tool <name>] [--output-type <name>] [--limit <n>] [--agent <targets>] [--dry-run] [--token <project-token> | --token-stdin | --token-prompt] [--api-url <url>] [--cwd <path>]
764
+
765
+ Why use it:
766
+ Refreshes agent-specific instruction files from the current prompt library without rewriting the Markdown prompt directory.
767
+
768
+ Options:
769
+ --query <text> Search query for the project prompt library.
770
+ --q <text> Short alias for --query.
771
+ --category <name> Filter prompts by category.
772
+ --tool <name> Filter prompts by supported tool.
773
+ --output-type <name> Filter prompts by output type.
774
+ --limit <n> Positive integer limit for returned prompts.
775
+ --agent <targets> Comma-separated agent targets. Default: all
776
+ --dry-run Preview what would be installed without writing any files.
777
+ --token <token> Override the saved local token for this command only.
778
+ --token-stdin Read the override token from stdin for this command only.
779
+ --token-prompt Prompt for the override token without echoing it.
780
+ --api-url <url> Override the saved API base URL for this command only.
781
+ --cwd <path> Project directory to inspect for local credentials and agent files.
782
+ --help Show this command help.
783
+ `;
784
+ }
785
+ if (command === "version") {
786
+ return `prompts-gpt version
787
+
788
+ Usage:
789
+ prompts-gpt version
790
+
791
+ Why use it:
792
+ Confirms the installed CLI version when debugging package resolution or publish issues.
793
+ `;
794
+ }
795
+ return `prompts-gpt help
796
+
797
+ Usage:
798
+ prompts-gpt help [command]
799
+
800
+ Why use it:
801
+ Shows focused usage for the exact command you are trying to run so shell users do not have to scan the full command list.
802
+ `;
803
+ }
225
804
  main().catch((error) => {
805
+ if (error instanceof CliError) {
806
+ console.error(error.message);
807
+ if (error.helpCommand) {
808
+ console.error(`Run \`prompts-gpt help${error.helpCommand ? ` ${error.helpCommand}` : ""}\` for usage.`);
809
+ }
810
+ process.exitCode = error.exitCode;
811
+ return;
812
+ }
226
813
  if (error instanceof PromptsGptApiError) {
227
- console.error(`${error.message}\n${error.recovery}`);
814
+ console.error(formatApiError(error));
228
815
  process.exitCode = error.status === 401 || error.status === 403
229
816
  ? CLI_EXIT_CODES.auth
230
- : error.code === "VALIDATION_ERROR"
231
- ? CLI_EXIT_CODES.validation
232
- : CLI_EXIT_CODES.general;
817
+ : error.status === 429
818
+ ? CLI_EXIT_CODES.rateLimit
819
+ : error.code === "VALIDATION_ERROR"
820
+ ? CLI_EXIT_CODES.validation
821
+ : CLI_EXIT_CODES.general;
233
822
  return;
234
823
  }
235
824
  console.error(error instanceof Error ? error.message : String(error));
236
825
  process.exitCode = CLI_EXIT_CODES.general;
237
826
  });
827
+ function formatApiError(error) {
828
+ const lines = [error.message];
829
+ for (const [field, messages] of Object.entries(error.fieldErrors ?? {})) {
830
+ for (const message of messages ?? []) {
831
+ lines.push(`- ${field}: ${message}`);
832
+ }
833
+ }
834
+ if (error.requestId) {
835
+ lines.push(`Request ID: ${error.requestId}`);
836
+ }
837
+ if (error.recovery) {
838
+ lines.push(error.recovery);
839
+ }
840
+ return lines.join("\n");
841
+ }
238
842
  //# sourceMappingURL=cli.js.map