@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
JavaScript
;
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