Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Create `~/.grok/user-settings.json`:

### Custom Base URL (Optional)

You can configure a custom Grok API endpoint (choose one method):
By default, the CLI uses `https://api.x.ai/v1` as the Grok API endpoint. You can configure a custom endpoint if needed (choose one method):

**Method 1: Environment Variable**
```bash
Expand All @@ -75,7 +75,7 @@ export GROK_BASE_URL=https://your-custom-endpoint.com/v1

**Method 2: Command Line Flag**
```bash
grok --api-key your_api_key_here --baseurl https://your-custom-endpoint.com/v1
grok --api-key your_api_key_here --base-url https://your-custom-endpoint.com/v1
```

**Method 3: User Settings File**
Expand Down Expand Up @@ -143,11 +143,50 @@ Add to `~/.grok/user-settings.json`:
```json
{
"apiKey": "your_api_key_here",
"model": "grok-4-latest"
"defaultModel": "grok-4-latest"
}
```

Priority order: `--model` flag > `GROK_MODEL` environment variable > user settings > default (grok-4-latest)
### Adding Custom Models

You can add custom models to your available model list by updating the `models` array in your user settings:

```json
{
"apiKey": "your_api_key_here",
"baseURL": "https://your-custom-api-endpoint.com/v1",
"defaultModel": "grok-4-latest",
"models": [
"grok-4-latest",
"grok-3-latest",
"grok-3-fast",
"grok-3-mini-fast",
"gemini-2.5-pro",
"claude-sonnet-4-20250514",
"gpt-4o",
"custom-model-name"
]
}
```

**Note**: When using non-Grok models, you'll need to:
1. Set the appropriate `baseURL` for the model's API endpoint
2. Ensure your API key is valid for that service
3. Use the correct model name as expected by the target API

**Example for different providers**:
```bash
# For Gemini models (Google AI)
grok --model gemini-2.5-pro --base-url https://generativelanguage.googleapis.com/v1beta

# For OpenAI models
grok --model gpt-4o --base-url https://api.openai.com/v1

# For Claude models (Anthropic)
grok --model claude-sonnet-4-20250514 --base-url https://api.anthropic.com/v1
```

Priority order: `--model` flag > `GROK_MODEL` environment variable > user default model > system default (grok-4-latest)

### Command Line Options

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 16 additions & 17 deletions src/hooks/use-input-handler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useState, useRef } from "react";
import { useState, useRef, useMemo } from "react";
import { useInput } from "ink";
import { GrokAgent, ChatEntry } from "../agent/grok-agent";
import { ConfirmationService } from "../utils/confirmation-service";
import { updateSetting } from "../utils/settings";
import { filterCommandSuggestions } from "../ui/components/command-suggestions";
import { loadModelConfig, ModelOption as ConfigModelOption, updateCurrentModel } from "../utils/model-config";

interface UseInputHandlerProps {
agent: GrokAgent;
Expand All @@ -26,7 +27,6 @@ interface CommandSuggestion {

interface ModelOption {
model: string;
description: string;
}

export function useInputHandler({
Expand Down Expand Up @@ -62,15 +62,10 @@ export function useInputHandler({
{ command: "/exit", description: "Exit the application" },
];

const availableModels: ModelOption[] = [
{
model: "grok-4-latest",
description: "Latest Grok-4 model (most capable)",
},
{ model: "grok-3-latest", description: "Latest Grok-3 model" },
{ model: "grok-3-fast", description: "Fast Grok-3 variant" },
{ model: "grok-3-mini-fast", description: "Fastest Grok-3 variant" },
];
// Load models from configuration with fallback to defaults
const availableModels: ModelOption[] = useMemo(() => {
return loadModelConfig(); // Return directly, interface already matches
}, []);

const handleDirectCommand = async (input: string): Promise<boolean> => {
const trimmedInput = input.trim();
Expand Down Expand Up @@ -102,24 +97,27 @@ export function useInputHandler({
Built-in Commands:
/clear - Clear chat history
/help - Show this help
/models - Switch Grok models
/models - Switch between available models
/exit - Exit application
exit, quit - Exit application

Git Commands:
/commit-and-push - AI-generated commit + push to remote

Keyboard Shortcuts:
Shift+Tab - Toggle auto-edit mode (bypass confirmations)

Direct Commands (executed immediately):
ls [path] - List directory contents
pwd - Show current directory
pwd - Show current directory
cd <path> - Change directory
cat <file> - View file contents
mkdir <dir> - Create directory
touch <file>- Create empty file

Model Configuration:
Edit ~/.grok/models.json to add custom models (Claude, GPT, Gemini, etc.)

For complex operations, just describe what you want in natural language.
Examples:
"edit package.json and add a new script"
Expand Down Expand Up @@ -150,7 +148,7 @@ Examples:

if (modelNames.includes(modelArg)) {
agent.setModel(modelArg);
updateSetting("model", modelArg);
updateCurrentModel(modelArg); // Update project current model
const confirmEntry: ChatEntry = {
type: "assistant",
content: `✓ Switched to model: ${modelArg}`,
Expand All @@ -172,6 +170,7 @@ Available models: ${modelNames.join(", ")}`,
return true;
}


