diff --git a/src/index.ts b/src/index.ts
index ba3c776..e2dae80 100755
--- a/src/index.ts
+++ b/src/index.ts
@@ -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
", "set working directory", process.cwd())
.option("-k, --api-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");
diff --git a/src/ui/components/api-key-input.tsx b/src/ui/components/api-key-input.tsx
new file mode 100644
index 0000000..b647366
--- /dev/null
+++ b/src/ui/components/api-key-input.tsx
@@ -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 (
+
+ š Grok API Key Required
+
+ Please enter your Grok API key to continue:
+
+
+
+ āÆ
+ {displayText}
+
+
+ {error ? (
+
+ ā {error}
+
+ ) : null}
+
+
+ ⢠Press Enter to submit
+ ⢠Press Ctrl+C to exit
+ Note: API key will be saved to ~/.grok/user-settings.json
+
+
+ {isSubmitting ? (
+
+ š Validating API key...
+
+ ) : null}
+
+ );
+}
\ No newline at end of file
diff --git a/src/ui/components/chat-interface.tsx b/src/ui/components/chat-interface.tsx
index 4505ada..8a984b1 100644
--- a/src/ui/components/chat-interface.tsx
+++ b/src/ui/components/chat-interface.tsx
@@ -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([]);
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) {
);
}
+
+// Main component that handles API key input or chat interface
+export default function ChatInterface({ agent }: ChatInterfaceProps) {
+ const [currentAgent, setCurrentAgent] = useState(agent || null);
+
+ const handleApiKeySet = (newAgent: GrokAgent) => {
+ setCurrentAgent(newAgent);
+ };
+
+ if (!currentAgent) {
+ return ;
+ }
+
+ return ;
+}