add support for setting api key in TUI (#11)

This commit is contained in:
Ismail Pelaseyed authored and GitHub committed 2025-07-21 13:27:59 +02:00
1 parent ca39bab356
commit eb3fd06c54
3 files changed
+186 -16

No files matched your search

+29 -13
View File
@@ -6,10 +6,36 @@ import { program } from "commander";
import * as dotenv from "dotenv";
import { GrokAgent } from "./agent/grok-agent";
import ChatInterface from "./ui/components/chat-interface";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
// Load environment variables
dotenv.config();
// Load API key from user settings if not in environment
function loadApiKey(): string | undefined {
// First check environment variables
let apiKey = process.env.GROK_API_KEY;
if (!apiKey) {
// Try to load from user settings file
try {
const homeDir = os.homedir();
const settingsFile = path.join(homeDir, '.grok', 'user-settings.json');
if (fs.existsSync(settingsFile)) {
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
apiKey = settings.apiKey;
}
} catch (error) {
// Ignore errors, apiKey will remain undefined
}
}
return apiKey;
}
program
.name("grok")
.description(
@@ -19,18 +45,6 @@ program
.option("-d, --directory <dir>", "set working directory", process.cwd())
.option("-k, --api-key <key>", "Grok API key (or set GROK_API_KEY env var)")
.action((options) => {
// Get API key from options or environment
const apiKey = options.apiKey || process.env.GROK_API_KEY;
if (!apiKey) {
console.error("❌ Error: Grok API key is required");
console.error(
" Set GROK_API_KEY environment variable or use --api-key option"
);
console.error(" Copy .env.example to .env and add your API key");
process.exit(1);
}
if (options.directory) {
try {
process.chdir(options.directory);
@@ -44,7 +58,9 @@ program
}
try {
const agent = new GrokAgent(apiKey);
// Get API key from options, environment, or user settings
const apiKey = options.apiKey || loadApiKey();
const agent = apiKey ? new GrokAgent(apiKey) : undefined;
console.log("🤖 Starting Grok CLI Conversational Assistant...\n");
+138
View File
@@ -0,0 +1,138 @@
import React, { useState } from "react";
import { Box, Text, useInput, useApp } from "ink";
import { GrokAgent } from "../../agent/grok-agent";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
interface ApiKeyInputProps {
onApiKeySet: (agent: GrokAgent) => void;
}
interface UserSettings {
apiKey?: string;
}
export default function ApiKeyInput({ onApiKeySet }: ApiKeyInputProps) {
const [input, setInput] = useState("");
const [error, setError] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const { exit } = useApp();
useInput((inputChar, key) => {
if (isSubmitting) return;
if (key.ctrl && inputChar === "c") {
exit();
return;
}
if (key.return) {
handleSubmit();
return;
}
if (key.backspace || key.delete) {
setInput((prev) => prev.slice(0, -1));
setError("");
return;
}
if (inputChar && !key.ctrl && !key.meta) {
setInput((prev) => prev + inputChar);
setError("");
}
});
const handleSubmit = async () => {
if (!input.trim()) {
setError("API key cannot be empty");
return;
}
setIsSubmitting(true);
try {
const apiKey = input.trim();
const agent = new GrokAgent(apiKey);
// Set environment variable for current process
process.env.GROK_API_KEY = apiKey;
// Save to .grok/user-settings.json
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, { mode: 0o700 });
}
// Load existing settings or create new
let settings: UserSettings = {};
if (fs.existsSync(settingsFile)) {
try {
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
} catch {
settings = {};
}
}
// Update API key
settings.apiKey = apiKey;
// Save settings
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), { mode: 0o600 });
console.log(`\n✅ API key saved to ~/.grok/user-settings.json`);
} catch (error) {
console.log('\n⚠️ Could not save API key to settings file');
console.log('API key set for current session only');
}
onApiKeySet(agent);
} catch (error: any) {
setError("Invalid API key format");
setIsSubmitting(false);
}
};
const displayText = input.length > 0 ?
(isSubmitting ? "*".repeat(input.length) : "*".repeat(input.length) + "█") :
(isSubmitting ? " " : "█");
return (
<Box flexDirection="column" paddingX={2} paddingY={1}>
<Text color="yellow">🔑 Grok API Key Required</Text>
<Box marginBottom={1}>
<Text color="gray">Please enter your Grok API key to continue:</Text>
</Box>
<Box borderStyle="round" borderColor="blue" paddingX={1} marginBottom={1}>
<Text color="gray">❯ </Text>
<Text>{displayText}</Text>
</Box>
{error ? (
<Box marginBottom={1}>
<Text color="red">❌ {error}</Text>
</Box>
) : null}
<Box flexDirection="column" marginTop={1}>
<Text color="gray" dimColor>• Press Enter to submit</Text>
<Text color="gray" dimColor>• Press Ctrl+C to exit</Text>
<Text color="gray" dimColor>Note: API key will be saved to ~/.grok/user-settings.json</Text>
</Box>
{isSubmitting ? (
<Box marginTop={1}>
<Text color="yellow">🔄 Validating API key...</Text>
</Box>
) : null}
</Box>
);
}
+19 -3
View File
@@ -9,13 +9,15 @@ import { ChatHistory } from "./chat-history";
import { ChatInput } from "./chat-input";
import ConfirmationDialog from "./confirmation-dialog";
import { ConfirmationService, ConfirmationOptions } from "../../utils/confirmation-service";
import ApiKeyInput from "./api-key-input";
import cfonts from "cfonts";
interface ChatInterfaceProps {
agent: GrokAgent;
agent?: GrokAgent;
}
export default function ChatInterface({ agent }: ChatInterfaceProps) {
// Main chat component that handles input when agent is available
function ChatInterfaceWithAgent({ agent }: { agent: GrokAgent }) {
const [chatHistory, setChatHistory] = useState<ChatEntry[]>([]);
const [isProcessing, setIsProcessing] = useState(false);
const [processingTime, setProcessingTime] = useState(0);
@@ -87,7 +89,6 @@ export default function ChatInterface({ agent }: ChatInterfaceProps) {
};
}, [confirmationService]);
useEffect(() => {
if (!isProcessing && !isStreaming) {
setProcessingTime(0);
@@ -181,3 +182,18 @@ export default function ChatInterface({ agent }: ChatInterfaceProps) {
</Box>
);
}
// Main component that handles API key input or chat interface
export default function ChatInterface({ agent }: ChatInterfaceProps) {
const [currentAgent, setCurrentAgent] = useState<GrokAgent | null>(agent || null);
const handleApiKeySet = (newAgent: GrokAgent) => {
setCurrentAgent(newAgent);
};
if (!currentAgent) {
return <ApiKeyInput onApiKeySet={handleApiKeySet} />;
}
return <ChatInterfaceWithAgent agent={currentAgent} />;
}