prab-cli 1.0.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.
- package/LICENSE +15 -0
- package/README.md +272 -0
- package/dist/index.js +374 -0
- package/dist/lib/chat-handler.js +219 -0
- package/dist/lib/config.js +156 -0
- package/dist/lib/context.js +44 -0
- package/dist/lib/groq-models.js +53 -0
- package/dist/lib/groq.js +33 -0
- package/dist/lib/models/groq-provider.js +82 -0
- package/dist/lib/models/provider.js +10 -0
- package/dist/lib/models/registry.js +101 -0
- package/dist/lib/safety.js +109 -0
- package/dist/lib/tools/base.js +115 -0
- package/dist/lib/tools/executor.js +127 -0
- package/dist/lib/tools/file-tools.js +283 -0
- package/dist/lib/tools/git-tools.js +280 -0
- package/dist/lib/tools/shell-tools.js +73 -0
- package/dist/lib/tools/todo-tool.js +105 -0
- package/dist/lib/tracker.js +314 -0
- package/dist/lib/ui.js +578 -0
- package/dist/log-viewer.js +374 -0
- package/dist/types/index.js +5 -0
- package/package.json +75 -0
package/dist/lib/ui.js
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StreamFormatter = exports.formatMarkdown = exports.showToolProgress = exports.showTodoList = exports.showDiff = exports.banner = exports.log = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const diff_1 = require("diff");
|
|
9
|
+
// Syntax highlighting color schemes for different languages
|
|
10
|
+
const syntaxColors = {
|
|
11
|
+
keyword: chalk_1.default.magenta,
|
|
12
|
+
string: chalk_1.default.green,
|
|
13
|
+
number: chalk_1.default.yellow,
|
|
14
|
+
comment: chalk_1.default.gray,
|
|
15
|
+
function: chalk_1.default.cyan,
|
|
16
|
+
variable: chalk_1.default.white,
|
|
17
|
+
operator: chalk_1.default.yellow,
|
|
18
|
+
punctuation: chalk_1.default.white,
|
|
19
|
+
type: chalk_1.default.blue,
|
|
20
|
+
};
|
|
21
|
+
// Common keywords for different languages
|
|
22
|
+
const keywords = new Set([
|
|
23
|
+
// JavaScript/TypeScript
|
|
24
|
+
'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do',
|
|
25
|
+
'switch', 'case', 'break', 'continue', 'try', 'catch', 'finally', 'throw',
|
|
26
|
+
'class', 'extends', 'new', 'this', 'super', 'import', 'export', 'from', 'as',
|
|
27
|
+
'default', 'async', 'await', 'yield', 'typeof', 'instanceof', 'in', 'of',
|
|
28
|
+
'true', 'false', 'null', 'undefined', 'void', 'delete', 'static', 'get', 'set',
|
|
29
|
+
'interface', 'type', 'enum', 'implements', 'private', 'public', 'protected',
|
|
30
|
+
// Python
|
|
31
|
+
'def', 'class', 'import', 'from', 'as', 'if', 'elif', 'else', 'for', 'while',
|
|
32
|
+
'try', 'except', 'finally', 'raise', 'with', 'lambda', 'return', 'yield',
|
|
33
|
+
'True', 'False', 'None', 'and', 'or', 'not', 'is', 'in', 'pass', 'global',
|
|
34
|
+
'nonlocal', 'assert', 'break', 'continue', 'self', 'async', 'await',
|
|
35
|
+
// Go
|
|
36
|
+
'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan',
|
|
37
|
+
'go', 'defer', 'select', 'range', 'make', 'append', 'len', 'cap', 'nil',
|
|
38
|
+
// Rust
|
|
39
|
+
'fn', 'let', 'mut', 'pub', 'mod', 'use', 'struct', 'enum', 'impl', 'trait',
|
|
40
|
+
'match', 'loop', 'move', 'ref', 'self', 'Self', 'where', 'unsafe', 'async',
|
|
41
|
+
// Common
|
|
42
|
+
'print', 'println', 'printf', 'console', 'log', 'error', 'warn',
|
|
43
|
+
]);
|
|
44
|
+
/**
|
|
45
|
+
* Truncate output for brief display
|
|
46
|
+
*/
|
|
47
|
+
const truncateOutput = (text, maxLen) => {
|
|
48
|
+
const firstLine = text.split('\n')[0];
|
|
49
|
+
if (firstLine.length > maxLen) {
|
|
50
|
+
return firstLine.substring(0, maxLen) + '...';
|
|
51
|
+
}
|
|
52
|
+
return firstLine;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Format diff output with colors
|
|
56
|
+
*/
|
|
57
|
+
const formatDiffOutput = (output) => {
|
|
58
|
+
const lines = output.split('\n');
|
|
59
|
+
return lines.map(line => {
|
|
60
|
+
// File headers
|
|
61
|
+
if (line.startsWith('diff --git')) {
|
|
62
|
+
return chalk_1.default.bold.white(line);
|
|
63
|
+
}
|
|
64
|
+
if (line.startsWith('index ')) {
|
|
65
|
+
return chalk_1.default.gray(line);
|
|
66
|
+
}
|
|
67
|
+
if (line.startsWith('---')) {
|
|
68
|
+
return chalk_1.default.red.bold(line);
|
|
69
|
+
}
|
|
70
|
+
if (line.startsWith('+++')) {
|
|
71
|
+
return chalk_1.default.green.bold(line);
|
|
72
|
+
}
|
|
73
|
+
// Hunk headers
|
|
74
|
+
if (line.startsWith('@@')) {
|
|
75
|
+
return chalk_1.default.cyan(line);
|
|
76
|
+
}
|
|
77
|
+
// Added lines
|
|
78
|
+
if (line.startsWith('+')) {
|
|
79
|
+
return chalk_1.default.green(line);
|
|
80
|
+
}
|
|
81
|
+
// Removed lines
|
|
82
|
+
if (line.startsWith('-')) {
|
|
83
|
+
return chalk_1.default.red(line);
|
|
84
|
+
}
|
|
85
|
+
// Context lines
|
|
86
|
+
return chalk_1.default.gray(line);
|
|
87
|
+
}).join('\n');
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Format git log output with colors
|
|
91
|
+
*/
|
|
92
|
+
const formatGitLogOutput = (output) => {
|
|
93
|
+
const lines = output.split('\n');
|
|
94
|
+
return lines.map(line => {
|
|
95
|
+
// Commit hash
|
|
96
|
+
if (line.match(/^[a-f0-9]{7,40}\s/)) {
|
|
97
|
+
const parts = line.split(' ');
|
|
98
|
+
const hash = parts[0];
|
|
99
|
+
const rest = parts.slice(1).join(' ');
|
|
100
|
+
return chalk_1.default.yellow(hash) + ' ' + rest;
|
|
101
|
+
}
|
|
102
|
+
// Date lines
|
|
103
|
+
if (line.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
104
|
+
return chalk_1.default.blue(line);
|
|
105
|
+
}
|
|
106
|
+
// Author
|
|
107
|
+
if (line.toLowerCase().includes('author:')) {
|
|
108
|
+
return chalk_1.default.cyan(line);
|
|
109
|
+
}
|
|
110
|
+
// Commit message (indented)
|
|
111
|
+
if (line.startsWith(' ')) {
|
|
112
|
+
return chalk_1.default.white(line);
|
|
113
|
+
}
|
|
114
|
+
return line;
|
|
115
|
+
}).join('\n');
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Format git status output with colors
|
|
119
|
+
*/
|
|
120
|
+
const formatGitStatusOutput = (output) => {
|
|
121
|
+
const lines = output.split('\n');
|
|
122
|
+
return lines.map(line => {
|
|
123
|
+
// Branch info
|
|
124
|
+
if (line.startsWith('On branch') || line.startsWith('HEAD detached')) {
|
|
125
|
+
return chalk_1.default.cyan.bold(line);
|
|
126
|
+
}
|
|
127
|
+
// Modified files
|
|
128
|
+
if (line.includes('modified:')) {
|
|
129
|
+
return chalk_1.default.yellow(line);
|
|
130
|
+
}
|
|
131
|
+
// New files
|
|
132
|
+
if (line.includes('new file:')) {
|
|
133
|
+
return chalk_1.default.green(line);
|
|
134
|
+
}
|
|
135
|
+
// Deleted files
|
|
136
|
+
if (line.includes('deleted:')) {
|
|
137
|
+
return chalk_1.default.red(line);
|
|
138
|
+
}
|
|
139
|
+
// Untracked files header
|
|
140
|
+
if (line.includes('Untracked files:')) {
|
|
141
|
+
return chalk_1.default.magenta.bold(line);
|
|
142
|
+
}
|
|
143
|
+
// Staged changes header
|
|
144
|
+
if (line.includes('Changes to be committed:')) {
|
|
145
|
+
return chalk_1.default.green.bold(line);
|
|
146
|
+
}
|
|
147
|
+
// Unstaged changes header
|
|
148
|
+
if (line.includes('Changes not staged')) {
|
|
149
|
+
return chalk_1.default.yellow.bold(line);
|
|
150
|
+
}
|
|
151
|
+
// File status indicators (M, A, D, ??)
|
|
152
|
+
if (line.match(/^\s*[MADRCU?]{1,2}\s+/)) {
|
|
153
|
+
const status = line.match(/^\s*([MADRCU?]{1,2})\s+(.*)$/);
|
|
154
|
+
if (status) {
|
|
155
|
+
const indicator = status[1];
|
|
156
|
+
const filename = status[2];
|
|
157
|
+
let color = chalk_1.default.white;
|
|
158
|
+
if (indicator.includes('M'))
|
|
159
|
+
color = chalk_1.default.yellow;
|
|
160
|
+
if (indicator.includes('A'))
|
|
161
|
+
color = chalk_1.default.green;
|
|
162
|
+
if (indicator.includes('D'))
|
|
163
|
+
color = chalk_1.default.red;
|
|
164
|
+
if (indicator.includes('?'))
|
|
165
|
+
color = chalk_1.default.gray;
|
|
166
|
+
return color(` ${indicator} ${filename}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return chalk_1.default.gray(line);
|
|
170
|
+
}).join('\n');
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Apply syntax highlighting to a line of code
|
|
174
|
+
*/
|
|
175
|
+
const highlightCodeLine = (line) => {
|
|
176
|
+
// Handle comments
|
|
177
|
+
const commentMatch = line.match(/^(\s*)(\/\/.*|#.*|\/\*.*\*\/|<!--.*-->)$/);
|
|
178
|
+
if (commentMatch) {
|
|
179
|
+
return commentMatch[1] + syntaxColors.comment(commentMatch[2]);
|
|
180
|
+
}
|
|
181
|
+
let result = line;
|
|
182
|
+
// Highlight strings (single, double, template)
|
|
183
|
+
result = result.replace(/(["'`])(?:(?!\1|\\).|\\.)*\1/g, (match) => syntaxColors.string(match));
|
|
184
|
+
// Highlight numbers
|
|
185
|
+
result = result.replace(/\b(\d+\.?\d*)\b/g, (match) => syntaxColors.number(match));
|
|
186
|
+
// Highlight keywords
|
|
187
|
+
const words = result.split(/(\s+|[^\w])/);
|
|
188
|
+
result = words
|
|
189
|
+
.map((word) => {
|
|
190
|
+
if (keywords.has(word)) {
|
|
191
|
+
return syntaxColors.keyword(word);
|
|
192
|
+
}
|
|
193
|
+
return word;
|
|
194
|
+
})
|
|
195
|
+
.join("");
|
|
196
|
+
// Highlight function calls
|
|
197
|
+
result = result.replace(/\b([a-zA-Z_]\w*)\s*\(/g, (match, name) => syntaxColors.function(name) + "(");
|
|
198
|
+
return result;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Format file content with line numbers and syntax highlighting
|
|
202
|
+
*/
|
|
203
|
+
const formatFileContent = (content, filename) => {
|
|
204
|
+
const lines = content.split('\n');
|
|
205
|
+
const lineNumWidth = String(lines.length).length;
|
|
206
|
+
return lines.map((line, idx) => {
|
|
207
|
+
const lineNum = String(idx + 1).padStart(lineNumWidth, ' ');
|
|
208
|
+
const highlighted = highlightCodeLine(line);
|
|
209
|
+
return chalk_1.default.gray(`${lineNum} │ `) + highlighted;
|
|
210
|
+
}).join('\n');
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Format bash command output
|
|
214
|
+
*/
|
|
215
|
+
const formatBashOutput = (output) => {
|
|
216
|
+
// Check if it looks like a diff
|
|
217
|
+
if (output.includes('diff --git') || output.includes('@@') && (output.includes('+') || output.includes('-'))) {
|
|
218
|
+
return formatDiffOutput(output);
|
|
219
|
+
}
|
|
220
|
+
// Check if it looks like git log
|
|
221
|
+
if (output.match(/^[a-f0-9]{7,40}\s+\d{4}-\d{2}-\d{2}/m)) {
|
|
222
|
+
return formatGitLogOutput(output);
|
|
223
|
+
}
|
|
224
|
+
// Generic output with some highlighting
|
|
225
|
+
const lines = output.split('\n');
|
|
226
|
+
return lines.map(line => {
|
|
227
|
+
// Error messages
|
|
228
|
+
if (line.toLowerCase().includes('error') || line.toLowerCase().includes('failed')) {
|
|
229
|
+
return chalk_1.default.red(line);
|
|
230
|
+
}
|
|
231
|
+
// Warning messages
|
|
232
|
+
if (line.toLowerCase().includes('warning') || line.toLowerCase().includes('warn')) {
|
|
233
|
+
return chalk_1.default.yellow(line);
|
|
234
|
+
}
|
|
235
|
+
// Success messages
|
|
236
|
+
if (line.toLowerCase().includes('success') || line.toLowerCase().includes('done') || line.toLowerCase().includes('passed')) {
|
|
237
|
+
return chalk_1.default.green(line);
|
|
238
|
+
}
|
|
239
|
+
// Paths
|
|
240
|
+
if (line.match(/^[./~]/)) {
|
|
241
|
+
return chalk_1.default.cyan(line);
|
|
242
|
+
}
|
|
243
|
+
return line;
|
|
244
|
+
}).join('\n');
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Format tool output based on tool type
|
|
248
|
+
*/
|
|
249
|
+
const formatToolOutput = (toolName, output) => {
|
|
250
|
+
if (!output || output.trim() === '') {
|
|
251
|
+
return chalk_1.default.gray(' (no output)');
|
|
252
|
+
}
|
|
253
|
+
const separator = chalk_1.default.gray('─'.repeat(60));
|
|
254
|
+
let formatted;
|
|
255
|
+
switch (toolName.toLowerCase()) {
|
|
256
|
+
case 'git_diff':
|
|
257
|
+
case 'gitdiff':
|
|
258
|
+
formatted = formatDiffOutput(output);
|
|
259
|
+
break;
|
|
260
|
+
case 'git_log':
|
|
261
|
+
case 'gitlog':
|
|
262
|
+
formatted = formatGitLogOutput(output);
|
|
263
|
+
break;
|
|
264
|
+
case 'git_status':
|
|
265
|
+
case 'gitstatus':
|
|
266
|
+
formatted = formatGitStatusOutput(output);
|
|
267
|
+
break;
|
|
268
|
+
case 'read_file':
|
|
269
|
+
case 'readfile':
|
|
270
|
+
formatted = formatFileContent(output);
|
|
271
|
+
break;
|
|
272
|
+
case 'bash':
|
|
273
|
+
case 'shell':
|
|
274
|
+
formatted = formatBashOutput(output);
|
|
275
|
+
break;
|
|
276
|
+
case 'glob':
|
|
277
|
+
case 'grep':
|
|
278
|
+
// File lists - highlight paths
|
|
279
|
+
formatted = output.split('\n').map(line => {
|
|
280
|
+
if (line.includes(':')) {
|
|
281
|
+
const [path, ...rest] = line.split(':');
|
|
282
|
+
return chalk_1.default.cyan(path) + ':' + chalk_1.default.white(rest.join(':'));
|
|
283
|
+
}
|
|
284
|
+
return chalk_1.default.cyan(line);
|
|
285
|
+
}).join('\n');
|
|
286
|
+
break;
|
|
287
|
+
default:
|
|
288
|
+
formatted = output;
|
|
289
|
+
}
|
|
290
|
+
return `\n${chalk_1.default.gray('Output:')}\n${formatted}`;
|
|
291
|
+
};
|
|
292
|
+
exports.log = {
|
|
293
|
+
info: (msg) => console.log(chalk_1.default.blue("ℹ"), msg),
|
|
294
|
+
success: (msg) => console.log(chalk_1.default.green("✔"), msg),
|
|
295
|
+
warning: (msg) => console.log(chalk_1.default.yellow("⚠"), msg),
|
|
296
|
+
error: (msg) => console.log(chalk_1.default.red("✖"), msg),
|
|
297
|
+
code: (msg) => console.log(chalk_1.default.gray(msg)),
|
|
298
|
+
// Tool feedback methods
|
|
299
|
+
tool: (name, action) => {
|
|
300
|
+
console.log(chalk_1.default.cyan("🔧"), `Tool: ${chalk_1.default.bold(name)} - ${action}`);
|
|
301
|
+
},
|
|
302
|
+
toolResult: (success, message) => {
|
|
303
|
+
if (success) {
|
|
304
|
+
console.log(chalk_1.default.green(" ✓"), chalk_1.default.gray(truncateOutput(message, 100)));
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
console.log(chalk_1.default.red(" ✗"), chalk_1.default.gray(message));
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
// Display formatted tool output
|
|
311
|
+
toolOutput: (toolName, output) => {
|
|
312
|
+
const formatted = formatToolOutput(toolName, output);
|
|
313
|
+
console.log(formatted);
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
const banner = (modelName, toolCount) => {
|
|
317
|
+
console.log(chalk_1.default.bold.cyan(`
|
|
318
|
+
____ __ ________ ____
|
|
319
|
+
/ __ \\_________ _/ /_ / ____/ / / _/
|
|
320
|
+
/ /_/ / ___/ __ \`/ __ \\ / / / / / /
|
|
321
|
+
/ ____/ / / /_/ / /_/ / / /___/ /____/ /
|
|
322
|
+
/_/ /_/ \\__,_/_.___/ \\____/_____/___/
|
|
323
|
+
`));
|
|
324
|
+
if (modelName) {
|
|
325
|
+
console.log(chalk_1.default.gray(` Active Model: ${chalk_1.default.cyan(modelName)}`));
|
|
326
|
+
}
|
|
327
|
+
if (toolCount !== undefined) {
|
|
328
|
+
console.log(chalk_1.default.gray(` Available Tools: ${chalk_1.default.cyan(toolCount.toString())}`));
|
|
329
|
+
}
|
|
330
|
+
console.log("");
|
|
331
|
+
};
|
|
332
|
+
exports.banner = banner;
|
|
333
|
+
/**
|
|
334
|
+
* Display a diff between two strings
|
|
335
|
+
*/
|
|
336
|
+
const showDiff = (before, after, filename) => {
|
|
337
|
+
if (filename) {
|
|
338
|
+
console.log(chalk_1.default.bold(`\nDiff for ${filename}:`));
|
|
339
|
+
}
|
|
340
|
+
const diff = (0, diff_1.diffLines)(before, after);
|
|
341
|
+
diff.forEach((part) => {
|
|
342
|
+
const color = part.added
|
|
343
|
+
? chalk_1.default.green
|
|
344
|
+
: part.removed
|
|
345
|
+
? chalk_1.default.red
|
|
346
|
+
: chalk_1.default.gray;
|
|
347
|
+
const prefix = part.added ? "+ " : part.removed ? "- " : " ";
|
|
348
|
+
const lines = part.value.split("\n");
|
|
349
|
+
lines.forEach((line) => {
|
|
350
|
+
if (line) {
|
|
351
|
+
console.log(color(prefix + line));
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
console.log("");
|
|
356
|
+
};
|
|
357
|
+
exports.showDiff = showDiff;
|
|
358
|
+
/**
|
|
359
|
+
* Display todo list
|
|
360
|
+
*/
|
|
361
|
+
const showTodoList = (todos) => {
|
|
362
|
+
if (todos.length === 0) {
|
|
363
|
+
console.log(chalk_1.default.gray(" No todos"));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
console.log(chalk_1.default.bold("\n📋 Todo List:"));
|
|
367
|
+
todos.forEach((todo, index) => {
|
|
368
|
+
const status = todo.status === "completed"
|
|
369
|
+
? chalk_1.default.green("✓")
|
|
370
|
+
: todo.status === "in_progress"
|
|
371
|
+
? chalk_1.default.yellow("⋯")
|
|
372
|
+
: chalk_1.default.gray("○");
|
|
373
|
+
const text = todo.status === "in_progress" ? todo.activeForm : todo.content;
|
|
374
|
+
const textColor = todo.status === "completed"
|
|
375
|
+
? chalk_1.default.gray
|
|
376
|
+
: todo.status === "in_progress"
|
|
377
|
+
? chalk_1.default.cyan
|
|
378
|
+
: chalk_1.default.white;
|
|
379
|
+
console.log(` ${status} ${textColor(text)}`);
|
|
380
|
+
});
|
|
381
|
+
console.log("");
|
|
382
|
+
};
|
|
383
|
+
exports.showTodoList = showTodoList;
|
|
384
|
+
/**
|
|
385
|
+
* Show tool execution progress
|
|
386
|
+
*/
|
|
387
|
+
const showToolProgress = (toolName, status) => {
|
|
388
|
+
const icon = status === "started" ? "⏳" : status === "completed" ? "✓" : "✗";
|
|
389
|
+
const color = status === "started"
|
|
390
|
+
? chalk_1.default.yellow
|
|
391
|
+
: status === "completed"
|
|
392
|
+
? chalk_1.default.green
|
|
393
|
+
: chalk_1.default.red;
|
|
394
|
+
console.log(color(`${icon} ${toolName} ${status}`));
|
|
395
|
+
};
|
|
396
|
+
exports.showToolProgress = showToolProgress;
|
|
397
|
+
/**
|
|
398
|
+
* Format a code block with syntax highlighting and borders
|
|
399
|
+
*/
|
|
400
|
+
const formatCodeBlock = (code, language) => {
|
|
401
|
+
const lines = code.split("\n");
|
|
402
|
+
const maxLineLen = Math.max(...lines.map((l) => l.length), 40);
|
|
403
|
+
const boxWidth = Math.min(maxLineLen + 4, 80);
|
|
404
|
+
const langLabel = language ? chalk_1.default.cyan.bold(` ${language} `) : "";
|
|
405
|
+
const topBorder = chalk_1.default.gray("╭") + langLabel + chalk_1.default.gray("─".repeat(Math.max(0, boxWidth - language.length - 3))) + chalk_1.default.gray("╮");
|
|
406
|
+
const bottomBorder = chalk_1.default.gray("╰" + "─".repeat(boxWidth - 1) + "╯");
|
|
407
|
+
const formattedLines = lines.map((line) => {
|
|
408
|
+
const highlighted = highlightCodeLine(line);
|
|
409
|
+
return chalk_1.default.gray("│ ") + highlighted;
|
|
410
|
+
});
|
|
411
|
+
return [
|
|
412
|
+
"",
|
|
413
|
+
topBorder,
|
|
414
|
+
...formattedLines,
|
|
415
|
+
bottomBorder,
|
|
416
|
+
"",
|
|
417
|
+
].join("\n");
|
|
418
|
+
};
|
|
419
|
+
/**
|
|
420
|
+
* Format markdown text with colors for terminal display
|
|
421
|
+
*/
|
|
422
|
+
const formatMarkdown = (text) => {
|
|
423
|
+
let result = text;
|
|
424
|
+
// Extract and format code blocks first (to avoid processing their content)
|
|
425
|
+
const codeBlocks = [];
|
|
426
|
+
let blockIndex = 0;
|
|
427
|
+
result = result.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
428
|
+
const placeholder = `__CODE_BLOCK_${blockIndex}__`;
|
|
429
|
+
codeBlocks.push({
|
|
430
|
+
placeholder,
|
|
431
|
+
formatted: formatCodeBlock(code.trimEnd(), lang || ""),
|
|
432
|
+
});
|
|
433
|
+
blockIndex++;
|
|
434
|
+
return placeholder;
|
|
435
|
+
});
|
|
436
|
+
// Format inline code
|
|
437
|
+
result = result.replace(/`([^`]+)`/g, (_, code) => {
|
|
438
|
+
return chalk_1.default.bgGray.white(` ${code} `);
|
|
439
|
+
});
|
|
440
|
+
// Format headers
|
|
441
|
+
result = result.replace(/^### (.+)$/gm, (_, text) => {
|
|
442
|
+
return chalk_1.default.cyan.bold(` ${text}`);
|
|
443
|
+
});
|
|
444
|
+
result = result.replace(/^## (.+)$/gm, (_, text) => {
|
|
445
|
+
return chalk_1.default.cyan.bold(` ${text}`);
|
|
446
|
+
});
|
|
447
|
+
result = result.replace(/^# (.+)$/gm, (_, text) => {
|
|
448
|
+
return chalk_1.default.cyan.bold.underline(text);
|
|
449
|
+
});
|
|
450
|
+
// Format bold text
|
|
451
|
+
result = result.replace(/\*\*([^*]+)\*\*/g, (_, text) => {
|
|
452
|
+
return chalk_1.default.bold(text);
|
|
453
|
+
});
|
|
454
|
+
// Format italic text
|
|
455
|
+
result = result.replace(/\*([^*]+)\*/g, (_, text) => {
|
|
456
|
+
return chalk_1.default.italic(text);
|
|
457
|
+
});
|
|
458
|
+
// Format bullet points
|
|
459
|
+
result = result.replace(/^(\s*)[-*] (.+)$/gm, (_, indent, text) => {
|
|
460
|
+
return indent + chalk_1.default.yellow("•") + " " + text;
|
|
461
|
+
});
|
|
462
|
+
// Format numbered lists
|
|
463
|
+
result = result.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, indent, num, text) => {
|
|
464
|
+
return indent + chalk_1.default.yellow(num + ".") + " " + text;
|
|
465
|
+
});
|
|
466
|
+
// Format blockquotes
|
|
467
|
+
result = result.replace(/^> (.+)$/gm, (_, text) => {
|
|
468
|
+
return chalk_1.default.gray("│ ") + chalk_1.default.italic.gray(text);
|
|
469
|
+
});
|
|
470
|
+
// Format horizontal rules
|
|
471
|
+
result = result.replace(/^---+$/gm, () => {
|
|
472
|
+
return chalk_1.default.gray("─".repeat(50));
|
|
473
|
+
});
|
|
474
|
+
// Restore code blocks
|
|
475
|
+
for (const block of codeBlocks) {
|
|
476
|
+
result = result.replace(block.placeholder, block.formatted);
|
|
477
|
+
}
|
|
478
|
+
return result;
|
|
479
|
+
};
|
|
480
|
+
exports.formatMarkdown = formatMarkdown;
|
|
481
|
+
/**
|
|
482
|
+
* Stream formatter for real-time markdown rendering
|
|
483
|
+
* Handles partial chunks and code block detection
|
|
484
|
+
*/
|
|
485
|
+
class StreamFormatter {
|
|
486
|
+
constructor() {
|
|
487
|
+
this.buffer = "";
|
|
488
|
+
this.inCodeBlock = false;
|
|
489
|
+
this.codeBlockLang = "";
|
|
490
|
+
this.codeBlockContent = "";
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Process a chunk of streaming text and return formatted output
|
|
494
|
+
*/
|
|
495
|
+
processChunk(chunk) {
|
|
496
|
+
this.buffer += chunk;
|
|
497
|
+
let output = "";
|
|
498
|
+
// Process complete lines
|
|
499
|
+
while (this.buffer.includes("\n")) {
|
|
500
|
+
const newlineIndex = this.buffer.indexOf("\n");
|
|
501
|
+
const line = this.buffer.slice(0, newlineIndex);
|
|
502
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
503
|
+
output += this.processLine(line) + "\n";
|
|
504
|
+
}
|
|
505
|
+
return output;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Flush remaining buffer content
|
|
509
|
+
*/
|
|
510
|
+
flush() {
|
|
511
|
+
if (this.buffer.length > 0) {
|
|
512
|
+
const remaining = this.processLine(this.buffer);
|
|
513
|
+
this.buffer = "";
|
|
514
|
+
return remaining;
|
|
515
|
+
}
|
|
516
|
+
return "";
|
|
517
|
+
}
|
|
518
|
+
processLine(line) {
|
|
519
|
+
// Check for code block start
|
|
520
|
+
if (line.startsWith("```") && !this.inCodeBlock) {
|
|
521
|
+
this.inCodeBlock = true;
|
|
522
|
+
this.codeBlockLang = line.slice(3).trim();
|
|
523
|
+
this.codeBlockContent = "";
|
|
524
|
+
return ""; // Don't output yet
|
|
525
|
+
}
|
|
526
|
+
// Check for code block end
|
|
527
|
+
if (line === "```" && this.inCodeBlock) {
|
|
528
|
+
this.inCodeBlock = false;
|
|
529
|
+
const formatted = formatCodeBlock(this.codeBlockContent.trimEnd(), this.codeBlockLang);
|
|
530
|
+
this.codeBlockContent = "";
|
|
531
|
+
this.codeBlockLang = "";
|
|
532
|
+
return formatted;
|
|
533
|
+
}
|
|
534
|
+
// Inside code block - accumulate
|
|
535
|
+
if (this.inCodeBlock) {
|
|
536
|
+
this.codeBlockContent += line + "\n";
|
|
537
|
+
return ""; // Don't output yet
|
|
538
|
+
}
|
|
539
|
+
// Regular line - format it
|
|
540
|
+
return this.formatLine(line);
|
|
541
|
+
}
|
|
542
|
+
formatLine(line) {
|
|
543
|
+
// Format inline code
|
|
544
|
+
let result = line.replace(/`([^`]+)`/g, (_, code) => {
|
|
545
|
+
return chalk_1.default.bgGray.white(` ${code} `);
|
|
546
|
+
});
|
|
547
|
+
// Format headers
|
|
548
|
+
if (result.startsWith("### ")) {
|
|
549
|
+
return chalk_1.default.cyan.bold(" " + result.slice(4));
|
|
550
|
+
}
|
|
551
|
+
if (result.startsWith("## ")) {
|
|
552
|
+
return chalk_1.default.cyan.bold(" " + result.slice(3));
|
|
553
|
+
}
|
|
554
|
+
if (result.startsWith("# ")) {
|
|
555
|
+
return chalk_1.default.cyan.bold.underline(result.slice(2));
|
|
556
|
+
}
|
|
557
|
+
// Format bold
|
|
558
|
+
result = result.replace(/\*\*([^*]+)\*\*/g, (_, text) => chalk_1.default.bold(text));
|
|
559
|
+
// Format italic
|
|
560
|
+
result = result.replace(/\*([^*]+)\*/g, (_, text) => chalk_1.default.italic(text));
|
|
561
|
+
// Format bullets
|
|
562
|
+
if (/^\s*[-*] /.test(result)) {
|
|
563
|
+
result = result.replace(/^(\s*)[-*] /, (_, indent) => indent + chalk_1.default.yellow("•") + " ");
|
|
564
|
+
}
|
|
565
|
+
// Format numbered lists
|
|
566
|
+
result = result.replace(/^(\s*)(\d+)\. /, (_, indent, num) => indent + chalk_1.default.yellow(num + ".") + " ");
|
|
567
|
+
// Format blockquotes
|
|
568
|
+
if (result.startsWith("> ")) {
|
|
569
|
+
return chalk_1.default.gray("│ ") + chalk_1.default.italic.gray(result.slice(2));
|
|
570
|
+
}
|
|
571
|
+
// Format horizontal rules
|
|
572
|
+
if (/^---+$/.test(result)) {
|
|
573
|
+
return chalk_1.default.gray("─".repeat(50));
|
|
574
|
+
}
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
exports.StreamFormatter = StreamFormatter;
|