universal-agent-memory 2.7.2 → 2.8.1

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.
@@ -0,0 +1,654 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { existsSync, statSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { execSync } from 'child_process';
6
+ import { TaskService } from '../tasks/service.js';
7
+ import { CoordinationService } from '../coordination/service.js';
8
+ import { SQLiteShortTermMemory } from '../memory/short-term/sqlite.js';
9
+ import { progressBar, stackedBar, stackedBarLegend, horizontalBarChart, table, tree, box, sectionHeader, keyValue, miniGauge, statusBadge, divider, bulletList, columns, } from './visualize.js';
10
+ import { STATUS_ICONS, TYPE_ICONS, PRIORITY_LABELS } from '../tasks/types.js';
11
+ export async function dashboardCommand(action, options = {}) {
12
+ switch (action) {
13
+ case 'overview':
14
+ await showOverview(options);
15
+ break;
16
+ case 'tasks':
17
+ await showTaskDashboard(options);
18
+ break;
19
+ case 'agents':
20
+ await showAgentDashboard(options);
21
+ break;
22
+ case 'memory':
23
+ await showMemoryDashboard(options);
24
+ break;
25
+ case 'progress':
26
+ await showProgressDashboard(options);
27
+ break;
28
+ }
29
+ }
30
+ async function showOverview(_options) {
31
+ const spinner = ora('Loading dashboard...').start();
32
+ try {
33
+ const taskService = new TaskService();
34
+ const coordService = new CoordinationService();
35
+ const stats = taskService.getStats();
36
+ const coordStatus = coordService.getStatus();
37
+ spinner.stop();
38
+ console.log('');
39
+ console.log(chalk.bold.cyan(' UAM Dashboard'));
40
+ console.log(divider(60));
41
+ console.log('');
42
+ // Task completion progress
43
+ const completedTasks = stats.byStatus.done + stats.byStatus.wont_do;
44
+ const activeTasks = stats.total - completedTasks;
45
+ console.log(sectionHeader('Task Progress'));
46
+ console.log('');
47
+ console.log(` ${progressBar(completedTasks, stats.total, 40, {
48
+ label: 'Completion',
49
+ filled: chalk.green,
50
+ })}`);
51
+ console.log('');
52
+ // Status breakdown bar
53
+ const statusSegments = [
54
+ { value: stats.byStatus.done, color: chalk.green, label: `Done ${STATUS_ICONS.done}` },
55
+ { value: stats.byStatus.in_progress, color: chalk.cyan, label: `In Progress ${STATUS_ICONS.in_progress}` },
56
+ { value: stats.byStatus.open, color: chalk.white, label: `Open ${STATUS_ICONS.open}` },
57
+ { value: stats.byStatus.blocked, color: chalk.red, label: `Blocked ${STATUS_ICONS.blocked}` },
58
+ { value: stats.byStatus.wont_do, color: chalk.dim, label: `Won't Do ${STATUS_ICONS.wont_do}` },
59
+ ];
60
+ console.log(` ${stackedBar(statusSegments, stats.total, 50)}`);
61
+ console.log(` ${stackedBarLegend(statusSegments)}`);
62
+ console.log('');
63
+ // Two-column layout: Priority vs Type
64
+ const priorityLines = [
65
+ chalk.bold(' By Priority'),
66
+ ...horizontalBarChart([
67
+ { label: 'P0 Critical', value: stats.byPriority[0], color: chalk.red },
68
+ { label: 'P1 High', value: stats.byPriority[1], color: chalk.yellow },
69
+ { label: 'P2 Medium', value: stats.byPriority[2], color: chalk.blue },
70
+ { label: 'P3 Low', value: stats.byPriority[3], color: chalk.dim },
71
+ { label: 'P4 Backlog', value: stats.byPriority[4], color: chalk.dim },
72
+ ], { maxWidth: 20, maxLabelWidth: 14 }),
73
+ ];
74
+ const typeData = Object.entries(stats.byType)
75
+ .filter(([, count]) => count > 0);
76
+ const typeLines = [
77
+ chalk.bold(' By Type'),
78
+ ...horizontalBarChart(typeData.map(([type, count]) => ({
79
+ label: `${TYPE_ICONS[type]} ${type}`,
80
+ value: count,
81
+ color: chalk.magenta,
82
+ })), { maxWidth: 20, maxLabelWidth: 14 }),
83
+ ];
84
+ const combined = columns(priorityLines, typeLines, { gap: 6, leftWidth: 42 });
85
+ for (const line of combined)
86
+ console.log(line);
87
+ console.log('');
88
+ // Agent Status
89
+ console.log(sectionHeader('Agents & Coordination'));
90
+ console.log('');
91
+ const agentItems = coordStatus.activeAgents.map(a => ({
92
+ text: `${chalk.cyan(a.name)} ${statusBadge(a.status)}${a.currentTask ? chalk.dim(` working on ${a.currentTask}`) : ''}`,
93
+ status: a.status === 'active' ? 'ok' : 'warn',
94
+ }));
95
+ if (agentItems.length > 0) {
96
+ for (const line of bulletList(agentItems))
97
+ console.log(line);
98
+ }
99
+ else {
100
+ console.log(chalk.dim(' No active agents'));
101
+ }
102
+ console.log('');
103
+ for (const line of keyValue([
104
+ ['Active Agents', coordStatus.activeAgents.length],
105
+ ['Resource Claims', coordStatus.activeClaims.length],
106
+ ['Pending Deploys', coordStatus.pendingDeploys.length],
107
+ ['Unread Messages', coordStatus.pendingMessages],
108
+ ]))
109
+ console.log(line);
110
+ // Memory summary
111
+ console.log('');
112
+ console.log(sectionHeader('Memory'));
113
+ console.log('');
114
+ const cwd = process.cwd();
115
+ const dbPath = join(cwd, 'agents/data/memory/short_term.db');
116
+ const memoryItems = [];
117
+ if (existsSync(dbPath)) {
118
+ const dbStats = statSync(dbPath);
119
+ const sizeKB = Math.round(dbStats.size / 1024);
120
+ memoryItems.push({
121
+ text: `Short-term: ${chalk.bold(sizeKB + ' KB')} ${chalk.dim(`(modified ${dbStats.mtime.toLocaleDateString()})`)}`,
122
+ status: 'ok',
123
+ });
124
+ }
125
+ else {
126
+ memoryItems.push({ text: 'Short-term: Not initialized', status: 'warn' });
127
+ }
128
+ let qdrantRunning = false;
129
+ try {
130
+ const dockerStatus = execSync('docker ps --filter name=qdrant --format "{{.Status}}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
131
+ if (dockerStatus) {
132
+ memoryItems.push({ text: `Qdrant: ${chalk.bold('Running')} ${chalk.dim(dockerStatus)}`, status: 'ok' });
133
+ qdrantRunning = true;
134
+ }
135
+ else {
136
+ memoryItems.push({ text: 'Qdrant: Stopped', status: 'warn' });
137
+ }
138
+ }
139
+ catch {
140
+ memoryItems.push({ text: 'Qdrant: Not available', status: 'warn' });
141
+ }
142
+ for (const line of bulletList(memoryItems))
143
+ console.log(line);
144
+ // Summary box
145
+ console.log('');
146
+ const summaryContent = [
147
+ `Tasks: ${chalk.bold(stats.total)} total, ${chalk.green(completedTasks + ' done')}, ${chalk.yellow(activeTasks + ' active')}`,
148
+ `Agents: ${chalk.bold(coordStatus.activeAgents.length)} active`,
149
+ `Memory: ${existsSync(dbPath) ? chalk.green('SQLite') : chalk.dim('None')} / ${qdrantRunning ? chalk.green('Qdrant') : chalk.dim('No Qdrant')}`,
150
+ ];
151
+ for (const line of box('Summary', summaryContent, { borderColor: chalk.cyan })) {
152
+ console.log(` ${line}`);
153
+ }
154
+ console.log('');
155
+ }
156
+ catch (error) {
157
+ spinner.fail('Failed to load dashboard');
158
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
159
+ }
160
+ }
161
+ async function showTaskDashboard(options) {
162
+ const spinner = ora('Loading task dashboard...').start();
163
+ try {
164
+ const service = new TaskService();
165
+ const stats = service.getStats();
166
+ const allTasks = service.list({});
167
+ const readyTasks = service.ready();
168
+ const blockedTasks = service.blocked();
169
+ spinner.stop();
170
+ console.log('');
171
+ console.log(chalk.bold.cyan(' Task Dashboard'));
172
+ console.log(divider(60));
173
+ console.log('');
174
+ // Completion gauge
175
+ const done = stats.byStatus.done + stats.byStatus.wont_do;
176
+ console.log(` ${chalk.bold('Completion')} ${miniGauge(done, stats.total, 20)} ${chalk.bold(Math.round(done / Math.max(stats.total, 1) * 100) + '%')} ${chalk.dim(`(${done}/${stats.total})`)}`);
177
+ console.log(` ${chalk.bold('In Flight ')} ${miniGauge(stats.byStatus.in_progress, stats.total, 20)} ${chalk.dim(`${stats.byStatus.in_progress} tasks`)}`);
178
+ console.log(` ${chalk.bold('Blocked ')} ${miniGauge(stats.byStatus.blocked, stats.total, 20)} ${chalk.dim(`${stats.byStatus.blocked} tasks`)}`);
179
+ console.log('');
180
+ // Status stacked bar
181
+ console.log(sectionHeader('Status Distribution'));
182
+ console.log('');
183
+ const segments = [
184
+ { value: stats.byStatus.done, color: chalk.green, label: 'Done' },
185
+ { value: stats.byStatus.in_progress, color: chalk.cyan, label: 'In Progress' },
186
+ { value: stats.byStatus.open, color: chalk.white, label: 'Open' },
187
+ { value: stats.byStatus.blocked, color: chalk.red, label: 'Blocked' },
188
+ { value: stats.byStatus.wont_do, color: chalk.dim, label: "Won't Do" },
189
+ ];
190
+ console.log(` ${stackedBar(segments, stats.total, 50)}`);
191
+ console.log(` ${stackedBarLegend(segments)}`);
192
+ console.log('');
193
+ // Priority chart
194
+ console.log(sectionHeader('Priority Breakdown'));
195
+ console.log('');
196
+ for (const line of horizontalBarChart([
197
+ { label: 'P0 Critical', value: stats.byPriority[0], color: chalk.red },
198
+ { label: 'P1 High', value: stats.byPriority[1], color: chalk.yellow },
199
+ { label: 'P2 Medium', value: stats.byPriority[2], color: chalk.blue },
200
+ { label: 'P3 Low', value: stats.byPriority[3], color: chalk.dim },
201
+ { label: 'P4 Backlog', value: stats.byPriority[4], color: chalk.dim },
202
+ ], { maxWidth: 35, maxLabelWidth: 14 })) {
203
+ console.log(line);
204
+ }
205
+ console.log('');
206
+ // Type chart
207
+ const typeData = Object.entries(stats.byType)
208
+ .filter(([, count]) => count > 0);
209
+ if (typeData.length > 0) {
210
+ console.log(sectionHeader('Type Breakdown'));
211
+ console.log('');
212
+ for (const line of horizontalBarChart(typeData.map(([type, count]) => ({
213
+ label: `${TYPE_ICONS[type]} ${type}`,
214
+ value: count,
215
+ color: chalk.magenta,
216
+ })), { maxWidth: 35, maxLabelWidth: 14 })) {
217
+ console.log(line);
218
+ }
219
+ console.log('');
220
+ }
221
+ // Ready tasks table
222
+ if (readyTasks.length > 0) {
223
+ console.log(sectionHeader('Ready to Work'));
224
+ console.log('');
225
+ const readyRows = readyTasks.slice(0, 10).map(t => ({
226
+ id: t.id,
227
+ priority: `P${t.priority}`,
228
+ type: TYPE_ICONS[t.type],
229
+ title: t.title.slice(0, 40) + (t.title.length > 40 ? '...' : ''),
230
+ }));
231
+ for (const line of table(readyRows, [
232
+ { key: 'id', header: 'ID', width: 10, color: chalk.cyan },
233
+ { key: 'priority', header: 'Pri', width: 5 },
234
+ { key: 'type', header: 'T', width: 3 },
235
+ { key: 'title', header: 'Title', width: 42 },
236
+ ])) {
237
+ console.log(line);
238
+ }
239
+ if (readyTasks.length > 10) {
240
+ console.log(chalk.dim(` ... and ${readyTasks.length - 10} more`));
241
+ }
242
+ console.log('');
243
+ }
244
+ // Blocked tasks
245
+ if (blockedTasks.length > 0) {
246
+ console.log(sectionHeader('Blocked Tasks'));
247
+ console.log('');
248
+ for (const t of blockedTasks.slice(0, 5)) {
249
+ console.log(` ${chalk.red(STATUS_ICONS.blocked)} ${chalk.cyan(t.id)} ${t.title}`);
250
+ if (t.blockedBy.length > 0) {
251
+ console.log(chalk.red(` Blocked by: ${t.blockedBy.join(', ')}`));
252
+ }
253
+ }
254
+ console.log('');
255
+ }
256
+ // In-progress tasks
257
+ const inProgress = allTasks.filter(t => t.status === 'in_progress');
258
+ if (inProgress.length > 0) {
259
+ console.log(sectionHeader('In Progress'));
260
+ console.log('');
261
+ for (const t of inProgress) {
262
+ console.log(` ${chalk.cyan(STATUS_ICONS.in_progress)} ${chalk.cyan(t.id)} ${t.title}`);
263
+ if (t.assignee)
264
+ console.log(chalk.dim(` Assigned: ${t.assignee}`));
265
+ }
266
+ console.log('');
267
+ }
268
+ // Task hierarchy tree (epics with children)
269
+ const epics = allTasks.filter(t => t.type === 'epic' && t.status !== 'done' && t.status !== 'wont_do');
270
+ if (epics.length > 0 && !options.compact) {
271
+ console.log(sectionHeader('Task Hierarchy'));
272
+ console.log('');
273
+ for (const epic of epics.slice(0, 3)) {
274
+ const children = allTasks.filter(t => t.parentId === epic.id);
275
+ const epicNode = {
276
+ label: `${chalk.bold(epic.title)} ${chalk.dim(epic.id)}`,
277
+ status: STATUS_ICONS[epic.status],
278
+ children: children.map(c => ({
279
+ label: `${c.title} ${chalk.dim(c.id)}`,
280
+ status: STATUS_ICONS[c.status],
281
+ meta: `P${c.priority} ${c.type}`,
282
+ })),
283
+ };
284
+ for (const line of tree(epicNode))
285
+ console.log(line);
286
+ }
287
+ console.log('');
288
+ }
289
+ }
290
+ catch (error) {
291
+ spinner.fail('Failed to load task dashboard');
292
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
293
+ }
294
+ }
295
+ async function showAgentDashboard(_options) {
296
+ const spinner = ora('Loading agent dashboard...').start();
297
+ try {
298
+ const coordService = new CoordinationService();
299
+ const status = coordService.getStatus();
300
+ const activeWork = coordService.getActiveWork();
301
+ spinner.stop();
302
+ console.log('');
303
+ console.log(chalk.bold.cyan(' Agent Dashboard'));
304
+ console.log(divider(60));
305
+ console.log('');
306
+ // Agent count and status
307
+ console.log(sectionHeader('Active Agents'));
308
+ console.log('');
309
+ if (status.activeAgents.length === 0) {
310
+ console.log(chalk.dim(' No active agents registered'));
311
+ }
312
+ else {
313
+ const agentRows = status.activeAgents.map(a => ({
314
+ name: a.name,
315
+ status: statusBadge(a.status),
316
+ task: a.currentTask || chalk.dim('idle'),
317
+ heartbeat: chalk.dim(a.lastHeartbeat.slice(11, 19)),
318
+ }));
319
+ for (const line of table(agentRows, [
320
+ { key: 'name', header: 'Agent', width: 18, color: chalk.cyan },
321
+ { key: 'status', header: 'Status', width: 16 },
322
+ { key: 'task', header: 'Current Task', width: 20 },
323
+ { key: 'heartbeat', header: 'Last Beat', width: 10 },
324
+ ])) {
325
+ console.log(line);
326
+ }
327
+ }
328
+ console.log('');
329
+ // Resource claims
330
+ console.log(sectionHeader('Resource Claims'));
331
+ console.log('');
332
+ if (status.activeClaims.length === 0) {
333
+ console.log(chalk.dim(' No active resource claims'));
334
+ }
335
+ else {
336
+ for (const claim of status.activeClaims) {
337
+ const lockIcon = claim.claimType === 'exclusive' ? chalk.red('EXCL') : chalk.green('SHARED');
338
+ console.log(` ${lockIcon} ${chalk.yellow(claim.resource)}`);
339
+ console.log(chalk.dim(` Agent: ${claim.agentId.slice(0, 8)}...`));
340
+ }
341
+ }
342
+ console.log('');
343
+ // Active work visualization
344
+ if (activeWork.length > 0) {
345
+ console.log(sectionHeader('Active Work'));
346
+ console.log('');
347
+ const grouped = new Map();
348
+ for (const work of activeWork) {
349
+ const existing = grouped.get(work.resource) || [];
350
+ existing.push(work);
351
+ grouped.set(work.resource, existing);
352
+ }
353
+ for (const [resource, works] of grouped) {
354
+ const hasConflict = works.length > 1;
355
+ const icon = hasConflict ? chalk.red('!!') : chalk.green('OK');
356
+ console.log(` ${icon} ${chalk.bold(resource)}`);
357
+ for (const w of works) {
358
+ console.log(` ${chalk.cyan(w.agentName || w.agentId.slice(0, 8))} ${chalk.dim(w.intentType)}`);
359
+ }
360
+ }
361
+ console.log('');
362
+ }
363
+ // Deploy queue
364
+ console.log(sectionHeader('Deploy Queue'));
365
+ console.log('');
366
+ if (status.pendingDeploys.length === 0) {
367
+ console.log(chalk.dim(' No pending deploys'));
368
+ }
369
+ else {
370
+ console.log(` ${chalk.bold(String(status.pendingDeploys.length))} pending deploy action(s)`);
371
+ const grouped = new Map();
372
+ for (const d of status.pendingDeploys) {
373
+ grouped.set(d.actionType, (grouped.get(d.actionType) || 0) + 1);
374
+ }
375
+ for (const line of horizontalBarChart([...grouped.entries()].map(([type, count]) => ({
376
+ label: type,
377
+ value: count,
378
+ color: chalk.yellow,
379
+ })), { maxWidth: 20, maxLabelWidth: 12 })) {
380
+ console.log(line);
381
+ }
382
+ }
383
+ console.log('');
384
+ // Summary
385
+ for (const line of keyValue([
386
+ ['Total Agents', status.activeAgents.length],
387
+ ['Resource Claims', status.activeClaims.length],
388
+ ['Active Work Items', activeWork.length],
389
+ ['Pending Deploys', status.pendingDeploys.length],
390
+ ['Unread Messages', status.pendingMessages],
391
+ ]))
392
+ console.log(line);
393
+ console.log('');
394
+ }
395
+ catch (error) {
396
+ spinner.fail('Failed to load agent dashboard');
397
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
398
+ }
399
+ }
400
+ async function showMemoryDashboard(_options) {
401
+ const spinner = ora('Loading memory dashboard...').start();
402
+ try {
403
+ const cwd = process.cwd();
404
+ const dbPath = join(cwd, 'agents/data/memory/short_term.db');
405
+ spinner.stop();
406
+ console.log('');
407
+ console.log(chalk.bold.cyan(' Memory Dashboard'));
408
+ console.log(divider(60));
409
+ console.log('');
410
+ // Short-term memory
411
+ console.log(sectionHeader('Short-Term Memory (SQLite)'));
412
+ console.log('');
413
+ if (existsSync(dbPath)) {
414
+ const dbStats = statSync(dbPath);
415
+ const sizeKB = Math.round(dbStats.size / 1024);
416
+ try {
417
+ const shortTermDb = new SQLiteShortTermMemory({
418
+ dbPath,
419
+ projectId: 'dashboard',
420
+ maxEntries: 9999,
421
+ });
422
+ const count = await shortTermDb.count();
423
+ await shortTermDb.close();
424
+ for (const line of keyValue([
425
+ ['Status', 'Active'],
426
+ ['Entries', count],
427
+ ['Size', `${sizeKB} KB`],
428
+ ['Last Modified', dbStats.mtime.toLocaleDateString()],
429
+ ['Path', dbPath],
430
+ ]))
431
+ console.log(line);
432
+ console.log('');
433
+ console.log(` ${chalk.bold('Capacity')} ${miniGauge(count, 50, 20)} ${chalk.dim(`${count}/50 entries`)}`);
434
+ }
435
+ catch {
436
+ console.log(` ${statusBadge('active')} ${chalk.dim(`${sizeKB} KB`)}`);
437
+ }
438
+ }
439
+ else {
440
+ console.log(` ${statusBadge('not_available')} Not initialized`);
441
+ }
442
+ console.log('');
443
+ // Qdrant status
444
+ console.log(sectionHeader('Long-Term Memory (Qdrant)'));
445
+ console.log('');
446
+ try {
447
+ const dockerStatus = execSync('docker ps --filter name=qdrant --format "{{.Status}}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
448
+ if (dockerStatus) {
449
+ console.log(` ${statusBadge('running')} ${chalk.dim(dockerStatus)}`);
450
+ try {
451
+ const dockerInspect = execSync('docker inspect --format "{{.Config.Image}}" uam-qdrant 2>/dev/null || docker inspect --format "{{.Config.Image}}" qdrant 2>/dev/null', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
452
+ if (dockerInspect) {
453
+ console.log(chalk.dim(` Image: ${dockerInspect}`));
454
+ }
455
+ }
456
+ catch { /* ignore */ }
457
+ console.log(chalk.dim(' Endpoint: http://localhost:6333'));
458
+ }
459
+ else {
460
+ console.log(` ${statusBadge('stopped')} Container not running`);
461
+ console.log(chalk.dim(' Start with: uam memory start'));
462
+ }
463
+ }
464
+ catch {
465
+ console.log(` ${statusBadge('not_available')} Docker not available`);
466
+ }
467
+ console.log('');
468
+ // Embeddings
469
+ console.log(sectionHeader('Embeddings (Ollama)'));
470
+ console.log('');
471
+ try {
472
+ const ollamaResponse = await fetch('http://localhost:11434/api/tags', {
473
+ method: 'GET',
474
+ signal: AbortSignal.timeout(2000),
475
+ });
476
+ if (ollamaResponse.ok) {
477
+ const ollamaData = await ollamaResponse.json();
478
+ const embedModels = ollamaData.models?.filter(m => m.name.includes('embed') || m.name.includes('nomic')) || [];
479
+ if (embedModels.length > 0) {
480
+ console.log(` ${statusBadge('active')}`);
481
+ for (const model of embedModels) {
482
+ const sizeMB = Math.round((model.size || 0) / 1024 / 1024);
483
+ console.log(` ${chalk.cyan(model.name)} ${chalk.dim(`${sizeMB} MB`)}`);
484
+ }
485
+ }
486
+ else {
487
+ console.log(` ${statusBadge('not_available')} No embedding models found`);
488
+ console.log(chalk.dim(' Install: ollama pull nomic-embed-text'));
489
+ }
490
+ }
491
+ else {
492
+ console.log(` ${statusBadge('stopped')} Not responding`);
493
+ }
494
+ }
495
+ catch {
496
+ console.log(` ${statusBadge('not_available')} Ollama not running`);
497
+ console.log(chalk.dim(' Install from https://ollama.ai'));
498
+ }
499
+ // Memory layers summary
500
+ console.log('');
501
+ console.log(sectionHeader('Memory Layer Architecture'));
502
+ console.log('');
503
+ const layers = {
504
+ label: chalk.bold('UAM Memory System'),
505
+ children: [
506
+ {
507
+ label: 'L1 Working Memory',
508
+ status: existsSync(dbPath) ? chalk.green('ON') : chalk.red('OFF'),
509
+ meta: 'SQLite, <1ms',
510
+ },
511
+ {
512
+ label: 'L2 Session Memory',
513
+ status: existsSync(dbPath) ? chalk.green('ON') : chalk.red('OFF'),
514
+ meta: 'SQLite, <5ms',
515
+ },
516
+ {
517
+ label: 'L3 Semantic Memory',
518
+ status: chalk.yellow('?'),
519
+ meta: 'Qdrant, ~50ms',
520
+ },
521
+ {
522
+ label: 'L4 Knowledge Graph',
523
+ status: existsSync(dbPath) ? chalk.green('ON') : chalk.red('OFF'),
524
+ meta: 'SQLite entities/rels',
525
+ },
526
+ ],
527
+ };
528
+ for (const line of tree(layers))
529
+ console.log(line);
530
+ console.log('');
531
+ }
532
+ catch (error) {
533
+ spinner.fail('Failed to load memory dashboard');
534
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
535
+ }
536
+ }
537
+ async function showProgressDashboard(_options) {
538
+ const spinner = ora('Loading progress dashboard...').start();
539
+ try {
540
+ const service = new TaskService();
541
+ const stats = service.getStats();
542
+ const allTasks = service.list({});
543
+ spinner.stop();
544
+ console.log('');
545
+ console.log(chalk.bold.cyan(' Progress Dashboard'));
546
+ console.log(divider(60));
547
+ console.log('');
548
+ const total = stats.total;
549
+ const done = stats.byStatus.done;
550
+ const wontDo = stats.byStatus.wont_do;
551
+ const inProgress = stats.byStatus.in_progress;
552
+ const blocked = stats.byStatus.blocked;
553
+ const open = stats.byStatus.open;
554
+ const completed = done + wontDo;
555
+ // Big completion percentage
556
+ const pct = total > 0 ? Math.round((completed / total) * 100) : 0;
557
+ const bigNum = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
558
+ console.log(` ${bigNum(chalk.bold(`${pct}%`))} ${chalk.dim('complete')}`);
559
+ console.log('');
560
+ console.log(` ${progressBar(completed, total, 50, {
561
+ showPercent: false,
562
+ showCount: false,
563
+ filled: pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red,
564
+ })}`);
565
+ console.log('');
566
+ // Flow breakdown
567
+ console.log(sectionHeader('Task Flow'));
568
+ console.log('');
569
+ console.log(` ${chalk.white('Open')} ${progressBar(open, total, 30, { filled: chalk.white, showPercent: true, showCount: true })}`);
570
+ console.log(` ${chalk.cyan('In Progress')} ${progressBar(inProgress, total, 30, { filled: chalk.cyan, showPercent: true, showCount: true })}`);
571
+ console.log(` ${chalk.red('Blocked')} ${progressBar(blocked, total, 30, { filled: chalk.red, showPercent: true, showCount: true })}`);
572
+ console.log(` ${chalk.green('Done')} ${progressBar(done, total, 30, { filled: chalk.green, showPercent: true, showCount: true })}`);
573
+ if (wontDo > 0) {
574
+ console.log(` ${chalk.dim("Won't Do")} ${progressBar(wontDo, total, 30, { filled: chalk.dim, showPercent: true, showCount: true })}`);
575
+ }
576
+ console.log('');
577
+ // Per-priority progress
578
+ console.log(sectionHeader('Progress by Priority'));
579
+ console.log('');
580
+ for (let p = 0; p <= 4; p++) {
581
+ const priority = p;
582
+ const priorityTasks = allTasks.filter(t => t.priority === priority);
583
+ const priorityDone = priorityTasks.filter(t => t.status === 'done' || t.status === 'wont_do').length;
584
+ const priorityTotal = priorityTasks.length;
585
+ if (priorityTotal > 0) {
586
+ const color = p === 0 ? chalk.red : p === 1 ? chalk.yellow : p === 2 ? chalk.blue : chalk.dim;
587
+ const label = PRIORITY_LABELS[priority].padEnd(14);
588
+ console.log(` ${color(label)} ${progressBar(priorityDone, priorityTotal, 25, {
589
+ filled: color,
590
+ showPercent: true,
591
+ showCount: true,
592
+ })}`);
593
+ }
594
+ }
595
+ console.log('');
596
+ // Per-type progress
597
+ const typeData = Object.entries(stats.byType)
598
+ .filter(([, count]) => count > 0);
599
+ if (typeData.length > 0) {
600
+ console.log(sectionHeader('Progress by Type'));
601
+ console.log('');
602
+ for (const [type, typeTotal] of typeData) {
603
+ const typeDone = allTasks.filter(t => t.type === type && (t.status === 'done' || t.status === 'wont_do')).length;
604
+ const label = `${TYPE_ICONS[type]} ${type}`.padEnd(14);
605
+ console.log(` ${label} ${progressBar(typeDone, typeTotal, 25, {
606
+ filled: chalk.magenta,
607
+ showPercent: true,
608
+ showCount: true,
609
+ })}`);
610
+ }
611
+ console.log('');
612
+ }
613
+ // Velocity indicator (recent completions)
614
+ const now = new Date();
615
+ const recentDone = allTasks.filter(t => {
616
+ if (t.status !== 'done' || !t.closedAt)
617
+ return false;
618
+ const closedDate = new Date(t.closedAt);
619
+ const daysDiff = (now.getTime() - closedDate.getTime()) / (1000 * 60 * 60 * 24);
620
+ return daysDiff <= 7;
621
+ });
622
+ const recentCreated = allTasks.filter(t => {
623
+ const createdDate = new Date(t.createdAt);
624
+ const daysDiff = (now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24);
625
+ return daysDiff <= 7;
626
+ });
627
+ console.log(sectionHeader('Velocity (Last 7 Days)'));
628
+ console.log('');
629
+ for (const line of keyValue([
630
+ ['Completed', `${recentDone.length} tasks`],
631
+ ['Created', `${recentCreated.length} tasks`],
632
+ ['Net Progress', `${recentDone.length - recentCreated.length > 0 ? '+' : ''}${recentDone.length - recentCreated.length}`],
633
+ ]))
634
+ console.log(line);
635
+ console.log('');
636
+ // Summary box
637
+ const summaryLines = [
638
+ `${chalk.bold(String(total))} total tasks`,
639
+ `${chalk.green(String(completed))} completed ${chalk.dim(`(${pct}%)`)}`,
640
+ `${chalk.cyan(String(inProgress))} in progress`,
641
+ `${blocked > 0 ? chalk.red(String(blocked) + ' blocked') : chalk.dim('0 blocked')}`,
642
+ `${chalk.dim(String(open))} open / awaiting`,
643
+ ];
644
+ for (const line of box('Summary', summaryLines, { borderColor: chalk.cyan })) {
645
+ console.log(` ${line}`);
646
+ }
647
+ console.log('');
648
+ }
649
+ catch (error) {
650
+ spinner.fail('Failed to load progress dashboard');
651
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
652
+ }
653
+ }
654
+ //# sourceMappingURL=dashboard.js.map