@simonfestl/husky-cli 0.5.1 → 0.6.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/dist/commands/config.js +4 -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/worktrees.d.ts +6 -0
- package/dist/commands/interactive/worktrees.js +354 -0
- package/dist/commands/interactive.js +118 -1208
- package/dist/commands/worktree.d.ts +2 -0
- package/dist/commands/worktree.js +404 -0
- package/dist/index.js +3 -1
- package/dist/lib/merge-lock.d.ts +83 -0
- package/dist/lib/merge-lock.js +242 -0
- package/dist/lib/worktree.d.ts +133 -0
- package/dist/lib/worktree.js +473 -0
- package/package.json +1 -1
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
2
|
+
import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
|
|
3
|
+
const STATUS_LABELS = {
|
|
4
|
+
not_started: "Not Started",
|
|
5
|
+
in_progress: "In Progress",
|
|
6
|
+
at_risk: "At Risk",
|
|
7
|
+
completed: "Completed",
|
|
8
|
+
};
|
|
9
|
+
export async function strategyMenu() {
|
|
10
|
+
const config = ensureConfig();
|
|
11
|
+
const menuItems = [
|
|
12
|
+
{ name: "Show current strategy", value: "show" },
|
|
13
|
+
{ name: "Set vision", value: "vision" },
|
|
14
|
+
{ name: "Set mission", value: "mission" },
|
|
15
|
+
{ name: "Manage values", value: "values" },
|
|
16
|
+
{ name: "Manage goals", value: "goals" },
|
|
17
|
+
{ name: "Manage personas", value: "personas" },
|
|
18
|
+
{ name: "Back to main menu", value: "back" },
|
|
19
|
+
];
|
|
20
|
+
const choice = await select({
|
|
21
|
+
message: "Business Strategy:",
|
|
22
|
+
choices: menuItems,
|
|
23
|
+
});
|
|
24
|
+
switch (choice) {
|
|
25
|
+
case "show":
|
|
26
|
+
await showStrategy(config);
|
|
27
|
+
break;
|
|
28
|
+
case "vision":
|
|
29
|
+
await setVision(config);
|
|
30
|
+
break;
|
|
31
|
+
case "mission":
|
|
32
|
+
await setMission(config);
|
|
33
|
+
break;
|
|
34
|
+
case "values":
|
|
35
|
+
await valuesSubMenu(config);
|
|
36
|
+
break;
|
|
37
|
+
case "goals":
|
|
38
|
+
await goalsSubMenu(config);
|
|
39
|
+
break;
|
|
40
|
+
case "personas":
|
|
41
|
+
await personasSubMenu(config);
|
|
42
|
+
break;
|
|
43
|
+
case "back":
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function fetchStrategy(config) {
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy`, {
|
|
50
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok)
|
|
53
|
+
return null;
|
|
54
|
+
return res.json();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function showStrategy(config) {
|
|
61
|
+
try {
|
|
62
|
+
const strategy = await fetchStrategy(config);
|
|
63
|
+
if (!strategy) {
|
|
64
|
+
console.log("\n Could not fetch business strategy.\n");
|
|
65
|
+
await pressEnterToContinue();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.log("\n BUSINESS STRATEGY");
|
|
69
|
+
console.log(" " + "=".repeat(60));
|
|
70
|
+
console.log("\n VISION");
|
|
71
|
+
console.log(" " + "-".repeat(40));
|
|
72
|
+
console.log(` ${strategy.vision || "(not set)"}`);
|
|
73
|
+
console.log("\n MISSION");
|
|
74
|
+
console.log(" " + "-".repeat(40));
|
|
75
|
+
console.log(` ${strategy.mission || "(not set)"}`);
|
|
76
|
+
console.log(`\n VALUES (${strategy.values.length})`);
|
|
77
|
+
console.log(" " + "-".repeat(40));
|
|
78
|
+
if (strategy.values.length === 0) {
|
|
79
|
+
console.log(" (none)");
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
for (const v of strategy.values) {
|
|
83
|
+
console.log(` - ${v.name}`);
|
|
84
|
+
if (v.description)
|
|
85
|
+
console.log(` ${truncate(v.description, 50)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
console.log(`\n STRATEGIC GOALS (${strategy.goals.length})`);
|
|
89
|
+
console.log(" " + "-".repeat(40));
|
|
90
|
+
if (strategy.goals.length === 0) {
|
|
91
|
+
console.log(" (none)");
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
for (const g of strategy.goals) {
|
|
95
|
+
const bar = getProgressBar(g.progress);
|
|
96
|
+
const statusIcon = g.status === "completed" ? "[OK]" : g.status === "at_risk" ? "[!!]" : "[..]";
|
|
97
|
+
console.log(` ${statusIcon} ${g.title}`);
|
|
98
|
+
console.log(` ${bar} ${g.progress}% - ${STATUS_LABELS[g.status]}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log(`\n TARGET PERSONAS (${strategy.targetAudience.length})`);
|
|
102
|
+
console.log(" " + "-".repeat(40));
|
|
103
|
+
if (strategy.targetAudience.length === 0) {
|
|
104
|
+
console.log(" (none)");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
for (const p of strategy.targetAudience) {
|
|
108
|
+
console.log(` - ${p.name}`);
|
|
109
|
+
if (p.description)
|
|
110
|
+
console.log(` ${truncate(p.description, 50)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
console.log("");
|
|
114
|
+
await pressEnterToContinue();
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error("\n Error fetching strategy:", error);
|
|
118
|
+
await pressEnterToContinue();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function setVision(config) {
|
|
122
|
+
try {
|
|
123
|
+
const strategy = await fetchStrategy(config);
|
|
124
|
+
const vision = await input({
|
|
125
|
+
message: "Enter vision:",
|
|
126
|
+
default: strategy?.vision || "",
|
|
127
|
+
validate: (v) => (v.length > 0 ? true : "Vision required"),
|
|
128
|
+
});
|
|
129
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy`, {
|
|
130
|
+
method: "PATCH",
|
|
131
|
+
headers: {
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({ vision }),
|
|
136
|
+
});
|
|
137
|
+
if (!res.ok) {
|
|
138
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
139
|
+
await pressEnterToContinue();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
console.log(`\n ✓ Vision updated!\n`);
|
|
143
|
+
await pressEnterToContinue();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error("\n Error updating vision:", error);
|
|
147
|
+
await pressEnterToContinue();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function setMission(config) {
|
|
151
|
+
try {
|
|
152
|
+
const strategy = await fetchStrategy(config);
|
|
153
|
+
const mission = await input({
|
|
154
|
+
message: "Enter mission:",
|
|
155
|
+
default: strategy?.mission || "",
|
|
156
|
+
validate: (v) => (v.length > 0 ? true : "Mission required"),
|
|
157
|
+
});
|
|
158
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy`, {
|
|
159
|
+
method: "PATCH",
|
|
160
|
+
headers: {
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify({ mission }),
|
|
165
|
+
});
|
|
166
|
+
if (!res.ok) {
|
|
167
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
168
|
+
await pressEnterToContinue();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
console.log(`\n ✓ Mission updated!\n`);
|
|
172
|
+
await pressEnterToContinue();
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
console.error("\n Error updating mission:", error);
|
|
176
|
+
await pressEnterToContinue();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// VALUES SUB-MENU
|
|
180
|
+
async function valuesSubMenu(config) {
|
|
181
|
+
const menuItems = [
|
|
182
|
+
{ name: "List values", value: "list" },
|
|
183
|
+
{ name: "Add value", value: "add" },
|
|
184
|
+
{ name: "Update value", value: "update" },
|
|
185
|
+
{ name: "Delete value", value: "delete" },
|
|
186
|
+
{ name: "Back", value: "back" },
|
|
187
|
+
];
|
|
188
|
+
const choice = await select({ message: "Values:", choices: menuItems });
|
|
189
|
+
switch (choice) {
|
|
190
|
+
case "list":
|
|
191
|
+
await listValues(config);
|
|
192
|
+
break;
|
|
193
|
+
case "add":
|
|
194
|
+
await addValue(config);
|
|
195
|
+
break;
|
|
196
|
+
case "update":
|
|
197
|
+
await updateValue(config);
|
|
198
|
+
break;
|
|
199
|
+
case "delete":
|
|
200
|
+
await deleteValue(config);
|
|
201
|
+
break;
|
|
202
|
+
case "back":
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function listValues(config) {
|
|
207
|
+
const strategy = await fetchStrategy(config);
|
|
208
|
+
if (!strategy) {
|
|
209
|
+
console.log("\n Could not fetch strategy.\n");
|
|
210
|
+
await pressEnterToContinue();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
console.log("\n COMPANY VALUES");
|
|
214
|
+
console.log(" " + "-".repeat(50));
|
|
215
|
+
if (strategy.values.length === 0) {
|
|
216
|
+
console.log(" No values defined.");
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
for (const v of strategy.values) {
|
|
220
|
+
console.log(` - ${v.name}`);
|
|
221
|
+
if (v.description)
|
|
222
|
+
console.log(` ${truncate(v.description, 50)}`);
|
|
223
|
+
console.log(` ID: ${v.id}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
console.log("");
|
|
227
|
+
await pressEnterToContinue();
|
|
228
|
+
}
|
|
229
|
+
async function addValue(config) {
|
|
230
|
+
try {
|
|
231
|
+
const name = await input({
|
|
232
|
+
message: "Value name:",
|
|
233
|
+
validate: (v) => (v.length > 0 ? true : "Name required"),
|
|
234
|
+
});
|
|
235
|
+
const description = await input({
|
|
236
|
+
message: "Description (optional):",
|
|
237
|
+
});
|
|
238
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy`, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers: {
|
|
241
|
+
"Content-Type": "application/json",
|
|
242
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
243
|
+
},
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
type: "value",
|
|
246
|
+
name,
|
|
247
|
+
description: description || "",
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
if (!res.ok) {
|
|
251
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
252
|
+
await pressEnterToContinue();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
console.log(`\n ✓ Value added!\n`);
|
|
256
|
+
await pressEnterToContinue();
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
console.error("\n Error adding value:", error);
|
|
260
|
+
await pressEnterToContinue();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function updateValue(config) {
|
|
264
|
+
try {
|
|
265
|
+
const strategy = await fetchStrategy(config);
|
|
266
|
+
if (!strategy || strategy.values.length === 0) {
|
|
267
|
+
console.log("\n No values to update.\n");
|
|
268
|
+
await pressEnterToContinue();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const choices = strategy.values.map((v) => ({
|
|
272
|
+
name: v.name,
|
|
273
|
+
value: v.id,
|
|
274
|
+
}));
|
|
275
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
276
|
+
const valueId = await select({ message: "Select value:", choices });
|
|
277
|
+
if (valueId === "__cancel__")
|
|
278
|
+
return;
|
|
279
|
+
const value = strategy.values.find((v) => v.id === valueId);
|
|
280
|
+
if (!value)
|
|
281
|
+
return;
|
|
282
|
+
const name = await input({
|
|
283
|
+
message: "New name:",
|
|
284
|
+
default: value.name,
|
|
285
|
+
validate: (v) => (v.length > 0 ? true : "Name required"),
|
|
286
|
+
});
|
|
287
|
+
const description = await input({
|
|
288
|
+
message: "New description:",
|
|
289
|
+
default: value.description || "",
|
|
290
|
+
});
|
|
291
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy/values/${valueId}`, {
|
|
292
|
+
method: "PATCH",
|
|
293
|
+
headers: {
|
|
294
|
+
"Content-Type": "application/json",
|
|
295
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
296
|
+
},
|
|
297
|
+
body: JSON.stringify({ name, description }),
|
|
298
|
+
});
|
|
299
|
+
if (!res.ok) {
|
|
300
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
301
|
+
await pressEnterToContinue();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
console.log(`\n ✓ Value updated!\n`);
|
|
305
|
+
await pressEnterToContinue();
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error("\n Error updating value:", error);
|
|
309
|
+
await pressEnterToContinue();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function deleteValue(config) {
|
|
313
|
+
try {
|
|
314
|
+
const strategy = await fetchStrategy(config);
|
|
315
|
+
if (!strategy || strategy.values.length === 0) {
|
|
316
|
+
console.log("\n No values to delete.\n");
|
|
317
|
+
await pressEnterToContinue();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const choices = strategy.values.map((v) => ({
|
|
321
|
+
name: v.name,
|
|
322
|
+
value: v.id,
|
|
323
|
+
}));
|
|
324
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
325
|
+
const valueId = await select({ message: "Select value to delete:", choices });
|
|
326
|
+
if (valueId === "__cancel__")
|
|
327
|
+
return;
|
|
328
|
+
const value = strategy.values.find((v) => v.id === valueId);
|
|
329
|
+
const confirmed = await confirm({
|
|
330
|
+
message: `Delete "${value?.name}"? This cannot be undone.`,
|
|
331
|
+
default: false,
|
|
332
|
+
});
|
|
333
|
+
if (!confirmed) {
|
|
334
|
+
console.log("\n Cancelled.\n");
|
|
335
|
+
await pressEnterToContinue();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy/values/${valueId}`, {
|
|
339
|
+
method: "DELETE",
|
|
340
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
341
|
+
});
|
|
342
|
+
if (!res.ok) {
|
|
343
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
344
|
+
await pressEnterToContinue();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
console.log(`\n ✓ Value deleted.\n`);
|
|
348
|
+
await pressEnterToContinue();
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
console.error("\n Error deleting value:", error);
|
|
352
|
+
await pressEnterToContinue();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// GOALS SUB-MENU
|
|
356
|
+
async function goalsSubMenu(config) {
|
|
357
|
+
const menuItems = [
|
|
358
|
+
{ name: "List goals", value: "list" },
|
|
359
|
+
{ name: "Add goal", value: "add" },
|
|
360
|
+
{ name: "Update goal", value: "update" },
|
|
361
|
+
{ name: "Delete goal", value: "delete" },
|
|
362
|
+
{ name: "Back", value: "back" },
|
|
363
|
+
];
|
|
364
|
+
const choice = await select({ message: "Goals:", choices: menuItems });
|
|
365
|
+
switch (choice) {
|
|
366
|
+
case "list":
|
|
367
|
+
await listGoals(config);
|
|
368
|
+
break;
|
|
369
|
+
case "add":
|
|
370
|
+
await addGoal(config);
|
|
371
|
+
break;
|
|
372
|
+
case "update":
|
|
373
|
+
await updateGoal(config);
|
|
374
|
+
break;
|
|
375
|
+
case "delete":
|
|
376
|
+
await deleteGoal(config);
|
|
377
|
+
break;
|
|
378
|
+
case "back":
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
async function listGoals(config) {
|
|
383
|
+
const strategy = await fetchStrategy(config);
|
|
384
|
+
if (!strategy) {
|
|
385
|
+
console.log("\n Could not fetch strategy.\n");
|
|
386
|
+
await pressEnterToContinue();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
console.log("\n STRATEGIC GOALS");
|
|
390
|
+
console.log(" " + "-".repeat(60));
|
|
391
|
+
if (strategy.goals.length === 0) {
|
|
392
|
+
console.log(" No goals defined.");
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
for (const g of strategy.goals) {
|
|
396
|
+
const bar = getProgressBar(g.progress);
|
|
397
|
+
console.log(` [${STATUS_LABELS[g.status]}] ${g.title}`);
|
|
398
|
+
console.log(` ${bar} ${g.progress}%`);
|
|
399
|
+
if (g.dueDate)
|
|
400
|
+
console.log(` Due: ${new Date(g.dueDate).toLocaleDateString()}`);
|
|
401
|
+
console.log(` ID: ${g.id}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
console.log("");
|
|
405
|
+
await pressEnterToContinue();
|
|
406
|
+
}
|
|
407
|
+
async function addGoal(config) {
|
|
408
|
+
try {
|
|
409
|
+
const title = await input({
|
|
410
|
+
message: "Goal title:",
|
|
411
|
+
validate: (v) => (v.length > 0 ? true : "Title required"),
|
|
412
|
+
});
|
|
413
|
+
const description = await input({
|
|
414
|
+
message: "Description (optional):",
|
|
415
|
+
});
|
|
416
|
+
const status = await select({
|
|
417
|
+
message: "Status:",
|
|
418
|
+
choices: [
|
|
419
|
+
{ name: "Not Started", value: "not_started" },
|
|
420
|
+
{ name: "In Progress", value: "in_progress" },
|
|
421
|
+
{ name: "At Risk", value: "at_risk" },
|
|
422
|
+
{ name: "Completed", value: "completed" },
|
|
423
|
+
],
|
|
424
|
+
default: "not_started",
|
|
425
|
+
});
|
|
426
|
+
const progressStr = await input({
|
|
427
|
+
message: "Progress (0-100):",
|
|
428
|
+
default: "0",
|
|
429
|
+
validate: (v) => {
|
|
430
|
+
const n = parseInt(v, 10);
|
|
431
|
+
return !isNaN(n) && n >= 0 && n <= 100 ? true : "Must be 0-100";
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy`, {
|
|
435
|
+
method: "POST",
|
|
436
|
+
headers: {
|
|
437
|
+
"Content-Type": "application/json",
|
|
438
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
439
|
+
},
|
|
440
|
+
body: JSON.stringify({
|
|
441
|
+
type: "goal",
|
|
442
|
+
title,
|
|
443
|
+
description: description || "",
|
|
444
|
+
status,
|
|
445
|
+
progress: parseInt(progressStr, 10),
|
|
446
|
+
}),
|
|
447
|
+
});
|
|
448
|
+
if (!res.ok) {
|
|
449
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
450
|
+
await pressEnterToContinue();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
console.log(`\n ✓ Goal added!\n`);
|
|
454
|
+
await pressEnterToContinue();
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
console.error("\n Error adding goal:", error);
|
|
458
|
+
await pressEnterToContinue();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async function updateGoal(config) {
|
|
462
|
+
try {
|
|
463
|
+
const strategy = await fetchStrategy(config);
|
|
464
|
+
if (!strategy || strategy.goals.length === 0) {
|
|
465
|
+
console.log("\n No goals to update.\n");
|
|
466
|
+
await pressEnterToContinue();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const choices = strategy.goals.map((g) => ({
|
|
470
|
+
name: `[${STATUS_LABELS[g.status]}] ${truncate(g.title, 40)}`,
|
|
471
|
+
value: g.id,
|
|
472
|
+
}));
|
|
473
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
474
|
+
const goalId = await select({ message: "Select goal:", choices });
|
|
475
|
+
if (goalId === "__cancel__")
|
|
476
|
+
return;
|
|
477
|
+
const goal = strategy.goals.find((g) => g.id === goalId);
|
|
478
|
+
if (!goal)
|
|
479
|
+
return;
|
|
480
|
+
const updateChoices = [
|
|
481
|
+
{ name: "Status", value: "status" },
|
|
482
|
+
{ name: "Progress", value: "progress" },
|
|
483
|
+
{ name: "Title", value: "title" },
|
|
484
|
+
{ name: "Description", value: "description" },
|
|
485
|
+
{ name: "Cancel", value: "cancel" },
|
|
486
|
+
];
|
|
487
|
+
const field = await select({ message: "What to update?", choices: updateChoices });
|
|
488
|
+
if (field === "cancel")
|
|
489
|
+
return;
|
|
490
|
+
let updateData = {};
|
|
491
|
+
switch (field) {
|
|
492
|
+
case "status":
|
|
493
|
+
updateData.status = await select({
|
|
494
|
+
message: "New status:",
|
|
495
|
+
choices: [
|
|
496
|
+
{ name: "Not Started", value: "not_started" },
|
|
497
|
+
{ name: "In Progress", value: "in_progress" },
|
|
498
|
+
{ name: "At Risk", value: "at_risk" },
|
|
499
|
+
{ name: "Completed", value: "completed" },
|
|
500
|
+
],
|
|
501
|
+
default: goal.status,
|
|
502
|
+
});
|
|
503
|
+
break;
|
|
504
|
+
case "progress":
|
|
505
|
+
const progressStr = await input({
|
|
506
|
+
message: "New progress (0-100):",
|
|
507
|
+
default: String(goal.progress),
|
|
508
|
+
validate: (v) => {
|
|
509
|
+
const n = parseInt(v, 10);
|
|
510
|
+
return !isNaN(n) && n >= 0 && n <= 100 ? true : "Must be 0-100";
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
updateData.progress = parseInt(progressStr, 10);
|
|
514
|
+
break;
|
|
515
|
+
case "title":
|
|
516
|
+
updateData.title = await input({
|
|
517
|
+
message: "New title:",
|
|
518
|
+
default: goal.title,
|
|
519
|
+
validate: (v) => (v.length > 0 ? true : "Title required"),
|
|
520
|
+
});
|
|
521
|
+
break;
|
|
522
|
+
case "description":
|
|
523
|
+
updateData.description = await input({
|
|
524
|
+
message: "New description:",
|
|
525
|
+
default: goal.description || "",
|
|
526
|
+
});
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy/goals/${goalId}`, {
|
|
530
|
+
method: "PATCH",
|
|
531
|
+
headers: {
|
|
532
|
+
"Content-Type": "application/json",
|
|
533
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
534
|
+
},
|
|
535
|
+
body: JSON.stringify(updateData),
|
|
536
|
+
});
|
|
537
|
+
if (!res.ok) {
|
|
538
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
539
|
+
await pressEnterToContinue();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
console.log(`\n ✓ Goal updated!\n`);
|
|
543
|
+
await pressEnterToContinue();
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
console.error("\n Error updating goal:", error);
|
|
547
|
+
await pressEnterToContinue();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async function deleteGoal(config) {
|
|
551
|
+
try {
|
|
552
|
+
const strategy = await fetchStrategy(config);
|
|
553
|
+
if (!strategy || strategy.goals.length === 0) {
|
|
554
|
+
console.log("\n No goals to delete.\n");
|
|
555
|
+
await pressEnterToContinue();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const choices = strategy.goals.map((g) => ({
|
|
559
|
+
name: `[${STATUS_LABELS[g.status]}] ${truncate(g.title, 40)}`,
|
|
560
|
+
value: g.id,
|
|
561
|
+
}));
|
|
562
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
563
|
+
const goalId = await select({ message: "Select goal to delete:", choices });
|
|
564
|
+
if (goalId === "__cancel__")
|
|
565
|
+
return;
|
|
566
|
+
const goal = strategy.goals.find((g) => g.id === goalId);
|
|
567
|
+
const confirmed = await confirm({
|
|
568
|
+
message: `Delete "${goal?.title}"? This cannot be undone.`,
|
|
569
|
+
default: false,
|
|
570
|
+
});
|
|
571
|
+
if (!confirmed) {
|
|
572
|
+
console.log("\n Cancelled.\n");
|
|
573
|
+
await pressEnterToContinue();
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy/goals/${goalId}`, {
|
|
577
|
+
method: "DELETE",
|
|
578
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
579
|
+
});
|
|
580
|
+
if (!res.ok) {
|
|
581
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
582
|
+
await pressEnterToContinue();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
console.log(`\n ✓ Goal deleted.\n`);
|
|
586
|
+
await pressEnterToContinue();
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
console.error("\n Error deleting goal:", error);
|
|
590
|
+
await pressEnterToContinue();
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// PERSONAS SUB-MENU
|
|
594
|
+
async function personasSubMenu(config) {
|
|
595
|
+
const menuItems = [
|
|
596
|
+
{ name: "List personas", value: "list" },
|
|
597
|
+
{ name: "Add persona", value: "add" },
|
|
598
|
+
{ name: "Update persona", value: "update" },
|
|
599
|
+
{ name: "Delete persona", value: "delete" },
|
|
600
|
+
{ name: "Back", value: "back" },
|
|
601
|
+
];
|
|
602
|
+
const choice = await select({ message: "Personas:", choices: menuItems });
|
|
603
|
+
switch (choice) {
|
|
604
|
+
case "list":
|
|
605
|
+
await listPersonas(config);
|
|
606
|
+
break;
|
|
607
|
+
case "add":
|
|
608
|
+
await addPersona(config);
|
|
609
|
+
break;
|
|
610
|
+
case "update":
|
|
611
|
+
await updatePersona(config);
|
|
612
|
+
break;
|
|
613
|
+
case "delete":
|
|
614
|
+
await deletePersona(config);
|
|
615
|
+
break;
|
|
616
|
+
case "back":
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function listPersonas(config) {
|
|
621
|
+
const strategy = await fetchStrategy(config);
|
|
622
|
+
if (!strategy) {
|
|
623
|
+
console.log("\n Could not fetch strategy.\n");
|
|
624
|
+
await pressEnterToContinue();
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
console.log("\n TARGET AUDIENCE PERSONAS");
|
|
628
|
+
console.log(" " + "-".repeat(50));
|
|
629
|
+
if (strategy.targetAudience.length === 0) {
|
|
630
|
+
console.log(" No personas defined.");
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
for (const p of strategy.targetAudience) {
|
|
634
|
+
console.log(` - ${p.name}`);
|
|
635
|
+
if (p.description)
|
|
636
|
+
console.log(` ${truncate(p.description, 50)}`);
|
|
637
|
+
if (p.characteristics.length > 0) {
|
|
638
|
+
console.log(` Characteristics: ${p.characteristics.join(", ")}`);
|
|
639
|
+
}
|
|
640
|
+
console.log(` ID: ${p.id}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
console.log("");
|
|
644
|
+
await pressEnterToContinue();
|
|
645
|
+
}
|
|
646
|
+
async function addPersona(config) {
|
|
647
|
+
try {
|
|
648
|
+
const name = await input({
|
|
649
|
+
message: "Persona name:",
|
|
650
|
+
validate: (v) => (v.length > 0 ? true : "Name required"),
|
|
651
|
+
});
|
|
652
|
+
const description = await input({
|
|
653
|
+
message: "Description (optional):",
|
|
654
|
+
});
|
|
655
|
+
const characteristicsStr = await input({
|
|
656
|
+
message: "Characteristics (comma-separated, optional):",
|
|
657
|
+
});
|
|
658
|
+
const characteristics = characteristicsStr
|
|
659
|
+
? characteristicsStr.split(",").map((c) => c.trim()).filter((c) => c)
|
|
660
|
+
: [];
|
|
661
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy`, {
|
|
662
|
+
method: "POST",
|
|
663
|
+
headers: {
|
|
664
|
+
"Content-Type": "application/json",
|
|
665
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
666
|
+
},
|
|
667
|
+
body: JSON.stringify({
|
|
668
|
+
type: "persona",
|
|
669
|
+
name,
|
|
670
|
+
description: description || "",
|
|
671
|
+
characteristics,
|
|
672
|
+
}),
|
|
673
|
+
});
|
|
674
|
+
if (!res.ok) {
|
|
675
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
676
|
+
await pressEnterToContinue();
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
console.log(`\n ✓ Persona added!\n`);
|
|
680
|
+
await pressEnterToContinue();
|
|
681
|
+
}
|
|
682
|
+
catch (error) {
|
|
683
|
+
console.error("\n Error adding persona:", error);
|
|
684
|
+
await pressEnterToContinue();
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function updatePersona(config) {
|
|
688
|
+
try {
|
|
689
|
+
const strategy = await fetchStrategy(config);
|
|
690
|
+
if (!strategy || strategy.targetAudience.length === 0) {
|
|
691
|
+
console.log("\n No personas to update.\n");
|
|
692
|
+
await pressEnterToContinue();
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const choices = strategy.targetAudience.map((p) => ({
|
|
696
|
+
name: p.name,
|
|
697
|
+
value: p.id,
|
|
698
|
+
}));
|
|
699
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
700
|
+
const personaId = await select({ message: "Select persona:", choices });
|
|
701
|
+
if (personaId === "__cancel__")
|
|
702
|
+
return;
|
|
703
|
+
const persona = strategy.targetAudience.find((p) => p.id === personaId);
|
|
704
|
+
if (!persona)
|
|
705
|
+
return;
|
|
706
|
+
const name = await input({
|
|
707
|
+
message: "New name:",
|
|
708
|
+
default: persona.name,
|
|
709
|
+
validate: (v) => (v.length > 0 ? true : "Name required"),
|
|
710
|
+
});
|
|
711
|
+
const description = await input({
|
|
712
|
+
message: "New description:",
|
|
713
|
+
default: persona.description || "",
|
|
714
|
+
});
|
|
715
|
+
const characteristicsStr = await input({
|
|
716
|
+
message: "Characteristics (comma-separated):",
|
|
717
|
+
default: persona.characteristics.join(", "),
|
|
718
|
+
});
|
|
719
|
+
const characteristics = characteristicsStr
|
|
720
|
+
? characteristicsStr.split(",").map((c) => c.trim()).filter((c) => c)
|
|
721
|
+
: [];
|
|
722
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy/personas/${personaId}`, {
|
|
723
|
+
method: "PATCH",
|
|
724
|
+
headers: {
|
|
725
|
+
"Content-Type": "application/json",
|
|
726
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
727
|
+
},
|
|
728
|
+
body: JSON.stringify({ name, description, characteristics }),
|
|
729
|
+
});
|
|
730
|
+
if (!res.ok) {
|
|
731
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
732
|
+
await pressEnterToContinue();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
console.log(`\n ✓ Persona updated!\n`);
|
|
736
|
+
await pressEnterToContinue();
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
console.error("\n Error updating persona:", error);
|
|
740
|
+
await pressEnterToContinue();
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async function deletePersona(config) {
|
|
744
|
+
try {
|
|
745
|
+
const strategy = await fetchStrategy(config);
|
|
746
|
+
if (!strategy || strategy.targetAudience.length === 0) {
|
|
747
|
+
console.log("\n No personas to delete.\n");
|
|
748
|
+
await pressEnterToContinue();
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
const choices = strategy.targetAudience.map((p) => ({
|
|
752
|
+
name: p.name,
|
|
753
|
+
value: p.id,
|
|
754
|
+
}));
|
|
755
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
756
|
+
const personaId = await select({ message: "Select persona to delete:", choices });
|
|
757
|
+
if (personaId === "__cancel__")
|
|
758
|
+
return;
|
|
759
|
+
const persona = strategy.targetAudience.find((p) => p.id === personaId);
|
|
760
|
+
const confirmed = await confirm({
|
|
761
|
+
message: `Delete "${persona?.name}"? This cannot be undone.`,
|
|
762
|
+
default: false,
|
|
763
|
+
});
|
|
764
|
+
if (!confirmed) {
|
|
765
|
+
console.log("\n Cancelled.\n");
|
|
766
|
+
await pressEnterToContinue();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const res = await fetch(`${config.apiUrl}/api/business-strategy/personas/${personaId}`, {
|
|
770
|
+
method: "DELETE",
|
|
771
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
772
|
+
});
|
|
773
|
+
if (!res.ok) {
|
|
774
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
775
|
+
await pressEnterToContinue();
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
console.log(`\n ✓ Persona deleted.\n`);
|
|
779
|
+
await pressEnterToContinue();
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
console.error("\n Error deleting persona:", error);
|
|
783
|
+
await pressEnterToContinue();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function getProgressBar(progress) {
|
|
787
|
+
const filled = Math.round(progress / 10);
|
|
788
|
+
const empty = 10 - filled;
|
|
789
|
+
return "[" + "#".repeat(filled) + "-".repeat(empty) + "]";
|
|
790
|
+
}
|