propr-cli 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +549 -0
  2. package/dist/api/agentTank.js +27 -0
  3. package/dist/api/agents.js +201 -0
  4. package/dist/api/client.js +284 -0
  5. package/dist/api/errors.js +145 -0
  6. package/dist/api/implement.js +147 -0
  7. package/dist/api/index.js +26 -0
  8. package/dist/api/logs.js +59 -0
  9. package/dist/api/plans.js +160 -0
  10. package/dist/api/relay.js +73 -0
  11. package/dist/api/repos.js +243 -0
  12. package/dist/api/settings.js +219 -0
  13. package/dist/api/system.js +53 -0
  14. package/dist/api/tasks.js +140 -0
  15. package/dist/api/todos.js +77 -0
  16. package/dist/api/types.js +6 -0
  17. package/dist/assets/.env.example +183 -0
  18. package/dist/assets/env.example.txt +198 -0
  19. package/dist/commands/agentCommands.js +405 -0
  20. package/dist/commands/checkCommands.js +384 -0
  21. package/dist/commands/implementCommands.js +178 -0
  22. package/dist/commands/index.js +22 -0
  23. package/dist/commands/initCommands.js +167 -0
  24. package/dist/commands/initStack.js +193 -0
  25. package/dist/commands/logCommands.js +170 -0
  26. package/dist/commands/planCommands.js +552 -0
  27. package/dist/commands/relayCommands.js +149 -0
  28. package/dist/commands/repoCommands.js +526 -0
  29. package/dist/commands/settingCommands.js +237 -0
  30. package/dist/commands/stackCommands.js +86 -0
  31. package/dist/commands/startCommand.js +36 -0
  32. package/dist/commands/systemCommands.js +221 -0
  33. package/dist/commands/tankCommands.js +55 -0
  34. package/dist/commands/taskCommands.js +554 -0
  35. package/dist/commands/todoCommands.js +620 -0
  36. package/dist/commands/uiDocsCommands.js +69 -0
  37. package/dist/config/ConfigManager.js +360 -0
  38. package/dist/config/index.js +8 -0
  39. package/dist/config/types.js +16 -0
  40. package/dist/index.js +276 -0
  41. package/dist/orchestrator/format.js +31 -0
  42. package/dist/orchestrator/index.js +102 -0
  43. package/dist/orchestrator/manifest.json +16 -0
  44. package/dist/orchestrator/orchestrator.mjs +798 -0
  45. package/dist/orchestrator/types.js +10 -0
  46. package/dist/tui/StartApp.js +175 -0
  47. package/dist/tui/app.js +9 -0
  48. package/dist/tui/render.js +87 -0
  49. package/dist/utils/envFile.js +65 -0
  50. package/dist/utils/index.js +8 -0
  51. package/dist/utils/io.js +186 -0
  52. package/dist/utils/parseState.js +14 -0
  53. package/dist/utils/resolveProject.js +50 -0
  54. package/dist/vendor/shared/demoMode.js +6 -0
  55. package/dist/vendor/shared/events.js +30 -0
  56. package/dist/vendor/shared/githubAuthMode.js +35 -0
  57. package/dist/vendor/shared/index.js +15 -0
  58. package/dist/vendor/shared/labelUtils.js +32 -0
  59. package/dist/vendor/shared/modelDefinitions.js +146 -0
  60. package/dist/vendor/shared/reviewPrompt.js +18 -0
  61. package/dist/vendor/shared/usageTypes.js +13 -0
  62. package/dist/vendor/shared/userWhitelist.js +30 -0
  63. package/dist/vendor/shared/validateRelayUrl.js +21 -0
  64. package/package.json +31 -0
