Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,5 @@ temp/
*.sw?

# Coding agents
.claude/
.claude/
.grok/
242 changes: 127 additions & 115 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vibe-kit/grok-cli",
"version": "0.0.9",
"version": "0.0.11",
"description": "An open-source AI agent that brings the power of Grok directly into your terminal.",
"main": "dist/index.js",
"bin": {
Expand Down
8 changes: 6 additions & 2 deletions src/agent/grok-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ToolResult } from "../types";
import { EventEmitter } from "events";
import { createTokenCounter, TokenCounter } from "../utils/token-counter";
import { loadCustomInstructions } from "../utils/custom-instructions";
import { getSetting } from "../utils/settings";

export interface ChatEntry {
type: "user" | "assistant" | "tool_result" | "tool_call";
Expand Down Expand Up @@ -45,13 +46,16 @@ export class GrokAgent extends EventEmitter {

constructor(apiKey: string, baseURL?: string, model?: string) {
super();
this.grokClient = new GrokClient(apiKey, model, baseURL);
// Use saved model if no model is explicitly provided
const savedModel = getSetting('selectedModel');
const modelToUse = model || savedModel || 'grok-4-latest';
this.grokClient = new GrokClient(apiKey, modelToUse, baseURL);
this.textEditor = new TextEditorTool();
this.bash = new BashTool();
this.todoTool = new TodoTool();
this.confirmationTool = new ConfirmationTool();
this.search = new SearchTool();
this.tokenCounter = createTokenCounter("grok-4-latest");
this.tokenCounter = createTokenCounter(modelToUse);

// Load custom instructions
const customInstructions = loadCustomInstructions();
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/use-input-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useRef } from "react";
import { useInput, useApp } from "ink";
import { GrokAgent, ChatEntry } from "../agent/grok-agent";
import { ConfirmationService } from "../utils/confirmation-service";
import { updateSetting } from "../utils/settings";

interface UseInputHandlerProps {
agent: GrokAgent;
Expand Down Expand Up @@ -139,6 +140,7 @@ Examples:

if (modelNames.includes(modelArg)) {
agent.setModel(modelArg);
updateSetting('selectedModel', modelArg);
const confirmEntry: ChatEntry = {
type: "assistant",
content: `✓ Switched to model: ${modelArg}`,
Expand Down Expand Up @@ -435,6 +437,7 @@ Available models: ${modelNames.join(", ")}`,
if (key.tab || key.return) {
const selectedModel = availableModels[selectedModelIndex];
agent.setModel(selectedModel.model);
updateSetting('selectedModel', selectedModel.model);
const confirmEntry: ChatEntry = {
type: "assistant",
content: `✓ Switched to model: ${selectedModel.model}`,
Expand Down
28 changes: 28 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ import { ConfirmationService } from "./utils/confirmation-service";
// Load environment variables
dotenv.config();

// Ensure user .grok directory exists with default settings
function ensureUserSettingsDirectory(): void {
try {
const homeDir = os.homedir();
const grokDir = path.join(homeDir, ".grok");
const settingsFile = path.join(grokDir, "user-settings.json");

// Create .grok directory if it doesn't exist
if (!fs.existsSync(grokDir)) {
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));
}
} catch (error) {
// Silently ignore errors during setup
}
}

// Load API key from user settings if not in environment
function loadApiKey(): string | undefined {
// First check environment variables
Expand All @@ -22,6 +48,7 @@ function loadApiKey(): string | undefined {
if (!apiKey) {
// Try to load from user settings file
try {
ensureUserSettingsDirectory();
const homeDir = os.homedir();
const settingsFile = path.join(homeDir, ".grok", "user-settings.json");

Expand All @@ -45,6 +72,7 @@ function loadBaseURL(): string | undefined {
if (!baseURL) {
// Try to load from user settings file
try {
ensureUserSettingsDirectory();
const homeDir = os.homedir();
const settingsFile = path.join(homeDir, ".grok", "user-settings.json");

Expand Down
8 changes: 7 additions & 1 deletion src/ui/components/chat-interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,17 @@ function ChatInterfaceWithAgent({ agent }: { agent: GrokAgent }) {
isStreaming={isStreaming}
/>

<Box>
<Box flexDirection="row">
<Text color="cyan">
{autoEditEnabled ? "▶" : "⏸"} auto-edit:{" "}
{autoEditEnabled ? "on" : "off"}
</Text>
<Box marginLeft={1}>
<Text dimColor>(shift + tab)</Text>
</Box>
<Box marginLeft={2}>
<Text color="yellow">⚡{agent.getCurrentModel()}</Text>
</Box>
</Box>

<CommandSuggestions
Expand Down
65 changes: 65 additions & 0 deletions src/utils/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import fs from 'fs';
import path from 'path';

interface Settings {
selectedModel?: string;
[key: string]: any;
}

const SETTINGS_DIR = '.grok';
const SETTINGS_FILE = 'settings.json';

function getSettingsPath(): string {
return path.join(process.cwd(), SETTINGS_DIR, SETTINGS_FILE);
}

function ensureSettingsDirectory(): void {
const settingsDir = path.join(process.cwd(), SETTINGS_DIR);
if (!fs.existsSync(settingsDir)) {
fs.mkdirSync(settingsDir, { recursive: true });
}
}

const DEFAULT_SETTINGS: Settings = {
selectedModel: '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');
return JSON.parse(settingsContent);
} catch (error) {
console.warn('Failed to load settings:', error);
return {};
}
}

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);
}
}

export function updateSetting<K extends keyof Settings>(key: K, value: Settings[K]): void {
const currentSettings = loadSettings();
currentSettings[key] = value;
saveSettings(currentSettings);
}

export function getSetting<K extends keyof Settings>(key: K): Settings[K] {
const settings = loadSettings();
return settings[key];
}
6 changes: 0 additions & 6 deletions test.py

This file was deleted.

Loading