@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,473 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getConfig } from "./config.js";
|
|
3
|
+
export const projectCommand = new Command("project")
|
|
4
|
+
.description("Manage projects and knowledge");
|
|
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
|
+
// Work status labels
|
|
15
|
+
const WORK_STATUS_LABELS = {
|
|
16
|
+
planning: "Planning",
|
|
17
|
+
in_progress: "In Progress",
|
|
18
|
+
review: "Review",
|
|
19
|
+
completed: "Completed",
|
|
20
|
+
on_hold: "On Hold",
|
|
21
|
+
};
|
|
22
|
+
// Knowledge category config
|
|
23
|
+
const KNOWLEDGE_CATEGORY_CONFIG = {
|
|
24
|
+
architecture: { label: "Architecture", icon: "A" },
|
|
25
|
+
patterns: { label: "Patterns", icon: "P" },
|
|
26
|
+
decisions: { label: "Decisions", icon: "D" },
|
|
27
|
+
learnings: { label: "Learnings", icon: "L" },
|
|
28
|
+
};
|
|
29
|
+
// ============================================
|
|
30
|
+
// PROJECT CRUD COMMANDS
|
|
31
|
+
// ============================================
|
|
32
|
+
// husky project list
|
|
33
|
+
projectCommand
|
|
34
|
+
.command("list")
|
|
35
|
+
.description("List all projects")
|
|
36
|
+
.option("--json", "Output as JSON")
|
|
37
|
+
.option("--status <status>", "Filter by status (active, archived)")
|
|
38
|
+
.option("--work-status <workStatus>", "Filter by work status (planning, in_progress, review, completed, on_hold)")
|
|
39
|
+
.option("--archived", "Include archived projects")
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
const config = ensureConfig();
|
|
42
|
+
try {
|
|
43
|
+
const url = new URL("/api/projects", config.apiUrl);
|
|
44
|
+
if (options.archived || options.status === "archived") {
|
|
45
|
+
url.searchParams.set("archived", "true");
|
|
46
|
+
}
|
|
47
|
+
const res = await fetch(url.toString(), {
|
|
48
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
throw new Error(`API error: ${res.status}`);
|
|
52
|
+
}
|
|
53
|
+
let projects = await res.json();
|
|
54
|
+
// Apply filters
|
|
55
|
+
if (options.status) {
|
|
56
|
+
projects = projects.filter((p) => p.status === options.status);
|
|
57
|
+
}
|
|
58
|
+
if (options.workStatus) {
|
|
59
|
+
projects = projects.filter((p) => p.workStatus === options.workStatus);
|
|
60
|
+
}
|
|
61
|
+
if (options.json) {
|
|
62
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
printProjects(projects);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("Error fetching projects:", error);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// husky project create <name>
|
|
74
|
+
projectCommand
|
|
75
|
+
.command("create <name>")
|
|
76
|
+
.description("Create a new project")
|
|
77
|
+
.option("-d, --description <description>", "Project description")
|
|
78
|
+
.option("--status <status>", "Project status (active, archived)", "active")
|
|
79
|
+
.option("--work-status <workStatus>", "Work status (planning, in_progress, review, completed, on_hold)", "planning")
|
|
80
|
+
.option("--tech-stack <techStack>", "Tech stack description")
|
|
81
|
+
.option("--github <githubRepo>", "GitHub repository URL")
|
|
82
|
+
.option("--color <color>", "Project color (hex)")
|
|
83
|
+
.option("--json", "Output as JSON")
|
|
84
|
+
.action(async (name, options) => {
|
|
85
|
+
const config = ensureConfig();
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetch(`${config.apiUrl}/api/projects`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
name,
|
|
95
|
+
description: options.description,
|
|
96
|
+
status: options.status,
|
|
97
|
+
workStatus: options.workStatus,
|
|
98
|
+
techStack: options.techStack,
|
|
99
|
+
githubRepo: options.github,
|
|
100
|
+
color: options.color,
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
const errorData = await res.json().catch(() => ({}));
|
|
105
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
106
|
+
}
|
|
107
|
+
const project = await res.json();
|
|
108
|
+
if (options.json) {
|
|
109
|
+
console.log(JSON.stringify(project, null, 2));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log(`Created project: ${project.name}`);
|
|
113
|
+
console.log(` ID: ${project.id}`);
|
|
114
|
+
console.log(` Status: ${project.status}`);
|
|
115
|
+
console.log(` Work Status: ${project.workStatus}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error("Error creating project:", error);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// husky project get <id>
|
|
124
|
+
projectCommand
|
|
125
|
+
.command("get <id>")
|
|
126
|
+
.description("Get project details")
|
|
127
|
+
.option("--json", "Output as JSON")
|
|
128
|
+
.option("--tasks", "Include project tasks")
|
|
129
|
+
.action(async (id, options) => {
|
|
130
|
+
const config = ensureConfig();
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(`/api/projects/${id}`, config.apiUrl);
|
|
133
|
+
if (options.tasks) {
|
|
134
|
+
url.searchParams.set("tasks", "true");
|
|
135
|
+
}
|
|
136
|
+
const res = await fetch(url.toString(), {
|
|
137
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
if (res.status === 404) {
|
|
141
|
+
console.error(`Error: Project ${id} not found`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.error(`Error: API returned ${res.status}`);
|
|
145
|
+
}
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
const project = await res.json();
|
|
149
|
+
if (options.json) {
|
|
150
|
+
console.log(JSON.stringify(project, null, 2));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
printProjectDetail(project);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error("Error fetching project:", error);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
// husky project update <id>
|
|
162
|
+
projectCommand
|
|
163
|
+
.command("update <id>")
|
|
164
|
+
.description("Update a project")
|
|
165
|
+
.option("-n, --name <name>", "New project name")
|
|
166
|
+
.option("-d, --description <description>", "New description")
|
|
167
|
+
.option("--status <status>", "New status (active, archived)")
|
|
168
|
+
.option("--work-status <workStatus>", "New work status (planning, in_progress, review, completed, on_hold)")
|
|
169
|
+
.option("--tech-stack <techStack>", "Tech stack description")
|
|
170
|
+
.option("--github <githubRepo>", "GitHub repository URL")
|
|
171
|
+
.option("--color <color>", "Project color (hex)")
|
|
172
|
+
.option("--json", "Output as JSON")
|
|
173
|
+
.action(async (id, options) => {
|
|
174
|
+
const config = ensureConfig();
|
|
175
|
+
// Build update payload
|
|
176
|
+
const updateData = {};
|
|
177
|
+
if (options.name)
|
|
178
|
+
updateData.name = options.name;
|
|
179
|
+
if (options.description)
|
|
180
|
+
updateData.description = options.description;
|
|
181
|
+
if (options.status)
|
|
182
|
+
updateData.status = options.status;
|
|
183
|
+
if (options.workStatus)
|
|
184
|
+
updateData.workStatus = options.workStatus;
|
|
185
|
+
if (options.techStack)
|
|
186
|
+
updateData.techStack = options.techStack;
|
|
187
|
+
if (options.github)
|
|
188
|
+
updateData.githubRepo = options.github;
|
|
189
|
+
if (options.color)
|
|
190
|
+
updateData.color = options.color;
|
|
191
|
+
if (Object.keys(updateData).length === 0) {
|
|
192
|
+
console.error("Error: No update options provided.");
|
|
193
|
+
console.log("Use -n/--name, -d/--description, --status, --work-status, --tech-stack, --github, or --color");
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${id}`, {
|
|
198
|
+
method: "PATCH",
|
|
199
|
+
headers: {
|
|
200
|
+
"Content-Type": "application/json",
|
|
201
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify(updateData),
|
|
204
|
+
});
|
|
205
|
+
if (!res.ok) {
|
|
206
|
+
if (res.status === 404) {
|
|
207
|
+
console.error(`Error: Project ${id} not found`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const errorData = await res.json().catch(() => ({}));
|
|
211
|
+
console.error(`Error: ${errorData.error || `API returned ${res.status}`}`);
|
|
212
|
+
}
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
const project = await res.json();
|
|
216
|
+
if (options.json) {
|
|
217
|
+
console.log(JSON.stringify(project, null, 2));
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
console.log(`Project updated successfully`);
|
|
221
|
+
console.log(` Name: ${project.name}`);
|
|
222
|
+
console.log(` Status: ${project.status}`);
|
|
223
|
+
console.log(` Work Status: ${project.workStatus}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
console.error("Error updating project:", error);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
// husky project delete <id>
|
|
232
|
+
projectCommand
|
|
233
|
+
.command("delete <id>")
|
|
234
|
+
.description("Delete a project")
|
|
235
|
+
.option("--force", "Skip confirmation")
|
|
236
|
+
.action(async (id, options) => {
|
|
237
|
+
const config = ensureConfig();
|
|
238
|
+
if (!options.force) {
|
|
239
|
+
console.log("Warning: This will permanently delete the project and all associated data.");
|
|
240
|
+
console.log("Use --force to confirm deletion.");
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${id}`, {
|
|
245
|
+
method: "DELETE",
|
|
246
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
247
|
+
});
|
|
248
|
+
if (!res.ok) {
|
|
249
|
+
if (res.status === 404) {
|
|
250
|
+
console.error(`Error: Project ${id} not found`);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
console.error(`Error: API returned ${res.status}`);
|
|
254
|
+
}
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
console.log(`Project deleted`);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error("Error deleting project:", error);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
// ============================================
|
|
265
|
+
// KNOWLEDGE MANAGEMENT COMMANDS
|
|
266
|
+
// ============================================
|
|
267
|
+
// husky project add-knowledge <projectId>
|
|
268
|
+
projectCommand
|
|
269
|
+
.command("add-knowledge <projectId>")
|
|
270
|
+
.description("Add a knowledge entry to a project")
|
|
271
|
+
.requiredOption("--title <title>", "Knowledge entry title")
|
|
272
|
+
.requiredOption("--content <content>", "Knowledge entry content (markdown)")
|
|
273
|
+
.option("--type <type>", "Category: architecture, patterns, decisions, learnings", "learnings")
|
|
274
|
+
.option("--json", "Output as JSON")
|
|
275
|
+
.action(async (projectId, options) => {
|
|
276
|
+
const config = ensureConfig();
|
|
277
|
+
const validCategories = ["architecture", "patterns", "decisions", "learnings"];
|
|
278
|
+
if (!validCategories.includes(options.type)) {
|
|
279
|
+
console.error(`Error: Invalid category "${options.type}". Must be one of: ${validCategories.join(", ")}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/knowledge`, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: {
|
|
286
|
+
"Content-Type": "application/json",
|
|
287
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
288
|
+
},
|
|
289
|
+
body: JSON.stringify({
|
|
290
|
+
title: options.title,
|
|
291
|
+
content: options.content,
|
|
292
|
+
category: options.type,
|
|
293
|
+
}),
|
|
294
|
+
});
|
|
295
|
+
if (!res.ok) {
|
|
296
|
+
if (res.status === 404) {
|
|
297
|
+
console.error(`Error: Project ${projectId} not found`);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const errorData = await res.json().catch(() => ({}));
|
|
301
|
+
console.error(`Error: ${errorData.error || `API returned ${res.status}`}`);
|
|
302
|
+
}
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
const knowledge = await res.json();
|
|
306
|
+
if (options.json) {
|
|
307
|
+
console.log(JSON.stringify(knowledge, null, 2));
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
console.log(`Knowledge entry added`);
|
|
311
|
+
console.log(` ID: ${knowledge.id}`);
|
|
312
|
+
console.log(` Title: ${knowledge.title}`);
|
|
313
|
+
console.log(` Category: ${knowledge.category}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
console.error("Error adding knowledge entry:", error);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
// husky project list-knowledge <projectId>
|
|
322
|
+
projectCommand
|
|
323
|
+
.command("list-knowledge <projectId>")
|
|
324
|
+
.description("List knowledge entries for a project")
|
|
325
|
+
.option("--json", "Output as JSON")
|
|
326
|
+
.option("--type <type>", "Filter by category: architecture, patterns, decisions, learnings")
|
|
327
|
+
.action(async (projectId, options) => {
|
|
328
|
+
const config = ensureConfig();
|
|
329
|
+
try {
|
|
330
|
+
const url = new URL(`/api/projects/${projectId}/knowledge`, config.apiUrl);
|
|
331
|
+
if (options.type) {
|
|
332
|
+
url.searchParams.set("category", options.type);
|
|
333
|
+
}
|
|
334
|
+
const res = await fetch(url.toString(), {
|
|
335
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
336
|
+
});
|
|
337
|
+
if (!res.ok) {
|
|
338
|
+
if (res.status === 404) {
|
|
339
|
+
console.error(`Error: Project ${projectId} not found`);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
console.error(`Error: API returned ${res.status}`);
|
|
343
|
+
}
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
const knowledge = await res.json();
|
|
347
|
+
if (options.json) {
|
|
348
|
+
console.log(JSON.stringify(knowledge, null, 2));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
printKnowledgeList(projectId, knowledge);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
console.error("Error fetching knowledge entries:", error);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
// husky project delete-knowledge <projectId> <knowledgeId>
|
|
360
|
+
projectCommand
|
|
361
|
+
.command("delete-knowledge <projectId> <knowledgeId>")
|
|
362
|
+
.description("Delete a knowledge entry from a project")
|
|
363
|
+
.option("--force", "Skip confirmation")
|
|
364
|
+
.action(async (projectId, knowledgeId, options) => {
|
|
365
|
+
const config = ensureConfig();
|
|
366
|
+
if (!options.force) {
|
|
367
|
+
console.log("Warning: This will permanently delete the knowledge entry.");
|
|
368
|
+
console.log("Use --force to confirm deletion.");
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/knowledge/${knowledgeId}`, {
|
|
373
|
+
method: "DELETE",
|
|
374
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
375
|
+
});
|
|
376
|
+
if (!res.ok) {
|
|
377
|
+
if (res.status === 404) {
|
|
378
|
+
console.error(`Error: Knowledge entry not found`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
console.error(`Error: API returned ${res.status}`);
|
|
382
|
+
}
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
console.log(`Knowledge entry deleted`);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
console.error("Error deleting knowledge entry:", error);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
// ============================================
|
|
393
|
+
// OUTPUT FORMATTERS
|
|
394
|
+
// ============================================
|
|
395
|
+
function printProjects(projects) {
|
|
396
|
+
if (projects.length === 0) {
|
|
397
|
+
console.log("\n No projects found.");
|
|
398
|
+
console.log(" Create one with: husky project create <name>\n");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
console.log("\n PROJECTS");
|
|
402
|
+
console.log(" " + "-".repeat(80));
|
|
403
|
+
console.log(` ${"ID".padEnd(24)} ${"NAME".padEnd(25)} ${"STATUS".padEnd(10)} ${"WORK STATUS".padEnd(14)}`);
|
|
404
|
+
console.log(" " + "-".repeat(80));
|
|
405
|
+
for (const project of projects) {
|
|
406
|
+
const workStatusLabel = WORK_STATUS_LABELS[project.workStatus] || project.workStatus;
|
|
407
|
+
const truncatedName = project.name.length > 23 ? project.name.substring(0, 20) + "..." : project.name;
|
|
408
|
+
const statusIcon = project.status === "active" ? "+" : "-";
|
|
409
|
+
console.log(` ${project.id.padEnd(24)} ${truncatedName.padEnd(25)} ${statusIcon} ${project.status.padEnd(8)} ${workStatusLabel}`);
|
|
410
|
+
}
|
|
411
|
+
console.log(" " + "-".repeat(80));
|
|
412
|
+
console.log(` Total: ${projects.length} project(s)\n`);
|
|
413
|
+
}
|
|
414
|
+
function printProjectDetail(project) {
|
|
415
|
+
const workStatusLabel = WORK_STATUS_LABELS[project.workStatus] || project.workStatus;
|
|
416
|
+
console.log(`\n Project: ${project.name}`);
|
|
417
|
+
console.log(" " + "=".repeat(60));
|
|
418
|
+
console.log(` ID: ${project.id}`);
|
|
419
|
+
console.log(` Status: ${project.status}`);
|
|
420
|
+
console.log(` Work Status: ${workStatusLabel}`);
|
|
421
|
+
if (project.description) {
|
|
422
|
+
console.log(` Description: ${project.description}`);
|
|
423
|
+
}
|
|
424
|
+
if (project.techStack) {
|
|
425
|
+
console.log(` Tech Stack: ${project.techStack}`);
|
|
426
|
+
}
|
|
427
|
+
if (project.githubRepo) {
|
|
428
|
+
console.log(` GitHub: ${project.githubRepo}`);
|
|
429
|
+
}
|
|
430
|
+
console.log(` Color: ${project.color}`);
|
|
431
|
+
console.log(` Created: ${new Date(project.createdAt).toLocaleString()}`);
|
|
432
|
+
if (project.tasks && project.tasks.length > 0) {
|
|
433
|
+
console.log(`\n Tasks (${project.tasks.length}):`);
|
|
434
|
+
console.log(" " + "-".repeat(50));
|
|
435
|
+
for (const task of project.tasks.slice(0, 10)) {
|
|
436
|
+
const statusIcon = task.status === "done" ? "[ok]" :
|
|
437
|
+
task.status === "in_progress" ? "[->]" :
|
|
438
|
+
"[ ]";
|
|
439
|
+
console.log(` ${statusIcon} ${task.title.slice(0, 40)}`);
|
|
440
|
+
}
|
|
441
|
+
if (project.tasks.length > 10) {
|
|
442
|
+
console.log(` ... and ${project.tasks.length - 10} more`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
console.log("");
|
|
446
|
+
}
|
|
447
|
+
function printKnowledgeList(projectId, knowledge) {
|
|
448
|
+
if (knowledge.length === 0) {
|
|
449
|
+
console.log(`\n No knowledge entries found for project ${projectId}.`);
|
|
450
|
+
console.log(` Add one with: husky project add-knowledge ${projectId} --title <title> --content <content>\n`);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
console.log(`\n KNOWLEDGE - Project: ${projectId}`);
|
|
454
|
+
console.log(" " + "-".repeat(80));
|
|
455
|
+
console.log(` ${"ID".padEnd(24)} ${"CATEGORY".padEnd(14)} ${"TITLE".padEnd(35)}`);
|
|
456
|
+
console.log(" " + "-".repeat(80));
|
|
457
|
+
for (const entry of knowledge) {
|
|
458
|
+
const categoryConfig = KNOWLEDGE_CATEGORY_CONFIG[entry.category] || { label: entry.category, icon: "?" };
|
|
459
|
+
const truncatedTitle = entry.title.length > 33 ? entry.title.substring(0, 30) + "..." : entry.title;
|
|
460
|
+
console.log(` ${entry.id.padEnd(24)} [${categoryConfig.icon}] ${categoryConfig.label.padEnd(10)} ${truncatedTitle}`);
|
|
461
|
+
}
|
|
462
|
+
// Summary by category
|
|
463
|
+
const byCategory = {};
|
|
464
|
+
for (const entry of knowledge) {
|
|
465
|
+
byCategory[entry.category] = (byCategory[entry.category] || 0) + 1;
|
|
466
|
+
}
|
|
467
|
+
console.log(" " + "-".repeat(80));
|
|
468
|
+
console.log(` Total: ${knowledge.length} entries`);
|
|
469
|
+
const categoryStr = Object.entries(byCategory)
|
|
470
|
+
.map(([cat, count]) => `${KNOWLEDGE_CATEGORY_CONFIG[cat]?.label || cat}: ${count}`)
|
|
471
|
+
.join(", ");
|
|
472
|
+
console.log(` By Category: ${categoryStr}\n`);
|
|
473
|
+
}
|