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,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
+ }