diff --git a/README.md b/README.md
index ec19fd6..b09d01c 100644
--- a/README.md
+++ b/README.md
@@ -118,8 +118,9 @@ This mode is particularly useful for:
### Model Selection
-You can specify which AI model to use with the `--model` parameter:
+You can specify which AI model to use with the `--model` parameter or `GROK_MODEL` environment variable:
+**Method 1: Command Line Flag**
```bash
# Use Grok models
grok --model grok-4-latest
@@ -131,6 +132,23 @@ 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
```
+**Method 2: Environment Variable**
+```bash
+export GROK_MODEL=grok-4-latest
+grok
+```
+
+**Method 3: User Settings File**
+Add to `~/.grok/user-settings.json`:
+```json
+{
+ "apiKey": "your_api_key_here",
+ "model": "grok-4-latest"
+}
+```
+
+Priority order: `--model` flag > `GROK_MODEL` environment variable > user settings > default (grok-4-latest)
+
### Command Line Options
```bash
@@ -141,7 +159,7 @@ Options:
-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)
+ -m, --model AI model to use (e.g., grok-4-latest, grok-3-latest) (or set GROK_MODEL env var)
-p, --prompt process a single prompt and exit (headless mode)
-h, --help display help for command
```
diff --git a/package.json b/package.json
index 20ee5c1..784913b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@vibe-kit/grok-cli",
- "version": "0.0.14",
+ "version": "0.0.15",
"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/agent/grok-agent.ts b/src/agent/grok-agent.ts
index f5d9423..6fd049b 100644
--- a/src/agent/grok-agent.ts
+++ b/src/agent/grok-agent.ts
@@ -54,8 +54,7 @@ export class GrokAgent extends EventEmitter {
constructor(apiKey: string, baseURL?: string, model?: string) {
super();
- // Use saved model if no model is explicitly provided
- const savedModel = getSetting("selectedModel");
+ const savedModel = getSetting("model");
const modelToUse = model || savedModel || "grok-4-latest";
this.grokClient = new GrokClient(apiKey, modelToUse, baseURL);
this.textEditor = new TextEditorTool();
diff --git a/src/hooks/use-input-handler.ts b/src/hooks/use-input-handler.ts
index 0ae90e2..c0ff353 100644
--- a/src/hooks/use-input-handler.ts
+++ b/src/hooks/use-input-handler.ts
@@ -150,7 +150,7 @@ Examples:
if (modelNames.includes(modelArg)) {
agent.setModel(modelArg);
- updateSetting("selectedModel", modelArg);
+ updateSetting("model", modelArg);
const confirmEntry: ChatEntry = {
type: "assistant",
content: `✓ Switched to model: ${modelArg}`,
@@ -663,7 +663,7 @@ Respond with ONLY the commit message, no additional text.`;
if (key.tab || key.return) {
const selectedModel = availableModels[selectedModelIndex];
agent.setModel(selectedModel.model);
- updateSetting("selectedModel", selectedModel.model);
+ updateSetting("model", selectedModel.model);
const confirmEntry: ChatEntry = {
type: "assistant",
content: `✓ Switched to model: ${selectedModel.model}`,
diff --git a/src/index.ts b/src/index.ts
index a89dcf6..d78471f 100755
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,6 +6,7 @@ import { program } from "commander";
import * as dotenv from "dotenv";
import { GrokAgent } from "./agent/grok-agent";
import ChatInterface from "./ui/components/chat-interface";
+import { getSetting, loadSettings } from "./utils/settings";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
@@ -16,32 +17,32 @@ import { createMCPCommand } from "./commands/mcp";
dotenv.config();
// Add proper signal handling for terminal cleanup
-process.on('SIGINT', () => {
+process.on("SIGINT", () => {
// Restore terminal to normal mode before exit
if (process.stdin.isTTY) {
process.stdin.setRawMode(false);
}
- console.log('\nGracefully shutting down...');
+ console.log("\nGracefully shutting down...");
process.exit(0);
});
-process.on('SIGTERM', () => {
+process.on("SIGTERM", () => {
// Restore terminal to normal mode before exit
if (process.stdin.isTTY) {
process.stdin.setRawMode(false);
}
- console.log('\nGracefully shutting down...');
+ console.log("\nGracefully shutting down...");
process.exit(0);
});
// Handle uncaught exceptions to prevent hanging
-process.on('uncaughtException', (error) => {
- console.error('Uncaught exception:', error);
+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.on("unhandledRejection", (reason, promise) => {
+ console.error("Unhandled rejection at:", promise, "reason:", reason);
process.exit(1);
});
@@ -62,7 +63,7 @@ function ensureUserSettingsDirectory(): void {
const defaultSettings = {
apiKey: "",
baseURL: "",
- defaultModel: "grok-4-latest"
+ defaultModel: "grok-4-latest",
};
fs.writeFileSync(settingsFile, JSON.stringify(defaultSettings, null, 2));
}
@@ -119,6 +120,24 @@ function loadBaseURL(): string | undefined {
return baseURL;
}
+// Load model from user settings if not in environment
+function loadModel(): string | undefined {
+ // First check environment variables
+ let model = process.env.GROK_MODEL;
+
+ if (!model) {
+ // Try to load from local settings file
+ try {
+ const settings = loadSettings();
+ model = settings.model;
+ } catch (error) {
+ // Ignore errors, model will remain undefined
+ }
+ }
+
+ return model;
+}
+
// Handle commit-and-push command in headless mode
async function handleCommitAndPushHeadless(
apiKey: string,
@@ -136,8 +155,10 @@ async function handleCommitAndPushHeadless(
console.log("> /commit-and-push\n");
// First check if there are any changes at all
- const initialStatusResult = await agent.executeBashCommand("git status --porcelain");
-
+ 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);
@@ -147,9 +168,11 @@ async function handleCommitAndPushHeadless(
// Add all changes
const addResult = await agent.executeBashCommand("git add .");
-
+
if (!addResult.success) {
- console.log(`❌ git add: ${addResult.error || 'Failed to stage changes'}`);
+ console.log(
+ `❌ git add: ${addResult.error || "Failed to stage changes"}`
+ );
process.exit(1);
}
@@ -171,10 +194,10 @@ Follow conventional commit format (feat:, fix:, docs:, etc.) and keep it under 7
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()) {
@@ -189,7 +212,7 @@ Respond with ONLY the commit message, no additional text.`;
}
// Clean the commit message
- const cleanCommitMessage = commitMessage.replace(/^["']|["']$/g, '');
+ const cleanCommitMessage = commitMessage.replace(/^["']|["']$/g, "");
console.log(`✅ Generated commit message: "${cleanCommitMessage}"`);
// Execute the commit
@@ -197,28 +220,38 @@ Respond with ONLY the commit message, no additional text.`;
const commitResult = await agent.executeBashCommand(commitCommand);
if (commitResult.success) {
- console.log(`✅ git commit: ${commitResult.output?.split('\n')[0] || 'Commit successful'}`);
-
+ 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")) {
+
+ 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'}`);
+ console.log(
+ `✅ git push: ${
+ pushResult.output?.split("\n")[0] || "Push successful"
+ }`
+ );
} else {
- console.log(`❌ git push: ${pushResult.error || 'Push failed'}`);
+ console.log(`❌ git push: ${pushResult.error || "Push failed"}`);
process.exit(1);
}
} else {
- console.log(`❌ git commit: ${commitResult.error || 'Commit failed'}`);
+ console.log(`❌ git commit: ${commitResult.error || "Commit failed"}`);
process.exit(1);
}
-
} catch (error: any) {
console.error("❌ Error during commit and push:", error.message);
process.exit(1);
@@ -306,7 +339,7 @@ program
)
.option(
"-m, --model ",
- "AI model to use (e.g., gemini-2.5-pro, grok-4-latest)"
+ "AI model to use (e.g., gemini-2.5-pro, grok-4-latest) (or set GROK_MODEL env var)"
)
.option(
"-p, --prompt ",
@@ -329,7 +362,7 @@ program
// Get API key from options, environment, or user settings
const apiKey = options.apiKey || loadApiKey();
const baseURL = options.baseUrl || loadBaseURL();
- const model = options.model;
+ const model = options.model || loadModel();
if (!apiKey) {
console.error(
@@ -370,7 +403,7 @@ gitCommand
)
.option(
"-m, --model ",
- "AI model to use (e.g., gemini-2.5-pro, grok-4-latest)"
+ "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) {
@@ -389,7 +422,7 @@ gitCommand
// Get API key from options, environment, or user settings
const apiKey = options.apiKey || loadApiKey();
const baseURL = options.baseUrl || loadBaseURL();
- const model = options.model;
+ const model = options.model || loadModel();
if (!apiKey) {
console.error(
diff --git a/src/mcp/config.ts b/src/mcp/config.ts
index d4466c4..f12ab3c 100644
--- a/src/mcp/config.ts
+++ b/src/mcp/config.ts
@@ -7,7 +7,7 @@ const CONFIG_DIR = path.join(process.cwd(), ".grok");
const SETTINGS_FILE = path.join(CONFIG_DIR, "settings.json");
export interface Settings {
- selectedModel?: string;
+ model?: string;
mcpServers?: Record;
}
@@ -52,12 +52,12 @@ export function loadMCPConfig(): MCPConfig {
export function saveMCPConfig(config: MCPConfig): void {
const settings = loadSettings();
settings.mcpServers = {};
-
+
// Convert servers array to object keyed by name
for (const server of config.servers) {
settings.mcpServers[server.name] = server;
}
-
+
saveSettings(settings);
}
@@ -66,7 +66,7 @@ export function addMCPServer(config: MCPServerConfig): void {
if (!settings.mcpServers) {
settings.mcpServers = {};
}
-
+
settings.mcpServers[config.name] = config;
saveSettings(settings);
}
diff --git a/src/utils/settings.ts b/src/utils/settings.ts
index 7cc07d4..283a990 100644
--- a/src/utils/settings.ts
+++ b/src/utils/settings.ts
@@ -1,13 +1,13 @@
-import fs from 'fs';
-import path from 'path';
+import fs from "fs";
+import path from "path";
interface Settings {
- selectedModel?: string;
+ model?: string;
[key: string]: any;
}
-const SETTINGS_DIR = '.grok';
-const SETTINGS_FILE = 'settings.json';
+const SETTINGS_DIR = ".grok";
+const SETTINGS_FILE = "settings.json";
function getSettingsPath(): string {
return path.join(process.cwd(), SETTINGS_DIR, SETTINGS_FILE);
@@ -21,23 +21,23 @@ function ensureSettingsDirectory(): void {
}
const DEFAULT_SETTINGS: Settings = {
- selectedModel: 'grok-4-latest'
+ model: "grok-4-latest",
};
export function loadSettings(): Settings {
try {
ensureSettingsDirectory();
const settingsPath = getSettingsPath();
-
+
if (!fs.existsSync(settingsPath)) {
saveSettings(DEFAULT_SETTINGS);
return DEFAULT_SETTINGS;
}
-
- const settingsContent = fs.readFileSync(settingsPath, 'utf-8');
+
+ const settingsContent = fs.readFileSync(settingsPath, "utf-8");
return JSON.parse(settingsContent);
} catch (error) {
- console.warn('Failed to load settings:', error);
+ console.warn("Failed to load settings:", error);
return {};
}
}
@@ -46,14 +46,17 @@ export function saveSettings(settings: Settings): void {
try {
ensureSettingsDirectory();
const settingsPath = getSettingsPath();
-
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
} catch (error) {
- console.error('Failed to save settings:', error);
+ console.error("Failed to save settings:", error);
}
}
-export function updateSetting(key: K, value: Settings[K]): void {
+export function updateSetting(
+ key: K,
+ value: Settings[K]
+): void {
const currentSettings = loadSettings();
currentSettings[key] = value;
saveSettings(currentSettings);
@@ -62,4 +65,4 @@ export function updateSetting(key: K, value: Settings[
export function getSetting(key: K): Settings[K] {
const settings = loadSettings();
return settings[key];
-}
\ No newline at end of file
+}