@cnrai/pave 0.3.32 → 0.3.34

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.
Files changed (83) hide show
  1. package/MARKETPLACE.md +406 -0
  2. package/README.md +218 -21
  3. package/build-binary.js +591 -0
  4. package/build-npm.js +537 -0
  5. package/build.js +230 -0
  6. package/check-binary.js +26 -0
  7. package/deploy.sh +95 -0
  8. package/index.js +5775 -0
  9. package/lib/agent-registry.js +1037 -0
  10. package/lib/args-parser.js +837 -0
  11. package/lib/blessed-widget-patched.js +93 -0
  12. package/lib/cli-markdown.js +590 -0
  13. package/lib/compaction.js +153 -0
  14. package/lib/duration.js +94 -0
  15. package/lib/hash.js +22 -0
  16. package/lib/marketplace.js +866 -0
  17. package/lib/memory-config.js +166 -0
  18. package/lib/skill-manager.js +891 -0
  19. package/lib/soul.js +31 -0
  20. package/lib/tool-output-formatter.js +180 -0
  21. package/package.json +35 -33
  22. package/start-pave.sh +149 -0
  23. package/status.js +271 -0
  24. package/test/abort-stream.test.js +445 -0
  25. package/test/agent-auto-compaction.test.js +552 -0
  26. package/test/agent-comm-abort.test.js +95 -0
  27. package/test/agent-comm.test.js +598 -0
  28. package/test/agent-inbox.test.js +576 -0
  29. package/test/agent-init.test.js +264 -0
  30. package/test/agent-interrupt.test.js +314 -0
  31. package/test/agent-lifecycle.test.js +520 -0
  32. package/test/agent-log-files.test.js +349 -0
  33. package/test/agent-mode.manual-test.js +392 -0
  34. package/test/agent-parsing.test.js +228 -0
  35. package/test/agent-post-stream-idle.test.js +762 -0
  36. package/test/agent-registry.test.js +359 -0
  37. package/test/agent-rm.test.js +442 -0
  38. package/test/agent-spawn.test.js +933 -0
  39. package/test/agent-status-api.test.js +624 -0
  40. package/test/agent-update.test.js +435 -0
  41. package/test/args-parser.test.js +391 -0
  42. package/test/auto-compaction-chat.manual-test.js +227 -0
  43. package/test/auto-compaction.test.js +941 -0
  44. package/test/build-config.test.js +120 -0
  45. package/test/build-npm.test.js +388 -0
  46. package/test/chat-command.test.js +137 -0
  47. package/test/chat-leading-lines.test.js +159 -0
  48. package/test/config-flag.test.js +272 -0
  49. package/test/cursor-drift.test.js +135 -0
  50. package/test/debug-require.js +23 -0
  51. package/test/dir-migration.test.js +323 -0
  52. package/test/duration.test.js +229 -0
  53. package/test/ghostty-term.test.js +202 -0
  54. package/test/http500-backoff.test.js +854 -0
  55. package/test/integration.test.js +86 -0
  56. package/test/memory-guard-env.test.js +220 -0
  57. package/test/pr233-fixes.test.js +259 -0
  58. package/test/run-agent-init.js +297 -0
  59. package/test/run-all.js +64 -0
  60. package/test/run-config-flag.js +159 -0
  61. package/test/run-cursor-drift.js +82 -0
  62. package/test/run-session-path.js +154 -0
  63. package/test/run-tests.js +643 -0
  64. package/test/sandbox-redirect.test.js +202 -0
  65. package/test/session-path.test.js +132 -0
  66. package/test/shebang-strip.test.js +241 -0
  67. package/test/soul-reinject.test.js +1027 -0
  68. package/test/soul-reread.test.js +281 -0
  69. package/test/tool-output-formatter.test.js +486 -0
  70. package/test/tool-output-gating.test.js +143 -0
  71. package/test/tool-states.test.js +167 -0
  72. package/test/tools-flag.test.js +65 -0
  73. package/test/tui-attach.test.js +1255 -0
  74. package/test/tui-compaction.test.js +354 -0
  75. package/test/tui-wrap.test.js +568 -0
  76. package/test-binary.js +52 -0
  77. package/test-binary2.js +36 -0
  78. package/LICENSE +0 -21
  79. package/pave.js +0 -2
  80. package/sandbox/SandboxRunner.js +0 -1
  81. package/sandbox/pave-run.js +0 -2
  82. package/sandbox/permission.js +0 -1
  83. package/sandbox/utils/yaml.js +0 -1
