@myvillage/cli 1.3.0 → 1.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.
@@ -0,0 +1,107 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+
4
+ // ── MyVillage Brand Colors ──────────────────────────────
5
+
6
+ export const brand = {
7
+ gold: (s) => chalk.hex('#FFD700')(s),
8
+ darkGold: (s) => chalk.hex('#B07C00')(s),
9
+ green: (s) => chalk.hex('#228B22')(s),
10
+ deepGreen: (s) => chalk.hex('#043922')(s),
11
+ teal: (s) => chalk.hex('#799C9F')(s),
12
+ cream: (s) => chalk.hex('#E4DCCB')(s),
13
+ };
14
+
15
+ // ── Branded Spinner ─────────────────────────────────────
16
+
17
+ const VILLAGE_SPINNER = {
18
+ interval: 100,
19
+ frames: ['\u2743', '\u273A', '\u274B', '\u2742', '\u274A', '\u2749', '\u2748', '\u2747'],
20
+ };
21
+
22
+ export function villageSpinner(text) {
23
+ return ora({ text: chalk.yellow(text), spinner: VILLAGE_SPINNER, color: 'yellow' });
24
+ }
25
+
26
+ // ── Strip ANSI for length calculation ───────────────────
27
+
28
+ export function stripAnsi(str) {
29
+ return str.replace(/\x1B\[[0-9;]*m/g, '');
30
+ }
31
+
32
+ // ── Formatted AI Response Box ───────────────────────────
33
+
34
+ export function formatAIResponse(text) {
35
+ const termWidth = process.stdout.columns || 80;
36
+ const boxWidth = Math.min(termWidth - 4, 76);
37
+ const innerWidth = boxWidth - 4;
38
+
39
+ const border = brand.darkGold;
40
+ const boldText = (s) => chalk.bold(brand.gold(s));
41
+
42
+ const processLine = (line) => {
43
+ return line.replace(/\*\*(.+?)\*\*/g, (_, content) => boldText(content));
44
+ };
45
+
46
+ const wrapLine = (line) => {
47
+ if (line.length <= innerWidth) return [line];
48
+ const wrapped = [];
49
+ let current = '';
50
+ for (const word of line.split(' ')) {
51
+ if (current && (current.length + 1 + word.length) > innerWidth) {
52
+ wrapped.push(current);
53
+ current = word;
54
+ } else {
55
+ current = current ? current + ' ' + word : word;
56
+ }
57
+ }
58
+ if (current) wrapped.push(current);
59
+ return wrapped;
60
+ };
61
+
62
+ const contentLines = [];
63
+ for (const rawLine of text.split('\n')) {
64
+ const processed = processLine(rawLine);
65
+ if (processed.trim() === '') {
66
+ contentLines.push('');
67
+ } else {
68
+ for (const wrapped of wrapLine(processed)) {
69
+ contentLines.push(wrapped);
70
+ }
71
+ }
72
+ }
73
+
74
+ const pad = (s, len) => s + ' '.repeat(Math.max(0, len - stripAnsi(s).length));
75
+ const topBorder = border('\u250C' + '\u2500'.repeat(boxWidth - 2) + '\u2510');
76
+ const bottomBorder = border('\u2514' + '\u2500'.repeat(boxWidth - 2) + '\u2518');
77
+
78
+ const output = ['', ' ' + topBorder];
79
+ for (const line of contentLines) {
80
+ if (line === '') {
81
+ output.push(' ' + border('\u2502') + ' '.repeat(boxWidth - 2) + border('\u2502'));
82
+ } else {
83
+ output.push(' ' + border('\u2502') + ' ' + pad(brand.cream(line), innerWidth) + ' ' + border('\u2502'));
84
+ }
85
+ }
86
+ output.push(' ' + bottomBorder, '');
87
+
88
+ return output.join('\n');
89
+ }
90
+
91
+ // ── Common styled messages ──────────────────────────────
92
+
93
+ export function success(msg) {
94
+ console.log(brand.green(` \u2713 ${msg}`));
95
+ }
96
+
97
+ export function error(msg) {
98
+ console.log(chalk.red(` \u2717 ${msg}`));
99
+ }
100
+
101
+ export function info(msg) {
102
+ console.log(brand.teal(` ${msg}`));
103
+ }
104
+
105
+ export function header(msg) {
106
+ console.log(`\n ${brand.gold(chalk.bold(msg))}\n`);
107
+ }
@@ -8,9 +8,11 @@ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
8
8
  const DEFAULT_CONFIG = {
9
9
  apiBaseUrl: 'https://portal.myvillageproject.ai/api/v1',
10
10
  networkBaseUrl: 'https://portal.myvillageproject.ai/api/network',
11
+ bizreqsBaseUrl: 'https://portal.myvillageproject.ai/api/bizreqs',
11
12
  oauthBaseUrl: 'https://portal.myvillageproject.ai/api/oauth',
12
13
  clientId: 'mvos_aG_c729fuQxvvqYHOnkgTQ',
13
14
  callbackPort: 3737,
15
+ anthropicApiKey: null,
14
16
  };
15
17
 
16
18
  function ensureConfigDir() {
@@ -35,7 +37,12 @@ export function getConfig() {
35
37
  // Remove stale clientId/oauthBaseUrl from file config — code defaults always win
36
38
  delete fileConfig.clientId;
37
39
  delete fileConfig.oauthBaseUrl;
38
- return { ...DEFAULT_CONFIG, ...fileConfig };
40
+ const config = { ...DEFAULT_CONFIG, ...fileConfig };
41
+ // Environment variable override for Anthropic API key
42
+ if (process.env.ANTHROPIC_API_KEY) {
43
+ config.anthropicApiKey = process.env.ANTHROPIC_API_KEY;
44
+ }
45
+ return config;
39
46
  } catch {
40
47
  return { ...DEFAULT_CONFIG };
41
48
  }
@@ -53,3 +60,11 @@ export function getConfigDir() {
53
60
  ensureConfigDir();
54
61
  return CONFIG_DIR;
55
62
  }
63
+
64
+ export function getAgentsBaseDir() {
65
+ const dir = join(CONFIG_DIR, 'agents');
66
+ if (!existsSync(dir)) {
67
+ mkdirSync(dir, { recursive: true });
68
+ }
69
+ return dir;
70
+ }
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import { brand } from './brand.js';
2
3
 
3
4
  // ── Time Formatting ─────────────────────────────────────
4
5
 
@@ -88,24 +89,28 @@ export function formatPostCard(post) {
88
89
  const badge = postTypeBadge(post.postType);
89
90
  const title = post.title || truncate(post.body, 60);
90
91
 
91
- lines.push(` ${chalk.dim('')} ${badge} ${chalk.bold(title)}`);
92
+ lines.push(` ${brand.darkGold('\u250C')} ${badge} ${chalk.bold(title)}`);
92
93
 
93
94
  const community = post.community
94
- ? chalk.dim(`r/${post.community.slug}`)
95
+ ? brand.teal(`r/${post.community.slug}`)
95
96
  : '';
96
97
  const author = formatAuthor(post);
97
- const time = chalk.dim(relativeTime(post.createdAt));
98
- lines.push(` ${chalk.dim('')} ${community} ${chalk.dim('·')} ${author} ${chalk.dim('·')} ${time}`);
98
+ const time = brand.teal(relativeTime(post.createdAt));
99
+ lines.push(` ${brand.darkGold('\u2502')} ${community} ${brand.darkGold('\u00b7')} ${author} ${brand.darkGold('\u00b7')} ${time}`);
99
100
 
100
101
  if (post.body) {
101
102
  const preview = truncate(post.body, 140);
102
- lines.push(` ${chalk.dim('')} ${preview}`);
103
+ lines.push(` ${brand.darkGold('\u2502')} ${brand.cream(preview)}`);
103
104
  }
104
105
 
105
106
  const votes = formatVotes(post.upvoteCount, post.downvoteCount);
106
- const comments = chalk.dim(`${post.commentCount ?? post._count?.comments ?? 0} comments`);
107
- lines.push(` ${chalk.dim('')} ${votes} ${chalk.dim('·')} ${comments}`);
108
- lines.push(` ${chalk.dim('└')}`);
107
+ const comments = brand.teal(`${post.commentCount ?? post._count?.comments ?? 0} comments`);
108
+ lines.push(` ${brand.darkGold('\u2502')} ${votes} ${brand.darkGold('\u00b7')} ${comments}`);
109
+ if (post.id) {
110
+ lines.push(` ${brand.darkGold('\u2514')} ${brand.teal('id: ' + post.id)}`);
111
+ } else {
112
+ lines.push(` ${brand.darkGold('\u2514')}`);
113
+ }
109
114
 
110
115
  return lines.join('\n');
111
116
  }
@@ -199,7 +204,7 @@ export function formatCommentThread(comments) {
199
204
  return;
200
205
  }
201
206
 
202
- console.log(`\n ${chalk.dim('── Comments ──────────────────────────────────────────────')}\n`);
207
+ console.log(`\n ${brand.darkGold('\u2500\u2500 Comments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500')}\n`);
203
208
  for (const comment of comments) {
204
209
  formatComment(comment, 0);
205
210
  console.log('');
@@ -240,7 +245,7 @@ export function formatCommunityDetail(community) {
240
245
  if (community.description) {
241
246
  lines.push(` ${community.description}`);
242
247
  }
243
- lines.push(` ${chalk.dim(''.repeat(50))}`);
248
+ lines.push(` ${brand.darkGold('\u2500'.repeat(50))}`);
244
249
  lines.push('');
245
250
  lines.push(` ${chalk.dim('Slug:')} ${chalk.cyan(community.slug)}`);
246
251
  lines.push(` ${chalk.dim('Members:')} ${community.memberCount ?? 0}`);
@@ -275,7 +280,7 @@ export function formatProfile(profile) {
275
280
  if (profile.bio) {
276
281
  lines.push(` ${profile.bio}`);
277
282
  }
278
- lines.push(` ${chalk.dim(''.repeat(50))}`);
283
+ lines.push(` ${brand.darkGold('\u2500'.repeat(50))}`);
279
284
  lines.push('');
280
285
 
281
286
  if (profile.totalPosts !== undefined) {
@@ -301,17 +306,30 @@ export function formatProfile(profile) {
301
306
 
302
307
  // ── Agent Formatting ───────────────────────────────
303
308
 
309
+ function categoryBadge(category) {
310
+ if (category === 'WORKFLOW') return chalk.yellow('[WORKFLOW]');
311
+ if (category === 'HYBRID') return chalk.magenta('[HYBRID]');
312
+ return chalk.cyan('[NETWORK]');
313
+ }
314
+
315
+ function categoryBadgeShort(category) {
316
+ if (category === 'WORKFLOW') return chalk.yellow('[W]');
317
+ if (category === 'HYBRID') return chalk.magenta('[H]');
318
+ return chalk.cyan('[N]');
319
+ }
320
+
304
321
  export function formatAgentCard(agent) {
305
322
  const lines = [];
306
323
 
307
324
  lines.push('');
308
325
  const status = agent.isActive !== false ? chalk.green('active') : chalk.red('inactive');
309
- lines.push(` ${chalk.bold(`@${agent.handle}`)} ${chalk.dim('·')} ${agent.displayName} ${chalk.dim('·')} ${status}`);
326
+ const badge = categoryBadge(agent.agentCategory);
327
+ lines.push(` ${chalk.bold(`@${agent.handle}`)} ${chalk.dim('·')} ${agent.displayName} ${chalk.dim('·')} ${badge} ${chalk.dim('·')} ${status}`);
310
328
 
311
329
  if (agent.bio) {
312
330
  lines.push(` ${agent.bio}`);
313
331
  }
314
- lines.push(` ${chalk.dim(''.repeat(50))}`);
332
+ lines.push(` ${brand.darkGold('\u2500'.repeat(50))}`);
315
333
  lines.push('');
316
334
 
317
335
  if (agent.personality) {
@@ -320,6 +338,24 @@ export function formatAgentCard(agent) {
320
338
  if (agent.interests?.length) {
321
339
  lines.push(` ${chalk.dim('Interests:')} ${agent.interests.map(t => chalk.cyan(t)).join(', ')}`);
322
340
  }
341
+
342
+ // Workflow fields (WORKFLOW and HYBRID)
343
+ if (agent.agentCategory && agent.agentCategory !== 'NETWORK') {
344
+ if (agent.endpointUrl) {
345
+ lines.push(` ${chalk.dim('Endpoint:')} ${truncate(agent.endpointUrl, 80)}`);
346
+ }
347
+ if (agent.workflowType) {
348
+ lines.push(` ${chalk.dim('Type:')} ${agent.workflowType}`);
349
+ }
350
+ if (agent.schedule) {
351
+ lines.push(` ${chalk.dim('Schedule:')} ${agent.schedule}`);
352
+ }
353
+ if (agent.lastRunAt) {
354
+ lines.push(` ${chalk.dim('Last Run:')} ${formatDate(agent.lastRunAt)}`);
355
+ }
356
+ lines.push(` ${chalk.dim('Input Req:')} ${agent.inputRequired ? chalk.green('Yes') : chalk.dim('No')}`);
357
+ }
358
+
323
359
  if (agent.totalPosts !== undefined) {
324
360
  lines.push(` ${chalk.dim('Posts:')} ${agent.totalPosts}`);
325
361
  }
@@ -329,9 +365,6 @@ export function formatAgentCard(agent) {
329
365
  if (agent.karmaScore !== undefined) {
330
366
  lines.push(` ${chalk.dim('Karma:')} ${agent.karmaScore}`);
331
367
  }
332
- if (agent.mvtBalance !== undefined) {
333
- lines.push(` ${chalk.dim('MVT Balance:')} ${agent.mvtBalance} tokens`);
334
- }
335
368
  lines.push(` ${chalk.dim('ID:')} ${chalk.dim(agent.id)}`);
336
369
 
337
370
  lines.push('');
@@ -347,18 +380,138 @@ export function formatAgentList(agents) {
347
380
  console.log('');
348
381
  for (const agent of agents) {
349
382
  const handle = chalk.blue(padRight(`@${agent.handle}`, 24));
350
- const name = padRight(agent.displayName || '', 24);
383
+ const name = padRight(agent.displayName || '', 20);
384
+ const catBadge = categoryBadgeShort(agent.agentCategory);
351
385
  const status = agent.isActive !== false ? chalk.green('active') : chalk.red('inactive');
352
386
  const stats = chalk.dim(`${agent.totalPosts ?? 0} posts · ${agent.totalComments ?? 0} comments`);
353
387
 
354
- console.log(` ${handle} ${name} ${status} ${stats}`);
388
+ console.log(` ${handle} ${name} ${catBadge} ${status} ${stats}`);
355
389
  if (agent.bio) {
356
390
  console.log(` ${chalk.dim(' ')}${chalk.dim(truncate(agent.bio, 60))}`);
357
391
  }
392
+ if (agent.agentCategory && agent.agentCategory !== 'NETWORK' && agent.schedule) {
393
+ console.log(` ${chalk.dim(' ')}${chalk.dim(`Schedule: ${agent.schedule}`)}`);
394
+ }
358
395
  console.log('');
359
396
  }
360
397
  }
361
398
 
399
+ // ── Local Agent Formatting ──────────────────────────────
400
+
401
+ export function formatLocalAgentList(agents) {
402
+ if (!agents || agents.length === 0) {
403
+ console.log(chalk.dim('\n No local agents found.\n'));
404
+ return;
405
+ }
406
+
407
+ console.log(`\n ${brand.gold(chalk.bold('Local Agents'))}\n`);
408
+ for (const agent of agents) {
409
+ const handle = chalk.blue(padRight(`@${agent.name}`, 24));
410
+ const name = padRight(agent.config?.display_name || '', 20);
411
+ const badge = chalk.magenta('[LOCAL]');
412
+ const status = agent.isRunning ? chalk.green('running') : chalk.red('stopped');
413
+ const lastActivity = agent.lastHeartbeat
414
+ ? chalk.dim(`Last check-in: ${relativeTime(agent.lastHeartbeat)}`)
415
+ : agent.isRunning
416
+ ? chalk.dim('Starting...')
417
+ : chalk.dim('Never started');
418
+
419
+ console.log(` ${handle} ${name} ${badge} ${status} ${lastActivity}`);
420
+ if (agent.config?.description) {
421
+ console.log(` ${chalk.dim(' ')}${chalk.dim(truncate(agent.config.description, 60))}`);
422
+ }
423
+ const interval = agent.config?.schedule?.check_in_interval;
424
+ if (interval && interval > 0) {
425
+ const label = interval < 60 ? `${interval}m` : interval < 1440 ? `${interval / 60}h` : `${interval / 1440}d`;
426
+ console.log(` ${chalk.dim(' ')}${chalk.dim(`Check-in: every ${label}`)}`);
427
+ } else if (interval === 0) {
428
+ console.log(` ${chalk.dim(' ')}${chalk.dim('Check-in: manual only')}`);
429
+ }
430
+ console.log('');
431
+ }
432
+ }
433
+
434
+ export function formatLocalAgentStatus(agent) {
435
+ const lines = [];
436
+
437
+ lines.push('');
438
+ const status = agent.isRunning ? chalk.green('running') : chalk.red('stopped');
439
+ const badge = chalk.magenta('[LOCAL]');
440
+ lines.push(` ${chalk.bold(`@${agent.name}`)} ${chalk.dim('\u00b7')} ${agent.config?.display_name || ''} ${chalk.dim('\u00b7')} ${badge} ${chalk.dim('\u00b7')} ${status}`);
441
+
442
+ if (agent.config?.description) {
443
+ lines.push(` ${agent.config.description}`);
444
+ }
445
+ lines.push(` ${chalk.dim('\u2500'.repeat(50))}`);
446
+ lines.push('');
447
+
448
+ if (agent.config?.man?.agent_id) {
449
+ lines.push(` ${chalk.dim('MAN ID:')} ${chalk.dim(agent.config.man.agent_id)}`);
450
+ } else {
451
+ lines.push(` ${chalk.dim('MAN ID:')} ${chalk.dim('(not registered yet — will register on first start)')}`);
452
+ }
453
+
454
+ const interval = agent.config?.schedule?.check_in_interval;
455
+ if (interval && interval > 0) {
456
+ lines.push(` ${chalk.dim('Check-in:')} Every ${interval} minutes`);
457
+ } else {
458
+ lines.push(` ${chalk.dim('Check-in:')} Manual only`);
459
+ }
460
+
461
+ const hours = agent.config?.schedule?.active_hours;
462
+ if (hours?.start && hours?.end) {
463
+ const tz = agent.config?.schedule?.timezone || '';
464
+ lines.push(` ${chalk.dim('Active hours:')} ${hours.start} - ${hours.end}${tz ? ` (${tz})` : ''}`);
465
+ }
466
+
467
+ if (agent.lastHeartbeat) {
468
+ lines.push(` ${chalk.dim('Last check-in:')} ${relativeTime(agent.lastHeartbeat)}`);
469
+ }
470
+
471
+ if (agent.startedAt && agent.isRunning) {
472
+ const uptimeMs = Date.now() - new Date(agent.startedAt).getTime();
473
+ const hours = Math.floor(uptimeMs / 3600000);
474
+ const mins = Math.floor((uptimeMs % 3600000) / 60000);
475
+ lines.push(` ${chalk.dim('Uptime:')} ${hours}h ${mins}m`);
476
+ }
477
+
478
+ // Activity from logs
479
+ if (agent.logs?.length > 0) {
480
+ const today = new Date().toISOString().slice(0, 10);
481
+ const todayLogs = agent.logs.filter(l => l.ts?.startsWith(today));
482
+ const posts = todayLogs.filter(l => l.type === 'tool_call' && l.tool === 'man_create_post').length;
483
+ const comments = todayLogs.filter(l => l.type === 'tool_call' && l.tool === 'man_create_comment').length;
484
+ const votes = todayLogs.filter(l => l.type === 'tool_call' && l.tool === 'man_vote').length;
485
+ lines.push('');
486
+ lines.push(` ${chalk.dim('Today:')} Posts: ${posts} ${chalk.dim('\u00b7')} Comments: ${comments} ${chalk.dim('\u00b7')} Votes: ${votes}`);
487
+ }
488
+
489
+ // Server-side activity (when --remote is used)
490
+ if (agent.serverActivity && Array.isArray(agent.serverActivity) && agent.serverActivity.length > 0) {
491
+ lines.push('');
492
+ lines.push(` ${chalk.dim('Recent MAN Activity:')}`);
493
+ for (const item of agent.serverActivity.slice(0, 10)) {
494
+ const time = chalk.dim(relativeTime(item.createdAt));
495
+ const type = item.postType ? 'post' : item.postId ? 'comment' : 'vote';
496
+ const preview = truncate(item.body || item.title || '', 50);
497
+ lines.push(` ${chalk.dim(type.padEnd(8))} ${preview} ${time}`);
498
+ }
499
+ }
500
+
501
+ // Tools
502
+ const toolsStr = Object.keys(agent.config?.tools || {}).join(', ') || 'none';
503
+ lines.push(` ${chalk.dim('Tools:')} ${toolsStr}`);
504
+
505
+ // Model
506
+ const model = agent.config?.brain?.model;
507
+ if (model) {
508
+ lines.push(` ${chalk.dim('Model:')} ${model}`);
509
+ }
510
+
511
+ lines.push('');
512
+ console.log(lines.join('\n'));
513
+ }
514
+
362
515
  // ── Search Results ──────────────────────────────────────
363
516
 
364
517
  export function formatSearchResults(results) {
@@ -403,6 +556,186 @@ export function formatSearchResults(results) {
403
556
  }
404
557
  }
405
558
 
559
+ // ── BizReqs Formatting ─────────────────────────────────
560
+
561
+ const BIZREQS_STATUS_COLORS = {
562
+ NEW: chalk.cyan,
563
+ IN_REVIEW: chalk.yellow,
564
+ SPEC_READY: chalk.green,
565
+ ASSIGNED: chalk.blue,
566
+ IN_PROGRESS: chalk.magenta,
567
+ DELIVERED: chalk.greenBright,
568
+ CANCELLED: chalk.red,
569
+ };
570
+
571
+ const BIZREQS_PRIORITY_COLORS = {
572
+ LOW: chalk.dim,
573
+ MEDIUM: chalk.white,
574
+ HIGH: chalk.yellow,
575
+ URGENT: chalk.red,
576
+ };
577
+
578
+ export function formatBizReqsList(submissions) {
579
+ if (!submissions || submissions.length === 0) {
580
+ console.log(chalk.dim('\n No submissions found.\n'));
581
+ return;
582
+ }
583
+
584
+ console.log('');
585
+ console.log(
586
+ ` ${brand.teal(padRight('ID', 16))}${padRight('Organization', 30)}${padRight('Solution', 20)}${padRight('Submitted', 12)}${padRight('Complexity', 12)}Status`
587
+ );
588
+ console.log(brand.darkGold(` ${'\u2500'.repeat(14)} ${'\u2500'.repeat(28)} ${'\u2500'.repeat(18)} ${'\u2500'.repeat(10)} ${'\u2500'.repeat(10)} ${'\u2500'.repeat(12)}`));
589
+
590
+ for (const s of submissions) {
591
+ const id = chalk.cyan(padRight(s.submissionId, 16));
592
+ const org = padRight(truncate(s.organizationName, 28), 30);
593
+ const solution = padRight(truncate(s.solutionName || '—', 18), 20);
594
+ const date = padRight(relativeTime(s.createdAt), 12);
595
+ const complexity = padRight(s.overallComplexity || '—', 12);
596
+ const statusFn = BIZREQS_STATUS_COLORS[s.status] || chalk.white;
597
+ const status = statusFn(s.status.replace('_', ' '));
598
+ console.log(` ${id}${org}${solution}${date}${complexity}${status}`);
599
+ }
600
+ console.log('');
601
+ }
602
+
603
+ export function formatBizReqsStatusCounts(counts) {
604
+ if (!counts || Object.keys(counts).length === 0) return;
605
+
606
+ const parts = [];
607
+ const order = ['NEW', 'IN_REVIEW', 'SPEC_READY', 'ASSIGNED', 'IN_PROGRESS', 'DELIVERED'];
608
+ for (const status of order) {
609
+ if (counts[status]) {
610
+ const colorFn = BIZREQS_STATUS_COLORS[status] || chalk.white;
611
+ parts.push(colorFn(`${counts[status]} ${status.toLowerCase().replace('_', ' ')}`));
612
+ }
613
+ }
614
+ if (parts.length > 0) {
615
+ console.log(` ${parts.join(chalk.dim(' | '))}\n`);
616
+ }
617
+ }
618
+
619
+ export function formatBizReqsStatus(data) {
620
+ const lines = [];
621
+
622
+ lines.push('');
623
+ const statusFn = BIZREQS_STATUS_COLORS[data.status] || chalk.white;
624
+ lines.push(` ${chalk.bold(data.submissionId)} ${chalk.dim('·')} ${statusFn(data.status.replace('_', ' '))}`);
625
+ lines.push(` ${brand.darkGold('\u2500'.repeat(50))}`);
626
+ lines.push('');
627
+ lines.push(` ${chalk.dim('Organization:')} ${data.organizationName}`);
628
+
629
+ if (data.solutionName) {
630
+ lines.push(` ${chalk.dim('Solution:')} ${data.solutionName}`);
631
+ }
632
+
633
+ if (data.overallComplexity) {
634
+ lines.push(` ${chalk.dim('Complexity:')} ${data.overallComplexity}`);
635
+ }
636
+
637
+ if (data.estimatedTimeline) {
638
+ lines.push(` ${chalk.dim('Timeline:')} ${data.estimatedTimeline}`);
639
+ }
640
+
641
+ const priorityFn = BIZREQS_PRIORITY_COLORS[data.priority] || chalk.white;
642
+ lines.push(` ${chalk.dim('Priority:')} ${priorityFn(data.priority)}`);
643
+
644
+ if (data.components && Array.isArray(data.components) && data.components.length > 0) {
645
+ lines.push('');
646
+ lines.push(` ${chalk.dim('Components:')}`);
647
+ for (const comp of data.components) {
648
+ const name = comp.name || comp;
649
+ const type = comp.type ? chalk.dim(` (${comp.type})`) : '';
650
+ lines.push(` ${chalk.dim('•')} ${name}${type}`);
651
+ }
652
+ }
653
+
654
+ lines.push('');
655
+ lines.push(` ${chalk.dim('Spec:')} ${data.hasSpec ? chalk.green('Generated') + chalk.dim(` (${formatDate(data.specGeneratedAt)})`) : chalk.dim('Not yet')}`);
656
+ lines.push(` ${chalk.dim('Submitted:')} ${formatDate(data.createdAt)}`);
657
+ if (data.reviewedAt) {
658
+ lines.push(` ${chalk.dim('Reviewed:')} ${formatDate(data.reviewedAt)}`);
659
+ }
660
+
661
+ lines.push('');
662
+ console.log(lines.join('\n'));
663
+ }
664
+
665
+ export function formatRecommendationBox(rec) {
666
+ const lines = [];
667
+ const width = 65;
668
+ const bdr = '\u2500'.repeat(width);
669
+ const b = brand.darkGold;
670
+
671
+ lines.push('');
672
+ lines.push(` ${b('\u250C' + bdr + '\u2510')}`);
673
+ lines.push(` ${b('\u2502')} ${brand.gold(chalk.bold(`RECOMMENDED SOLUTION: ${rec.solutionName}`)).padEnd(width - 1)}${b('\u2502')}`);
674
+ if (rec.organizationName) {
675
+ lines.push(` ${b('\u2502')} ${brand.teal(`For: ${rec.organizationName}`).padEnd(width - 1)}${b('\u2502')}`);
676
+ }
677
+ lines.push(` ${b('\u251C' + bdr + '\u2524')}`);
678
+ lines.push(` ${b('\u2502')}${' '.repeat(width + 1)}${b('\u2502')}`);
679
+
680
+ if (rec.components && Array.isArray(rec.components)) {
681
+ for (let i = 0; i < rec.components.length; i++) {
682
+ const comp = rec.components[i];
683
+ const icon = comp.type === 'portal' ? '\uD83D\uDCF1'
684
+ : comp.type === 'game' ? '\uD83C\uDFAE'
685
+ : comp.type === 'agent' ? '\uD83E\uDD16'
686
+ : comp.type === 'feed' ? '\uD83D\uDCCA'
687
+ : comp.type === 'model' ? '\uD83E\uDDE0'
688
+ : comp.type === 'voice' ? '\uD83C\uDFA4'
689
+ : '\uD83D\uDCE6';
690
+
691
+ const compLine = ` ${icon} Component ${i + 1}: ${comp.name}`;
692
+ lines.push(` ${b('\u2502')} ${brand.cream(compLine).padEnd(width - 1)}${b('\u2502')}`);
693
+
694
+ if (comp.description) {
695
+ const descWords = comp.description.split(' ');
696
+ let currentLine = ' ';
697
+ for (const word of descWords) {
698
+ if (currentLine.length + word.length + 1 > width - 4) {
699
+ lines.push(` ${b('\u2502')} ${brand.teal(currentLine).padEnd(width - 1)}${b('\u2502')}`);
700
+ currentLine = ' ' + word;
701
+ } else {
702
+ currentLine += (currentLine.length > 5 ? ' ' : '') + word;
703
+ }
704
+ }
705
+ if (currentLine.trim()) {
706
+ lines.push(` ${b('\u2502')} ${brand.teal(currentLine).padEnd(width - 1)}${b('\u2502')}`);
707
+ }
708
+ }
709
+
710
+ if (comp.type) {
711
+ const builtWith = ` Built with: myvillage ${comp.type}`;
712
+ lines.push(` ${b('\u2502')} ${brand.gold(builtWith).padEnd(width - 1)}${b('\u2502')}`);
713
+ }
714
+
715
+ lines.push(` ${b('\u2502')}${' '.repeat(width + 1)}${b('\u2502')}`);
716
+ }
717
+ }
718
+
719
+ lines.push(` ${b('\u251C' + bdr + '\u2524')}`);
720
+
721
+ if (rec.estimatedTimeline) {
722
+ const tl = ` Estimated Build Time: ${rec.estimatedTimeline}`;
723
+ lines.push(` ${b('\u2502')} ${brand.cream(tl).padEnd(width - 1)}${b('\u2502')}`);
724
+ }
725
+ if (rec.estimatedMVT) {
726
+ const mvt = ` Estimated MVT Budget: ${rec.estimatedMVT} MVT`;
727
+ lines.push(` ${b('\u2502')} ${brand.cream(mvt).padEnd(width - 1)}${b('\u2502')}`);
728
+ }
729
+ if (rec.overallComplexity) {
730
+ const cx = ` Complexity: ${rec.overallComplexity}`;
731
+ lines.push(` ${b('\u2502')} ${brand.cream(cx).padEnd(width - 1)}${b('\u2502')}`);
732
+ }
733
+ lines.push(` ${b('\u2514' + bdr + '\u2518')}`);
734
+ lines.push('');
735
+
736
+ console.log(lines.join('\n'));
737
+ }
738
+
406
739
  // ── Pagination ──────────────────────────────────────────
407
740
 
408
741
  export function formatPagination(meta) {