@simonfestl/husky-cli 0.3.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.
@@ -0,0 +1,635 @@
1
+ import { Command } from "commander";
2
+ import { getConfig } from "./config.js";
3
+ import * as fs from "fs";
4
+ export const taskCommand = new Command("task")
5
+ .description("Manage tasks");
6
+ // Helper: Get task ID from --id flag or HUSKY_TASK_ID env var
7
+ function getTaskId(options) {
8
+ const id = options.id || process.env.HUSKY_TASK_ID;
9
+ if (!id) {
10
+ console.error("Error: Task ID required. Use --id or set HUSKY_TASK_ID environment variable.");
11
+ process.exit(1);
12
+ }
13
+ return id;
14
+ }
15
+ // Helper: Ensure API is configured
16
+ function ensureConfig() {
17
+ const config = getConfig();
18
+ if (!config.apiUrl) {
19
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
20
+ process.exit(1);
21
+ }
22
+ return config;
23
+ }
24
+ // husky task list
25
+ taskCommand
26
+ .command("list")
27
+ .description("List all tasks")
28
+ .option("-s, --status <status>", "Filter by status")
29
+ .action(async (options) => {
30
+ const config = getConfig();
31
+ if (!config.apiUrl) {
32
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
33
+ process.exit(1);
34
+ }
35
+ try {
36
+ const url = new URL("/api/tasks", config.apiUrl);
37
+ if (options.status) {
38
+ url.searchParams.set("status", options.status);
39
+ }
40
+ const res = await fetch(url.toString(), {
41
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
42
+ });
43
+ if (!res.ok) {
44
+ throw new Error(`API error: ${res.status}`);
45
+ }
46
+ const tasks = await res.json();
47
+ printTasks(tasks);
48
+ }
49
+ catch (error) {
50
+ console.error("Error fetching tasks:", error);
51
+ process.exit(1);
52
+ }
53
+ });
54
+ // husky task start <id>
55
+ taskCommand
56
+ .command("start <id>")
57
+ .description("Start working on a task")
58
+ .action(async (id) => {
59
+ const config = getConfig();
60
+ if (!config.apiUrl) {
61
+ console.error("Error: API URL not configured.");
62
+ process.exit(1);
63
+ }
64
+ try {
65
+ const res = await fetch(`${config.apiUrl}/api/tasks/${id}/start`, {
66
+ method: "POST",
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
70
+ },
71
+ body: JSON.stringify({ agent: "claude-code" }),
72
+ });
73
+ if (!res.ok) {
74
+ throw new Error(`API error: ${res.status}`);
75
+ }
76
+ const task = await res.json();
77
+ console.log(`✓ Started: ${task.title}`);
78
+ }
79
+ catch (error) {
80
+ console.error("Error starting task:", error);
81
+ process.exit(1);
82
+ }
83
+ });
84
+ // husky task done <id>
85
+ taskCommand
86
+ .command("done <id>")
87
+ .description("Mark task as done")
88
+ .option("--pr <url>", "Link to PR")
89
+ .action(async (id, options) => {
90
+ const config = getConfig();
91
+ if (!config.apiUrl) {
92
+ console.error("Error: API URL not configured.");
93
+ process.exit(1);
94
+ }
95
+ try {
96
+ const res = await fetch(`${config.apiUrl}/api/tasks/${id}/done`, {
97
+ method: "POST",
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
101
+ },
102
+ body: JSON.stringify({ prUrl: options.pr }),
103
+ });
104
+ if (!res.ok) {
105
+ throw new Error(`API error: ${res.status}`);
106
+ }
107
+ const task = await res.json();
108
+ console.log(`✓ Completed: ${task.title}`);
109
+ }
110
+ catch (error) {
111
+ console.error("Error completing task:", error);
112
+ process.exit(1);
113
+ }
114
+ });
115
+ // husky task create <title>
116
+ taskCommand
117
+ .command("create <title>")
118
+ .description("Create a new task")
119
+ .option("-d, --description <desc>", "Task description")
120
+ .option("--project <projectId>", "Project ID")
121
+ .option("--path <path>", "Path in project")
122
+ .option("-p, --priority <priority>", "Priority (low, medium, high)", "medium")
123
+ .action(async (title, options) => {
124
+ const config = getConfig();
125
+ if (!config.apiUrl) {
126
+ console.error("Error: API URL not configured.");
127
+ process.exit(1);
128
+ }
129
+ try {
130
+ const res = await fetch(`${config.apiUrl}/api/tasks`, {
131
+ method: "POST",
132
+ headers: {
133
+ "Content-Type": "application/json",
134
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
135
+ },
136
+ body: JSON.stringify({
137
+ title,
138
+ description: options.description,
139
+ projectId: options.project,
140
+ linkedPath: options.path,
141
+ priority: options.priority,
142
+ }),
143
+ });
144
+ if (!res.ok) {
145
+ throw new Error(`API error: ${res.status}`);
146
+ }
147
+ const task = await res.json();
148
+ console.log(`✓ Created: #${task.id} ${task.title}`);
149
+ }
150
+ catch (error) {
151
+ console.error("Error creating task:", error);
152
+ process.exit(1);
153
+ }
154
+ });
155
+ // husky task get [--id <id>] [--json]
156
+ taskCommand
157
+ .command("get")
158
+ .description("Get task details")
159
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
160
+ .option("--json", "Output as JSON")
161
+ .action(async (options) => {
162
+ const config = ensureConfig();
163
+ const taskId = getTaskId(options);
164
+ try {
165
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}`, {
166
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
167
+ });
168
+ if (!res.ok) {
169
+ if (res.status === 404) {
170
+ console.error(`Error: Task ${taskId} not found`);
171
+ }
172
+ else {
173
+ console.error(`Error: API returned ${res.status}`);
174
+ }
175
+ process.exit(1);
176
+ }
177
+ const task = await res.json();
178
+ if (options.json) {
179
+ console.log(JSON.stringify(task, null, 2));
180
+ }
181
+ else {
182
+ console.log(`\n Task: ${task.title}`);
183
+ console.log(" " + "─".repeat(50));
184
+ console.log(` ID: ${task.id}`);
185
+ console.log(` Status: ${task.status}`);
186
+ console.log(` Priority: ${task.priority}`);
187
+ if (task.description)
188
+ console.log(` Description: ${task.description}`);
189
+ if (task.agent)
190
+ console.log(` Agent: ${task.agent}`);
191
+ if (task.projectId)
192
+ console.log(` Project: ${task.projectId}`);
193
+ console.log("");
194
+ }
195
+ }
196
+ catch (error) {
197
+ console.error("Error fetching task:", error);
198
+ process.exit(1);
199
+ }
200
+ });
201
+ // husky task status <message> [--id <id>]
202
+ taskCommand
203
+ .command("status <message>")
204
+ .description("Report task progress status")
205
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
206
+ .action(async (message, options) => {
207
+ const config = ensureConfig();
208
+ const taskId = getTaskId(options);
209
+ try {
210
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/status`, {
211
+ method: "POST",
212
+ headers: {
213
+ "Content-Type": "application/json",
214
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
215
+ },
216
+ body: JSON.stringify({
217
+ message,
218
+ timestamp: new Date().toISOString(),
219
+ }),
220
+ });
221
+ if (!res.ok) {
222
+ throw new Error(`API error: ${res.status}`);
223
+ }
224
+ console.log(`✓ Status updated: ${message}`);
225
+ }
226
+ catch (error) {
227
+ console.error("Error updating status:", error);
228
+ process.exit(1);
229
+ }
230
+ });
231
+ // husky task plan [--summary <text>] [--file <path>] [--stdin] [--id <id>]
232
+ taskCommand
233
+ .command("plan")
234
+ .description("Submit execution plan for approval")
235
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
236
+ .option("--summary <text>", "Plan summary")
237
+ .option("--file <path>", "Read plan from file")
238
+ .option("--stdin", "Read plan from stdin")
239
+ .option("--steps <steps>", "Comma-separated steps")
240
+ .action(async (options) => {
241
+ const config = ensureConfig();
242
+ const taskId = getTaskId(options);
243
+ let content;
244
+ let summary = options.summary;
245
+ // Read content from file or stdin
246
+ if (options.file) {
247
+ try {
248
+ content = fs.readFileSync(options.file, "utf-8");
249
+ if (!summary) {
250
+ // Use first line as summary if not provided
251
+ summary = content.split("\n")[0].replace(/^#\s*/, "").slice(0, 100);
252
+ }
253
+ }
254
+ catch (error) {
255
+ console.error(`Error reading file ${options.file}:`, error);
256
+ process.exit(1);
257
+ }
258
+ }
259
+ else if (options.stdin) {
260
+ content = fs.readFileSync(0, "utf-8"); // Read from stdin
261
+ if (!summary) {
262
+ summary = content.split("\n")[0].replace(/^#\s*/, "").slice(0, 100);
263
+ }
264
+ }
265
+ if (!summary && !content) {
266
+ console.error("Error: Provide --summary, --file, or --stdin");
267
+ process.exit(1);
268
+ }
269
+ const steps = options.steps ? options.steps.split(",").map((s) => s.trim()) : undefined;
270
+ try {
271
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/plan`, {
272
+ method: "POST",
273
+ headers: {
274
+ "Content-Type": "application/json",
275
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
276
+ },
277
+ body: JSON.stringify({
278
+ summary: summary || "Execution plan",
279
+ steps,
280
+ content,
281
+ }),
282
+ });
283
+ if (!res.ok) {
284
+ throw new Error(`API error: ${res.status}`);
285
+ }
286
+ console.log(`✓ Plan submitted for task ${taskId}`);
287
+ console.log(" Waiting for approval...");
288
+ }
289
+ catch (error) {
290
+ console.error("Error submitting plan:", error);
291
+ process.exit(1);
292
+ }
293
+ });
294
+ // husky task wait-approval [--timeout <seconds>] [--id <id>]
295
+ taskCommand
296
+ .command("wait-approval")
297
+ .description("Wait for plan approval")
298
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
299
+ .option("--timeout <seconds>", "Timeout in seconds", "1800")
300
+ .action(async (options) => {
301
+ const config = ensureConfig();
302
+ const taskId = getTaskId(options);
303
+ const timeout = parseInt(options.timeout, 10) * 1000;
304
+ const pollInterval = 5000; // 5 seconds
305
+ const startTime = Date.now();
306
+ console.log(`Waiting for approval on task ${taskId}...`);
307
+ while (Date.now() - startTime < timeout) {
308
+ try {
309
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/approval`, {
310
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
311
+ });
312
+ if (!res.ok) {
313
+ throw new Error(`API error: ${res.status}`);
314
+ }
315
+ const data = await res.json();
316
+ if (data.status === "approved") {
317
+ console.log("✓ Plan approved!");
318
+ process.exit(0);
319
+ }
320
+ else if (data.status === "rejected") {
321
+ console.log("✗ Plan rejected");
322
+ process.exit(1);
323
+ }
324
+ // Still pending, wait and poll again
325
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
326
+ process.stdout.write(".");
327
+ }
328
+ catch (error) {
329
+ console.error("\nError checking approval:", error);
330
+ process.exit(1);
331
+ }
332
+ }
333
+ console.log("\n✗ Timeout waiting for approval");
334
+ process.exit(2);
335
+ });
336
+ // husky task complete [--output <text>] [--pr <url>] [--error <text>] [--id <id>]
337
+ taskCommand
338
+ .command("complete")
339
+ .description("Mark task as complete with result")
340
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
341
+ .option("--output <text>", "Completion output/summary")
342
+ .option("--pr <url>", "Pull request URL")
343
+ .option("--error <text>", "Error message (marks task as failed)")
344
+ .action(async (options) => {
345
+ const config = ensureConfig();
346
+ const taskId = getTaskId(options);
347
+ try {
348
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/complete`, {
349
+ method: "POST",
350
+ headers: {
351
+ "Content-Type": "application/json",
352
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
353
+ },
354
+ body: JSON.stringify({
355
+ output: options.output,
356
+ prUrl: options.pr,
357
+ error: options.error,
358
+ }),
359
+ });
360
+ if (!res.ok) {
361
+ throw new Error(`API error: ${res.status}`);
362
+ }
363
+ if (options.error) {
364
+ console.log(`✗ Task ${taskId} marked as failed`);
365
+ }
366
+ else {
367
+ console.log(`✓ Task ${taskId} completed`);
368
+ if (options.pr) {
369
+ console.log(` PR: ${options.pr}`);
370
+ }
371
+ }
372
+ }
373
+ catch (error) {
374
+ console.error("Error completing task:", error);
375
+ process.exit(1);
376
+ }
377
+ });
378
+ // ============================================
379
+ // QA VALIDATION COMMANDS
380
+ // ============================================
381
+ // husky task qa-start [--id <id>] [--max-iterations <n>]
382
+ taskCommand
383
+ .command("qa-start")
384
+ .description("Start QA validation for a task")
385
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
386
+ .option("--max-iterations <n>", "Max QA iterations", "5")
387
+ .option("--no-auto-fix", "Disable automatic fix attempts")
388
+ .action(async (options) => {
389
+ const config = ensureConfig();
390
+ const taskId = getTaskId(options);
391
+ try {
392
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/qa/start`, {
393
+ method: "POST",
394
+ headers: {
395
+ "Content-Type": "application/json",
396
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
397
+ },
398
+ body: JSON.stringify({
399
+ maxIterations: parseInt(options.maxIterations, 10),
400
+ autoFix: options.autoFix !== false,
401
+ }),
402
+ });
403
+ if (!res.ok) {
404
+ throw new Error(`API error: ${res.status}`);
405
+ }
406
+ const data = await res.json();
407
+ console.log(`✓ QA validation started for task ${taskId}`);
408
+ console.log(` Max iterations: ${data.maxIterations}`);
409
+ console.log(` Status: ${data.qaStatus}`);
410
+ }
411
+ catch (error) {
412
+ console.error("Error starting QA:", error);
413
+ process.exit(1);
414
+ }
415
+ });
416
+ // husky task qa-status [--id <id>] [--json]
417
+ taskCommand
418
+ .command("qa-status")
419
+ .description("Get QA validation status for a task")
420
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
421
+ .option("--json", "Output as JSON")
422
+ .action(async (options) => {
423
+ const config = ensureConfig();
424
+ const taskId = getTaskId(options);
425
+ try {
426
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/qa/status`, {
427
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
428
+ });
429
+ if (!res.ok) {
430
+ throw new Error(`API error: ${res.status}`);
431
+ }
432
+ const data = await res.json();
433
+ if (options.json) {
434
+ console.log(JSON.stringify(data, null, 2));
435
+ }
436
+ else {
437
+ console.log(`\n QA Status: ${data.taskTitle}`);
438
+ console.log(" " + "─".repeat(50));
439
+ console.log(` Status: ${data.qaStatus}`);
440
+ console.log(` Iterations: ${data.iterations.total}/${data.qaMaxIterations}`);
441
+ console.log(` Approved: ${data.iterations.approved}`);
442
+ console.log(` Rejected: ${data.iterations.rejected}`);
443
+ console.log(` Errors: ${data.iterations.errors}`);
444
+ if (data.latestIssues && data.latestIssues.length > 0) {
445
+ console.log(`\n Latest Issues:`);
446
+ for (const issue of data.latestIssues.slice(0, 5)) {
447
+ const icon = issue.type === "critical" ? "🔴" : issue.type === "major" ? "🟠" : "🟡";
448
+ console.log(` ${icon} [${issue.type}] ${issue.title}`);
449
+ }
450
+ }
451
+ if (data.isComplete) {
452
+ console.log(`\n ✓ QA Complete: ${data.qaStatus}`);
453
+ }
454
+ else if (data.requiresHumanReview) {
455
+ console.log(`\n ⚠ Human review required`);
456
+ }
457
+ console.log("");
458
+ }
459
+ }
460
+ catch (error) {
461
+ console.error("Error getting QA status:", error);
462
+ process.exit(1);
463
+ }
464
+ });
465
+ // husky task qa-approve [--id <id>] [--notes <text>]
466
+ taskCommand
467
+ .command("qa-approve")
468
+ .description("Manually approve QA for a task")
469
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
470
+ .option("--notes <text>", "Approval notes")
471
+ .action(async (options) => {
472
+ const config = ensureConfig();
473
+ const taskId = getTaskId(options);
474
+ try {
475
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/qa/approve`, {
476
+ method: "POST",
477
+ headers: {
478
+ "Content-Type": "application/json",
479
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
480
+ },
481
+ body: JSON.stringify({
482
+ approved: true,
483
+ notes: options.notes,
484
+ }),
485
+ });
486
+ if (!res.ok) {
487
+ throw new Error(`API error: ${res.status}`);
488
+ }
489
+ console.log(`✓ QA manually approved for task ${taskId}`);
490
+ }
491
+ catch (error) {
492
+ console.error("Error approving QA:", error);
493
+ process.exit(1);
494
+ }
495
+ });
496
+ // husky task qa-reject [--id <id>] [--notes <text>]
497
+ taskCommand
498
+ .command("qa-reject")
499
+ .description("Manually reject QA for a task")
500
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
501
+ .option("--notes <text>", "Rejection notes")
502
+ .action(async (options) => {
503
+ const config = ensureConfig();
504
+ const taskId = getTaskId(options);
505
+ try {
506
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/qa/approve`, {
507
+ method: "POST",
508
+ headers: {
509
+ "Content-Type": "application/json",
510
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
511
+ },
512
+ body: JSON.stringify({
513
+ approved: false,
514
+ notes: options.notes,
515
+ }),
516
+ });
517
+ if (!res.ok) {
518
+ throw new Error(`API error: ${res.status}`);
519
+ }
520
+ console.log(`✗ QA manually rejected for task ${taskId}`);
521
+ }
522
+ catch (error) {
523
+ console.error("Error rejecting QA:", error);
524
+ process.exit(1);
525
+ }
526
+ });
527
+ // husky task qa-iteration [--id <id>] --iteration <n> --status <status> [--issues <json>] [--duration <seconds>]
528
+ taskCommand
529
+ .command("qa-iteration")
530
+ .description("Add a QA iteration result (for agents)")
531
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
532
+ .requiredOption("--iteration <n>", "Iteration number")
533
+ .requiredOption("--status <status>", "Status (approved, rejected, error)")
534
+ .option("--issues <json>", "Issues as JSON array")
535
+ .option("--duration <seconds>", "Duration in seconds", "0")
536
+ .action(async (options) => {
537
+ const config = ensureConfig();
538
+ const taskId = getTaskId(options);
539
+ // Parse issues
540
+ let issues = [];
541
+ if (options.issues) {
542
+ try {
543
+ issues = JSON.parse(options.issues);
544
+ }
545
+ catch {
546
+ console.error("Error: --issues must be valid JSON");
547
+ process.exit(1);
548
+ }
549
+ }
550
+ try {
551
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/qa/iteration`, {
552
+ method: "POST",
553
+ headers: {
554
+ "Content-Type": "application/json",
555
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
556
+ },
557
+ body: JSON.stringify({
558
+ iteration: parseInt(options.iteration, 10),
559
+ status: options.status,
560
+ issues,
561
+ duration: parseFloat(options.duration),
562
+ }),
563
+ });
564
+ if (!res.ok) {
565
+ throw new Error(`API error: ${res.status}`);
566
+ }
567
+ const data = await res.json();
568
+ console.log(`✓ QA iteration ${options.iteration} recorded`);
569
+ console.log(` Status: ${data.qaStatus}`);
570
+ console.log(` Issues: ${data.issuesCount}`);
571
+ console.log(` ${data.message}`);
572
+ }
573
+ catch (error) {
574
+ console.error("Error adding QA iteration:", error);
575
+ process.exit(1);
576
+ }
577
+ });
578
+ // husky task qa-escalate [--id <id>]
579
+ taskCommand
580
+ .command("qa-escalate")
581
+ .description("Escalate QA to human review")
582
+ .option("--id <id>", "Task ID (or set HUSKY_TASK_ID)")
583
+ .action(async (options) => {
584
+ const config = ensureConfig();
585
+ const taskId = getTaskId(options);
586
+ try {
587
+ const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/qa/approve`, {
588
+ method: "PUT", // PUT for escalation
589
+ headers: {
590
+ "Content-Type": "application/json",
591
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
592
+ },
593
+ });
594
+ if (!res.ok) {
595
+ throw new Error(`API error: ${res.status}`);
596
+ }
597
+ console.log(`⚠ QA escalated to human review for task ${taskId}`);
598
+ }
599
+ catch (error) {
600
+ console.error("Error escalating QA:", error);
601
+ process.exit(1);
602
+ }
603
+ });
604
+ function printTasks(tasks) {
605
+ const byStatus = {
606
+ backlog: [],
607
+ in_progress: [],
608
+ review: [],
609
+ done: [],
610
+ };
611
+ for (const task of tasks) {
612
+ if (byStatus[task.status]) {
613
+ byStatus[task.status].push(task);
614
+ }
615
+ }
616
+ const statusLabels = {
617
+ backlog: "BACKLOG",
618
+ in_progress: "IN PROGRESS",
619
+ review: "REVIEW",
620
+ done: "DONE",
621
+ };
622
+ for (const [status, label] of Object.entries(statusLabels)) {
623
+ const statusTasks = byStatus[status];
624
+ if (statusTasks.length === 0)
625
+ continue;
626
+ console.log(`\n ${label}`);
627
+ console.log(" " + "─".repeat(50));
628
+ for (const task of statusTasks) {
629
+ const agentStr = task.agent ? ` (${task.agent})` : "";
630
+ const doneStr = status === "done" ? " ✓" : "";
631
+ console.log(` #${task.id.slice(0, 6)} ${task.title.padEnd(30)} ${task.priority}${agentStr}${doneStr}`);
632
+ }
633
+ }
634
+ console.log("");
635
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { taskCommand } from "./commands/task.js";
4
+ import { configCommand } from "./commands/config.js";
5
+ import { agentCommand } from "./commands/agent.js";
6
+ import { roadmapCommand } from "./commands/roadmap.js";
7
+ const program = new Command();
8
+ program
9
+ .name("husky")
10
+ .description("CLI for Huskyv0 Task Orchestration with Claude Agent")
11
+ .version("0.3.0");
12
+ program.addCommand(taskCommand);
13
+ program.addCommand(configCommand);
14
+ program.addCommand(agentCommand);
15
+ program.addCommand(roadmapCommand);
16
+ program.parse();
@@ -0,0 +1,44 @@
1
+ /**
2
+ * StreamClient - Sends output to Husky Dashboard via SSE
3
+ * Uses batching to reduce API calls
4
+ */
5
+ export declare class StreamClient {
6
+ private apiUrl;
7
+ private sessionId;
8
+ private apiKey;
9
+ private buffer;
10
+ private flushTimeout;
11
+ private flushIntervalMs;
12
+ private maxBufferSize;
13
+ constructor(apiUrl: string, sessionId: string, apiKey: string);
14
+ private flushBuffer;
15
+ private scheduleFlush;
16
+ send(content: string, type: "stdout" | "stderr" | "system" | "plan"): Promise<void>;
17
+ sendImmediate(content: string, type: "stdout" | "stderr" | "system" | "plan"): Promise<void>;
18
+ stdout(content: string): Promise<void>;
19
+ stderr(content: string): Promise<void>;
20
+ system(content: string): Promise<void>;
21
+ plan(content: string): Promise<void>;
22
+ flush(): Promise<void>;
23
+ }
24
+ /**
25
+ * Update session status in Husky Dashboard
26
+ */
27
+ export declare function updateSessionStatus(apiUrl: string, sessionId: string, apiKey: string, status: string, data?: Record<string, unknown>): Promise<void>;
28
+ /**
29
+ * Submit plan for approval
30
+ */
31
+ export declare function submitPlan(apiUrl: string, sessionId: string, apiKey: string, plan: {
32
+ steps: Array<{
33
+ order: number;
34
+ description: string;
35
+ files: string[];
36
+ risk: "low" | "medium" | "high";
37
+ }>;
38
+ estimatedCost: number;
39
+ estimatedRuntime: number;
40
+ }): Promise<void>;
41
+ /**
42
+ * Wait for plan approval from user
43
+ */
44
+ export declare function waitForApproval(apiUrl: string, sessionId: string, apiKey: string, timeoutMs?: number): Promise<"approved" | "rejected" | "timeout">;