@simonfestl/husky-cli 0.5.0 → 0.5.2
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 +21 -25
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +10 -3
- package/dist/commands/idea.js +9 -7
- package/dist/commands/interactive/changelog.d.ts +1 -0
- package/dist/commands/interactive/changelog.js +398 -0
- package/dist/commands/interactive/departments.d.ts +1 -0
- package/dist/commands/interactive/departments.js +242 -0
- package/dist/commands/interactive/ideas.d.ts +1 -0
- package/dist/commands/interactive/ideas.js +311 -0
- package/dist/commands/interactive/jules-sessions.d.ts +1 -0
- package/dist/commands/interactive/jules-sessions.js +460 -0
- package/dist/commands/interactive/processes.d.ts +1 -0
- package/dist/commands/interactive/processes.js +271 -0
- package/dist/commands/interactive/projects.d.ts +1 -0
- package/dist/commands/interactive/projects.js +297 -0
- package/dist/commands/interactive/roadmaps.d.ts +1 -0
- package/dist/commands/interactive/roadmaps.js +650 -0
- package/dist/commands/interactive/strategy.d.ts +1 -0
- package/dist/commands/interactive/strategy.js +790 -0
- package/dist/commands/interactive/tasks.d.ts +1 -0
- package/dist/commands/interactive/tasks.js +415 -0
- package/dist/commands/interactive/utils.d.ts +15 -0
- package/dist/commands/interactive/utils.js +54 -0
- package/dist/commands/interactive/vm-sessions.d.ts +1 -0
- package/dist/commands/interactive/vm-sessions.js +319 -0
- package/dist/commands/interactive/workflows.d.ts +1 -0
- package/dist/commands/interactive/workflows.js +442 -0
- package/dist/commands/interactive.js +150 -1135
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
2
|
+
import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
|
|
3
|
+
export async function roadmapsMenu() {
|
|
4
|
+
const config = ensureConfig();
|
|
5
|
+
const menuItems = [
|
|
6
|
+
{ name: "List all roadmaps", value: "list" },
|
|
7
|
+
{ name: "View roadmap details", value: "view" },
|
|
8
|
+
{ name: "Create new roadmap", value: "create" },
|
|
9
|
+
{ name: "Update roadmap", value: "update" },
|
|
10
|
+
{ name: "Add phase", value: "add-phase" },
|
|
11
|
+
{ name: "Add feature", value: "add-feature" },
|
|
12
|
+
{ name: "Update feature", value: "update-feature" },
|
|
13
|
+
{ name: "Convert feature to task", value: "convert" },
|
|
14
|
+
{ name: "Delete feature", value: "delete-feature" },
|
|
15
|
+
{ name: "Generate with AI", value: "generate" },
|
|
16
|
+
{ name: "Delete roadmap", value: "delete" },
|
|
17
|
+
{ name: "Back to main menu", value: "back" },
|
|
18
|
+
];
|
|
19
|
+
const choice = await select({
|
|
20
|
+
message: "Roadmaps:",
|
|
21
|
+
choices: menuItems,
|
|
22
|
+
});
|
|
23
|
+
switch (choice) {
|
|
24
|
+
case "list":
|
|
25
|
+
await listRoadmaps(config);
|
|
26
|
+
break;
|
|
27
|
+
case "view":
|
|
28
|
+
await viewRoadmap(config);
|
|
29
|
+
break;
|
|
30
|
+
case "create":
|
|
31
|
+
await createRoadmap(config);
|
|
32
|
+
break;
|
|
33
|
+
case "update":
|
|
34
|
+
await updateRoadmap(config);
|
|
35
|
+
break;
|
|
36
|
+
case "add-phase":
|
|
37
|
+
await addPhase(config);
|
|
38
|
+
break;
|
|
39
|
+
case "add-feature":
|
|
40
|
+
await addFeature(config);
|
|
41
|
+
break;
|
|
42
|
+
case "update-feature":
|
|
43
|
+
await updateFeature(config);
|
|
44
|
+
break;
|
|
45
|
+
case "convert":
|
|
46
|
+
await convertFeature(config);
|
|
47
|
+
break;
|
|
48
|
+
case "delete-feature":
|
|
49
|
+
await deleteFeature(config);
|
|
50
|
+
break;
|
|
51
|
+
case "generate":
|
|
52
|
+
await generateRoadmap(config);
|
|
53
|
+
break;
|
|
54
|
+
case "delete":
|
|
55
|
+
await deleteRoadmap(config);
|
|
56
|
+
break;
|
|
57
|
+
case "back":
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function fetchRoadmaps(config) {
|
|
62
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps`, {
|
|
63
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok)
|
|
66
|
+
throw new Error(`API returned ${res.status}`);
|
|
67
|
+
return res.json();
|
|
68
|
+
}
|
|
69
|
+
async function selectRoadmap(config, message) {
|
|
70
|
+
const roadmaps = await fetchRoadmaps(config);
|
|
71
|
+
if (roadmaps.length === 0) {
|
|
72
|
+
console.log("\n No roadmaps found.\n");
|
|
73
|
+
await pressEnterToContinue();
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const choices = roadmaps.map((r) => ({
|
|
77
|
+
name: `[${r.type}] ${truncate(r.name, 40)} (${r.phases?.length || 0} phases)`,
|
|
78
|
+
value: r.id,
|
|
79
|
+
}));
|
|
80
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
81
|
+
const roadmapId = await select({ message, choices });
|
|
82
|
+
if (roadmapId === "__cancel__")
|
|
83
|
+
return null;
|
|
84
|
+
// Fetch full roadmap with phases/features
|
|
85
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}`, {
|
|
86
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok)
|
|
89
|
+
return null;
|
|
90
|
+
return res.json();
|
|
91
|
+
}
|
|
92
|
+
async function listRoadmaps(config) {
|
|
93
|
+
try {
|
|
94
|
+
const roadmaps = await fetchRoadmaps(config);
|
|
95
|
+
console.log("\n ROADMAPS");
|
|
96
|
+
console.log(" " + "-".repeat(70));
|
|
97
|
+
if (roadmaps.length === 0) {
|
|
98
|
+
console.log(" No roadmaps found.");
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
for (const rm of roadmaps) {
|
|
102
|
+
const phaseCount = rm.phases?.length || 0;
|
|
103
|
+
const featureCount = rm.features?.length || 0;
|
|
104
|
+
console.log(` [${rm.type}] ${truncate(rm.name, 40)}`);
|
|
105
|
+
console.log(` ID: ${rm.id}`);
|
|
106
|
+
console.log(` Phases: ${phaseCount}, Features: ${featureCount}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
console.log("");
|
|
110
|
+
await pressEnterToContinue();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error("\n Error fetching roadmaps:", error);
|
|
114
|
+
await pressEnterToContinue();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function viewRoadmap(config) {
|
|
118
|
+
try {
|
|
119
|
+
const roadmap = await selectRoadmap(config, "Select roadmap to view:");
|
|
120
|
+
if (!roadmap)
|
|
121
|
+
return;
|
|
122
|
+
console.log(`\n Roadmap: ${roadmap.name}`);
|
|
123
|
+
console.log(" " + "-".repeat(60));
|
|
124
|
+
console.log(` ID: ${roadmap.id}`);
|
|
125
|
+
console.log(` Type: ${roadmap.type}`);
|
|
126
|
+
if (roadmap.vision) {
|
|
127
|
+
console.log(` Vision: ${truncate(roadmap.vision, 60)}`);
|
|
128
|
+
}
|
|
129
|
+
const phases = (roadmap.phases || []).sort((a, b) => a.order - b.order);
|
|
130
|
+
const features = roadmap.features || [];
|
|
131
|
+
console.log(`\n Phases (${phases.length}):`);
|
|
132
|
+
console.log(" " + "-".repeat(40));
|
|
133
|
+
for (const phase of phases) {
|
|
134
|
+
const phaseFeatures = features.filter((f) => f.phaseId === phase.id);
|
|
135
|
+
const statusIcon = phase.status === "completed" ? "[OK]" : phase.status === "in_progress" ? "[>>]" : "[..]";
|
|
136
|
+
console.log(`\n ${statusIcon} ${phase.name} (${phaseFeatures.length} features)`);
|
|
137
|
+
if (phase.description) {
|
|
138
|
+
console.log(` ${truncate(phase.description, 50)}`);
|
|
139
|
+
}
|
|
140
|
+
for (const feature of phaseFeatures) {
|
|
141
|
+
const pIcon = feature.priority === "must" ? "[M]" : feature.priority === "should" ? "[S]" : "[C]";
|
|
142
|
+
console.log(` ${pIcon} ${truncate(feature.title, 45)} [${feature.status}]`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Summary
|
|
146
|
+
const mustCount = features.filter((f) => f.priority === "must").length;
|
|
147
|
+
const shouldCount = features.filter((f) => f.priority === "should").length;
|
|
148
|
+
console.log(`\n Summary: ${features.length} features (${mustCount} must, ${shouldCount} should)`);
|
|
149
|
+
console.log("");
|
|
150
|
+
await pressEnterToContinue();
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error("\n Error viewing roadmap:", error);
|
|
154
|
+
await pressEnterToContinue();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function createRoadmap(config) {
|
|
158
|
+
try {
|
|
159
|
+
const name = await input({
|
|
160
|
+
message: "Roadmap name:",
|
|
161
|
+
validate: (v) => (v.length > 0 ? true : "Name required"),
|
|
162
|
+
});
|
|
163
|
+
const type = await select({
|
|
164
|
+
message: "Type:",
|
|
165
|
+
choices: [
|
|
166
|
+
{ name: "Global", value: "global" },
|
|
167
|
+
{ name: "Project-specific", value: "project" },
|
|
168
|
+
],
|
|
169
|
+
default: "global",
|
|
170
|
+
});
|
|
171
|
+
const vision = await input({
|
|
172
|
+
message: "Product vision (optional):",
|
|
173
|
+
});
|
|
174
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps`, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: {
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
179
|
+
},
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
name,
|
|
182
|
+
type,
|
|
183
|
+
vision: vision || undefined,
|
|
184
|
+
targetAudience: { primary: "All users", secondary: [] },
|
|
185
|
+
}),
|
|
186
|
+
});
|
|
187
|
+
if (!res.ok) {
|
|
188
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
189
|
+
await pressEnterToContinue();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const roadmap = await res.json();
|
|
193
|
+
console.log(`\n ✓ Roadmap created!`);
|
|
194
|
+
console.log(` ID: ${roadmap.id}`);
|
|
195
|
+
console.log(` Name: ${roadmap.name}\n`);
|
|
196
|
+
await pressEnterToContinue();
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.error("\n Error creating roadmap:", error);
|
|
200
|
+
await pressEnterToContinue();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function updateRoadmap(config) {
|
|
204
|
+
try {
|
|
205
|
+
const roadmap = await selectRoadmap(config, "Select roadmap to update:");
|
|
206
|
+
if (!roadmap)
|
|
207
|
+
return;
|
|
208
|
+
const updateChoices = [
|
|
209
|
+
{ name: "Name", value: "name" },
|
|
210
|
+
{ name: "Vision", value: "vision" },
|
|
211
|
+
{ name: "Type", value: "type" },
|
|
212
|
+
{ name: "Cancel", value: "cancel" },
|
|
213
|
+
];
|
|
214
|
+
const field = await select({ message: "What to update?", choices: updateChoices });
|
|
215
|
+
if (field === "cancel")
|
|
216
|
+
return;
|
|
217
|
+
let updateData = {};
|
|
218
|
+
switch (field) {
|
|
219
|
+
case "name":
|
|
220
|
+
updateData.name = await input({
|
|
221
|
+
message: "New name:",
|
|
222
|
+
default: roadmap.name,
|
|
223
|
+
validate: (v) => (v.length > 0 ? true : "Name required"),
|
|
224
|
+
});
|
|
225
|
+
break;
|
|
226
|
+
case "vision":
|
|
227
|
+
updateData.vision = await input({
|
|
228
|
+
message: "New vision:",
|
|
229
|
+
default: roadmap.vision || "",
|
|
230
|
+
});
|
|
231
|
+
break;
|
|
232
|
+
case "type":
|
|
233
|
+
updateData.type = await select({
|
|
234
|
+
message: "New type:",
|
|
235
|
+
choices: [
|
|
236
|
+
{ name: "Global", value: "global" },
|
|
237
|
+
{ name: "Project-specific", value: "project" },
|
|
238
|
+
],
|
|
239
|
+
default: roadmap.type,
|
|
240
|
+
});
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmap.id}`, {
|
|
244
|
+
method: "PATCH",
|
|
245
|
+
headers: {
|
|
246
|
+
"Content-Type": "application/json",
|
|
247
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify(updateData),
|
|
250
|
+
});
|
|
251
|
+
if (!res.ok) {
|
|
252
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
253
|
+
await pressEnterToContinue();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
console.log(`\n ✓ Roadmap updated!\n`);
|
|
257
|
+
await pressEnterToContinue();
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error("\n Error updating roadmap:", error);
|
|
261
|
+
await pressEnterToContinue();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function addPhase(config) {
|
|
265
|
+
try {
|
|
266
|
+
const roadmap = await selectRoadmap(config, "Select roadmap:");
|
|
267
|
+
if (!roadmap)
|
|
268
|
+
return;
|
|
269
|
+
const name = await input({
|
|
270
|
+
message: "Phase name:",
|
|
271
|
+
validate: (v) => (v.length > 0 ? true : "Name required"),
|
|
272
|
+
});
|
|
273
|
+
const description = await input({
|
|
274
|
+
message: "Description (optional):",
|
|
275
|
+
});
|
|
276
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmap.id}/phases`, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: {
|
|
279
|
+
"Content-Type": "application/json",
|
|
280
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
281
|
+
},
|
|
282
|
+
body: JSON.stringify({
|
|
283
|
+
name,
|
|
284
|
+
description: description || "",
|
|
285
|
+
status: "planned",
|
|
286
|
+
}),
|
|
287
|
+
});
|
|
288
|
+
if (!res.ok) {
|
|
289
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
290
|
+
await pressEnterToContinue();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
console.log(`\n ✓ Phase added!\n`);
|
|
294
|
+
await pressEnterToContinue();
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
console.error("\n Error adding phase:", error);
|
|
298
|
+
await pressEnterToContinue();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function addFeature(config) {
|
|
302
|
+
try {
|
|
303
|
+
const roadmap = await selectRoadmap(config, "Select roadmap:");
|
|
304
|
+
if (!roadmap)
|
|
305
|
+
return;
|
|
306
|
+
const phases = roadmap.phases || [];
|
|
307
|
+
if (phases.length === 0) {
|
|
308
|
+
console.log("\n No phases in this roadmap. Add a phase first.\n");
|
|
309
|
+
await pressEnterToContinue();
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
// Select phase
|
|
313
|
+
const phaseChoices = phases.map((p) => ({
|
|
314
|
+
name: `${p.name} [${p.status}]`,
|
|
315
|
+
value: p.id,
|
|
316
|
+
}));
|
|
317
|
+
phaseChoices.push({ name: "Cancel", value: "__cancel__" });
|
|
318
|
+
const phaseId = await select({ message: "Select phase:", choices: phaseChoices });
|
|
319
|
+
if (phaseId === "__cancel__")
|
|
320
|
+
return;
|
|
321
|
+
const title = await input({
|
|
322
|
+
message: "Feature title:",
|
|
323
|
+
validate: (v) => (v.length > 0 ? true : "Title required"),
|
|
324
|
+
});
|
|
325
|
+
const description = await input({
|
|
326
|
+
message: "Description (optional):",
|
|
327
|
+
});
|
|
328
|
+
const priority = await select({
|
|
329
|
+
message: "Priority:",
|
|
330
|
+
choices: [
|
|
331
|
+
{ name: "Must have", value: "must" },
|
|
332
|
+
{ name: "Should have", value: "should" },
|
|
333
|
+
{ name: "Could have", value: "could" },
|
|
334
|
+
{ name: "Won't have", value: "wont" },
|
|
335
|
+
],
|
|
336
|
+
default: "should",
|
|
337
|
+
});
|
|
338
|
+
const complexity = await select({
|
|
339
|
+
message: "Complexity:",
|
|
340
|
+
choices: [
|
|
341
|
+
{ name: "Low", value: "low" },
|
|
342
|
+
{ name: "Medium", value: "medium" },
|
|
343
|
+
{ name: "High", value: "high" },
|
|
344
|
+
],
|
|
345
|
+
default: "medium",
|
|
346
|
+
});
|
|
347
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmap.id}/features`, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: {
|
|
350
|
+
"Content-Type": "application/json",
|
|
351
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify({
|
|
354
|
+
title,
|
|
355
|
+
description: description || "",
|
|
356
|
+
rationale: "",
|
|
357
|
+
phaseId,
|
|
358
|
+
priority,
|
|
359
|
+
complexity,
|
|
360
|
+
impact: "medium",
|
|
361
|
+
status: "idea",
|
|
362
|
+
acceptanceCriteria: [],
|
|
363
|
+
userStories: [],
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
368
|
+
await pressEnterToContinue();
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
console.log(`\n ✓ Feature added!\n`);
|
|
372
|
+
await pressEnterToContinue();
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
console.error("\n Error adding feature:", error);
|
|
376
|
+
await pressEnterToContinue();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function updateFeature(config) {
|
|
380
|
+
try {
|
|
381
|
+
const roadmap = await selectRoadmap(config, "Select roadmap:");
|
|
382
|
+
if (!roadmap)
|
|
383
|
+
return;
|
|
384
|
+
const features = roadmap.features || [];
|
|
385
|
+
if (features.length === 0) {
|
|
386
|
+
console.log("\n No features in this roadmap.\n");
|
|
387
|
+
await pressEnterToContinue();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
// Select feature
|
|
391
|
+
const featureChoices = features.map((f) => ({
|
|
392
|
+
name: `[${f.priority}] ${truncate(f.title, 40)} [${f.status}]`,
|
|
393
|
+
value: f.id,
|
|
394
|
+
}));
|
|
395
|
+
featureChoices.push({ name: "Cancel", value: "__cancel__" });
|
|
396
|
+
const featureId = await select({ message: "Select feature:", choices: featureChoices });
|
|
397
|
+
if (featureId === "__cancel__")
|
|
398
|
+
return;
|
|
399
|
+
const feature = features.find((f) => f.id === featureId);
|
|
400
|
+
if (!feature)
|
|
401
|
+
return;
|
|
402
|
+
const updateChoices = [
|
|
403
|
+
{ name: "Status", value: "status" },
|
|
404
|
+
{ name: "Priority", value: "priority" },
|
|
405
|
+
{ name: "Title", value: "title" },
|
|
406
|
+
{ name: "Description", value: "description" },
|
|
407
|
+
{ name: "Cancel", value: "cancel" },
|
|
408
|
+
];
|
|
409
|
+
const field = await select({ message: "What to update?", choices: updateChoices });
|
|
410
|
+
if (field === "cancel")
|
|
411
|
+
return;
|
|
412
|
+
let updateData = {};
|
|
413
|
+
switch (field) {
|
|
414
|
+
case "status":
|
|
415
|
+
updateData.status = await select({
|
|
416
|
+
message: "New status:",
|
|
417
|
+
choices: [
|
|
418
|
+
{ name: "Idea", value: "idea" },
|
|
419
|
+
{ name: "Planned", value: "planned" },
|
|
420
|
+
{ name: "In Progress", value: "in_progress" },
|
|
421
|
+
{ name: "Done", value: "done" },
|
|
422
|
+
],
|
|
423
|
+
default: feature.status,
|
|
424
|
+
});
|
|
425
|
+
break;
|
|
426
|
+
case "priority":
|
|
427
|
+
updateData.priority = await select({
|
|
428
|
+
message: "New priority:",
|
|
429
|
+
choices: [
|
|
430
|
+
{ name: "Must have", value: "must" },
|
|
431
|
+
{ name: "Should have", value: "should" },
|
|
432
|
+
{ name: "Could have", value: "could" },
|
|
433
|
+
{ name: "Won't have", value: "wont" },
|
|
434
|
+
],
|
|
435
|
+
default: feature.priority,
|
|
436
|
+
});
|
|
437
|
+
break;
|
|
438
|
+
case "title":
|
|
439
|
+
updateData.title = await input({
|
|
440
|
+
message: "New title:",
|
|
441
|
+
default: feature.title,
|
|
442
|
+
validate: (v) => (v.length > 0 ? true : "Title required"),
|
|
443
|
+
});
|
|
444
|
+
break;
|
|
445
|
+
case "description":
|
|
446
|
+
updateData.description = await input({
|
|
447
|
+
message: "New description:",
|
|
448
|
+
default: feature.description || "",
|
|
449
|
+
});
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmap.id}/features/${featureId}`, {
|
|
453
|
+
method: "PATCH",
|
|
454
|
+
headers: {
|
|
455
|
+
"Content-Type": "application/json",
|
|
456
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
457
|
+
},
|
|
458
|
+
body: JSON.stringify(updateData),
|
|
459
|
+
});
|
|
460
|
+
if (!res.ok) {
|
|
461
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
462
|
+
await pressEnterToContinue();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
console.log(`\n ✓ Feature updated!\n`);
|
|
466
|
+
await pressEnterToContinue();
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
console.error("\n Error updating feature:", error);
|
|
470
|
+
await pressEnterToContinue();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function convertFeature(config) {
|
|
474
|
+
try {
|
|
475
|
+
const roadmap = await selectRoadmap(config, "Select roadmap:");
|
|
476
|
+
if (!roadmap)
|
|
477
|
+
return;
|
|
478
|
+
const features = roadmap.features || [];
|
|
479
|
+
if (features.length === 0) {
|
|
480
|
+
console.log("\n No features in this roadmap.\n");
|
|
481
|
+
await pressEnterToContinue();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
// Select feature
|
|
485
|
+
const featureChoices = features.map((f) => ({
|
|
486
|
+
name: `[${f.priority}] ${truncate(f.title, 40)}`,
|
|
487
|
+
value: f.id,
|
|
488
|
+
}));
|
|
489
|
+
featureChoices.push({ name: "Cancel", value: "__cancel__" });
|
|
490
|
+
const featureId = await select({ message: "Select feature to convert:", choices: featureChoices });
|
|
491
|
+
if (featureId === "__cancel__")
|
|
492
|
+
return;
|
|
493
|
+
const priority = await select({
|
|
494
|
+
message: "Task priority:",
|
|
495
|
+
choices: [
|
|
496
|
+
{ name: "Low", value: "low" },
|
|
497
|
+
{ name: "Medium", value: "medium" },
|
|
498
|
+
{ name: "High", value: "high" },
|
|
499
|
+
],
|
|
500
|
+
default: "medium",
|
|
501
|
+
});
|
|
502
|
+
const assignee = await select({
|
|
503
|
+
message: "Assign to:",
|
|
504
|
+
choices: [
|
|
505
|
+
{ name: "Human", value: "human" },
|
|
506
|
+
{ name: "LLM Agent", value: "llm" },
|
|
507
|
+
{ name: "Unassigned", value: "unassigned" },
|
|
508
|
+
],
|
|
509
|
+
default: "unassigned",
|
|
510
|
+
});
|
|
511
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmap.id}/features/${featureId}/convert`, {
|
|
512
|
+
method: "POST",
|
|
513
|
+
headers: {
|
|
514
|
+
"Content-Type": "application/json",
|
|
515
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
516
|
+
},
|
|
517
|
+
body: JSON.stringify({ priority, assignee }),
|
|
518
|
+
});
|
|
519
|
+
if (!res.ok) {
|
|
520
|
+
const error = await res.json().catch(() => ({}));
|
|
521
|
+
console.error(`\n Error: ${error.error || `API returned ${res.status}`}\n`);
|
|
522
|
+
await pressEnterToContinue();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const result = await res.json();
|
|
526
|
+
console.log(`\n ✓ Feature converted to task!`);
|
|
527
|
+
console.log(` Task ID: ${result.task?.id}`);
|
|
528
|
+
console.log(` Title: ${result.task?.title}\n`);
|
|
529
|
+
await pressEnterToContinue();
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
console.error("\n Error converting feature:", error);
|
|
533
|
+
await pressEnterToContinue();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async function deleteFeature(config) {
|
|
537
|
+
try {
|
|
538
|
+
const roadmap = await selectRoadmap(config, "Select roadmap:");
|
|
539
|
+
if (!roadmap)
|
|
540
|
+
return;
|
|
541
|
+
const features = roadmap.features || [];
|
|
542
|
+
if (features.length === 0) {
|
|
543
|
+
console.log("\n No features in this roadmap.\n");
|
|
544
|
+
await pressEnterToContinue();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
// Select feature
|
|
548
|
+
const featureChoices = features.map((f) => ({
|
|
549
|
+
name: `[${f.priority}] ${truncate(f.title, 40)}`,
|
|
550
|
+
value: f.id,
|
|
551
|
+
}));
|
|
552
|
+
featureChoices.push({ name: "Cancel", value: "__cancel__" });
|
|
553
|
+
const featureId = await select({ message: "Select feature to delete:", choices: featureChoices });
|
|
554
|
+
if (featureId === "__cancel__")
|
|
555
|
+
return;
|
|
556
|
+
const feature = features.find((f) => f.id === featureId);
|
|
557
|
+
const confirmed = await confirm({
|
|
558
|
+
message: `Delete "${feature?.title}"? This cannot be undone.`,
|
|
559
|
+
default: false,
|
|
560
|
+
});
|
|
561
|
+
if (!confirmed) {
|
|
562
|
+
console.log("\n Cancelled.\n");
|
|
563
|
+
await pressEnterToContinue();
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmap.id}/features/${featureId}`, {
|
|
567
|
+
method: "DELETE",
|
|
568
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
569
|
+
});
|
|
570
|
+
if (!res.ok) {
|
|
571
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
572
|
+
await pressEnterToContinue();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
console.log(`\n ✓ Feature deleted.\n`);
|
|
576
|
+
await pressEnterToContinue();
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
console.error("\n Error deleting feature:", error);
|
|
580
|
+
await pressEnterToContinue();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function generateRoadmap(config) {
|
|
584
|
+
try {
|
|
585
|
+
const roadmap = await selectRoadmap(config, "Select roadmap to generate:");
|
|
586
|
+
if (!roadmap)
|
|
587
|
+
return;
|
|
588
|
+
const context = await input({
|
|
589
|
+
message: "Additional context for AI (optional):",
|
|
590
|
+
});
|
|
591
|
+
console.log("\n Generating roadmap with AI... This may take a moment.\n");
|
|
592
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/generate`, {
|
|
593
|
+
method: "POST",
|
|
594
|
+
headers: {
|
|
595
|
+
"Content-Type": "application/json",
|
|
596
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
597
|
+
},
|
|
598
|
+
body: JSON.stringify({
|
|
599
|
+
roadmapId: roadmap.id,
|
|
600
|
+
additionalContext: context || undefined,
|
|
601
|
+
}),
|
|
602
|
+
});
|
|
603
|
+
if (!res.ok) {
|
|
604
|
+
const error = await res.json().catch(() => ({}));
|
|
605
|
+
console.error(`\n Error: ${error.error || `API returned ${res.status}`}\n`);
|
|
606
|
+
await pressEnterToContinue();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const result = await res.json();
|
|
610
|
+
console.log(` ✓ Roadmap generated!`);
|
|
611
|
+
console.log(` Phases created: ${result.phasesGenerated || "?"}`);
|
|
612
|
+
console.log(` Features created: ${result.featuresGenerated || "?"}\n`);
|
|
613
|
+
await pressEnterToContinue();
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
console.error("\n Error generating roadmap:", error);
|
|
617
|
+
await pressEnterToContinue();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function deleteRoadmap(config) {
|
|
621
|
+
try {
|
|
622
|
+
const roadmap = await selectRoadmap(config, "Select roadmap to delete:");
|
|
623
|
+
if (!roadmap)
|
|
624
|
+
return;
|
|
625
|
+
const confirmed = await confirm({
|
|
626
|
+
message: `Delete "${roadmap.name}" and all phases/features? This cannot be undone.`,
|
|
627
|
+
default: false,
|
|
628
|
+
});
|
|
629
|
+
if (!confirmed) {
|
|
630
|
+
console.log("\n Cancelled.\n");
|
|
631
|
+
await pressEnterToContinue();
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmap.id}`, {
|
|
635
|
+
method: "DELETE",
|
|
636
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
637
|
+
});
|
|
638
|
+
if (!res.ok) {
|
|
639
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
640
|
+
await pressEnterToContinue();
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
console.log(`\n ✓ Roadmap deleted.\n`);
|
|
644
|
+
await pressEnterToContinue();
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
console.error("\n Error deleting roadmap:", error);
|
|
648
|
+
await pressEnterToContinue();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function strategyMenu(): Promise<void>;
|