@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.
- package/README.md +144 -0
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.js +279 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +73 -0
- package/dist/commands/roadmap.d.ts +2 -0
- package/dist/commands/roadmap.js +325 -0
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.js +635 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -0
- package/dist/lib/streaming.d.ts +44 -0
- package/dist/lib/streaming.js +157 -0
- package/package.json +30 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getConfig } from "./config.js";
|
|
3
|
+
export const roadmapCommand = new Command("roadmap")
|
|
4
|
+
.description("Manage roadmaps");
|
|
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 roadmap list
|
|
15
|
+
roadmapCommand
|
|
16
|
+
.command("list")
|
|
17
|
+
.description("List all roadmaps")
|
|
18
|
+
.option("--type <type>", "Filter by type (global, project)")
|
|
19
|
+
.option("--project <projectId>", "Filter by project ID")
|
|
20
|
+
.option("--json", "Output as JSON")
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
const config = ensureConfig();
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL("/api/roadmaps", config.apiUrl);
|
|
25
|
+
if (options.type) {
|
|
26
|
+
url.searchParams.set("type", options.type);
|
|
27
|
+
}
|
|
28
|
+
if (options.project) {
|
|
29
|
+
url.searchParams.set("projectId", options.project);
|
|
30
|
+
}
|
|
31
|
+
const res = await fetch(url.toString(), {
|
|
32
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
throw new Error(`API error: ${res.status}`);
|
|
36
|
+
}
|
|
37
|
+
const roadmaps = await res.json();
|
|
38
|
+
if (options.json) {
|
|
39
|
+
console.log(JSON.stringify(roadmaps, null, 2));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
printRoadmaps(roadmaps);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error("Error fetching roadmaps:", error);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// husky roadmap get <id>
|
|
51
|
+
roadmapCommand
|
|
52
|
+
.command("get <id>")
|
|
53
|
+
.description("Get roadmap details")
|
|
54
|
+
.option("--json", "Output as JSON")
|
|
55
|
+
.action(async (id, options) => {
|
|
56
|
+
const config = ensureConfig();
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${id}`, {
|
|
59
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
if (res.status === 404) {
|
|
63
|
+
console.error(`Error: Roadmap ${id} not found`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.error(`Error: API returned ${res.status}`);
|
|
67
|
+
}
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const roadmap = await res.json();
|
|
71
|
+
if (options.json) {
|
|
72
|
+
console.log(JSON.stringify(roadmap, null, 2));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
printRoadmapDetail(roadmap);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error("Error fetching roadmap:", error);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// husky roadmap create <name>
|
|
84
|
+
roadmapCommand
|
|
85
|
+
.command("create <name>")
|
|
86
|
+
.description("Create a new roadmap")
|
|
87
|
+
.option("--type <type>", "Roadmap type (global, project)", "global")
|
|
88
|
+
.option("--project <projectId>", "Project ID (for project type)")
|
|
89
|
+
.option("--vision <vision>", "Product vision")
|
|
90
|
+
.option("--audience <audience>", "Primary target audience")
|
|
91
|
+
.action(async (name, options) => {
|
|
92
|
+
const config = ensureConfig();
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
name,
|
|
102
|
+
type: options.type,
|
|
103
|
+
projectId: options.project,
|
|
104
|
+
vision: options.vision,
|
|
105
|
+
targetAudience: {
|
|
106
|
+
primary: options.audience || "All users",
|
|
107
|
+
secondary: [],
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
throw new Error(`API error: ${res.status}`);
|
|
113
|
+
}
|
|
114
|
+
const roadmap = await res.json();
|
|
115
|
+
console.log(`✓ Created roadmap: ${roadmap.name}`);
|
|
116
|
+
console.log(` ID: ${roadmap.id}`);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error("Error creating roadmap:", error);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// husky roadmap add-phase <roadmapId> <name>
|
|
124
|
+
roadmapCommand
|
|
125
|
+
.command("add-phase <roadmapId> <name>")
|
|
126
|
+
.description("Add a phase to a roadmap")
|
|
127
|
+
.option("--description <description>", "Phase description")
|
|
128
|
+
.action(async (roadmapId, name, options) => {
|
|
129
|
+
const config = ensureConfig();
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}/phases`, {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: {
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
name,
|
|
139
|
+
description: options.description || "",
|
|
140
|
+
status: "planned",
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
throw new Error(`API error: ${res.status}`);
|
|
145
|
+
}
|
|
146
|
+
const roadmap = await res.json();
|
|
147
|
+
console.log(`✓ Added phase "${name}" to roadmap`);
|
|
148
|
+
console.log(` Total phases: ${roadmap.phases.length}`);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error("Error adding phase:", error);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// husky roadmap add-feature <roadmapId> <phaseId> <title>
|
|
156
|
+
roadmapCommand
|
|
157
|
+
.command("add-feature <roadmapId> <phaseId> <title>")
|
|
158
|
+
.description("Add a feature to a roadmap phase")
|
|
159
|
+
.option("--description <description>", "Feature description")
|
|
160
|
+
.option("--priority <priority>", "Priority (must, should, could, wont)", "should")
|
|
161
|
+
.option("--complexity <complexity>", "Complexity (low, medium, high)", "medium")
|
|
162
|
+
.option("--impact <impact>", "Impact (low, medium, high)", "medium")
|
|
163
|
+
.action(async (roadmapId, phaseId, title, options) => {
|
|
164
|
+
const config = ensureConfig();
|
|
165
|
+
try {
|
|
166
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}/features`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: {
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
title,
|
|
174
|
+
description: options.description || "",
|
|
175
|
+
rationale: "",
|
|
176
|
+
phaseId,
|
|
177
|
+
priority: options.priority,
|
|
178
|
+
complexity: options.complexity,
|
|
179
|
+
impact: options.impact,
|
|
180
|
+
status: "idea",
|
|
181
|
+
acceptanceCriteria: [],
|
|
182
|
+
userStories: [],
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
if (!res.ok) {
|
|
186
|
+
throw new Error(`API error: ${res.status}`);
|
|
187
|
+
}
|
|
188
|
+
const roadmap = await res.json();
|
|
189
|
+
console.log(`✓ Added feature "${title}" to phase`);
|
|
190
|
+
console.log(` Total features: ${roadmap.features.length}`);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
console.error("Error adding feature:", error);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
// husky roadmap generate <roadmapId>
|
|
198
|
+
roadmapCommand
|
|
199
|
+
.command("generate <roadmapId>")
|
|
200
|
+
.description("Generate roadmap phases and features using AI")
|
|
201
|
+
.option("--context <context>", "Additional context for AI generation")
|
|
202
|
+
.option("--project <projectId>", "Project ID for context")
|
|
203
|
+
.action(async (roadmapId, options) => {
|
|
204
|
+
const config = ensureConfig();
|
|
205
|
+
console.log("Generating roadmap with AI...");
|
|
206
|
+
console.log("This may take a moment...\n");
|
|
207
|
+
try {
|
|
208
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/generate`, {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: {
|
|
211
|
+
"Content-Type": "application/json",
|
|
212
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
213
|
+
},
|
|
214
|
+
body: JSON.stringify({
|
|
215
|
+
roadmapId,
|
|
216
|
+
projectId: options.project,
|
|
217
|
+
additionalContext: options.context,
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
const error = await res.json();
|
|
222
|
+
throw new Error(error.error || `API error: ${res.status}`);
|
|
223
|
+
}
|
|
224
|
+
const result = await res.json();
|
|
225
|
+
console.log(`✓ Roadmap generated successfully!`);
|
|
226
|
+
console.log(` Phases created: ${result.phasesGenerated}`);
|
|
227
|
+
console.log(` Features created: ${result.featuresGenerated}`);
|
|
228
|
+
console.log("");
|
|
229
|
+
if (result.roadmap) {
|
|
230
|
+
printRoadmapDetail(result.roadmap);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
console.error("Error generating roadmap:", error);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// husky roadmap delete <id>
|
|
239
|
+
roadmapCommand
|
|
240
|
+
.command("delete <id>")
|
|
241
|
+
.description("Delete a roadmap")
|
|
242
|
+
.option("--force", "Skip confirmation")
|
|
243
|
+
.action(async (id, options) => {
|
|
244
|
+
const config = ensureConfig();
|
|
245
|
+
if (!options.force) {
|
|
246
|
+
console.log("Warning: This will permanently delete the roadmap and all its phases/features.");
|
|
247
|
+
console.log("Use --force to confirm deletion.");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${id}`, {
|
|
252
|
+
method: "DELETE",
|
|
253
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
254
|
+
});
|
|
255
|
+
if (!res.ok) {
|
|
256
|
+
throw new Error(`API error: ${res.status}`);
|
|
257
|
+
}
|
|
258
|
+
console.log(`✓ Roadmap deleted`);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("Error deleting roadmap:", error);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
function printRoadmaps(roadmaps) {
|
|
266
|
+
if (roadmaps.length === 0) {
|
|
267
|
+
console.log("\n No roadmaps found.");
|
|
268
|
+
console.log(" Create one with: husky roadmap create <name>\n");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
console.log("\n ROADMAPS");
|
|
272
|
+
console.log(" " + "─".repeat(60));
|
|
273
|
+
for (const roadmap of roadmaps) {
|
|
274
|
+
const typeLabel = roadmap.type === "global" ? "[Global]" : "[Project]";
|
|
275
|
+
const phaseCount = roadmap.phases?.length || 0;
|
|
276
|
+
const featureCount = roadmap.features?.length || 0;
|
|
277
|
+
console.log(` ${typeLabel.padEnd(10)} ${roadmap.name.padEnd(30)} ${phaseCount} phases, ${featureCount} features`);
|
|
278
|
+
console.log(` ID: ${roadmap.id}`);
|
|
279
|
+
}
|
|
280
|
+
console.log("");
|
|
281
|
+
}
|
|
282
|
+
function printRoadmapDetail(roadmap) {
|
|
283
|
+
console.log(`\n Roadmap: ${roadmap.name}`);
|
|
284
|
+
console.log(" " + "─".repeat(60));
|
|
285
|
+
console.log(` ID: ${roadmap.id}`);
|
|
286
|
+
console.log(` Type: ${roadmap.type}`);
|
|
287
|
+
if (roadmap.vision) {
|
|
288
|
+
console.log(` Vision: ${roadmap.vision}`);
|
|
289
|
+
}
|
|
290
|
+
if (roadmap.projectId) {
|
|
291
|
+
console.log(` Project: ${roadmap.projectId}`);
|
|
292
|
+
}
|
|
293
|
+
const phases = roadmap.phases || [];
|
|
294
|
+
const features = roadmap.features || [];
|
|
295
|
+
console.log(`\n Phases: ${phases.length}`);
|
|
296
|
+
console.log(" " + "─".repeat(40));
|
|
297
|
+
for (const phase of phases.sort((a, b) => a.order - b.order)) {
|
|
298
|
+
const phaseFeatures = features.filter((f) => f.phaseId === phase.id);
|
|
299
|
+
const statusIcon = phase.status === "completed" ? "✓" : phase.status === "in_progress" ? "▶" : "○";
|
|
300
|
+
console.log(`\n ${statusIcon} ${phase.name} (${phaseFeatures.length} features)`);
|
|
301
|
+
if (phase.description) {
|
|
302
|
+
console.log(` ${phase.description}`);
|
|
303
|
+
}
|
|
304
|
+
for (const feature of phaseFeatures) {
|
|
305
|
+
const priorityIcon = feature.priority === "must"
|
|
306
|
+
? "🔴"
|
|
307
|
+
: feature.priority === "should"
|
|
308
|
+
? "🟠"
|
|
309
|
+
: feature.priority === "could"
|
|
310
|
+
? "🟡"
|
|
311
|
+
: "⚪";
|
|
312
|
+
console.log(` ${priorityIcon} ${feature.title} [${feature.priority}/${feature.complexity}]`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Summary
|
|
316
|
+
const mustCount = features.filter((f) => f.priority === "must").length;
|
|
317
|
+
const shouldCount = features.filter((f) => f.priority === "should").length;
|
|
318
|
+
const couldCount = features.filter((f) => f.priority === "could").length;
|
|
319
|
+
console.log(`\n Summary:`);
|
|
320
|
+
console.log(` Total Features: ${features.length}`);
|
|
321
|
+
console.log(` Must Have: ${mustCount}`);
|
|
322
|
+
console.log(` Should Have: ${shouldCount}`);
|
|
323
|
+
console.log(` Could Have: ${couldCount}`);
|
|
324
|
+
console.log("");
|
|
325
|
+
}
|