add support for setting api key in TUI (#11)
This commit is contained in:
1 parent
ca39bab356
commit
eb3fd06c54
3 files changed
+186
-16
No files matched your search
+29
-13
@@ -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");
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
Reference in new issue
Block a user