diff --git a/README.md b/README.md index 54214f9..fcdc4f3 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ Add to `~/.grok/user-settings.json`: ## Usage +### Interactive Mode + Start the conversational AI assistant: ```bash grok @@ -98,6 +100,21 @@ Or specify a working directory: grok -d /path/to/project ``` +### Headless Mode + +Process a single prompt and exit (useful for scripting and automation): +```bash +grok --prompt "show me the package.json file" +grok -p "create a new file called example.js with a hello world function" +grok --prompt "run npm test and show me the results" --directory /path/to/project +``` + +This mode is particularly useful for: +- **CI/CD pipelines**: Automate code analysis and file operations +- **Scripting**: Integrate AI assistance into shell scripts +- **Terminal benchmarks**: Perfect for tools like Terminal Bench that need non-interactive execution +- **Batch processing**: Process multiple prompts programmatically + ### Model Selection You can specify which AI model to use with the `--model` parameter: @@ -113,6 +130,21 @@ grok --model gemini-2.5-pro --base-url https://api-endpoint.com/v1 grok --model claude-sonnet-4-20250514 --base-url https://api-endpoint.com/v1 ``` +### Command Line Options + +```bash +grok [options] + +Options: + -V, --version output the version number + -d, --directory set working directory + -k, --api-key Grok API key (or set GROK_API_KEY env var) + -u, --base-url Grok API base URL (or set GROK_BASE_URL env var) + -m, --model AI model to use (e.g., grok-4-latest, grok-3-latest) + -p, --prompt process a single prompt and exit (headless mode) + -h, --help display help for command +``` + ### Custom Instructions You can provide custom instructions to tailor Grok's behavior to your project by creating a `.grok/GROK.md` file in your project directory: diff --git a/package.json b/package.json index 1088370..018b867 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vibe-kit/grok-cli", - "version": "0.0.4", + "version": "0.0.5", "description": "An open-source AI agent that brings the power of Grok directly into your terminal.", "main": "dist/index.js", "bin": { diff --git a/src/index.ts b/src/index.ts index 2dc821e..7d00b14 100755 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import ChatInterface from "./ui/components/chat-interface"; import * as fs from "fs"; import * as path from "path"; import * as os from "os"; +import { ConfirmationService } from "./utils/confirmation-service"; // Load environment variables dotenv.config(); @@ -17,22 +18,22 @@ dotenv.config(); function loadApiKey(): string | undefined { // First check environment variables let apiKey = process.env.GROK_API_KEY; - + if (!apiKey) { // Try to load from user settings file try { const homeDir = os.homedir(); - const settingsFile = path.join(homeDir, '.grok', 'user-settings.json'); - + const settingsFile = path.join(homeDir, ".grok", "user-settings.json"); + if (fs.existsSync(settingsFile)) { - const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); + const settings = JSON.parse(fs.readFileSync(settingsFile, "utf8")); apiKey = settings.apiKey; } } catch (error) { // Ignore errors, apiKey will remain undefined } } - + return apiKey; } @@ -40,25 +41,92 @@ function loadApiKey(): string | undefined { function loadBaseURL(): string | undefined { // First check environment variables let baseURL = process.env.GROK_BASE_URL; - + if (!baseURL) { // Try to load from user settings file try { const homeDir = os.homedir(); - const settingsFile = path.join(homeDir, '.grok', 'user-settings.json'); - + const settingsFile = path.join(homeDir, ".grok", "user-settings.json"); + if (fs.existsSync(settingsFile)) { - const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); + const settings = JSON.parse(fs.readFileSync(settingsFile, "utf8")); baseURL = settings.baseURL; } } catch (error) { // Ignore errors, baseURL will remain undefined } } - + return baseURL; } +// Headless mode processing function +async function processPromptHeadless( + prompt: string, + apiKey: string, + baseURL?: string, + model?: string +): Promise { + try { + const agent = new GrokAgent(apiKey, baseURL, model); + + // Configure confirmation service for headless mode (auto-approve all operations) + const confirmationService = ConfirmationService.getInstance(); + confirmationService.setSessionFlag("allOperations", true); + + console.log("🤖 Processing prompt...\n"); + + // Process the user message + const chatEntries = await agent.processUserMessage(prompt); + + // Output the results + for (const entry of chatEntries) { + switch (entry.type) { + case "user": + console.log(`> ${entry.content}\n`); + break; + + case "assistant": + if (entry.content.trim()) { + console.log(entry.content.trim()); + console.log(); + } + break; + + case "tool_result": + const toolName = entry.toolCall?.function?.name || "unknown"; + const getFilePath = (toolCall: any) => { + if (toolCall?.function?.arguments) { + try { + const args = JSON.parse(toolCall.function.arguments); + if (toolCall.function.name === "search") { + return args.query; + } + return args.path || args.file_path || args.command || ""; + } catch { + return ""; + } + } + return ""; + }; + + const filePath = getFilePath(entry.toolCall); + const toolDisplay = filePath ? `${toolName}(${filePath})` : toolName; + + if (entry.toolResult?.success) { + console.log(`✅ ${toolDisplay}: ${entry.content.split("\n")[0]}`); + } else { + console.log(`❌ ${toolDisplay}: ${entry.content}`); + } + break; + } + } + } catch (error: any) { + console.error("❌ Error processing prompt:", error.message); + process.exit(1); + } +} + program .name("grok") .description( @@ -67,9 +135,19 @@ program .version("1.0.0") .option("-d, --directory ", "set working directory", process.cwd()) .option("-k, --api-key ", "Grok API key (or set GROK_API_KEY env var)") - .option("-u, --base-url ", "Grok API base URL (or set GROK_BASE_URL env var)") - .option("-m, --model ", "AI model to use (e.g., gemini-2.5-pro, grok-4-latest)") - .action((options) => { + .option( + "-u, --base-url ", + "Grok API base URL (or set GROK_BASE_URL env var)" + ) + .option( + "-m, --model ", + "AI model to use (e.g., gemini-2.5-pro, grok-4-latest)" + ) + .option( + "-p, --prompt ", + "process a single prompt and exit (headless mode)" + ) + .action(async (options) => { if (options.directory) { try { process.chdir(options.directory); @@ -87,10 +165,23 @@ program const apiKey = options.apiKey || loadApiKey(); const baseURL = options.baseUrl || loadBaseURL(); const model = options.model; - const agent = apiKey ? new GrokAgent(apiKey, baseURL, model) : undefined; + if (!apiKey) { + console.error( + "❌ Error: API key required. Set GROK_API_KEY environment variable, use --api-key flag, or save to ~/.grok/user-settings.json" + ); + process.exit(1); + } + + // Headless mode: process prompt and exit + if (options.prompt) { + await processPromptHeadless(options.prompt, apiKey, baseURL, model); + return; + } + + // Interactive mode: launch UI + const agent = new GrokAgent(apiKey, baseURL, model); console.log("🤖 Starting Grok CLI Conversational Assistant...\n"); - render(React.createElement(ChatInterface, { agent })); } catch (error: any) { console.error("❌ Error initializing Grok CLI:", error.message);