@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.
@@ -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(" • Assign tasks to available workers");
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(" • Suspend idle worker VMs to save costs");
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
- // Step 2: Check available worker VMs
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "1.34.0",
3
+ "version": "1.36.0",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {