@simonfestl/husky-cli 1.34.0 → 1.36.0
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/plan.js +95 -0
- package/dist/commands/supervisor.js +4 -19
- package/package.json +1 -1
package/dist/commands/plan.js
CHANGED
|
@@ -215,5 +215,100 @@ program
|
|
|
215
215
|
process.exit(1);
|
|
216
216
|
}
|
|
217
217
|
});
|
|
218
|
+
/**
|
|
219
|
+
* Wait for plan approval (worker command)
|
|
220
|
+
*
|
|
221
|
+
* Exit codes:
|
|
222
|
+
* 0 = approved
|
|
223
|
+
* 1 = rejected
|
|
224
|
+
* 2 = revision_requested
|
|
225
|
+
* 3 = timeout
|
|
226
|
+
*/
|
|
227
|
+
program
|
|
228
|
+
.command("await")
|
|
229
|
+
.description("Wait for supervisor to approve/reject a plan (worker)")
|
|
230
|
+
.argument("<id>", "Plan ID to wait for")
|
|
231
|
+
.option("--timeout <seconds>", "Timeout in seconds (default: 300)", "300")
|
|
232
|
+
.option("--interval <seconds>", "Poll interval in seconds (default: 5)", "5")
|
|
233
|
+
.option("--json", "Output result as JSON")
|
|
234
|
+
.action(async (id, options) => {
|
|
235
|
+
const timeout = parseInt(options.timeout, 10) * 1000;
|
|
236
|
+
const interval = parseInt(options.interval, 10) * 1000;
|
|
237
|
+
const startTime = Date.now();
|
|
238
|
+
const api = getApiClient();
|
|
239
|
+
if (!options.json) {
|
|
240
|
+
console.log(chalk.yellow(`⏳ Waiting for plan ${id} approval...`));
|
|
241
|
+
console.log(chalk.dim(` Timeout: ${options.timeout}s, Poll interval: ${options.interval}s`));
|
|
242
|
+
}
|
|
243
|
+
while (true) {
|
|
244
|
+
try {
|
|
245
|
+
const result = await api.get(`/api/plans/${id}`);
|
|
246
|
+
const { plan } = result;
|
|
247
|
+
if (plan.status === "approved") {
|
|
248
|
+
if (options.json) {
|
|
249
|
+
console.log(JSON.stringify({ status: "approved", notes: plan.supervisorNotes }));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.log(chalk.green("\n✓ Plan approved!"));
|
|
253
|
+
if (plan.supervisorNotes) {
|
|
254
|
+
console.log(chalk.dim(` Supervisor notes: ${plan.supervisorNotes}`));
|
|
255
|
+
}
|
|
256
|
+
console.log(chalk.dim(" You may proceed with implementation."));
|
|
257
|
+
}
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
if (plan.status === "rejected") {
|
|
261
|
+
if (options.json) {
|
|
262
|
+
console.log(JSON.stringify({ status: "rejected", notes: plan.supervisorNotes }));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
console.log(chalk.red("\n✗ Plan rejected"));
|
|
266
|
+
if (plan.supervisorNotes) {
|
|
267
|
+
console.log(chalk.dim(` Reason: ${plan.supervisorNotes}`));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
if (plan.status === "revision_requested") {
|
|
273
|
+
if (options.json) {
|
|
274
|
+
console.log(JSON.stringify({ status: "revision_requested", notes: plan.supervisorNotes }));
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
console.log(chalk.blue("\n🔄 Revision requested"));
|
|
278
|
+
if (plan.supervisorNotes) {
|
|
279
|
+
console.log(chalk.dim(` Feedback: ${plan.supervisorNotes}`));
|
|
280
|
+
}
|
|
281
|
+
console.log(chalk.dim(" Please update and resubmit the plan."));
|
|
282
|
+
}
|
|
283
|
+
process.exit(2);
|
|
284
|
+
}
|
|
285
|
+
// Still pending - check timeout
|
|
286
|
+
if (Date.now() - startTime >= timeout) {
|
|
287
|
+
if (options.json) {
|
|
288
|
+
console.log(JSON.stringify({ status: "timeout", elapsed: Math.round((Date.now() - startTime) / 1000) }));
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
console.log(chalk.yellow("\n⏱️ Timeout reached"));
|
|
292
|
+
console.log(chalk.dim(` Plan is still pending after ${options.timeout}s`));
|
|
293
|
+
}
|
|
294
|
+
process.exit(3);
|
|
295
|
+
}
|
|
296
|
+
// Wait before next poll
|
|
297
|
+
if (!options.json) {
|
|
298
|
+
process.stdout.write(".");
|
|
299
|
+
}
|
|
300
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
if (options.json) {
|
|
304
|
+
console.log(JSON.stringify({ status: "error", message: error instanceof Error ? error.message : String(error) }));
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
console.error(chalk.red("\n✗ Error checking plan status:"), error instanceof Error ? error.message : error);
|
|
308
|
+
}
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
218
313
|
export const planCommand = program;
|
|
219
314
|
export default program;
|
|
@@ -229,7 +229,7 @@ supervisorCommand
|
|
|
229
229
|
const tasksRes = await fetch(`${config.apiUrl}/api/tasks?status=backlog&assignee=llm&limit=10`, {
|
|
230
230
|
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
231
231
|
});
|
|
232
|
-
// Get VM status
|
|
232
|
+
// Get VM status (read-only)
|
|
233
233
|
const vmCommand = "gcloud compute instances list --filter='name~husky-' --format='json(name,status,zone,machineType)'";
|
|
234
234
|
const { stdout: vmOutput } = await execAsync(vmCommand);
|
|
235
235
|
const vms = JSON.parse(vmOutput || "[]");
|
|
@@ -283,20 +283,14 @@ supervisorCommand
|
|
|
283
283
|
console.log(` Online: ${onlineAgents.length}, Busy: ${busyAgents.length}, Offline: ${offlineAgents.length}`);
|
|
284
284
|
// Next actions
|
|
285
285
|
console.log("\n🎯 Suggested Actions:");
|
|
286
|
-
if (backlogTasks.length > 0 && runningWorkers.length === 0) {
|
|
287
|
-
console.log(" • Start worker VM(s) to handle task backlog");
|
|
288
|
-
}
|
|
289
|
-
if (terminatedWorkers.length > 0 && backlogTasks.length > 0) {
|
|
290
|
-
console.log(" • Resume terminated worker VMs");
|
|
291
|
-
}
|
|
292
286
|
if (backlogTasks.length > 0) {
|
|
293
|
-
console.log(" •
|
|
287
|
+
console.log(" • Create tasks only; autoscaling will provision workers");
|
|
294
288
|
}
|
|
295
289
|
if (offlineAgents.length > 0) {
|
|
296
290
|
console.log(" • Check on offline agents");
|
|
297
291
|
}
|
|
298
292
|
if (backlogTasks.length === 0 && runningWorkers.length > 0) {
|
|
299
|
-
console.log(" •
|
|
293
|
+
console.log(" • Autoscaling will handle idle workers");
|
|
300
294
|
}
|
|
301
295
|
console.log("\nQuick commands:");
|
|
302
296
|
console.log(" husky supervisor start - Start supervisor");
|
|
@@ -335,16 +329,7 @@ supervisorCommand
|
|
|
335
329
|
const tasks = tasksData.tasks || [];
|
|
336
330
|
if (tasks.length > 0) {
|
|
337
331
|
console.log(` Found ${tasks.length} tasks in backlog`);
|
|
338
|
-
|
|
339
|
-
const { stdout: vmOutput } = await execAsync("gcloud compute instances list --filter='name~husky-worker' --format='json(name,status,zone)'");
|
|
340
|
-
const vms = JSON.parse(vmOutput || "[]");
|
|
341
|
-
const runningWorkers = vms.filter((vm) => vm.name.includes("worker") && vm.status === "RUNNING");
|
|
342
|
-
if (runningWorkers.length === 0) {
|
|
343
|
-
console.log(" No worker VMs running, starting one...");
|
|
344
|
-
// This would need to be implemented based on your GCP setup
|
|
345
|
-
console.log(" TODO: Implement VM startup logic");
|
|
346
|
-
}
|
|
347
|
-
// Step 3: Assign tasks to workers
|
|
332
|
+
console.log(" Autoscaling will provision workers via API");
|
|
348
333
|
tasks.forEach((task) => {
|
|
349
334
|
console.log(` Task ${task.id}: ${task.title}`);
|
|
350
335
|
});
|