fix: improve auth status display and remove verbose console logging

- 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 <noreply@anthropic.com>
This commit is contained in:
SuperComboGamer
2025-12-10 22:28:27 -05:00
parent 41f14167a6
commit d474208e8b
16 changed files with 232 additions and 419 deletions

View File

@@ -20,54 +20,54 @@ Automaker is an autonomous AI development studio that helps you build software f
## Getting Started ## 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 ```bash
# 1. Clone the repo
git clone git@github.com:AutoMaker-Org/automaker.git git clone git@github.com:AutoMaker-Org/automaker.git
cd automaker cd automaker
```
**Step 2:** Install dependencies: # 2. Install dependencies
```bash
npm install npm install
```
**Step 3:** Run the Claude Code setup token command: # 3. Get your Claude Code OAuth token
```bash
claude setup-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. # 4. Set the token and run
export CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-..."
**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
npm run dev:electron 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 ```bash
echo "W" export CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-your-token-here"
echo "W"
echo "W"
echo "W"
echo "W"
echo "W"
echo "W"
echo "W"
``` ```
Then restart your terminal or run `source ~/.bashrc`.
## Features ## Features
- 📋 **Kanban Board** - Visual drag-and-drop board to manage features through backlog, in progress, waiting approval, and verified stages - 📋 **Kanban Board** - Visual drag-and-drop board to manage features through backlog, in progress, waiting approval, and verified stages

View File

@@ -19,14 +19,12 @@ class AgentService {
this.stateDir = path.join(appDataPath, "agent-sessions"); this.stateDir = path.join(appDataPath, "agent-sessions");
this.metadataFile = path.join(appDataPath, "sessions-metadata.json"); this.metadataFile = path.join(appDataPath, "sessions-metadata.json");
await fs.mkdir(this.stateDir, { recursive: true }); await fs.mkdir(this.stateDir, { recursive: true });
console.log("[AgentService] Initialized with state dir:", this.stateDir);
} }
/** /**
* Start or resume a conversation * Start or resume a conversation
*/ */
async startConversation({ sessionId, workingDirectory }) { async startConversation({ sessionId, workingDirectory }) {
console.log("[AgentService] Starting conversation:", sessionId);
// Initialize session if it doesn't exist // Initialize session if it doesn't exist
if (!this.sessions.has(sessionId)) { if (!this.sessions.has(sessionId)) {
@@ -307,7 +305,7 @@ class AgentService {
}; };
} catch (error) { } catch (error) {
if (error instanceof AbortError || error?.name === "AbortError") { if (error instanceof AbortError || error?.name === "AbortError") {
console.log("[AgentService] Query aborted"); // Query aborted
session.isRunning = false; session.isRunning = false;
session.abortController = null; session.abortController = null;
return { success: false, aborted: true }; return { success: false, aborted: true };

View File

@@ -70,9 +70,6 @@ app.whenReady().then(async () => {
addAllowedPath(session.projectPath); addAllowedPath(session.projectPath);
} }
}); });
console.log(
`[Security] Pre-loaded ${allowedPaths.size} allowed paths from history`
);
} catch (error) { } catch (error) {
console.error("Failed to load sessions for security whitelist:", error); console.error("Failed to load sessions for security whitelist:", error);
} }
@@ -101,7 +98,6 @@ const allowedPaths = new Set();
function addAllowedPath(pathToAdd) { function addAllowedPath(pathToAdd) {
if (!pathToAdd) return; if (!pathToAdd) return;
allowedPaths.add(path.resolve(pathToAdd)); allowedPaths.add(path.resolve(pathToAdd));
console.log(`[Security] Added allowed path: ${pathToAdd}`);
} }
/** /**
@@ -341,7 +337,6 @@ ipcMain.handle(
// Write image to file // Write image to file
await fs.writeFile(imageFilePath, base64Data, "base64"); await fs.writeFile(imageFilePath, base64Data, "base64");
console.log("[IPC] Saved image to .automaker/images:", imageFilePath);
return { success: true, path: imageFilePath }; return { success: true, path: imageFilePath };
} catch (error) { } catch (error) {
console.error("[IPC] Failed to save image:", error); console.error("[IPC] Failed to save image:", error);
@@ -640,10 +635,6 @@ ipcMain.handle(
ipcMain.handle( ipcMain.handle(
"auto-mode:verify-feature", "auto-mode:verify-feature",
async (_, { projectPath, featureId }) => { async (_, { projectPath, featureId }) => {
console.log("[IPC] auto-mode:verify-feature called with:", {
projectPath,
featureId,
});
try { try {
const sendToRenderer = (data) => { const sendToRenderer = (data) => {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@@ -669,10 +660,6 @@ ipcMain.handle(
ipcMain.handle( ipcMain.handle(
"auto-mode:resume-feature", "auto-mode:resume-feature",
async (_, { projectPath, featureId }) => { async (_, { projectPath, featureId }) => {
console.log("[IPC] auto-mode:resume-feature called with:", {
projectPath,
featureId,
});
try { try {
const sendToRenderer = (data) => { const sendToRenderer = (data) => {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@@ -723,7 +710,6 @@ ipcMain.handle(
* and update the app_spec.txt with tech stack and implemented features * and update the app_spec.txt with tech stack and implemented features
*/ */
ipcMain.handle("auto-mode:analyze-project", async (_, { projectPath }) => { ipcMain.handle("auto-mode:analyze-project", async (_, { projectPath }) => {
console.log("[IPC] auto-mode:analyze-project called with:", { projectPath });
try { try {
// Add project path to allowed paths // Add project path to allowed paths
addAllowedPath(projectPath); addAllowedPath(projectPath);
@@ -748,7 +734,6 @@ ipcMain.handle("auto-mode:analyze-project", async (_, { projectPath }) => {
* Stop a specific feature * Stop a specific feature
*/ */
ipcMain.handle("auto-mode:stop-feature", async (_, { featureId }) => { ipcMain.handle("auto-mode:stop-feature", async (_, { featureId }) => {
console.log("[IPC] auto-mode:stop-feature called with:", { featureId });
try { try {
return await autoModeService.stopFeature({ featureId }); return await autoModeService.stopFeature({ featureId });
} catch (error) { } catch (error) {
@@ -763,12 +748,6 @@ ipcMain.handle("auto-mode:stop-feature", async (_, { featureId }) => {
ipcMain.handle( ipcMain.handle(
"auto-mode:follow-up-feature", "auto-mode:follow-up-feature",
async (_, { projectPath, featureId, prompt, imagePaths }) => { async (_, { projectPath, featureId, prompt, imagePaths }) => {
console.log("[IPC] auto-mode:follow-up-feature called with:", {
projectPath,
featureId,
prompt,
imagePaths,
});
try { try {
const sendToRenderer = (data) => { const sendToRenderer = (data) => {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@@ -796,10 +775,6 @@ ipcMain.handle(
ipcMain.handle( ipcMain.handle(
"auto-mode:commit-feature", "auto-mode:commit-feature",
async (_, { projectPath, featureId }) => { async (_, { projectPath, featureId }) => {
console.log("[IPC] auto-mode:commit-feature called with:", {
projectPath,
featureId,
});
try { try {
const sendToRenderer = (data) => { const sendToRenderer = (data) => {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@@ -957,8 +932,6 @@ let suggestionsExecution = null;
* @param {string} suggestionType - Type of suggestions: "features", "refactoring", "security", "performance" * @param {string} suggestionType - Type of suggestions: "features", "refactoring", "security", "performance"
*/ */
ipcMain.handle("suggestions:generate", async (_, { projectPath, suggestionType = "features" }) => { ipcMain.handle("suggestions:generate", async (_, { projectPath, suggestionType = "features" }) => {
console.log("[IPC] suggestions:generate called with:", { projectPath, suggestionType });
try { try {
// Check if already running // Check if already running
if (suggestionsExecution && suggestionsExecution.isActive()) { if (suggestionsExecution && suggestionsExecution.isActive()) {
@@ -1008,7 +981,6 @@ ipcMain.handle("suggestions:generate", async (_, { projectPath, suggestionType =
* Stop the current suggestions generation * Stop the current suggestions generation
*/ */
ipcMain.handle("suggestions:stop", async () => { ipcMain.handle("suggestions:stop", async () => {
console.log("[IPC] suggestions:stop called");
try { try {
if (suggestionsExecution && suggestionsExecution.abortController) { if (suggestionsExecution && suggestionsExecution.abortController) {
suggestionsExecution.abortController.abort(); suggestionsExecution.abortController.abort();
@@ -1081,10 +1053,6 @@ ipcMain.handle("openai:test-connection", async (_, { apiKey }) => {
ipcMain.handle( ipcMain.handle(
"worktree:revert-feature", "worktree:revert-feature",
async (_, { projectPath, featureId }) => { async (_, { projectPath, featureId }) => {
console.log("[IPC] worktree:revert-feature called with:", {
projectPath,
featureId,
});
try { try {
const sendToRenderer = (data) => { const sendToRenderer = (data) => {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@@ -1117,10 +1085,6 @@ let specRegenerationExecution = null;
ipcMain.handle( ipcMain.handle(
"spec-regeneration:generate", "spec-regeneration:generate",
async (_, { projectPath, projectDefinition }) => { async (_, { projectPath, projectDefinition }) => {
console.log("[IPC] spec-regeneration:generate called with:", {
projectPath,
});
try { try {
// Add project path to allowed paths // Add project path to allowed paths
addAllowedPath(projectPath); addAllowedPath(projectPath);
@@ -1182,7 +1146,6 @@ ipcMain.handle(
* Stop the current spec regeneration * Stop the current spec regeneration
*/ */
ipcMain.handle("spec-regeneration:stop", async () => { ipcMain.handle("spec-regeneration:stop", async () => {
console.log("[IPC] spec-regeneration:stop called");
try { try {
if ( if (
specRegenerationExecution && specRegenerationExecution &&
@@ -1216,11 +1179,6 @@ ipcMain.handle("spec-regeneration:status", () => {
ipcMain.handle( ipcMain.handle(
"spec-regeneration:create", "spec-regeneration:create",
async (_, { projectPath, projectOverview, generateFeatures = true }) => { async (_, { projectPath, projectOverview, generateFeatures = true }) => {
console.log("[IPC] spec-regeneration:create called with:", {
projectPath,
generateFeatures,
});
try { try {
// Add project path to allowed paths // Add project path to allowed paths
addAllowedPath(projectPath); addAllowedPath(projectPath);
@@ -1282,11 +1240,6 @@ ipcMain.handle(
ipcMain.handle( ipcMain.handle(
"worktree:merge-feature", "worktree:merge-feature",
async (_, { projectPath, featureId, options }) => { async (_, { projectPath, featureId, options }) => {
console.log("[IPC] worktree:merge-feature called with:", {
projectPath,
featureId,
options,
});
try { try {
const sendToRenderer = (data) => { const sendToRenderer = (data) => {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@@ -1412,7 +1365,6 @@ ipcMain.handle("setup:claude-status", async () => {
"credentials.json" "credentials.json"
); );
const result = claudeCliDetector.getFullStatus(credentialsPath); const result = claudeCliDetector.getFullStatus(credentialsPath);
console.log("[IPC] setup:claude-status result:", result);
return result; return result;
} catch (error) { } catch (error) {
console.error("[IPC] setup:claude-status error:", 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 }) => { ipcMain.handle("setup:store-api-key", async (_, { provider, apiKey }) => {
try { try {
console.log("[IPC] setup:store-api-key called for provider:", provider);
const configPath = path.join(app.getPath("userData"), "credentials.json"); const configPath = path.join(app.getPath("userData"), "credentials.json");
let credentials = {}; let credentials = {};
@@ -1559,7 +1510,6 @@ ipcMain.handle("setup:store-api-key", async (_, { provider, apiKey }) => {
"utf-8" "utf-8"
); );
console.log("[IPC] setup:store-api-key stored successfully for:", provider);
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
console.error("[IPC] setup:store-api-key error:", error); console.error("[IPC] setup:store-api-key error:", error);

View File

@@ -24,7 +24,6 @@ class ClaudeCliDetector {
const shell = process.env.SHELL || "/bin/bash"; const shell = process.env.SHELL || "/bin/bash";
const shellName = path.basename(shell); const shellName = path.basename(shell);
// Common shell config files
const configFiles = []; const configFiles = [];
if (shellName.includes("zsh")) { if (shellName.includes("zsh")) {
configFiles.push(path.join(homeDir, ".zshrc")); configFiles.push(path.join(homeDir, ".zshrc"));
@@ -36,7 +35,6 @@ class ClaudeCliDetector {
configFiles.push(path.join(homeDir, ".profile")); configFiles.push(path.join(homeDir, ".profile"));
} }
// Also check common locations
const commonPaths = [ const commonPaths = [
path.join(homeDir, ".local", "bin"), path.join(homeDir, ".local", "bin"),
path.join(homeDir, ".cargo", "bin"), path.join(homeDir, ".cargo", "bin"),
@@ -45,12 +43,10 @@ class ClaudeCliDetector {
path.join(homeDir, "bin"), path.join(homeDir, "bin"),
]; ];
// Try to extract PATH additions from config files
for (const configFile of configFiles) { for (const configFile of configFiles) {
if (fs.existsSync(configFile)) { if (fs.existsSync(configFile)) {
try { try {
const content = fs.readFileSync(configFile, "utf-8"); const content = fs.readFileSync(configFile, "utf-8");
// Look for PATH exports that might include claude installation paths
const pathMatches = content.match( const pathMatches = content.match(
/export\s+PATH=["']?([^"'\n]+)["']?/g /export\s+PATH=["']?([^"'\n]+)["']?/g
); );
@@ -71,14 +67,12 @@ class ClaudeCliDetector {
} }
} }
return [...new Set(commonPaths)]; // Remove duplicates return [...new Set(commonPaths)];
} }
static detectClaudeInstallation() { static detectClaudeInstallation() {
console.log("[ClaudeCliDetector] Detecting Claude installation...");
try { try {
// Method 1: Check if 'claude' command is in PATH (Unix) // Check if 'claude' command is in PATH (Unix)
if (process.platform !== "win32") { if (process.platform !== "win32") {
try { try {
const claudePath = execSync("which claude 2>/dev/null", { const claudePath = execSync("which claude 2>/dev/null", {
@@ -86,12 +80,6 @@ class ClaudeCliDetector {
}).trim(); }).trim();
if (claudePath) { if (claudePath) {
const version = this.getClaudeVersion(claudePath); const version = this.getClaudeVersion(claudePath);
console.log(
"[ClaudeCliDetector] Found claude at:",
claudePath,
"version:",
version
);
return { return {
installed: true, installed: true,
path: claudePath, path: claudePath,
@@ -100,11 +88,11 @@ class ClaudeCliDetector {
}; };
} }
} catch (error) { } 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") { if (process.platform === "win32") {
try { try {
const claudePath = execSync("where claude 2>nul", { const claudePath = execSync("where claude 2>nul", {
@@ -114,12 +102,6 @@ class ClaudeCliDetector {
.split("\n")[0]; .split("\n")[0];
if (claudePath) { if (claudePath) {
const version = this.getClaudeVersion(claudePath); const version = this.getClaudeVersion(claudePath);
console.log(
"[ClaudeCliDetector] Found claude at:",
claudePath,
"version:",
version
);
return { return {
installed: true, installed: true,
path: claudePath, path: claudePath,
@@ -132,7 +114,7 @@ class ClaudeCliDetector {
} }
} }
// Method 3: Check for local installation // Check for local installation
const localClaudePath = path.join( const localClaudePath = path.join(
os.homedir(), os.homedir(),
".claude", ".claude",
@@ -141,12 +123,6 @@ class ClaudeCliDetector {
); );
if (fs.existsSync(localClaudePath)) { if (fs.existsSync(localClaudePath)) {
const version = this.getClaudeVersion(localClaudePath); const version = this.getClaudeVersion(localClaudePath);
console.log(
"[ClaudeCliDetector] Found local claude at:",
localClaudePath,
"version:",
version
);
return { return {
installed: true, installed: true,
path: localClaudePath, 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 commonPaths = this.getUpdatedPathFromShellConfig();
const binaryNames = ["claude", "claude-code"]; const binaryNames = ["claude", "claude-code"];
@@ -165,12 +141,6 @@ class ClaudeCliDetector {
if (fs.existsSync(claudePath)) { if (fs.existsSync(claudePath)) {
try { try {
const version = this.getClaudeVersion(claudePath); const version = this.getClaudeVersion(claudePath);
console.log(
"[ClaudeCliDetector] Found claude at:",
claudePath,
"version:",
version
);
return { return {
installed: true, installed: true,
path: claudePath, path: claudePath,
@@ -178,13 +148,13 @@ class ClaudeCliDetector {
method: "cli", method: "cli",
}; };
} catch (error) { } 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") { if (process.platform !== "win32") {
try { try {
const shell = process.env.SHELL || "/bin/bash"; const shell = process.env.SHELL || "/bin/bash";
@@ -205,12 +175,6 @@ class ClaudeCliDetector {
}).trim(); }).trim();
if (claudePath && claudePath.startsWith("/")) { if (claudePath && claudePath.startsWith("/")) {
const version = this.getClaudeVersion(claudePath); const version = this.getClaudeVersion(claudePath);
console.log(
"[ClaudeCliDetector] Found claude via shell config at:",
claudePath,
"version:",
version
);
return { return {
installed: true, installed: true,
path: claudePath, path: claudePath,
@@ -220,11 +184,10 @@ class ClaudeCliDetector {
} }
} }
} catch (error) { } catch (error) {
// Failed to source shell config or find claude // Failed to source shell config
} }
} }
console.log("[ClaudeCliDetector] Claude CLI not found");
return { return {
installed: false, installed: false,
path: null, path: null,
@@ -232,10 +195,6 @@ class ClaudeCliDetector {
method: "none", method: "none",
}; };
} catch (error) { } catch (error) {
console.error(
"[ClaudeCliDetector] Error detecting Claude installation:",
error
);
return { return {
installed: false, installed: false,
path: null, path: null,
@@ -274,12 +233,9 @@ class ClaudeCliDetector {
* @returns {Object} Authentication status * @returns {Object} Authentication status
*/ */
static getAuthStatus(appCredentialsPath) { static getAuthStatus(appCredentialsPath) {
console.log("[ClaudeCliDetector] Checking auth status...");
const envApiKey = process.env.ANTHROPIC_API_KEY; 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 storedOAuthToken = null;
let storedApiKey = null; let storedApiKey = null;
@@ -290,51 +246,37 @@ class ClaudeCliDetector {
storedOAuthToken = credentials.anthropic_oauth_token || null; storedOAuthToken = credentials.anthropic_oauth_token || null;
storedApiKey = storedApiKey =
credentials.anthropic || credentials.anthropic_api_key || null; credentials.anthropic || credentials.anthropic_api_key || null;
console.log("[ClaudeCliDetector] App credentials:", {
hasOAuthToken: !!storedOAuthToken,
hasApiKey: !!storedApiKey,
});
} catch (error) { } catch (error) {
console.error( // Ignore credential read errors
"[ClaudeCliDetector] Error reading app credentials:",
error
);
} }
} }
// Determine authentication method // Priority: Env OAuth Token > Stored OAuth Token > Stored API Key > Env API Key
// Priority: Stored OAuth Token > Stored API Key > Env API Key
let authenticated = false; let authenticated = false;
let method = "none"; let method = "none";
if (storedOAuthToken) { if (envOAuthToken) {
authenticated = true;
method = "oauth_token_env";
} else if (storedOAuthToken) {
authenticated = true; authenticated = true;
method = "oauth_token"; method = "oauth_token";
console.log(
"[ClaudeCliDetector] Using stored OAuth token (subscription)"
);
} else if (storedApiKey) { } else if (storedApiKey) {
authenticated = true; authenticated = true;
method = "api_key"; method = "api_key";
console.log("[ClaudeCliDetector] Using stored API key");
} else if (envApiKey) { } else if (envApiKey) {
authenticated = true; authenticated = true;
method = "api_key_env"; method = "api_key_env";
console.log("[ClaudeCliDetector] Using environment API key");
} else {
console.log("[ClaudeCliDetector] No authentication found");
} }
const result = { return {
authenticated, authenticated,
method, method,
hasStoredOAuthToken: !!storedOAuthToken, hasStoredOAuthToken: !!storedOAuthToken,
hasStoredApiKey: !!storedApiKey, hasStoredApiKey: !!storedApiKey,
hasEnvApiKey: !!envApiKey, hasEnvApiKey: !!envApiKey,
hasEnvOAuthToken: !!envOAuthToken,
}; };
console.log("[ClaudeCliDetector] Auth status result:", result);
return result;
} }
/** /**
* Get installation info (installation status only, no auth) * Get installation info (installation status only, no auth)

View File

@@ -32,35 +32,28 @@ class CodexCliDetector {
* @returns {Object} Authentication status * @returns {Object} Authentication status
*/ */
static checkAuth() { static checkAuth() {
console.log('[CodexCliDetector] Checking auth status...');
try { try {
const authPath = this.getAuthPath(); const authPath = this.getAuthPath();
const envApiKey = process.env.OPENAI_API_KEY; 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 { try {
const detection = this.detectCodexInstallation(); const detection = this.detectCodexInstallation();
if (detection.installed) { if (detection.installed) {
try { 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', encoding: 'utf-8',
timeout: 5000 timeout: 5000
}); });
// If command succeeds and shows logged in status
if (statusOutput && (statusOutput.includes('Logged in') || statusOutput.includes('Authenticated'))) { if (statusOutput && (statusOutput.includes('Logged in') || statusOutput.includes('Authenticated'))) {
const result = { return {
authenticated: true, authenticated: true,
method: 'cli_verified', method: 'cli_verified',
hasAuthFile: fs.existsSync(authPath), hasAuthFile: fs.existsSync(authPath),
hasEnvKey: !!envApiKey, hasEnvKey: !!envApiKey,
authPath authPath
}; };
console.log('[CodexCliDetector] Auth result (cli_verified):', result);
return result;
} }
} catch (statusError) { } catch (statusError) {
// status command failed, continue with file-based check // status command failed, continue with file-based check
@@ -72,63 +65,47 @@ class CodexCliDetector {
// Check if auth file exists // Check if auth file exists
if (fs.existsSync(authPath)) { if (fs.existsSync(authPath)) {
console.log('[CodexCliDetector] Auth file exists, reading content...');
let auth = null; let auth = null;
try { try {
const content = fs.readFileSync(authPath, 'utf-8'); const content = fs.readFileSync(authPath, 'utf-8');
auth = JSON.parse(content); 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) // Check for token object structure
// Structure: { token: { Id_token, access_token, refresh_token }, last_refresh: ... }
if (auth.token && typeof auth.token === 'object') { if (auth.token && typeof auth.token === 'object') {
const token = auth.token; const token = auth.token;
if (token.Id_token || token.access_token || token.refresh_token || token.id_token) { if (token.Id_token || token.access_token || token.refresh_token || token.id_token) {
const result = { return {
authenticated: true, authenticated: true,
method: 'cli_tokens', // Distinguish token-based auth from API key auth method: 'cli_tokens',
hasAuthFile: true, hasAuthFile: true,
hasEnvKey: !!envApiKey, hasEnvKey: !!envApiKey,
authPath 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) { if (auth.access_token || auth.refresh_token || auth.Id_token || auth.id_token) {
const result = { return {
authenticated: true, authenticated: true,
method: 'cli_tokens', // These are tokens, not API keys method: 'cli_tokens',
hasAuthFile: true, hasAuthFile: true,
hasEnvKey: !!envApiKey, hasEnvKey: !!envApiKey,
authPath authPath
}; };
console.log('[CodexCliDetector] Auth result (cli_tokens - root level):', result);
return result;
} }
// Check for various possible API key fields that codex might use // Check for API key fields
// Note: access_token is NOT an API key, it's a token, so we check for it above
if (auth.api_key || auth.openai_api_key || auth.apiKey) { if (auth.api_key || auth.openai_api_key || auth.apiKey) {
const result = { return {
authenticated: true, authenticated: true,
method: 'auth_file', method: 'auth_file',
hasAuthFile: true, hasAuthFile: true,
hasEnvKey: !!envApiKey, hasEnvKey: !!envApiKey,
authPath authPath
}; };
console.log('[CodexCliDetector] Auth result (auth_file - API key):', result);
return result;
} }
} catch (error) { } 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 { return {
authenticated: false, authenticated: false,
method: 'none', 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) { if (!auth) {
// File exists but couldn't be parsed
return { return {
authenticated: false, authenticated: false,
method: 'none', method: 'none',
@@ -150,121 +124,32 @@ class CodexCliDetector {
authPath authPath
}; };
} }
const keys = Object.keys(auth); const keys = Object.keys(auth);
console.log('[CodexCliDetector] File has content, keys:', keys);
if (keys.length > 0) { if (keys.length > 0) {
// Check again for tokens in case we missed them (maybe nested differently) const hasTokens = keys.some(key =>
const hasTokens = keys.some(key => key.toLowerCase().includes('token') ||
key.toLowerCase().includes('token') ||
key.toLowerCase().includes('refresh') || key.toLowerCase().includes('refresh') ||
(auth[key] && typeof auth[key] === 'object' && ( (auth[key] && typeof auth[key] === 'object' && (
auth[key].access_token || auth[key].refresh_token || auth[key].Id_token || auth[key].id_token auth[key].access_token || auth[key].refresh_token || auth[key].Id_token || auth[key].id_token
)) ))
); );
if (hasTokens) { if (hasTokens) {
const result = { return {
authenticated: true, authenticated: true,
method: 'cli_tokens', method: 'cli_tokens',
hasAuthFile: true, hasAuthFile: true,
hasEnvKey: !!envApiKey, hasEnvKey: !!envApiKey,
authPath 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 // File exists and has content - check if it's tokens or API key
if (envApiKey) { const likelyTokens = keys.some(key => key.toLowerCase().includes('token') || key.toLowerCase().includes('refresh'));
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
return { return {
authenticated: true, authenticated: true,
method: 'auth_file', method: likelyTokens ? 'cli_tokens' : 'auth_file',
hasAuthFile: true, hasAuthFile: true,
hasEnvKey: !!envApiKey, hasEnvKey: !!envApiKey,
authPath 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, authenticated: false,
method: 'none', method: 'none',
hasAuthFile: false, hasAuthFile: false,
hasEnvKey: false, hasEnvKey: false,
authPath authPath
}; };
console.log('[CodexCliDetector] Auth result (not authenticated):', result);
return result;
} catch (error) { } catch (error) {
console.error('[CodexCliDetector] Error checking auth:', error); return {
const result = {
authenticated: false, authenticated: false,
method: 'none', method: 'none',
error: error.message error: error.message
}; };
console.log('[CodexCliDetector] Auth result (error):', result);
return result;
} }
} }
/** /**
@@ -409,7 +311,7 @@ class CodexCliDetector {
method: 'none' method: 'none'
}; };
} catch (error) { } catch (error) {
console.error('[CodexCliDetector] Error detecting Codex installation:', error); // Error detecting Codex installation
return { return {
installed: false, installed: false,
path: null, path: null,

View File

@@ -764,7 +764,13 @@ ${Object.entries(projectAnalysis.filesByExtension)
} }
for (const feature of detectedFeatures) { 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); setFeatureListGenerated(true);

View File

@@ -317,7 +317,7 @@ export function InterviewView() {
id: `feature-${Date.now()}-0`, id: `feature-${Date.now()}-0`,
category: "Core", category: "Core",
description: "Initial project setup", description: "Initial project setup",
status: "backlog", status: "backlog" as const,
steps: [ steps: [
"Step 1: Review app_spec.txt", "Step 1: Review app_spec.txt",
"Step 2: Set up development environment", "Step 2: Set up development environment",
@@ -325,7 +325,9 @@ export function InterviewView() {
], ],
skipTests: true, skipTests: true,
}; };
await api.features.create(fullProjectPath, initialFeature); if (api.features) {
await api.features.create(fullProjectPath, initialFeature);
}
const project = { const project = {
id: `project-${Date.now()}`, id: `project-${Date.now()}`,

View File

@@ -53,41 +53,22 @@ export function AuthenticationStatusDisplay({
<> <>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" /> <CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
<span className="text-muted-foreground"> <span className="text-green-400 font-medium">Authenticated</span>
Method:{" "} </div>
<span className="font-mono text-foreground"> <div className="flex items-center gap-2 text-muted-foreground">
{claudeAuthStatus.method === "oauth" <Info className="w-3 h-3 shrink-0" />
? "OAuth Token" <span>
: claudeAuthStatus.method === "api_key" {claudeAuthStatus.method === "oauth_token_env"
? "API Key" ? "Using CLAUDE_CODE_OAUTH_TOKEN"
: "Unknown"} : claudeAuthStatus.method === "oauth_token"
</span> ? "Using stored OAuth token"
: claudeAuthStatus.method === "api_key_env"
? "Using ANTHROPIC_API_KEY"
: claudeAuthStatus.method === "api_key"
? "Using stored API key"
: "Unknown method"}
</span> </span>
</div> </div>
{claudeAuthStatus.oauthTokenValid && (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>OAuth token configured</span>
</div>
)}
{claudeAuthStatus.apiKeyValid && (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>API key configured</span>
</div>
)}
{apiKeyStatus?.hasAnthropicKey && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Environment variable detected</span>
</div>
)}
{apiKeys.anthropic && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Manual API key in settings</span>
</div>
)}
</> </>
) : apiKeyStatus?.hasAnthropicKey ? ( ) : apiKeyStatus?.hasAnthropicKey ? (
<div className="flex items-center gap-2 text-blue-400"> <div className="flex items-center gap-2 text-blue-400">
@@ -100,9 +81,9 @@ export function AuthenticationStatusDisplay({
<span>Using manual API key from settings</span> <span>Using manual API key from settings</span>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-1.5 text-muted-foreground py-0.5"> <div className="flex items-center gap-1.5 text-yellow-500 py-0.5">
<AlertCircle className="w-2.5 h-2.5 shrink-0" /> <AlertCircle className="w-3 h-3 shrink-0" />
<span className="text-xs">Not Setup</span> <span className="text-xs">Not configured</span>
</div> </div>
)} )}
</div> </div>
@@ -121,44 +102,21 @@ export function AuthenticationStatusDisplay({
<> <>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" /> <CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
<span className="text-muted-foreground"> <span className="text-green-400 font-medium">Authenticated</span>
Method:{" "} </div>
<span className="font-mono text-foreground"> <div className="flex items-center gap-2 text-muted-foreground">
{codexAuthStatus.method === "cli_verified" || <Info className="w-3 h-3 shrink-0" />
codexAuthStatus.method === "cli_tokens" <span>
? "CLI Login (OpenAI Account)" {codexAuthStatus.method === "cli_verified" ||
: codexAuthStatus.method === "api_key" codexAuthStatus.method === "cli_tokens"
? "API Key (Auth File)" ? "Using CLI login (OpenAI account)"
: codexAuthStatus.method === "env" : codexAuthStatus.method === "api_key"
? "API Key (Environment)" ? "Using stored API key"
: "Unknown"} : codexAuthStatus.method === "env"
</span> ? "Using OPENAI_API_KEY"
: "Unknown method"}
</span> </span>
</div> </div>
{codexAuthStatus.method === "cli_verified" ||
codexAuthStatus.method === "cli_tokens" ? (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>Account authenticated</span>
</div>
) : codexAuthStatus.apiKeyValid ? (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>API key configured</span>
</div>
) : null}
{apiKeyStatus?.hasOpenAIKey && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Environment variable detected</span>
</div>
)}
{apiKeys.openai && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Manual API key in settings</span>
</div>
)}
</> </>
) : apiKeyStatus?.hasOpenAIKey ? ( ) : apiKeyStatus?.hasOpenAIKey ? (
<div className="flex items-center gap-2 text-blue-400"> <div className="flex items-center gap-2 text-blue-400">
@@ -171,9 +129,9 @@ export function AuthenticationStatusDisplay({
<span>Using manual API key from settings</span> <span>Using manual API key from settings</span>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-1.5 text-muted-foreground py-0.5"> <div className="flex items-center gap-1.5 text-yellow-500 py-0.5">
<AlertCircle className="w-2.5 h-2.5 shrink-0" /> <AlertCircle className="w-3 h-3 shrink-0" />
<span className="text-xs">Not Setup</span> <span className="text-xs">Not configured</span>
</div> </div>
)} )}
</div> </div>
@@ -189,19 +147,31 @@ export function AuthenticationStatusDisplay({
</div> </div>
<div className="space-y-1.5 text-xs min-h-12"> <div className="space-y-1.5 text-xs min-h-12">
{apiKeyStatus?.hasGoogleKey ? ( {apiKeyStatus?.hasGoogleKey ? (
<div className="flex items-center gap-2 text-blue-400"> <>
<Info className="w-3 h-3 shrink-0" /> <div className="flex items-center gap-2">
<span>Using environment variable (GOOGLE_API_KEY)</span> <CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
</div> <span className="text-green-400 font-medium">Authenticated</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<Info className="w-3 h-3 shrink-0" />
<span>Using GOOGLE_API_KEY</span>
</div>
</>
) : apiKeys.google ? ( ) : apiKeys.google ? (
<div className="flex items-center gap-2 text-blue-400"> <>
<Info className="w-3 h-3 shrink-0" /> <div className="flex items-center gap-2">
<span>Using manual API key from settings</span> <CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
</div> <span className="text-green-400 font-medium">Authenticated</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<Info className="w-3 h-3 shrink-0" />
<span>Using stored API key</span>
</div>
</>
) : ( ) : (
<div className="flex items-center gap-1.5 text-muted-foreground py-0.5"> <div className="flex items-center gap-1.5 text-yellow-500 py-0.5">
<AlertCircle className="w-2.5 h-2.5 shrink-0" /> <AlertCircle className="w-3 h-3 shrink-0" />
<span className="text-xs">Not Setup</span> <span className="text-xs">Not configured</span>
</div> </div>
)} )}
</div> </div>

View File

@@ -8,7 +8,7 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import type { Project } from "@/store/app-store"; import type { Project } from "@/lib/electron";
interface DeleteProjectDialogProps { interface DeleteProjectDialogProps {
open: boolean; open: boolean;

View File

@@ -1,5 +1,5 @@
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { Project } from "@/store/app-store"; import type { Project } from "@/lib/electron";
import type { NavigationItem } from "../config/navigation"; import type { NavigationItem } from "../config/navigation";
interface SettingsNavigationProps { interface SettingsNavigationProps {

View File

@@ -69,17 +69,22 @@ export function useCliStatus() {
const result = await api.setup.getClaudeStatus(); const result = await api.setup.getClaudeStatus();
if (result.success && result.auth) { if (result.success && result.auth) {
const auth = result.auth; const auth = result.auth;
// Map the method directly from detector
const methodMap: Record<string, "oauth_token_env" | "oauth_token" | "api_key" | "api_key_env" | "none"> = {
oauth_token_env: "oauth_token_env",
oauth_token: "oauth_token",
api_key: "api_key",
api_key_env: "api_key_env",
none: "none",
};
const authStatus = { const authStatus = {
authenticated: auth.authenticated, authenticated: auth.authenticated,
method: method: methodMap[auth.method] || "none",
auth.method === "oauth_token"
? ("oauth" as const)
: auth.method?.includes("api_key")
? ("api_key" as const)
: ("none" as const),
hasCredentialsFile: auth.hasCredentialsFile ?? false, hasCredentialsFile: auth.hasCredentialsFile ?? false,
oauthTokenValid: auth.hasStoredOAuthToken, oauthTokenValid: auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
apiKeyValid: auth.hasStoredApiKey || auth.hasEnvApiKey, apiKeyValid: auth.hasStoredApiKey || auth.hasEnvApiKey,
hasEnvOAuthToken: auth.hasEnvOAuthToken,
hasEnvApiKey: auth.hasEnvApiKey,
}; };
setClaudeAuthStatus(authStatus); setClaudeAuthStatus(authStatus);
} }

View File

@@ -233,19 +233,23 @@ function ClaudeSetupStep({
setClaudeCliStatus(cliStatus); setClaudeCliStatus(cliStatus);
if (result.auth) { if (result.auth) {
const methodMap: Record<string, "oauth_token_env" | "oauth_token" | "api_key" | "api_key_env" | "none"> = {
oauth_token_env: "oauth_token_env",
oauth_token: "oauth_token",
api_key: "api_key",
api_key_env: "api_key_env",
none: "none",
};
const authStatus = { const authStatus = {
authenticated: result.auth.authenticated, authenticated: result.auth.authenticated,
method: result.auth.method === "oauth_token" method: methodMap[result.auth.method] || "none",
? "oauth"
: result.auth.method?.includes("api_key")
? "api_key"
: "none",
hasCredentialsFile: false, hasCredentialsFile: false,
oauthTokenValid: result.auth.hasStoredOAuthToken, oauthTokenValid: result.auth.hasStoredOAuthToken || result.auth.hasEnvOAuthToken,
apiKeyValid: result.auth.hasStoredApiKey || result.auth.hasEnvApiKey, apiKeyValid: result.auth.hasStoredApiKey || result.auth.hasEnvApiKey,
hasEnvOAuthToken: result.auth.hasEnvOAuthToken,
hasEnvApiKey: result.auth.hasEnvApiKey,
}; };
console.log("[Claude Setup] Auth Status:", authStatus); setClaudeAuthStatus(authStatus);
setClaudeAuthStatus(authStatus as any);
} }
} }
} }
@@ -355,7 +359,7 @@ function ClaudeSetupStep({
if (result.success) { if (result.success) {
setClaudeAuthStatus({ setClaudeAuthStatus({
authenticated: true, authenticated: true,
method: "oauth", method: "oauth_token",
hasCredentialsFile: false, hasCredentialsFile: false,
oauthTokenValid: true, oauthTokenValid: true,
}); });
@@ -433,8 +437,8 @@ function ClaudeSetupStep({
const getAuthMethodLabel = () => { const getAuthMethodLabel = () => {
if (!isAuthenticated) return null; if (!isAuthenticated) return null;
if (claudeAuthStatus?.method === "oauth") return "Subscription Token"; if (claudeAuthStatus?.method === "oauth_token_env" || claudeAuthStatus?.method === "oauth_token") return "Subscription Token";
if (apiKeys.anthropic || claudeAuthStatus?.method === "api_key") return "API Key"; if (apiKeys.anthropic || claudeAuthStatus?.method === "api_key" || claudeAuthStatus?.method === "api_key_env") return "API Key";
return "Authenticated"; return "Authenticated";
}; };

View File

@@ -41,8 +41,22 @@ export interface StatResult {
error?: string; 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 { 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, AutoModeEvent,
ModelDefinition, ModelDefinition,
ProviderStatus, ProviderStatus,
@@ -55,6 +69,18 @@ import type {
FileStatus, FileStatus,
} from "@/types/electron"; } 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 // Feature type - Import from app-store
import type { Feature } from "@/store/app-store"; import type { Feature } from "@/store/app-store";
@@ -308,17 +334,19 @@ export interface ElectronAPI {
getClaudeStatus: () => Promise<{ getClaudeStatus: () => Promise<{
success: boolean; success: boolean;
status?: string; status?: string;
installed?: boolean;
method?: string; method?: string;
version?: string; version?: string;
path?: string; path?: string;
auth?: { auth?: {
authenticated: boolean; authenticated: boolean;
method: string; method: string;
hasCredentialsFile: boolean; hasCredentialsFile?: boolean;
hasToken: boolean; hasToken?: boolean;
hasStoredOAuthToken?: boolean; hasStoredOAuthToken?: boolean;
hasStoredApiKey?: boolean; hasStoredApiKey?: boolean;
hasEnvApiKey?: boolean; hasEnvApiKey?: boolean;
hasEnvOAuthToken?: boolean;
}; };
error?: string; error?: string;
}>; }>;

View File

@@ -19,7 +19,10 @@ export interface ProjectInitResult {
* Required files and directories in the .automaker directory * 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 * 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<string, string>;
} = {
directories: [ directories: [
".automaker", ".automaker",
".automaker/context", ".automaker/context",

View File

@@ -13,10 +13,12 @@ export interface CliStatus {
// Claude Auth Status // Claude Auth Status
export interface ClaudeAuthStatus { export interface ClaudeAuthStatus {
authenticated: boolean; authenticated: boolean;
method: "oauth" | "api_key" | "none"; method: "oauth_token_env" | "oauth_token" | "api_key" | "api_key_env" | "none";
hasCredentialsFile: boolean; hasCredentialsFile?: boolean;
oauthTokenValid?: boolean; oauthTokenValid?: boolean;
apiKeyValid?: boolean; apiKeyValid?: boolean;
hasEnvOAuthToken?: boolean;
hasEnvApiKey?: boolean;
error?: string; error?: string;
} }

View File

@@ -345,6 +345,7 @@ export interface AutoModeAPI {
export interface ElectronAPI { export interface ElectronAPI {
ping: () => Promise<string>; ping: () => Promise<string>;
openExternalLink: (url: string) => Promise<{ success: boolean; error?: string }>;
// Dialog APIs // Dialog APIs
openDirectory: () => Promise<{ openDirectory: () => Promise<{