@@ -0,0 +1,552 @@
1
+ /**
2
+ * Plan Management Commands
3
+ *
4
+ * CLI commands for managing plans using the ProPR backend.
5
+ * Provides the `plan` command group with `list`, `create`, `get`, `delete`, and `abort` subcommands.
6
+ */
7
+ import { Command } from "commander";
8
+ import { listPlans, createPlan, getPlan, deletePlan, abortPlan, generatePlan, finalizePlan, listPlanIssues, } from "../api/index.js";
9
+ import { createConfigManager } from "../config/index.js";
10
+ import { resolveProject, ProjectResolutionError, printOutput } from "../utils/index.js";
11
+ /**
12
+ * Formats a plan status for display with color hints.
13
+ */
14
+ function formatStatus(status) {
15
+ const statusMap = {
16
+ draft: "Draft",
17
+ review: "Review",
18
+ generating: "Generating",
19
+ refining: "Refining",
20
+ executed: "Executed",
21
+ approved: "Approved",
22
+ merged: "Merged",
23
+ pr_created: "PR Created",
24
+ failed: "Failed",
25
+ };
26
+ return statusMap[status] || status;
27
+ }
28
+ /**
29
+ * Formats a date string for display.
30
+ */
31
+ function formatDate(dateStr) {
32
+ const date = new Date(dateStr);
33
+ return date.toLocaleString();
34
+ }
35
+ /**
36
+ * Displays detailed plan information.
37
+ */
38
+ function displayPlanDetails(plan) {
39
+ console.log("");
40
+ console.log("=".repeat(60));
41
+ console.log("Plan Details");
42
+ console.log("=".repeat(60));
43
+ console.log("");
44
+ console.log(`ID: ${plan.draft_id}`);
45
+ console.log(`Name: ${plan.name || plan.task_title || "(Untitled)"}`);
46
+ console.log(`Repository: ${plan.repository}`);
47
+ console.log(`Status: ${formatStatus(plan.status)}`);
48
+ console.log(`Created: ${formatDate(plan.created_at)}`);
49
+ console.log(`Updated: ${formatDate(plan.updated_at)}`);
50
+ if (plan.initial_prompt) {
51
+ console.log("");
52
+ console.log("Initial Prompt:");
53
+ console.log("-".repeat(40));
54
+ console.log(plan.initial_prompt);
55
+ }
56
+ // Display plan items (tasks/issues)
57
+ if (plan.plan_json && Array.isArray(plan.plan_json) && plan.plan_json.length > 0) {
58
+ console.log("");
59
+ console.log("Plan Items:");
60
+ console.log("-".repeat(40));
61
+ for (let i = 0; i < plan.plan_json.length; i++) {
62
+ const item = plan.plan_json[i];
63
+ const title = item.title || item.name || `Item ${i + 1}`;
64
+ const description = item.description || "";
65
+ console.log(`${i + 1}. ${title}`);
66
+ if (description) {
67
+ const truncated = description.toString().length > 100
68
+ ? description.toString().substring(0, 100) + "..."
69
+ : description;
70
+ console.log(` ${truncated}`);
71
+ }
72
+ }
73
+ }
74
+ // Display attachments if any
75
+ if (plan.attachments && plan.attachments.length > 0) {
76
+ console.log("");
77
+ console.log("Attachments:");
78
+ console.log("-".repeat(40));
79
+ for (const attachment of plan.attachments) {
80
+ console.log(`- ${attachment.filename} (${attachment.contentType}, ${attachment.size} bytes)`);
81
+ }
82
+ }
83
+ // Display chat history count if any
84
+ if (plan.chat_history && plan.chat_history.length > 0) {
85
+ console.log("");
86
+ console.log(`Chat History: ${plan.chat_history.length} message(s)`);
87
+ }
88
+ console.log("");
89
+ console.log("=".repeat(60));
90
+ }
91
+ /**
92
+ * Prompts the user for confirmation.
93
+ */
94
+ async function confirm(message) {
95
+ const readline = await import("readline");
96
+ const rl = readline.createInterface({
97
+ input: process.stdin,
98
+ output: process.stdout,
99
+ });
100
+ return new Promise((resolve) => {
101
+ rl.question(`${message} (y/N): `, (answer) => {
102
+ rl.close();
103
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
104
+ });
105
+ });
106
+ }
107
+ /**
108
+ * Creates the `plan` command group.
109
+ */
110
+ export function createPlanCommand() {
111
+ const plan = new Command("plan")
112
+ .description("Manage implementation plans")
113
+ .addHelpText("after", `
114
+ Examples:
115
+ $ propr plan list # List all plans
116
+ $ propr plan create "Add dark mode" -w # Create and wait for generation
117
+ $ propr plan get abc123 # View plan details
118
+ $ propr plan generate abc123 --wait # Trigger generation for existing draft
119
+ $ propr plan finalize abc123 # Create GitHub issues from plan
120
+ $ propr plan issues abc123 # List plan issues
121
+ $ propr plan delete abc123 # Delete a plan
122
+ $ propr plan abort abc123 # Abort generation
123
+ `);
124
+ // plan list
125
+ plan
126
+ .command("list")
127
+ .description("List all implementation plans for a project")
128
+ .option("-p, --project <project>", "Target project (owner/repo)")
129
+ .option("-j, --json", "Output as JSON for programmatic use")
130
+ .addHelpText("after", `
131
+ Examples:
132
+ $ propr plan list # Use default project
133
+ $ propr plan list -p myorg/myrepo # Specify project
134
+ $ propr plan list --json # JSON output
135
+ `)
136
+ .action(async (options) => {
137
+ try {
138
+ const configManager = await createConfigManager();
139
+ const project = resolveProject(options, configManager);
140
+ const result = await listPlans(project);
141
+ if (options.json) {
142
+ console.log(JSON.stringify(result, null, 2));
143
+ return;
144
+ }
145
+ if (result.drafts.length === 0) {
146
+ console.log(`No plans found for project: ${project}`);
147
+ console.log("");
148
+ console.log("To create a new plan, use:");
149
+ console.log(" propr plan create \"<prompt>\"");
150
+ return;
151
+ }
152
+ console.log(`Plans for ${project}:`);
153
+ console.log("");
154
+ const idWidth = Math.max("ID".length, ...result.drafts.map((p) => p.draft_id.length));
155
+ const nameWidth = Math.max("Name".length, ...result.drafts.map((p) => p.name.length));
156
+ const statusWidth = Math.max("Status".length, ...result.drafts.map((p) => p.status.length));
157
+ const header = `${"ID".padEnd(idWidth)} ${"Name".padEnd(nameWidth)} ${"Status".padEnd(statusWidth)}`;
158
+ console.log(header);
159
+ console.log("-".repeat(header.length));
160
+ for (const p of result.drafts) {
161
+ console.log(`${p.draft_id.padEnd(idWidth)} ${p.name.padEnd(nameWidth)} ${p.status.padEnd(statusWidth)}`);
162
+ }
163
+ console.log("");
164
+ console.log(`Total: ${result.total} plan(s)`);
165
+ if (result.hasMore) {
166
+ console.log(`Showing page ${result.page} of results. More plans available.`);
167
+ }
168
+ }
169
+ catch (error) {
170
+ if (error instanceof ProjectResolutionError) {
171
+ console.error(`Error: ${error.message}`);
172
+ process.exit(1);
173
+ }
174
+ console.error(`Error fetching plans: ${error.message}`);
175
+ process.exit(1);
176
+ }
177
+ });
178
+ // plan create
179
+ plan
180
+ .command("create <prompt>")
181
+ .description("Create a new implementation plan from a natural language prompt")
182
+ .option("-p, --project <project>", "Target project (owner/repo)")
183
+ .option("-b, --branch <branch>", "Target branch (default: main)", "main")
184
+ .option("-w, --wait", "Wait for plan generation to complete")
185
+ .addHelpText("after", `
186
+ Argument:
187
+ prompt Natural language description of what to implement
188
+
189
+ Examples:
190
+ $ propr plan create "Add user authentication with JWT"
191
+ $ propr plan create "Fix the login page styling" --wait
192
+ $ propr plan create "Add dark mode" -b develop -p myorg/myrepo --wait
193
+ `)
194
+ .action(async (prompt, options) => {
195
+ try {
196
+ const configManager = await createConfigManager();
197
+ const project = resolveProject(options, configManager);
198
+ console.log(`Creating plan for ${project}...`);
199
+ const planResult = await createPlan(project, prompt, {
200
+ contextConfig: {
201
+ branch: options.branch,
202
+ },
203
+ });
204
+ console.log(`Plan created with ID: ${planResult.draft_id}`);
205
+ console.log(`Status: ${planResult.status}`);
206
+ // Trigger generation
207
+ console.log("Triggering plan generation...");
208
+ await generatePlan(planResult.draft_id);
209
+ if (!options.wait) {
210
+ console.log("");
211
+ console.log(`Generation started. Use 'propr plan get ${planResult.draft_id}' to check status.`);
212
+ return;
213
+ }
214
+ console.log("");
215
+ console.log("Waiting for plan generation to complete...");
216
+ // "review" is the terminal success state after generation completes.
217
+ // "draft" is only terminal if we already saw generation activity.
218
+ const doneStatuses = ["review", "approved", "failed", "executed", "merged", "pr_created"];
219
+ const pollIntervalMs = 3000;
220
+ const maxWaitMs = 600000;
221
+ const startTime = Date.now();
222
+ let currentPlan = planResult;
223
+ let lastStatus = planResult.status;
224
+ let sawGenerating = false;
225
+ while (Date.now() - startTime < maxWaitMs) {
226
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
227
+ currentPlan = await getPlan(planResult.draft_id);
228
+ if (currentPlan.status !== lastStatus) {
229
+ console.log(`Status: ${formatStatus(currentPlan.status)}`);
230
+ lastStatus = currentPlan.status;
231
+ }
232
+ if (currentPlan.status === "generating" || currentPlan.status === "refining") {
233
+ sawGenerating = true;
234
+ }
235
+ if (doneStatuses.includes(currentPlan.status)) {
236
+ break;
237
+ }
238
+ // "draft" after generation means it returned to draft (generation done)
239
+ if (currentPlan.status === "draft" && sawGenerating) {
240
+ break;
241
+ }
242
+ }
243
+ console.log("");
244
+ const isDone = doneStatuses.includes(currentPlan.status) || (currentPlan.status === "draft" && sawGenerating);
245
+ if (currentPlan.status === "failed") {
246
+ console.error("Plan generation failed.");
247
+ process.exit(1);
248
+ }
249
+ else if (isDone) {
250
+ console.log(`Plan generation completed.`);
251
+ console.log(`Final status: ${formatStatus(currentPlan.status)}`);
252
+ if (currentPlan.name) {
253
+ console.log(`Name: ${currentPlan.name}`);
254
+ }
255
+ }
256
+ else {
257
+ console.log(`Timeout: Plan is still ${formatStatus(currentPlan.status)} after ${Math.round((Date.now() - startTime) / 1000)} seconds.`);
258
+ console.log(`Plan ID: ${currentPlan.draft_id}`);
259
+ console.log(`Use 'propr plan get ${currentPlan.draft_id}' to check status.`);
260
+ }
261
+ }
262
+ catch (error) {
263
+ if (error instanceof ProjectResolutionError) {
264
+ console.error(`Error: ${error.message}`);
265
+ process.exit(1);
266
+ }
267
+ console.error(`Error creating plan: ${error.message}`);
268
+ process.exit(1);
269
+ }
270
+ });
271
+ // plan get
272
+ plan
273
+ .command("get <draft-id>")
274
+ .description("Get detailed information about a specific plan")
275
+ .option("-j, --json", "Output as JSON for programmatic use")
276
+ .addHelpText("after", `
277
+ Argument:
278
+ draft-id The unique identifier of the plan
279
+
280
+ Examples:
281
+ $ propr plan get abc123-def456
282
+ $ propr plan get abc123-def456 --json
283
+ `)
284
+ .action(async (draftId, options) => {
285
+ try {
286
+ const fetchedPlan = await getPlan(draftId);
287
+ if (printOutput(fetchedPlan, options.json ?? false)) {
288
+ return;
289
+ }
290
+ console.log(`Fetching plan ${draftId}...`);
291
+ displayPlanDetails(fetchedPlan);
292
+ }
293
+ catch (error) {
294
+ const errorMessage = error.message;
295
+ if (errorMessage.includes("404") || errorMessage.includes("not found")) {
296
+ console.error(`Error: Plan not found: ${draftId}`);
297
+ }
298
+ else if (errorMessage.includes("401") || errorMessage.includes("unauthorized")) {
299
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
300
+ }
301
+ else if (errorMessage.includes("403") || errorMessage.includes("forbidden")) {
302
+ console.error("Error: Access denied. You do not have permission to view this plan.");
303
+ }
304
+ else {
305
+ console.error(`Error fetching plan: ${errorMessage}`);
306
+ }
307
+ process.exit(1);
308
+ }
309
+ });
310
+ // plan delete
311
+ plan
312
+ .command("delete <draft-id>")
313
+ .description("Delete a plan from the system permanently")
314
+ .option("-f, --force", "Skip confirmation prompt")
315
+ .addHelpText("after", `
316
+ Argument:
317
+ draft-id The unique identifier of the plan to delete
318
+
319
+ Examples:
320
+ $ propr plan delete abc123-def456 # With confirmation
321
+ $ propr plan delete abc123-def456 --force # Skip confirmation
322
+ `)
323
+ .action(async (draftId, options) => {
324
+ try {
325
+ let planName = draftId;
326
+ try {
327
+ const fetchedPlan = await getPlan(draftId);
328
+ planName = fetchedPlan.name || fetchedPlan.task_title || draftId;
329
+ console.log(`Plan: ${planName}`);
330
+ console.log(`Repository: ${fetchedPlan.repository}`);
331
+ console.log(`Status: ${formatStatus(fetchedPlan.status)}`);
332
+ console.log("");
333
+ }
334
+ catch {
335
+ console.log(`Plan ID: ${draftId}`);
336
+ console.log("");
337
+ }
338
+ if (!options.force) {
339
+ const confirmed = await confirm(`Are you sure you want to delete this plan?`);
340
+ if (!confirmed) {
341
+ console.log("Deletion cancelled.");
342
+ return;
343
+ }
344
+ }
345
+ console.log(`Deleting plan ${draftId}...`);
346
+ await deletePlan(draftId);
347
+ console.log("Plan deleted successfully.");
348
+ }
349
+ catch (error) {
350
+ const errorMessage = error.message;
351
+ if (errorMessage.includes("404") || errorMessage.includes("not found")) {
352
+ console.error(`Error: Plan not found: ${draftId}`);
353
+ }
354
+ else if (errorMessage.includes("401") || errorMessage.includes("unauthorized")) {
355
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
356
+ }
357
+ else if (errorMessage.includes("403") || errorMessage.includes("forbidden")) {
358
+ console.error("Error: Access denied. You do not have permission to delete this plan.");
359
+ }
360
+ else {
361
+ console.error(`Error deleting plan: ${errorMessage}`);
362
+ }
363
+ process.exit(1);
364
+ }
365
+ });
366
+ // plan abort
367
+ plan
368
+ .command("abort <draft-id>")
369
+ .description("Abort ongoing LLM generation for a plan (only works for generating/refining plans)")
370
+ .addHelpText("after", `
371
+ Argument:
372
+ draft-id The unique identifier of the plan with active generation
373
+
374
+ Note:
375
+ This command only works for plans in 'generating' or 'refining' status.
376
+
377
+ Example:
378
+ $ propr plan abort abc123-def456
379
+ `)
380
+ .action(async (draftId) => {
381
+ try {
382
+ try {
383
+ const fetchedPlan = await getPlan(draftId);
384
+ if (fetchedPlan.status !== "generating" && fetchedPlan.status !== "refining") {
385
+ console.log(`Plan status: ${formatStatus(fetchedPlan.status)}`);
386
+ console.log("");
387
+ console.log("Warning: This plan is not currently generating or refining.");
388
+ console.log("The abort command is only effective for plans in 'generating' or 'refining' status.");
389
+ console.log("");
390
+ }
391
+ }
392
+ catch {
393
+ // If we can't fetch the plan, continue with abort attempt
394
+ }
395
+ console.log(`Aborting generation for plan ${draftId}...`);
396
+ const result = await abortPlan(draftId);
397
+ if (result.success) {
398
+ console.log(result.message || "Generation aborted successfully.");
399
+ }
400
+ else {
401
+ console.error(`Failed to abort: ${result.message}`);
402
+ process.exit(1);
403
+ }
404
+ }
405
+ catch (error) {
406
+ const errorMessage = error.message;
407
+ if (errorMessage.includes("404") || errorMessage.includes("not found")) {
408
+ console.error(`Error: Plan not found: ${draftId}`);
409
+ }
410
+ else if (errorMessage.includes("400")) {
411
+ console.error("Error: Plan is not in a state that can be aborted (must be 'generating' or 'refining').");
412
+ }
413
+ else if (errorMessage.includes("401") || errorMessage.includes("unauthorized")) {
414
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
415
+ }
416
+ else if (errorMessage.includes("403") || errorMessage.includes("forbidden")) {
417
+ console.error("Error: Access denied. You do not have permission to abort this plan.");
418
+ }
419
+ else {
420
+ console.error(`Error aborting plan: ${errorMessage}`);
421
+ }
422
+ process.exit(1);
423
+ }
424
+ });
425
+ // plan generate
426
+ plan
427
+ .command("generate <draft-id>")
428
+ .description("Trigger plan generation for an existing draft")
429
+ .option("-w, --wait", "Wait for generation to complete")
430
+ .addHelpText("after", `
431
+ Argument:
432
+ draft-id The unique identifier of the plan draft
433
+
434
+ Examples:
435
+ $ propr plan generate abc123-def456
436
+ $ propr plan generate abc123-def456 --wait
437
+ `)
438
+ .action(async (draftId, options) => {
439
+ try {
440
+ console.log(`Triggering generation for plan ${draftId}...`);
441
+ await generatePlan(draftId);
442
+ console.log("Generation started.");
443
+ if (!options.wait) {
444
+ console.log(`Use 'propr plan get ${draftId}' to check status.`);
445
+ return;
446
+ }
447
+ console.log("");
448
+ console.log("Waiting for generation to complete...");
449
+ const terminalStatuses = ["draft", "review", "approved", "failed", "executed", "merged", "pr_created"];
450
+ const pollIntervalMs = 3000;
451
+ const maxWaitMs = 600000;
452
+ const startTime = Date.now();
453
+ let lastStatus = "generating";
454
+ while (Date.now() - startTime < maxWaitMs) {
455
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
456
+ const currentPlan = await getPlan(draftId);
457
+ if (currentPlan.status !== lastStatus) {
458
+ console.log(`Status: ${formatStatus(currentPlan.status)}`);
459
+ lastStatus = currentPlan.status;
460
+ }
461
+ if (terminalStatuses.includes(currentPlan.status)) {
462
+ if (currentPlan.status === "failed") {
463
+ console.error("Plan generation failed.");
464
+ process.exit(1);
465
+ }
466
+ console.log("Generation completed.");
467
+ return;
468
+ }
469
+ }
470
+ console.log(`Timeout after ${Math.round((Date.now() - startTime) / 1000)} seconds.`);
471
+ }
472
+ catch (error) {
473
+ console.error(`Error generating plan: ${error.message}`);
474
+ process.exit(1);
475
+ }
476
+ });
477
+ // plan finalize
478
+ plan
479
+ .command("finalize <draft-id>")
480
+ .description("Finalize a plan by creating GitHub issues from its items")
481
+ .option("-j, --json", "Output as JSON for programmatic use")
482
+ .addHelpText("after", `
483
+ Argument:
484
+ draft-id The unique identifier of the plan to finalize
485
+
486
+ Example:
487
+ $ propr plan finalize abc123-def456
488
+ `)
489
+ .action(async (draftId, options) => {
490
+ try {
491
+ console.log(`Finalizing plan ${draftId}...`);
492
+ const result = await finalizePlan(draftId);
493
+ if (printOutput(result, options.json ?? false)) {
494
+ return;
495
+ }
496
+ if (result.alreadyExecuted) {
497
+ console.log("Plan was already finalized.");
498
+ }
499
+ else {
500
+ console.log(`Created ${result.issuesCreated} GitHub issue(s).`);
501
+ }
502
+ }
503
+ catch (error) {
504
+ console.error(`Error finalizing plan: ${error.message}`);
505
+ process.exit(1);
506
+ }
507
+ });
508
+ // plan issues
509
+ plan
510
+ .command("issues <draft-id>")
511
+ .description("List GitHub issues created from a plan")
512
+ .option("-j, --json", "Output as JSON for programmatic use")
513
+ .addHelpText("after", `
514
+ Argument:
515
+ draft-id The unique identifier of the plan
516
+
517
+ Example:
518
+ $ propr plan issues abc123-def456
519
+ $ propr plan issues abc123-def456 --json
520
+ `)
521
+ .action(async (draftId, options) => {
522
+ try {
523
+ const issues = await listPlanIssues(draftId);
524
+ if (printOutput(issues, options.json ?? false)) {
525
+ return;
526
+ }
527
+ if (issues.length === 0) {
528
+ console.log("No issues found for this plan.");
529
+ console.log("Use 'propr plan finalize' to create issues from plan items.");
530
+ return;
531
+ }
532
+ console.log(`Issues for plan ${draftId}:`);
533
+ console.log("");
534
+ const numWidth = Math.max("Issue".length, ...issues.map((i) => String(i.issue_number).length));
535
+ const statusWidth = Math.max("Status".length, ...issues.map((i) => i.status.length));
536
+ const modelWidth = Math.max("Model".length, ...issues.map((i) => (i.model_name || "-").length));
537
+ const header = `${"Issue".padEnd(numWidth)} ${"Status".padEnd(statusWidth)} ${"Model".padEnd(modelWidth)} Task ID`;
538
+ console.log(header);
539
+ console.log("-".repeat(header.length + 20));
540
+ for (const issue of issues) {
541
+ console.log(`#${String(issue.issue_number).padEnd(numWidth - 1)} ${issue.status.padEnd(statusWidth)} ${(issue.model_name || "-").padEnd(modelWidth)} ${issue.task_id || "-"}`);
542
+ }
543
+ console.log("");
544
+ console.log(`Total: ${issues.length} issue(s)`);
545
+ }
546
+ catch (error) {
547
+ console.error(`Error listing plan issues: ${error.message}`);
548
+ process.exit(1);
549
+ }
550
+ });
551
+ return plan;
552
+ }