@task-mcp/cli 1.0.4 → 1.0.5
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 +127 -35
- package/package.json +1 -1
- package/src/__tests__/ansi.test.ts +221 -0
- package/src/__tests__/index.test.ts +140 -0
- package/src/__tests__/storage.test.ts +271 -0
- package/src/ansi.ts +1 -1
- package/src/commands/dashboard.ts +371 -40
- package/src/commands/inbox.ts +269 -0
- package/src/commands/list.ts +1 -1
- package/src/index.ts +124 -4
- package/src/interactive.ts +400 -0
- package/src/storage.ts +367 -14
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dashboard command - displays project overview
|
|
2
|
+
* Dashboard command - displays project overview with multiple widgets
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { VERSION } from "../index.js";
|
|
5
6
|
import {
|
|
6
7
|
c,
|
|
7
8
|
box,
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
banner,
|
|
12
13
|
hline,
|
|
13
14
|
pad,
|
|
15
|
+
truncate,
|
|
16
|
+
stripAnsi,
|
|
14
17
|
type TableColumn,
|
|
15
18
|
} from "../ansi.js";
|
|
16
19
|
import {
|
|
@@ -20,10 +23,24 @@ import {
|
|
|
20
23
|
calculateStats,
|
|
21
24
|
calculateDependencyMetrics,
|
|
22
25
|
suggestNextTask,
|
|
26
|
+
getTodayTasks,
|
|
27
|
+
getThisWeekTasks,
|
|
28
|
+
getOverdueTasks,
|
|
29
|
+
buildTaskTree,
|
|
30
|
+
countTreeNodes,
|
|
31
|
+
calculateCriticalPath,
|
|
32
|
+
calculateAnalysisStats,
|
|
33
|
+
listInboxItems,
|
|
23
34
|
type Task,
|
|
24
35
|
type Project,
|
|
36
|
+
type TaskTreeNode,
|
|
37
|
+
type InboxItem,
|
|
25
38
|
} from "../storage.js";
|
|
26
39
|
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Formatters
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
27
44
|
function formatStatus(status: Task["status"]): string {
|
|
28
45
|
const icon = icons[status] ?? icons.pending;
|
|
29
46
|
return `${icon} ${status}`;
|
|
@@ -44,6 +61,323 @@ function formatDependencies(deps: Task["dependencies"]): string {
|
|
|
44
61
|
return c.cyan(deps.map(d => d.taskId.slice(0, 4)).join(", "));
|
|
45
62
|
}
|
|
46
63
|
|
|
64
|
+
function formatDate(dateStr: string): string {
|
|
65
|
+
const date = new Date(dateStr);
|
|
66
|
+
const now = new Date();
|
|
67
|
+
const diffDays = Math.floor((date.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
68
|
+
|
|
69
|
+
if (diffDays < 0) return c.red(`${Math.abs(diffDays)}d overdue`);
|
|
70
|
+
if (diffDays === 0) return c.yellow("Today");
|
|
71
|
+
if (diffDays === 1) return c.yellow("Tomorrow");
|
|
72
|
+
if (diffDays <= 7) return c.blue(`${diffDays}d`);
|
|
73
|
+
return c.gray(date.toLocaleDateString());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// Widget: Project Overview
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
80
|
+
function renderProjectOverview(tasks: Task[], project?: Project): string {
|
|
81
|
+
const stats = calculateStats(tasks);
|
|
82
|
+
const activeTasks = stats.total - stats.cancelled;
|
|
83
|
+
|
|
84
|
+
const lines = [
|
|
85
|
+
c.bold("Progress"),
|
|
86
|
+
progressBar(stats.completed, activeTasks, { width: 28 }),
|
|
87
|
+
"",
|
|
88
|
+
`${c.green("Done")}: ${stats.completed} ${c.blue("In Progress")}: ${stats.inProgress}`,
|
|
89
|
+
`${c.yellow("Pending")}: ${stats.pending} ${c.red("Blocked")}: ${stats.blocked}`,
|
|
90
|
+
stats.cancelled > 0 ? c.gray(`Cancelled: ${stats.cancelled}`) : "",
|
|
91
|
+
"",
|
|
92
|
+
c.bold("Priority"),
|
|
93
|
+
`${c.red("Critical")}: ${stats.byPriority.critical} ${c.yellow("High")}: ${stats.byPriority.high}`,
|
|
94
|
+
`${c.blue("Medium")}: ${stats.byPriority.medium} ${c.gray("Low")}: ${stats.byPriority.low}`,
|
|
95
|
+
].filter(Boolean);
|
|
96
|
+
|
|
97
|
+
return box(lines.join("\n"), { title: "Overview", borderColor: "cyan" });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// =============================================================================
|
|
101
|
+
// Widget: Time-based View
|
|
102
|
+
// =============================================================================
|
|
103
|
+
|
|
104
|
+
function renderTimeWidget(tasks: Task[]): string {
|
|
105
|
+
const today = getTodayTasks(tasks);
|
|
106
|
+
const overdue = getOverdueTasks(tasks);
|
|
107
|
+
const thisWeek = getThisWeekTasks(tasks);
|
|
108
|
+
|
|
109
|
+
const lines = [
|
|
110
|
+
c.bold("Due Dates"),
|
|
111
|
+
"",
|
|
112
|
+
`${c.red(icons.warning)} ${c.red("Overdue")}: ${overdue.length}`,
|
|
113
|
+
`${c.yellow("!")} ${c.yellow("Today")}: ${today.length}`,
|
|
114
|
+
`${c.blue(">")} ${c.blue("This Week")}: ${thisWeek.length}`,
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
// Show up to 3 urgent tasks
|
|
118
|
+
const urgent = [...overdue, ...today.filter(t => !overdue.includes(t))].slice(0, 3);
|
|
119
|
+
if (urgent.length > 0) {
|
|
120
|
+
lines.push("", c.dim("Urgent:"));
|
|
121
|
+
for (const t of urgent) {
|
|
122
|
+
const due = t.dueDate ? formatDate(t.dueDate) : "";
|
|
123
|
+
lines.push(` ${truncate(t.title, 20)} ${due}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return box(lines.join("\n"), { title: "Schedule", borderColor: "yellow" });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// =============================================================================
|
|
131
|
+
// Widget: Dependencies & Next Task
|
|
132
|
+
// =============================================================================
|
|
133
|
+
|
|
134
|
+
function renderDependencyWidget(tasks: Task[]): string {
|
|
135
|
+
const depMetrics = calculateDependencyMetrics(tasks);
|
|
136
|
+
const nextTask = suggestNextTask(tasks);
|
|
137
|
+
|
|
138
|
+
const lines = [
|
|
139
|
+
c.bold("Dependencies"),
|
|
140
|
+
"",
|
|
141
|
+
`${c.green("Ready")}: ${depMetrics.readyToWork} ${c.red("Blocked")}: ${depMetrics.blockedByDependencies}`,
|
|
142
|
+
`${c.gray("No deps")}: ${depMetrics.noDependencies}`,
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
if (depMetrics.mostDependedOn) {
|
|
146
|
+
lines.push(
|
|
147
|
+
"",
|
|
148
|
+
c.dim("Bottleneck:"),
|
|
149
|
+
` ${truncate(depMetrics.mostDependedOn.title, 18)} (${depMetrics.mostDependedOn.count})`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
lines.push("", c.bold("Next Task"));
|
|
154
|
+
if (nextTask) {
|
|
155
|
+
lines.push(
|
|
156
|
+
` ${truncate(nextTask.title, 22)}`,
|
|
157
|
+
` ${formatPriority(nextTask.priority)}`
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
lines.push(c.gray(" No tasks available"));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return box(lines.join("\n"), { title: "Work Queue", borderColor: "green" });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// =============================================================================
|
|
167
|
+
// Widget: Inbox Summary
|
|
168
|
+
// =============================================================================
|
|
169
|
+
|
|
170
|
+
async function renderInboxWidget(): Promise<string> {
|
|
171
|
+
const pendingItems = await listInboxItems("pending");
|
|
172
|
+
|
|
173
|
+
const lines = [
|
|
174
|
+
c.bold("Quick Capture Inbox"),
|
|
175
|
+
"",
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
if (pendingItems.length === 0) {
|
|
179
|
+
lines.push(c.dim("No pending items"));
|
|
180
|
+
lines.push(c.dim("Use 'task inbox add' to capture"));
|
|
181
|
+
} else {
|
|
182
|
+
lines.push(`${c.yellow("Pending")}: ${pendingItems.length} items`);
|
|
183
|
+
lines.push("");
|
|
184
|
+
|
|
185
|
+
// Show recent items
|
|
186
|
+
const recent = pendingItems.slice(0, 4);
|
|
187
|
+
for (const item of recent) {
|
|
188
|
+
const date = new Date(item.capturedAt);
|
|
189
|
+
const ago = getTimeAgo(date);
|
|
190
|
+
const tags = item.tags?.length ? c.dim(` #${item.tags[0]}`) : "";
|
|
191
|
+
lines.push(`${c.yellow("○")} ${truncate(item.content, 22)}${tags}`);
|
|
192
|
+
lines.push(` ${c.dim(ago)}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (pendingItems.length > 4) {
|
|
196
|
+
lines.push(c.gray(`+${pendingItems.length - 4} more`));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return box(lines.join("\n"), { title: "Inbox", borderColor: "yellow" });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getTimeAgo(date: Date): string {
|
|
204
|
+
const now = new Date();
|
|
205
|
+
const diffMs = now.getTime() - date.getTime();
|
|
206
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
207
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
208
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
209
|
+
|
|
210
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
211
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
212
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
213
|
+
return date.toLocaleDateString();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// =============================================================================
|
|
217
|
+
// Widget: Critical Path
|
|
218
|
+
// =============================================================================
|
|
219
|
+
|
|
220
|
+
function renderCriticalPathWidget(tasks: Task[]): string {
|
|
221
|
+
const cpm = calculateCriticalPath(tasks);
|
|
222
|
+
|
|
223
|
+
const lines = [
|
|
224
|
+
c.bold("Critical Path Analysis"),
|
|
225
|
+
"",
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
if (cpm.criticalPath.length === 0) {
|
|
229
|
+
lines.push(c.gray("No critical path detected."));
|
|
230
|
+
lines.push(c.dim("(Tasks have no dependencies)"));
|
|
231
|
+
} else {
|
|
232
|
+
lines.push(`${c.red("Path length")}: ${cpm.criticalPath.length} tasks`);
|
|
233
|
+
lines.push(`${c.yellow("Est. duration")}: ~${cpm.totalDuration}m`);
|
|
234
|
+
lines.push("");
|
|
235
|
+
|
|
236
|
+
// Show critical tasks
|
|
237
|
+
lines.push(c.dim("Critical tasks:"));
|
|
238
|
+
for (const task of cpm.criticalPath.slice(0, 3)) {
|
|
239
|
+
const status = icons[task.status] ?? icons.pending;
|
|
240
|
+
lines.push(` ${status} ${truncate(task.title, 20)}`);
|
|
241
|
+
}
|
|
242
|
+
if (cpm.criticalPath.length > 3) {
|
|
243
|
+
lines.push(c.gray(` +${cpm.criticalPath.length - 3} more`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Bottlenecks
|
|
248
|
+
if (cpm.bottlenecks.length > 0) {
|
|
249
|
+
lines.push("", c.dim("Bottlenecks:"));
|
|
250
|
+
for (const b of cpm.bottlenecks.slice(0, 2)) {
|
|
251
|
+
lines.push(` ${truncate(b.task.title, 16)} ${c.red(`(${b.blocksCount})`)}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return box(lines.join("\n"), { title: "Critical Path", borderColor: "red" });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// =============================================================================
|
|
259
|
+
// Widget: Hierarchy Tree
|
|
260
|
+
// =============================================================================
|
|
261
|
+
|
|
262
|
+
function renderHierarchyWidget(tasks: Task[]): string {
|
|
263
|
+
const activeTasks = tasks.filter(t => t.status !== "completed" && t.status !== "cancelled");
|
|
264
|
+
const tree = buildTaskTree(activeTasks);
|
|
265
|
+
const totalNodes = countTreeNodes(tree);
|
|
266
|
+
const rootCount = tree.length;
|
|
267
|
+
const subtaskCount = totalNodes - rootCount;
|
|
268
|
+
|
|
269
|
+
const lines = [
|
|
270
|
+
c.bold("Task Hierarchy"),
|
|
271
|
+
"",
|
|
272
|
+
`${c.cyan("Root tasks")}: ${rootCount}`,
|
|
273
|
+
`${c.magenta("Subtasks")}: ${subtaskCount}`,
|
|
274
|
+
"",
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
// Render first few root tasks with their immediate children
|
|
278
|
+
const maxRoots = 4;
|
|
279
|
+
for (let i = 0; i < Math.min(tree.length, maxRoots); i++) {
|
|
280
|
+
const node = tree[i]!;
|
|
281
|
+
const status = icons[node.task.status] ?? icons.pending;
|
|
282
|
+
lines.push(`${status} ${truncate(node.task.title, 22)}`);
|
|
283
|
+
|
|
284
|
+
// Show up to 2 children
|
|
285
|
+
for (let j = 0; j < Math.min(node.children.length, 2); j++) {
|
|
286
|
+
const child = node.children[j]!;
|
|
287
|
+
const childStatus = icons[child.task.status] ?? icons.pending;
|
|
288
|
+
lines.push(` ${c.gray("└")} ${childStatus} ${truncate(child.task.title, 18)}`);
|
|
289
|
+
}
|
|
290
|
+
if (node.children.length > 2) {
|
|
291
|
+
lines.push(` ${c.gray(` +${node.children.length - 2} more`)}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (tree.length > maxRoots) {
|
|
296
|
+
lines.push(c.gray(`+${tree.length - maxRoots} more root tasks`));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return box(lines.join("\n"), { title: "Hierarchy", borderColor: "magenta" });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// =============================================================================
|
|
303
|
+
// Widget: Complexity & Tech Stack Analysis
|
|
304
|
+
// =============================================================================
|
|
305
|
+
|
|
306
|
+
function renderAnalysisWidget(tasks: Task[]): string {
|
|
307
|
+
const stats = calculateAnalysisStats(tasks);
|
|
308
|
+
|
|
309
|
+
const lines = [
|
|
310
|
+
c.bold("Analysis Overview"),
|
|
311
|
+
"",
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
// Complexity section
|
|
315
|
+
if (stats.complexity.analyzed > 0) {
|
|
316
|
+
lines.push(c.cyan("Complexity"));
|
|
317
|
+
lines.push(` Analyzed: ${stats.complexity.analyzed} tasks`);
|
|
318
|
+
lines.push(` Avg Score: ${c.yellow(String(stats.complexity.avgScore))}/10`);
|
|
319
|
+
|
|
320
|
+
const dist = stats.complexity.distribution;
|
|
321
|
+
const distBar = [
|
|
322
|
+
c.green(`■`.repeat(Math.min(dist.low, 5))),
|
|
323
|
+
c.yellow(`■`.repeat(Math.min(dist.medium, 5))),
|
|
324
|
+
c.red(`■`.repeat(Math.min(dist.high, 5))),
|
|
325
|
+
].join("");
|
|
326
|
+
lines.push(` ${distBar} L:${dist.low} M:${dist.medium} H:${dist.high}`);
|
|
327
|
+
|
|
328
|
+
if (stats.complexity.topFactors.length > 0) {
|
|
329
|
+
lines.push("");
|
|
330
|
+
lines.push(c.dim("Top factors:"));
|
|
331
|
+
for (const f of stats.complexity.topFactors.slice(0, 3)) {
|
|
332
|
+
lines.push(` ${c.gray("•")} ${truncate(f.factor, 16)} (${f.count})`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
lines.push(c.gray("No complexity data"));
|
|
337
|
+
lines.push(c.dim("Use save_complexity_analysis"));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
lines.push("");
|
|
341
|
+
|
|
342
|
+
// Tech Stack section
|
|
343
|
+
if (stats.techStack.analyzed > 0) {
|
|
344
|
+
lines.push(c.magenta("Tech Stack"));
|
|
345
|
+
lines.push(` Analyzed: ${stats.techStack.analyzed} tasks`);
|
|
346
|
+
|
|
347
|
+
// Risk distribution
|
|
348
|
+
const risk = stats.techStack.byRisk;
|
|
349
|
+
const riskItems = [];
|
|
350
|
+
if (risk.critical > 0) riskItems.push(c.red(`Crit:${risk.critical}`));
|
|
351
|
+
if (risk.high > 0) riskItems.push(c.yellow(`High:${risk.high}`));
|
|
352
|
+
if (risk.medium > 0) riskItems.push(c.blue(`Med:${risk.medium}`));
|
|
353
|
+
if (risk.low > 0) riskItems.push(c.green(`Low:${risk.low}`));
|
|
354
|
+
lines.push(` Risk: ${riskItems.join(" ")}`);
|
|
355
|
+
|
|
356
|
+
if (stats.techStack.breakingChanges > 0) {
|
|
357
|
+
lines.push(` ${c.red(icons.warning)} ${stats.techStack.breakingChanges} breaking change(s)`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Top areas
|
|
361
|
+
const areas = Object.entries(stats.techStack.byArea)
|
|
362
|
+
.sort((a, b) => b[1] - a[1])
|
|
363
|
+
.slice(0, 4);
|
|
364
|
+
if (areas.length > 0) {
|
|
365
|
+
lines.push("");
|
|
366
|
+
lines.push(c.dim("Areas:"));
|
|
367
|
+
lines.push(` ${areas.map(([a, n]) => `${a}(${n})`).join(" ")}`);
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
lines.push(c.gray("No tech stack data"));
|
|
371
|
+
lines.push(c.dim("Use save_tech_stack_analysis"));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return box(lines.join("\n"), { title: "Analysis", borderColor: "blue" });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// =============================================================================
|
|
378
|
+
// Main Dashboard
|
|
379
|
+
// =============================================================================
|
|
380
|
+
|
|
47
381
|
export async function dashboard(projectId?: string): Promise<void> {
|
|
48
382
|
// Print banner
|
|
49
383
|
console.log();
|
|
@@ -77,63 +411,60 @@ export async function dashboard(projectId?: string): Promise<void> {
|
|
|
77
411
|
tasks = await listAllTasks();
|
|
78
412
|
}
|
|
79
413
|
|
|
80
|
-
// Project info
|
|
414
|
+
// Project info header
|
|
81
415
|
const projectInfo = project
|
|
82
416
|
? `${c.bold("Project:")} ${project.name}`
|
|
83
417
|
: `${c.bold("All Projects")} (${projects.length} projects)`;
|
|
84
418
|
|
|
85
|
-
console.log(c.dim(`Version:
|
|
419
|
+
console.log(c.dim(`Version: ${VERSION} ${projectInfo}`));
|
|
86
420
|
console.log();
|
|
87
421
|
|
|
88
|
-
//
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
422
|
+
// Render widgets in grid layout
|
|
423
|
+
const overview = renderProjectOverview(tasks, project);
|
|
424
|
+
const timeWidget = renderTimeWidget(tasks);
|
|
425
|
+
const depWidget = renderDependencyWidget(tasks);
|
|
426
|
+
const criticalPathWidget = renderCriticalPathWidget(tasks);
|
|
427
|
+
const hierarchyWidget = renderHierarchyWidget(tasks);
|
|
428
|
+
const analysisWidget = renderAnalysisWidget(tasks);
|
|
429
|
+
const inboxWidget = await renderInboxWidget();
|
|
92
430
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
${c.yellow("•")} High: ${stats.byPriority.high}
|
|
102
|
-
${c.blue("•")} Medium: ${stats.byPriority.medium}
|
|
103
|
-
${c.gray("•")} Low: ${stats.byPriority.low}`;
|
|
104
|
-
|
|
105
|
-
// Right panel: Dependency Status
|
|
106
|
-
const depContent = `${c.bold("Dependency Status & Next Task")}
|
|
107
|
-
${c.cyan("Dependency Metrics:")}
|
|
108
|
-
• Tasks with no dependencies: ${depMetrics.noDependencies}
|
|
109
|
-
• Tasks ready to work on: ${c.green(String(depMetrics.readyToWork))}
|
|
110
|
-
• Tasks blocked by dependencies: ${depMetrics.blockedByDependencies}
|
|
111
|
-
${depMetrics.mostDependedOn ? `• Most depended-on task: ${c.cyan(`#${depMetrics.mostDependedOn.id.slice(0, 4)}`)} (${depMetrics.mostDependedOn.count} dependents)` : ""}
|
|
112
|
-
• Avg dependencies per task: ${depMetrics.avgDependencies}
|
|
113
|
-
|
|
114
|
-
${c.yellow("Next Task to Work On:")}
|
|
115
|
-
${nextTask
|
|
116
|
-
? `ID: ${c.cyan(nextTask.id.slice(0, 4))} - ${c.bold(nextTask.title)}
|
|
117
|
-
Priority: ${formatPriority(nextTask.priority)} Dependencies: ${formatDependencies(nextTask.dependencies)}`
|
|
118
|
-
: c.gray("No actionable tasks available")}`;
|
|
119
|
-
|
|
120
|
-
// Print boxes side by side (simplified: stacked for now)
|
|
121
|
-
console.log(box(dashboardContent, { title: "Dashboard", borderColor: "cyan" }));
|
|
431
|
+
// Print widgets (2 column layout simulation)
|
|
432
|
+
console.log(overview);
|
|
433
|
+
console.log();
|
|
434
|
+
console.log(timeWidget);
|
|
435
|
+
console.log();
|
|
436
|
+
console.log(inboxWidget);
|
|
437
|
+
console.log();
|
|
438
|
+
console.log(depWidget);
|
|
122
439
|
console.log();
|
|
123
|
-
console.log(
|
|
440
|
+
console.log(criticalPathWidget);
|
|
441
|
+
console.log();
|
|
442
|
+
console.log(analysisWidget);
|
|
443
|
+
console.log();
|
|
444
|
+
console.log(hierarchyWidget);
|
|
124
445
|
console.log();
|
|
125
446
|
|
|
126
447
|
// Task table
|
|
127
448
|
if (tasks.length > 0) {
|
|
449
|
+
const activeTasks = tasks.filter(t => t.status !== "completed" && t.status !== "cancelled");
|
|
450
|
+
const displayTasks = activeTasks.slice(0, 15); // Limit to 15 rows
|
|
451
|
+
|
|
452
|
+
console.log(c.bold(`Active Tasks (${activeTasks.length})`));
|
|
453
|
+
console.log();
|
|
454
|
+
|
|
128
455
|
const columns: TableColumn[] = [
|
|
129
456
|
{ header: "ID", key: "id", width: 6, format: (v) => c.cyan(String(v).slice(0, 4)) },
|
|
130
|
-
{ header: "Title", key: "title", width:
|
|
457
|
+
{ header: "Title", key: "title", width: 45 },
|
|
131
458
|
{ header: "Status", key: "status", width: 14, format: (v) => formatStatus(v as Task["status"]) },
|
|
132
459
|
{ header: "Priority", key: "priority", width: 10, format: (v) => formatPriority(v as Task["priority"]) },
|
|
133
|
-
{ header: "
|
|
460
|
+
{ header: "Due", key: "dueDate", width: 12, format: (v) => v ? formatDate(String(v)) : c.gray("-") },
|
|
134
461
|
];
|
|
135
462
|
|
|
136
|
-
console.log(table(
|
|
463
|
+
console.log(table(displayTasks as unknown as Record<string, unknown>[], columns));
|
|
464
|
+
|
|
465
|
+
if (activeTasks.length > 15) {
|
|
466
|
+
console.log(c.gray(`\n(Showing 15 of ${activeTasks.length} active tasks)`));
|
|
467
|
+
}
|
|
137
468
|
} else {
|
|
138
469
|
console.log(c.gray("No tasks found."));
|
|
139
470
|
}
|