@simonfestl/husky-cli 0.3.0 → 0.5.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 +230 -56
- package/dist/commands/changelog.d.ts +2 -0
- package/dist/commands/changelog.js +401 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +400 -0
- package/dist/commands/config.js +95 -1
- package/dist/commands/department.d.ts +2 -0
- package/dist/commands/department.js +240 -0
- package/dist/commands/explain.d.ts +2 -0
- package/dist/commands/explain.js +411 -0
- package/dist/commands/idea.d.ts +2 -0
- package/dist/commands/idea.js +340 -0
- package/dist/commands/interactive.d.ts +1 -0
- package/dist/commands/interactive.js +1287 -0
- package/dist/commands/jules.d.ts +2 -0
- package/dist/commands/jules.js +593 -0
- package/dist/commands/process.d.ts +2 -0
- package/dist/commands/process.js +289 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +473 -0
- package/dist/commands/roadmap.js +318 -0
- package/dist/commands/settings.d.ts +2 -0
- package/dist/commands/settings.js +153 -0
- package/dist/commands/strategy.d.ts +2 -0
- package/dist/commands/strategy.js +706 -0
- package/dist/commands/task.js +244 -1
- package/dist/commands/vm-config.d.ts +2 -0
- package/dist/commands/vm-config.js +318 -0
- package/dist/commands/vm.d.ts +2 -0
- package/dist/commands/vm.js +621 -0
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +545 -0
- package/dist/index.js +35 -2
- package/package.json +8 -2
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getConfig } from "./config.js";
|
|
3
|
+
export const ideaCommand = new Command("idea")
|
|
4
|
+
.description("Manage ideas");
|
|
5
|
+
// Helper: Ensure API is configured
|
|
6
|
+
function ensureConfig() {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
if (!config.apiUrl) {
|
|
9
|
+
console.error("Error: API URL not configured. Run: husky config set api-url <url>");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
// husky idea list
|
|
15
|
+
ideaCommand
|
|
16
|
+
.command("list")
|
|
17
|
+
.description("List all ideas")
|
|
18
|
+
.option("--status <status>", "Filter by status (draft, active, archived, converted)")
|
|
19
|
+
.option("--json", "Output as JSON")
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const config = ensureConfig();
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL("/api/ideas", config.apiUrl);
|
|
24
|
+
if (options.status) {
|
|
25
|
+
url.searchParams.set("status", options.status);
|
|
26
|
+
}
|
|
27
|
+
const res = await fetch(url.toString(), {
|
|
28
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new Error(`API error: ${res.status}`);
|
|
32
|
+
}
|
|
33
|
+
const ideas = await res.json();
|
|
34
|
+
if (options.json) {
|
|
35
|
+
console.log(JSON.stringify(ideas, null, 2));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
printIdeas(ideas);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error("Error fetching ideas:", error);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// husky idea create <title>
|
|
47
|
+
ideaCommand
|
|
48
|
+
.command("create <title>")
|
|
49
|
+
.description("Create a new idea")
|
|
50
|
+
.option("-d, --description <description>", "Idea description")
|
|
51
|
+
.option("--category <category>", "Idea category")
|
|
52
|
+
.option("--json", "Output as JSON")
|
|
53
|
+
.action(async (title, options) => {
|
|
54
|
+
const config = ensureConfig();
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(`${config.apiUrl}/api/ideas`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
title,
|
|
64
|
+
description: options.description,
|
|
65
|
+
category: options.category,
|
|
66
|
+
status: "draft",
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
throw new Error(`API error: ${res.status}`);
|
|
71
|
+
}
|
|
72
|
+
const idea = await res.json();
|
|
73
|
+
if (options.json) {
|
|
74
|
+
console.log(JSON.stringify(idea, null, 2));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.log(`✓ Created idea: ${idea.title}`);
|
|
78
|
+
console.log(` ID: ${idea.id}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error("Error creating idea:", error);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// husky idea get <id>
|
|
87
|
+
ideaCommand
|
|
88
|
+
.command("get <id>")
|
|
89
|
+
.description("Get idea details")
|
|
90
|
+
.option("--json", "Output as JSON")
|
|
91
|
+
.action(async (id, options) => {
|
|
92
|
+
const config = ensureConfig();
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch(`${config.apiUrl}/api/ideas/${id}`, {
|
|
95
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
if (res.status === 404) {
|
|
99
|
+
console.error(`Error: Idea ${id} not found`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.error(`Error: API returned ${res.status}`);
|
|
103
|
+
}
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const idea = await res.json();
|
|
107
|
+
if (options.json) {
|
|
108
|
+
console.log(JSON.stringify(idea, null, 2));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
printIdeaDetail(idea);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error("Error fetching idea:", error);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// husky idea update <id>
|
|
120
|
+
ideaCommand
|
|
121
|
+
.command("update <id>")
|
|
122
|
+
.description("Update an idea")
|
|
123
|
+
.option("-t, --title <title>", "New title")
|
|
124
|
+
.option("-d, --description <description>", "New description")
|
|
125
|
+
.option("--status <status>", "New status (draft, active, archived, converted)")
|
|
126
|
+
.option("--category <category>", "New category")
|
|
127
|
+
.option("--json", "Output as JSON")
|
|
128
|
+
.action(async (id, options) => {
|
|
129
|
+
const config = ensureConfig();
|
|
130
|
+
// Build update payload
|
|
131
|
+
const updateData = {};
|
|
132
|
+
if (options.title) {
|
|
133
|
+
updateData.title = options.title;
|
|
134
|
+
}
|
|
135
|
+
if (options.description) {
|
|
136
|
+
updateData.description = options.description;
|
|
137
|
+
}
|
|
138
|
+
if (options.status) {
|
|
139
|
+
const validStatuses = ["draft", "active", "archived", "converted"];
|
|
140
|
+
if (!validStatuses.includes(options.status)) {
|
|
141
|
+
console.error(`Error: Invalid status "${options.status}". Must be one of: ${validStatuses.join(", ")}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
updateData.status = options.status;
|
|
145
|
+
}
|
|
146
|
+
if (options.category) {
|
|
147
|
+
updateData.category = options.category;
|
|
148
|
+
}
|
|
149
|
+
if (Object.keys(updateData).length === 0) {
|
|
150
|
+
console.error("Error: No update options provided. Use -t, -d, --status, or --category");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const res = await fetch(`${config.apiUrl}/api/ideas/${id}`, {
|
|
155
|
+
method: "PATCH",
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify(updateData),
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
if (res.status === 404) {
|
|
164
|
+
console.error(`Error: Idea ${id} not found`);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
168
|
+
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
169
|
+
}
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const idea = await res.json();
|
|
173
|
+
if (options.json) {
|
|
174
|
+
console.log(JSON.stringify(idea, null, 2));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log(`✓ Idea updated successfully`);
|
|
178
|
+
console.log(` Title: ${idea.title}`);
|
|
179
|
+
console.log(` Status: ${idea.status}`);
|
|
180
|
+
if (idea.category) {
|
|
181
|
+
console.log(` Category: ${idea.category}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error("Error updating idea:", error);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// husky idea delete <id>
|
|
191
|
+
ideaCommand
|
|
192
|
+
.command("delete <id>")
|
|
193
|
+
.description("Delete an idea")
|
|
194
|
+
.option("--force", "Skip confirmation")
|
|
195
|
+
.option("--json", "Output as JSON")
|
|
196
|
+
.action(async (id, options) => {
|
|
197
|
+
const config = ensureConfig();
|
|
198
|
+
if (!options.force) {
|
|
199
|
+
console.log("Warning: This will permanently delete the idea.");
|
|
200
|
+
console.log("Use --force to confirm deletion.");
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch(`${config.apiUrl}/api/ideas/${id}`, {
|
|
205
|
+
method: "DELETE",
|
|
206
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
207
|
+
});
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
if (res.status === 404) {
|
|
210
|
+
console.error(`Error: Idea ${id} not found`);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
console.error(`Error: API returned ${res.status}`);
|
|
214
|
+
}
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
if (options.json) {
|
|
218
|
+
console.log(JSON.stringify({ deleted: true, id }, null, 2));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.log(`✓ Idea deleted`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.error("Error deleting idea:", error);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// husky idea convert <id>
|
|
230
|
+
ideaCommand
|
|
231
|
+
.command("convert <id>")
|
|
232
|
+
.description("Convert an idea to a task")
|
|
233
|
+
.option("--priority <priority>", "Task priority (low, medium, high)", "medium")
|
|
234
|
+
.option("--assignee <assignee>", "Assign to agent or user")
|
|
235
|
+
.option("--json", "Output as JSON")
|
|
236
|
+
.action(async (id, options) => {
|
|
237
|
+
const config = ensureConfig();
|
|
238
|
+
// Validate priority
|
|
239
|
+
const validPriorities = ["low", "medium", "high"];
|
|
240
|
+
if (!validPriorities.includes(options.priority)) {
|
|
241
|
+
console.error(`Error: Invalid priority "${options.priority}". Must be one of: ${validPriorities.join(", ")}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const res = await fetch(`${config.apiUrl}/api/ideas/${id}/convert`, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
headers: {
|
|
248
|
+
"Content-Type": "application/json",
|
|
249
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
priority: options.priority,
|
|
253
|
+
assignee: options.assignee,
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
if (!res.ok) {
|
|
257
|
+
if (res.status === 404) {
|
|
258
|
+
console.error(`Error: Idea ${id} not found`);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
262
|
+
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
263
|
+
}
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
const result = await res.json();
|
|
267
|
+
if (options.json) {
|
|
268
|
+
console.log(JSON.stringify(result, null, 2));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
console.log(`✓ Idea converted to task`);
|
|
272
|
+
console.log(` Task ID: ${result.taskId}`);
|
|
273
|
+
console.log(` Title: ${result.title}`);
|
|
274
|
+
console.log(` Priority: ${result.priority}`);
|
|
275
|
+
if (result.assignee) {
|
|
276
|
+
console.log(` Assignee: ${result.assignee}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
console.error("Error converting idea:", error);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
function printIdeas(ideas) {
|
|
286
|
+
if (ideas.length === 0) {
|
|
287
|
+
console.log("\n No ideas found.");
|
|
288
|
+
console.log(" Create one with: husky idea create <title>\n");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
console.log("\n IDEAS");
|
|
292
|
+
console.log(" " + "─".repeat(70));
|
|
293
|
+
console.log(` ${"ID".padEnd(24)} ${"TITLE".padEnd(30)} ${"STATUS".padEnd(12)} CATEGORY`);
|
|
294
|
+
console.log(" " + "─".repeat(70));
|
|
295
|
+
for (const idea of ideas) {
|
|
296
|
+
const statusIcon = getStatusIcon(idea.status);
|
|
297
|
+
const truncatedTitle = idea.title.length > 28 ? idea.title.substring(0, 25) + "..." : idea.title;
|
|
298
|
+
const category = idea.category || "-";
|
|
299
|
+
console.log(` ${idea.id.padEnd(24)} ${truncatedTitle.padEnd(30)} ${statusIcon} ${idea.status.padEnd(10)} ${category}`);
|
|
300
|
+
}
|
|
301
|
+
// Summary by status
|
|
302
|
+
const draftCount = ideas.filter((i) => i.status === "draft").length;
|
|
303
|
+
const activeCount = ideas.filter((i) => i.status === "active").length;
|
|
304
|
+
const archivedCount = ideas.filter((i) => i.status === "archived").length;
|
|
305
|
+
const convertedCount = ideas.filter((i) => i.status === "converted").length;
|
|
306
|
+
console.log(" " + "─".repeat(70));
|
|
307
|
+
console.log(`\n Summary: ${ideas.length} total`);
|
|
308
|
+
console.log(` · Draft: ${draftCount} ○ Active: ${activeCount} ▷ Converted: ${convertedCount} ✗ Archived: ${archivedCount}`);
|
|
309
|
+
console.log("");
|
|
310
|
+
}
|
|
311
|
+
function printIdeaDetail(idea) {
|
|
312
|
+
console.log(`\n Idea: ${idea.title}`);
|
|
313
|
+
console.log(" " + "─".repeat(50));
|
|
314
|
+
console.log(` ID: ${idea.id}`);
|
|
315
|
+
console.log(` Status: ${idea.status}`);
|
|
316
|
+
if (idea.category) {
|
|
317
|
+
console.log(` Category: ${idea.category}`);
|
|
318
|
+
}
|
|
319
|
+
if (idea.description) {
|
|
320
|
+
console.log(` Description:`);
|
|
321
|
+
console.log(` ${idea.description}`);
|
|
322
|
+
}
|
|
323
|
+
console.log(` Created: ${new Date(idea.createdAt).toLocaleString()}`);
|
|
324
|
+
console.log(` Updated: ${new Date(idea.updatedAt).toLocaleString()}`);
|
|
325
|
+
console.log("");
|
|
326
|
+
}
|
|
327
|
+
function getStatusIcon(status) {
|
|
328
|
+
switch (status) {
|
|
329
|
+
case "draft":
|
|
330
|
+
return "·";
|
|
331
|
+
case "active":
|
|
332
|
+
return "○";
|
|
333
|
+
case "converted":
|
|
334
|
+
return "▷";
|
|
335
|
+
case "archived":
|
|
336
|
+
return "✗";
|
|
337
|
+
default:
|
|
338
|
+
return " ";
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInteractiveMode(): Promise<void>;
|