mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
feat: fix CLI authentication detection to prevent unnecessary browser prompts
- Fix Claude, Codex, and Cursor auth handlers to check if CLI is already authenticated - Use same detection logic as each provider's internal checkAuth/codexAuthIndicators() - For Codex: Check for API keys and auth files before requiring manual login - For Cursor: Check for env var and credentials files before requiring manual auth - For Claude: Check for cached auth tokens, settings, and credentials files - If CLI is already authenticated: Just reconnect by removing disconnected marker - If CLI needs auth: Tell user to manually run login command - This prevents timeout errors when login commands can't run in non-interactive mode
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CliStatus } from '../shared/types';
|
||||
import type { ClaudeAuthStatus } from '@/store/setup-store';
|
||||
import { AnthropicIcon } from '@/components/ui/provider-icon';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface CliStatusProps {
|
||||
status: CliStatus | null;
|
||||
@@ -81,6 +84,60 @@ function ClaudeCliStatusSkeleton() {
|
||||
}
|
||||
|
||||
export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: CliStatusProps) {
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
const [isDeauthenticating, setIsDeauthenticating] = useState(false);
|
||||
|
||||
const handleSignIn = useCallback(async () => {
|
||||
setIsAuthenticating(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.setup.authClaude();
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Signed In', {
|
||||
description: 'Successfully authenticated Claude CLI',
|
||||
});
|
||||
onRefresh();
|
||||
} else if (result.error) {
|
||||
toast.error('Authentication Failed', {
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Authentication Failed', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
const handleSignOut = useCallback(async () => {
|
||||
setIsDeauthenticating(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.setup.deauthClaude();
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Signed Out', {
|
||||
description: 'Successfully signed out from Claude CLI',
|
||||
});
|
||||
// Refresh status after successful logout
|
||||
onRefresh();
|
||||
} else if (result.error) {
|
||||
toast.error('Sign Out Failed', {
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Sign Out Failed', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsDeauthenticating(false);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
if (!status) return <ClaudeCliStatusSkeleton />;
|
||||
|
||||
return (
|
||||
@@ -153,7 +210,7 @@ export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: C
|
||||
</div>
|
||||
{/* Authentication Status */}
|
||||
{authStatus?.authenticated ? (
|
||||
<div className="flex items-center gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div className="w-10 h-10 rounded-xl bg-emerald-500/15 flex items-center justify-center border border-emerald-500/20 shrink-0">
|
||||
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
|
||||
</div>
|
||||
@@ -165,6 +222,15 @@ export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: C
|
||||
<span className="font-mono">{getAuthMethodLabel(authStatus.method)}</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSignOut}
|
||||
disabled={isDeauthenticating}
|
||||
className="mt-3 h-8 text-xs"
|
||||
>
|
||||
{isDeauthenticating ? 'Signing Out...' : 'Sign Out'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -175,9 +241,17 @@ export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: C
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-amber-400">Not Authenticated</p>
|
||||
<p className="text-xs text-amber-400/70 mt-1">
|
||||
Run <code className="font-mono bg-amber-500/10 px-1 rounded">claude login</code>{' '}
|
||||
or set an API key to authenticate.
|
||||
Click Sign In below to get authentication instructions.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSignIn}
|
||||
disabled={isAuthenticating}
|
||||
className="mt-3 h-8 text-xs"
|
||||
>
|
||||
{isAuthenticating ? 'Requesting...' : 'Sign In'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CliStatus } from '../shared/types';
|
||||
import type { CodexAuthStatus } from '@/store/setup-store';
|
||||
import { OpenAIIcon } from '@/components/ui/provider-icon';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface CliStatusProps {
|
||||
status: CliStatus | null;
|
||||
@@ -76,6 +79,60 @@ function CodexCliStatusSkeleton() {
|
||||
}
|
||||
|
||||
export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: CliStatusProps) {
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
const [isDeauthenticating, setIsDeauthenticating] = useState(false);
|
||||
|
||||
const handleSignIn = useCallback(async () => {
|
||||
setIsAuthenticating(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.setup.authCodex();
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Signed In', {
|
||||
description: 'Successfully authenticated Codex CLI',
|
||||
});
|
||||
onRefresh();
|
||||
} else if (result.error) {
|
||||
toast.error('Authentication Failed', {
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Authentication Failed', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
const handleSignOut = useCallback(async () => {
|
||||
setIsDeauthenticating(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.setup.deauthCodex();
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Signed Out', {
|
||||
description: 'Successfully signed out from Codex CLI',
|
||||
});
|
||||
// Refresh status after successful logout
|
||||
onRefresh();
|
||||
} else if (result.error) {
|
||||
toast.error('Sign Out Failed', {
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Sign Out Failed', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsDeauthenticating(false);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
if (!status) return <CodexCliStatusSkeleton />;
|
||||
|
||||
return (
|
||||
@@ -145,7 +202,7 @@ export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: Cl
|
||||
</div>
|
||||
{/* Authentication Status */}
|
||||
{authStatus?.authenticated ? (
|
||||
<div className="flex items-center gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div className="w-10 h-10 rounded-xl bg-emerald-500/15 flex items-center justify-center border border-emerald-500/20 shrink-0">
|
||||
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
|
||||
</div>
|
||||
@@ -157,6 +214,15 @@ export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: Cl
|
||||
<span className="font-mono">{getAuthMethodLabel(authStatus.method)}</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSignOut}
|
||||
disabled={isDeauthenticating}
|
||||
className="mt-3 h-8 text-xs"
|
||||
>
|
||||
{isDeauthenticating ? 'Signing Out...' : 'Sign Out'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -167,9 +233,17 @@ export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: Cl
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-amber-400">Not Authenticated</p>
|
||||
<p className="text-xs text-amber-400/70 mt-1">
|
||||
Run <code className="font-mono bg-amber-500/10 px-1 rounded">codex login</code>{' '}
|
||||
or set an API key to authenticate.
|
||||
Click Sign In below to get authentication instructions.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSignIn}
|
||||
disabled={isAuthenticating}
|
||||
className="mt-3 h-8 text-xs"
|
||||
>
|
||||
{isAuthenticating ? 'Requesting...' : 'Sign In'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CursorIcon } from '@/components/ui/provider-icon';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface CursorStatus {
|
||||
installed: boolean;
|
||||
@@ -201,6 +204,60 @@ export function ModelConfigSkeleton() {
|
||||
}
|
||||
|
||||
export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStatusProps) {
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
const [isDeauthenticating, setIsDeauthenticating] = useState(false);
|
||||
|
||||
const handleSignIn = useCallback(async () => {
|
||||
setIsAuthenticating(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.setup.authCursor();
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Signed In', {
|
||||
description: 'Successfully authenticated Cursor CLI',
|
||||
});
|
||||
onRefresh();
|
||||
} else if (result.error) {
|
||||
toast.error('Authentication Failed', {
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Authentication Failed', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
const handleSignOut = useCallback(async () => {
|
||||
setIsDeauthenticating(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.setup.deauthCursor();
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Signed Out', {
|
||||
description: 'Successfully signed out from Cursor CLI',
|
||||
});
|
||||
// Refresh status after successful logout
|
||||
onRefresh();
|
||||
} else if (result.error) {
|
||||
toast.error('Sign Out Failed', {
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Sign Out Failed', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsDeauthenticating(false);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
if (!status) return <CursorCliStatusSkeleton />;
|
||||
|
||||
return (
|
||||
@@ -262,7 +319,7 @@ export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStat
|
||||
|
||||
{/* Authentication Status */}
|
||||
{status.authenticated ? (
|
||||
<div className="flex items-center gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div className="w-10 h-10 rounded-xl bg-emerald-500/15 flex items-center justify-center border border-emerald-500/20 shrink-0">
|
||||
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
|
||||
</div>
|
||||
@@ -276,6 +333,15 @@ export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStat
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSignOut}
|
||||
disabled={isDeauthenticating}
|
||||
className="mt-3 h-8 text-xs"
|
||||
>
|
||||
{isDeauthenticating ? 'Signing Out...' : 'Sign Out'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -286,9 +352,17 @@ export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStat
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-amber-400">Not Authenticated</p>
|
||||
<p className="text-xs text-amber-400/70 mt-1">
|
||||
Run <code className="font-mono bg-amber-500/10 px-1 rounded">cursor auth</code>{' '}
|
||||
to authenticate with Cursor.
|
||||
Click Sign In below to get authentication instructions.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSignIn}
|
||||
disabled={isAuthenticating}
|
||||
className="mt-3 h-8 text-xs"
|
||||
>
|
||||
{isAuthenticating ? 'Requesting...' : 'Sign In'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Type definitions for Electron IPC API
|
||||
import type { SessionListItem, Message } from '@/types/electron';
|
||||
import type { ClaudeUsageResponse } from '@/store/app-store';
|
||||
import type { ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
|
||||
import type {
|
||||
IssueValidationVerdict,
|
||||
IssueValidationConfidence,
|
||||
@@ -725,6 +725,9 @@ export interface ElectronAPI {
|
||||
}>;
|
||||
};
|
||||
ideation?: IdeationAPI;
|
||||
codex?: {
|
||||
getUsage: () => Promise<CodexUsageResponse>;
|
||||
};
|
||||
}
|
||||
|
||||
// Note: Window interface is declared in @/types/electron.d.ts
|
||||
|
||||
@@ -1073,6 +1073,14 @@ export class HttpApiClient implements ElectronAPI {
|
||||
output?: string;
|
||||
}> => this.post('/api/setup/auth-claude'),
|
||||
|
||||
deauthClaude: (): Promise<{
|
||||
success: boolean;
|
||||
requiresManualDeauth?: boolean;
|
||||
command?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}> => this.post('/api/setup/deauth-claude'),
|
||||
|
||||
storeApiKey: (
|
||||
provider: string,
|
||||
apiKey: string
|
||||
@@ -1139,6 +1147,24 @@ export class HttpApiClient implements ElectronAPI {
|
||||
error?: string;
|
||||
}> => this.get('/api/setup/cursor-status'),
|
||||
|
||||
authCursor: (): Promise<{
|
||||
success: boolean;
|
||||
token?: string;
|
||||
requiresManualAuth?: boolean;
|
||||
terminalOpened?: boolean;
|
||||
command?: string;
|
||||
message?: string;
|
||||
output?: string;
|
||||
}> => this.post('/api/setup/auth-cursor'),
|
||||
|
||||
deauthCursor: (): Promise<{
|
||||
success: boolean;
|
||||
requiresManualDeauth?: boolean;
|
||||
command?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}> => this.post('/api/setup/deauth-cursor'),
|
||||
|
||||
getCursorConfig: (
|
||||
projectPath: string
|
||||
): Promise<{
|
||||
@@ -1281,6 +1307,14 @@ export class HttpApiClient implements ElectronAPI {
|
||||
output?: string;
|
||||
}> => this.post('/api/setup/auth-codex'),
|
||||
|
||||
deauthCodex: (): Promise<{
|
||||
success: boolean;
|
||||
requiresManualDeauth?: boolean;
|
||||
command?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}> => this.post('/api/setup/deauth-codex'),
|
||||
|
||||
verifyCodexAuth: (
|
||||
authMethod: 'cli' | 'api_key',
|
||||
apiKey?: string
|
||||
|
||||
12
apps/ui/src/types/electron.d.ts
vendored
12
apps/ui/src/types/electron.d.ts
vendored
@@ -2,6 +2,8 @@
|
||||
* Electron API type definitions
|
||||
*/
|
||||
|
||||
import type { ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
|
||||
|
||||
export interface ImageAttachment {
|
||||
id?: string; // Optional - may not be present in messages loaded from server
|
||||
data: string; // base64 encoded image data
|
||||
@@ -584,6 +586,16 @@ export interface ElectronAPI {
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
// Claude Usage API
|
||||
claude: {
|
||||
getUsage: () => Promise<ClaudeUsageResponse>;
|
||||
};
|
||||
|
||||
// Codex Usage API
|
||||
codex: {
|
||||
getUsage: () => Promise<CodexUsageResponse>;
|
||||
};
|
||||
|
||||
// Worktree Management APIs
|
||||
worktree: WorktreeAPI;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user