From d474208e8b1d0fca4232cb04ff424853d36851af Mon Sep 17 00:00:00 2001 From: SuperComboGamer Date: Wed, 10 Dec 2025 22:28:27 -0500 Subject: [PATCH] fix: improve auth status display and remove verbose console logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix authentication status display in settings showing "Method: Unknown" - Add support for CLAUDE_CODE_OAUTH_TOKEN environment variable - Update ClaudeAuthStatus type to include all auth methods - Fix method mapping in use-cli-status hook - Display correct auth method labels in UI - Remove verbose console logging from: - claude-cli-detector.js - codex-cli-detector.js - agent-service.js - main.js (IPC, Security logs) - Fix TypeScript errors: - Add proper type exports for AutoModeEvent - Fix Project import paths - Add null checks for api.features - Add openExternalLink to ElectronAPI type - Add type annotation for REQUIRED_STRUCTURE - Update README with clearer getting started guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 62 +++--- app/electron/agent-service.js | 4 +- app/electron/main.js | 50 ----- app/electron/services/claude-cli-detector.js | 94 ++------- app/electron/services/codex-cli-detector.js | 190 +++++------------- app/src/components/views/analysis-view.tsx | 8 +- app/src/components/views/interview-view.tsx | 6 +- .../authentication-status-display.tsx | 142 ++++++------- .../components/delete-project-dialog.tsx | 2 +- .../components/settings-navigation.tsx | 2 +- .../settings-view/hooks/use-cli-status.ts | 19 +- app/src/components/views/setup-view.tsx | 26 ++- app/src/lib/electron.ts | 34 +++- app/src/lib/project-init.ts | 5 +- app/src/store/setup-store.ts | 6 +- app/src/types/electron.d.ts | 1 + 16 files changed, 232 insertions(+), 419 deletions(-) diff --git a/README.md b/README.md index d23a9d80..72758a78 100644 --- a/README.md +++ b/README.md @@ -20,54 +20,54 @@ Automaker is an autonomous AI development studio that helps you build software f ## Getting Started -**Step 1:** Clone this repository: +### Prerequisites + +- Node.js 18+ +- npm +- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated + +### Quick Start ```bash +# 1. Clone the repo git clone git@github.com:AutoMaker-Org/automaker.git cd automaker -``` -**Step 2:** Install dependencies: - -```bash +# 2. Install dependencies npm install -``` -**Step 3:** Run the Claude Code setup token command: - -```bash +# 3. Get your Claude Code OAuth token claude setup-token -``` +# ⚠️ This prints your token - don't share your screen! -> **⚠️ Warning:** This command will print your token to your terminal. Be careful if you're streaming or sharing your screen, as the token will be visible to anyone watching. - -**Step 4:** Export the Claude Code OAuth token in your shell: - -```bash -export CLAUDE_CODE_OAUTH_TOKEN="your-token-here" -``` - -**Step 5:** Start the development server: - -```bash +# 4. Set the token and run +export CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-..." npm run dev:electron ``` -This will start both the Next.js development server and the Electron application. +### Authentication Options -**Step 6:** MOST IMPORANT: Run the Following after all is setup +Automaker supports multiple authentication methods (in order of priority): + +| Method | Environment Variable | Description | +|--------|---------------------|-------------| +| OAuth Token (env) | `CLAUDE_CODE_OAUTH_TOKEN` | From `claude setup-token` - uses your Claude subscription | +| OAuth Token (stored) | — | Stored in app credentials file | +| API Key (stored) | — | Anthropic API key stored in app | +| API Key (env) | `ANTHROPIC_API_KEY` | Pay-per-use API key | + +**Recommended:** Use `CLAUDE_CODE_OAUTH_TOKEN` if you have a Claude subscription. + +### Persistent Setup (Optional) + +Add to your `~/.bashrc` or `~/.zshrc`: ```bash -echo "W" -echo "W" -echo "W" -echo "W" -echo "W" -echo "W" -echo "W" -echo "W" +export CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-your-token-here" ``` +Then restart your terminal or run `source ~/.bashrc`. + ## Features - 📋 **Kanban Board** - Visual drag-and-drop board to manage features through backlog, in progress, waiting approval, and verified stages diff --git a/app/electron/agent-service.js b/app/electron/agent-service.js index 82d446f5..d8dc0247 100644 --- a/app/electron/agent-service.js +++ b/app/electron/agent-service.js @@ -19,14 +19,12 @@ class AgentService { this.stateDir = path.join(appDataPath, "agent-sessions"); this.metadataFile = path.join(appDataPath, "sessions-metadata.json"); await fs.mkdir(this.stateDir, { recursive: true }); - console.log("[AgentService] Initialized with state dir:", this.stateDir); } /** * Start or resume a conversation */ async startConversation({ sessionId, workingDirectory }) { - console.log("[AgentService] Starting conversation:", sessionId); // Initialize session if it doesn't exist if (!this.sessions.has(sessionId)) { @@ -307,7 +305,7 @@ class AgentService { }; } catch (error) { if (error instanceof AbortError || error?.name === "AbortError") { - console.log("[AgentService] Query aborted"); + // Query aborted session.isRunning = false; session.abortController = null; return { success: false, aborted: true }; diff --git a/app/electron/main.js b/app/electron/main.js index 270f5386..41e96442 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -70,9 +70,6 @@ app.whenReady().then(async () => { addAllowedPath(session.projectPath); } }); - console.log( - `[Security] Pre-loaded ${allowedPaths.size} allowed paths from history` - ); } catch (error) { console.error("Failed to load sessions for security whitelist:", error); } @@ -101,7 +98,6 @@ const allowedPaths = new Set(); function addAllowedPath(pathToAdd) { if (!pathToAdd) return; allowedPaths.add(path.resolve(pathToAdd)); - console.log(`[Security] Added allowed path: ${pathToAdd}`); } /** @@ -341,7 +337,6 @@ ipcMain.handle( // Write image to file await fs.writeFile(imageFilePath, base64Data, "base64"); - console.log("[IPC] Saved image to .automaker/images:", imageFilePath); return { success: true, path: imageFilePath }; } catch (error) { console.error("[IPC] Failed to save image:", error); @@ -640,10 +635,6 @@ ipcMain.handle( ipcMain.handle( "auto-mode:verify-feature", async (_, { projectPath, featureId }) => { - console.log("[IPC] auto-mode:verify-feature called with:", { - projectPath, - featureId, - }); try { const sendToRenderer = (data) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -669,10 +660,6 @@ ipcMain.handle( ipcMain.handle( "auto-mode:resume-feature", async (_, { projectPath, featureId }) => { - console.log("[IPC] auto-mode:resume-feature called with:", { - projectPath, - featureId, - }); try { const sendToRenderer = (data) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -723,7 +710,6 @@ ipcMain.handle( * and update the app_spec.txt with tech stack and implemented features */ ipcMain.handle("auto-mode:analyze-project", async (_, { projectPath }) => { - console.log("[IPC] auto-mode:analyze-project called with:", { projectPath }); try { // Add project path to allowed paths addAllowedPath(projectPath); @@ -748,7 +734,6 @@ ipcMain.handle("auto-mode:analyze-project", async (_, { projectPath }) => { * Stop a specific feature */ ipcMain.handle("auto-mode:stop-feature", async (_, { featureId }) => { - console.log("[IPC] auto-mode:stop-feature called with:", { featureId }); try { return await autoModeService.stopFeature({ featureId }); } catch (error) { @@ -763,12 +748,6 @@ ipcMain.handle("auto-mode:stop-feature", async (_, { featureId }) => { ipcMain.handle( "auto-mode:follow-up-feature", async (_, { projectPath, featureId, prompt, imagePaths }) => { - console.log("[IPC] auto-mode:follow-up-feature called with:", { - projectPath, - featureId, - prompt, - imagePaths, - }); try { const sendToRenderer = (data) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -796,10 +775,6 @@ ipcMain.handle( ipcMain.handle( "auto-mode:commit-feature", async (_, { projectPath, featureId }) => { - console.log("[IPC] auto-mode:commit-feature called with:", { - projectPath, - featureId, - }); try { const sendToRenderer = (data) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -957,8 +932,6 @@ let suggestionsExecution = null; * @param {string} suggestionType - Type of suggestions: "features", "refactoring", "security", "performance" */ ipcMain.handle("suggestions:generate", async (_, { projectPath, suggestionType = "features" }) => { - console.log("[IPC] suggestions:generate called with:", { projectPath, suggestionType }); - try { // Check if already running if (suggestionsExecution && suggestionsExecution.isActive()) { @@ -1008,7 +981,6 @@ ipcMain.handle("suggestions:generate", async (_, { projectPath, suggestionType = * Stop the current suggestions generation */ ipcMain.handle("suggestions:stop", async () => { - console.log("[IPC] suggestions:stop called"); try { if (suggestionsExecution && suggestionsExecution.abortController) { suggestionsExecution.abortController.abort(); @@ -1081,10 +1053,6 @@ ipcMain.handle("openai:test-connection", async (_, { apiKey }) => { ipcMain.handle( "worktree:revert-feature", async (_, { projectPath, featureId }) => { - console.log("[IPC] worktree:revert-feature called with:", { - projectPath, - featureId, - }); try { const sendToRenderer = (data) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -1117,10 +1085,6 @@ let specRegenerationExecution = null; ipcMain.handle( "spec-regeneration:generate", async (_, { projectPath, projectDefinition }) => { - console.log("[IPC] spec-regeneration:generate called with:", { - projectPath, - }); - try { // Add project path to allowed paths addAllowedPath(projectPath); @@ -1182,7 +1146,6 @@ ipcMain.handle( * Stop the current spec regeneration */ ipcMain.handle("spec-regeneration:stop", async () => { - console.log("[IPC] spec-regeneration:stop called"); try { if ( specRegenerationExecution && @@ -1216,11 +1179,6 @@ ipcMain.handle("spec-regeneration:status", () => { ipcMain.handle( "spec-regeneration:create", async (_, { projectPath, projectOverview, generateFeatures = true }) => { - console.log("[IPC] spec-regeneration:create called with:", { - projectPath, - generateFeatures, - }); - try { // Add project path to allowed paths addAllowedPath(projectPath); @@ -1282,11 +1240,6 @@ ipcMain.handle( ipcMain.handle( "worktree:merge-feature", async (_, { projectPath, featureId, options }) => { - console.log("[IPC] worktree:merge-feature called with:", { - projectPath, - featureId, - options, - }); try { const sendToRenderer = (data) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -1412,7 +1365,6 @@ ipcMain.handle("setup:claude-status", async () => { "credentials.json" ); const result = claudeCliDetector.getFullStatus(credentialsPath); - console.log("[IPC] setup:claude-status result:", result); return result; } catch (error) { console.error("[IPC] setup:claude-status error:", error); @@ -1537,7 +1489,6 @@ ipcMain.handle("setup:auth-codex", async (event, { apiKey }) => { */ ipcMain.handle("setup:store-api-key", async (_, { provider, apiKey }) => { try { - console.log("[IPC] setup:store-api-key called for provider:", provider); const configPath = path.join(app.getPath("userData"), "credentials.json"); let credentials = {}; @@ -1559,7 +1510,6 @@ ipcMain.handle("setup:store-api-key", async (_, { provider, apiKey }) => { "utf-8" ); - console.log("[IPC] setup:store-api-key stored successfully for:", provider); return { success: true }; } catch (error) { console.error("[IPC] setup:store-api-key error:", error); diff --git a/app/electron/services/claude-cli-detector.js b/app/electron/services/claude-cli-detector.js index 715754db..10f319cf 100644 --- a/app/electron/services/claude-cli-detector.js +++ b/app/electron/services/claude-cli-detector.js @@ -24,7 +24,6 @@ class ClaudeCliDetector { const shell = process.env.SHELL || "/bin/bash"; const shellName = path.basename(shell); - // Common shell config files const configFiles = []; if (shellName.includes("zsh")) { configFiles.push(path.join(homeDir, ".zshrc")); @@ -36,7 +35,6 @@ class ClaudeCliDetector { configFiles.push(path.join(homeDir, ".profile")); } - // Also check common locations const commonPaths = [ path.join(homeDir, ".local", "bin"), path.join(homeDir, ".cargo", "bin"), @@ -45,12 +43,10 @@ class ClaudeCliDetector { path.join(homeDir, "bin"), ]; - // Try to extract PATH additions from config files for (const configFile of configFiles) { if (fs.existsSync(configFile)) { try { const content = fs.readFileSync(configFile, "utf-8"); - // Look for PATH exports that might include claude installation paths const pathMatches = content.match( /export\s+PATH=["']?([^"'\n]+)["']?/g ); @@ -71,14 +67,12 @@ class ClaudeCliDetector { } } - return [...new Set(commonPaths)]; // Remove duplicates + return [...new Set(commonPaths)]; } static detectClaudeInstallation() { - console.log("[ClaudeCliDetector] Detecting Claude installation..."); - try { - // Method 1: Check if 'claude' command is in PATH (Unix) + // Check if 'claude' command is in PATH (Unix) if (process.platform !== "win32") { try { const claudePath = execSync("which claude 2>/dev/null", { @@ -86,12 +80,6 @@ class ClaudeCliDetector { }).trim(); if (claudePath) { const version = this.getClaudeVersion(claudePath); - console.log( - "[ClaudeCliDetector] Found claude at:", - claudePath, - "version:", - version - ); return { installed: true, path: claudePath, @@ -100,11 +88,11 @@ class ClaudeCliDetector { }; } } catch (error) { - // CLI not in PATH, continue checking other locations + // CLI not in PATH } } - // Method 2: Check Windows path + // Check Windows path if (process.platform === "win32") { try { const claudePath = execSync("where claude 2>nul", { @@ -114,12 +102,6 @@ class ClaudeCliDetector { .split("\n")[0]; if (claudePath) { const version = this.getClaudeVersion(claudePath); - console.log( - "[ClaudeCliDetector] Found claude at:", - claudePath, - "version:", - version - ); return { installed: true, path: claudePath, @@ -132,7 +114,7 @@ class ClaudeCliDetector { } } - // Method 3: Check for local installation + // Check for local installation const localClaudePath = path.join( os.homedir(), ".claude", @@ -141,12 +123,6 @@ class ClaudeCliDetector { ); if (fs.existsSync(localClaudePath)) { const version = this.getClaudeVersion(localClaudePath); - console.log( - "[ClaudeCliDetector] Found local claude at:", - localClaudePath, - "version:", - version - ); return { installed: true, path: localClaudePath, @@ -155,7 +131,7 @@ class ClaudeCliDetector { }; } - // Method 4: Check common installation locations (including those from shell config) + // Check common installation locations const commonPaths = this.getUpdatedPathFromShellConfig(); const binaryNames = ["claude", "claude-code"]; @@ -165,12 +141,6 @@ class ClaudeCliDetector { if (fs.existsSync(claudePath)) { try { const version = this.getClaudeVersion(claudePath); - console.log( - "[ClaudeCliDetector] Found claude at:", - claudePath, - "version:", - version - ); return { installed: true, path: claudePath, @@ -178,13 +148,13 @@ class ClaudeCliDetector { method: "cli", }; } catch (error) { - // File exists but can't get version, might not be executable + // File exists but can't get version } } } } - // Method 5: Try to source shell config and check PATH again (for Unix) + // Try to source shell config and check PATH again (Unix) if (process.platform !== "win32") { try { const shell = process.env.SHELL || "/bin/bash"; @@ -205,12 +175,6 @@ class ClaudeCliDetector { }).trim(); if (claudePath && claudePath.startsWith("/")) { const version = this.getClaudeVersion(claudePath); - console.log( - "[ClaudeCliDetector] Found claude via shell config at:", - claudePath, - "version:", - version - ); return { installed: true, path: claudePath, @@ -220,11 +184,10 @@ class ClaudeCliDetector { } } } catch (error) { - // Failed to source shell config or find claude + // Failed to source shell config } } - console.log("[ClaudeCliDetector] Claude CLI not found"); return { installed: false, path: null, @@ -232,10 +195,6 @@ class ClaudeCliDetector { method: "none", }; } catch (error) { - console.error( - "[ClaudeCliDetector] Error detecting Claude installation:", - error - ); return { installed: false, path: null, @@ -274,12 +233,9 @@ class ClaudeCliDetector { * @returns {Object} Authentication status */ static getAuthStatus(appCredentialsPath) { - console.log("[ClaudeCliDetector] Checking auth status..."); - const envApiKey = process.env.ANTHROPIC_API_KEY; - console.log("[ClaudeCliDetector] Env ANTHROPIC_API_KEY:", !!envApiKey); + const envOAuthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN; - // Check app's stored credentials let storedOAuthToken = null; let storedApiKey = null; @@ -290,51 +246,37 @@ class ClaudeCliDetector { storedOAuthToken = credentials.anthropic_oauth_token || null; storedApiKey = credentials.anthropic || credentials.anthropic_api_key || null; - console.log("[ClaudeCliDetector] App credentials:", { - hasOAuthToken: !!storedOAuthToken, - hasApiKey: !!storedApiKey, - }); } catch (error) { - console.error( - "[ClaudeCliDetector] Error reading app credentials:", - error - ); + // Ignore credential read errors } } - // Determine authentication method - // Priority: Stored OAuth Token > Stored API Key > Env API Key + // Priority: Env OAuth Token > Stored OAuth Token > Stored API Key > Env API Key let authenticated = false; let method = "none"; - if (storedOAuthToken) { + if (envOAuthToken) { + authenticated = true; + method = "oauth_token_env"; + } else if (storedOAuthToken) { authenticated = true; method = "oauth_token"; - console.log( - "[ClaudeCliDetector] Using stored OAuth token (subscription)" - ); } else if (storedApiKey) { authenticated = true; method = "api_key"; - console.log("[ClaudeCliDetector] Using stored API key"); } else if (envApiKey) { authenticated = true; method = "api_key_env"; - console.log("[ClaudeCliDetector] Using environment API key"); - } else { - console.log("[ClaudeCliDetector] No authentication found"); } - const result = { + return { authenticated, method, hasStoredOAuthToken: !!storedOAuthToken, hasStoredApiKey: !!storedApiKey, hasEnvApiKey: !!envApiKey, + hasEnvOAuthToken: !!envOAuthToken, }; - - console.log("[ClaudeCliDetector] Auth status result:", result); - return result; } /** * Get installation info (installation status only, no auth) diff --git a/app/electron/services/codex-cli-detector.js b/app/electron/services/codex-cli-detector.js index 8a2f2a45..d100fd10 100644 --- a/app/electron/services/codex-cli-detector.js +++ b/app/electron/services/codex-cli-detector.js @@ -32,35 +32,28 @@ class CodexCliDetector { * @returns {Object} Authentication status */ static checkAuth() { - console.log('[CodexCliDetector] Checking auth status...'); try { const authPath = this.getAuthPath(); const envApiKey = process.env.OPENAI_API_KEY; - console.log('[CodexCliDetector] Auth path:', authPath); - console.log('[CodexCliDetector] Has env API key:', !!envApiKey); - // First, try to verify authentication using codex CLI command if available + // Try to verify authentication using codex CLI command if available try { const detection = this.detectCodexInstallation(); if (detection.installed) { try { - // Use 'codex login status' to verify authentication - const statusOutput = execSync(`"${detection.path || 'codex'}" login status 2>/dev/null`, { + const statusOutput = execSync(`"${detection.path || 'codex'}" login status 2>/dev/null`, { encoding: 'utf-8', - timeout: 5000 + timeout: 5000 }); - - // If command succeeds and shows logged in status + if (statusOutput && (statusOutput.includes('Logged in') || statusOutput.includes('Authenticated'))) { - const result = { + return { authenticated: true, method: 'cli_verified', hasAuthFile: fs.existsSync(authPath), hasEnvKey: !!envApiKey, authPath }; - console.log('[CodexCliDetector] Auth result (cli_verified):', result); - return result; } } catch (statusError) { // status command failed, continue with file-based check @@ -72,63 +65,47 @@ class CodexCliDetector { // Check if auth file exists if (fs.existsSync(authPath)) { - console.log('[CodexCliDetector] Auth file exists, reading content...'); let auth = null; try { const content = fs.readFileSync(authPath, 'utf-8'); auth = JSON.parse(content); - console.log('[CodexCliDetector] Auth file content keys:', Object.keys(auth)); - console.log('[CodexCliDetector] Auth file has token object:', !!auth.token); - if (auth.token) { - console.log('[CodexCliDetector] Token object keys:', Object.keys(auth.token)); - } - // Check for token object structure (from codex auth login) - // Structure: { token: { Id_token, access_token, refresh_token }, last_refresh: ... } + // Check for token object structure if (auth.token && typeof auth.token === 'object') { const token = auth.token; if (token.Id_token || token.access_token || token.refresh_token || token.id_token) { - const result = { + return { authenticated: true, - method: 'cli_tokens', // Distinguish token-based auth from API key auth + method: 'cli_tokens', hasAuthFile: true, hasEnvKey: !!envApiKey, authPath }; - console.log('[CodexCliDetector] Auth result (cli_tokens):', result); - return result; } } - // Check for tokens at root level (alternative structure) + // Check for tokens at root level if (auth.access_token || auth.refresh_token || auth.Id_token || auth.id_token) { - const result = { + return { authenticated: true, - method: 'cli_tokens', // These are tokens, not API keys + method: 'cli_tokens', hasAuthFile: true, hasEnvKey: !!envApiKey, authPath }; - console.log('[CodexCliDetector] Auth result (cli_tokens - root level):', result); - return result; } - // Check for various possible API key fields that codex might use - // Note: access_token is NOT an API key, it's a token, so we check for it above + // Check for API key fields if (auth.api_key || auth.openai_api_key || auth.apiKey) { - const result = { + return { authenticated: true, method: 'auth_file', hasAuthFile: true, hasEnvKey: !!envApiKey, authPath }; - console.log('[CodexCliDetector] Auth result (auth_file - API key):', result); - return result; } } catch (error) { - console.error('[CodexCliDetector] Error reading/parsing auth file:', error.message); - // If we can't parse the file, we can't determine auth status return { authenticated: false, method: 'none', @@ -138,10 +115,7 @@ class CodexCliDetector { }; } - // Also check if the file has any meaningful content (non-empty object) - // This is a fallback - but we should still try to detect if it's tokens if (!auth) { - // File exists but couldn't be parsed return { authenticated: false, method: 'none', @@ -150,121 +124,32 @@ class CodexCliDetector { authPath }; } - + const keys = Object.keys(auth); - console.log('[CodexCliDetector] File has content, keys:', keys); if (keys.length > 0) { - // Check again for tokens in case we missed them (maybe nested differently) - const hasTokens = keys.some(key => - key.toLowerCase().includes('token') || + const hasTokens = keys.some(key => + key.toLowerCase().includes('token') || key.toLowerCase().includes('refresh') || (auth[key] && typeof auth[key] === 'object' && ( auth[key].access_token || auth[key].refresh_token || auth[key].Id_token || auth[key].id_token )) ); - + if (hasTokens) { - const result = { + return { authenticated: true, method: 'cli_tokens', hasAuthFile: true, hasEnvKey: !!envApiKey, authPath }; - console.log('[CodexCliDetector] Auth result (cli_tokens - fallback detection):', result); - return result; } - - // File exists and has content, likely authenticated - // Try to verify by checking if codex command works - try { - const detection = this.detectCodexInstallation(); - if (detection.installed) { - // Try to verify auth by running a simple command - try { - execSync(`"${detection.path || 'codex'}" --version 2>/dev/null`, { - encoding: 'utf-8', - timeout: 3000 - }); - // If command succeeds, assume authenticated - // But check if it's likely tokens vs API key based on file structure - const likelyTokens = keys.some(key => key.toLowerCase().includes('token') || key.toLowerCase().includes('refresh')); - const result = { - authenticated: true, - method: likelyTokens ? 'cli_tokens' : 'auth_file', - hasAuthFile: true, - hasEnvKey: !!envApiKey, - authPath - }; - console.log('[CodexCliDetector] Auth result (verified via CLI, method:', result.method, '):', result); - return result; - } catch (cmdError) { - // Command failed, but file exists - might still be authenticated - // Check if it's likely tokens - const likelyTokens = keys.some(key => key.toLowerCase().includes('token') || key.toLowerCase().includes('refresh')); - const result = { - authenticated: true, - method: likelyTokens ? 'cli_tokens' : 'auth_file', - hasAuthFile: true, - hasEnvKey: !!envApiKey, - authPath - }; - console.log('[CodexCliDetector] Auth result (file exists, method:', result.method, '):', result); - return result; - } - } - } catch (verifyError) { - // Verification failed, but file exists with content - // Check if it's likely tokens - const likelyTokens = keys.some(key => key.toLowerCase().includes('token') || key.toLowerCase().includes('refresh')); - const result = { - authenticated: true, - method: likelyTokens ? 'cli_tokens' : 'auth_file', - hasAuthFile: true, - hasEnvKey: !!envApiKey, - authPath - }; - console.log('[CodexCliDetector] Auth result (fallback, method:', result.method, '):', result); - return result; - } - } - } - // Check environment variable - if (envApiKey) { - const result = { - authenticated: true, - method: 'env_var', - hasAuthFile: false, - hasEnvKey: true, - authPath - }; - console.log('[CodexCliDetector] Auth result (env_var):', result); - return result; - } - - // If auth file exists but we didn't find standard keys, - // check if codex CLI is installed and try to verify auth - if (fs.existsSync(authPath)) { - try { - const detection = this.detectCodexInstallation(); - if (detection.installed) { - // Auth file exists and CLI is installed - likely authenticated - // The file existing is a good indicator that login was successful - return { - authenticated: true, - method: 'auth_file', - hasAuthFile: true, - hasEnvKey: !!envApiKey, - authPath - }; - } - } catch (verifyError) { - // Verification attempt failed, but file exists - // Assume authenticated if file exists + // File exists and has content - check if it's tokens or API key + const likelyTokens = keys.some(key => key.toLowerCase().includes('token') || key.toLowerCase().includes('refresh')); return { authenticated: true, - method: 'auth_file', + method: likelyTokens ? 'cli_tokens' : 'auth_file', hasAuthFile: true, hasEnvKey: !!envApiKey, authPath @@ -272,24 +157,41 @@ class CodexCliDetector { } } - const result = { + // Check environment variable + if (envApiKey) { + return { + authenticated: true, + method: 'env_var', + hasAuthFile: false, + hasEnvKey: true, + authPath + }; + } + + // If auth file exists, assume authenticated + if (fs.existsSync(authPath)) { + return { + authenticated: true, + method: 'auth_file', + hasAuthFile: true, + hasEnvKey: !!envApiKey, + authPath + }; + } + + return { authenticated: false, method: 'none', hasAuthFile: false, hasEnvKey: false, authPath }; - console.log('[CodexCliDetector] Auth result (not authenticated):', result); - return result; } catch (error) { - console.error('[CodexCliDetector] Error checking auth:', error); - const result = { + return { authenticated: false, method: 'none', error: error.message }; - console.log('[CodexCliDetector] Auth result (error):', result); - return result; } } /** @@ -409,7 +311,7 @@ class CodexCliDetector { method: 'none' }; } catch (error) { - console.error('[CodexCliDetector] Error detecting Codex installation:', error); + // Error detecting Codex installation return { installed: false, path: null, diff --git a/app/src/components/views/analysis-view.tsx b/app/src/components/views/analysis-view.tsx index 4a6d7237..505f237c 100644 --- a/app/src/components/views/analysis-view.tsx +++ b/app/src/components/views/analysis-view.tsx @@ -764,7 +764,13 @@ ${Object.entries(projectAnalysis.filesByExtension) } for (const feature of detectedFeatures) { - await api.features.create(currentProject.path, feature); + await api.features.create(currentProject.path, { + id: crypto.randomUUID(), + category: feature.category, + description: feature.description, + steps: feature.steps, + status: "backlog", + }); } setFeatureListGenerated(true); diff --git a/app/src/components/views/interview-view.tsx b/app/src/components/views/interview-view.tsx index 144be421..fd93bb75 100644 --- a/app/src/components/views/interview-view.tsx +++ b/app/src/components/views/interview-view.tsx @@ -317,7 +317,7 @@ export function InterviewView() { id: `feature-${Date.now()}-0`, category: "Core", description: "Initial project setup", - status: "backlog", + status: "backlog" as const, steps: [ "Step 1: Review app_spec.txt", "Step 2: Set up development environment", @@ -325,7 +325,9 @@ export function InterviewView() { ], skipTests: true, }; - await api.features.create(fullProjectPath, initialFeature); + if (api.features) { + await api.features.create(fullProjectPath, initialFeature); + } const project = { id: `project-${Date.now()}`, diff --git a/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx b/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx index 515db7c7..288c45ef 100644 --- a/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx +++ b/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx @@ -53,41 +53,22 @@ export function AuthenticationStatusDisplay({ <>
- - Method:{" "} - - {claudeAuthStatus.method === "oauth" - ? "OAuth Token" - : claudeAuthStatus.method === "api_key" - ? "API Key" - : "Unknown"} - + Authenticated +
+
+ + + {claudeAuthStatus.method === "oauth_token_env" + ? "Using CLAUDE_CODE_OAUTH_TOKEN" + : claudeAuthStatus.method === "oauth_token" + ? "Using stored OAuth token" + : claudeAuthStatus.method === "api_key_env" + ? "Using ANTHROPIC_API_KEY" + : claudeAuthStatus.method === "api_key" + ? "Using stored API key" + : "Unknown method"}
- {claudeAuthStatus.oauthTokenValid && ( -
- - OAuth token configured -
- )} - {claudeAuthStatus.apiKeyValid && ( -
- - API key configured -
- )} - {apiKeyStatus?.hasAnthropicKey && ( -
- - Environment variable detected -
- )} - {apiKeys.anthropic && ( -
- - Manual API key in settings -
- )} ) : apiKeyStatus?.hasAnthropicKey ? (
@@ -100,9 +81,9 @@ export function AuthenticationStatusDisplay({ Using manual API key from settings
) : ( -
- - Not Setup +
+ + Not configured
)}
@@ -121,44 +102,21 @@ export function AuthenticationStatusDisplay({ <>
- - Method:{" "} - - {codexAuthStatus.method === "cli_verified" || - codexAuthStatus.method === "cli_tokens" - ? "CLI Login (OpenAI Account)" - : codexAuthStatus.method === "api_key" - ? "API Key (Auth File)" - : codexAuthStatus.method === "env" - ? "API Key (Environment)" - : "Unknown"} - + Authenticated +
+
+ + + {codexAuthStatus.method === "cli_verified" || + codexAuthStatus.method === "cli_tokens" + ? "Using CLI login (OpenAI account)" + : codexAuthStatus.method === "api_key" + ? "Using stored API key" + : codexAuthStatus.method === "env" + ? "Using OPENAI_API_KEY" + : "Unknown method"}
- {codexAuthStatus.method === "cli_verified" || - codexAuthStatus.method === "cli_tokens" ? ( -
- - Account authenticated -
- ) : codexAuthStatus.apiKeyValid ? ( -
- - API key configured -
- ) : null} - {apiKeyStatus?.hasOpenAIKey && ( -
- - Environment variable detected -
- )} - {apiKeys.openai && ( -
- - Manual API key in settings -
- )} ) : apiKeyStatus?.hasOpenAIKey ? (
@@ -171,9 +129,9 @@ export function AuthenticationStatusDisplay({ Using manual API key from settings
) : ( -
- - Not Setup +
+ + Not configured
)}
@@ -189,19 +147,31 @@ export function AuthenticationStatusDisplay({
{apiKeyStatus?.hasGoogleKey ? ( -
- - Using environment variable (GOOGLE_API_KEY) -
+ <> +
+ + Authenticated +
+
+ + Using GOOGLE_API_KEY +
+ ) : apiKeys.google ? ( -
- - Using manual API key from settings -
+ <> +
+ + Authenticated +
+
+ + Using stored API key +
+ ) : ( -
- - Not Setup +
+ + Not configured
)}
diff --git a/app/src/components/views/settings-view/components/delete-project-dialog.tsx b/app/src/components/views/settings-view/components/delete-project-dialog.tsx index 0ac5870b..13c0a4e6 100644 --- a/app/src/components/views/settings-view/components/delete-project-dialog.tsx +++ b/app/src/components/views/settings-view/components/delete-project-dialog.tsx @@ -8,7 +8,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; -import type { Project } from "@/store/app-store"; +import type { Project } from "@/lib/electron"; interface DeleteProjectDialogProps { open: boolean; diff --git a/app/src/components/views/settings-view/components/settings-navigation.tsx b/app/src/components/views/settings-view/components/settings-navigation.tsx index 7a90b7ca..b8c362c5 100644 --- a/app/src/components/views/settings-view/components/settings-navigation.tsx +++ b/app/src/components/views/settings-view/components/settings-navigation.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils"; -import type { Project } from "@/store/app-store"; +import type { Project } from "@/lib/electron"; import type { NavigationItem } from "../config/navigation"; interface SettingsNavigationProps { diff --git a/app/src/components/views/settings-view/hooks/use-cli-status.ts b/app/src/components/views/settings-view/hooks/use-cli-status.ts index 078acdbe..0a9e2652 100644 --- a/app/src/components/views/settings-view/hooks/use-cli-status.ts +++ b/app/src/components/views/settings-view/hooks/use-cli-status.ts @@ -69,17 +69,22 @@ export function useCliStatus() { const result = await api.setup.getClaudeStatus(); if (result.success && result.auth) { const auth = result.auth; + // Map the method directly from detector + const methodMap: Record = { + oauth_token_env: "oauth_token_env", + oauth_token: "oauth_token", + api_key: "api_key", + api_key_env: "api_key_env", + none: "none", + }; const authStatus = { authenticated: auth.authenticated, - method: - auth.method === "oauth_token" - ? ("oauth" as const) - : auth.method?.includes("api_key") - ? ("api_key" as const) - : ("none" as const), + method: methodMap[auth.method] || "none", hasCredentialsFile: auth.hasCredentialsFile ?? false, - oauthTokenValid: auth.hasStoredOAuthToken, + oauthTokenValid: auth.hasStoredOAuthToken || auth.hasEnvOAuthToken, apiKeyValid: auth.hasStoredApiKey || auth.hasEnvApiKey, + hasEnvOAuthToken: auth.hasEnvOAuthToken, + hasEnvApiKey: auth.hasEnvApiKey, }; setClaudeAuthStatus(authStatus); } diff --git a/app/src/components/views/setup-view.tsx b/app/src/components/views/setup-view.tsx index cac5e1f1..67bf4f7e 100644 --- a/app/src/components/views/setup-view.tsx +++ b/app/src/components/views/setup-view.tsx @@ -233,19 +233,23 @@ function ClaudeSetupStep({ setClaudeCliStatus(cliStatus); if (result.auth) { + const methodMap: Record = { + oauth_token_env: "oauth_token_env", + oauth_token: "oauth_token", + api_key: "api_key", + api_key_env: "api_key_env", + none: "none", + }; const authStatus = { authenticated: result.auth.authenticated, - method: result.auth.method === "oauth_token" - ? "oauth" - : result.auth.method?.includes("api_key") - ? "api_key" - : "none", + method: methodMap[result.auth.method] || "none", hasCredentialsFile: false, - oauthTokenValid: result.auth.hasStoredOAuthToken, + oauthTokenValid: result.auth.hasStoredOAuthToken || result.auth.hasEnvOAuthToken, apiKeyValid: result.auth.hasStoredApiKey || result.auth.hasEnvApiKey, + hasEnvOAuthToken: result.auth.hasEnvOAuthToken, + hasEnvApiKey: result.auth.hasEnvApiKey, }; - console.log("[Claude Setup] Auth Status:", authStatus); - setClaudeAuthStatus(authStatus as any); + setClaudeAuthStatus(authStatus); } } } @@ -355,7 +359,7 @@ function ClaudeSetupStep({ if (result.success) { setClaudeAuthStatus({ authenticated: true, - method: "oauth", + method: "oauth_token", hasCredentialsFile: false, oauthTokenValid: true, }); @@ -433,8 +437,8 @@ function ClaudeSetupStep({ const getAuthMethodLabel = () => { if (!isAuthenticated) return null; - if (claudeAuthStatus?.method === "oauth") return "Subscription Token"; - if (apiKeys.anthropic || claudeAuthStatus?.method === "api_key") return "API Key"; + if (claudeAuthStatus?.method === "oauth_token_env" || claudeAuthStatus?.method === "oauth_token") return "Subscription Token"; + if (apiKeys.anthropic || claudeAuthStatus?.method === "api_key" || claudeAuthStatus?.method === "api_key_env") return "API Key"; return "Authenticated"; }; diff --git a/app/src/lib/electron.ts b/app/src/lib/electron.ts index 43ae6e9a..9c18b921 100644 --- a/app/src/lib/electron.ts +++ b/app/src/lib/electron.ts @@ -41,8 +41,22 @@ export interface StatResult { error?: string; } -// Auto Mode types - Import from electron.d.ts to avoid duplication +// Auto Mode types - Import from electron.d.ts for internal use import type { + AutoModeEvent as AutoModeEventType, + ModelDefinition as ModelDefinitionType, + ProviderStatus as ProviderStatusType, + WorktreeAPI as WorktreeAPIType, + GitAPI as GitAPIType, + WorktreeInfo as WorktreeInfoType, + WorktreeStatus as WorktreeStatusType, + FileDiffsResult as FileDiffsResultType, + FileDiffResult as FileDiffResultType, + FileStatus as FileStatusType, +} from "@/types/electron"; + +// Re-export types for external use +export type { AutoModeEvent, ModelDefinition, ProviderStatus, @@ -55,6 +69,18 @@ import type { FileStatus, } from "@/types/electron"; +// Type aliases for internal use +type AutoModeEvent = AutoModeEventType; +type ModelDefinition = ModelDefinitionType; +type ProviderStatus = ProviderStatusType; +type WorktreeAPI = WorktreeAPIType; +type GitAPI = GitAPIType; +type WorktreeInfo = WorktreeInfoType; +type WorktreeStatus = WorktreeStatusType; +type FileDiffsResult = FileDiffsResultType; +type FileDiffResult = FileDiffResultType; +type FileStatus = FileStatusType; + // Feature type - Import from app-store import type { Feature } from "@/store/app-store"; @@ -308,17 +334,19 @@ export interface ElectronAPI { getClaudeStatus: () => Promise<{ success: boolean; status?: string; + installed?: boolean; method?: string; version?: string; path?: string; auth?: { authenticated: boolean; method: string; - hasCredentialsFile: boolean; - hasToken: boolean; + hasCredentialsFile?: boolean; + hasToken?: boolean; hasStoredOAuthToken?: boolean; hasStoredApiKey?: boolean; hasEnvApiKey?: boolean; + hasEnvOAuthToken?: boolean; }; error?: string; }>; diff --git a/app/src/lib/project-init.ts b/app/src/lib/project-init.ts index 977a10c4..77bbe590 100644 --- a/app/src/lib/project-init.ts +++ b/app/src/lib/project-init.ts @@ -19,7 +19,10 @@ export interface ProjectInitResult { * Required files and directories in the .automaker directory * Note: app_spec.txt is NOT created automatically - user must set it up via the spec editor */ -const REQUIRED_STRUCTURE = { +const REQUIRED_STRUCTURE: { + directories: string[]; + files: Record; +} = { directories: [ ".automaker", ".automaker/context", diff --git a/app/src/store/setup-store.ts b/app/src/store/setup-store.ts index 8741ba69..8609d339 100644 --- a/app/src/store/setup-store.ts +++ b/app/src/store/setup-store.ts @@ -13,10 +13,12 @@ export interface CliStatus { // Claude Auth Status export interface ClaudeAuthStatus { authenticated: boolean; - method: "oauth" | "api_key" | "none"; - hasCredentialsFile: boolean; + method: "oauth_token_env" | "oauth_token" | "api_key" | "api_key_env" | "none"; + hasCredentialsFile?: boolean; oauthTokenValid?: boolean; apiKeyValid?: boolean; + hasEnvOAuthToken?: boolean; + hasEnvApiKey?: boolean; error?: string; } diff --git a/app/src/types/electron.d.ts b/app/src/types/electron.d.ts index 46ce7e2e..157069ea 100644 --- a/app/src/types/electron.d.ts +++ b/app/src/types/electron.d.ts @@ -345,6 +345,7 @@ export interface AutoModeAPI { export interface ElectronAPI { ping: () => Promise; + openExternalLink: (url: string) => Promise<{ success: boolean; error?: string }>; // Dialog APIs openDirectory: () => Promise<{