@simonfestl/husky-cli 0.8.2 β 0.9.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/README.md +3 -116
- package/dist/commands/biz/customers.d.ts +8 -0
- package/dist/commands/biz/customers.js +181 -0
- package/dist/commands/biz/orders.d.ts +8 -0
- package/dist/commands/biz/orders.js +226 -0
- package/dist/commands/biz/products.d.ts +8 -0
- package/dist/commands/biz/products.js +255 -0
- package/dist/commands/biz/qdrant.d.ts +8 -0
- package/dist/commands/biz/qdrant.js +170 -0
- package/dist/commands/biz/seatable.d.ts +8 -0
- package/dist/commands/biz/seatable.js +449 -0
- package/dist/commands/biz/tickets.d.ts +8 -0
- package/dist/commands/biz/tickets.js +600 -0
- package/dist/commands/biz.d.ts +9 -0
- package/dist/commands/biz.js +22 -0
- package/dist/commands/config.d.ts +13 -0
- package/dist/commands/config.js +43 -16
- package/dist/commands/explain.js +12 -595
- package/dist/commands/idea.js +2 -50
- package/dist/commands/project.js +2 -47
- package/dist/commands/roadmap.js +0 -107
- package/dist/commands/task.js +11 -17
- package/dist/commands/vm.js +0 -225
- package/dist/commands/workflow.js +4 -60
- package/dist/index.js +5 -1
- package/dist/lib/biz/billbee-types.d.ts +259 -0
- package/dist/lib/biz/billbee-types.js +41 -0
- package/dist/lib/biz/billbee.d.ts +37 -0
- package/dist/lib/biz/billbee.js +165 -0
- package/dist/lib/biz/embeddings.d.ts +45 -0
- package/dist/lib/biz/embeddings.js +115 -0
- package/dist/lib/biz/index.d.ts +13 -0
- package/dist/lib/biz/index.js +11 -0
- package/dist/lib/biz/qdrant.d.ts +52 -0
- package/dist/lib/biz/qdrant.js +158 -0
- package/dist/lib/biz/seatable-types.d.ts +115 -0
- package/dist/lib/biz/seatable-types.js +27 -0
- package/dist/lib/biz/seatable.d.ts +49 -0
- package/dist/lib/biz/seatable.js +210 -0
- package/dist/lib/biz/zendesk-types.d.ts +136 -0
- package/dist/lib/biz/zendesk-types.js +28 -0
- package/dist/lib/biz/zendesk.d.ts +45 -0
- package/dist/lib/biz/zendesk.js +206 -0
- package/package.json +2 -2
package/dist/commands/idea.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getConfig } from "./config.js";
|
|
3
|
-
import { paginateList, printPaginated } from "../lib/pagination.js";
|
|
4
3
|
export const ideaCommand = new Command("idea")
|
|
5
4
|
.description("Manage ideas");
|
|
6
5
|
// Helper: Ensure API is configured
|
|
@@ -12,22 +11,12 @@ function ensureConfig() {
|
|
|
12
11
|
}
|
|
13
12
|
return config;
|
|
14
13
|
}
|
|
15
|
-
// Status labels and icons
|
|
16
|
-
const STATUS_CONFIG = {
|
|
17
|
-
draft: { label: "Draft", icon: "π" },
|
|
18
|
-
active: { label: "Active", icon: "π‘" },
|
|
19
|
-
archived: { label: "Archived", icon: "π¦" },
|
|
20
|
-
converted: { label: "Converted", icon: "β
" },
|
|
21
|
-
};
|
|
22
14
|
// husky idea list
|
|
23
15
|
ideaCommand
|
|
24
16
|
.command("list")
|
|
25
17
|
.description("List all ideas")
|
|
26
18
|
.option("--status <status>", "Filter by status (draft, active, archived, converted)")
|
|
27
19
|
.option("--json", "Output as JSON")
|
|
28
|
-
.option("-p, --page <num>", "Page number (starts at 1)", "1")
|
|
29
|
-
.option("-n, --per-page <num>", "Items per page (default: all)")
|
|
30
|
-
.option("-i, --interactive", "Interactive pagination with arrow keys")
|
|
31
20
|
.action(async (options) => {
|
|
32
21
|
const config = ensureConfig();
|
|
33
22
|
try {
|
|
@@ -44,47 +33,10 @@ ideaCommand
|
|
|
44
33
|
const ideas = await res.json();
|
|
45
34
|
if (options.json) {
|
|
46
35
|
console.log(JSON.stringify(ideas, null, 2));
|
|
47
|
-
return;
|
|
48
36
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
await paginateList({
|
|
52
|
-
items: ideas,
|
|
53
|
-
pageSize: 10,
|
|
54
|
-
title: "Ideas",
|
|
55
|
-
emptyMessage: "No ideas found.",
|
|
56
|
-
renderItem: (idea) => {
|
|
57
|
-
const cfg = STATUS_CONFIG[idea.status] || { label: idea.status, icon: "β" };
|
|
58
|
-
return ` ${cfg.icon} ${idea.title.slice(0, 40).padEnd(40)} β ${cfg.label}`;
|
|
59
|
-
},
|
|
60
|
-
selectableItems: true,
|
|
61
|
-
onSelect: async (idea) => {
|
|
62
|
-
console.clear();
|
|
63
|
-
console.log(`\n Idea: ${idea.title}`);
|
|
64
|
-
console.log(" " + "β".repeat(50));
|
|
65
|
-
console.log(` ID: ${idea.id}`);
|
|
66
|
-
console.log(` Status: ${STATUS_CONFIG[idea.status]?.label || idea.status}`);
|
|
67
|
-
if (idea.category)
|
|
68
|
-
console.log(` Category: ${idea.category}`);
|
|
69
|
-
if (idea.description)
|
|
70
|
-
console.log(` Desc: ${idea.description}`);
|
|
71
|
-
console.log("");
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
// Simple pagination mode
|
|
77
|
-
if (options.perPage) {
|
|
78
|
-
const pageNum = parseInt(options.page, 10) - 1;
|
|
79
|
-
const pageSize = parseInt(options.perPage, 10);
|
|
80
|
-
printPaginated(ideas, pageNum, pageSize, (idea) => {
|
|
81
|
-
const cfg = STATUS_CONFIG[idea.status] || { label: idea.status, icon: "β" };
|
|
82
|
-
return ` ${cfg.icon} ${idea.id.slice(0, 8)} β ${idea.title}`;
|
|
83
|
-
}, "Ideas");
|
|
84
|
-
return;
|
|
37
|
+
else {
|
|
38
|
+
printIdeas(ideas);
|
|
85
39
|
}
|
|
86
|
-
// Default list
|
|
87
|
-
printIdeas(ideas);
|
|
88
40
|
}
|
|
89
41
|
catch (error) {
|
|
90
42
|
console.error("Error fetching ideas:", error);
|
package/dist/commands/project.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getConfig } from "./config.js";
|
|
3
|
-
import { paginateList, printPaginated } from "../lib/pagination.js";
|
|
4
3
|
export const projectCommand = new Command("project")
|
|
5
4
|
.description("Manage projects and knowledge");
|
|
6
5
|
// Helper: Ensure API is configured
|
|
@@ -38,9 +37,6 @@ projectCommand
|
|
|
38
37
|
.option("--status <status>", "Filter by status (active, archived)")
|
|
39
38
|
.option("--work-status <workStatus>", "Filter by work status (planning, in_progress, review, completed, on_hold)")
|
|
40
39
|
.option("--archived", "Include archived projects")
|
|
41
|
-
.option("-p, --page <num>", "Page number (starts at 1)", "1")
|
|
42
|
-
.option("-n, --per-page <num>", "Items per page (default: all)")
|
|
43
|
-
.option("-i, --interactive", "Interactive pagination with arrow keys")
|
|
44
40
|
.action(async (options) => {
|
|
45
41
|
const config = ensureConfig();
|
|
46
42
|
try {
|
|
@@ -64,51 +60,10 @@ projectCommand
|
|
|
64
60
|
}
|
|
65
61
|
if (options.json) {
|
|
66
62
|
console.log(JSON.stringify(projects, null, 2));
|
|
67
|
-
return;
|
|
68
63
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await paginateList({
|
|
72
|
-
items: projects,
|
|
73
|
-
pageSize: 10,
|
|
74
|
-
title: "Projects",
|
|
75
|
-
emptyMessage: "No projects found.",
|
|
76
|
-
renderItem: (project) => {
|
|
77
|
-
const statusIcon = project.status === "archived" ? "π¦" : "π";
|
|
78
|
-
const workStatusLabel = WORK_STATUS_LABELS[project.workStatus] || project.workStatus;
|
|
79
|
-
return ` ${statusIcon} ${project.name.slice(0, 30).padEnd(30)} β ${workStatusLabel}`;
|
|
80
|
-
},
|
|
81
|
-
selectableItems: true,
|
|
82
|
-
onSelect: async (project) => {
|
|
83
|
-
console.clear();
|
|
84
|
-
console.log(`\n Project: ${project.name}`);
|
|
85
|
-
console.log(" " + "β".repeat(50));
|
|
86
|
-
console.log(` ID: ${project.id}`);
|
|
87
|
-
console.log(` Status: ${project.status}`);
|
|
88
|
-
console.log(` Work: ${WORK_STATUS_LABELS[project.workStatus]}`);
|
|
89
|
-
if (project.description)
|
|
90
|
-
console.log(` Desc: ${project.description}`);
|
|
91
|
-
if (project.techStack)
|
|
92
|
-
console.log(` Tech: ${project.techStack}`);
|
|
93
|
-
if (project.githubRepo)
|
|
94
|
-
console.log(` GitHub: ${project.githubRepo}`);
|
|
95
|
-
console.log("");
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
// Simple pagination mode
|
|
101
|
-
if (options.perPage) {
|
|
102
|
-
const pageNum = parseInt(options.page, 10) - 1;
|
|
103
|
-
const pageSize = parseInt(options.perPage, 10);
|
|
104
|
-
printPaginated(projects, pageNum, pageSize, (project) => {
|
|
105
|
-
const statusIcon = project.status === "archived" ? "π¦" : "π";
|
|
106
|
-
return ` ${statusIcon} ${project.id.slice(0, 8)} β ${project.name}`;
|
|
107
|
-
}, "Projects");
|
|
108
|
-
return;
|
|
64
|
+
else {
|
|
65
|
+
printProjects(projects);
|
|
109
66
|
}
|
|
110
|
-
// Default: standard list
|
|
111
|
-
printProjects(projects);
|
|
112
67
|
}
|
|
113
68
|
catch (error) {
|
|
114
69
|
console.error("Error fetching projects:", error);
|
package/dist/commands/roadmap.js
CHANGED
|
@@ -152,113 +152,6 @@ roadmapCommand
|
|
|
152
152
|
process.exit(1);
|
|
153
153
|
}
|
|
154
154
|
});
|
|
155
|
-
// husky roadmap update-phase <roadmapId> <phaseId>
|
|
156
|
-
roadmapCommand
|
|
157
|
-
.command("update-phase <roadmapId> <phaseId>")
|
|
158
|
-
.description("Update a roadmap phase")
|
|
159
|
-
.option("-n, --name <name>", "New phase name")
|
|
160
|
-
.option("-d, --description <description>", "New phase description")
|
|
161
|
-
.option("--status <status>", "Phase status (planned, in_progress, completed)")
|
|
162
|
-
.option("--order <order>", "Phase order (0-based)")
|
|
163
|
-
.action(async (roadmapId, phaseId, options) => {
|
|
164
|
-
const config = ensureConfig();
|
|
165
|
-
// Build update payload
|
|
166
|
-
const updateData = { phaseId };
|
|
167
|
-
if (options.name)
|
|
168
|
-
updateData.name = options.name;
|
|
169
|
-
if (options.description)
|
|
170
|
-
updateData.description = options.description;
|
|
171
|
-
if (options.status) {
|
|
172
|
-
const validStatuses = ["planned", "in_progress", "completed"];
|
|
173
|
-
if (!validStatuses.includes(options.status)) {
|
|
174
|
-
console.error(`Error: Invalid status "${options.status}". Must be one of: ${validStatuses.join(", ")}`);
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
updateData.status = options.status;
|
|
178
|
-
}
|
|
179
|
-
if (options.order !== undefined) {
|
|
180
|
-
const order = parseInt(options.order, 10);
|
|
181
|
-
if (isNaN(order) || order < 0) {
|
|
182
|
-
console.error("Error: Order must be a non-negative integer");
|
|
183
|
-
process.exit(1);
|
|
184
|
-
}
|
|
185
|
-
updateData.order = order;
|
|
186
|
-
}
|
|
187
|
-
if (Object.keys(updateData).length === 1) {
|
|
188
|
-
console.error("Error: No update options provided. Use -n/--name, -d/--description, --status, or --order");
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}/phases`, {
|
|
193
|
-
method: "PATCH",
|
|
194
|
-
headers: {
|
|
195
|
-
"Content-Type": "application/json",
|
|
196
|
-
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
197
|
-
},
|
|
198
|
-
body: JSON.stringify(updateData),
|
|
199
|
-
});
|
|
200
|
-
if (!res.ok) {
|
|
201
|
-
if (res.status === 404) {
|
|
202
|
-
console.error(`Error: Roadmap or phase not found`);
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
const errorBody = await res.json().catch(() => ({}));
|
|
206
|
-
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
207
|
-
}
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
const roadmap = await res.json();
|
|
211
|
-
const updatedPhase = roadmap.phases.find((p) => p.id === phaseId);
|
|
212
|
-
console.log(`β Phase updated successfully`);
|
|
213
|
-
if (updatedPhase) {
|
|
214
|
-
console.log(` Name: ${updatedPhase.name}`);
|
|
215
|
-
console.log(` Status: ${updatedPhase.status}`);
|
|
216
|
-
console.log(` Order: ${updatedPhase.order}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
console.error("Error updating phase:", error);
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
// husky roadmap delete-phase <roadmapId> <phaseId>
|
|
225
|
-
roadmapCommand
|
|
226
|
-
.command("delete-phase <roadmapId> <phaseId>")
|
|
227
|
-
.description("Delete a phase from a roadmap (including all its features)")
|
|
228
|
-
.option("--force", "Skip confirmation")
|
|
229
|
-
.action(async (roadmapId, phaseId, options) => {
|
|
230
|
-
const config = ensureConfig();
|
|
231
|
-
if (!options.force) {
|
|
232
|
-
console.log("Warning: This will permanently delete the phase and all its features.");
|
|
233
|
-
console.log("Use --force to confirm deletion.");
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
try {
|
|
237
|
-
const url = new URL(`/api/roadmaps/${roadmapId}/phases`, config.apiUrl);
|
|
238
|
-
url.searchParams.set("phaseId", phaseId);
|
|
239
|
-
const res = await fetch(url.toString(), {
|
|
240
|
-
method: "DELETE",
|
|
241
|
-
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
242
|
-
});
|
|
243
|
-
if (!res.ok) {
|
|
244
|
-
if (res.status === 404) {
|
|
245
|
-
console.error(`Error: Roadmap or phase not found`);
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
const errorBody = await res.json().catch(() => ({}));
|
|
249
|
-
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
250
|
-
}
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
const roadmap = await res.json();
|
|
254
|
-
console.log(`β Phase deleted`);
|
|
255
|
-
console.log(` Remaining phases: ${roadmap.phases.length}`);
|
|
256
|
-
}
|
|
257
|
-
catch (error) {
|
|
258
|
-
console.error("Error deleting phase:", error);
|
|
259
|
-
process.exit(1);
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
155
|
// husky roadmap add-feature <roadmapId> <phaseId> <title>
|
|
263
156
|
roadmapCommand
|
|
264
157
|
.command("add-feature <roadmapId> <phaseId> <title>")
|
package/dist/commands/task.js
CHANGED
|
@@ -182,7 +182,7 @@ taskCommand
|
|
|
182
182
|
renderItem: (task) => {
|
|
183
183
|
const statusIcon = task.status === "done" ? "β" : task.status === "in_progress" ? "βΆ" : "β";
|
|
184
184
|
const priorityIcon = task.priority === "urgent" ? "π΄" : task.priority === "high" ? "π " : "";
|
|
185
|
-
return ` ${statusIcon} ${task.id.
|
|
185
|
+
return ` ${statusIcon} ${task.id.padEnd(20)} β ${task.title.slice(0, 40).padEnd(40)} ${priorityIcon}`;
|
|
186
186
|
},
|
|
187
187
|
selectableItems: true,
|
|
188
188
|
onSelect: async (task) => {
|
|
@@ -205,7 +205,7 @@ taskCommand
|
|
|
205
205
|
const pageSize = parseInt(options.perPage, 10);
|
|
206
206
|
printPaginated(tasks, pageNum, pageSize, (task) => {
|
|
207
207
|
const statusIcon = task.status === "done" ? "β" : task.status === "in_progress" ? "βΆ" : "β";
|
|
208
|
-
return ` ${statusIcon} ${task.id.
|
|
208
|
+
return ` ${statusIcon} ${task.id.padEnd(20)} β ${task.title}`;
|
|
209
209
|
}, "Tasks");
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
@@ -545,19 +545,13 @@ taskCommand
|
|
|
545
545
|
process.exit(1);
|
|
546
546
|
}
|
|
547
547
|
});
|
|
548
|
-
// husky task message
|
|
548
|
+
// husky task message <id> "message" - post status message to task
|
|
549
549
|
taskCommand
|
|
550
|
-
.command("message")
|
|
550
|
+
.command("message <id> <message>")
|
|
551
551
|
.description("Post a status message to a task")
|
|
552
|
-
.
|
|
553
|
-
.option("-m, --message <text>", "Status message", "")
|
|
554
|
-
.action(async (options) => {
|
|
552
|
+
.action(async (id, message) => {
|
|
555
553
|
const config = ensureConfig();
|
|
556
|
-
const taskId =
|
|
557
|
-
if (!options.message) {
|
|
558
|
-
console.error("Error: Message required. Use -m or --message");
|
|
559
|
-
process.exit(1);
|
|
560
|
-
}
|
|
554
|
+
const taskId = id;
|
|
561
555
|
try {
|
|
562
556
|
const res = await fetch(`${config.apiUrl}/api/tasks/${taskId}/status`, {
|
|
563
557
|
method: "POST",
|
|
@@ -566,7 +560,7 @@ taskCommand
|
|
|
566
560
|
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
567
561
|
},
|
|
568
562
|
body: JSON.stringify({
|
|
569
|
-
message
|
|
563
|
+
message,
|
|
570
564
|
timestamp: new Date().toISOString(),
|
|
571
565
|
}),
|
|
572
566
|
});
|
|
@@ -1079,15 +1073,15 @@ function printTasks(tasks) {
|
|
|
1079
1073
|
if (statusTasks.length === 0)
|
|
1080
1074
|
continue;
|
|
1081
1075
|
console.log(`\n ${label}`);
|
|
1082
|
-
console.log(" " + "β".repeat(
|
|
1083
|
-
console.log(` ${"ID".padEnd(
|
|
1084
|
-
console.log(" " + "β".repeat(
|
|
1076
|
+
console.log(" " + "β".repeat(95));
|
|
1077
|
+
console.log(` ${"ID".padEnd(20)} ${"Title".padEnd(30)} ${"Project".padEnd(15)} ${"Project ID".padEnd(12)} ${"Priority".padEnd(6)}`);
|
|
1078
|
+
console.log(" " + "β".repeat(95));
|
|
1085
1079
|
for (const task of statusTasks) {
|
|
1086
1080
|
const agentStr = task.agent ? ` (${task.agent})` : "";
|
|
1087
1081
|
const doneStr = status === "done" ? " β" : "";
|
|
1088
1082
|
const projectName = task.projectName?.slice(0, 15).padEnd(15) || "β".padEnd(15);
|
|
1089
1083
|
const projectId = task.projectId?.slice(0, 12).padEnd(12) || "β".padEnd(12);
|
|
1090
|
-
console.log(` ${task.id.
|
|
1084
|
+
console.log(` ${task.id.padEnd(20)} ${task.title.slice(0, 30).padEnd(30)} ${projectName} ${projectId} ${task.priority.padEnd(6)}${agentStr}${doneStr}`);
|
|
1091
1085
|
}
|
|
1092
1086
|
}
|
|
1093
1087
|
console.log("");
|
package/dist/commands/vm.js
CHANGED
|
@@ -569,13 +569,6 @@ function printVMSessionDetail(session) {
|
|
|
569
569
|
if (session.workflowId) {
|
|
570
570
|
console.log(` Workflow ID: ${session.workflowId}`);
|
|
571
571
|
}
|
|
572
|
-
// Pool info
|
|
573
|
-
if (session.sessionType) {
|
|
574
|
-
console.log(` Session Type: ${session.sessionType === 'pool' ? 'Pool VM' : 'On-Demand'}`);
|
|
575
|
-
if (session.poolVmId) {
|
|
576
|
-
console.log(` Pool VM ID: ${session.poolVmId}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
572
|
console.log(`\n Prompt:`);
|
|
580
573
|
console.log(` ${session.prompt}`);
|
|
581
574
|
if (session.costEstimate !== undefined) {
|
|
@@ -626,221 +619,3 @@ function printLog(log) {
|
|
|
626
619
|
}
|
|
627
620
|
console.log(`${prefix}${timestamp} ${level} ${source} ${log.message}`);
|
|
628
621
|
}
|
|
629
|
-
// ============================================
|
|
630
|
-
// VM POOL COMMANDS
|
|
631
|
-
// ============================================
|
|
632
|
-
const poolCommand = new Command("pool")
|
|
633
|
-
.description("Manage VM pool (admin)");
|
|
634
|
-
// husky vm pool status
|
|
635
|
-
poolCommand
|
|
636
|
-
.command("status")
|
|
637
|
-
.description("Show VM pool status")
|
|
638
|
-
.option("--json", "Output as JSON")
|
|
639
|
-
.action(async (options) => {
|
|
640
|
-
const config = ensureConfig();
|
|
641
|
-
try {
|
|
642
|
-
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool`, {
|
|
643
|
-
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
644
|
-
});
|
|
645
|
-
if (!res.ok) {
|
|
646
|
-
const errorData = await res.json().catch(() => ({}));
|
|
647
|
-
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
648
|
-
}
|
|
649
|
-
const data = await res.json();
|
|
650
|
-
const vms = data.vms || [];
|
|
651
|
-
if (options.json) {
|
|
652
|
-
console.log(JSON.stringify(data, null, 2));
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
printPoolStatus(vms);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
catch (error) {
|
|
659
|
-
console.error("Error fetching VM pool status:", error);
|
|
660
|
-
process.exit(1);
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
// husky vm pool init
|
|
664
|
-
poolCommand
|
|
665
|
-
.command("init")
|
|
666
|
-
.description("Initialize VM pool (1 HOT + 4 COLD VMs)")
|
|
667
|
-
.option("--json", "Output as JSON")
|
|
668
|
-
.action(async (options) => {
|
|
669
|
-
const config = ensureConfig();
|
|
670
|
-
console.log("Initializing VM pool...");
|
|
671
|
-
console.log("This will create 1 HOT VM + 4 COLD VMs\n");
|
|
672
|
-
try {
|
|
673
|
-
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/initialize`, {
|
|
674
|
-
method: "POST",
|
|
675
|
-
headers: {
|
|
676
|
-
"Content-Type": "application/json",
|
|
677
|
-
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
678
|
-
},
|
|
679
|
-
});
|
|
680
|
-
if (!res.ok) {
|
|
681
|
-
const errorData = await res.json().catch(() => ({}));
|
|
682
|
-
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
683
|
-
}
|
|
684
|
-
const data = await res.json();
|
|
685
|
-
if (options.json) {
|
|
686
|
-
console.log(JSON.stringify(data, null, 2));
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
console.log(`VM Pool initialized!`);
|
|
690
|
-
console.log(` HOT VMs: ${data.hotCount || 1}`);
|
|
691
|
-
console.log(` COLD VMs: ${data.coldCount || 4}`);
|
|
692
|
-
console.log(`\nRun 'husky vm pool status' to see pool details`);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
catch (error) {
|
|
696
|
-
console.error("Error initializing VM pool:", error);
|
|
697
|
-
process.exit(1);
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
// husky vm pool suspend <id>
|
|
701
|
-
poolCommand
|
|
702
|
-
.command("suspend <id>")
|
|
703
|
-
.description("Suspend a pool VM")
|
|
704
|
-
.option("--json", "Output as JSON")
|
|
705
|
-
.action(async (id, options) => {
|
|
706
|
-
const config = ensureConfig();
|
|
707
|
-
console.log(`Suspending VM ${id}...\n`);
|
|
708
|
-
try {
|
|
709
|
-
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/${id}/suspend`, {
|
|
710
|
-
method: "POST",
|
|
711
|
-
headers: {
|
|
712
|
-
"Content-Type": "application/json",
|
|
713
|
-
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
714
|
-
},
|
|
715
|
-
});
|
|
716
|
-
if (!res.ok) {
|
|
717
|
-
const errorData = await res.json().catch(() => ({}));
|
|
718
|
-
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
719
|
-
}
|
|
720
|
-
const data = await res.json();
|
|
721
|
-
if (options.json) {
|
|
722
|
-
console.log(JSON.stringify(data, null, 2));
|
|
723
|
-
}
|
|
724
|
-
else {
|
|
725
|
-
console.log(`VM suspended successfully`);
|
|
726
|
-
console.log(` VM Name: ${data.vmName}`);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
catch (error) {
|
|
730
|
-
console.error("Error suspending VM:", error);
|
|
731
|
-
process.exit(1);
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
// husky vm pool resume <id>
|
|
735
|
-
poolCommand
|
|
736
|
-
.command("resume <id>")
|
|
737
|
-
.description("Resume a suspended pool VM")
|
|
738
|
-
.option("--json", "Output as JSON")
|
|
739
|
-
.action(async (id, options) => {
|
|
740
|
-
const config = ensureConfig();
|
|
741
|
-
console.log(`Resuming VM ${id}...\n`);
|
|
742
|
-
try {
|
|
743
|
-
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/${id}/resume`, {
|
|
744
|
-
method: "POST",
|
|
745
|
-
headers: {
|
|
746
|
-
"Content-Type": "application/json",
|
|
747
|
-
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
748
|
-
},
|
|
749
|
-
});
|
|
750
|
-
if (!res.ok) {
|
|
751
|
-
const errorData = await res.json().catch(() => ({}));
|
|
752
|
-
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
753
|
-
}
|
|
754
|
-
const data = await res.json();
|
|
755
|
-
if (options.json) {
|
|
756
|
-
console.log(JSON.stringify(data, null, 2));
|
|
757
|
-
}
|
|
758
|
-
else {
|
|
759
|
-
console.log(`VM resumed successfully`);
|
|
760
|
-
console.log(` VM Name: ${data.vmName}`);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
catch (error) {
|
|
764
|
-
console.error("Error resuming VM:", error);
|
|
765
|
-
process.exit(1);
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
// husky vm pool setup <id>
|
|
769
|
-
poolCommand
|
|
770
|
-
.command("setup <id>")
|
|
771
|
-
.description("Start Claude subscription setup for a pool VM")
|
|
772
|
-
.option("--json", "Output as JSON")
|
|
773
|
-
.action(async (id, options) => {
|
|
774
|
-
const config = ensureConfig();
|
|
775
|
-
console.log(`Starting subscription setup for VM ${id}...\n`);
|
|
776
|
-
try {
|
|
777
|
-
const res = await fetch(`${config.apiUrl}/api/admin/vm-pool/${id}/setup`, {
|
|
778
|
-
method: "POST",
|
|
779
|
-
headers: {
|
|
780
|
-
"Content-Type": "application/json",
|
|
781
|
-
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
782
|
-
},
|
|
783
|
-
});
|
|
784
|
-
if (!res.ok) {
|
|
785
|
-
const errorData = await res.json().catch(() => ({}));
|
|
786
|
-
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
787
|
-
}
|
|
788
|
-
const data = await res.json();
|
|
789
|
-
if (options.json) {
|
|
790
|
-
console.log(JSON.stringify(data, null, 2));
|
|
791
|
-
}
|
|
792
|
-
else {
|
|
793
|
-
console.log(`Setup session created!`);
|
|
794
|
-
console.log(` Session ID: ${data.sessionId}`);
|
|
795
|
-
console.log(` VM Name: ${data.vmName}`);
|
|
796
|
-
console.log(` VM IP: ${data.vmIpAddress || "pending"}`);
|
|
797
|
-
console.log(`\nInstructions:`);
|
|
798
|
-
for (const instruction of data.instructions || []) {
|
|
799
|
-
console.log(` ${instruction}`);
|
|
800
|
-
}
|
|
801
|
-
console.log(`\nExpires: ${data.expiresAt}`);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
catch (error) {
|
|
805
|
-
console.error("Error starting setup:", error);
|
|
806
|
-
process.exit(1);
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
// Print helper for pool status
|
|
810
|
-
function printPoolStatus(vms) {
|
|
811
|
-
if (vms.length === 0) {
|
|
812
|
-
console.log("\n VM Pool is empty.");
|
|
813
|
-
console.log(" Initialize with: husky vm pool init\n");
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
// Calculate stats
|
|
817
|
-
const total = vms.length;
|
|
818
|
-
const available = vms.filter(v => v.status === 'available').length;
|
|
819
|
-
const inUse = vms.filter(v => v.status === 'in_use').length;
|
|
820
|
-
const suspended = vms.filter(v => v.status === 'suspended').length;
|
|
821
|
-
const hotVms = vms.filter(v => v.tier === 'hot').length;
|
|
822
|
-
const authenticated = vms.filter(v => v.subscriptionAuth).length;
|
|
823
|
-
// Estimate monthly cost
|
|
824
|
-
const runningCost = (available + inUse) * 12.24; // $12.24/month for e2-small running
|
|
825
|
-
const suspendedCost = suspended * 0.30; // $0.30/month for suspended
|
|
826
|
-
const totalCost = runningCost + suspendedCost;
|
|
827
|
-
console.log("\n VM POOL STATUS");
|
|
828
|
-
console.log(" " + "=".repeat(70));
|
|
829
|
-
console.log(` Total: ${total} | Available: ${available} | In Use: ${inUse} | Suspended: ${suspended}`);
|
|
830
|
-
console.log(` HOT VMs: ${hotVms} | Authenticated: ${authenticated}/${total}`);
|
|
831
|
-
console.log(` Est. Monthly Cost: ~$${totalCost.toFixed(2)}`);
|
|
832
|
-
console.log(" " + "-".repeat(70));
|
|
833
|
-
console.log(` ${"ID".padEnd(24)} ${"NAME".padEnd(18)} ${"STATUS".padEnd(12)} ${"TIER".padEnd(6)} ${"AUTH".padEnd(5)} LAST USED`);
|
|
834
|
-
console.log(" " + "-".repeat(70));
|
|
835
|
-
for (const vm of vms) {
|
|
836
|
-
const status = vm.status.toUpperCase().padEnd(12);
|
|
837
|
-
const tier = vm.tier.toUpperCase().padEnd(6);
|
|
838
|
-
const auth = vm.subscriptionAuth ? "Yes" : "No";
|
|
839
|
-
const lastUsed = new Date(vm.lastUsedAt).toLocaleDateString();
|
|
840
|
-
const truncatedName = vm.vmName.length > 16 ? vm.vmName.substring(0, 13) + "..." : vm.vmName;
|
|
841
|
-
console.log(` ${vm.id.padEnd(24)} ${truncatedName.padEnd(18)} ${status} ${tier} ${auth.padEnd(5)} ${lastUsed}`);
|
|
842
|
-
}
|
|
843
|
-
console.log("");
|
|
844
|
-
}
|
|
845
|
-
// Add pool command to vm command
|
|
846
|
-
vmCommand.addCommand(poolCommand);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getConfig } from "./config.js";
|
|
3
|
-
import { paginateList, printPaginated } from "../lib/pagination.js";
|
|
4
3
|
export const workflowCommand = new Command("workflow")
|
|
5
4
|
.description("Manage workflows and workflow steps");
|
|
6
5
|
// Helper: Ensure API is configured
|
|
@@ -12,28 +11,12 @@ function ensureConfig() {
|
|
|
12
11
|
}
|
|
13
12
|
return config;
|
|
14
13
|
}
|
|
15
|
-
// Status icons
|
|
16
|
-
const STATUS_ICONS = {
|
|
17
|
-
draft: "π",
|
|
18
|
-
active: "βΆοΈ",
|
|
19
|
-
paused: "βΈοΈ",
|
|
20
|
-
archived: "π¦",
|
|
21
|
-
};
|
|
22
|
-
// Action labels
|
|
23
|
-
const ACTION_LABELS = {
|
|
24
|
-
manual: "Manual",
|
|
25
|
-
semi_automated: "Semi-Auto",
|
|
26
|
-
fully_automated: "Auto",
|
|
27
|
-
};
|
|
28
14
|
// husky workflow list
|
|
29
15
|
workflowCommand
|
|
30
16
|
.command("list")
|
|
31
17
|
.description("List all workflows")
|
|
32
18
|
.option("--value-stream <valueStream>", "Filter by value stream")
|
|
33
19
|
.option("--json", "Output as JSON")
|
|
34
|
-
.option("-p, --page <num>", "Page number (starts at 1)", "1")
|
|
35
|
-
.option("-n, --per-page <num>", "Items per page (default: all)")
|
|
36
|
-
.option("-i, --interactive", "Interactive pagination with arrow keys")
|
|
37
20
|
.action(async (options) => {
|
|
38
21
|
const config = ensureConfig();
|
|
39
22
|
try {
|
|
@@ -50,49 +33,10 @@ workflowCommand
|
|
|
50
33
|
const workflows = await res.json();
|
|
51
34
|
if (options.json) {
|
|
52
35
|
console.log(JSON.stringify(workflows, null, 2));
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
await paginateList({
|
|
58
|
-
items: workflows,
|
|
59
|
-
pageSize: 10,
|
|
60
|
-
title: "Workflows",
|
|
61
|
-
emptyMessage: "No workflows found.",
|
|
62
|
-
renderItem: (wf) => {
|
|
63
|
-
const icon = STATUS_ICONS[wf.status] || "β";
|
|
64
|
-
const action = ACTION_LABELS[wf.action] || wf.action;
|
|
65
|
-
return ` ${icon} ${wf.name.slice(0, 35).padEnd(35)} β ${action}`;
|
|
66
|
-
},
|
|
67
|
-
selectableItems: true,
|
|
68
|
-
onSelect: async (wf) => {
|
|
69
|
-
console.clear();
|
|
70
|
-
console.log(`\n Workflow: ${wf.name}`);
|
|
71
|
-
console.log(" " + "β".repeat(50));
|
|
72
|
-
console.log(` ID: ${wf.id}`);
|
|
73
|
-
console.log(` Status: ${wf.status}`);
|
|
74
|
-
console.log(` Action: ${ACTION_LABELS[wf.action]}`);
|
|
75
|
-
console.log(` ValueStream: ${wf.valueStream}`);
|
|
76
|
-
console.log(` Autonomy: ${wf.autonomyWeight}%`);
|
|
77
|
-
if (wf.description)
|
|
78
|
-
console.log(` Desc: ${wf.description}`);
|
|
79
|
-
console.log("");
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
// Simple pagination mode
|
|
85
|
-
if (options.perPage) {
|
|
86
|
-
const pageNum = parseInt(options.page, 10) - 1;
|
|
87
|
-
const pageSize = parseInt(options.perPage, 10);
|
|
88
|
-
printPaginated(workflows, pageNum, pageSize, (wf) => {
|
|
89
|
-
const icon = STATUS_ICONS[wf.status] || "β";
|
|
90
|
-
return ` ${icon} ${wf.id.slice(0, 8)} β ${wf.name}`;
|
|
91
|
-
}, "Workflows");
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
// Default list
|
|
95
|
-
printWorkflows(workflows);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
printWorkflows(workflows);
|
|
39
|
+
}
|
|
96
40
|
}
|
|
97
41
|
catch (error) {
|
|
98
42
|
console.error("Error fetching workflows:", error);
|