@simonfestl/husky-cli 1.26.0 → 1.27.1
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/commands/e2e.js +1 -1
- package/dist/commands/plan.d.ts +14 -0
- package/dist/commands/plan.js +219 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
package/dist/commands/e2e.js
CHANGED
|
@@ -16,7 +16,7 @@ import { getConfig } from "./config.js";
|
|
|
16
16
|
export const e2eCommand = new Command("e2e")
|
|
17
17
|
.description("E2E testing and browser automation (E2E Agent)");
|
|
18
18
|
// GCS bucket for E2E artifacts
|
|
19
|
-
const GCS_BUCKET = "husky-
|
|
19
|
+
const GCS_BUCKET = "husky-e2e-artifacts";
|
|
20
20
|
// Helper: Ensure API is configured
|
|
21
21
|
function ensureConfig() {
|
|
22
22
|
const config = getConfig();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for the plan approval workflow:
|
|
5
|
+
* - husky plan submit - Submit a plan for supervisor review
|
|
6
|
+
* - husky plan list - List pending plans (supervisor)
|
|
7
|
+
* - husky plan get - Get plan details
|
|
8
|
+
* - husky plan approve - Approve a plan (supervisor)
|
|
9
|
+
* - husky plan reject - Reject a plan (supervisor)
|
|
10
|
+
*/
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
declare const program: Command;
|
|
13
|
+
export declare const planCommand: Command;
|
|
14
|
+
export default program;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for the plan approval workflow:
|
|
5
|
+
* - husky plan submit - Submit a plan for supervisor review
|
|
6
|
+
* - husky plan list - List pending plans (supervisor)
|
|
7
|
+
* - husky plan get - Get plan details
|
|
8
|
+
* - husky plan approve - Approve a plan (supervisor)
|
|
9
|
+
* - husky plan reject - Reject a plan (supervisor)
|
|
10
|
+
*/
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { getApiClient } from "../lib/api-client.js";
|
|
14
|
+
const program = new Command();
|
|
15
|
+
program
|
|
16
|
+
.name("plan")
|
|
17
|
+
.description("Plan approval workflow commands");
|
|
18
|
+
/**
|
|
19
|
+
* Submit a plan for supervisor review
|
|
20
|
+
*/
|
|
21
|
+
program
|
|
22
|
+
.command("submit")
|
|
23
|
+
.description("Submit a plan for supervisor review")
|
|
24
|
+
.requiredOption("--task-id <id>", "Task ID this plan is for")
|
|
25
|
+
.requiredOption("--plan <text>", "The implementation plan")
|
|
26
|
+
.option("--title <title>", "Task title (if not fetching from task)")
|
|
27
|
+
.option("--steps <n>", "Estimated number of steps", parseInt)
|
|
28
|
+
.option("--files <files>", "Comma-separated list of files to modify")
|
|
29
|
+
.option("--questions <questions>", "Questions for supervisor (comma-separated)")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
try {
|
|
32
|
+
const api = getApiClient();
|
|
33
|
+
// First get the task to get title if not provided
|
|
34
|
+
let taskTitle = options.title;
|
|
35
|
+
let taskDescription;
|
|
36
|
+
if (!taskTitle) {
|
|
37
|
+
try {
|
|
38
|
+
const task = await api.get(`/api/tasks/${options.taskId}`);
|
|
39
|
+
taskTitle = task.title;
|
|
40
|
+
taskDescription = task.description;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
taskTitle = `Task ${options.taskId}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const result = await api.post("/api/plans", {
|
|
47
|
+
taskId: options.taskId,
|
|
48
|
+
taskTitle,
|
|
49
|
+
taskDescription,
|
|
50
|
+
plan: options.plan,
|
|
51
|
+
estimatedSteps: options.steps,
|
|
52
|
+
estimatedFiles: options.files ? options.files.split(",").map((f) => f.trim()) : undefined,
|
|
53
|
+
questions: options.questions ? options.questions.split(",").map((q) => q.trim()) : undefined,
|
|
54
|
+
});
|
|
55
|
+
console.log(chalk.green("✓ Plan submitted for supervisor review"));
|
|
56
|
+
console.log(` Plan ID: ${chalk.cyan(result.planId)}`);
|
|
57
|
+
console.log(` Task: ${taskTitle}`);
|
|
58
|
+
console.log(chalk.yellow("\n⏳ Waiting for supervisor approval..."));
|
|
59
|
+
console.log(chalk.dim(" You will be notified when the plan is reviewed."));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error(chalk.red("✗ Failed to submit plan:"), error instanceof Error ? error.message : error);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* List pending plans (supervisor only)
|
|
68
|
+
*/
|
|
69
|
+
program
|
|
70
|
+
.command("list")
|
|
71
|
+
.description("List pending plans awaiting review (supervisor)")
|
|
72
|
+
.action(async () => {
|
|
73
|
+
try {
|
|
74
|
+
const api = getApiClient();
|
|
75
|
+
const result = await api.get("/api/plans");
|
|
76
|
+
const { plans } = result;
|
|
77
|
+
if (plans.length === 0) {
|
|
78
|
+
console.log(chalk.yellow("No pending plans"));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.log(chalk.bold(`\n📋 Pending Plans (${plans.length}):\n`));
|
|
82
|
+
for (const plan of plans) {
|
|
83
|
+
console.log(chalk.cyan(` ${plan.id.substring(0, 8)}`), chalk.white(plan.taskTitle));
|
|
84
|
+
console.log(` Worker: ${plan.workerName || plan.workerId}`);
|
|
85
|
+
console.log(` Preview: ${plan.plan.substring(0, 100)}...`);
|
|
86
|
+
console.log("");
|
|
87
|
+
}
|
|
88
|
+
console.log(chalk.dim("Use: husky plan get <id> to see full plan"));
|
|
89
|
+
console.log(chalk.dim("Use: husky plan approve <id> or husky plan reject <id>"));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(chalk.red("✗ Failed to list plans:"), error instanceof Error ? error.message : error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Get plan details
|
|
98
|
+
*/
|
|
99
|
+
program
|
|
100
|
+
.command("get")
|
|
101
|
+
.description("Get plan details")
|
|
102
|
+
.argument("<id>", "Plan ID")
|
|
103
|
+
.action(async (id) => {
|
|
104
|
+
try {
|
|
105
|
+
const api = getApiClient();
|
|
106
|
+
const result = await api.get(`/api/plans/${id}`);
|
|
107
|
+
const { plan } = result;
|
|
108
|
+
console.log(chalk.bold(`\n📋 Plan: ${plan.taskTitle}\n`));
|
|
109
|
+
console.log(` Plan ID: ${chalk.cyan(plan.id)}`);
|
|
110
|
+
console.log(` Task ID: ${plan.taskId}`);
|
|
111
|
+
console.log(` Worker: ${plan.workerName || plan.workerId}`);
|
|
112
|
+
console.log(` Status: ${plan.status === "pending" ? chalk.yellow(plan.status) : plan.status === "approved" ? chalk.green(plan.status) : chalk.red(plan.status)}`);
|
|
113
|
+
if (plan.taskDescription) {
|
|
114
|
+
console.log(`\n ${chalk.dim("Task Description:")}`);
|
|
115
|
+
console.log(` ${plan.taskDescription}`);
|
|
116
|
+
}
|
|
117
|
+
console.log(`\n ${chalk.bold("Implementation Plan:")}`);
|
|
118
|
+
console.log(` ${plan.plan}`);
|
|
119
|
+
if (plan.estimatedSteps) {
|
|
120
|
+
console.log(`\n Estimated Steps: ${plan.estimatedSteps}`);
|
|
121
|
+
}
|
|
122
|
+
if (plan.estimatedFiles && plan.estimatedFiles.length > 0) {
|
|
123
|
+
console.log(`\n Files to modify:`);
|
|
124
|
+
for (const file of plan.estimatedFiles) {
|
|
125
|
+
console.log(` - ${file}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (plan.questions && plan.questions.length > 0) {
|
|
129
|
+
console.log(chalk.yellow(`\n Questions for Supervisor:`));
|
|
130
|
+
for (const q of plan.questions) {
|
|
131
|
+
console.log(` • ${q}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (plan.supervisorNotes) {
|
|
135
|
+
console.log(chalk.blue(`\n Supervisor Notes:`));
|
|
136
|
+
console.log(` ${plan.supervisorNotes}`);
|
|
137
|
+
}
|
|
138
|
+
if (plan.status === "pending") {
|
|
139
|
+
console.log(chalk.dim("\nCommands:"));
|
|
140
|
+
console.log(chalk.dim(` husky plan approve ${plan.id} --notes "LGTM"`));
|
|
141
|
+
console.log(chalk.dim(` husky plan reject ${plan.id} --notes "Needs more detail"`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error(chalk.red("✗ Plan not found:"), error instanceof Error ? error.message : error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* Approve a plan (supervisor only)
|
|
151
|
+
*/
|
|
152
|
+
program
|
|
153
|
+
.command("approve")
|
|
154
|
+
.description("Approve a plan (supervisor)")
|
|
155
|
+
.argument("<id>", "Plan ID")
|
|
156
|
+
.option("--notes <notes>", "Approval notes for the worker")
|
|
157
|
+
.action(async (id, options) => {
|
|
158
|
+
try {
|
|
159
|
+
const api = getApiClient();
|
|
160
|
+
await api.patch(`/api/plans/${id}`, {
|
|
161
|
+
status: "approved",
|
|
162
|
+
supervisorNotes: options.notes,
|
|
163
|
+
});
|
|
164
|
+
console.log(chalk.green("✓ Plan approved"));
|
|
165
|
+
console.log(chalk.dim(" Worker has been notified and can proceed with implementation."));
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error(chalk.red("✗ Failed to approve plan:"), error instanceof Error ? error.message : error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* Reject a plan (supervisor only)
|
|
174
|
+
*/
|
|
175
|
+
program
|
|
176
|
+
.command("reject")
|
|
177
|
+
.description("Reject a plan (supervisor)")
|
|
178
|
+
.argument("<id>", "Plan ID")
|
|
179
|
+
.requiredOption("--notes <notes>", "Rejection reason (required)")
|
|
180
|
+
.action(async (id, options) => {
|
|
181
|
+
try {
|
|
182
|
+
const api = getApiClient();
|
|
183
|
+
await api.patch(`/api/plans/${id}`, {
|
|
184
|
+
status: "rejected",
|
|
185
|
+
supervisorNotes: options.notes,
|
|
186
|
+
});
|
|
187
|
+
console.log(chalk.yellow("✓ Plan rejected"));
|
|
188
|
+
console.log(chalk.dim(" Worker has been notified to revise the plan."));
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error(chalk.red("✗ Failed to reject plan:"), error instanceof Error ? error.message : error);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
/**
|
|
196
|
+
* Request revision on a plan (supervisor only)
|
|
197
|
+
*/
|
|
198
|
+
program
|
|
199
|
+
.command("revise")
|
|
200
|
+
.description("Request revision on a plan (supervisor)")
|
|
201
|
+
.argument("<id>", "Plan ID")
|
|
202
|
+
.requiredOption("--notes <notes>", "Revision feedback (required)")
|
|
203
|
+
.action(async (id, options) => {
|
|
204
|
+
try {
|
|
205
|
+
const api = getApiClient();
|
|
206
|
+
await api.patch(`/api/plans/${id}`, {
|
|
207
|
+
status: "revision_requested",
|
|
208
|
+
supervisorNotes: options.notes,
|
|
209
|
+
});
|
|
210
|
+
console.log(chalk.blue("✓ Revision requested"));
|
|
211
|
+
console.log(chalk.dim(" Worker has been notified to address the feedback."));
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error(chalk.red("✗ Failed to request revision:"), error instanceof Error ? error.message : error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
export const planCommand = program;
|
|
219
|
+
export default program;
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import { youtubeCommand } from "./commands/youtube.js";
|
|
|
36
36
|
import { imageCommand } from "./commands/image.js";
|
|
37
37
|
import { authCommand } from "./commands/auth.js";
|
|
38
38
|
import { businessCommand } from "./commands/business.js";
|
|
39
|
+
import { planCommand } from "./commands/plan.js";
|
|
39
40
|
// Read version from package.json
|
|
40
41
|
const require = createRequire(import.meta.url);
|
|
41
42
|
const packageJson = require("../package.json");
|
|
@@ -79,6 +80,7 @@ program.addCommand(youtubeCommand);
|
|
|
79
80
|
program.addCommand(imageCommand);
|
|
80
81
|
program.addCommand(authCommand);
|
|
81
82
|
program.addCommand(businessCommand);
|
|
83
|
+
program.addCommand(planCommand);
|
|
82
84
|
// Handle --llm flag specially
|
|
83
85
|
if (process.argv.includes("--llm")) {
|
|
84
86
|
printLLMContext();
|