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.
- package/README.md +549 -0
- package/dist/api/agentTank.js +27 -0
- package/dist/api/agents.js +201 -0
- package/dist/api/client.js +284 -0
- package/dist/api/errors.js +145 -0
- package/dist/api/implement.js +147 -0
- package/dist/api/index.js +26 -0
- package/dist/api/logs.js +59 -0
- package/dist/api/plans.js +160 -0
- package/dist/api/relay.js +73 -0
- package/dist/api/repos.js +243 -0
- package/dist/api/settings.js +219 -0
- package/dist/api/system.js +53 -0
- package/dist/api/tasks.js +140 -0
- package/dist/api/todos.js +77 -0
- package/dist/api/types.js +6 -0
- package/dist/assets/.env.example +183 -0
- package/dist/assets/env.example.txt +198 -0
- package/dist/commands/agentCommands.js +405 -0
- package/dist/commands/checkCommands.js +384 -0
- package/dist/commands/implementCommands.js +178 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/initCommands.js +167 -0
- package/dist/commands/initStack.js +193 -0
- package/dist/commands/logCommands.js +170 -0
- package/dist/commands/planCommands.js +552 -0
- package/dist/commands/relayCommands.js +149 -0
- package/dist/commands/repoCommands.js +526 -0
- package/dist/commands/settingCommands.js +237 -0
- package/dist/commands/stackCommands.js +86 -0
- package/dist/commands/startCommand.js +36 -0
- package/dist/commands/systemCommands.js +221 -0
- package/dist/commands/tankCommands.js +55 -0
- package/dist/commands/taskCommands.js +554 -0
- package/dist/commands/todoCommands.js +620 -0
- package/dist/commands/uiDocsCommands.js +69 -0
- package/dist/config/ConfigManager.js +360 -0
- package/dist/config/index.js +8 -0
- package/dist/config/types.js +16 -0
- package/dist/index.js +276 -0
- package/dist/orchestrator/format.js +31 -0
- package/dist/orchestrator/index.js +102 -0
- package/dist/orchestrator/manifest.json +16 -0
- package/dist/orchestrator/orchestrator.mjs +798 -0
- package/dist/orchestrator/types.js +10 -0
- package/dist/tui/StartApp.js +175 -0
- package/dist/tui/app.js +9 -0
- package/dist/tui/render.js +87 -0
- package/dist/utils/envFile.js +65 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/io.js +186 -0
- package/dist/utils/parseState.js +14 -0
- package/dist/utils/resolveProject.js +50 -0
- package/dist/vendor/shared/demoMode.js +6 -0
- package/dist/vendor/shared/events.js +30 -0
- package/dist/vendor/shared/githubAuthMode.js +35 -0
- package/dist/vendor/shared/index.js +15 -0
- package/dist/vendor/shared/labelUtils.js +32 -0
- package/dist/vendor/shared/modelDefinitions.js +146 -0
- package/dist/vendor/shared/reviewPrompt.js +18 -0
- package/dist/vendor/shared/usageTypes.js +13 -0
- package/dist/vendor/shared/userWhitelist.js +30 -0
- package/dist/vendor/shared/validateRelayUrl.js +21 -0
- 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
|
+
}
|