@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.
Files changed (31) hide show
  1. package/README.md +21 -25
  2. package/dist/commands/config.d.ts +1 -0
  3. package/dist/commands/config.js +10 -3
  4. package/dist/commands/idea.js +9 -7
  5. package/dist/commands/interactive/changelog.d.ts +1 -0
  6. package/dist/commands/interactive/changelog.js +398 -0
  7. package/dist/commands/interactive/departments.d.ts +1 -0
  8. package/dist/commands/interactive/departments.js +242 -0
  9. package/dist/commands/interactive/ideas.d.ts +1 -0
  10. package/dist/commands/interactive/ideas.js +311 -0
  11. package/dist/commands/interactive/jules-sessions.d.ts +1 -0
  12. package/dist/commands/interactive/jules-sessions.js +460 -0
  13. package/dist/commands/interactive/processes.d.ts +1 -0
  14. package/dist/commands/interactive/processes.js +271 -0
  15. package/dist/commands/interactive/projects.d.ts +1 -0
  16. package/dist/commands/interactive/projects.js +297 -0
  17. package/dist/commands/interactive/roadmaps.d.ts +1 -0
  18. package/dist/commands/interactive/roadmaps.js +650 -0
  19. package/dist/commands/interactive/strategy.d.ts +1 -0
  20. package/dist/commands/interactive/strategy.js +790 -0
  21. package/dist/commands/interactive/tasks.d.ts +1 -0
  22. package/dist/commands/interactive/tasks.js +415 -0
  23. package/dist/commands/interactive/utils.d.ts +15 -0
  24. package/dist/commands/interactive/utils.js +54 -0
  25. package/dist/commands/interactive/vm-sessions.d.ts +1 -0
  26. package/dist/commands/interactive/vm-sessions.js +319 -0
  27. package/dist/commands/interactive/workflows.d.ts +1 -0
  28. package/dist/commands/interactive/workflows.js +442 -0
  29. package/dist/commands/interactive.js +150 -1135
  30. package/dist/index.js +1 -1
  31. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ export declare function tasksMenu(): Promise<void>;
