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.
Files changed (99) hide show
  1. package/.claude/settings.local.json +32 -0
  2. package/README.md +1464 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +61 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/loader.d.ts +34 -0
  8. package/dist/commands/loader.d.ts.map +1 -0
  9. package/dist/commands/loader.js +192 -0
  10. package/dist/commands/loader.js.map +1 -0
  11. package/dist/config/manager.d.ts +21 -0
  12. package/dist/config/manager.d.ts.map +1 -0
  13. package/dist/config/manager.js +203 -0
  14. package/dist/config/manager.js.map +1 -0
  15. package/dist/conversation/chat.d.ts +50 -0
  16. package/dist/conversation/chat.d.ts.map +1 -0
  17. package/dist/conversation/chat.js +1145 -0
  18. package/dist/conversation/chat.js.map +1 -0
  19. package/dist/conversation/history.d.ts +24 -0
  20. package/dist/conversation/history.d.ts.map +1 -0
  21. package/dist/conversation/history.js +103 -0
  22. package/dist/conversation/history.js.map +1 -0
  23. package/dist/grok/client.d.ts +86 -0
  24. package/dist/grok/client.d.ts.map +1 -0
  25. package/dist/grok/client.js +106 -0
  26. package/dist/grok/client.js.map +1 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +8 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/permissions/manager.d.ts +26 -0
  32. package/dist/permissions/manager.d.ts.map +1 -0
  33. package/dist/permissions/manager.js +170 -0
  34. package/dist/permissions/manager.js.map +1 -0
  35. package/dist/tools/bash.d.ts +8 -0
  36. package/dist/tools/bash.d.ts.map +1 -0
  37. package/dist/tools/bash.js +102 -0
  38. package/dist/tools/bash.js.map +1 -0
  39. package/dist/tools/edit.d.ts +9 -0
  40. package/dist/tools/edit.d.ts.map +1 -0
  41. package/dist/tools/edit.js +61 -0
  42. package/dist/tools/edit.js.map +1 -0
  43. package/dist/tools/glob.d.ts +7 -0
  44. package/dist/tools/glob.d.ts.map +1 -0
  45. package/dist/tools/glob.js +38 -0
  46. package/dist/tools/glob.js.map +1 -0
  47. package/dist/tools/grep.d.ts +8 -0
  48. package/dist/tools/grep.d.ts.map +1 -0
  49. package/dist/tools/grep.js +78 -0
  50. package/dist/tools/grep.js.map +1 -0
  51. package/dist/tools/read.d.ts +8 -0
  52. package/dist/tools/read.d.ts.map +1 -0
  53. package/dist/tools/read.js +96 -0
  54. package/dist/tools/read.js.map +1 -0
  55. package/dist/tools/registry.d.ts +42 -0
  56. package/dist/tools/registry.d.ts.map +1 -0
  57. package/dist/tools/registry.js +230 -0
  58. package/dist/tools/registry.js.map +1 -0
  59. package/dist/tools/webfetch.d.ts +10 -0
  60. package/dist/tools/webfetch.d.ts.map +1 -0
  61. package/dist/tools/webfetch.js +108 -0
  62. package/dist/tools/webfetch.js.map +1 -0
  63. package/dist/tools/websearch.d.ts +7 -0
  64. package/dist/tools/websearch.d.ts.map +1 -0
  65. package/dist/tools/websearch.js +180 -0
  66. package/dist/tools/websearch.js.map +1 -0
  67. package/dist/tools/write.d.ts +7 -0
  68. package/dist/tools/write.d.ts.map +1 -0
  69. package/dist/tools/write.js +80 -0
  70. package/dist/tools/write.js.map +1 -0
  71. package/dist/utils/security.d.ts +36 -0
  72. package/dist/utils/security.d.ts.map +1 -0
  73. package/dist/utils/security.js +227 -0
  74. package/dist/utils/security.js.map +1 -0
  75. package/dist/utils/ui.d.ts +49 -0
  76. package/dist/utils/ui.d.ts.map +1 -0
  77. package/dist/utils/ui.js +302 -0
  78. package/dist/utils/ui.js.map +1 -0
  79. package/package.json +45 -0
  80. package/src/cli.ts +68 -0
  81. package/src/commands/loader.ts +244 -0
  82. package/src/config/manager.ts +239 -0
  83. package/src/conversation/chat.ts +1294 -0
  84. package/src/conversation/history.ts +131 -0
  85. package/src/grok/client.ts +192 -0
  86. package/src/index.ts +8 -0
  87. package/src/permissions/manager.ts +208 -0
  88. package/src/tools/bash.ts +119 -0
  89. package/src/tools/edit.ts +73 -0
  90. package/src/tools/glob.ts +49 -0
  91. package/src/tools/grep.ts +96 -0
  92. package/src/tools/read.ts +116 -0
  93. package/src/tools/registry.ts +248 -0
  94. package/src/tools/webfetch.ts +127 -0
  95. package/src/tools/websearch.ts +219 -0
  96. package/src/tools/write.ts +94 -0
  97. package/src/utils/security.ts +259 -0
  98. package/src/utils/ui.ts +382 -0
  99. package/tsconfig.json +22 -0
