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