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,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Commands Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads custom slash commands from:
|
|
5
|
+
* - .grok/commands/ (project-specific)
|
|
6
|
+
* - ~/.grok/commands/ (user-specific)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs/promises';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as os from 'os';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
|
|
14
|
+
export interface CustomCommand {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
content: string;
|
|
18
|
+
source: 'project' | 'user';
|
|
19
|
+
filePath: string;
|
|
20
|
+
argumentHint?: string;
|
|
21
|
+
allowedTools?: string[];
|
|
22
|
+
model?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CommandFrontmatter {
|
|
26
|
+
description?: string;
|
|
27
|
+
'argument-hint'?: string;
|
|
28
|
+
'allowed-tools'?: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse frontmatter from markdown content
|
|
34
|
+
*/
|
|
35
|
+
function parseFrontmatter(content: string): { frontmatter: CommandFrontmatter; body: string } {
|
|
36
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
37
|
+
|
|
38
|
+
if (!match) {
|
|
39
|
+
return { frontmatter: {}, body: content };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const frontmatter: CommandFrontmatter = {};
|
|
43
|
+
const lines = match[1].split('\n');
|
|
44
|
+
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const colonIdx = line.indexOf(':');
|
|
47
|
+
if (colonIdx > 0) {
|
|
48
|
+
const key = line.slice(0, colonIdx).trim();
|
|
49
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
50
|
+
(frontmatter as any)[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { frontmatter, body: match[2] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load commands from a directory
|
|
59
|
+
*/
|
|
60
|
+
async function loadCommandsFromDir(
|
|
61
|
+
dir: string,
|
|
62
|
+
source: 'project' | 'user',
|
|
63
|
+
namespace?: string
|
|
64
|
+
): Promise<CustomCommand[]> {
|
|
65
|
+
const commands: CustomCommand[] = [];
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
69
|
+
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const fullPath = path.join(dir, entry.name);
|
|
72
|
+
|
|
73
|
+
if (entry.isDirectory()) {
|
|
74
|
+
// Recurse into subdirectories with namespace
|
|
75
|
+
const subCommands = await loadCommandsFromDir(
|
|
76
|
+
fullPath,
|
|
77
|
+
source,
|
|
78
|
+
namespace ? `${namespace}:${entry.name}` : entry.name
|
|
79
|
+
);
|
|
80
|
+
commands.push(...subCommands);
|
|
81
|
+
} else if (entry.name.endsWith('.md')) {
|
|
82
|
+
// Load markdown command file
|
|
83
|
+
try {
|
|
84
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
85
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
86
|
+
|
|
87
|
+
const name = entry.name.replace(/\.md$/, '');
|
|
88
|
+
const fullName = namespace ? `${name}` : name;
|
|
89
|
+
|
|
90
|
+
commands.push({
|
|
91
|
+
name: fullName,
|
|
92
|
+
description: frontmatter.description || `Custom command from ${source}`,
|
|
93
|
+
content: body.trim(),
|
|
94
|
+
source,
|
|
95
|
+
filePath: fullPath,
|
|
96
|
+
argumentHint: frontmatter['argument-hint'],
|
|
97
|
+
allowedTools: frontmatter['allowed-tools']?.split(',').map(s => s.trim()),
|
|
98
|
+
model: frontmatter.model,
|
|
99
|
+
});
|
|
100
|
+
} catch (err) {
|
|
101
|
+
// Skip files that can't be read
|
|
102
|
+
console.error(chalk.yellow(`Warning: Could not load command ${fullPath}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Directory doesn't exist, return empty
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return commands;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Load all custom commands
|
|
115
|
+
*/
|
|
116
|
+
export async function loadCustomCommands(): Promise<CustomCommand[]> {
|
|
117
|
+
const commands: CustomCommand[] = [];
|
|
118
|
+
|
|
119
|
+
// Load project commands
|
|
120
|
+
const projectDir = path.join(process.cwd(), '.grok', 'commands');
|
|
121
|
+
const projectCommands = await loadCommandsFromDir(projectDir, 'project');
|
|
122
|
+
commands.push(...projectCommands);
|
|
123
|
+
|
|
124
|
+
// Load user commands
|
|
125
|
+
const userDir = path.join(os.homedir(), '.grok', 'commands');
|
|
126
|
+
const userCommands = await loadCommandsFromDir(userDir, 'user');
|
|
127
|
+
|
|
128
|
+
// User commands only added if not overridden by project
|
|
129
|
+
for (const cmd of userCommands) {
|
|
130
|
+
if (!commands.find(c => c.name === cmd.name)) {
|
|
131
|
+
commands.push(cmd);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return commands;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Process command arguments
|
|
140
|
+
*/
|
|
141
|
+
export function processCommandArgs(content: string, args: string): string {
|
|
142
|
+
let result = content;
|
|
143
|
+
|
|
144
|
+
// Replace $ARGUMENTS with all args
|
|
145
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
146
|
+
|
|
147
|
+
// Replace $1, $2, etc. with individual args
|
|
148
|
+
const argList = args.split(/\s+/).filter(Boolean);
|
|
149
|
+
for (let i = 0; i < argList.length; i++) {
|
|
150
|
+
result = result.replace(new RegExp(`\\$${i + 1}`, 'g'), argList[i]);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Process bash execution (!`command`)
|
|
154
|
+
result = result.replace(/!\`([^`]+)\`/g, (_, cmd) => {
|
|
155
|
+
// Return placeholder - actual execution happens in chat
|
|
156
|
+
return `[Execute: ${cmd}]`;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Process file references (@path)
|
|
160
|
+
result = result.replace(/@([^\s]+)/g, (_, filePath) => {
|
|
161
|
+
return `[Read file: ${filePath}]`;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get command help text
|
|
169
|
+
*/
|
|
170
|
+
export function getCommandHelp(commands: CustomCommand[]): string {
|
|
171
|
+
if (commands.length === 0) {
|
|
172
|
+
return chalk.gray('No custom commands found.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const lines: string[] = [];
|
|
176
|
+
const projectCmds = commands.filter(c => c.source === 'project');
|
|
177
|
+
const userCmds = commands.filter(c => c.source === 'user');
|
|
178
|
+
|
|
179
|
+
if (projectCmds.length > 0) {
|
|
180
|
+
lines.push(chalk.bold('Project Commands:'));
|
|
181
|
+
for (const cmd of projectCmds) {
|
|
182
|
+
const hint = cmd.argumentHint ? chalk.gray(` ${cmd.argumentHint}`) : '';
|
|
183
|
+
lines.push(` /${cmd.name}${hint}`);
|
|
184
|
+
lines.push(` ${chalk.gray(cmd.description)}`);
|
|
185
|
+
}
|
|
186
|
+
lines.push('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (userCmds.length > 0) {
|
|
190
|
+
lines.push(chalk.bold('User Commands:'));
|
|
191
|
+
for (const cmd of userCmds) {
|
|
192
|
+
const hint = cmd.argumentHint ? chalk.gray(` ${cmd.argumentHint}`) : '';
|
|
193
|
+
lines.push(` /${cmd.name}${hint}`);
|
|
194
|
+
lines.push(` ${chalk.gray(cmd.description)}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return lines.join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Initialize custom commands directory structure
|
|
203
|
+
*/
|
|
204
|
+
export async function initCommandsDir(): Promise<void> {
|
|
205
|
+
const projectDir = path.join(process.cwd(), '.grok', 'commands');
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
await fs.mkdir(projectDir, { recursive: true });
|
|
209
|
+
|
|
210
|
+
// Create example command
|
|
211
|
+
const examplePath = path.join(projectDir, 'review.md');
|
|
212
|
+
try {
|
|
213
|
+
await fs.access(examplePath);
|
|
214
|
+
} catch {
|
|
215
|
+
await fs.writeFile(examplePath, `---
|
|
216
|
+
description: Review the current code changes
|
|
217
|
+
argument-hint: [focus-area]
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
Please review the recent code changes in this project.
|
|
221
|
+
|
|
222
|
+
Focus on:
|
|
223
|
+
- Code quality and best practices
|
|
224
|
+
- Potential bugs or issues
|
|
225
|
+
- Security concerns
|
|
226
|
+
- Performance implications
|
|
227
|
+
|
|
228
|
+
$ARGUMENTS
|
|
229
|
+
`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Create .gitignore for commands
|
|
233
|
+
const gitignorePath = path.join(process.cwd(), '.grok', '.gitignore');
|
|
234
|
+
try {
|
|
235
|
+
await fs.access(gitignorePath);
|
|
236
|
+
} catch {
|
|
237
|
+
await fs.writeFile(gitignorePath, `# Ignore local-only files
|
|
238
|
+
*.local.md
|
|
239
|
+
`);
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
// Ignore errors
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as readline from 'readline';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { platform } from 'os';
|
|
6
|
+
|
|
7
|
+
interface GrokConfig {
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
model: string;
|
|
10
|
+
temperature: number;
|
|
11
|
+
maxTokens: number;
|
|
12
|
+
autoApprove: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const defaults: Omit<GrokConfig, 'apiKey'> = {
|
|
16
|
+
model: 'grok-4-0709',
|
|
17
|
+
temperature: 0.7,
|
|
18
|
+
maxTokens: 16384,
|
|
19
|
+
autoApprove: [],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Open URL in default browser
|
|
23
|
+
function openBrowser(url: string): Promise<void> {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const os = platform();
|
|
26
|
+
let cmd: string;
|
|
27
|
+
|
|
28
|
+
switch (os) {
|
|
29
|
+
case 'darwin':
|
|
30
|
+
cmd = `open "${url}"`;
|
|
31
|
+
break;
|
|
32
|
+
case 'win32':
|
|
33
|
+
cmd = `start "" "${url}"`;
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
cmd = `xdg-open "${url}"`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exec(cmd, (error) => {
|
|
40
|
+
if (error) {
|
|
41
|
+
reject(error);
|
|
42
|
+
} else {
|
|
43
|
+
resolve();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class ConfigManager {
|
|
50
|
+
private config: Conf<GrokConfig>;
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
this.config = new Conf<GrokConfig>({
|
|
54
|
+
projectName: 'grokcodecli',
|
|
55
|
+
defaults: defaults as GrokConfig,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getApiKey(): Promise<string | undefined> {
|
|
60
|
+
// Check environment variable first
|
|
61
|
+
const envKey = process.env.XAI_API_KEY || process.env.GROK_API_KEY;
|
|
62
|
+
if (envKey) return envKey;
|
|
63
|
+
|
|
64
|
+
// Fall back to stored config
|
|
65
|
+
return this.config.get('apiKey');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async setApiKey(apiKey: string): Promise<void> {
|
|
69
|
+
this.config.set('apiKey', apiKey);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get(key: keyof GrokConfig): GrokConfig[keyof GrokConfig] {
|
|
73
|
+
return this.config.get(key);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
set<K extends keyof GrokConfig>(key: K, value: GrokConfig[K]): void {
|
|
77
|
+
this.config.set(key, value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async setupAuth(): Promise<void> {
|
|
81
|
+
const rl = readline.createInterface({
|
|
82
|
+
input: process.stdin,
|
|
83
|
+
output: process.stdout,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const question = (prompt: string): Promise<string> => {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
rl.question(prompt, (answer) => {
|
|
89
|
+
resolve(answer);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Beautiful auth header
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk.cyan('╭──────────────────────────────────────────────────────────────────────╮'));
|
|
97
|
+
console.log(chalk.cyan('│') + chalk.bold.cyan(' 🔐 Grok Code CLI - Authentication ') + chalk.cyan('│'));
|
|
98
|
+
console.log(chalk.cyan('╰──────────────────────────────────────────────────────────────────────╯'));
|
|
99
|
+
console.log();
|
|
100
|
+
|
|
101
|
+
console.log(chalk.bold(' Welcome to Grok Code!'));
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(chalk.gray(' To use Grok Code, you need an API key from xAI.'));
|
|
104
|
+
console.log(chalk.gray(' We\'ll open your browser to the xAI console where you can:'));
|
|
105
|
+
console.log();
|
|
106
|
+
console.log(chalk.cyan(' 1.') + ' Sign in or create an account');
|
|
107
|
+
console.log(chalk.cyan(' 2.') + ' Go to API Keys section');
|
|
108
|
+
console.log(chalk.cyan(' 3.') + ' Create a new API key');
|
|
109
|
+
console.log(chalk.cyan(' 4.') + ' Copy the key and paste it here');
|
|
110
|
+
console.log();
|
|
111
|
+
|
|
112
|
+
const xaiUrl = 'https://console.x.ai/';
|
|
113
|
+
|
|
114
|
+
// Ask to open browser
|
|
115
|
+
const openChoice = await question(chalk.bold.green('❯ ') + 'Open xAI Console in browser? [Y/n]: ');
|
|
116
|
+
|
|
117
|
+
if (openChoice.toLowerCase() !== 'n') {
|
|
118
|
+
console.log();
|
|
119
|
+
console.log(chalk.cyan(' ⏳ Opening browser...'));
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await openBrowser(xaiUrl);
|
|
123
|
+
console.log(chalk.green(' ✓ Browser opened!'));
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
|
|
126
|
+
console.log(chalk.gray(' Follow these steps in the browser:'));
|
|
127
|
+
console.log(chalk.gray(' 1. Sign in to your xAI account'));
|
|
128
|
+
console.log(chalk.gray(' 2. Click on "API Keys" in the sidebar'));
|
|
129
|
+
console.log(chalk.gray(' 3. Click "Create API Key"'));
|
|
130
|
+
console.log(chalk.gray(' 4. Copy the key (starts with "xai-")'));
|
|
131
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────────'));
|
|
132
|
+
console.log();
|
|
133
|
+
} catch {
|
|
134
|
+
console.log(chalk.yellow(' ⚠ Could not open browser automatically.'));
|
|
135
|
+
console.log(chalk.gray(` Please visit: ${chalk.cyan(xaiUrl)}`));
|
|
136
|
+
console.log();
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(chalk.gray(` Visit: ${chalk.cyan(xaiUrl)}`));
|
|
141
|
+
console.log();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Get API key with masked input visual
|
|
145
|
+
console.log(chalk.gray(' Paste your API key below (it will be hidden):'));
|
|
146
|
+
console.log();
|
|
147
|
+
const apiKey = await question(chalk.bold.green('❯ ') + 'API Key: ');
|
|
148
|
+
|
|
149
|
+
if (!apiKey.trim()) {
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(chalk.red(' ✗ API key cannot be empty.'));
|
|
152
|
+
console.log(chalk.gray(' Run `grok auth` to try again.\n'));
|
|
153
|
+
rl.close();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Validate the key with spinner
|
|
158
|
+
console.log();
|
|
159
|
+
process.stdout.write(chalk.cyan(' ⠋ Validating API key...'));
|
|
160
|
+
|
|
161
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
162
|
+
let frameIndex = 0;
|
|
163
|
+
const spinner = setInterval(() => {
|
|
164
|
+
process.stdout.write(`\r${chalk.cyan(' ' + frames[frameIndex] + ' Validating API key...')}`);
|
|
165
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
166
|
+
}, 80);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const response = await fetch('https://api.x.ai/v1/models', {
|
|
170
|
+
headers: {
|
|
171
|
+
'Authorization': `Bearer ${apiKey.trim()}`,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
clearInterval(spinner);
|
|
176
|
+
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
console.log(`\r${chalk.red(' ✗ Invalid API key. Please check and try again.')} `);
|
|
179
|
+
console.log();
|
|
180
|
+
console.log(chalk.gray(' Make sure you copied the complete key starting with "xai-"'));
|
|
181
|
+
console.log(chalk.gray(' Run `grok auth` to try again.\n'));
|
|
182
|
+
rl.close();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Get available models to show
|
|
187
|
+
const data = await response.json() as { data: { id: string }[] };
|
|
188
|
+
const modelCount = data.data?.length || 0;
|
|
189
|
+
|
|
190
|
+
await this.setApiKey(apiKey.trim());
|
|
191
|
+
|
|
192
|
+
// Success animation
|
|
193
|
+
console.log(`\r${chalk.green(' ✓ API key validated!')} `);
|
|
194
|
+
console.log();
|
|
195
|
+
console.log(chalk.cyan('╭──────────────────────────────────────────────────────────────────────╮'));
|
|
196
|
+
console.log(chalk.cyan('│') + chalk.bold.green(' 🎉 Authentication Successful! ') + chalk.cyan('│'));
|
|
197
|
+
console.log(chalk.cyan('│') + ` ` + chalk.cyan('│'));
|
|
198
|
+
console.log(chalk.cyan('│') + ` ${chalk.gray('API Key:')} ${chalk.green('✓ Saved securely')} ` + chalk.cyan('│'));
|
|
199
|
+
console.log(chalk.cyan('│') + ` ${chalk.gray('Models:')} ${chalk.cyan(modelCount + ' available')} ` + chalk.cyan('│'));
|
|
200
|
+
console.log(chalk.cyan('│') + ` ${chalk.gray('Config:')} ${chalk.blue(this.config.path.slice(0, 45))}...` + chalk.cyan('│'));
|
|
201
|
+
console.log(chalk.cyan('│') + ` ` + chalk.cyan('│'));
|
|
202
|
+
console.log(chalk.cyan('│') + ` ${chalk.bold('Get started:')} ${chalk.cyan('grok')} ` + chalk.cyan('│'));
|
|
203
|
+
console.log(chalk.cyan('╰──────────────────────────────────────────────────────────────────────╯'));
|
|
204
|
+
console.log();
|
|
205
|
+
} catch (error) {
|
|
206
|
+
clearInterval(spinner);
|
|
207
|
+
console.log(`\r${chalk.red(' ✗ Error validating key: ' + (error as Error).message)} `);
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(chalk.gray(' Check your internet connection and try again.\n'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
rl.close();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async show(): Promise<void> {
|
|
216
|
+
console.log(chalk.cyan('\n📋 Current Configuration\n'));
|
|
217
|
+
|
|
218
|
+
const apiKey = await this.getApiKey();
|
|
219
|
+
console.log(` API Key: ${apiKey ? chalk.green('✓ Set') : chalk.red('✗ Not set')}`);
|
|
220
|
+
console.log(` Model: ${this.get('model')}`);
|
|
221
|
+
console.log(` Temperature: ${this.get('temperature')}`);
|
|
222
|
+
console.log(` Max Tokens: ${this.get('maxTokens')}`);
|
|
223
|
+
console.log(` Auto-approve: ${(this.get('autoApprove') as string[]).join(', ') || 'none'}`);
|
|
224
|
+
console.log(`\n Config file: ${this.config.path}\n`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async reset(): Promise<void> {
|
|
228
|
+
this.config.clear();
|
|
229
|
+
Object.entries(defaults).forEach(([key, value]) => {
|
|
230
|
+
this.config.set(key as keyof GrokConfig, value);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async interactive(): Promise<void> {
|
|
235
|
+
await this.show();
|
|
236
|
+
console.log(chalk.gray('Use `grok config --reset` to reset to defaults.'));
|
|
237
|
+
console.log(chalk.gray('Use `grok auth` to update your API key.\n'));
|
|
238
|
+
}
|
|
239
|
+
}
|