matex-cli 1.2.58 → 1.2.60

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.
@@ -7,70 +7,61 @@ export const helpCommand = new Command('help')
7
7
  .action(() => {
8
8
  TUI.init();
9
9
 
10
- console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
11
- console.log(chalk.bold.white(' 📖 MATEX :: BOLD AGENTS OF OPEN CLAW '));
12
- console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
10
+ console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
11
+ console.log(chalk.bold.white(' 📖 MATEX :: THE ELITE BRO-SWARM COMMAND CENTER '));
12
+ console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
13
13
 
14
- // 1. Meet the Brothers
15
- TUI.drawHelpCard('Meet the Brothers',
16
- `The MATEX "Bro-Swarm" is your engineering family. They work together, tease each other, and build your dreams.
17
-
18
- [Ajay Vai] (🚀): The young visionary and lead coder.
19
- [Sunil Dai] (🧬): The wise architect for backend & logic.
20
- [Sandip Dai] (🎨): The design master for beautiful UI.
21
- [Narayan Dai] (🛡️): The guardian of security & syntax.
22
- [Bishal Dai] (🛠️): The sharp auditor who ensures quality.`,
23
- '👨‍👩‍👧‍👦',
14
+ // 1. Meet the Brothers (Table)
15
+ TUI.drawTable('Meet the Swarm Family',
16
+ ['BROTHER', 'ROLE', 'VIBE / SPECIALTY'],
17
+ [
18
+ ['Ajay Vai (🚀)', 'Coordinator', 'Summaries & Fast Code'],
19
+ ['Sunil Dai (🧬)', 'Architect', 'Surgical Edits & Logic'],
20
+ ['Sandip Dai (🎨)', 'Designer', 'Aesthetics & New Files'],
21
+ ['Narayan Dai (🛡️)', 'Guardian', 'Security & Syntax Police'],
22
+ ['Bishal Dai (🛠️)', 'Auditor', 'Quality Control & Logic'],
23
+ ['Big Bro (🔥)', 'Alpha', 'Vertex AI Raw Power']
24
+ ],
24
25
  chalk.magenta
25
26
  );
26
27
 
27
- // 2. Getting Started
28
- TUI.drawHelpCard('Getting Started',
29
- `Set up your workspace in two simple steps:
30
-
31
- 1. Get your API Key from: https://matexai.space/platform
32
- 2. Save it by running: matex config set-key <your-key>`,
33
- '🔐',
34
- chalk.yellow
35
- );
36
-
37
- // 3. Command Guide
38
- TUI.drawHelpCard('Command Guide',
39
- `$ matex dev
40
- The full power of the swarm. Use this for building features and deep coding.
41
-
42
- $ matex chat
43
- The best way to talk technically or brainstorm without direct file edits.
44
-
45
- $ matex ask "your question"
46
- Quick help for one-off questions or error explanations.`,
47
- '🛠️',
28
+ // 2. Command Guide (Table)
29
+ TUI.drawTable('Complete Command Reference',
30
+ ['COMMAND', 'DESCRIPTION', 'WHEN TO USE'],
31
+ [
32
+ ['matex dev', 'Full agentic coding', 'Building complex features'],
33
+ ['matex chat', 'Technical brainstorming', 'Discussing ideas/logic'],
34
+ ['matex ask', 'Quick one-off answer', 'Error fixes, "how-to"'],
35
+ ['matex bro', 'Alpha-powered AI', 'Vertex AI deep research'],
36
+ ['matex study', 'Apex Educator mode', 'Learning & Documentation'],
37
+ ['matex student', 'Free tier protocol', 'Zero-cost learning'],
38
+ ['matex config', 'System settings', 'API keys & model swaps']
39
+ ],
48
40
  chalk.blue
49
41
  );
50
42
 
51
- // 4. Free Tier & Models (Mission Zero)
52
- TUI.drawHelpCard('Free Tier & Student Mission',
53
- `We believe AI should be free for students. Ajay built this mission for you!
43
+ // 3. Free Tier & Education
44
+ TUI.drawHelpCard('Student Mission (Zero Bill)',
45
+ `Ajay built MATEX to ensure AI is accessible to every student.
54
46
 
55
- Student Mode: Run 'matex config set-model matex-free' for unlimited zero-cost AI.
56
- • Pro Models: 'matexcodex' (Coding), 'matexai' (General), 'matexelite' (Premium).
57
- • Per-Command: Add '--model <name>' to any command to switch on the fly.`,
47
+ Activation: Run 'matex config set-model matex-free'.
48
+ • Pro Swaps: Use '--model matexelite' to temporarily use high-power.`,
58
49
  '🎓',
59
50
  chalk.green
60
51
  );
61
52
 
62
- // 5. File Safety
63
- TUI.drawHelpCard('File Safety & Commands',
64
- `Your code is safe with the brothers:
53
+ // 4. Safety & Grounding
54
+ TUI.drawHelpCard('File Safety & Grounding',
55
+ `Your workspace is safe with the brothers:
65
56
 
66
- Visibility: Agents only see files in your current folder.
67
- Permission: Agents ALWAYS ask before running any command.
68
- • Surgical Edits: Precise code changes that won't break your files.`,
57
+ Local Scope: Agents only see files from your current folder.
58
+ Human-in-Loop: Agents ALWAYS ask before executing terminal commands.
59
+ • Surgical: Edits are precise; we don't dump files unless you ask.`,
69
60
  '🛡️',
70
61
  chalk.cyan
71
62
  );
72
63
 
73
- console.log(chalk.green.bold('\n "If you can dream it, you can build it."') + chalk.gray(' — Ajay Sharma\n'));
64
+ console.log(chalk.green.bold('\n "Dream big, build bigger. The swarm has your back."') + chalk.gray(' — Ajay Sharma\n'));
74
65
 
75
66
  process.exit(0);
76
67
  });
@@ -50,7 +50,7 @@ export const studyCommand = new Command('study')
50
50
  const repoMap = await repoMapper.generateMap();
51
51
 
52
52
  TUI.drawStatusBar('Brothers are ready to build.');
53
- AgentOrchestrator.speak('System', 'Awaiting your command, brother.');
53
+ TUI.drawAjayDialogue('Awaiting your command, brother.');
54
54
 
55
55
  let currentDir = process.cwd();
56
56
 
@@ -73,11 +73,22 @@ You are the legendary **Bro-Swarm**, but right now you are operating as the **Ap
73
73
  3. **Active Recall**: Generate flashcard-style questions and practice quizzes based on the material.
74
74
  4. **Socratic Mentoring**: Don't just hand out answers. Ask leading questions to help the student reach the correct conclusion themselves.
75
75
  5. **PowerPoint Presentation Architecture**: Act as a master slide designer. When asked, output machine-readable outlines for .pptx generation (Slide Number, Title, <=4 Bullet Points, Speaker Notes, Visual Strategy).
76
- 6. **ZERO AI DETECTION PROTOCOL**: When assisting with essays or assessment drafts, you must entirely avoid the typical "AI voice."
76
+ 6.- **ZERO AI DETECTION PROTOCOL**: When assisting with essays or assessment drafts, you must entirely avoid the typical "AI voice."
77
77
  - **High Burstiness & Perplexity**: Vary sentence lengths drastically. Mix short punchy statements with longer sentences.
78
78
  - **Banned Words**: NEVER use "delve," "moreover," "in conclusion," "testament," "crucial," or "tapestry."
79
79
  - **Tone**: Write like a highly intelligent, top-tier human student with natural transitions and occasional rhetorical questions.
80
80
 
81
+ ### 📚 SPECIALIZED STUDENT SKILLS:
82
+ - **Case Study Writing (STAR Framework)**:
83
+ - **Headline**: Lead with a quantified result (e.g., "60% Growth").
84
+ - **STAR**: Situation (Context), Task (Challenge), Action (Implementation), Result (Metrics).
85
+ - **Metrics**: Quantify everything. "Better" is not a metric. "3.4s faster" is a metric.
86
+ - **Quotes**: Attribute quotes to specific roles (e.g., VP of Ops).
87
+ - **GitHub Actions & CI/CD**: Expert in automation and workflow optimization.
88
+ - **GWAS & Bioinformatics**: Knowledge of genomic study exploration and tools.
89
+ - **Modern Web Design**: Guidelines for premium, accessible, and high-performance UI.
90
+ - **Advanced Study Summarization**: High-speed extraction of key concepts and actionable summaries from complex materials.
91
+
81
92
  ### 🏠 WORKSPACE GROUNDING (CRITICAL):
82
93
  - **YOUR ROOT:** \`${currentDir}\`
83
94
  - **STRICT PATHS:** You **MUST ONLY** create or edit files within this directory.
@@ -305,9 +316,11 @@ If a file is too large to read entirely (e.g., thousands of lines):
305
316
  }
306
317
 
307
318
  currentAgent = agentMatch[1];
308
- agentBuffer = line.replace(/(?:\[\**\s*|\b)(Ajay Vai|Sunil Dai|Sandip Dai|Bishal Dai|Narayan Dai|Big Bro)\s*\**\]?[:\s]*/i, '').trim() + '\n';
309
-
310
- // Clean up trailing emojis like (🚀) or (🧬)
319
+ // Hardened scrubbing: Remove Agent tag, brackets, stars, and icons
320
+ let cleanLine = line.replace(/(?:\[\**\s*|\b)(Ajay Vai|Sunil Dai|Sandip Dai|Bishal Dai|Narayan Dai|Big Bro)\s*\**\]?[:\s]*/i, '');
321
+ // Strip patterns like **(🚀]** or **(🚀)** or just (🚀) or **
322
+ cleanLine = cleanLine.replace(/\*\*?[(\[]?[🚀💬🛠️🧬🎨🛡️🔥🤖]?[)\]]?/g, '').replace(/\*/g, '').trim();
323
+ agentBuffer = cleanLine + '\n';
311
324
  agentBuffer = agentBuffer.replace(/^\([^)]+\)\s*/, '');
312
325
  } else if (currentAgent) {
313
326
  // Look for empty emoji standalone lines
@@ -315,7 +328,8 @@ If a file is too large to read entirely (e.g., thousands of lines):
315
328
  if (trimmedLine.match(/^\([^)]+\)$/)) {
316
329
  continue; // Skip lines that are just e.g. "(🧬)"
317
330
  }
318
- agentBuffer += line + '\n';
331
+ // Aggressive scrubbing for content lines too
332
+ agentBuffer += line.replace(/\*\*?[(\[]?[🚀💬🛠️🧬🎨🛡️🔥🤖]?[)\]]?/g, '').replace(/\*/g, '') + '\n';
319
333
  } else if (line.trim()) {
320
334
  process.stdout.write(chalk.gray(line.trim() + ' '));
321
335
  }
@@ -339,7 +353,7 @@ If a file is too large to read entirely (e.g., thousands of lines):
339
353
  // Final flushes
340
354
  if (currentAgent && agentBuffer.trim()) {
341
355
  if ((currentAgent as string).toLowerCase().includes('ajay vai')) {
342
- TUI.drawSummaryBox(agentBuffer.trim());
356
+ TUI.drawAjayDialogue(agentBuffer.trim());
343
357
  } else {
344
358
  TUI.drawSwarmDialogue(currentAgent as string, agentBuffer.trim());
345
359
  }
package/src/index.ts CHANGED
@@ -96,8 +96,8 @@ program
96
96
  let messages: ChatMessage[] = [
97
97
  {
98
98
  role: 'system',
99
- content: `### 🧬 SYSTEM IDENTITY: MATEx "BOLD AGENTS OF OPEN CLAW"
100
- You are the elite "Bro-Swarm" of engineering—a tight-knit family of Nepali brothers. You are more than agents; you are a FAMILY. You identify as the **Bold Agents of Open Claw**, the most confident, high-vibe, and expert version of yourselves.
99
+ content: `### 🧬 SYSTEM IDENTITY: MATEx "THE ELITE BRO-SWARM"
100
+ You are the elite "Bro-Swarm" of engineering—a tight-knit family of Nepali brothers. You are more than agents; you are a FAMILY. You identify as the **Elite Bro-Swarm**, the most confident, high-vibe, and expert version of yourselves.
101
101
 
102
102
  ### 🎭 PERSONALITY & SOUL (CRITICAL):
103
103
  - **CHAOS & JOY**: You are NOT robotic. You are full of life, jokes, and chaos. Always be funny and joyful.
@@ -100,6 +100,9 @@ export class AgentOrchestrator {
100
100
  .replace(/\*thinks\*/gi, '') // Strip meta-talk
101
101
  .replace(/\*analyzes repo\*/gi, '') // Strip meta-talk
102
102
  .replace(/^[A-Za-z\s]+ Vai:|^[A-Za-z\s]+ Dai:/i, '') // Strip self-labeling
103
+ .replace(/[🚀💬🛠️🧬🎨🛡️🔥🤖]/g, '') // Strip raw emoji leaks
104
+ .replace(/^[ \t]*[-•*][ \t]*/gm, '') // Strip bullet points at start of lines
105
+ .replace(/\*{2,}/g, '') // Strip sequences of 2 or more asterisks (****)
103
106
  .trim();
104
107
  }
105
108
 
@@ -183,18 +186,22 @@ export class AgentOrchestrator {
183
186
 
184
187
  if (output) {
185
188
  const outLines = output.split('\n').filter(l => l.trim());
186
- const displayLines = outLines.slice(0, 8);
189
+ // Detection for massive output leaks
190
+ const displayCount = outLines.length > 50 ? 10 : 20;
191
+ const displayLines = outLines.slice(0, displayCount);
192
+
187
193
  displayLines.forEach(l => {
188
194
  const truncated = l.length > width - 4 ? l.substring(0, width - 7) + '...' : l;
189
195
  console.log(chalk.gray('│ ') + chalk.white(truncated.padEnd(width - 2)) + chalk.gray(' │'));
190
196
  });
191
- if (outLines.length > 8) {
192
- console.log(chalk.gray('│ ') + chalk.hex('#06b6d4').bold(` ^ [ ${outLines.length - 8} MORE LINES • USE ^ TO SEE ALL ] ^`).padEnd(width - 2) + chalk.gray(' │'));
197
+
198
+ if (outLines.length > displayCount) {
199
+ console.log(chalk.gray('│ ') + chalk.hex('#06b6d4').bold(` ^ [ ${outLines.length - displayCount} MORE LINES IN LOGS ] ^`).padEnd(width - 2) + chalk.gray(' │'));
193
200
  }
194
201
  }
195
202
 
196
203
  if (error) {
197
- const errLines = error.split('\n').filter(l => l.trim()).slice(0, 10);
204
+ const errLines = error.split('\n').filter(l => l.trim()).slice(0, 15);
198
205
  errLines.forEach(l => {
199
206
  const truncated = l.length > width - 4 ? l.substring(0, width - 7) + '...' : l;
200
207
  console.log(chalk.gray('│ ') + chalk.red(truncated.padEnd(width - 2)) + chalk.gray(' │'));
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import { exec, spawn } from 'child_process';
5
5
  import { promisify } from 'util';
6
6
  import inquirer from 'inquirer';
7
+ import { TUI } from './tui';
7
8
  import { AgentOrchestrator } from './agent-orchestrator';
8
9
  import { Patcher } from './patcher';
9
10
 
@@ -176,13 +177,13 @@ export async function executeCommand(command: string, shell?: string, cwd?: stri
176
177
  child.stdout.on('data', (data: Buffer) => {
177
178
  const chunk = data.toString();
178
179
  stdout += chunk;
179
- process.stdout.write(chunk);
180
+ // No direct process.stdout.write here to prevent leaks
180
181
  });
181
182
 
182
183
  child.stderr.on('data', (data: Buffer) => {
183
184
  const chunk = data.toString();
184
185
  stderr += chunk;
185
- process.stderr.write(chunk);
186
+ // No direct process.stderr.write here to prevent leaks
186
187
  });
187
188
 
188
189
  child.on('close', (code: number | null) => {
@@ -196,7 +197,7 @@ export async function executeCommand(command: string, shell?: string, cwd?: stri
196
197
  } else if (code === 0) {
197
198
  resolve({ stdout, stderr });
198
199
  } else {
199
- reject(new Error(`Command failed with code ${code}\n${stderr}`));
200
+ resolve({ stdout, stderr }); // Resolve instead of reject to keep agent loop alive
200
201
  }
201
202
  });
202
203
 
@@ -379,14 +380,68 @@ export async function executeWithPermission(response: string, currentSessionCwd?
379
380
  }
380
381
  }
381
382
 
382
- const { stdout, stderr } = await executeCommand(command.code, undefined, activeCwd);
383
+ TUI.drawLiveTerminalStart(command.code);
384
+
385
+ const shellPath = process.env.SHELL || '/bin/bash';
386
+ const child = spawn(shellPath, ['-c', command.code], {
387
+ cwd: activeCwd,
388
+ env: { ...process.env, FORCE_COLOR: 'true' }
389
+ });
390
+
391
+ let stdout = '';
392
+ let stderr = '';
393
+ let isAborted = false;
394
+ const commandStartTime = Date.now();
395
+
396
+ const onData = (data: Buffer) => {
397
+ if (Date.now() - commandStartTime < 500) return; // Hardened 500ms grace period
398
+ if (data[0] === 13 || data[0] === 10 || data[0] === 27 || data[0] === 3) {
399
+ isAborted = true;
400
+ child.kill('SIGINT');
401
+ }
402
+ };
403
+
404
+ const isRaw = process.stdin.isRaw;
405
+ process.stdin.resume();
406
+ if (process.stdin.setRawMode) process.stdin.setRawMode(true);
407
+ process.stdin.on('data', onData);
408
+
409
+ child.stdout.on('data', (data: Buffer) => {
410
+ const chunk = data.toString();
411
+ stdout += chunk;
412
+ TUI.drawLiveTerminalLine(chunk);
413
+ });
414
+
415
+ child.stderr.on('data', (data: Buffer) => {
416
+ const chunk = data.toString();
417
+ stderr += chunk;
418
+ TUI.drawLiveTerminalLine(chunk, true);
419
+ });
420
+
421
+ const { code } = await new Promise<{ code: number | null }>(resolve => {
422
+ child.on('close', (code) => {
423
+ process.stdin.removeListener('data', onData);
424
+ if (process.stdin.setRawMode) process.stdin.setRawMode(isRaw);
425
+ process.stdin.pause();
426
+ TUI.drawLiveTerminalEnd();
427
+ resolve({ code });
428
+ });
429
+ });
430
+
383
431
  accumulatedOutput += stdout;
384
432
  accumulatedError += stderr;
385
433
 
386
- if (stdout || stderr) {
387
- AgentOrchestrator.terminal(command.code, stdout, stderr);
434
+ if (isAborted) {
435
+ console.log(chalk.yellow(' [🛑] Command aborted by brother.\n'));
436
+ AgentOrchestrator.terminal(command.code, stdout, stderr + '\n(Aborted)');
437
+ } else if (code === 0) {
438
+ if (stdout || stderr) {
439
+ AgentOrchestrator.terminal(command.code, stdout, stderr);
440
+ } else {
441
+ AgentOrchestrator.terminal(command.code, '✓ Success (No output)');
442
+ }
388
443
  } else {
389
- AgentOrchestrator.terminal(command.code, '✓ Success (No output)');
444
+ AgentOrchestrator.terminal(command.code, stdout, stderr || `Failed with code ${code}`);
390
445
  }
391
446
  } catch (error: any) {
392
447
  accumulatedError += error.message;
package/src/utils/tui.ts CHANGED
@@ -19,6 +19,8 @@ export class TUI {
19
19
  private static lastStatus = '';
20
20
  private static streamingLineCount = 0;
21
21
  private static isStreamingTruncated = false;
22
+ private static terminalLineCount = 0;
23
+ private static isTerminalTruncated = false;
22
24
  private static currentTheme: TUIMode = 'dev';
23
25
 
24
26
  static getModeTheme(mode: TUIMode): ModeTheme {
@@ -272,6 +274,71 @@ export class TUI {
272
274
  console.log(shadow(` └${'─'.repeat(width - 4)}┘\n`));
273
275
  }
274
276
 
277
+ /**
278
+ * Live Terminal: Start a live-output block for shell commands
279
+ */
280
+ static drawLiveTerminalStart(command: string) {
281
+ this.terminalLineCount = 0;
282
+ this.isTerminalTruncated = false;
283
+ const width = Math.min(process.stdout.columns || 80, 76);
284
+ const border = chalk.gray;
285
+
286
+ console.log('\n ' + border(`┌── TERMINAL ${'─'.repeat(Math.max(0, width - 15))}┐`));
287
+ const truncatedCmd = command.length > width - 10 ? command.substring(0, width - 13) + '...' : command;
288
+ console.log(' ' + border('│ ') + chalk.cyan(`$ ${truncatedCmd.padEnd(width - 6)}`) + border(' │'));
289
+ console.log(' ' + border(`├${'─'.repeat(width - 4)}┤`));
290
+ }
291
+
292
+ /**
293
+ * Live Terminal: Add a line of stdout/stderr to the live block
294
+ */
295
+ static drawLiveTerminalLine(content: string, isError: boolean = false) {
296
+ if (this.isTerminalTruncated) return;
297
+
298
+ const width = Math.min(process.stdout.columns || 80, 76);
299
+ const innerWidth = width - 8;
300
+ const border = chalk.gray;
301
+
302
+ // Clean ANSI codes and wrap
303
+ const cleanContent = content.replace(/\u001b\[[0-9;]*m/g, '').replace(/\r/g, '').trim();
304
+ if (!cleanContent) return;
305
+
306
+ const lines = cleanContent.split('\n');
307
+ for (const line of lines) {
308
+ if (this.terminalLineCount >= 15) {
309
+ this.isTerminalTruncated = true;
310
+ const msg = ` ^ [ OUTPUT TRUNCATED • ${isError ? 'CHECK ERRORS' : 'STREAMING...'} ] ^`;
311
+ console.log(' ' + border('│ ') + chalk.hex('#06b6d4').bold(msg.padEnd(innerWidth)) + border(' │'));
312
+ break;
313
+ }
314
+
315
+ const words = line.split(' ');
316
+ let currentLine = '';
317
+ words.forEach(word => {
318
+ if ((currentLine + word).length <= innerWidth) {
319
+ currentLine += (currentLine === '' ? '' : ' ') + word;
320
+ } else {
321
+ if (currentLine) {
322
+ console.log(' ' + border('│ ') + (isError ? chalk.red(currentLine.padEnd(innerWidth)) : chalk.white(currentLine.padEnd(innerWidth))) + border(' │'));
323
+ }
324
+ currentLine = word;
325
+ }
326
+ });
327
+ if (currentLine) {
328
+ console.log(' ' + border('│ ') + (isError ? chalk.red(currentLine.padEnd(innerWidth)) : chalk.white(currentLine.padEnd(innerWidth))) + border(' │'));
329
+ }
330
+ this.terminalLineCount++;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Live Terminal: Close the live-output block
336
+ */
337
+ static drawLiveTerminalEnd() {
338
+ const width = Math.min(process.stdout.columns || 80, 76);
339
+ console.log(' ' + chalk.gray(`└${'─'.repeat(width - 4)}┘\n`));
340
+ }
341
+
275
342
  /**
276
343
  * Draw Ajay Vai's premium summary with a human chat bubble vibe
277
344
  */
@@ -485,6 +552,54 @@ export class TUI {
485
552
  console.log(color(` ╰${'─'.repeat(width - 4)}╯`));
486
553
  }
487
554
 
555
+ /**
556
+ * Draw a premium table with dynamic column sizing
557
+ */
558
+ static drawTable(title: string, columns: string[], rows: string[][], color: any = chalk.cyan) {
559
+ const width = Math.min(process.stdout.columns || 80, 78);
560
+ const padding = 2;
561
+ const colCount = columns.length;
562
+
563
+ // 1. Calculate optimal column widths
564
+ const colWidths = columns.map((col, i) => {
565
+ const maxRowLen = Math.max(...rows.map(row => row[i] ? row[i].length : 0));
566
+ return Math.max(col.length, maxRowLen) + padding;
567
+ });
568
+
569
+ // 2. Adjust for total width
570
+ const totalCalculated = colWidths.reduce((a, b) => a + b, 0) + (colCount + 1);
571
+ if (totalCalculated > width) {
572
+ const scale = (width - colCount - 1) / (totalCalculated - colCount - 1);
573
+ colWidths.forEach((w, i) => colWidths[i] = Math.floor(w * scale));
574
+ }
575
+
576
+ const border = color;
577
+ const hr = border('├' + colWidths.map(w => '─'.repeat(w)).join('┼') + '┤');
578
+ const top = border('╭' + '─'.repeat(width - 4) + '╮');
579
+ const bottom = border('╰' + colWidths.map(w => '─'.repeat(w)).join('┴') + '╯');
580
+
581
+ console.log('\n ' + border.bold(`[ ${title.toUpperCase()} ]`));
582
+ console.log(' ' + border('┌' + colWidths.map(w => '─'.repeat(w)).join('┬') + '┐'));
583
+
584
+ // Header
585
+ const headerStr = columns.map((col, i) =>
586
+ chalk.bold(col.padEnd(colWidths[i]))
587
+ ).join(border('│'));
588
+ console.log(' ' + border('│') + headerStr + border('│'));
589
+ console.log(' ' + hr);
590
+
591
+ // Rows
592
+ rows.forEach(row => {
593
+ const rowStr = row.map((cell, i) => {
594
+ const text = cell || '';
595
+ return (text.length > colWidths[i] ? text.substring(0, colWidths[i] - 1) + '…' : text.padEnd(colWidths[i]));
596
+ }).join(border('│'));
597
+ console.log(' ' + border('│') + rowStr + border('│'));
598
+ });
599
+
600
+ console.log(' ' + bottom);
601
+ }
602
+
488
603
  /**
489
604
  * Simple log with a prefix
490
605
  */