diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index e6044823..8fb0a5f6 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -29,5 +29,13 @@ jobs: # optional dependencies (e.g., @tailwindcss/oxide, lightningcss binaries) run: npm install + - name: Install Linux native bindings + # Workaround for npm optional dependencies bug (npm/cli#4828) + # Explicitly install Linux bindings needed for build tools + run: | + npm install --no-save --force \ + @rollup/rollup-linux-x64-gnu@4.53.3 \ + @tailwindcss/oxide-linux-x64-gnu@4.1.17 + - name: Run build:electron run: npm run build:electron diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36d53e37..d39673da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,15 @@ jobs: # optional dependencies (e.g., @tailwindcss/oxide, lightningcss binaries) run: npm install + - name: Install Linux native bindings + # Workaround for npm optional dependencies bug (npm/cli#4828) + # Only needed on Linux - macOS and Windows get their bindings automatically + if: matrix.os == 'ubuntu-latest' + run: | + npm install --no-save --force \ + @rollup/rollup-linux-x64-gnu@4.53.3 \ + @tailwindcss/oxide-linux-x64-gnu@4.1.17 + - name: Extract and set version id: version shell: bash diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..4cdc9c6e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,53 @@ +name: Test Suite + +on: + pull_request: + branches: + - "*" + push: + branches: + - main + - master + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: Install dependencies + # Use npm install instead of npm ci to correctly resolve platform-specific + # optional dependencies (e.g., @tailwindcss/oxide, lightningcss binaries) + run: npm install + + - name: Install Linux native bindings + # Workaround for npm optional dependencies bug (npm/cli#4828) + # Explicitly install Linux bindings needed for build tools + run: | + npm install --no-save --force \ + @rollup/rollup-linux-x64-gnu@4.53.3 \ + @tailwindcss/oxide-linux-x64-gnu@4.1.17 + + - name: Run server tests with coverage + run: npm run test:server:coverage + env: + NODE_ENV: test + + # - name: Upload coverage reports + # uses: codecov/codecov-action@v4 + # if: always() + # with: + # files: ./apps/server/coverage/coverage-final.json + # flags: server + # name: server-coverage + # env: + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index dba6edc7..1d01c7c2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ dist/ .automaker/ /.automaker/* /.automaker/ + +/old \ No newline at end of file diff --git a/.npmrc b/.npmrc index e5c1ee73..86aca125 100644 --- a/.npmrc +++ b/.npmrc @@ -8,3 +8,9 @@ # # In CI/CD: Use "npm install" instead of "npm ci" to allow npm to resolve # the correct platform-specific binaries at install time. + +# Include bindings for all platforms in package-lock.json to support CI/CD +# This ensures Linux, macOS, and Windows bindings are all present +# NOTE: Only enable when regenerating package-lock.json, then comment out to keep installs fast +# supportedArchitectures.os=linux,darwin,win32 +# supportedArchitectures.cpu=x64,arm64 diff --git a/apps/app/.gitignore b/apps/app/.gitignore index cefc9348..cb9812cb 100644 --- a/apps/app/.gitignore +++ b/apps/app/.gitignore @@ -48,3 +48,4 @@ next-env.d.ts # Electron /dist/ +/server-bundle/ diff --git a/apps/app/electron/main.js b/apps/app/electron/main.js index 0fd95d97..63fb2280 100644 --- a/apps/app/electron/main.js +++ b/apps/app/electron/main.js @@ -8,21 +8,114 @@ const path = require("path"); const { spawn } = require("child_process"); const fs = require("fs"); - -// Load environment variables from .env file -require("dotenv").config({ path: path.join(__dirname, "../.env") }); - +const http = require("http"); const { app, BrowserWindow, ipcMain, dialog, shell } = require("electron"); +// Load environment variables from .env file (development only) +if (!app.isPackaged) { + try { + require("dotenv").config({ path: path.join(__dirname, "../.env") }); + } catch (error) { + console.warn("[Electron] dotenv not available:", error.message); + } +} + let mainWindow = null; let serverProcess = null; +let staticServer = null; const SERVER_PORT = 3008; +const STATIC_PORT = 3007; -// Get icon path - works in both dev and production +// Get icon path - works in both dev and production, cross-platform function getIconPath() { - return app.isPackaged - ? path.join(process.resourcesPath, "app", "public", "logo.png") - : path.join(__dirname, "../public/logo.png"); + // Different icon formats for different platforms + let iconFile; + if (process.platform === "win32") { + iconFile = "icon.ico"; + } else if (process.platform === "darwin") { + iconFile = "logo_larger.png"; + } else { + // Linux + iconFile = "logo_larger.png"; + } + + const iconPath = path.join(__dirname, "../public", iconFile); + + // Verify the icon exists + if (!fs.existsSync(iconPath)) { + console.warn(`[Electron] Icon not found at: ${iconPath}`); + return null; + } + + return iconPath; +} + +/** + * Start static file server for production builds + */ +async function startStaticServer() { + const staticPath = path.join(__dirname, "../out"); + + staticServer = http.createServer((request, response) => { + // Parse the URL and remove query string + let filePath = path.join(staticPath, request.url.split("?")[0]); + + // Default to index.html for directory requests + if (filePath.endsWith("/")) { + filePath = path.join(filePath, "index.html"); + } else if (!path.extname(filePath)) { + filePath += ".html"; + } + + // Check if file exists + fs.stat(filePath, (err, stats) => { + if (err || !stats.isFile()) { + // Try index.html for SPA fallback + filePath = path.join(staticPath, "index.html"); + } + + // Read and serve the file + fs.readFile(filePath, (error, content) => { + if (error) { + response.writeHead(500); + response.end("Server Error"); + return; + } + + // Set content type based on file extension + const ext = path.extname(filePath); + const contentTypes = { + ".html": "text/html", + ".js": "application/javascript", + ".css": "text/css", + ".json": "application/json", + ".png": "image/png", + ".jpg": "image/jpeg", + ".gif": "image/gif", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".woff": "font/woff", + ".woff2": "font/woff2", + ".ttf": "font/ttf", + ".eot": "application/vnd.ms-fontobject", + }; + + response.writeHead(200, { "Content-Type": contentTypes[ext] || "application/octet-stream" }); + response.end(content); + }); + }); + }); + + return new Promise((resolve, reject) => { + staticServer.listen(STATIC_PORT, (err) => { + if (err) { + reject(err); + } else { + console.log(`[Electron] Static server running at http://localhost:${STATIC_PORT}`); + resolve(); + } + }); + }); } /** @@ -35,14 +128,18 @@ async function startServer() { let command, args, serverPath; if (isDev) { // In development, use tsx to run TypeScript directly - // Use the node executable that's running Electron - command = process.execPath; // This is the path to node.exe + // Use node from PATH (process.execPath in Electron points to Electron, not Node.js) + // spawn() resolves "node" from PATH on all platforms (Windows, Linux, macOS) + command = "node"; serverPath = path.join(__dirname, "../../server/src/index.ts"); - + // Find tsx CLI - check server node_modules first, then root - const serverNodeModules = path.join(__dirname, "../../server/node_modules/tsx"); + const serverNodeModules = path.join( + __dirname, + "../../server/node_modules/tsx" + ); const rootNodeModules = path.join(__dirname, "../../../node_modules/tsx"); - + let tsxCliPath; if (fs.existsSync(path.join(serverNodeModules, "dist/cli.mjs"))) { tsxCliPath = path.join(serverNodeModules, "dist/cli.mjs"); @@ -51,30 +148,61 @@ async function startServer() { } else { // Last resort: try require.resolve try { - tsxCliPath = require.resolve("tsx/cli.mjs", { paths: [path.join(__dirname, "../../server")] }); + tsxCliPath = require.resolve("tsx/cli.mjs", { + paths: [path.join(__dirname, "../../server")], + }); } catch { - throw new Error("Could not find tsx. Please run 'npm install' in the server directory."); + throw new Error( + "Could not find tsx. Please run 'npm install' in the server directory." + ); } } - + args = [tsxCliPath, "watch", serverPath]; } else { // In production, use compiled JavaScript command = "node"; serverPath = path.join(process.resourcesPath, "server", "index.js"); args = [serverPath]; + + // Verify server files exist + if (!fs.existsSync(serverPath)) { + throw new Error(`Server not found at: ${serverPath}`); + } } // Set environment variables for server + const serverNodeModules = app.isPackaged + ? path.join(process.resourcesPath, "server", "node_modules") + : path.join(__dirname, "../../server/node_modules"); + + // Set default workspace directory to user's Documents/Automaker + const defaultWorkspaceDir = path.join(app.getPath("documents"), "Automaker"); + + // Ensure workspace directory exists + if (!fs.existsSync(defaultWorkspaceDir)) { + try { + fs.mkdirSync(defaultWorkspaceDir, { recursive: true }); + console.log("[Electron] Created workspace directory:", defaultWorkspaceDir); + } catch (error) { + console.error("[Electron] Failed to create workspace directory:", error); + } + } + const env = { ...process.env, PORT: SERVER_PORT.toString(), DATA_DIR: app.getPath("userData"), + NODE_PATH: serverNodeModules, + WORKSPACE_DIR: process.env.WORKSPACE_DIR || defaultWorkspaceDir, }; console.log("[Electron] Starting backend server..."); + console.log("[Electron] Server path:", serverPath); + console.log("[Electron] NODE_PATH:", serverNodeModules); serverProcess = spawn(command, args, { + cwd: path.dirname(serverPath), env, stdio: ["ignore", "pipe", "pipe"], }); @@ -92,6 +220,11 @@ async function startServer() { serverProcess = null; }); + serverProcess.on("error", (err) => { + console.error(`[Server] Failed to start server process:`, err); + serverProcess = null; + }); + // Wait for server to be ready await waitForServer(); } @@ -105,13 +238,16 @@ async function waitForServer(maxAttempts = 30) { for (let i = 0; i < maxAttempts; i++) { try { await new Promise((resolve, reject) => { - const req = http.get(`http://localhost:${SERVER_PORT}/api/health`, (res) => { - if (res.statusCode === 200) { - resolve(); - } else { - reject(new Error(`Status: ${res.statusCode}`)); + const req = http.get( + `http://localhost:${SERVER_PORT}/api/health`, + (res) => { + if (res.statusCode === 200) { + resolve(); + } else { + reject(new Error(`Status: ${res.statusCode}`)); + } } - }); + ); req.on("error", reject); req.setTimeout(1000, () => { req.destroy(); @@ -132,12 +268,12 @@ async function waitForServer(maxAttempts = 30) { * Create the main window */ function createWindow() { - mainWindow = new BrowserWindow({ + const iconPath = getIconPath(); + const windowOptions = { width: 1400, height: 900, minWidth: 1024, minHeight: 700, - icon: getIconPath(), webPreferences: { preload: path.join(__dirname, "preload.js"), contextIsolation: true, @@ -145,17 +281,20 @@ function createWindow() { }, titleBarStyle: "hiddenInset", backgroundColor: "#0a0a0a", - }); + }; - // Load Next.js dev server in development or production build + // Only set icon if it exists + if (iconPath) { + windowOptions.icon = iconPath; + } + + mainWindow = new BrowserWindow(windowOptions); + + // Load Next.js dev server in development or static server in production const isDev = !app.isPackaged; - if (isDev) { - mainWindow.loadURL("http://localhost:3007"); - if (process.env.OPEN_DEVTOOLS === "true") { - mainWindow.webContents.openDevTools(); - } - } else { - mainWindow.loadFile(path.join(__dirname, "../.next/server/app/index.html")); + mainWindow.loadURL(`http://localhost:${STATIC_PORT}`); + if (isDev && process.env.OPEN_DEVTOOLS === "true") { + mainWindow.webContents.openDevTools(); } mainWindow.on("closed", () => { @@ -173,10 +312,22 @@ function createWindow() { app.whenReady().then(async () => { // Set app icon (dock icon on macOS) if (process.platform === "darwin" && app.dock) { - app.dock.setIcon(getIconPath()); + const iconPath = getIconPath(); + if (iconPath) { + try { + app.dock.setIcon(iconPath); + } catch (error) { + console.warn("[Electron] Failed to set dock icon:", error.message); + } + } } try { + // Start static file server in production + if (app.isPackaged) { + await startStaticServer(); + } + // Start backend server await startServer(); @@ -207,6 +358,13 @@ app.on("before-quit", () => { serverProcess.kill(); serverProcess = null; } + + // Close static server + if (staticServer) { + console.log("[Electron] Stopping static server..."); + staticServer.close(); + staticServer = null; + } }); // ============================================ diff --git a/apps/app/next.config.ts b/apps/app/next.config.ts index 6211e3b9..7f34a8f1 100644 --- a/apps/app/next.config.ts +++ b/apps/app/next.config.ts @@ -1,6 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + output: "export", env: { CLAUDE_CODE_OAUTH_TOKEN: process.env.CLAUDE_CODE_OAUTH_TOKEN || "", }, diff --git a/apps/app/package.json b/apps/app/package.json index 98a8b0b4..a5bafda6 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -20,7 +20,11 @@ "dev:electron": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && electron .\"", "dev:electron:debug": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && OPEN_DEVTOOLS=true electron .\"", "build": "next build", - "build:electron": "next build && electron-builder", + "build:electron": "node scripts/prepare-server.js && next build && electron-builder", + "build:electron:win": "node scripts/prepare-server.js && next build && electron-builder --win", + "build:electron:mac": "node scripts/prepare-server.js && next build && electron-builder --mac", + "build:electron:linux": "node scripts/prepare-server.js && next build && electron-builder --linux", + "postinstall": "electron-builder install-app-deps", "start": "next start", "lint": "eslint", "test": "playwright test", @@ -51,7 +55,7 @@ "dotenv": "^17.2.3", "geist": "^1.5.1", "lucide-react": "^0.556.0", - "next": "16.0.7", + "next": "^16.0.10", "react": "19.2.0", "react-dom": "19.2.0", "react-markdown": "^10.1.0", @@ -79,35 +83,46 @@ "@types/react": "^19", "@types/react-dom": "^19", "concurrently": "^9.2.1", - "electron": "^39.2.6", + "electron": "39.2.7", "electron-builder": "^26.0.12", "eslint": "^9", "eslint-config-next": "16.0.7", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", - "typescript": "^5", + "typescript": "5.9.3", "wait-on": "^9.0.3" }, "build": { "appId": "com.automaker.app", "productName": "Automaker", "artifactName": "${productName}-${version}-${arch}.${ext}", + "afterPack": "./scripts/rebuild-server-natives.js", "directories": { "output": "dist" }, "files": [ "electron/**/*", - ".next/**/*", + "out/**/*", "public/**/*", "!node_modules/**/*" ], "extraResources": [ { - "from": ".env", + "from": "server-bundle/dist", + "to": "server" + }, + { + "from": "server-bundle/node_modules", + "to": "server/node_modules" + }, + { + "from": "server-bundle/package.json", + "to": "server/package.json" + }, + { + "from": "../../.env", "to": ".env", - "filter": [ - "**/*" - ] + "filter": ["**/*"] } ], "mac": { @@ -139,7 +154,7 @@ ] } ], - "icon": "public/logo_larger.png" + "icon": "public/icon.ico" }, "linux": { "target": [ diff --git a/apps/app/public/icon.ico b/apps/app/public/icon.ico new file mode 100644 index 00000000..388438d7 Binary files /dev/null and b/apps/app/public/icon.ico differ diff --git a/apps/app/scripts/prepare-server.js b/apps/app/scripts/prepare-server.js new file mode 100644 index 00000000..83c0f055 --- /dev/null +++ b/apps/app/scripts/prepare-server.js @@ -0,0 +1,81 @@ +#!/usr/bin/env node + +/** + * This script prepares the server for bundling with Electron. + * It copies the server dist and installs production dependencies + * in a way that works with npm workspaces. + */ + +import { execSync } from 'child_process'; +import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const APP_DIR = join(__dirname, '..'); +const SERVER_DIR = join(APP_DIR, '..', 'server'); +const BUNDLE_DIR = join(APP_DIR, 'server-bundle'); + +console.log('šŸ”§ Preparing server for Electron bundling...\n'); + +// Step 1: Clean up previous bundle +if (existsSync(BUNDLE_DIR)) { + console.log('šŸ—‘ļø Cleaning previous server-bundle...'); + rmSync(BUNDLE_DIR, { recursive: true }); +} +mkdirSync(BUNDLE_DIR, { recursive: true }); + +// Step 2: Build the server TypeScript +console.log('šŸ“¦ Building server TypeScript...'); +execSync('npm run build', { cwd: SERVER_DIR, stdio: 'inherit' }); + +// Step 3: Copy server dist +console.log('šŸ“‹ Copying server dist...'); +cpSync(join(SERVER_DIR, 'dist'), join(BUNDLE_DIR, 'dist'), { recursive: true }); + +// Step 4: Create a minimal package.json for the server +console.log('šŸ“ Creating server package.json...'); +const serverPkg = JSON.parse(readFileSync(join(SERVER_DIR, 'package.json'), 'utf-8')); + +const bundlePkg = { + name: '@automaker/server-bundle', + version: serverPkg.version, + type: 'module', + main: 'dist/index.js', + dependencies: serverPkg.dependencies +}; + +writeFileSync( + join(BUNDLE_DIR, 'package.json'), + JSON.stringify(bundlePkg, null, 2) +); + +// Step 5: Install production dependencies +console.log('šŸ“„ Installing server production dependencies...'); +execSync('npm install --omit=dev', { + cwd: BUNDLE_DIR, + stdio: 'inherit', + env: { + ...process.env, + // Prevent npm from using workspace resolution + npm_config_workspace: '' + } +}); + +// Step 6: Rebuild native modules for current architecture +// This is critical for modules like node-pty that have native bindings +console.log('šŸ”Ø Rebuilding native modules for current architecture...'); +try { + execSync('npm rebuild', { + cwd: BUNDLE_DIR, + stdio: 'inherit' + }); + console.log('āœ… Native modules rebuilt successfully'); +} catch (error) { + console.warn('āš ļø Warning: Failed to rebuild native modules. Terminal functionality may not work.'); + console.warn(' Error:', error.message); +} + +console.log('\nāœ… Server prepared for bundling at:', BUNDLE_DIR); diff --git a/apps/app/scripts/rebuild-server-natives.js b/apps/app/scripts/rebuild-server-natives.js new file mode 100644 index 00000000..c2eef844 --- /dev/null +++ b/apps/app/scripts/rebuild-server-natives.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +/** + * Electron-builder afterPack hook + * Rebuilds native modules in the server bundle for the target architecture + */ + +const { exec } = require('child_process'); +const { promisify } = require('util'); +const path = require('path'); + +const execAsync = promisify(exec); + +exports.default = async function(context) { + const { appOutDir, electronPlatformName, arch, packager } = context; + const electronVersion = packager.config.electronVersion; + + // Convert arch to string if it's a number (electron-builder sometimes passes indices) + const archNames = ['ia32', 'x64', 'armv7l', 'arm64', 'universal']; + const archStr = typeof arch === 'number' ? archNames[arch] : arch; + + console.log(`\nšŸ”Ø Rebuilding server native modules for ${electronPlatformName}-${archStr}...`); + + // Path to server node_modules in the packaged app + let serverNodeModulesPath; + if (electronPlatformName === 'darwin') { + serverNodeModulesPath = path.join( + appOutDir, + `${packager.appInfo.productName}.app`, + 'Contents', + 'Resources', + 'server', + 'node_modules' + ); + } else if (electronPlatformName === 'win32') { + serverNodeModulesPath = path.join( + appOutDir, + 'resources', + 'server', + 'node_modules' + ); + } else { + serverNodeModulesPath = path.join( + appOutDir, + 'resources', + 'server', + 'node_modules' + ); + } + + try { + // Rebuild native modules for the target architecture + const rebuildCmd = `npx --yes @electron/rebuild --version=${electronVersion} --arch=${archStr} --force --module-dir="${serverNodeModulesPath}/.."`; + + console.log(` Command: ${rebuildCmd}`); + + const { stdout, stderr } = await execAsync(rebuildCmd); + if (stdout) console.log(stdout); + if (stderr) console.error(stderr); + + console.log(`āœ… Server native modules rebuilt successfully for ${archStr}\n`); + } catch (error) { + console.error(`āŒ Failed to rebuild server native modules:`, error.message); + // Don't fail the build, just warn + } +}; diff --git a/apps/app/src/components/dialogs/file-browser-dialog.tsx b/apps/app/src/components/dialogs/file-browser-dialog.tsx index 29c183f1..e16c2a43 100644 --- a/apps/app/src/components/dialogs/file-browser-dialog.tsx +++ b/apps/app/src/components/dialogs/file-browser-dialog.tsx @@ -113,8 +113,8 @@ export function FileBrowserDialog({ return ( - - + + {title} @@ -124,7 +124,7 @@ export function FileBrowserDialog({ -
+
{/* Drives selector (Windows only) */} {drives.length > 0 && (
@@ -216,7 +216,7 @@ export function FileBrowserDialog({
- + diff --git a/apps/app/src/components/layout/sidebar.tsx b/apps/app/src/components/layout/sidebar.tsx index 02b6bbf4..c2a38245 100644 --- a/apps/app/src/components/layout/sidebar.tsx +++ b/apps/app/src/components/layout/sidebar.tsx @@ -238,6 +238,25 @@ export function Sidebar() { // Ref for project search input const projectSearchInputRef = useRef(null); + // Auto-collapse sidebar on small screens + useEffect(() => { + const mediaQuery = window.matchMedia('(max-width: 1024px)'); // lg breakpoint + + const handleResize = () => { + if (mediaQuery.matches && sidebarOpen) { + // Auto-collapse on small screens + toggleSidebar(); + } + }; + + // Check on mount + handleResize(); + + // Listen for changes + mediaQuery.addEventListener('change', handleResize); + return () => mediaQuery.removeEventListener('change', handleResize); + }, [sidebarOpen, toggleSidebar]); + // Filtered projects based on search query const filteredProjects = useMemo(() => { if (!projectSearchQuery.trim()) { diff --git a/apps/app/src/components/new-project-modal.tsx b/apps/app/src/components/new-project-modal.tsx index fd1429de..2e54c8f4 100644 --- a/apps/app/src/components/new-project-modal.tsx +++ b/apps/app/src/components/new-project-modal.tsx @@ -198,7 +198,10 @@ export function NewProjectModal({ } }; - const projectPath = workspaceDir && projectName ? `${workspaceDir}/${projectName}` : ""; + // Use platform-specific path separator + const pathSep = typeof window !== 'undefined' && (window as any).electronAPI ? + (navigator.platform.indexOf('Win') !== -1 ? '\\' : '/') : '/'; + const projectPath = workspaceDir && projectName ? `${workspaceDir}${pathSep}${projectName}` : ""; return ( diff --git a/apps/app/src/components/views/board-view.tsx b/apps/app/src/components/views/board-view.tsx index 12df34e5..5739790f 100644 --- a/apps/app/src/components/views/board-view.tsx +++ b/apps/app/src/components/views/board-view.tsx @@ -121,7 +121,7 @@ type ModelOption = { label: string; description: string; badge?: string; - provider: "claude" | "codex"; + provider: "claude"; }; const CLAUDE_MODELS: ModelOption[] = [ @@ -148,37 +148,6 @@ const CLAUDE_MODELS: ModelOption[] = [ }, ]; -const CODEX_MODELS: ModelOption[] = [ - { - id: "gpt-5.1-codex-max", - label: "GPT-5.1 Codex Max", - description: "Flagship Codex model tuned for deep coding tasks.", - badge: "Flagship", - provider: "codex", - }, - { - id: "gpt-5.1-codex", - label: "GPT-5.1 Codex", - description: "Strong coding performance with lower cost.", - badge: "Standard", - provider: "codex", - }, - { - id: "gpt-5.1-codex-mini", - label: "GPT-5.1 Codex Mini", - description: "Fastest Codex option for lightweight edits.", - badge: "Fast", - provider: "codex", - }, - { - id: "gpt-5.1", - label: "GPT-5.1", - description: "General-purpose reasoning with solid coding ability.", - badge: "General", - provider: "codex", - }, -]; - // Profile icon mapping const PROFILE_ICONS: Record< string, @@ -1693,12 +1662,8 @@ export function BoardView() {
{options.map((option) => { const isSelected = selectedModel === option.id; - const isCodex = option.provider === "codex"; // Shorter display names for compact view - const shortName = option.label - .replace("Claude ", "") - .replace("GPT-5.1 Codex ", "") - .replace("GPT-5.1 ", ""); + const shortName = option.label.replace("Claude ", ""); return ( - ))} -
-
- - {/* Thinking Level - Only for Claude models */} + {/* Thinking Level */} {supportsThinking && (
- setShowAddDialog(true)} - hotkey={shortcuts.addProfile} - hotkeyActive={false} - data-testid="add-profile-button" - > - - New Profile - +
+ + setShowAddDialog(true)} + hotkey={shortcuts.addProfile} + hotkeyActive={false} + data-testid="add-profile-button" + > + + New Profile + +
diff --git a/apps/app/src/components/views/settings-view.tsx b/apps/app/src/components/views/settings-view.tsx index 9b085f2a..7797620f 100644 --- a/apps/app/src/components/views/settings-view.tsx +++ b/apps/app/src/components/views/settings-view.tsx @@ -7,7 +7,6 @@ import { Key, Palette, Terminal, - Atom, FlaskConical, Trash2, Settings2, @@ -24,7 +23,6 @@ import { DeleteProjectDialog } from "./settings-view/components/delete-project-d import { SettingsNavigation } from "./settings-view/components/settings-navigation"; import { ApiKeysSection } from "./settings-view/api-keys/api-keys-section"; import { ClaudeCliStatus } from "./settings-view/cli-status/claude-cli-status"; -import { CodexCliStatus } from "./settings-view/cli-status/codex-cli-status"; import { AppearanceSection } from "./settings-view/appearance/appearance-section"; import { KeyboardShortcutsSection } from "./settings-view/keyboard-shortcuts/keyboard-shortcuts-section"; import { FeatureDefaultsSection } from "./settings-view/feature-defaults/feature-defaults-section"; @@ -39,7 +37,6 @@ import type { Project as ElectronProject } from "@/lib/electron"; const NAV_ITEMS = [ { id: "api-keys", label: "API Keys", icon: Key }, { id: "claude", label: "Claude", icon: Terminal }, - { id: "codex", label: "Codex", icon: Atom }, { id: "appearance", label: "Appearance", icon: Palette }, { id: "audio", label: "Audio", icon: Volume2 }, { id: "keyboard", label: "Keyboard Shortcuts", icon: Settings2 }, @@ -96,11 +93,8 @@ export function SettingsView() { // Use CLI status hook const { claudeCliStatus, - codexCliStatus, isCheckingClaudeCli, - isCheckingCodexCli, handleRefreshClaudeCli, - handleRefreshCodexCli, } = useCliStatus(); // Use scroll tracking hook @@ -147,15 +141,6 @@ export function SettingsView() { /> )} - {/* Codex CLI Status Section */} - {codexCliStatus && ( - - )} - {/* Appearance Section */} diff --git a/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx b/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx index 0c36b2ef..988e62cb 100644 --- a/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx +++ b/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx @@ -4,29 +4,24 @@ import { AlertCircle, Info, Terminal, - Atom, Sparkles, } from "lucide-react"; -import type { ClaudeAuthStatus, CodexAuthStatus } from "@/store/setup-store"; +import type { ClaudeAuthStatus } from "@/store/setup-store"; interface AuthenticationStatusDisplayProps { claudeAuthStatus: ClaudeAuthStatus | null; - codexAuthStatus: CodexAuthStatus | null; apiKeyStatus: { hasAnthropicKey: boolean; - hasOpenAIKey: boolean; hasGoogleKey: boolean; } | null; apiKeys: { anthropic: string; google: string; - openai: string; }; } export function AuthenticationStatusDisplay({ claudeAuthStatus, - codexAuthStatus, apiKeyStatus, apiKeys, }: AuthenticationStatusDisplayProps) { @@ -93,56 +88,6 @@ export function AuthenticationStatusDisplay({ - {/* Codex/OpenAI Authentication Status */} -
-
- - - Codex (OpenAI) - -
-
- {codexAuthStatus?.authenticated ? ( - <> -
- - Authenticated -
-
- - - {codexAuthStatus.method === "subscription" - ? "Using Codex subscription (Plus/Team)" - : 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" - : `Using ${codexAuthStatus.method || "unknown"} authentication`} - -
- - ) : apiKeyStatus?.hasOpenAIKey ? ( -
- - Using environment variable (OPENAI_API_KEY) -
- ) : apiKeys.openai ? ( -
- - Using manual API key from settings -
- ) : ( -
- - Not configured -
- )} -
-
- {/* Google/Gemini Authentication Status */}
diff --git a/apps/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts b/apps/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts index f45939ed..d5478ca0 100644 --- a/apps/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts +++ b/apps/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts @@ -10,7 +10,6 @@ interface TestResult { interface ApiKeyStatus { hasAnthropicKey: boolean; - hasOpenAIKey: boolean; hasGoogleKey: boolean; } @@ -24,12 +23,10 @@ export function useApiKeyManagement() { // API key values const [anthropicKey, setAnthropicKey] = useState(apiKeys.anthropic); const [googleKey, setGoogleKey] = useState(apiKeys.google); - const [openaiKey, setOpenaiKey] = useState(apiKeys.openai); // Visibility toggles const [showAnthropicKey, setShowAnthropicKey] = useState(false); const [showGoogleKey, setShowGoogleKey] = useState(false); - const [showOpenaiKey, setShowOpenaiKey] = useState(false); // Test connection states const [testingConnection, setTestingConnection] = useState(false); @@ -38,10 +35,6 @@ export function useApiKeyManagement() { const [geminiTestResult, setGeminiTestResult] = useState( null ); - const [testingOpenaiConnection, setTestingOpenaiConnection] = useState(false); - const [openaiTestResult, setOpenaiTestResult] = useState( - null - ); // API key status from environment const [apiKeyStatus, setApiKeyStatus] = useState(null); @@ -53,7 +46,6 @@ export function useApiKeyManagement() { useEffect(() => { setAnthropicKey(apiKeys.anthropic); setGoogleKey(apiKeys.google); - setOpenaiKey(apiKeys.openai); }, [apiKeys]); // Check API key status from environment on mount @@ -66,7 +58,6 @@ export function useApiKeyManagement() { if (status.success) { setApiKeyStatus({ hasAnthropicKey: status.hasAnthropicKey, - hasOpenAIKey: status.hasOpenAIKey, hasGoogleKey: status.hasGoogleKey, }); } @@ -152,68 +143,11 @@ export function useApiKeyManagement() { } }; - // Test OpenAI connection - const handleTestOpenaiConnection = async () => { - setTestingOpenaiConnection(true); - setOpenaiTestResult(null); - - try { - const api = getElectronAPI(); - if (api?.testOpenAIConnection) { - const result = await api.testOpenAIConnection(openaiKey); - if (result.success) { - setOpenaiTestResult({ - success: true, - message: - result.message || "Connection successful! OpenAI API responded.", - }); - } else { - setOpenaiTestResult({ - success: false, - message: result.error || "Failed to connect to OpenAI API.", - }); - } - } else { - // Fallback to web API test - const response = await fetch("/api/openai/test", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ apiKey: openaiKey }), - }); - - const data = await response.json(); - - if (response.ok && data.success) { - setOpenaiTestResult({ - success: true, - message: - data.message || "Connection successful! OpenAI API responded.", - }); - } else { - setOpenaiTestResult({ - success: false, - message: data.error || "Failed to connect to OpenAI API.", - }); - } - } - } catch { - setOpenaiTestResult({ - success: false, - message: "Network error. Please check your connection.", - }); - } finally { - setTestingOpenaiConnection(false); - } - }; - // Save API keys const handleSave = () => { setApiKeys({ anthropic: anthropicKey, google: googleKey, - openai: openaiKey, }); setSaved(true); setTimeout(() => setSaved(false), 2000); @@ -240,15 +174,6 @@ export function useApiKeyManagement() { onTest: handleTestGeminiConnection, result: geminiTestResult, }, - openai: { - value: openaiKey, - setValue: setOpenaiKey, - show: showOpenaiKey, - setShow: setShowOpenaiKey, - testing: testingOpenaiConnection, - onTest: handleTestOpenaiConnection, - result: openaiTestResult, - }, }; return { diff --git a/apps/app/src/components/views/settings-view/cli-status/codex-cli-status.tsx b/apps/app/src/components/views/settings-view/cli-status/codex-cli-status.tsx deleted file mode 100644 index 5f0bde25..00000000 --- a/apps/app/src/components/views/settings-view/cli-status/codex-cli-status.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Terminal, - CheckCircle2, - AlertCircle, - RefreshCw, -} from "lucide-react"; -import type { CliStatus } from "../shared/types"; - -interface CliStatusProps { - status: CliStatus | null; - isChecking: boolean; - onRefresh: () => void; -} - -export function CodexCliStatus({ - status, - isChecking, - onRefresh, -}: CliStatusProps) { - if (!status) return null; - - return ( -
-
-
-
- -

- OpenAI Codex CLI -

-
- -
-

- Codex CLI enables GPT-5.1 Codex models for autonomous coding tasks. -

-
-
- {status.success && status.status === "installed" ? ( -
-
- -
-

- Codex CLI Installed -

-
- {status.method && ( -

- Method: {status.method} -

- )} - {status.version && ( -

- Version:{" "} - {status.version} -

- )} - {status.path && ( -

- Path:{" "} - - {status.path} - -

- )} -
-
-
- {status.recommendation && ( -

- {status.recommendation} -

- )} -
- ) : status.status === "api_key_only" ? ( -
-
- -
-

- API Key Detected - CLI Not Installed -

-

- {status.recommendation || - "OPENAI_API_KEY found but Codex CLI not installed. Install the CLI for full agentic capabilities."} -

-
-
- {status.installCommands && ( -
-

- Installation Commands: -

-
- {status.installCommands.npm && ( -
-

npm:

- - {status.installCommands.npm} - -
- )} -
-
- )} -
- ) : ( -
-
- -
-

- Codex CLI Not Detected -

-

- {status.recommendation || - "Install OpenAI Codex CLI to use GPT-5.1 Codex models for autonomous coding."} -

-
-
- {status.installCommands && ( -
-

- Installation Commands: -

-
- {status.installCommands.npm && ( -
-

npm:

- - {status.installCommands.npm} - -
- )} - {status.installCommands.macos && ( -
-

- macOS (Homebrew): -

- - {status.installCommands.macos} - -
- )} -
-
- )} -
- )} -
-
- ); -} diff --git a/apps/app/src/components/views/settings-view/config/navigation.ts b/apps/app/src/components/views/settings-view/config/navigation.ts index c6f5432a..b98a6e66 100644 --- a/apps/app/src/components/views/settings-view/config/navigation.ts +++ b/apps/app/src/components/views/settings-view/config/navigation.ts @@ -2,7 +2,6 @@ import type { LucideIcon } from "lucide-react"; import { Key, Terminal, - Atom, Palette, LayoutGrid, Settings2, @@ -20,7 +19,6 @@ export interface NavigationItem { export const NAV_ITEMS: NavigationItem[] = [ { id: "api-keys", label: "API Keys", icon: Key }, { id: "claude", label: "Claude", icon: Terminal }, - { id: "codex", label: "Codex", icon: Atom }, { id: "appearance", label: "Appearance", icon: Palette }, { id: "kanban", label: "Kanban Display", icon: LayoutGrid }, { id: "keyboard", label: "Keyboard Shortcuts", icon: Settings2 }, diff --git a/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx b/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx index 83d8df35..9a274c60 100644 --- a/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx +++ b/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx @@ -59,7 +59,7 @@ export function FeatureDefaultsSection({

When enabled, the Add Feature dialog will show only AI profiles and hide advanced model tweaking options (Claude SDK, thinking - levels, and OpenAI Codex CLI). This creates a cleaner, less + levels). This creates a cleaner, less overwhelming UI. You can always disable this to access advanced settings.

diff --git a/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts b/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts index 600a5f67..4b65d1ae 100644 --- a/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts +++ b/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts @@ -18,25 +18,17 @@ interface CliStatusResult { error?: string; } -interface CodexCliStatusResult extends CliStatusResult { - hasApiKey?: boolean; -} - /** - * Custom hook for managing Claude and Codex CLI status + * Custom hook for managing Claude CLI status * Handles checking CLI installation, authentication, and refresh functionality */ export function useCliStatus() { - const { setClaudeAuthStatus, setCodexAuthStatus } = useSetupStore(); + const { setClaudeAuthStatus } = useSetupStore(); const [claudeCliStatus, setClaudeCliStatus] = useState(null); - const [codexCliStatus, setCodexCliStatus] = - useState(null); - const [isCheckingClaudeCli, setIsCheckingClaudeCli] = useState(false); - const [isCheckingCodexCli, setIsCheckingCodexCli] = useState(false); // Check CLI status on mount useEffect(() => { @@ -53,16 +45,6 @@ export function useCliStatus() { } } - // Check Codex CLI - if (api?.checkCodexCli) { - try { - const status = await api.checkCodexCli(); - setCodexCliStatus(status); - } catch (error) { - console.error("Failed to check Codex CLI status:", error); - } - } - // Check Claude auth status (re-fetch on mount to ensure persistence) if (api?.setup?.getClaudeStatus) { try { @@ -95,47 +77,10 @@ export function useCliStatus() { console.error("Failed to check Claude auth status:", error); } } - - // Check Codex auth status (re-fetch on mount to ensure persistence) - if (api?.setup?.getCodexStatus) { - try { - const result = await api.setup.getCodexStatus(); - if (result.success && result.auth) { - // Cast to extended type that includes server-added fields - const auth = result.auth as typeof result.auth & { - hasSubscription?: boolean; - cliLoggedIn?: boolean; - hasEnvApiKey?: boolean; - }; - // Map server method names to client method types - // Server returns: subscription, cli_verified, cli_tokens, api_key, env, none - const validMethods = ["subscription", "cli_verified", "cli_tokens", "api_key", "env", "none"] as const; - type CodexMethod = typeof validMethods[number]; - const method: CodexMethod = validMethods.includes(auth.method as CodexMethod) - ? (auth.method as CodexMethod) - : auth.authenticated ? "api_key" : "none"; // Default authenticated to api_key - - const authStatus = { - authenticated: auth.authenticated, - method, - // Only set apiKeyValid for actual API key methods, not CLI login or subscription - apiKeyValid: - method === "cli_verified" || method === "cli_tokens" || method === "subscription" - ? undefined - : auth.hasAuthFile || auth.hasEnvKey || auth.hasEnvApiKey, - hasSubscription: auth.hasSubscription, - cliLoggedIn: auth.cliLoggedIn, - }; - setCodexAuthStatus(authStatus); - } - } catch (error) { - console.error("Failed to check Codex auth status:", error); - } - } }; checkCliStatus(); - }, [setClaudeAuthStatus, setCodexAuthStatus]); + }, [setClaudeAuthStatus]); // Refresh Claude CLI status const handleRefreshClaudeCli = useCallback(async () => { @@ -153,28 +98,9 @@ export function useCliStatus() { } }, []); - // Refresh Codex CLI status - const handleRefreshCodexCli = useCallback(async () => { - setIsCheckingCodexCli(true); - try { - const api = getElectronAPI(); - if (api?.checkCodexCli) { - const status = await api.checkCodexCli(); - setCodexCliStatus(status); - } - } catch (error) { - console.error("Failed to refresh Codex CLI status:", error); - } finally { - setIsCheckingCodexCli(false); - } - }, []); - return { claudeCliStatus, - codexCliStatus, isCheckingClaudeCli, - isCheckingCodexCli, handleRefreshClaudeCli, - handleRefreshCodexCli, }; } diff --git a/apps/app/src/components/views/setup-view.tsx b/apps/app/src/components/views/setup-view.tsx index 331dbc0a..cf5cb7d3 100644 --- a/apps/app/src/components/views/setup-view.tsx +++ b/apps/app/src/components/views/setup-view.tsx @@ -7,7 +7,6 @@ import { WelcomeStep, CompleteStep, ClaudeSetupStep, - CodexSetupStep, } from "./setup-view/steps"; // Main Setup View @@ -17,17 +16,14 @@ export function SetupView() { setCurrentStep, completeSetup, setSkipClaudeSetup, - setSkipCodexSetup, } = useSetupStore(); const { setCurrentView } = useAppStore(); - const steps = ["welcome", "claude", "codex", "complete"] as const; + const steps = ["welcome", "claude", "complete"] as const; type StepName = (typeof steps)[number]; const getStepName = (): StepName => { if (currentStep === "claude_detect" || currentStep === "claude_auth") return "claude"; - if (currentStep === "codex_detect" || currentStep === "codex_auth") - return "codex"; if (currentStep === "welcome") return "welcome"; return "complete"; }; @@ -46,10 +42,6 @@ export function SetupView() { setCurrentStep("claude_detect"); break; case "claude": - console.log("[Setup Flow] Moving to codex_detect step"); - setCurrentStep("codex_detect"); - break; - case "codex": console.log("[Setup Flow] Moving to complete step"); setCurrentStep("complete"); break; @@ -62,21 +54,12 @@ export function SetupView() { case "claude": setCurrentStep("welcome"); break; - case "codex": - setCurrentStep("claude_detect"); - break; } }; const handleSkipClaude = () => { console.log("[Setup Flow] Skipping Claude setup"); setSkipClaudeSetup(true); - setCurrentStep("codex_detect"); - }; - - const handleSkipCodex = () => { - console.log("[Setup Flow] Skipping Codex setup"); - setSkipCodexSetup(true); setCurrentStep("complete"); }; @@ -127,15 +110,6 @@ export function SetupView() { /> )} - {(currentStep === "codex_detect" || - currentStep === "codex_auth") && ( - handleNext("codex")} - onBack={() => handleBack("codex")} - onSkip={handleSkipCodex} - /> - )} - {currentStep === "complete" && ( )} diff --git a/apps/app/src/components/views/setup-view/hooks/use-cli-installation.ts b/apps/app/src/components/views/setup-view/hooks/use-cli-installation.ts index cc9c8bba..e406d871 100644 --- a/apps/app/src/components/views/setup-view/hooks/use-cli-installation.ts +++ b/apps/app/src/components/views/setup-view/hooks/use-cli-installation.ts @@ -2,7 +2,7 @@ import { useState, useCallback } from "react"; import { toast } from "sonner"; interface UseCliInstallationOptions { - cliType: "claude" | "codex"; + cliType: "claude"; installApi: () => Promise; onProgressEvent?: (callback: (progress: any) => void) => (() => void) | undefined; onSuccess?: () => void; diff --git a/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts b/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts index 1aa0d094..4249b95c 100644 --- a/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts +++ b/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts @@ -1,7 +1,7 @@ import { useState, useCallback } from "react"; interface UseCliStatusOptions { - cliType: "claude" | "codex"; + cliType: "claude"; statusApi: () => Promise; setCliStatus: (status: any) => void; setAuthStatus: (status: any) => void; @@ -33,65 +33,35 @@ export function useCliStatus({ setCliStatus(cliStatus); if (result.auth) { - if (cliType === "claude") { - // Validate method is one of the expected values, default to "none" - const validMethods = [ - "oauth_token_env", - "oauth_token", - "api_key", - "api_key_env", - "credentials_file", - "cli_authenticated", - "none", - ] as const; - type AuthMethod = (typeof validMethods)[number]; - const method: AuthMethod = validMethods.includes( - result.auth.method as AuthMethod - ) - ? (result.auth.method as AuthMethod) - : "none"; - const authStatus = { - authenticated: result.auth.authenticated, - method, - hasCredentialsFile: false, - oauthTokenValid: - result.auth.hasStoredOAuthToken || - result.auth.hasEnvOAuthToken, - apiKeyValid: - result.auth.hasStoredApiKey || result.auth.hasEnvApiKey, - hasEnvOAuthToken: result.auth.hasEnvOAuthToken, - hasEnvApiKey: result.auth.hasEnvApiKey, - }; - setAuthStatus(authStatus); - } else { - // Codex auth status mapping - const mapAuthMethod = (method?: string): any => { - switch (method) { - case "cli_verified": - return "cli_verified"; - case "cli_tokens": - return "cli_tokens"; - case "auth_file": - return "api_key"; - case "env_var": - return "env"; - default: - return "none"; - } - }; - - const method = mapAuthMethod(result.auth.method); - const authStatus = { - authenticated: result.auth.authenticated, - method, - apiKeyValid: - method === "cli_verified" || method === "cli_tokens" - ? undefined - : result.auth.authenticated, - }; - console.log(`[${cliType} Setup] Auth Status:`, authStatus); - setAuthStatus(authStatus); - } + // Validate method is one of the expected values, default to "none" + const validMethods = [ + "oauth_token_env", + "oauth_token", + "api_key", + "api_key_env", + "credentials_file", + "cli_authenticated", + "none", + ] as const; + type AuthMethod = (typeof validMethods)[number]; + const method: AuthMethod = validMethods.includes( + result.auth.method as AuthMethod + ) + ? (result.auth.method as AuthMethod) + : "none"; + const authStatus = { + authenticated: result.auth.authenticated, + method, + hasCredentialsFile: false, + oauthTokenValid: + result.auth.hasStoredOAuthToken || + result.auth.hasEnvOAuthToken, + apiKeyValid: + result.auth.hasStoredApiKey || result.auth.hasEnvApiKey, + hasEnvOAuthToken: result.auth.hasEnvOAuthToken, + hasEnvApiKey: result.auth.hasEnvApiKey, + }; + setAuthStatus(authStatus); } } } catch (error) { diff --git a/apps/app/src/components/views/setup-view/hooks/use-oauth-authentication.ts b/apps/app/src/components/views/setup-view/hooks/use-oauth-authentication.ts index 4648231e..f1b8957f 100644 --- a/apps/app/src/components/views/setup-view/hooks/use-oauth-authentication.ts +++ b/apps/app/src/components/views/setup-view/hooks/use-oauth-authentication.ts @@ -4,7 +4,7 @@ import { getElectronAPI } from "@/lib/electron"; type AuthState = "idle" | "running" | "success" | "error" | "manual"; interface UseOAuthAuthenticationOptions { - cliType: "claude" | "codex"; + cliType: "claude"; enabled?: boolean; } @@ -70,11 +70,8 @@ export function useOAuthAuthentication({ } try { - // Call the appropriate auth API based on cliType - const result = - cliType === "claude" - ? await api.setup.authClaude() - : await api.setup.authCodex?.(); + // Call the auth API + const result = await api.setup.authClaude(); // Cleanup subscription if (unsubscribeRef.current) { diff --git a/apps/app/src/components/views/setup-view/steps/codex-setup-step.tsx b/apps/app/src/components/views/setup-view/steps/codex-setup-step.tsx deleted file mode 100644 index 13335e02..00000000 --- a/apps/app/src/components/views/setup-view/steps/codex-setup-step.tsx +++ /dev/null @@ -1,445 +0,0 @@ -"use client"; - -import { useState, useEffect, useCallback } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { useSetupStore } from "@/store/setup-store"; -import { useAppStore } from "@/store/app-store"; -import { getElectronAPI } from "@/lib/electron"; -import { - CheckCircle2, - Loader2, - Terminal, - Key, - ArrowRight, - ArrowLeft, - ExternalLink, - Copy, - AlertCircle, - RefreshCw, - Download, -} from "lucide-react"; -import { toast } from "sonner"; -import { StatusBadge, TerminalOutput } from "../components"; -import { - useCliStatus, - useCliInstallation, - useTokenSave, -} from "../hooks"; - -interface CodexSetupStepProps { - onNext: () => void; - onBack: () => void; - onSkip: () => void; -} - -export function CodexSetupStep({ - onNext, - onBack, - onSkip, -}: CodexSetupStepProps) { - const { - codexCliStatus, - codexAuthStatus, - setCodexCliStatus, - setCodexAuthStatus, - setCodexInstallProgress, - } = useSetupStore(); - const { setApiKeys, apiKeys } = useAppStore(); - - const [showApiKeyInput, setShowApiKeyInput] = useState(false); - const [apiKey, setApiKey] = useState(""); - - // Memoize API functions to prevent infinite loops - const statusApi = useCallback( - () => getElectronAPI().setup?.getCodexStatus() || Promise.reject(), - [] - ); - - const installApi = useCallback( - () => getElectronAPI().setup?.installCodex() || Promise.reject(), - [] - ); - - // Use custom hooks - const { isChecking, checkStatus } = useCliStatus({ - cliType: "codex", - statusApi, - setCliStatus: setCodexCliStatus, - setAuthStatus: setCodexAuthStatus, - }); - - const onInstallSuccess = useCallback(() => { - checkStatus(); - }, [checkStatus]); - - const { isInstalling, installProgress, install } = useCliInstallation({ - cliType: "codex", - installApi, - onProgressEvent: getElectronAPI().setup?.onInstallProgress, - onSuccess: onInstallSuccess, - }); - - const { isSaving: isSavingKey, saveToken: saveApiKeyToken } = useTokenSave({ - provider: "openai", - onSuccess: () => { - setCodexAuthStatus({ - authenticated: true, - method: "api_key", - apiKeyValid: true, - }); - setApiKeys({ ...apiKeys, openai: apiKey }); - setShowApiKeyInput(false); - checkStatus(); - }, - }); - - // Sync install progress to store - useEffect(() => { - setCodexInstallProgress({ - isInstalling, - output: installProgress.output, - }); - }, [isInstalling, installProgress, setCodexInstallProgress]); - - // Check status on mount - useEffect(() => { - checkStatus(); - }, [checkStatus]); - - const copyCommand = (command: string) => { - navigator.clipboard.writeText(command); - toast.success("Command copied to clipboard"); - }; - - const isAuthenticated = codexAuthStatus?.authenticated || apiKeys.openai; - - const getAuthMethodLabel = () => { - if (!isAuthenticated) return null; - if (apiKeys.openai) return "API Key (Manual)"; - if (codexAuthStatus?.method === "api_key") return "API Key (Auth File)"; - if (codexAuthStatus?.method === "env") return "API Key (Environment)"; - if (codexAuthStatus?.method === "cli_verified") - return "CLI Login (ChatGPT)"; - return "Authenticated"; - }; - - return ( -
-
-
- -
-

- Codex CLI Setup -

-

- OpenAI's GPT-5.1 Codex for advanced code generation -

-
- - {/* Status Card */} - - -
- Installation Status - -
-
- -
- CLI Installation - {isChecking ? ( - - ) : codexCliStatus?.installed ? ( - - ) : ( - - )} -
- - {codexCliStatus?.version && ( -
- Version - - {codexCliStatus.version} - -
- )} - -
- Authentication - {isAuthenticated ? ( -
- - {getAuthMethodLabel() && ( - - ({getAuthMethodLabel()}) - - )} -
- ) : ( - - )} -
-
-
- - {/* Installation Section */} - {!codexCliStatus?.installed && ( - - - - - Install Codex CLI - - - Install via npm (Node.js required) - - - -
- -
- - npm install -g @openai/codex - - -
-
- - {isInstalling && ( - - )} - -
- -
- -
-
- -

- Requires Node.js to be installed. If the auto-install fails, - try running the command manually in your terminal. -

-
-
-
-
- )} - - {/* Authentication Section */} - {!isAuthenticated && ( - - - - - Authentication - - Codex requires an OpenAI API key - - - {codexCliStatus?.installed && ( -
-
- -
-

- Authenticate via CLI -

-

- Run this command in your terminal: -

-
- - codex auth login - - -
-
-
-
- )} - -
-
- -
-
- - or enter API key - -
-
- - {showApiKeyInput ? ( -
-
- - setApiKey(e.target.value)} - className="bg-input border-border text-foreground" - data-testid="openai-api-key-input" - /> -

- Get your API key from{" "} - - platform.openai.com - - -

-
-
- - -
-
- ) : ( - - )} -
-
- )} - - {/* Success State */} - {isAuthenticated && ( - - -
-
- -
-
-

- Codex is ready to use! -

-

- {getAuthMethodLabel() && - `Authenticated via ${getAuthMethodLabel()}. `} - You can proceed to complete setup -

-
-
-
-
- )} - - {/* Navigation */} -
- -
- - -
-
-
- ); -} diff --git a/apps/app/src/components/views/setup-view/steps/complete-step.tsx b/apps/app/src/components/views/setup-view/steps/complete-step.tsx index 447c6465..bcffebc1 100644 --- a/apps/app/src/components/views/setup-view/steps/complete-step.tsx +++ b/apps/app/src/components/views/setup-view/steps/complete-step.tsx @@ -14,16 +14,13 @@ interface CompleteStepProps { } export function CompleteStep({ onFinish }: CompleteStepProps) { - const { claudeCliStatus, claudeAuthStatus, codexCliStatus, codexAuthStatus } = + const { claudeCliStatus, claudeAuthStatus } = useSetupStore(); const { apiKeys } = useAppStore(); const claudeReady = (claudeCliStatus?.installed && claudeAuthStatus?.authenticated) || apiKeys.anthropic; - const codexReady = - (codexCliStatus?.installed && codexAuthStatus?.authenticated) || - apiKeys.openai; return (
@@ -41,7 +38,7 @@ export function CompleteStep({ onFinish }: CompleteStepProps) {

-
+
- - - -
- {codexReady ? ( - - ) : ( - - )} -
-

Codex

-

- {codexReady ? "Ready to use" : "Configure later in settings"} -

-
-
-
-
diff --git a/apps/app/src/components/views/setup-view/steps/index.ts b/apps/app/src/components/views/setup-view/steps/index.ts index 5fa3a01c..4ad4a782 100644 --- a/apps/app/src/components/views/setup-view/steps/index.ts +++ b/apps/app/src/components/views/setup-view/steps/index.ts @@ -2,4 +2,3 @@ export { WelcomeStep } from "./welcome-step"; export { CompleteStep } from "./complete-step"; export { ClaudeSetupStep } from "./claude-setup-step"; -export { CodexSetupStep } from "./codex-setup-step"; diff --git a/apps/app/src/components/views/setup-view/steps/welcome-step.tsx b/apps/app/src/components/views/setup-view/steps/welcome-step.tsx index 1c7e945b..a9116daa 100644 --- a/apps/app/src/components/views/setup-view/steps/welcome-step.tsx +++ b/apps/app/src/components/views/setup-view/steps/welcome-step.tsx @@ -24,7 +24,7 @@ export function WelcomeStep({ onNext }: WelcomeStepProps) {

-
+
@@ -40,19 +40,6 @@ export function WelcomeStep({ onNext }: WelcomeStepProps) { - - - - - Codex CLI - - - -

- OpenAI's GPT-5.1 Codex for advanced code generation tasks -

-
-