grokcodecli 0.1.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/.claude/settings.local.json +32 -0
- package/README.md +1464 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/loader.d.ts +34 -0
- package/dist/commands/loader.d.ts.map +1 -0
- package/dist/commands/loader.js +192 -0
- package/dist/commands/loader.js.map +1 -0
- package/dist/config/manager.d.ts +21 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +203 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/conversation/chat.d.ts +50 -0
- package/dist/conversation/chat.d.ts.map +1 -0
- package/dist/conversation/chat.js +1145 -0
- package/dist/conversation/chat.js.map +1 -0
- package/dist/conversation/history.d.ts +24 -0
- package/dist/conversation/history.d.ts.map +1 -0
- package/dist/conversation/history.js +103 -0
- package/dist/conversation/history.js.map +1 -0
- package/dist/grok/client.d.ts +86 -0
- package/dist/grok/client.d.ts.map +1 -0
- package/dist/grok/client.js +106 -0
- package/dist/grok/client.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +26 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +170 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/tools/bash.d.ts +8 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +102 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit.d.ts +9 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +61 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/glob.d.ts +7 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +38 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +8 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +78 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/read.d.ts +8 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +96 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/registry.d.ts +42 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +230 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/webfetch.d.ts +10 -0
- package/dist/tools/webfetch.d.ts.map +1 -0
- package/dist/tools/webfetch.js +108 -0
- package/dist/tools/webfetch.js.map +1 -0
- package/dist/tools/websearch.d.ts +7 -0
- package/dist/tools/websearch.d.ts.map +1 -0
- package/dist/tools/websearch.js +180 -0
- package/dist/tools/websearch.js.map +1 -0
- package/dist/tools/write.d.ts +7 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +80 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/utils/security.d.ts +36 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +227 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/ui.d.ts +49 -0
- package/dist/utils/ui.d.ts.map +1 -0
- package/dist/utils/ui.js +302 -0
- package/dist/utils/ui.js.map +1 -0
- package/package.json +45 -0
- package/src/cli.ts +68 -0
- package/src/commands/loader.ts +244 -0
- package/src/config/manager.ts +239 -0
- package/src/conversation/chat.ts +1294 -0
- package/src/conversation/history.ts +131 -0
- package/src/grok/client.ts +192 -0
- package/src/index.ts +8 -0
- package/src/permissions/manager.ts +208 -0
- package/src/tools/bash.ts +119 -0
- package/src/tools/edit.ts +73 -0
- package/src/tools/glob.ts +49 -0
- package/src/tools/grep.ts +96 -0
- package/src/tools/read.ts +116 -0
- package/src/tools/registry.ts +248 -0
- package/src/tools/webfetch.ts +127 -0
- package/src/tools/websearch.ts +219 -0
- package/src/tools/write.ts +94 -0
- package/src/utils/security.ts +259 -0
- package/src/utils/ui.ts +382 -0
- package/tsconfig.json +22 -0
package/src/utils/ui.ts
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Utilities
|
|
3
|
+
*
|
|
4
|
+
* Beautiful terminal output, syntax highlighting, progress indicators,
|
|
5
|
+
* and user experience enhancements.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Box Drawing & Borders
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
export const BOX = {
|
|
15
|
+
topLeft: '╭',
|
|
16
|
+
topRight: '╮',
|
|
17
|
+
bottomLeft: '╰',
|
|
18
|
+
bottomRight: '╯',
|
|
19
|
+
horizontal: '─',
|
|
20
|
+
vertical: '│',
|
|
21
|
+
teeRight: '├',
|
|
22
|
+
teeLeft: '┤',
|
|
23
|
+
teeDown: '┬',
|
|
24
|
+
teeUp: '┴',
|
|
25
|
+
cross: '┼',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function drawBox(content: string[], options: {
|
|
29
|
+
title?: string;
|
|
30
|
+
width?: number;
|
|
31
|
+
padding?: number;
|
|
32
|
+
borderColor?: typeof chalk;
|
|
33
|
+
} = {}): string {
|
|
34
|
+
const {
|
|
35
|
+
title = '',
|
|
36
|
+
width = Math.max(...content.map(l => stripAnsi(l).length), title.length) + 4,
|
|
37
|
+
padding = 1,
|
|
38
|
+
borderColor = chalk.cyan,
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
const innerWidth = width - 2;
|
|
42
|
+
const lines: string[] = [];
|
|
43
|
+
|
|
44
|
+
// Top border
|
|
45
|
+
if (title) {
|
|
46
|
+
const titlePadded = ` ${title} `;
|
|
47
|
+
const leftPad = Math.floor((innerWidth - titlePadded.length) / 2);
|
|
48
|
+
const rightPad = innerWidth - leftPad - titlePadded.length;
|
|
49
|
+
lines.push(
|
|
50
|
+
borderColor(BOX.topLeft) +
|
|
51
|
+
borderColor(BOX.horizontal.repeat(leftPad)) +
|
|
52
|
+
chalk.bold(titlePadded) +
|
|
53
|
+
borderColor(BOX.horizontal.repeat(rightPad)) +
|
|
54
|
+
borderColor(BOX.topRight)
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
lines.push(
|
|
58
|
+
borderColor(BOX.topLeft + BOX.horizontal.repeat(innerWidth) + BOX.topRight)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Padding top
|
|
63
|
+
for (let i = 0; i < padding; i++) {
|
|
64
|
+
lines.push(borderColor(BOX.vertical) + ' '.repeat(innerWidth) + borderColor(BOX.vertical));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Content
|
|
68
|
+
for (const line of content) {
|
|
69
|
+
const stripped = stripAnsi(line);
|
|
70
|
+
const pad = innerWidth - stripped.length - 2;
|
|
71
|
+
lines.push(
|
|
72
|
+
borderColor(BOX.vertical) +
|
|
73
|
+
' ' + line + ' '.repeat(Math.max(0, pad + 1)) +
|
|
74
|
+
borderColor(BOX.vertical)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Padding bottom
|
|
79
|
+
for (let i = 0; i < padding; i++) {
|
|
80
|
+
lines.push(borderColor(BOX.vertical) + ' '.repeat(innerWidth) + borderColor(BOX.vertical));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Bottom border
|
|
84
|
+
lines.push(
|
|
85
|
+
borderColor(BOX.bottomLeft + BOX.horizontal.repeat(innerWidth) + BOX.bottomRight)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return lines.join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Syntax Highlighting
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
const LANGUAGE_KEYWORDS: Record<string, string[]> = {
|
|
96
|
+
typescript: ['const', 'let', 'var', 'function', 'class', 'interface', 'type', 'import', 'export', 'from', 'return', 'if', 'else', 'for', 'while', 'async', 'await', 'new', 'this', 'extends', 'implements', 'private', 'public', 'protected', 'static', 'readonly'],
|
|
97
|
+
javascript: ['const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'return', 'if', 'else', 'for', 'while', 'async', 'await', 'new', 'this', 'extends'],
|
|
98
|
+
python: ['def', 'class', 'import', 'from', 'return', 'if', 'elif', 'else', 'for', 'while', 'try', 'except', 'finally', 'with', 'as', 'yield', 'lambda', 'None', 'True', 'False', 'and', 'or', 'not', 'in', 'is', 'async', 'await'],
|
|
99
|
+
rust: ['fn', 'let', 'mut', 'const', 'struct', 'enum', 'impl', 'trait', 'pub', 'use', 'mod', 'match', 'if', 'else', 'for', 'while', 'loop', 'return', 'async', 'await', 'self', 'Self'],
|
|
100
|
+
go: ['func', 'var', 'const', 'type', 'struct', 'interface', 'package', 'import', 'return', 'if', 'else', 'for', 'range', 'switch', 'case', 'default', 'defer', 'go', 'chan', 'map', 'make', 'new'],
|
|
101
|
+
bash: ['if', 'then', 'else', 'elif', 'fi', 'for', 'do', 'done', 'while', 'case', 'esac', 'function', 'return', 'exit', 'export', 'local', 'readonly'],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const LANG_ALIASES: Record<string, string> = {
|
|
105
|
+
ts: 'typescript',
|
|
106
|
+
js: 'javascript',
|
|
107
|
+
py: 'python',
|
|
108
|
+
rs: 'rust',
|
|
109
|
+
sh: 'bash',
|
|
110
|
+
shell: 'bash',
|
|
111
|
+
zsh: 'bash',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export function highlightCode(code: string, language?: string): string {
|
|
115
|
+
const lang = language ? (LANG_ALIASES[language] || language).toLowerCase() : '';
|
|
116
|
+
const keywords = LANGUAGE_KEYWORDS[lang] || [];
|
|
117
|
+
|
|
118
|
+
let result = code;
|
|
119
|
+
|
|
120
|
+
// Highlight strings
|
|
121
|
+
result = result.replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1/g, chalk.green('$&'));
|
|
122
|
+
|
|
123
|
+
// Highlight numbers
|
|
124
|
+
result = result.replace(/\b(\d+\.?\d*)\b/g, chalk.yellow('$1'));
|
|
125
|
+
|
|
126
|
+
// Highlight comments
|
|
127
|
+
result = result.replace(/(\/\/.*$|#.*$)/gm, chalk.gray('$1'));
|
|
128
|
+
result = result.replace(/(\/\*[\s\S]*?\*\/)/g, chalk.gray('$1'));
|
|
129
|
+
|
|
130
|
+
// Highlight keywords
|
|
131
|
+
for (const keyword of keywords) {
|
|
132
|
+
const regex = new RegExp(`\\b(${keyword})\\b`, 'g');
|
|
133
|
+
result = result.replace(regex, chalk.magenta('$1'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Highlight function calls
|
|
137
|
+
result = result.replace(/\b([a-zA-Z_]\w*)\s*\(/g, chalk.blue('$1') + '(');
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function formatCodeBlock(code: string, language?: string, showLineNumbers = true): string {
|
|
143
|
+
const highlighted = highlightCode(code, language);
|
|
144
|
+
const lines = highlighted.split('\n');
|
|
145
|
+
|
|
146
|
+
const header = chalk.gray(`─── ${language || 'code'} ${'─'.repeat(Math.max(0, 40 - (language?.length || 4)))}`);
|
|
147
|
+
const footer = chalk.gray('─'.repeat(45));
|
|
148
|
+
|
|
149
|
+
if (showLineNumbers) {
|
|
150
|
+
const padding = String(lines.length).length;
|
|
151
|
+
const numberedLines = lines.map((line, i) =>
|
|
152
|
+
chalk.gray(String(i + 1).padStart(padding) + ' │ ') + line
|
|
153
|
+
);
|
|
154
|
+
return `${header}\n${numberedLines.join('\n')}\n${footer}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return `${header}\n${highlighted}\n${footer}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Progress & Status Indicators
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
export function progressBar(current: number, total: number, width = 30): string {
|
|
165
|
+
const percent = Math.min(100, Math.round((current / total) * 100));
|
|
166
|
+
const filled = Math.round((percent / 100) * width);
|
|
167
|
+
const empty = width - filled;
|
|
168
|
+
|
|
169
|
+
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
170
|
+
return `[${bar}] ${percent}%`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function spinner(): { start: () => void; stop: (success?: boolean) => void; update: (text: string) => void } {
|
|
174
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
175
|
+
let i = 0;
|
|
176
|
+
let interval: NodeJS.Timeout | null = null;
|
|
177
|
+
let text = '';
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
start() {
|
|
181
|
+
interval = setInterval(() => {
|
|
182
|
+
process.stdout.write(`\r${chalk.cyan(frames[i])} ${text}`);
|
|
183
|
+
i = (i + 1) % frames.length;
|
|
184
|
+
}, 80);
|
|
185
|
+
},
|
|
186
|
+
stop(success = true) {
|
|
187
|
+
if (interval) {
|
|
188
|
+
clearInterval(interval);
|
|
189
|
+
const icon = success ? chalk.green('✓') : chalk.red('✗');
|
|
190
|
+
process.stdout.write(`\r${icon} ${text}\n`);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
update(newText: string) {
|
|
194
|
+
text = newText;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// Badges & Tags
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
export function badge(text: string, type: 'info' | 'success' | 'warning' | 'error' = 'info'): string {
|
|
204
|
+
const colors = {
|
|
205
|
+
info: chalk.bgCyan.black,
|
|
206
|
+
success: chalk.bgGreen.black,
|
|
207
|
+
warning: chalk.bgYellow.black,
|
|
208
|
+
error: chalk.bgRed.white,
|
|
209
|
+
};
|
|
210
|
+
return colors[type](` ${text} `);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function tag(text: string, color: 'cyan' | 'green' | 'yellow' | 'red' | 'blue' | 'magenta' = 'cyan'): string {
|
|
214
|
+
const colors = {
|
|
215
|
+
cyan: chalk.cyan,
|
|
216
|
+
green: chalk.green,
|
|
217
|
+
yellow: chalk.yellow,
|
|
218
|
+
red: chalk.red,
|
|
219
|
+
blue: chalk.blue,
|
|
220
|
+
magenta: chalk.magenta,
|
|
221
|
+
};
|
|
222
|
+
return colors[color](`[${text}]`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// Welcome Screen & Branding
|
|
227
|
+
// ============================================================================
|
|
228
|
+
|
|
229
|
+
export function welcomeScreen(version: string, model: string, cwd: string): string {
|
|
230
|
+
const logo = `
|
|
231
|
+
██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
232
|
+
██╔════╝ ██╔══██╗██╔═══██╗██║ ██╔╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
233
|
+
██║ ███╗██████╔╝██║ ██║█████╔╝ ██║ ██║ ██║██║ ██║█████╗
|
|
234
|
+
██║ ██║██╔══██╗██║ ██║██╔═██╗ ██║ ██║ ██║██║ ██║██╔══╝
|
|
235
|
+
╚██████╔╝██║ ██║╚██████╔╝██║ ██╗ ╚██████╗╚██████╔╝██████╔╝███████╗
|
|
236
|
+
╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝`;
|
|
237
|
+
|
|
238
|
+
const info = [
|
|
239
|
+
'',
|
|
240
|
+
` ${chalk.gray('Version:')} ${chalk.cyan(version)}`,
|
|
241
|
+
` ${chalk.gray('Model:')} ${chalk.green(model)}`,
|
|
242
|
+
` ${chalk.gray('CWD:')} ${chalk.blue(cwd)}`,
|
|
243
|
+
'',
|
|
244
|
+
` ${chalk.gray('Type')} ${chalk.cyan('/help')} ${chalk.gray('for commands,')} ${chalk.yellow('exit')} ${chalk.gray('to quit')}`,
|
|
245
|
+
'',
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
return chalk.cyan(logo) + info.join('\n');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function compactWelcome(version: string, model: string): string {
|
|
252
|
+
return `
|
|
253
|
+
${chalk.cyan('╭─────────────────────────────────────────────╮')}
|
|
254
|
+
${chalk.cyan('│')} ${chalk.bold.cyan('🚀 Grok Code CLI')} ${chalk.gray(`v${version}`)} ${chalk.cyan('│')}
|
|
255
|
+
${chalk.cyan('│')} ${chalk.gray('Model:')} ${chalk.green(model.padEnd(32))} ${chalk.cyan('│')}
|
|
256
|
+
${chalk.cyan('│')} ${chalk.gray('Type /help for commands, exit to quit')} ${chalk.cyan('│')}
|
|
257
|
+
${chalk.cyan('╰─────────────────────────────────────────────╯')}
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// Tips & Hints
|
|
263
|
+
// ============================================================================
|
|
264
|
+
|
|
265
|
+
const TIPS = [
|
|
266
|
+
'Use /compact to reduce context when conversations get long',
|
|
267
|
+
'Press Ctrl+C to cancel a running command',
|
|
268
|
+
'Use /export conversation.md to save your chat',
|
|
269
|
+
'The /doctor command checks your setup for issues',
|
|
270
|
+
'You can resume previous sessions with /resume',
|
|
271
|
+
'Use /model to switch between Grok 4, Grok 3 and specialized models',
|
|
272
|
+
'The /context command shows how much context you\'re using',
|
|
273
|
+
'Session are auto-saved - you can always pick up where you left off',
|
|
274
|
+
'Use /history to see your recent conversations',
|
|
275
|
+
'The Read, Write, and Edit tools work on any file type',
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
export function randomTip(): string {
|
|
279
|
+
const tip = TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
280
|
+
return chalk.gray(`💡 Tip: ${tip}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ============================================================================
|
|
284
|
+
// Tables
|
|
285
|
+
// ============================================================================
|
|
286
|
+
|
|
287
|
+
export function table(headers: string[], rows: string[][], options: {
|
|
288
|
+
padding?: number;
|
|
289
|
+
headerColor?: typeof chalk;
|
|
290
|
+
} = {}): string {
|
|
291
|
+
const { padding = 1, headerColor = chalk.bold.cyan } = options;
|
|
292
|
+
|
|
293
|
+
// Calculate column widths
|
|
294
|
+
const colWidths = headers.map((h, i) => {
|
|
295
|
+
const maxContent = Math.max(h.length, ...rows.map(r => stripAnsi(r[i] || '').length));
|
|
296
|
+
return maxContent + padding * 2;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Build separator
|
|
300
|
+
const separator = BOX.horizontal;
|
|
301
|
+
const topBorder = BOX.topLeft + colWidths.map(w => separator.repeat(w)).join(BOX.teeDown) + BOX.topRight;
|
|
302
|
+
const midBorder = BOX.teeRight + colWidths.map(w => separator.repeat(w)).join(BOX.cross) + BOX.teeLeft;
|
|
303
|
+
const bottomBorder = BOX.bottomLeft + colWidths.map(w => separator.repeat(w)).join(BOX.teeUp) + BOX.bottomRight;
|
|
304
|
+
|
|
305
|
+
// Build header row
|
|
306
|
+
const headerRow = BOX.vertical + headers.map((h, i) => {
|
|
307
|
+
const padded = h.padStart(Math.floor((colWidths[i] + h.length) / 2)).padEnd(colWidths[i]);
|
|
308
|
+
return headerColor(padded);
|
|
309
|
+
}).join(BOX.vertical) + BOX.vertical;
|
|
310
|
+
|
|
311
|
+
// Build content rows
|
|
312
|
+
const contentRows = rows.map(row =>
|
|
313
|
+
BOX.vertical + row.map((cell, i) => {
|
|
314
|
+
const stripped = stripAnsi(cell || '');
|
|
315
|
+
const padded = ' '.repeat(padding) + cell + ' '.repeat(colWidths[i] - stripped.length - padding);
|
|
316
|
+
return padded;
|
|
317
|
+
}).join(BOX.vertical) + BOX.vertical
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
return [topBorder, headerRow, midBorder, ...contentRows, bottomBorder].join('\n');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ============================================================================
|
|
324
|
+
// Diff Display
|
|
325
|
+
// ============================================================================
|
|
326
|
+
|
|
327
|
+
export function formatDiff(oldContent: string, newContent: string): string {
|
|
328
|
+
const oldLines = oldContent.split('\n');
|
|
329
|
+
const newLines = newContent.split('\n');
|
|
330
|
+
|
|
331
|
+
const lines: string[] = [];
|
|
332
|
+
|
|
333
|
+
let oldIdx = 0;
|
|
334
|
+
let newIdx = 0;
|
|
335
|
+
|
|
336
|
+
while (oldIdx < oldLines.length || newIdx < newLines.length) {
|
|
337
|
+
if (oldIdx >= oldLines.length) {
|
|
338
|
+
lines.push(chalk.green(`+ ${newLines[newIdx]}`));
|
|
339
|
+
newIdx++;
|
|
340
|
+
} else if (newIdx >= newLines.length) {
|
|
341
|
+
lines.push(chalk.red(`- ${oldLines[oldIdx]}`));
|
|
342
|
+
oldIdx++;
|
|
343
|
+
} else if (oldLines[oldIdx] === newLines[newIdx]) {
|
|
344
|
+
lines.push(chalk.gray(` ${oldLines[oldIdx]}`));
|
|
345
|
+
oldIdx++;
|
|
346
|
+
newIdx++;
|
|
347
|
+
} else {
|
|
348
|
+
lines.push(chalk.red(`- ${oldLines[oldIdx]}`));
|
|
349
|
+
lines.push(chalk.green(`+ ${newLines[newIdx]}`));
|
|
350
|
+
oldIdx++;
|
|
351
|
+
newIdx++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return lines.join('\n');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ============================================================================
|
|
359
|
+
// Helpers
|
|
360
|
+
// ============================================================================
|
|
361
|
+
|
|
362
|
+
function stripAnsi(str: string): string {
|
|
363
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function truncate(str: string, maxLength: number, suffix = '...'): string {
|
|
367
|
+
if (str.length <= maxLength) return str;
|
|
368
|
+
return str.slice(0, maxLength - suffix.length) + suffix;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function indent(text: string, spaces = 2): string {
|
|
372
|
+
const pad = ' '.repeat(spaces);
|
|
373
|
+
return text.split('\n').map(line => pad + line).join('\n');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function divider(char = '─', length = 50): string {
|
|
377
|
+
return chalk.gray(char.repeat(length));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function timestamp(): string {
|
|
381
|
+
return chalk.gray(new Date().toLocaleTimeString());
|
|
382
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
"jsxImportSource": "react"
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|