@@ -0,0 +1,415 @@
1
+ import { select, input, confirm } from "@inquirer/prompts";
2
+ import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
3
+ export async function tasksMenu() {
4
+ const config = ensureConfig();
5
+ const menuItems = [
6
+ { name: "List all tasks", value: "list" },
7
+ { name: "View task details", value: "view" },
8
+ { name: "Create new task", value: "create" },
9
+ { name: "Update task", value: "update" },
10
+ { name: "Start task", value: "start" },
11
+ { name: "Mark as done", value: "done" },
12
+ { name: "Delete task", value: "delete" },
13
+ { name: "Search by status", value: "search" },
14
+ { name: "Back to main menu", value: "back" },
15
+ ];
16
+ const choice = await select({
17
+ message: "Tasks:",
18
+ choices: menuItems,
19
+ });
20
+ switch (choice) {
21
+ case "list":
22
+ await listTasks(config);
23
+ break;
24
+ case "view":
25
+ await viewTask(config);
26
+ break;
27
+ case "create":
28
+ await createTask(config);
29
+ break;
30
+ case "update":
31
+ await updateTask(config);
32
+ break;
33
+ case "start":
34
+ await startTask(config);
35
+ break;
36
+ case "done":
37
+ await markTaskDone(config);
38
+ break;
39
+ case "delete":
40
+ await deleteTask(config);
41
+ break;
42
+ case "search":
43
+ await searchTasks(config);
44
+ break;
45
+ case "back":
46
+ return;
47
+ }
48
+ }
49
+ async function fetchTasks(config, status) {
50
+ const url = new URL("/api/tasks", config.apiUrl);
51
+ if (status)
52
+ url.searchParams.set("status", status);
53
+ const res = await fetch(url.toString(), {
54
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
55
+ });
56
+ if (!res.ok)
57
+ throw new Error(`API returned ${res.status}`);
58
+ return res.json();
59
+ }
60
+ async function fetchProjects(config) {
61
+ const res = await fetch(`${config.apiUrl}/api/projects`, {
62
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
63
+ });
64
+ if (!res.ok)
65
+ return [];
66
+ return res.json();
67
+ }
68
+ async function selectTask(config, message) {
69
+ const tasks = await fetchTasks(config);
70
+ if (tasks.length === 0) {
71
+ console.log("\n No tasks found.\n");
72
+ await pressEnterToContinue();
73
+ return null;
74
+ }
75
+ const choices = tasks.map((t) => ({
76
+ name: `[${t.status}] ${truncate(t.title, 40)} (${t.priority})`,
77
+ value: t.id,
78
+ }));
79
+ choices.push({ name: "Cancel", value: "__cancel__" });
80
+ const taskId = await select({ message, choices });
81
+ if (taskId === "__cancel__")
82
+ return null;
83
+ return tasks.find((t) => t.id === taskId) || null;
84
+ }
85
+ async function listTasks(config) {
86
+ try {
87
+ const tasks = await fetchTasks(config);
88
+ console.log("\n TASKS");
89
+ console.log(" " + "-".repeat(70));
90
+ if (tasks.length === 0) {
91
+ console.log(" No tasks found.");
92
+ }
93
+ else {
94
+ for (const task of tasks) {
95
+ const statusIcon = task.status === "done" ? "[x]" : task.status === "in_progress" ? "[>]" : "[ ]";
96
+ console.log(` ${statusIcon} ${truncate(task.title, 50).padEnd(50)} [${task.priority}]`);
97
+ console.log(` ID: ${task.id}`);
98
+ }
99
+ }
100
+ console.log("");
101
+ await pressEnterToContinue();
102
+ }
103
+ catch (error) {
104
+ console.error("\n Error fetching tasks:", error);
105
+ await pressEnterToContinue();
106
+ }
107
+ }
108
+ async function viewTask(config) {
109
+ try {
110
+ const task = await selectTask(config, "Select task to view:");
111
+ if (!task)
112
+ return;
113
+ const res = await fetch(`${config.apiUrl}/api/tasks/${task.id}`, {
114
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
115
+ });
116
+ if (!res.ok) {
117
+ console.error(`\n Error: API returned ${res.status}\n`);
118
+ await pressEnterToContinue();
119
+ return;
120
+ }
121
+ const fullTask = await res.json();
122
+ console.log(`\n Task: ${fullTask.title}`);
123
+ console.log(" " + "-".repeat(50));
124
+ console.log(` ID: ${fullTask.id}`);
125
+ console.log(` Status: ${fullTask.status}`);
126
+ console.log(` Priority: ${fullTask.priority}`);
127
+ console.log(` Assignee: ${fullTask.assignee || "unassigned"}`);
128
+ if (fullTask.projectId) {
129
+ console.log(` Project: ${fullTask.projectId}`);
130
+ }
131
+ if (fullTask.description) {
132
+ console.log(` Description: ${fullTask.description}`);
133
+ }
134
+ console.log("");
135
+ await pressEnterToContinue();
136
+ }
137
+ catch (error) {
138
+ console.error("\n Error viewing task:", error);
139
+ await pressEnterToContinue();
140
+ }
141
+ }
142
+ async function createTask(config) {
143
+ try {
144
+ const title = await input({
145
+ message: "Task title:",
146
+ validate: (value) => (value.length > 0 ? true : "Title is required"),
147
+ });
148
+ const description = await input({
149
+ message: "Description (optional):",
150
+ });
151
+ const priority = await select({
152
+ message: "Priority:",
153
+ choices: [
154
+ { name: "Low", value: "low" },
155
+ { name: "Medium", value: "medium" },
156
+ { name: "High", value: "high" },
157
+ ],
158
+ default: "medium",
159
+ });
160
+ // Optional: Link to project
161
+ const projects = await fetchProjects(config);
162
+ let projectId;
163
+ if (projects.length > 0) {
164
+ const linkToProject = await confirm({
165
+ message: "Link to a project?",
166
+ default: false,
167
+ });
168
+ if (linkToProject) {
169
+ const projectChoices = projects.map((p) => ({ name: p.name, value: p.id }));
170
+ projectId = await select({ message: "Select project:", choices: projectChoices });
171
+ }
172
+ }
173
+ const res = await fetch(`${config.apiUrl}/api/tasks`, {
174
+ method: "POST",
175
+ headers: {
176
+ "Content-Type": "application/json",
177
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
178
+ },
179
+ body: JSON.stringify({
180
+ title,
181
+ description: description || undefined,
182
+ priority,
183
+ projectId,
184
+ }),
185
+ });
186
+ if (!res.ok) {
187
+ console.error(`\n Error: API returned ${res.status}\n`);
188
+ await pressEnterToContinue();
189
+ return;
190
+ }
191
+ const task = await res.json();
192
+ console.log(`\n ✓ Task created!`);
193
+ console.log(` ID: ${task.id}`);
194
+ console.log(` Title: ${task.title}\n`);
195
+ await pressEnterToContinue();
196
+ }
197
+ catch (error) {
198
+ console.error("\n Error creating task:", error);
199
+ await pressEnterToContinue();
200
+ }
201
+ }
202
+ async function updateTask(config) {
203
+ try {
204
+ const task = await selectTask(config, "Select task to update:");
205
+ if (!task)
206
+ return;
207
+ const updateChoices = [
208
+ { name: "Status", value: "status" },
209
+ { name: "Priority", value: "priority" },
210
+ { name: "Assignee", value: "assignee" },
211
+ { name: "Link to Project", value: "project" },
212
+ { name: "Title", value: "title" },
213
+ { name: "Description", value: "description" },
214
+ { name: "Cancel", value: "cancel" },
215
+ ];
216
+ const field = await select({ message: "What to update?", choices: updateChoices });
217
+ if (field === "cancel")
218
+ return;
219
+ let updateData = {};
220
+ switch (field) {
221
+ case "status":
222
+ updateData.status = await select({
223
+ message: "New status:",
224
+ choices: [
225
+ { name: "Backlog", value: "backlog" },
226
+ { name: "In Progress", value: "in_progress" },
227
+ { name: "Review", value: "review" },
228
+ { name: "Done", value: "done" },
229
+ ],
230
+ default: task.status,
231
+ });
232
+ break;
233
+ case "priority":
234
+ updateData.priority = await select({
235
+ message: "New priority:",
236
+ choices: [
237
+ { name: "Low", value: "low" },
238
+ { name: "Medium", value: "medium" },
239
+ { name: "High", value: "high" },
240
+ { name: "Urgent", value: "urgent" },
241
+ ],
242
+ default: task.priority,
243
+ });
244
+ break;
245
+ case "assignee":
246
+ updateData.assignee = await select({
247
+ message: "Assign to:",
248
+ choices: [
249
+ { name: "Human", value: "human" },
250
+ { name: "LLM Agent", value: "llm" },
251
+ { name: "Unassigned", value: "unassigned" },
252
+ ],
253
+ default: task.assignee || "unassigned",
254
+ });
255
+ break;
256
+ case "project":
257
+ const projects = await fetchProjects(config);
258
+ if (projects.length === 0) {
259
+ console.log("\n No projects available.\n");
260
+ await pressEnterToContinue();
261
+ return;
262
+ }
263
+ const projectChoices = projects.map((p) => ({ name: p.name, value: p.id }));
264
+ projectChoices.push({ name: "Remove project link", value: "__none__" });
265
+ const projectId = await select({ message: "Select project:", choices: projectChoices });
266
+ updateData.projectId = projectId === "__none__" ? "" : projectId;
267
+ break;
268
+ case "title":
269
+ updateData.title = await input({
270
+ message: "New title:",
271
+ default: task.title,
272
+ validate: (v) => (v.length > 0 ? true : "Title required"),
273
+ });
274
+ break;
275
+ case "description":
276
+ updateData.description = await input({
277
+ message: "New description:",
278
+ default: task.description || "",
279
+ });
280
+ break;
281
+ }
282
+ const res = await fetch(`${config.apiUrl}/api/tasks/${task.id}`, {
283
+ method: "PATCH",
284
+ headers: {
285
+ "Content-Type": "application/json",
286
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
287
+ },
288
+ body: JSON.stringify(updateData),
289
+ });
290
+ if (!res.ok) {
291
+ console.error(`\n Error: API returned ${res.status}\n`);
292
+ await pressEnterToContinue();
293
+ return;
294
+ }
295
+ console.log(`\n ✓ Task updated!\n`);
296
+ await pressEnterToContinue();
297
+ }
298
+ catch (error) {
299
+ console.error("\n Error updating task:", error);
300
+ await pressEnterToContinue();
301
+ }
302
+ }
303
+ async function startTask(config) {
304
+ try {
305
+ const task = await selectTask(config, "Select task to start:");
306
+ if (!task)
307
+ return;
308
+ const res = await fetch(`${config.apiUrl}/api/tasks/${task.id}/start`, {
309
+ method: "POST",
310
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
311
+ });
312
+ if (!res.ok) {
313
+ console.error(`\n Error: API returned ${res.status}\n`);
314
+ await pressEnterToContinue();
315
+ return;
316
+ }
317
+ console.log(`\n ✓ Task started: ${task.title}\n`);
318
+ await pressEnterToContinue();
319
+ }
320
+ catch (error) {
321
+ console.error("\n Error starting task:", error);
322
+ await pressEnterToContinue();
323
+ }
324
+ }
325
+ async function markTaskDone(config) {
326
+ try {
327
+ const task = await selectTask(config, "Select task to mark as done:");
328
+ if (!task)
329
+ return;
330
+ const prUrl = await input({
331
+ message: "PR URL (optional):",
332
+ });
333
+ const res = await fetch(`${config.apiUrl}/api/tasks/${task.id}/done`, {
334
+ method: "POST",
335
+ headers: {
336
+ "Content-Type": "application/json",
337
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
338
+ },
339
+ body: JSON.stringify({ prUrl: prUrl || undefined }),
340
+ });
341
+ if (!res.ok) {
342
+ console.error(`\n Error: API returned ${res.status}\n`);
343
+ await pressEnterToContinue();
344
+ return;
345
+ }
346
+ console.log(`\n ✓ Task marked as done: ${task.title}\n`);
347
+ await pressEnterToContinue();
348
+ }
349
+ catch (error) {
350
+ console.error("\n Error marking task done:", error);
351
+ await pressEnterToContinue();
352
+ }
353
+ }
354
+ async function deleteTask(config) {
355
+ try {
356
+ const task = await selectTask(config, "Select task to delete:");
357
+ if (!task)
358
+ return;
359
+ const confirmed = await confirm({
360
+ message: `Delete "${task.title}"? This cannot be undone.`,
361
+ default: false,
362
+ });
363
+ if (!confirmed) {
364
+ console.log("\n Cancelled.\n");
365
+ await pressEnterToContinue();
366
+ return;
367
+ }
368
+ const res = await fetch(`${config.apiUrl}/api/tasks/${task.id}`, {
369
+ method: "DELETE",
370
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
371
+ });
372
+ if (!res.ok) {
373
+ console.error(`\n Error: API returned ${res.status}\n`);
374
+ await pressEnterToContinue();
375
+ return;
376
+ }
377
+ console.log(`\n ✓ Task deleted.\n`);
378
+ await pressEnterToContinue();
379
+ }
380
+ catch (error) {
381
+ console.error("\n Error deleting task:", error);
382
+ await pressEnterToContinue();
383
+ }
384
+ }
385
+ async function searchTasks(config) {
386
+ try {
387
+ const status = await select({
388
+ message: "Filter by status:",
389
+ choices: [
390
+ { name: "Backlog", value: "backlog" },
391
+ { name: "In Progress", value: "in_progress" },
392
+ { name: "Review", value: "review" },
393
+ { name: "Done", value: "done" },
394
+ ],
395
+ });
396
+ const tasks = await fetchTasks(config, status);
397
+ console.log(`\n TASKS (${status.replace("_", " ").toUpperCase()})`);
398
+ console.log(" " + "-".repeat(70));
399
+ if (tasks.length === 0) {
400
+ console.log(" No tasks found with this status.");
401
+ }
402
+ else {
403
+ for (const task of tasks) {
404
+ console.log(` - ${truncate(task.title, 50)} [${task.priority}]`);
405
+ console.log(` ID: ${task.id}`);
406
+ }
407
+ }
408
+ console.log("");
409
+ await pressEnterToContinue();
410
+ }
411
+ catch (error) {
412
+ console.error("\n Error searching tasks:", error);
413
+ await pressEnterToContinue();
414
+ }
415
+ }
@@ -0,0 +1,15 @@
1
+ export interface MenuItem {
2
+ name: string;
3
+ value: string;
4
+ description?: string;
5
+ }
6
+ export interface ValidConfig {
7
+ apiUrl: string;
8
+ apiKey?: string;
9
+ }
10
+ export declare function ensureConfig(): ValidConfig;
11
+ export declare function clearScreen(): void;
12
+ export declare function printHeader(): void;
13
+ export declare function pressEnterToContinue(): Promise<void>;
14
+ export declare function formatDate(dateStr: string | undefined): string;
15
+ export declare function truncate(str: string, length: number): string;
@@ -0,0 +1,54 @@
1
+ import { input } from "@inquirer/prompts";
2
+ import { getConfig } from "../config.js";
3
+ // Helper: Ensure API is configured
4
+ export function ensureConfig() {
5
+ const config = getConfig();
6
+ if (!config.apiUrl) {
7
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
8
+ process.exit(1);
9
+ }
10
+ return config;
11
+ }
12
+ // Clear screen helper
13
+ export function clearScreen() {
14
+ console.clear();
15
+ }
16
+ // Print header
17
+ export function printHeader() {
18
+ const config = getConfig();
19
+ console.log("\n");
20
+ console.log(" 🐕 Husky CLI - Interactive Mode");
21
+ console.log(" " + "─".repeat(35));
22
+ if (!config.apiUrl) {
23
+ console.log("");
24
+ console.log(" ⚠️ Getting Started:");
25
+ console.log(" Go to 'CLI Config' to set up your API URL and Key");
26
+ }
27
+ else {
28
+ console.log(` Connected to: ${config.apiUrl.substring(0, 40)}`);
29
+ }
30
+ console.log("");
31
+ }
32
+ // Press Enter to continue
33
+ export async function pressEnterToContinue() {
34
+ await input({
35
+ message: "Press Enter to continue...",
36
+ });
37
+ }
38
+ // Format date for display
39
+ export function formatDate(dateStr) {
40
+ if (!dateStr)
41
+ return "-";
42
+ try {
43
+ return new Date(dateStr).toLocaleDateString();
44
+ }
45
+ catch {
46
+ return "-";
47
+ }
48
+ }
49
+ // Truncate string with ellipsis
50
+ export function truncate(str, length) {
51
+ if (!str)
52
+ return "";
53
+ return str.length > length ? str.substring(0, length - 3) + "..." : str;
54
+ }
@@ -0,0 +1 @@
1
+ export declare function vmSessionsMenu(): Promise<void>;