UNPKG

@vibe-kit/grok-cli

Version:

An open-source AI agent that brings the power of Grok directly into your terminal.

360 lines (357 loc) 15.3 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = __importDefault(require("react")); const ink_1 = require("ink"); const commander_1 = require("commander"); const dotenv = __importStar(require("dotenv")); const grok_agent_1 = require("./agent/grok-agent"); const chat_interface_1 = __importDefault(require("./ui/components/chat-interface")); const settings_manager_1 = require("./utils/settings-manager"); const confirmation_service_1 = require("./utils/confirmation-service"); const mcp_1 = require("./commands/mcp"); // Load environment variables dotenv.config(); // Add proper signal handling for terminal cleanup process.on("SIGINT", () => { // Restore terminal to normal mode before exit if (process.stdin.isTTY) { process.stdin.setRawMode(false); } console.log("\nGracefully shutting down..."); process.exit(0); }); process.on("SIGTERM", () => { // Restore terminal to normal mode before exit if (process.stdin.isTTY) { process.stdin.setRawMode(false); } console.log("\nGracefully shutting down..."); process.exit(0); }); // Handle uncaught exceptions to prevent hanging process.on("uncaughtException", (error) => { console.error("Uncaught exception:", error); process.exit(1); }); process.on("unhandledRejection", (reason, promise) => { console.error("Unhandled rejection at:", promise, "reason:", reason); process.exit(1); }); // Ensure user settings are initialized function ensureUserSettingsDirectory() { try { const manager = (0, settings_manager_1.getSettingsManager)(); // This will create default settings if they don't exist manager.loadUserSettings(); } catch (error) { // Silently ignore errors during setup } } // Load API key from user settings if not in environment function loadApiKey() { const manager = (0, settings_manager_1.getSettingsManager)(); return manager.getApiKey(); } // Load base URL from user settings if not in environment function loadBaseURL() { const manager = (0, settings_manager_1.getSettingsManager)(); return manager.getBaseURL(); } // Save command line settings to user settings file async function saveCommandLineSettings(apiKey, baseURL) { try { const manager = (0, settings_manager_1.getSettingsManager)(); // Update with command line values if (apiKey) { manager.updateUserSetting('apiKey', apiKey); console.log("✅ API key saved to ~/.grok/user-settings.json"); } if (baseURL) { manager.updateUserSetting('baseURL', baseURL); console.log("✅ Base URL saved to ~/.grok/user-settings.json"); } } catch (error) { console.warn("⚠️ Could not save settings to file:", error instanceof Error ? error.message : "Unknown error"); } } // Load model from user settings if not in environment function loadModel() { // First check environment variables let model = process.env.GROK_MODEL; if (!model) { // Use the unified model loading from settings manager try { const manager = (0, settings_manager_1.getSettingsManager)(); model = manager.getCurrentModel(); } catch (error) { // Ignore errors, model will remain undefined } } return model; } // Handle commit-and-push command in headless mode async function handleCommitAndPushHeadless(apiKey, baseURL, model) { try { const agent = new grok_agent_1.GrokAgent(apiKey, baseURL, model); // Configure confirmation service for headless mode (auto-approve all operations) const confirmationService = confirmation_service_1.ConfirmationService.getInstance(); confirmationService.setSessionFlag("allOperations", true); console.log("🤖 Processing commit and push...\n"); console.log("> /commit-and-push\n"); // First check if there are any changes at all const initialStatusResult = await agent.executeBashCommand("git status --porcelain"); if (!initialStatusResult.success || !initialStatusResult.output?.trim()) { console.log("❌ No changes to commit. Working directory is clean."); process.exit(1); } console.log("✅ git status: Changes detected"); // Add all changes const addResult = await agent.executeBashCommand("git add ."); if (!addResult.success) { console.log(`❌ git add: ${addResult.error || "Failed to stage changes"}`); process.exit(1); } console.log("✅ git add: Changes staged"); // Get staged changes for commit message generation const diffResult = await agent.executeBashCommand("git diff --cached"); // Generate commit message using AI const commitPrompt = `Generate a concise, professional git commit message for these changes: Git Status: ${initialStatusResult.output} Git Diff (staged changes): ${diffResult.output || "No staged changes shown"} Follow conventional commit format (feat:, fix:, docs:, etc.) and keep it under 72 characters. Respond with ONLY the commit message, no additional text.`; console.log("🤖 Generating commit message..."); const commitMessageEntries = await agent.processUserMessage(commitPrompt); let commitMessage = ""; // Extract the commit message from the AI response for (const entry of commitMessageEntries) { if (entry.type === "assistant" && entry.content.trim()) { commitMessage = entry.content.trim(); break; } } if (!commitMessage) { console.log("❌ Failed to generate commit message"); process.exit(1); } // Clean the commit message const cleanCommitMessage = commitMessage.replace(/^["']|["']$/g, ""); console.log(`✅ Generated commit message: "${cleanCommitMessage}"`); // Execute the commit const commitCommand = `git commit -m "${cleanCommitMessage}"`; const commitResult = await agent.executeBashCommand(commitCommand); if (commitResult.success) { console.log(`✅ git commit: ${commitResult.output?.split("\n")[0] || "Commit successful"}`); // If commit was successful, push to remote // First try regular push, if it fails try with upstream setup let pushResult = await agent.executeBashCommand("git push"); if (!pushResult.success && pushResult.error?.includes("no upstream branch")) { console.log("🔄 Setting upstream and pushing..."); pushResult = await agent.executeBashCommand("git push -u origin HEAD"); } if (pushResult.success) { console.log(`✅ git push: ${pushResult.output?.split("\n")[0] || "Push successful"}`); } else { console.log(`❌ git push: ${pushResult.error || "Push failed"}`); process.exit(1); } } else { console.log(`❌ git commit: ${commitResult.error || "Commit failed"}`); process.exit(1); } } catch (error) { console.error("❌ Error during commit and push:", error.message); process.exit(1); } } // Headless mode processing function async function processPromptHeadless(prompt, apiKey, baseURL, model) { try { const agent = new grok_agent_1.GrokAgent(apiKey, baseURL, model); // Configure confirmation service for headless mode (auto-approve all operations) const confirmationService = confirmation_service_1.ConfirmationService.getInstance(); confirmationService.setSessionFlag("allOperations", true); // Process the user message const chatEntries = await agent.processUserMessage(prompt); // Convert chat entries to OpenAI compatible message objects const messages = []; for (const entry of chatEntries) { switch (entry.type) { case "user": messages.push({ role: "user", content: entry.content, }); break; case "assistant": const assistantMessage = { role: "assistant", content: entry.content, }; // Add tool calls if present if (entry.toolCalls && entry.toolCalls.length > 0) { assistantMessage.tool_calls = entry.toolCalls.map((toolCall) => ({ id: toolCall.id, type: "function", function: { name: toolCall.function.name, arguments: toolCall.function.arguments, }, })); } messages.push(assistantMessage); break; case "tool_result": if (entry.toolCall) { messages.push({ role: "tool", tool_call_id: entry.toolCall.id, content: entry.content, }); } break; } } // Output each message as a separate JSON object for (const message of messages) { console.log(JSON.stringify(message)); } } catch (error) { // Output error in OpenAI compatible format console.log(JSON.stringify({ role: "assistant", content: `Error: ${error.message}`, })); process.exit(1); } } commander_1.program .name("grok") .description("A conversational AI CLI tool powered by Grok with text editor capabilities") .version("1.0.1") .option("-d, --directory <dir>", "set working directory", process.cwd()) .option("-k, --api-key <key>", "Grok API key (or set GROK_API_KEY env var)") .option("-u, --base-url <url>", "Grok API base URL (or set GROK_BASE_URL env var)") .option("-m, --model <model>", "AI model to use (e.g., gemini-2.5-pro, grok-4-latest) (or set GROK_MODEL env var)") .option("-p, --prompt <prompt>", "process a single prompt and exit (headless mode)") .action(async (options) => { if (options.directory) { try { process.chdir(options.directory); } catch (error) { console.error(`Error changing directory to ${options.directory}:`, error.message); process.exit(1); } } try { // Get API key from options, environment, or user settings const apiKey = options.apiKey || loadApiKey(); const baseURL = options.baseUrl || loadBaseURL(); const model = options.model || loadModel(); 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); } // Save API key and base URL to user settings if provided via command line if (options.apiKey || options.baseUrl) { await saveCommandLineSettings(options.apiKey, options.baseUrl); } // Headless mode: process prompt and exit if (options.prompt) { await processPromptHeadless(options.prompt, apiKey, baseURL, model); return; } // Interactive mode: launch UI const agent = new grok_agent_1.GrokAgent(apiKey, baseURL, model); console.log("🤖 Starting Grok CLI Conversational Assistant...\n"); ensureUserSettingsDirectory(); (0, ink_1.render)(react_1.default.createElement(chat_interface_1.default, { agent })); } catch (error) { console.error("❌ Error initializing Grok CLI:", error.message); process.exit(1); } }); // Git subcommand const gitCommand = commander_1.program .command("git") .description("Git operations with AI assistance"); gitCommand .command("commit-and-push") .description("Generate AI commit message and push to remote") .option("-d, --directory <dir>", "set working directory", process.cwd()) .option("-k, --api-key <key>", "Grok API key (or set GROK_API_KEY env var)") .option("-u, --base-url <url>", "Grok API base URL (or set GROK_BASE_URL env var)") .option("-m, --model <model>", "AI model to use (e.g., gemini-2.5-pro, grok-4-latest) (or set GROK_MODEL env var)") .action(async (options) => { if (options.directory) { try { process.chdir(options.directory); } catch (error) { console.error(`Error changing directory to ${options.directory}:`, error.message); process.exit(1); } } try { // Get API key from options, environment, or user settings const apiKey = options.apiKey || loadApiKey(); const baseURL = options.baseUrl || loadBaseURL(); const model = options.model || loadModel(); 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); } // Save API key and base URL to user settings if provided via command line if (options.apiKey || options.baseUrl) { await saveCommandLineSettings(options.apiKey, options.baseUrl); } await handleCommitAndPushHeadless(apiKey, baseURL, model); } catch (error) { console.error("❌ Error during git commit-and-push:", error.message); process.exit(1); } }); // MCP command commander_1.program.addCommand((0, mcp_1.createMCPCommand)()); commander_1.program.parse(); //# sourceMappingURL=index.js.map