@@ -0,0 +1,93 @@
1
+ /**
2
+ * widget.js - patched version for bundling
3
+ *
4
+ * Original blessed uses dynamic require('./widgets/' + file)
5
+ * which esbuild cannot bundle. This version uses static requires.
6
+ */
7
+
8
+ const widget = exports;
9
+
10
+ widget.classes = [
11
+ 'Node',
12
+ 'Screen',
13
+ 'Element',
14
+ 'Box',
15
+ 'Text',
16
+ 'Line',
17
+ 'ScrollableBox',
18
+ 'ScrollableText',
19
+ 'BigText',
20
+ 'List',
21
+ 'Form',
22
+ 'Input',
23
+ 'Textarea',
24
+ 'Textbox',
25
+ 'Button',
26
+ 'ProgressBar',
27
+ 'FileManager',
28
+ 'Checkbox',
29
+ 'RadioSet',
30
+ 'RadioButton',
31
+ 'Prompt',
32
+ 'Question',
33
+ 'Message',
34
+ 'Loading',
35
+ 'Listbar',
36
+ 'Log',
37
+ 'Table',
38
+ 'ListTable',
39
+ 'Terminal',
40
+ 'Image',
41
+ 'ANSIImage',
42
+ 'OverlayImage',
43
+ 'Video',
44
+ 'Layout',
45
+ ];
46
+
47
+ // Static requires - esbuild can bundle these
48
+ widget.Node = widget.node = require('./widgets/node');
49
+ widget.Screen = widget.screen = require('./widgets/screen');
50
+ widget.Element = widget.element = require('./widgets/element');
51
+ widget.Box = widget.box = require('./widgets/box');
52
+ widget.Text = widget.text = require('./widgets/text');
53
+ widget.Line = widget.line = require('./widgets/line');
54
+ widget.ScrollableBox = widget.scrollablebox = require('./widgets/scrollablebox');
55
+ widget.ScrollableText = widget.scrollabletext = require('./widgets/scrollabletext');
56
+ widget.BigText = widget.bigtext = require('./widgets/bigtext');
57
+ widget.List = widget.list = require('./widgets/list');
58
+ widget.Form = widget.form = require('./widgets/form');
59
+ widget.Input = widget.input = require('./widgets/input');
60
+ widget.Textarea = widget.textarea = require('./widgets/textarea');
61
+ widget.Textbox = widget.textbox = require('./widgets/textbox');
62
+ widget.Button = widget.button = require('./widgets/button');
63
+ widget.ProgressBar = widget.progressbar = require('./widgets/progressbar');
64
+ widget.FileManager = widget.filemanager = require('./widgets/filemanager');
65
+ widget.Checkbox = widget.checkbox = require('./widgets/checkbox');
66
+ widget.RadioSet = widget.radioset = require('./widgets/radioset');
67
+ widget.RadioButton = widget.radiobutton = require('./widgets/radiobutton');
68
+ widget.Prompt = widget.prompt = require('./widgets/prompt');
69
+ widget.Question = widget.question = require('./widgets/question');
70
+ widget.Message = widget.message = require('./widgets/message');
71
+ widget.Loading = widget.loading = require('./widgets/loading');
72
+ widget.Listbar = widget.listbar = require('./widgets/listbar');
73
+ widget.Log = widget.log = require('./widgets/log');
74
+ widget.Table = widget.table = require('./widgets/table');
75
+ widget.ListTable = widget.listtable = require('./widgets/listtable');
76
+ widget.Terminal = widget.terminal = require('./widgets/terminal');
77
+ widget.Image = widget.image = require('./widgets/image');
78
+ widget.ANSIImage = widget.ansiimage = require('./widgets/ansiimage');
79
+ widget.OverlayImage = widget.overlayimage = require('./widgets/overlayimage');
80
+ widget.Video = widget.video = require('./widgets/video');
81
+ widget.Layout = widget.layout = require('./widgets/layout');
82
+
83
+ // Aliases
84
+ widget.aliases = {
85
+ ListBar: 'Listbar',
86
+ PNG: 'ANSIImage',
87
+ };
88
+
89
+ Object.keys(widget.aliases).forEach((key) => {
90
+ const name = widget.aliases[key];
91
+ widget[key] = widget[name];
92
+ widget[key.toLowerCase()] = widget[name];
93
+ });
@@ -0,0 +1,590 @@
1
+ /**
2
+ * CLI Markdown Renderer with Theme Support
3
+ * Renders markdown to ANSI-colored terminal output
4
+ * For use with stdout (not blessed TUI)
5
+ *
6
+ * Supports PAVE_THEME environment variable:
7
+ * - 'neon' (default) - Original cyan/green theme
8
+ * - 'dracula' - Pink/purple Dracula theme
9
+ */
10
+
11
+ // ANSI escape codes
12
+ const ANSI = {
13
+ // Reset
14
+ reset: '\x1b[0m',
15
+
16
+ // Styles
17
+ bold: '\x1b[1m',
18
+ dim: '\x1b[2m',
19
+ italic: '\x1b[3m',
20
+ underline: '\x1b[4m',
21
+ inverse: '\x1b[7m',
22
+
23
+ // Foreground colors
24
+ fg: {
25
+ black: '\x1b[30m',
26
+ red: '\x1b[31m',
27
+ green: '\x1b[32m',
28
+ yellow: '\x1b[33m',
29
+ blue: '\x1b[34m',
30
+ magenta: '\x1b[35m',
31
+ cyan: '\x1b[36m',
32
+ white: '\x1b[37m',
33
+ gray: '\x1b[90m',
34
+
35
+ // Bright variants
36
+ brightRed: '\x1b[91m',
37
+ brightGreen: '\x1b[92m',
38
+ brightYellow: '\x1b[93m',
39
+ brightBlue: '\x1b[94m',
40
+ brightMagenta: '\x1b[95m',
41
+ brightCyan: '\x1b[96m',
42
+ brightWhite: '\x1b[97m',
43
+ },
44
+
45
+ // Background colors
46
+ bg: {
47
+ black: '\x1b[40m',
48
+ red: '\x1b[41m',
49
+ green: '\x1b[42m',
50
+ yellow: '\x1b[43m',
51
+ blue: '\x1b[44m',
52
+ magenta: '\x1b[45m',
53
+ cyan: '\x1b[46m',
54
+ white: '\x1b[47m',
55
+ gray: '\x1b[100m',
56
+ },
57
+ };
58
+
59
+ // Neon theme (default) - Original cyan/green look
60
+ const NEON_THEME = {
61
+ name: 'neon',
62
+ heading: ANSI.bold + ANSI.fg.cyan,
63
+ bold: ANSI.bold,
64
+ italic: ANSI.italic,
65
+ code: ANSI.fg.yellow,
66
+ codeBlock: ANSI.fg.white,
67
+ codeBorder: ANSI.fg.gray,
68
+ codeLang: ANSI.fg.cyan,
69
+ link: ANSI.fg.blue + ANSI.underline,
70
+ linkUrl: ANSI.fg.gray,
71
+ listBullet: ANSI.fg.yellow,
72
+ blockquote: ANSI.fg.gray,
73
+ hr: ANSI.fg.gray,
74
+ tableHeader: ANSI.bold + ANSI.fg.cyan,
75
+ tableBorder: ANSI.fg.gray,
76
+
77
+ // Syntax highlighting
78
+ keyword: ANSI.fg.magenta,
79
+ string: ANSI.fg.yellow,
80
+ number: ANSI.fg.cyan,
81
+ comment: ANSI.fg.green,
82
+ function: ANSI.fg.cyan,
83
+ variable: ANSI.fg.yellow,
84
+
85
+ // Role colors
86
+ user: ANSI.bold + ANSI.fg.cyan,
87
+ assistant: ANSI.bold + ANSI.fg.green,
88
+ system: ANSI.bold + ANSI.fg.yellow,
89
+ tool: ANSI.bold + ANSI.fg.magenta,
90
+
91
+ reset: ANSI.reset,
92
+ };
93
+
94
+ // Dracula theme - Pink/purple look
95
+ const DRACULA_THEME = {
96
+ name: 'dracula',
97
+ heading: ANSI.bold + ANSI.fg.magenta,
98
+ bold: ANSI.bold,
99
+ italic: ANSI.italic,
100
+ code: ANSI.fg.yellow,
101
+ codeBlock: ANSI.fg.white,
102
+ codeBorder: ANSI.fg.gray,
103
+ codeLang: ANSI.fg.cyan,
104
+ link: ANSI.fg.cyan + ANSI.underline,
105
+ linkUrl: ANSI.fg.gray,
106
+ listBullet: ANSI.fg.green,
107
+ blockquote: ANSI.fg.gray,
108
+ hr: ANSI.fg.gray,
109
+ tableHeader: ANSI.bold + ANSI.fg.magenta,
110
+ tableBorder: ANSI.fg.gray,
111
+
112
+ // Syntax highlighting
113
+ keyword: ANSI.fg.magenta,
114
+ string: ANSI.fg.yellow,
115
+ number: ANSI.fg.magenta,
116
+ comment: ANSI.fg.gray,
117
+ function: ANSI.fg.green,
118
+ variable: ANSI.fg.cyan,
119
+
120
+ // Role colors
121
+ user: ANSI.bold + ANSI.fg.magenta,
122
+ assistant: ANSI.bold + ANSI.fg.cyan,
123
+ system: ANSI.bold + ANSI.fg.yellow,
124
+ tool: ANSI.bold + ANSI.fg.green,
125
+
126
+ reset: ANSI.reset,
127
+ };
128
+
129
+ // Theme selection based on PAVE_THEME env var
130
+ function getTheme() {
131
+ const themeName = (process.env.PAVE_THEME || 'neon').toLowerCase();
132
+ if (themeName === 'dracula') {
133
+ return DRACULA_THEME;
134
+ }
135
+ return NEON_THEME;
136
+ }
137
+
138
+ // Box drawing characters
139
+ const BOX = {
140
+ topLeft: '╭',
141
+ topRight: '╮',
142
+ bottomLeft: '╰',
143
+ bottomRight: '╯',
144
+ horizontal: '─',
145
+ vertical: '│',
146
+
147
+ // Table
148
+ tableTopLeft: '┌',
149
+ tableTopRight: '┐',
150
+ tableBottomLeft: '└',
151
+ tableBottomRight: '┘',
152
+ tableHorizontal: '─',
153
+ tableVertical: '│',
154
+ tableCross: '┼',
155
+ tableTopT: '┬',
156
+ tableBottomT: '┴',
157
+ tableLeftT: '├',
158
+ tableRightT: '┤',
159
+
160
+ // Blockquote
161
+ quote: '┃',
162
+ };
163
+
164
+ /**
165
+ * CLI Markdown Renderer
166
+ */
167
+ class CLIMarkdownRenderer {
168
+ constructor(options = {}) {
169
+ this.width = options.width || process.stdout.columns || 80;
170
+ this.colors = options.colors !== false; // Enable colors by default
171
+ this.theme = options.theme || getTheme();
172
+ }
173
+
174
+ /**
175
+ * Render markdown to ANSI-colored string
176
+ */
177
+ render(text) {
178
+ if (!text) return '';
179
+ if (!this.colors) return text; // Return plain text if colors disabled
180
+
181
+ const T = this.theme; // Theme shorthand
182
+ const lines = text.split('\n');
183
+ const rendered = [];
184
+ let inCodeBlock = false;
185
+ let codeBlockLang = '';
186
+ let codeBlockLines = [];
187
+ let inTable = false;
188
+ let tableLines = [];
189
+
190
+ for (let i = 0; i < lines.length; i++) {
191
+ const line = lines[i];
192
+
193
+ // Code block start/end
194
+ if (line.startsWith('```')) {
195
+ if (!inCodeBlock) {
196
+ inCodeBlock = true;
197
+ codeBlockLang = line.slice(3).trim();
198
+ codeBlockLines = [];
199
+ } else {
200
+ // End code block
201
+ rendered.push(this.renderCodeBlock(codeBlockLines, codeBlockLang));
202
+ inCodeBlock = false;
203
+ codeBlockLang = '';
204
+ codeBlockLines = [];
205
+ }
206
+ continue;
207
+ }
208
+
209
+ if (inCodeBlock) {
210
+ codeBlockLines.push(line);
211
+ continue;
212
+ }
213
+
214
+ // Table detection
215
+ if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
216
+ if (!inTable) {
217
+ inTable = true;
218
+ tableLines = [];
219
+ }
220
+ tableLines.push(line);
221
+ continue;
222
+ } else if (inTable) {
223
+ rendered.push(this.renderTable(tableLines));
224
+ inTable = false;
225
+ tableLines = [];
226
+ }
227
+
228
+ // Headers
229
+ if (line.startsWith('#### ')) {
230
+ rendered.push(`${T.heading}#### ${line.slice(5)}${T.reset}`);
231
+ continue;
232
+ }
233
+ if (line.startsWith('### ')) {
234
+ rendered.push(`${T.heading}### ${line.slice(4)}${T.reset}`);
235
+ continue;
236
+ }
237
+ if (line.startsWith('## ')) {
238
+ rendered.push(`${T.heading}## ${line.slice(3)}${T.reset}`);
239
+ continue;
240
+ }
241
+ if (line.startsWith('# ')) {
242
+ rendered.push(`${T.heading}# ${line.slice(2)}${T.reset}`);
243
+ continue;
244
+ }
245
+
246
+ // Horizontal rule
247
+ if (/^[-*_]{3,}$/.test(line.trim())) {
248
+ rendered.push(`${T.hr}${'─'.repeat(Math.min(this.width - 4, 50))}${T.reset}`);
249
+ continue;
250
+ }
251
+
252
+ // Blockquote
253
+ if (line.startsWith('> ')) {
254
+ rendered.push(`${T.blockquote}${BOX.quote} ${line.slice(2)}${T.reset}`);
255
+ continue;
256
+ }
257
+
258
+ // Unordered list
259
+ if (/^\s*[-*+]\s/.test(line)) {
260
+ const match = line.match(/^(\s*)([-*+])\s(.*)$/);
261
+ if (match) {
262
+ const indent = match[1] || '';
263
+ const content = this.renderInline(match[3]);
264
+ rendered.push(`${indent}${T.listBullet}•${T.reset} ${content}`);
265
+ continue;
266
+ }
267
+ }
268
+
269
+ // Ordered list
270
+ if (/^\s*\d+\.\s/.test(line)) {
271
+ const match = line.match(/^(\s*)(\d+)\.\s(.*)$/);
272
+ if (match) {
273
+ const indent = match[1] || '';
274
+ const num = match[2];
275
+ const content = this.renderInline(match[3]);
276
+ rendered.push(`${indent}${T.listBullet}${num}.${T.reset} ${content}`);
277
+ continue;
278
+ }
279
+ }
280
+
281
+ // Regular paragraph with inline formatting
282
+ rendered.push(this.renderInline(line));
283
+ }
284
+
285
+ // Handle unclosed code block
286
+ if (inCodeBlock && codeBlockLines.length > 0) {
287
+ rendered.push(this.renderCodeBlock(codeBlockLines, codeBlockLang));
288
+ }
289
+
290
+ // Handle unclosed table
291
+ if (inTable && tableLines.length > 0) {
292
+ rendered.push(this.renderTable(tableLines));
293
+ }
294
+
295
+ return rendered.join('\n');
296
+ }
297
+
298
+ /**
299
+ * Render inline markdown
300
+ */
301
+ renderInline(text) {
302
+ if (!text) return '';
303
+ const T = this.theme;
304
+
305
+ // Inline code
306
+ text = text.replace(/`([^`]+)`/g, (_, code) => {
307
+ return `${T.code}${code}${T.reset}`;
308
+ });
309
+
310
+ // Bold + Italic
311
+ text = text.replace(/(\*\*\*|___)([^*_]+)\1/g, (_, marker, content) => {
312
+ return `${ANSI.bold}${ANSI.italic}${content}${T.reset}`;
313
+ });
314
+
315
+ // Bold
316
+ text = text.replace(/(\*\*|__)([^*_]+)\1/g, (_, marker, content) => {
317
+ return `${ANSI.bold}${content}${T.reset}`;
318
+ });
319
+
320
+ // Italic
321
+ text = text.replace(/(\*|_)([^*_]+)\1/g, (_, marker, content) => {
322
+ return `${ANSI.italic}${content}${T.reset}`;
323
+ });
324
+
325
+ // Links [text](url)
326
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) => {
327
+ return `${T.link}${label}${T.reset} ${T.linkUrl}(${url})${T.reset}`;
328
+ });
329
+
330
+ return text;
331
+ }
332
+
333
+ /**
334
+ * Render code block with syntax highlighting
335
+ */
336
+ renderCodeBlock(lines, lang) {
337
+ const T = this.theme;
338
+ const content = lines.join('\n');
339
+ const highlighted = this.highlightCode(content, lang);
340
+
341
+ const langLabel = lang ? ` ${lang} ` : '';
342
+ const maxLineLen = Math.max(...lines.map((l) => l.length), langLabel.length + 4);
343
+ const borderWidth = Math.min(Math.max(maxLineLen + 4, 40), this.width - 4);
344
+
345
+ const topBorder = `${T.codeBorder}${BOX.topLeft}${T.reset}${T.codeLang}${langLabel}${T.reset}${T.codeBorder}${BOX.horizontal.repeat(Math.max(0, borderWidth - langLabel.length - 1))}${BOX.topRight}${T.reset}`;
346
+ const bottomBorder = `${T.codeBorder}${BOX.bottomLeft}${BOX.horizontal.repeat(borderWidth)}${BOX.bottomRight}${T.reset}`;
347
+
348
+ const formattedLines = highlighted.split('\n').map((line) => {
349
+ return `${T.codeBorder}${BOX.vertical}${T.reset} ${line}`;
350
+ });
351
+
352
+ return [topBorder, ...formattedLines, bottomBorder].join('\n');
353
+ }
354
+
355
+ /**
356
+ * Render table
357
+ */
358
+ renderTable(lines) {
359
+ if (lines.length === 0) return '';
360
+ const T = this.theme;
361
+
362
+ const rows = lines
363
+ .filter((line) => !line.match(/^\|\s*[-:]+\s*\|/))
364
+ .map((line) => {
365
+ return line
366
+ .split('|')
367
+ .slice(1, -1)
368
+ .map((cell) => cell.trim());
369
+ });
370
+
371
+ if (rows.length === 0) return '';
372
+
373
+ const colCount = Math.max(...rows.map((r) => r.length));
374
+ const colWidths = [];
375
+ for (let i = 0; i < colCount; i++) {
376
+ colWidths[i] = Math.max(...rows.map((r) => (r[i] || '').length), 3);
377
+ }
378
+
379
+ const result = [];
380
+
381
+ // Top border
382
+ const topBorder = `${T.tableBorder}${BOX.tableTopLeft}${colWidths.map((w) => BOX.tableHorizontal.repeat(w + 2)).join(BOX.tableTopT)}${BOX.tableTopRight}${T.reset}`;
383
+ result.push(topBorder);
384
+
385
+ // Header row
386
+ if (rows.length > 0) {
387
+ const headerRow = rows[0];
388
+ const headerCells = colWidths.map((w, i) => {
389
+ const cell = (headerRow[i] || '').padEnd(w);
390
+ return `${T.tableHeader}${cell}${T.reset}`;
391
+ });
392
+ result.push(`${T.tableBorder}${BOX.tableVertical}${T.reset} ${headerCells.join(` ${T.tableBorder}${BOX.tableVertical}${T.reset} `)} ${T.tableBorder}${BOX.tableVertical}${T.reset}`);
393
+
394
+ // Header separator
395
+ const sepBorder = `${T.tableBorder}${BOX.tableLeftT}${colWidths.map((w) => BOX.tableHorizontal.repeat(w + 2)).join(BOX.tableCross)}${BOX.tableRightT}${T.reset}`;
396
+ result.push(sepBorder);
397
+ }
398
+
399
+ // Data rows
400
+ for (let i = 1; i < rows.length; i++) {
401
+ const row = rows[i];
402
+ const cells = colWidths.map((w, j) => {
403
+ return (row[j] || '').padEnd(w);
404
+ });
405
+ result.push(`${T.tableBorder}${BOX.tableVertical}${T.reset} ${cells.join(` ${T.tableBorder}${BOX.tableVertical}${T.reset} `)} ${T.tableBorder}${BOX.tableVertical}${T.reset}`);
406
+ }
407
+
408
+ // Bottom border
409
+ const bottomBorder = `${T.tableBorder}${BOX.tableBottomLeft}${colWidths.map((w) => BOX.tableHorizontal.repeat(w + 2)).join(BOX.tableBottomT)}${BOX.tableBottomRight}${T.reset}`;
410
+ result.push(bottomBorder);
411
+
412
+ return result.join('\n');
413
+ }
414
+
415
+ /**
416
+ * Syntax highlighting for code
417
+ */
418
+ highlightCode(code, lang) {
419
+ if (!lang) return code;
420
+
421
+ const language = lang.toLowerCase();
422
+
423
+ switch (language) {
424
+ case 'javascript':
425
+ case 'js':
426
+ case 'typescript':
427
+ case 'ts':
428
+ return this.highlightJS(code);
429
+ case 'json':
430
+ return this.highlightJSON(code);
431
+ case 'bash':
432
+ case 'sh':
433
+ case 'shell':
434
+ return this.highlightBash(code);
435
+ case 'python':
436
+ case 'py':
437
+ return this.highlightPython(code);
438
+ default:
439
+ return code;
440
+ }
441
+ }
442
+
443
+ highlightJS(code) {
444
+ const T = this.theme;
445
+ return code.split('\n').map((line) => {
446
+ // Comments
447
+ const commentMatch = line.match(/^(.*?)(\/\/.*)$/);
448
+ if (commentMatch) {
449
+ return this.highlightJSLine(commentMatch[1]) + `${T.comment}${commentMatch[2]}${T.reset}`;
450
+ }
451
+ return this.highlightJSLine(line);
452
+ }).join('\n');
453
+ }
454
+
455
+ highlightJSLine(line) {
456
+ const T = this.theme;
457
+ // Keywords
458
+ line = line.replace(/\b(const|let|var|function|return|if|else|for|while|class|import|export|from|async|await|try|catch|new|this|null|undefined|true|false)\b/g,
459
+ `${T.keyword}$1${T.reset}`);
460
+ // Strings
461
+ line = line.replace(/(["'])(?:(?!\1)[^\\]|\\.)*\1/g, `${T.string}$&${T.reset}`);
462
+ // Numbers
463
+ line = line.replace(/\b(\d+\.?\d*)\b/g, `${T.number}$1${T.reset}`);
464
+ // Function calls
465
+ line = line.replace(/\b([a-zA-Z_]\w*)\s*\(/g, `${T.function}$1${T.reset}(`);
466
+
467
+ return line;
468
+ }
469
+
470
+ highlightJSON(code) {
471
+ const T = this.theme;
472
+ let result = code;
473
+ // Keys
474
+ result = result.replace(/"([^"]+)"(?=\s*:)/g, `${T.variable}"$1"${T.reset}`);
475
+ // String values
476
+ result = result.replace(/:\s*"([^"\\]|\\.)*"/g, (match) => {
477
+ return match.replace(/"([^"\\]|\\.)*"/, `${T.string}$&${T.reset}`);
478
+ });
479
+ // Numbers
480
+ result = result.replace(/:\s*(-?\d+\.?\d*)/g, `:${T.number} $1${T.reset}`);
481
+ // Booleans
482
+ result = result.replace(/\b(true|false|null)\b/g, `${T.keyword}$1${T.reset}`);
483
+
484
+ return result;
485
+ }
486
+
487
+ highlightBash(code) {
488
+ const T = this.theme;
489
+ return code.split('\n').map((line) => {
490
+ // Comments
491
+ const commentMatch = line.match(/^(.*?)(#.*)$/);
492
+ if (commentMatch && !commentMatch[1].includes('"') && !commentMatch[1].includes("'")) {
493
+ return this.highlightBashLine(commentMatch[1]) + `${T.comment}${commentMatch[2]}${T.reset}`;
494
+ }
495
+ return this.highlightBashLine(line);
496
+ }).join('\n');
497
+ }
498
+
499
+ highlightBashLine(line) {
500
+ const T = this.theme;
501
+ // Commands
502
+ line = line.replace(/^\s*(node|npm|npx|git|cd|ls|mkdir|rm|cp|mv|cat|echo|curl|wget|grep|sudo|pave)\b/,
503
+ `${T.function}$1${T.reset}`);
504
+ // Flags
505
+ line = line.replace(/\s(-{1,2}[a-zA-Z][\w-]*)/g, ` ${T.variable}$1${T.reset}`);
506
+ // Variables
507
+ line = line.replace(/(\$\w+|\$\{[^}]+\})/g, `${T.variable}$1${T.reset}`);
508
+ // Strings
509
+ line = line.replace(/(["'])(?:(?!\1)[^\\]|\\.)*\1/g, `${T.string}$&${T.reset}`);
510
+
511
+ return line;
512
+ }
513
+
514
+ highlightPython(code) {
515
+ const T = this.theme;
516
+ return code.split('\n').map((line) => {
517
+ // Comments
518
+ const commentMatch = line.match(/^(.*?)(#.*)$/);
519
+ if (commentMatch && !commentMatch[1].includes('"') && !commentMatch[1].includes("'")) {
520
+ return this.highlightPythonLine(commentMatch[1]) + `${T.comment}${commentMatch[2]}${T.reset}`;
521
+ }
522
+ return this.highlightPythonLine(line);
523
+ }).join('\n');
524
+ }
525
+
526
+ highlightPythonLine(line) {
527
+ const T = this.theme;
528
+ // Keywords
529
+ line = line.replace(/\b(def|class|return|if|elif|else|for|while|try|except|import|from|None|True|False|and|or|not|in|is)\b/g,
530
+ `${T.keyword}$1${T.reset}`);
531
+ // Built-ins
532
+ line = line.replace(/\b(print|len|range|str|int|float|list|dict)\b/g,
533
+ `${T.function}$1${T.reset}`);
534
+ // Strings
535
+ line = line.replace(/(["'])(?:(?!\1)[^\\]|\\.)*\1/g, `${T.string}$&${T.reset}`);
536
+ // Numbers
537
+ line = line.replace(/\b(\d+\.?\d*)\b/g, `${T.number}$1${T.reset}`);
538
+
539
+ return line;
540
+ }
541
+
542
+ /**
543
+ * Format role label with color
544
+ */
545
+ formatRole(role) {
546
+ const T = this.theme;
547
+ const roles = {
548
+ user: `${T.user}👤 User${T.reset}`,
549
+ assistant: `${T.assistant}🤖 Assistant${T.reset}`,
550
+ system: `${T.system}🛠️ System${T.reset}`,
551
+ tool: `${T.tool}🔧 Tool${T.reset}`,
552
+ };
553
+ return roles[role] || `${T.assistant}🤖 ${role}${T.reset}`;
554
+ }
555
+
556
+ /**
557
+ * Format timestamp
558
+ */
559
+ formatTime(timestamp) {
560
+ return `${ANSI.fg.gray}(${new Date(timestamp).toLocaleString()})${ANSI.reset}`;
561
+ }
562
+
563
+ /**
564
+ * Static method for formatting role (for external use)
565
+ */
566
+ static formatRole(role) {
567
+ const T = getTheme();
568
+ const roles = {
569
+ user: `${T.user}👤 User${T.reset}`,
570
+ assistant: `${T.assistant}🤖 Assistant${T.reset}`,
571
+ system: `${T.system}🛠️ System${T.reset}`,
572
+ tool: `${T.tool}🔧 Tool${T.reset}`,
573
+ };
574
+ return roles[role] || `${T.assistant}🤖 ${role}${T.reset}`;
575
+ }
576
+
577
+ /**
578
+ * Static method for formatting time (for external use)
579
+ */
580
+ static formatTime(timestamp) {
581
+ return `${ANSI.fg.gray}(${new Date(timestamp).toLocaleString()})${ANSI.reset}`;
582
+ }
583
+ }
584
+
585
+ module.exports = CLIMarkdownRenderer;
586
+ module.exports.ANSI = ANSI;
587
+ module.exports.BOX = BOX;
588
+ module.exports.getTheme = getTheme;
589
+ module.exports.NEON_THEME = NEON_THEME;
590
+ module.exports.DRACULA_THEME = DRACULA_THEME;