@@ -0,0 +1,1145 @@
1
+ import * as readline from 'readline';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import chalk from 'chalk';
6
+ import { GrokClient } from '../grok/client.js';
7
+ import { allTools, executeTool } from '../tools/registry.js';
8
+ import { PermissionManager } from '../permissions/manager.js';
9
+ import { HistoryManager } from './history.js';
10
+ import { ConfigManager } from '../config/manager.js';
11
+ import { drawBox, randomTip } from '../utils/ui.js';
12
+ const SYSTEM_PROMPT = `You are Grok Code, a powerful CLI coding assistant powered by xAI's Grok.
13
+ You help users with software engineering tasks including writing code, debugging, explaining code, and managing files.
14
+
15
+ ## Available Tools
16
+ - **Read**: Read file contents with line numbers
17
+ - **Write**: Write content to files (creates directories if needed)
18
+ - **Edit**: Edit files by replacing exact strings
19
+ - **Bash**: Execute shell commands (git, npm, etc.)
20
+ - **Glob**: Find files by pattern (e.g., "**/*.ts")
21
+ - **Grep**: Search file contents with regex
22
+ - **WebFetch**: Fetch content from URLs
23
+
24
+ ## Guidelines
25
+ 1. **Always read before editing**: Never modify a file you haven't read first
26
+ 2. **Be precise with edits**: The Edit tool requires exact string matches
27
+ 3. **Explain your actions**: Tell the user what you're doing and why
28
+ 4. **Security first**: Never introduce vulnerabilities (XSS, SQL injection, command injection)
29
+ 5. **Stay focused**: Only make changes that are directly requested
30
+ 6. **Use appropriate tools**: Prefer Read/Write/Edit over Bash for file operations
31
+
32
+ ## Git Operations
33
+ - Use Bash for git commands
34
+ - Never force push to main/master without explicit permission
35
+ - Write clear, descriptive commit messages
36
+
37
+ ## Current Context
38
+ - Working directory: ${process.cwd()}
39
+ - Platform: ${process.platform}
40
+ - Date: ${new Date().toLocaleDateString()}`;
41
+ const VERSION = '0.1.0';
42
+ export class GrokChat {
43
+ client;
44
+ messages = [];
45
+ rl;
46
+ permissions;
47
+ history;
48
+ config;
49
+ session = null;
50
+ useStreaming = true;
51
+ workingDirs = [process.cwd()];
52
+ tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
53
+ sessionStartTime = new Date();
54
+ apiKey;
55
+ constructor(options) {
56
+ this.apiKey = options.apiKey;
57
+ this.client = new GrokClient(options.apiKey, options.model || 'grok-4-0709');
58
+ this.rl = readline.createInterface({
59
+ input: process.stdin,
60
+ output: process.stdout,
61
+ });
62
+ this.permissions = new PermissionManager();
63
+ this.permissions.setReadlineInterface(this.rl);
64
+ this.history = new HistoryManager();
65
+ this.config = new ConfigManager();
66
+ }
67
+ async start() {
68
+ // Beautiful welcome screen
69
+ console.log(chalk.cyan(`
70
+ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
71
+ ██╔════╝ ██╔══██╗██╔═══██╗██║ ██╔╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
72
+ ██║ ███╗██████╔╝██║ ██║█████╔╝ ██║ ██║ ██║██║ ██║█████╗
73
+ ██║ ██║██╔══██╗██║ ██║██╔═██╗ ██║ ██║ ██║██║ ██║██╔══╝
74
+ ╚██████╔╝██║ ██║╚██████╔╝██║ ██╗ ╚██████╗╚██████╔╝██████╔╝███████╗
75
+ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝`));
76
+ console.log();
77
+ console.log(drawBox([
78
+ `${chalk.bold('Grok Code CLI')} ${chalk.gray(`v${VERSION}`)}`,
79
+ '',
80
+ `${chalk.gray('Model:')} ${chalk.green(this.client.model)}`,
81
+ `${chalk.gray('CWD:')} ${chalk.blue(process.cwd())}`,
82
+ `${chalk.gray('Tools:')} ${chalk.cyan('8 available')} ${chalk.gray('(Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch)')}`,
83
+ '',
84
+ `${chalk.gray('Commands:')} ${chalk.cyan('/help')} ${chalk.gray('•')} ${chalk.cyan('/model')} ${chalk.gray('•')} ${chalk.cyan('/doctor')} ${chalk.gray('•')} ${chalk.yellow('exit')}`,
85
+ ], { borderColor: chalk.cyan, padding: 0 }));
86
+ console.log();
87
+ console.log(randomTip());
88
+ console.log();
89
+ // Create new session
90
+ this.session = await this.history.createSession(process.cwd());
91
+ this.sessionStartTime = new Date();
92
+ // Initialize with system prompt
93
+ this.messages.push({
94
+ role: 'system',
95
+ content: SYSTEM_PROMPT,
96
+ });
97
+ await this.loop();
98
+ }
99
+ async resume(sessionId) {
100
+ let session = null;
101
+ if (sessionId) {
102
+ session = await this.history.loadSession(sessionId);
103
+ }
104
+ else {
105
+ session = await this.history.getLastSession();
106
+ }
107
+ if (!session) {
108
+ console.log(chalk.yellow('No previous session found. Starting new conversation.\n'));
109
+ await this.start();
110
+ return;
111
+ }
112
+ this.session = session;
113
+ this.messages = session.messages;
114
+ this.sessionStartTime = new Date();
115
+ console.log(chalk.cyan('\n🚀 Grok Code CLI (Resumed)'));
116
+ console.log(chalk.gray(`Session: ${session.title}`));
117
+ console.log(chalk.gray(`Model: ${this.client.model}`));
118
+ console.log(chalk.gray(`Working directory: ${process.cwd()}`));
119
+ console.log(chalk.gray('Type /help for commands, "exit" to quit.\n'));
120
+ // Show recent context
121
+ const recentMessages = this.messages.filter(m => m.role !== 'system').slice(-4);
122
+ if (recentMessages.length > 0) {
123
+ console.log(chalk.gray('─── Recent context ───'));
124
+ for (const msg of recentMessages) {
125
+ if (msg.role === 'user') {
126
+ console.log(chalk.green('You: ') + chalk.gray(msg.content.slice(0, 100) + (msg.content.length > 100 ? '...' : '')));
127
+ }
128
+ else if (msg.role === 'assistant' && msg.content) {
129
+ console.log(chalk.blue('Grok: ') + chalk.gray(msg.content.slice(0, 100) + (msg.content.length > 100 ? '...' : '')));
130
+ }
131
+ }
132
+ console.log(chalk.gray('──────────────────────\n'));
133
+ }
134
+ await this.loop();
135
+ }
136
+ async loop() {
137
+ const prompt = chalk.bold.green('❯ ');
138
+ const question = () => {
139
+ return new Promise((resolve) => {
140
+ this.rl.question(prompt, (answer) => {
141
+ resolve(answer);
142
+ });
143
+ });
144
+ };
145
+ while (true) {
146
+ const input = await question();
147
+ const trimmed = input.trim();
148
+ if (!trimmed)
149
+ continue;
150
+ // Handle commands
151
+ if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {
152
+ await this.saveSession();
153
+ console.log(chalk.gray('\nSession saved. Goodbye!\n'));
154
+ this.rl.close();
155
+ break;
156
+ }
157
+ if (trimmed.startsWith('/')) {
158
+ const shouldExit = await this.handleCommand(trimmed);
159
+ if (shouldExit) {
160
+ this.rl.close();
161
+ break;
162
+ }
163
+ continue;
164
+ }
165
+ await this.processMessage(trimmed);
166
+ }
167
+ }
168
+ async handleCommand(command) {
169
+ const parts = command.slice(1).split(' ');
170
+ const cmd = parts[0].toLowerCase();
171
+ const args = parts.slice(1).join(' ');
172
+ switch (cmd) {
173
+ // Session Management
174
+ case 'clear':
175
+ this.messages = [this.messages[0]]; // Keep system prompt
176
+ this.session = await this.history.createSession(process.cwd());
177
+ this.tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
178
+ console.log(chalk.gray('Conversation cleared.\n'));
179
+ break;
180
+ case 'exit':
181
+ await this.saveSession();
182
+ console.log(chalk.gray('\nSession saved. Goodbye!\n'));
183
+ return true;
184
+ case 'save':
185
+ await this.saveSession();
186
+ console.log(chalk.green('Session saved.\n'));
187
+ break;
188
+ case 'compact':
189
+ await this.handleCompact(args);
190
+ break;
191
+ case 'history':
192
+ await this.showHistory();
193
+ break;
194
+ case 'resume':
195
+ await this.handleResume(args);
196
+ break;
197
+ case 'rename':
198
+ await this.handleRename(args);
199
+ break;
200
+ case 'export':
201
+ await this.handleExport(args);
202
+ break;
203
+ // Configuration
204
+ case 'config':
205
+ await this.handleConfig();
206
+ break;
207
+ case 'model':
208
+ await this.handleModel(args);
209
+ break;
210
+ case 'stream':
211
+ this.useStreaming = !this.useStreaming;
212
+ console.log(chalk.gray(`Streaming ${this.useStreaming ? 'enabled' : 'disabled'}.\n`));
213
+ break;
214
+ case 'permissions':
215
+ this.handlePermissions();
216
+ break;
217
+ // Status & Info
218
+ case 'status':
219
+ this.showStatus();
220
+ break;
221
+ case 'context':
222
+ this.showContext();
223
+ break;
224
+ case 'cost':
225
+ this.showCost();
226
+ break;
227
+ case 'usage':
228
+ this.showUsage();
229
+ break;
230
+ case 'doctor':
231
+ await this.runDoctor();
232
+ break;
233
+ // Working Directory
234
+ case 'add-dir':
235
+ this.handleAddDir(args);
236
+ break;
237
+ case 'pwd':
238
+ console.log(chalk.cyan(`\nWorking directories:\n`));
239
+ this.workingDirs.forEach((dir, i) => {
240
+ console.log(` ${i === 0 ? chalk.green('→') : ' '} ${dir}`);
241
+ });
242
+ console.log();
243
+ break;
244
+ // Help
245
+ case 'help':
246
+ this.showHelp();
247
+ break;
248
+ case 'version':
249
+ console.log(chalk.cyan(`\nGrok Code CLI v${VERSION}\n`));
250
+ break;
251
+ // Project Setup
252
+ case 'init':
253
+ await this.handleInit();
254
+ break;
255
+ case 'review':
256
+ await this.handleReview(args);
257
+ break;
258
+ case 'terminal-setup':
259
+ this.showTerminalSetup();
260
+ break;
261
+ // Convenience aliases
262
+ case 'h':
263
+ this.showHelp();
264
+ break;
265
+ case 'q':
266
+ await this.saveSession();
267
+ console.log(chalk.gray('\nSession saved. Goodbye!\n'));
268
+ return true;
269
+ case 's':
270
+ await this.saveSession();
271
+ console.log(chalk.green('Session saved.\n'));
272
+ break;
273
+ case 'c':
274
+ this.messages = [this.messages[0]];
275
+ this.session = await this.history.createSession(process.cwd());
276
+ console.log(chalk.gray('Conversation cleared.\n'));
277
+ break;
278
+ default:
279
+ console.log(chalk.yellow(`Unknown command: /${cmd}`));
280
+ console.log(chalk.gray('Type /help to see available commands.\n'));
281
+ }
282
+ return false;
283
+ }
284
+ // === Command Handlers ===
285
+ async handleCompact(instructions) {
286
+ const keepCount = 20;
287
+ const originalCount = this.messages.length;
288
+ if (this.messages.length > keepCount + 1) {
289
+ const systemPrompt = this.messages[0];
290
+ const recentMessages = this.messages.slice(-keepCount);
291
+ this.messages = [systemPrompt, ...recentMessages];
292
+ }
293
+ const removedCount = originalCount - this.messages.length;
294
+ console.log(chalk.gray(`Conversation compacted. Removed ${removedCount} messages, kept ${this.messages.length}.\n`));
295
+ if (instructions) {
296
+ console.log(chalk.gray(`Focus instructions: ${instructions}\n`));
297
+ }
298
+ }
299
+ async handleResume(sessionId) {
300
+ if (!sessionId) {
301
+ const sessions = await this.history.listSessions(5);
302
+ if (sessions.length === 0) {
303
+ console.log(chalk.yellow('No sessions to resume.\n'));
304
+ return;
305
+ }
306
+ console.log(chalk.cyan('\nRecent sessions:\n'));
307
+ sessions.forEach((s) => {
308
+ console.log(` ${chalk.gray(s.id.slice(0, 8))} ${s.title}`);
309
+ });
310
+ console.log(chalk.gray('\nUse /resume <session-id> to resume.\n'));
311
+ return;
312
+ }
313
+ const session = await this.history.loadSession(sessionId);
314
+ if (!session) {
315
+ // Try partial match
316
+ const sessions = await this.history.listSessions(50);
317
+ const match = sessions.find(s => s.id.startsWith(sessionId));
318
+ if (match) {
319
+ this.session = match;
320
+ this.messages = match.messages;
321
+ console.log(chalk.green(`Resumed session: ${match.title}\n`));
322
+ return;
323
+ }
324
+ console.log(chalk.red(`Session not found: ${sessionId}\n`));
325
+ return;
326
+ }
327
+ this.session = session;
328
+ this.messages = session.messages;
329
+ console.log(chalk.green(`Resumed session: ${session.title}\n`));
330
+ }
331
+ async handleRename(name) {
332
+ if (!name) {
333
+ console.log(chalk.yellow('Usage: /rename <new-name>\n'));
334
+ return;
335
+ }
336
+ if (this.session) {
337
+ this.session.title = name;
338
+ await this.saveSession();
339
+ console.log(chalk.green(`Session renamed to: ${name}\n`));
340
+ }
341
+ else {
342
+ console.log(chalk.red('No active session to rename.\n'));
343
+ }
344
+ }
345
+ async handleExport(filename) {
346
+ const content = this.messages
347
+ .filter(m => m.role !== 'system')
348
+ .map(m => {
349
+ const role = m.role === 'user' ? 'You' : m.role === 'assistant' ? 'Grok' : 'Tool';
350
+ return `## ${role}\n\n${m.content}\n`;
351
+ })
352
+ .join('\n---\n\n');
353
+ if (filename) {
354
+ const filePath = path.resolve(filename);
355
+ await fs.writeFile(filePath, content, 'utf-8');
356
+ console.log(chalk.green(`Conversation exported to: ${filePath}\n`));
357
+ }
358
+ else {
359
+ // Copy to clipboard concept - just show it
360
+ console.log(chalk.cyan('\n─── Exported Conversation ───\n'));
361
+ console.log(content.slice(0, 2000));
362
+ if (content.length > 2000) {
363
+ console.log(chalk.gray('\n... (truncated, use /export <filename> to save full conversation)'));
364
+ }
365
+ console.log(chalk.cyan('\n─────────────────────────────\n'));
366
+ }
367
+ }
368
+ async handleConfig() {
369
+ console.log(chalk.cyan('\n⚙️ Configuration\n'));
370
+ const apiKey = await this.config.getApiKey();
371
+ console.log(` API Key: ${apiKey ? chalk.green('✓ Set') : chalk.red('✗ Not set')}`);
372
+ console.log(` Model: ${this.client.model}`);
373
+ console.log(` Streaming: ${this.useStreaming ? chalk.green('enabled') : chalk.gray('disabled')}`);
374
+ console.log(` Temperature: ${this.config.get('temperature')}`);
375
+ console.log(` Max Tokens: ${this.config.get('maxTokens')}`);
376
+ console.log(` Auto-approve: ${this.config.get('autoApprove').join(', ') || 'none'}`);
377
+ console.log();
378
+ console.log(chalk.gray(` Config file: ~/.config/grokcodecli/config.json`));
379
+ console.log(chalk.gray(' Run `grok config` in terminal to modify.\n'));
380
+ }
381
+ async handleModel(modelName) {
382
+ // Fetch latest models from xAI API dynamically
383
+ console.log(chalk.gray('Fetching available models...\n'));
384
+ let availableModels = [];
385
+ try {
386
+ const response = await fetch('https://api.x.ai/v1/models', {
387
+ headers: { 'Authorization': `Bearer ${this.apiKey}` },
388
+ });
389
+ if (response.ok) {
390
+ const data = await response.json();
391
+ availableModels = data.data.map(m => m.id).sort();
392
+ }
393
+ else {
394
+ // Fallback to known models if API fails
395
+ availableModels = [
396
+ 'grok-4-0709', 'grok-4-fast-reasoning', 'grok-4-fast-non-reasoning',
397
+ 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning',
398
+ 'grok-3', 'grok-3-mini', 'grok-code-fast-1',
399
+ 'grok-2-vision-1212', 'grok-2-image-1212',
400
+ ];
401
+ }
402
+ }
403
+ catch {
404
+ // Fallback to known models
405
+ availableModels = [
406
+ 'grok-4-0709', 'grok-4-fast-reasoning', 'grok-4-fast-non-reasoning',
407
+ 'grok-3', 'grok-3-mini', 'grok-code-fast-1',
408
+ ];
409
+ }
410
+ if (!modelName) {
411
+ console.log();
412
+ console.log(chalk.cyan('╭──────────────────────────────────────────────────────────────────────╮'));
413
+ console.log(chalk.cyan('│') + chalk.bold.cyan(' 🤖 Model Selection ') + chalk.cyan('│'));
414
+ console.log(chalk.cyan('╰──────────────────────────────────────────────────────────────────────╯'));
415
+ console.log();
416
+ console.log(` ${chalk.gray('Current Model:')} ${chalk.bold.green(this.client.model)}`);
417
+ console.log();
418
+ // Better categorization
419
+ const grok41Reasoning = [];
420
+ const grok41NonReasoning = [];
421
+ const grok4Reasoning = [];
422
+ const grok4NonReasoning = [];
423
+ const grok4Other = [];
424
+ const grok3 = [];
425
+ const grok2 = [];
426
+ const specialized = [];
427
+ for (const model of availableModels) {
428
+ if (model.startsWith('grok-4-1')) {
429
+ if (model.includes('non-reasoning')) {
430
+ grok41NonReasoning.push(model);
431
+ }
432
+ else if (model.includes('reasoning')) {
433
+ grok41Reasoning.push(model);
434
+ }
435
+ }
436
+ else if (model.startsWith('grok-4')) {
437
+ if (model.includes('non-reasoning')) {
438
+ grok4NonReasoning.push(model);
439
+ }
440
+ else if (model.includes('reasoning')) {
441
+ grok4Reasoning.push(model);
442
+ }
443
+ else {
444
+ grok4Other.push(model);
445
+ }
446
+ }
447
+ else if (model.startsWith('grok-3')) {
448
+ grok3.push(model);
449
+ }
450
+ else if (model.startsWith('grok-2')) {
451
+ grok2.push(model);
452
+ }
453
+ else if (model.includes('code') || model.includes('vision') || model.includes('image')) {
454
+ specialized.push(model);
455
+ }
456
+ }
457
+ // Display Grok 4.1 (Latest)
458
+ if (grok41Reasoning.length > 0 || grok41NonReasoning.length > 0) {
459
+ console.log(chalk.bold.magenta(' ⭐ Grok 4.1 (Latest)'));
460
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
461
+ for (const model of grok41Reasoning) {
462
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
463
+ console.log(` ${chalk.green('🧠')} ${chalk.green(model)}${current} ${chalk.yellow('[REASONING]')}`);
464
+ }
465
+ for (const model of grok41NonReasoning) {
466
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
467
+ console.log(` ${chalk.cyan('⚡')} ${model}${current} ${chalk.gray('[FAST]')}`);
468
+ }
469
+ console.log();
470
+ }
471
+ // Display Grok 4
472
+ if (grok4Reasoning.length > 0 || grok4NonReasoning.length > 0 || grok4Other.length > 0) {
473
+ console.log(chalk.bold.cyan(' 🚀 Grok 4'));
474
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
475
+ for (const model of grok4Other) {
476
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
477
+ console.log(` ${chalk.cyan('•')} ${model}${current} ${chalk.gray('(recommended)')}`);
478
+ }
479
+ for (const model of grok4Reasoning) {
480
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
481
+ console.log(` ${chalk.green('🧠')} ${chalk.green(model)}${current} ${chalk.yellow('[REASONING]')}`);
482
+ }
483
+ for (const model of grok4NonReasoning) {
484
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
485
+ console.log(` ${chalk.cyan('⚡')} ${model}${current} ${chalk.gray('[FAST]')}`);
486
+ }
487
+ console.log();
488
+ }
489
+ // Display Grok 3
490
+ if (grok3.length > 0) {
491
+ console.log(chalk.bold.blue(' 📦 Grok 3'));
492
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
493
+ for (const model of grok3) {
494
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
495
+ console.log(` ${chalk.cyan('•')} ${model}${current}`);
496
+ }
497
+ console.log();
498
+ }
499
+ // Display Grok 2
500
+ if (grok2.length > 0) {
501
+ console.log(chalk.bold.gray(' 📷 Grok 2 (Vision/Image)'));
502
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
503
+ for (const model of grok2) {
504
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
505
+ console.log(` ${chalk.cyan('•')} ${model}${current}`);
506
+ }
507
+ console.log();
508
+ }
509
+ // Display Specialized
510
+ if (specialized.length > 0) {
511
+ console.log(chalk.bold.yellow(' 🔧 Specialized'));
512
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
513
+ for (const model of specialized) {
514
+ const current = model === this.client.model ? chalk.green(' ← current') : '';
515
+ console.log(` ${chalk.cyan('•')} ${model}${current}`);
516
+ }
517
+ console.log();
518
+ }
519
+ console.log(chalk.gray(` ${availableModels.length} models available • Use /model <name> to switch`));
520
+ console.log(chalk.gray(' 🧠 = Reasoning (best for complex tasks) • ⚡ = Fast (quick responses)'));
521
+ console.log();
522
+ return;
523
+ }
524
+ // Allow partial matching with normalization
525
+ let matchedModel = modelName;
526
+ if (!availableModels.includes(modelName)) {
527
+ // Normalize input:
528
+ // "grok41" → "grok-4-1", "grok4" → "grok-4", "grok 3" → "grok-3"
529
+ // "4.1" → "4-1", "grok 4 1" → "grok-4-1"
530
+ let normalized = modelName.toLowerCase()
531
+ .replace(/grok\s*(\d)(\d)?(\d)?/g, (_, d1, d2, d3) => {
532
+ if (d3)
533
+ return `grok-${d1}-${d2}-${d3}`;
534
+ if (d2)
535
+ return `grok-${d1}-${d2}`;
536
+ return `grok-${d1}`;
537
+ })
538
+ .replace(/(\d+)\.(\d+)/g, '$1-$2') // "4.1" → "4-1"
539
+ .replace(/\s+/g, '-'); // spaces to hyphens
540
+ // Try to find a match
541
+ let partialMatch = availableModels.find(m => m.toLowerCase().includes(normalized));
542
+ // If no match, try the original input
543
+ if (!partialMatch) {
544
+ partialMatch = availableModels.find(m => m.toLowerCase().includes(modelName.toLowerCase()));
545
+ }
546
+ if (partialMatch) {
547
+ matchedModel = partialMatch;
548
+ console.log(chalk.gray(`Matched: ${matchedModel}`));
549
+ }
550
+ else {
551
+ console.log(chalk.red(`Unknown model: ${modelName}\n`));
552
+ console.log(chalk.gray('Use /model to see available models.\n'));
553
+ return;
554
+ }
555
+ }
556
+ this.client = new GrokClient(this.apiKey, matchedModel);
557
+ console.log(chalk.green(`✓ Switched to model: ${matchedModel}\n`));
558
+ }
559
+ handlePermissions() {
560
+ console.log(chalk.cyan('\n🔐 Permission Settings\n'));
561
+ console.log(' Tool Risk Levels:');
562
+ console.log(` ${chalk.green('📖 Read')} - Read, Glob, Grep, WebFetch`);
563
+ console.log(` ${chalk.yellow('✏️ Write')} - Write, Edit`);
564
+ console.log(` ${chalk.red('⚡ Execute')} - Bash`);
565
+ console.log();
566
+ console.log(' Permission Responses:');
567
+ console.log(' [y] Allow once');
568
+ console.log(' [a] Allow for session');
569
+ console.log(' [n] Deny');
570
+ console.log(' [!] Block for session');
571
+ console.log();
572
+ console.log(chalk.gray(' Auto-approved tools: ' + (this.config.get('autoApprove').join(', ') || 'none')));
573
+ console.log(chalk.gray(' Edit config to add auto-approve rules.\n'));
574
+ }
575
+ showStatus() {
576
+ const uptime = Math.floor((Date.now() - this.sessionStartTime.getTime()) / 1000);
577
+ const minutes = Math.floor(uptime / 60);
578
+ const seconds = uptime % 60;
579
+ console.log(chalk.cyan('\n📊 Status\n'));
580
+ console.log(` Version: ${VERSION}`);
581
+ console.log(` Model: ${this.client.model}`);
582
+ console.log(` Session: ${this.session?.title || 'Untitled'}`);
583
+ console.log(` Session ID: ${this.session?.id.slice(0, 8) || 'N/A'}`);
584
+ console.log(` Messages: ${this.messages.length}`);
585
+ console.log(` Uptime: ${minutes}m ${seconds}s`);
586
+ console.log(` Streaming: ${this.useStreaming ? 'on' : 'off'}`);
587
+ console.log(` Working Dir: ${process.cwd()}`);
588
+ console.log(` Platform: ${process.platform} ${process.arch}`);
589
+ console.log(` Node: ${process.version}`);
590
+ console.log();
591
+ }
592
+ showContext() {
593
+ const messageCount = this.messages.length;
594
+ const userMessages = this.messages.filter(m => m.role === 'user').length;
595
+ const assistantMessages = this.messages.filter(m => m.role === 'assistant').length;
596
+ const toolMessages = this.messages.filter(m => m.role === 'tool').length;
597
+ // Estimate tokens (rough: 4 chars = 1 token)
598
+ const totalChars = this.messages.reduce((acc, m) => acc + (m.content?.length || 0), 0);
599
+ const estimatedTokens = Math.ceil(totalChars / 4);
600
+ const maxTokens = 128000; // Approximate context window
601
+ const usagePercent = Math.min(100, Math.round((estimatedTokens / maxTokens) * 100));
602
+ console.log(chalk.cyan('\n📈 Context Usage\n'));
603
+ // Visual bar
604
+ const barWidth = 40;
605
+ const filledWidth = Math.round((usagePercent / 100) * barWidth);
606
+ const emptyWidth = barWidth - filledWidth;
607
+ const bar = chalk.green('█'.repeat(filledWidth)) + chalk.gray('░'.repeat(emptyWidth));
608
+ console.log(` [${bar}] ${usagePercent}%`);
609
+ console.log();
610
+ console.log(` Estimated tokens: ~${estimatedTokens.toLocaleString()} / ${maxTokens.toLocaleString()}`);
611
+ console.log(` Total messages: ${messageCount}`);
612
+ console.log(` User: ${userMessages}`);
613
+ console.log(` Assistant: ${assistantMessages}`);
614
+ console.log(` Tool results: ${toolMessages}`);
615
+ console.log();
616
+ if (usagePercent > 80) {
617
+ console.log(chalk.yellow(' ⚠️ Context is getting full. Consider using /compact.\n'));
618
+ }
619
+ }
620
+ showCost() {
621
+ // Rough cost estimation based on token usage
622
+ const inputCostPer1M = 3.00; // $3 per 1M input tokens (estimated)
623
+ const outputCostPer1M = 15.00; // $15 per 1M output tokens (estimated)
624
+ const inputCost = (this.tokenUsage.promptTokens / 1000000) * inputCostPer1M;
625
+ const outputCost = (this.tokenUsage.completionTokens / 1000000) * outputCostPer1M;
626
+ const totalCost = inputCost + outputCost;
627
+ console.log(chalk.cyan('\n💰 Token Usage & Cost (Estimated)\n'));
628
+ console.log(` Input tokens: ${this.tokenUsage.promptTokens.toLocaleString()}`);
629
+ console.log(` Output tokens: ${this.tokenUsage.completionTokens.toLocaleString()}`);
630
+ console.log(` Total tokens: ${this.tokenUsage.totalTokens.toLocaleString()}`);
631
+ console.log();
632
+ console.log(` Estimated cost: $${totalCost.toFixed(4)}`);
633
+ console.log(chalk.gray('\n Note: Actual costs may vary. Check xAI pricing.\n'));
634
+ }
635
+ showUsage() {
636
+ console.log(chalk.cyan('\n📊 Usage Statistics\n'));
637
+ console.log(` Session tokens: ${this.tokenUsage.totalTokens.toLocaleString()}`);
638
+ console.log(` Messages sent: ${this.messages.filter(m => m.role === 'user').length}`);
639
+ console.log(` Tool calls: ${this.messages.filter(m => m.role === 'tool').length}`);
640
+ console.log();
641
+ console.log(chalk.gray(' For billing info, visit: https://console.x.ai/\n'));
642
+ }
643
+ async runDoctor() {
644
+ console.log(chalk.cyan('\n🩺 Running diagnostics...\n'));
645
+ const checks = [];
646
+ // Check API key
647
+ const apiKey = await this.config.getApiKey();
648
+ if (apiKey) {
649
+ checks.push({ name: 'API Key', status: 'ok', message: 'API key is configured' });
650
+ }
651
+ else {
652
+ checks.push({ name: 'API Key', status: 'fail', message: 'No API key found. Run `grok auth`' });
653
+ }
654
+ // Check Node version
655
+ const nodeVersion = process.version;
656
+ const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
657
+ if (majorVersion >= 18) {
658
+ checks.push({ name: 'Node.js', status: 'ok', message: `${nodeVersion} (>=18 required)` });
659
+ }
660
+ else {
661
+ checks.push({ name: 'Node.js', status: 'fail', message: `${nodeVersion} - upgrade to >=18` });
662
+ }
663
+ // Check working directory
664
+ try {
665
+ await fs.access(process.cwd(), fs.constants.R_OK | fs.constants.W_OK);
666
+ checks.push({ name: 'Working Dir', status: 'ok', message: 'Read/write access confirmed' });
667
+ }
668
+ catch {
669
+ checks.push({ name: 'Working Dir', status: 'warn', message: 'Limited access to working directory' });
670
+ }
671
+ // Check config directory
672
+ const configDir = path.join(os.homedir(), '.config', 'grokcodecli');
673
+ try {
674
+ await fs.access(configDir);
675
+ checks.push({ name: 'Config Dir', status: 'ok', message: configDir });
676
+ }
677
+ catch {
678
+ checks.push({ name: 'Config Dir', status: 'warn', message: 'Will be created on first use' });
679
+ }
680
+ // Check git
681
+ try {
682
+ const { execSync } = await import('child_process');
683
+ execSync('git --version', { stdio: 'pipe' });
684
+ checks.push({ name: 'Git', status: 'ok', message: 'Git is available' });
685
+ }
686
+ catch {
687
+ checks.push({ name: 'Git', status: 'warn', message: 'Git not found (optional)' });
688
+ }
689
+ // Test API connection
690
+ if (apiKey) {
691
+ try {
692
+ const response = await fetch('https://api.x.ai/v1/models', {
693
+ headers: { 'Authorization': `Bearer ${apiKey}` },
694
+ });
695
+ if (response.ok) {
696
+ checks.push({ name: 'API Connection', status: 'ok', message: 'Connected to xAI API' });
697
+ }
698
+ else {
699
+ checks.push({ name: 'API Connection', status: 'fail', message: `API error: ${response.status}` });
700
+ }
701
+ }
702
+ catch (error) {
703
+ checks.push({ name: 'API Connection', status: 'fail', message: 'Cannot reach xAI API' });
704
+ }
705
+ }
706
+ // Display results
707
+ for (const check of checks) {
708
+ const icon = check.status === 'ok' ? chalk.green('✓') :
709
+ check.status === 'warn' ? chalk.yellow('⚠') : chalk.red('✗');
710
+ const name = check.status === 'ok' ? chalk.green(check.name) :
711
+ check.status === 'warn' ? chalk.yellow(check.name) : chalk.red(check.name);
712
+ console.log(` ${icon} ${name.padEnd(20)} ${chalk.gray(check.message)}`);
713
+ }
714
+ const failures = checks.filter(c => c.status === 'fail').length;
715
+ const warnings = checks.filter(c => c.status === 'warn').length;
716
+ console.log();
717
+ if (failures > 0) {
718
+ console.log(chalk.red(` ${failures} issue(s) found. Please fix before using.\n`));
719
+ }
720
+ else if (warnings > 0) {
721
+ console.log(chalk.yellow(` ${warnings} warning(s). Grok Code should work fine.\n`));
722
+ }
723
+ else {
724
+ console.log(chalk.green(' All checks passed! Grok Code is ready.\n'));
725
+ }
726
+ }
727
+ handleAddDir(dirPath) {
728
+ if (!dirPath) {
729
+ console.log(chalk.yellow('Usage: /add-dir <path>\n'));
730
+ return;
731
+ }
732
+ const resolved = path.resolve(dirPath);
733
+ if (this.workingDirs.includes(resolved)) {
734
+ console.log(chalk.yellow(`Directory already added: ${resolved}\n`));
735
+ return;
736
+ }
737
+ this.workingDirs.push(resolved);
738
+ console.log(chalk.green(`Added working directory: ${resolved}\n`));
739
+ }
740
+ async showHistory() {
741
+ const sessions = await this.history.listSessions(10);
742
+ if (sessions.length === 0) {
743
+ console.log(chalk.gray('No saved conversations.\n'));
744
+ return;
745
+ }
746
+ console.log(chalk.cyan('\n📚 Recent Conversations\n'));
747
+ for (const session of sessions) {
748
+ const date = new Date(session.updatedAt).toLocaleDateString();
749
+ const time = new Date(session.updatedAt).toLocaleTimeString();
750
+ const isCurrent = session.id === this.session?.id;
751
+ const marker = isCurrent ? chalk.green(' ← current') : '';
752
+ console.log(` ${chalk.gray(session.id.slice(0, 8))} ${session.title}${marker}`);
753
+ console.log(` ${chalk.gray(`${date} ${time} • ${session.messages.length} messages`)}`);
754
+ }
755
+ console.log(chalk.gray('\nUse /resume <id> to switch sessions.\n'));
756
+ }
757
+ async handleInit() {
758
+ const grokMdPath = path.join(process.cwd(), 'GROK.md');
759
+ try {
760
+ await fs.access(grokMdPath);
761
+ console.log(chalk.yellow('GROK.md already exists in this project.\n'));
762
+ console.log(chalk.gray('Edit it directly or delete it to re-initialize.\n'));
763
+ return;
764
+ }
765
+ catch {
766
+ // File doesn't exist, create it
767
+ }
768
+ const template = `# Project Guide for Grok
769
+
770
+ ## Project Overview
771
+ <!-- Describe what this project does -->
772
+
773
+ ## Tech Stack
774
+ <!-- List the main technologies used -->
775
+
776
+ ## Project Structure
777
+ <!-- Describe the key directories and files -->
778
+
779
+ ## Development Guidelines
780
+ <!-- Any coding standards or practices to follow -->
781
+
782
+ ## Common Commands
783
+ \`\`\`bash
784
+ # Build the project
785
+ npm run build
786
+
787
+ # Run tests
788
+ npm test
789
+
790
+ # Start development server
791
+ npm run dev
792
+ \`\`\`
793
+
794
+ ## Notes for Grok
795
+ <!-- Any specific instructions for the AI assistant -->
796
+ - Always read files before editing
797
+ - Run tests after making changes
798
+ - Follow existing code patterns
799
+ `;
800
+ await fs.writeFile(grokMdPath, template, 'utf-8');
801
+ console.log(chalk.green('✓ Created GROK.md\n'));
802
+ console.log(chalk.gray('Edit this file to help Grok understand your project better.\n'));
803
+ console.log(chalk.cyan('Contents will be automatically included in conversations.\n'));
804
+ }
805
+ async handleReview(focus) {
806
+ console.log(chalk.cyan('\n🔍 Starting Code Review\n'));
807
+ const reviewPrompt = focus
808
+ ? `Please review the code changes in this project, focusing on: ${focus}
809
+
810
+ Check for:
811
+ 1. Code quality and best practices
812
+ 2. Potential bugs or issues
813
+ 3. Security vulnerabilities
814
+ 4. Performance concerns
815
+ 5. Test coverage gaps
816
+
817
+ Provide specific, actionable feedback.`
818
+ : `Please review the recent code changes in this project.
819
+
820
+ Check for:
821
+ 1. Code quality and best practices
822
+ 2. Potential bugs or issues
823
+ 3. Security vulnerabilities
824
+ 4. Performance concerns
825
+ 5. Test coverage gaps
826
+
827
+ Start by checking git status and recent changes, then provide specific, actionable feedback.`;
828
+ // Send as a message to Grok
829
+ await this.processMessage(reviewPrompt);
830
+ }
831
+ showTerminalSetup() {
832
+ console.log(chalk.cyan('\n⌨️ Terminal Setup\n'));
833
+ console.log(chalk.bold('Recommended Key Bindings:\n'));
834
+ console.log(' ' + chalk.green('Shift+Enter') + ' - Insert newline without sending');
835
+ console.log(' ' + chalk.green('Ctrl+C') + ' - Cancel current operation');
836
+ console.log(' ' + chalk.green('Ctrl+D') + ' - Exit (same as typing "exit")');
837
+ console.log(' ' + chalk.green('Up/Down') + ' - Navigate command history');
838
+ console.log();
839
+ console.log(chalk.bold('For Bash/Zsh (add to ~/.bashrc or ~/.zshrc):\n'));
840
+ console.log(chalk.gray(' # Grok Code CLI alias'));
841
+ console.log(chalk.cyan(' alias g="grok"'));
842
+ console.log(chalk.cyan(' alias gr="grok --resume"'));
843
+ console.log();
844
+ console.log(chalk.bold('For Fish (add to ~/.config/fish/config.fish):\n'));
845
+ console.log(chalk.gray(' # Grok Code CLI alias'));
846
+ console.log(chalk.cyan(' alias g "grok"'));
847
+ console.log(chalk.cyan(' alias gr "grok --resume"'));
848
+ console.log();
849
+ console.log(chalk.bold('VS Code Integration:\n'));
850
+ console.log(' Add to settings.json:');
851
+ console.log(chalk.gray(' "terminal.integrated.env.linux": {'));
852
+ console.log(chalk.gray(' "XAI_API_KEY": "your-api-key"'));
853
+ console.log(chalk.gray(' }'));
854
+ console.log();
855
+ console.log(chalk.gray('Tip: Run `grok auth` to save your API key permanently.\n'));
856
+ }
857
+ showHelp() {
858
+ console.log();
859
+ console.log(chalk.cyan('╭──────────────────────────────────────────────────────────────────────╮'));
860
+ console.log(chalk.cyan('│') + chalk.bold.cyan(' 📚 Grok Code CLI - Command Reference ') + chalk.cyan('│'));
861
+ console.log(chalk.cyan('╰──────────────────────────────────────────────────────────────────────╯'));
862
+ console.log();
863
+ console.log(chalk.bold.cyan(' Session Management'));
864
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
865
+ console.log(` ${chalk.cyan('/clear')} Clear conversation and start fresh`);
866
+ console.log(` ${chalk.cyan('/save')}, ${chalk.cyan('/s')} Save current conversation`);
867
+ console.log(` ${chalk.cyan('/history')} Show saved conversations`);
868
+ console.log(` ${chalk.cyan('/resume')} ${chalk.gray('[id]')} Resume a previous conversation`);
869
+ console.log(` ${chalk.cyan('/rename')} ${chalk.gray('<name>')} Rename current session`);
870
+ console.log(` ${chalk.cyan('/export')} ${chalk.gray('[file]')} Export conversation to file`);
871
+ console.log(` ${chalk.cyan('/compact')} ${chalk.gray('[focus]')} Reduce context size`);
872
+ console.log(` ${chalk.cyan('/exit')}, ${chalk.cyan('/q')} Save and quit`);
873
+ console.log();
874
+ console.log(chalk.bold.cyan(' Configuration'));
875
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
876
+ console.log(` ${chalk.cyan('/config')} Show current configuration`);
877
+ console.log(` ${chalk.cyan('/model')} ${chalk.gray('[name]')} Show or change the AI model`);
878
+ console.log(` ${chalk.cyan('/stream')} Toggle streaming mode`);
879
+ console.log(` ${chalk.cyan('/permissions')} View permission settings`);
880
+ console.log();
881
+ console.log(chalk.bold.cyan(' Status & Diagnostics'));
882
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
883
+ console.log(` ${chalk.cyan('/status')} Show session status and info`);
884
+ console.log(` ${chalk.cyan('/context')} Visualize context usage`);
885
+ console.log(` ${chalk.cyan('/cost')} Show token usage and estimated cost`);
886
+ console.log(` ${chalk.cyan('/doctor')} Run diagnostics check`);
887
+ console.log(` ${chalk.cyan('/version')} Show version`);
888
+ console.log();
889
+ console.log(chalk.bold.cyan(' Project Setup'));
890
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
891
+ console.log(` ${chalk.cyan('/init')} Initialize project with GROK.md`);
892
+ console.log(` ${chalk.cyan('/review')} ${chalk.gray('[focus]')} Request AI code review`);
893
+ console.log(` ${chalk.cyan('/terminal-setup')} Show terminal tips`);
894
+ console.log();
895
+ console.log(chalk.bold.cyan(' Available Tools'));
896
+ console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
897
+ console.log(` ${chalk.green('📖 Read')} Read file contents with line numbers`);
898
+ console.log(` ${chalk.yellow('✏️ Write')} Create or overwrite files`);
899
+ console.log(` ${chalk.yellow('🔧 Edit')} Edit files by string replacement`);
900
+ console.log(` ${chalk.red('⚡ Bash')} Execute shell commands`);
901
+ console.log(` ${chalk.green('🔍 Glob')} Find files by pattern`);
902
+ console.log(` ${chalk.green('🔎 Grep')} Search file contents with regex`);
903
+ console.log(` ${chalk.green('🌐 WebFetch')} Fetch and parse web content`);
904
+ console.log(` ${chalk.green('🔍 WebSearch')} Search the web for information`);
905
+ console.log();
906
+ console.log(chalk.bold('Permission Responses:'));
907
+ console.log(' [y] Allow once [a] Allow for session');
908
+ console.log(' [n] Deny [!] Block for session');
909
+ console.log();
910
+ }
911
+ // === Core Processing ===
912
+ async processMessage(input) {
913
+ this.messages.push({
914
+ role: 'user',
915
+ content: input,
916
+ });
917
+ try {
918
+ if (this.useStreaming) {
919
+ await this.getStreamingResponse();
920
+ }
921
+ else {
922
+ await this.getResponse();
923
+ }
924
+ await this.saveSession();
925
+ }
926
+ catch (error) {
927
+ const err = error;
928
+ console.log(chalk.red(`\nError: ${err.message}\n`));
929
+ }
930
+ }
931
+ async getResponse() {
932
+ console.log(chalk.blue('\nGrok: ') + chalk.gray('thinking...'));
933
+ const response = await this.client.chat(this.messages, allTools);
934
+ const choice = response.choices[0];
935
+ const message = choice.message;
936
+ // Update token usage
937
+ if (response.usage) {
938
+ this.tokenUsage.promptTokens += response.usage.prompt_tokens;
939
+ this.tokenUsage.completionTokens += response.usage.completion_tokens;
940
+ this.tokenUsage.totalTokens += response.usage.total_tokens;
941
+ }
942
+ this.messages.push(message);
943
+ if (message.tool_calls && message.tool_calls.length > 0) {
944
+ if (message.content) {
945
+ this.printMarkdown(message.content);
946
+ }
947
+ for (const toolCall of message.tool_calls) {
948
+ await this.executeToolCall(toolCall);
949
+ }
950
+ await this.getResponse();
951
+ }
952
+ else {
953
+ if (message.content) {
954
+ process.stdout.write('\x1b[1A\x1b[2K');
955
+ console.log(chalk.blue('Grok: '));
956
+ this.printMarkdown(message.content);
957
+ console.log();
958
+ }
959
+ }
960
+ }
961
+ async getStreamingResponse() {
962
+ // Show thinking indicator
963
+ process.stdout.write(chalk.cyan('\n╭─ ') + chalk.bold.cyan('Grok') + chalk.cyan(' ─────────────────────────────────────────────────────────────╮\n'));
964
+ process.stdout.write(chalk.cyan('│ ') + chalk.gray('⠋ thinking...'));
965
+ let fullContent = '';
966
+ let toolCalls = [];
967
+ let currentToolCall = null;
968
+ let firstChunk = true;
969
+ try {
970
+ for await (const chunk of this.client.chatStream(this.messages, allTools)) {
971
+ const delta = chunk.choices[0]?.delta;
972
+ if (delta?.content) {
973
+ if (firstChunk) {
974
+ // Clear thinking indicator and start content
975
+ process.stdout.write('\r' + chalk.cyan('│ ') + ' '.repeat(50) + '\r' + chalk.cyan('│ '));
976
+ firstChunk = false;
977
+ }
978
+ process.stdout.write(delta.content);
979
+ fullContent += delta.content;
980
+ }
981
+ // Handle streaming tool calls
982
+ if (delta?.tool_calls) {
983
+ for (const tc of delta.tool_calls) {
984
+ if (tc.id) {
985
+ // New tool call
986
+ if (currentToolCall && currentToolCall.id) {
987
+ toolCalls.push(currentToolCall);
988
+ }
989
+ currentToolCall = {
990
+ id: tc.id,
991
+ type: 'function',
992
+ function: {
993
+ name: tc.function?.name || '',
994
+ arguments: tc.function?.arguments || '',
995
+ },
996
+ };
997
+ }
998
+ else if (currentToolCall && tc.function?.arguments) {
999
+ // Append to current tool call arguments
1000
+ currentToolCall.function.arguments += tc.function.arguments;
1001
+ }
1002
+ }
1003
+ }
1004
+ }
1005
+ // Push last tool call if exists
1006
+ if (currentToolCall && currentToolCall.id) {
1007
+ toolCalls.push(currentToolCall);
1008
+ }
1009
+ // Close the response box
1010
+ if (fullContent) {
1011
+ console.log();
1012
+ console.log(chalk.cyan('╰──────────────────────────────────────────────────────────────────────╯'));
1013
+ }
1014
+ else if (toolCalls.length > 0) {
1015
+ process.stdout.write('\r' + chalk.cyan('│ ') + chalk.gray('Using tools...') + ' '.repeat(40) + '\n');
1016
+ console.log(chalk.cyan('╰──────────────────────────────────────────────────────────────────────╯'));
1017
+ }
1018
+ // Build the message for history
1019
+ const message = {
1020
+ role: 'assistant',
1021
+ content: fullContent,
1022
+ };
1023
+ if (toolCalls.length > 0) {
1024
+ message.tool_calls = toolCalls;
1025
+ }
1026
+ this.messages.push(message);
1027
+ // Execute tool calls
1028
+ if (toolCalls.length > 0) {
1029
+ for (const toolCall of toolCalls) {
1030
+ await this.executeToolCall(toolCall);
1031
+ }
1032
+ await this.getStreamingResponse();
1033
+ }
1034
+ }
1035
+ catch (error) {
1036
+ console.log();
1037
+ throw error;
1038
+ }
1039
+ }
1040
+ async executeToolCall(toolCall) {
1041
+ const { name, arguments: argsJson } = toolCall.function;
1042
+ let params;
1043
+ try {
1044
+ params = JSON.parse(argsJson);
1045
+ }
1046
+ catch {
1047
+ console.log(chalk.red(`\n⚠️ Invalid arguments for ${name}`));
1048
+ this.messages.push({
1049
+ role: 'tool',
1050
+ tool_call_id: toolCall.id,
1051
+ content: 'Error: Invalid JSON arguments',
1052
+ });
1053
+ return;
1054
+ }
1055
+ // Request permission
1056
+ const riskLevel = this.permissions.getToolRiskLevel(name);
1057
+ const description = this.permissions.formatToolDetails(name, params);
1058
+ const approved = await this.permissions.requestPermission({
1059
+ tool: name,
1060
+ description,
1061
+ riskLevel,
1062
+ details: params,
1063
+ });
1064
+ if (!approved) {
1065
+ this.messages.push({
1066
+ role: 'tool',
1067
+ tool_call_id: toolCall.id,
1068
+ content: 'Error: Permission denied by user',
1069
+ });
1070
+ return;
1071
+ }
1072
+ // Show execution with beautiful box
1073
+ const toolIcons = {
1074
+ Read: '📖', Write: '✏️', Edit: '🔧', Bash: '⚡',
1075
+ Glob: '🔍', Grep: '🔎', WebFetch: '🌐', WebSearch: '🔍'
1076
+ };
1077
+ const toolColors = {
1078
+ Read: chalk.green, Write: chalk.yellow, Edit: chalk.yellow, Bash: chalk.red,
1079
+ Glob: chalk.green, Grep: chalk.green, WebFetch: chalk.green, WebSearch: chalk.green
1080
+ };
1081
+ const icon = toolIcons[name] || '🔧';
1082
+ const color = toolColors[name] || chalk.gray;
1083
+ console.log();
1084
+ console.log(color('┌─ ') + chalk.bold(`${icon} ${name}`) + color(' ─────────────────────────────────────────────────'));
1085
+ // Show details based on tool type
1086
+ if (name === 'Bash') {
1087
+ console.log(color('│ ') + chalk.gray('$') + ' ' + chalk.white(params.command));
1088
+ }
1089
+ else if (name === 'Read' || name === 'Write' || name === 'Edit') {
1090
+ console.log(color('│ ') + chalk.gray('File:') + ' ' + chalk.cyan(params.file_path));
1091
+ }
1092
+ else if (name === 'Glob' || name === 'Grep') {
1093
+ console.log(color('│ ') + chalk.gray('Pattern:') + ' ' + chalk.cyan(params.pattern));
1094
+ }
1095
+ else if (name === 'WebFetch') {
1096
+ console.log(color('│ ') + chalk.gray('URL:') + ' ' + chalk.cyan(params.url));
1097
+ }
1098
+ else if (name === 'WebSearch') {
1099
+ console.log(color('│ ') + chalk.gray('Query:') + ' ' + chalk.cyan(params.query));
1100
+ }
1101
+ // Execute
1102
+ const result = await executeTool(name, params);
1103
+ if (result.success) {
1104
+ console.log(color('│'));
1105
+ console.log(color('│ ') + chalk.green('✓ Success'));
1106
+ if (result.output && result.output.length < 500) {
1107
+ const lines = result.output.split('\n').slice(0, 10);
1108
+ for (const line of lines) {
1109
+ console.log(color('│ ') + chalk.gray(line.slice(0, 80)));
1110
+ }
1111
+ if (result.output.split('\n').length > 10) {
1112
+ console.log(color('│ ') + chalk.gray('... (truncated)'));
1113
+ }
1114
+ }
1115
+ else if (result.output) {
1116
+ console.log(color('│ ') + chalk.gray(result.output.slice(0, 200) + '... (truncated)'));
1117
+ }
1118
+ }
1119
+ else {
1120
+ console.log(color('│'));
1121
+ console.log(color('│ ') + chalk.red('✗ Failed: ') + chalk.red(result.error || 'Unknown error'));
1122
+ }
1123
+ console.log(color('└──────────────────────────────────────────────────────────────────────'));
1124
+ this.messages.push({
1125
+ role: 'tool',
1126
+ tool_call_id: toolCall.id,
1127
+ content: result.success ? result.output : `Error: ${result.error}`,
1128
+ });
1129
+ }
1130
+ async saveSession() {
1131
+ if (this.session) {
1132
+ this.session.messages = this.messages;
1133
+ await this.history.saveSession(this.session);
1134
+ }
1135
+ }
1136
+ printMarkdown(content) {
1137
+ const formatted = content
1138
+ .replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
1139
+ return chalk.gray(`─── ${lang || 'code'} ───\n`) + chalk.cyan(code.trim()) + chalk.gray('\n───────────');
1140
+ })
1141
+ .replace(/`([^`]+)`/g, chalk.cyan('$1'));
1142
+ console.log(formatted);
1143
+ }
1144
+ }
1145
+ //# sourceMappingURL=chat.js.map