if (trimmedInput === "/commit-and-push") {
const userEntry: ChatEntry = {
type: "user",
Expand Down Expand Up @@ -665,7 +664,7 @@ Respond with ONLY the commit message, no additional text.`;
if (key.tab || key.return) {
const selectedModel = availableModels[selectedModelIndex];
agent.setModel(selectedModel.model);
updateSetting("model", selectedModel.model);
updateCurrentModel(selectedModel.model); // Update project current model
const confirmEntry: ChatEntry = {
type: "assistant",
content: `✓ Switched to model: ${selectedModel.model}`,
Expand Down
106 changes: 91 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,37 @@ function ensureUserSettingsDirectory(): void {
fs.mkdirSync(grokDir, { recursive: true });
}

// Create default user-settings.json if it doesn't exist
if (!fs.existsSync(settingsFile)) {
const defaultSettings = {
apiKey: "",
baseURL: "",
defaultModel: "grok-4-latest",
};
fs.writeFileSync(settingsFile, JSON.stringify(defaultSettings, null, 2));
// Create or update user-settings.json with default settings
let settings: any = {};

// Load existing settings if file exists
if (fs.existsSync(settingsFile)) {
try {
settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
} catch (error) {
// If file is corrupted, start fresh
settings = {};
}
}

// Ensure all required fields exist with defaults
const defaultSettings: any = {
baseURL: settings.baseURL || "https://api.x.ai/v1",
defaultModel: settings.defaultModel || "grok-4-latest",
models: settings.models || [
"grok-4-latest",
"grok-3-latest",
"grok-3-fast",
"grok-3-mini-fast"
]
};

// Only include apiKey if it has a meaningful value
if (settings.apiKey) {
defaultSettings.apiKey = settings.apiKey;
}

fs.writeFileSync(settingsFile, JSON.stringify(defaultSettings, null, 2));
} catch (error) {
// Silently ignore errors during setup
}
Expand Down Expand Up @@ -98,7 +120,7 @@ function loadApiKey(): string | undefined {
}

// Load base URL from user settings if not in environment
function loadBaseURL(): string | undefined {
function loadBaseURL(): string {
// First check environment variables
let baseURL = process.env.GROK_BASE_URL;

Expand All @@ -114,11 +136,46 @@ function loadBaseURL(): string | undefined {
baseURL = settings.baseURL;
}
} catch (error) {
// Ignore errors, baseURL will remain undefined
// Ignore errors, will use default
}
}

return baseURL;
// Return default if no baseURL found
return baseURL || "https://api.x.ai/v1";
}

// Save command line settings to user settings file
async function saveCommandLineSettings(apiKey?: string, baseURL?: string): Promise<void> {
try {
ensureUserSettingsDirectory();
const homeDir = os.homedir();
const settingsFile = path.join(homeDir, ".grok", "user-settings.json");

// Load existing settings
let settings: any = {};
if (fs.existsSync(settingsFile)) {
try {
settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
} catch {
settings = {};
}
}

// Update with command line values
if (apiKey) {
settings.apiKey = apiKey;
console.log("✅ API key saved to ~/.grok/user-settings.json");
}
if (baseURL) {
settings.baseURL = baseURL;
console.log("✅ Base URL saved to ~/.grok/user-settings.json");
}

// Save updated settings
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), { mode: 0o600 });
} 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
Expand All @@ -127,12 +184,18 @@ function loadModel(): string | undefined {
let model = process.env.GROK_MODEL;

if (!model) {
// Try to load from local settings file
// Use the unified model loading from model-config
try {
const settings = loadSettings();
model = settings.model;
const { getCurrentModel } = require("./utils/model-config");
model = getCurrentModel();
} catch (error) {
// Ignore errors, model will remain undefined
// Fallback to local settings file for backward compatibility
try {
const settings = loadSettings();
model = settings.model;
} catch (error) {
// Ignore errors, model will remain undefined
}
}
}

Expand Down Expand Up @@ -383,6 +446,11 @@ program
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);
Expand All @@ -392,6 +460,9 @@ program
// Interactive mode: launch UI
const agent = new GrokAgent(apiKey, baseURL, model);
console.log("🤖 Starting Grok CLI Conversational Assistant...\n");

ensureUserSettingsDirectory();

render(React.createElement(ChatInterface, { agent }));
} catch (error: any) {
console.error("❌ Error initializing Grok CLI:", error.message);
Expand Down Expand Up @@ -443,6 +514,11 @@ gitCommand
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: any) {
console.error("❌ Error during git commit-and-push:", error.message);
Expand Down
4 changes: 0 additions & 4 deletions src/ui/components/model-selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Box, Text } from "ink";

interface ModelOption {
model: string;
description: string;
}

interface ModelSelectionProps {
Expand Down Expand Up @@ -34,9 +33,6 @@ export function ModelSelection({
>
{modelOption.model}
</Text>
<Box marginLeft={1}>
<Text color="gray">{modelOption.description}</Text>
</Box>
</Box>
))}
<Box marginTop={1}>
Expand Down
Loading
Loading