feat: implement OAuth token setup for Claude CLI

- Added a new SetupTokenModal component for handling OAuth token authentication.
- Integrated in-app terminal support using node-pty for seamless user experience.
- Updated ClaudeCliDetector to extract tokens from command output and handle authentication flow.
- Enhanced README with Windows-specific notes and authentication instructions.
- Updated package.json and package-lock.json to include necessary dependencies for the new functionality.
This commit is contained in:
Kacper
2025-12-11 19:08:08 +01:00
parent acae5526b7
commit 0510ab31e3
8 changed files with 1775 additions and 268 deletions

View File

@@ -33,6 +33,13 @@ cd automaker
npm install
```
### Windows notes (in-app Claude auth)
- Node.js 22.x
- Prebuilt PTY is bundled; Visual Studio build tools are not required for Claude auth.
- If you prefer the external terminal flow, set `CLAUDE_AUTH_DISABLE_PTY=1`.
- If you later add native modules beyond the prebuilt PTY, you may still need VS Build Tools + Python to rebuild those.
**Step 3:** Run the Claude Code setup token command:
```bash
@@ -55,6 +62,14 @@ npm run dev:electron
This will start both the Next.js development server and the Electron application.
### Auth smoke test (Windows)
1. Ensure dependencies are installed (prebuilt pty is included).
2. Run `npm run dev:electron` and open the Setup modal.
3. Click Start on Claude auth; watch the embedded terminal stream logs.
4. Successful runs show “Token captured automatically.”; otherwise copy/paste the token from the log.
5. Optional: `node --test tests/claude-cli-detector.test.js` to verify token parsing.
**Step 6:** MOST IMPORANT: Run the Following after all is setup
```bash

View File

@@ -3,6 +3,22 @@ const fs = require("fs");
const path = require("path");
const os = require("os");
let runPtyCommand = null;
try {
({ runPtyCommand } = require("./pty-runner"));
} catch (error) {
console.warn(
"[ClaudeCliDetector] node-pty unavailable, will fall back to external terminal:",
error?.message || error
);
}
const ANSI_REGEX =
// eslint-disable-next-line no-control-regex
/\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b[@-_]|\u001b\][^\u0007]*\u0007/g;
const stripAnsi = (text = "") => text.replace(ANSI_REGEX, "");
/**
* Claude CLI Detector
*
@@ -459,6 +475,231 @@ class ClaudeCliDetector {
note: "This token is from your Claude subscription and allows you to use Claude without API charges.",
};
}
/**
* Extract OAuth token from command output
* Tries multiple patterns to find the token
* @param {string} output The command output
* @returns {string|null} Extracted token or null
*/
static extractTokenFromOutput(output) {
// Pattern 1: CLAUDE_CODE_OAUTH_TOKEN=<token> or CLAUDE_CODE_OAUTH_TOKEN: <token>
const envMatch = output.match(
/CLAUDE_CODE_OAUTH_TOKEN[=:]\s*["']?([a-zA-Z0-9_\-\.]+)["']?/i
);
if (envMatch) return envMatch[1];
// Pattern 2: "Token: <token>" or "token: <token>"
const tokenLabelMatch = output.match(
/\btoken[:\s]+["']?([a-zA-Z0-9_\-\.]{40,})["']?/i
);
if (tokenLabelMatch) return tokenLabelMatch[1];
// Pattern 3: Look for token after success/authenticated message
const successMatch = output.match(
/(?:success|authenticated|generated|token is)[^\n]*\n\s*([a-zA-Z0-9_\-\.]{40,})/i
);
if (successMatch) return successMatch[1];
// Pattern 4: Standalone long alphanumeric string on its own line (last resort)
// This catches tokens that are printed on their own line
const lines = output.split("\n");
for (const line of lines) {
const trimmed = line.trim();
// Token should be 40+ chars, alphanumeric with possible hyphens/underscores/dots
if (/^[a-zA-Z0-9_\-\.]{40,}$/.test(trimmed)) {
return trimmed;
}
}
return null;
}
/**
* Run claude setup-token command to generate OAuth token
* Opens an external terminal window since Claude CLI requires TTY for its Ink-based UI
* @param {Function} onProgress Callback for progress updates
* @returns {Promise<Object>} Result indicating terminal was opened
*/
static async runSetupToken(onProgress) {
const detection = this.detectClaudeInstallation();
if (!detection.installed) {
throw {
success: false,
error: "Claude CLI is not installed. Please install it first.",
requiresManualAuth: false,
};
}
const claudePath = detection.path;
const platform = process.platform;
const preferPty =
(platform === "win32" ||
process.env.CLAUDE_AUTH_FORCE_PTY === "1") &&
process.env.CLAUDE_AUTH_DISABLE_PTY !== "1";
const send = (data) => {
if (onProgress && data) {
onProgress({ type: "stdout", data });
}
};
if (preferPty && runPtyCommand) {
try {
send("Starting in-app terminal session for Claude auth...\n");
send("If your browser opens, complete sign-in and return here.\n\n");
const ptyResult = await runPtyCommand(claudePath, ["setup-token"], {
cols: 120,
rows: 30,
onData: (chunk) => send(chunk),
env: {
FORCE_COLOR: "1",
},
});
const cleanedOutput = stripAnsi(ptyResult.output || "");
const token = this.extractTokenFromOutput(cleanedOutput);
if (ptyResult.success && token) {
send("\nCaptured token automatically.\n");
return {
success: true,
token,
requiresManualAuth: false,
terminalOpened: false,
};
}
if (ptyResult.success && !token) {
send(
"\nCLI completed but token was not detected automatically. You can copy it above or retry.\n"
);
return {
success: true,
requiresManualAuth: true,
terminalOpened: false,
error: "Could not capture token automatically",
output: cleanedOutput,
};
}
send(
`\nClaude CLI exited with code ${ptyResult.exitCode}. Falling back to manual copy.\n`
);
return {
success: false,
error: `Claude CLI exited with code ${ptyResult.exitCode}`,
requiresManualAuth: true,
output: cleanedOutput,
};
} catch (error) {
console.error("[ClaudeCliDetector] PTY auth failed, falling back:", error);
send(
`In-app terminal failed (${error?.message || "unknown error"}). Falling back to external terminal...\n`
);
}
}
// Fallback: external terminal window
if (preferPty && !runPtyCommand) {
send("In-app terminal unavailable (node-pty not loaded).");
} else if (!preferPty) {
send("Using system terminal for authentication on this platform.");
}
send("Opening system terminal for authentication...\n");
return await new Promise((resolve, reject) => {
// Open command in external terminal since Claude CLI requires TTY
let command, args;
if (platform === "win32") {
// Windows: Open new cmd window that stays open
command = "cmd";
args = ["/c", "start", "cmd", "/k", `"${claudePath}" setup-token`];
} else if (platform === "darwin") {
// macOS: Open Terminal.app
command = "osascript";
args = [
"-e",
`tell application "Terminal" to do script "${claudePath} setup-token"`,
"-e",
'tell application "Terminal" to activate',
];
} else {
// Linux: Try common terminal emulators
const terminals = [
["gnome-terminal", ["--", claudePath, "setup-token"]],
["konsole", ["-e", claudePath, "setup-token"]],
["xterm", ["-e", claudePath, "setup-token"]],
["x-terminal-emulator", ["-e", `${claudePath} setup-token`]],
];
// Try to find an available terminal
for (const [term, termArgs] of terminals) {
try {
execSync(`which ${term}`, { stdio: "ignore" });
command = term;
args = termArgs;
break;
} catch {
// Terminal not found, try next
}
}
if (!command) {
reject({
success: false,
error:
"Could not find a terminal emulator. Please run 'claude setup-token' manually in your terminal.",
requiresManualAuth: true,
});
return;
}
}
console.log(
"[ClaudeCliDetector] Spawning terminal:",
command,
args.join(" ")
);
const proc = spawn(command, args, {
detached: true,
stdio: "ignore",
shell: platform === "win32",
});
proc.unref();
proc.on("error", (error) => {
console.error("[ClaudeCliDetector] Failed to open terminal:", error);
reject({
success: false,
error: `Failed to open terminal: ${error.message}`,
requiresManualAuth: true,
});
});
// Give the terminal a moment to open
setTimeout(() => {
send("Terminal window opened!\n\n");
send("1. Complete the sign-in in your browser\n");
send("2. Copy the token from the terminal\n");
send("3. Paste it below\n");
// Resolve with manual auth required since we can't capture from external terminal
resolve({
success: true,
requiresManualAuth: true,
terminalOpened: true,
message:
"Terminal opened. Complete authentication and paste the token below.",
});
}, 500);
});
}
}
module.exports = ClaudeCliDetector;

View File

@@ -0,0 +1,84 @@
const os = require("os");
// Prefer prebuilt to avoid native build issues.
const pty = require("@homebridge/node-pty-prebuilt-multiarch");
/**
* Minimal PTY helper to run CLI commands with a pseudo-terminal.
* Useful for CLIs (like Claude) that need raw mode on Windows.
*
* @param {string} command Executable path
* @param {string[]} args Arguments for the executable
* @param {Object} options Additional spawn options
* @param {(chunk: string) => void} [options.onData] Data callback
* @param {string} [options.cwd] Working directory
* @param {Object} [options.env] Extra env vars
* @param {number} [options.cols] Terminal columns
* @param {number} [options.rows] Terminal rows
* @returns {Promise<{ success: boolean, exitCode: number, signal?: number, output: string, errorOutput: string }>}
*/
function runPtyCommand(command, args = [], options = {}) {
const {
onData,
cwd = process.cwd(),
env = {},
cols = 120,
rows = 30,
} = options;
const mergedEnv = {
...process.env,
TERM: process.env.TERM || "xterm-256color",
...env,
};
return new Promise((resolve, reject) => {
let ptyProcess;
try {
ptyProcess = pty.spawn(command, args, {
name: os.platform() === "win32" ? "Windows.Terminal" : "xterm-color",
cols,
rows,
cwd,
env: mergedEnv,
useConpty: true,
});
} catch (error) {
return reject(error);
}
let output = "";
let errorOutput = "";
ptyProcess.onData((data) => {
output += data;
if (typeof onData === "function") {
onData(data);
}
});
// node-pty does not emit 'error' in practice, but guard anyway
if (ptyProcess.on) {
ptyProcess.on("error", (err) => {
errorOutput += err?.message || "";
reject(err);
});
}
ptyProcess.onExit(({ exitCode, signal }) => {
resolve({
success: exitCode === 0,
exitCode,
signal,
output,
errorOutput,
});
});
});
}
module.exports = {
runPtyCommand,
};

1209
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,7 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -56,6 +57,7 @@
"zustand": "^5.0.9"
},
"devDependencies": {
"@electron/rebuild": "^4.0.2",
"@playwright/test": "^1.57.0",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",

View File

@@ -0,0 +1,387 @@
"use client";
import { useState, useEffect, useRef, useCallback } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Loader2,
Terminal,
CheckCircle2,
XCircle,
Copy,
RotateCcw,
} from "lucide-react";
import { getElectronAPI } from "@/lib/electron";
import { toast } from "sonner";
interface SetupTokenModalProps {
open: boolean;
onClose: () => void;
onTokenObtained: (token: string) => void;
}
type AuthState = "idle" | "running" | "success" | "error" | "manual";
export function SetupTokenModal({
open,
onClose,
onTokenObtained,
}: SetupTokenModalProps) {
const [authState, setAuthState] = useState<AuthState>("idle");
const [output, setOutput] = useState<string[]>([]);
const [token, setToken] = useState("");
const [error, setError] = useState<string | null>(null);
const [manualToken, setManualToken] = useState("");
const scrollRef = useRef<HTMLDivElement>(null);
const unsubscribeRef = useRef<(() => void) | null>(null);
// Auto-scroll to bottom when output changes
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [output]);
// Reset state when modal opens
useEffect(() => {
if (open) {
setAuthState("idle");
setOutput([]);
setToken("");
setError(null);
setManualToken("");
} else {
// Cleanup subscription when modal closes
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
}
}, [open]);
const startAuth = useCallback(async () => {
const api = getElectronAPI();
if (!api.setup) {
setError("Setup API not available");
setAuthState("error");
return;
}
setAuthState("running");
setOutput([
"Starting authentication...",
"Running Claude CLI in an embedded terminal so you don't need to copy/paste.",
"When your browser opens, complete sign-in and return here.",
"",
]);
setError(null);
setToken("");
// Subscribe to progress events
if (api.setup.onAuthProgress) {
unsubscribeRef.current = api.setup.onAuthProgress((progress) => {
if (progress.cli === "claude" && progress.data) {
// Split by newlines and add each line
const normalized = progress.data.replace(/\r/g, "\n");
const lines = normalized
.split("\n")
.map((line: string) => line.trimEnd())
.filter((line: string) => line.length > 0);
if (lines.length > 0) {
setOutput((prev) => [...prev, ...lines]);
}
}
});
}
try {
const result = await api.setup.authClaude();
// Cleanup subscription
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
if (result.success && result.token) {
setToken(result.token);
setAuthState("success");
setOutput((prev) => [
...prev,
"",
"✓ Authentication successful!",
"✓ Token captured automatically.",
]);
} else if (result.requiresManualAuth) {
// Terminal was opened - user needs to copy token manually
setAuthState("manual");
// Don't add extra messages if terminalOpened - the progress messages already explain
if (!result.terminalOpened) {
const extraMessages = [
"",
"⚠ Could not capture token automatically.",
];
if (result.error) {
extraMessages.push(result.error);
}
setOutput((prev) => [
...prev,
...extraMessages,
"Please copy the token from above and paste it below.",
]);
}
} else {
setError(result.error || "Authentication failed");
setAuthState("error");
}
} catch (err: unknown) {
// Cleanup subscription
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
const errorMessage =
err instanceof Error
? err.message
: typeof err === "object" && err !== null && "error" in err
? String((err as { error: unknown }).error)
: "Authentication failed";
// Check if we should fall back to manual mode
if (
typeof err === "object" &&
err !== null &&
"requiresManualAuth" in err &&
(err as { requiresManualAuth: boolean }).requiresManualAuth
) {
setAuthState("manual");
setOutput((prev) => [
...prev,
"",
"⚠ " + errorMessage,
"Please copy the token manually and paste it below.",
]);
} else {
setError(errorMessage);
setAuthState("error");
}
}
}, []);
const handleUseToken = useCallback(() => {
const tokenToUse = token || manualToken;
if (tokenToUse.trim()) {
onTokenObtained(tokenToUse.trim());
onClose();
}
}, [token, manualToken, onTokenObtained, onClose]);
const copyCommand = useCallback(() => {
navigator.clipboard.writeText("claude setup-token");
toast.success("Command copied to clipboard");
}, []);
const handleRetry = useCallback(() => {
setAuthState("idle");
setOutput([]);
setError(null);
setToken("");
setManualToken("");
}, []);
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent
className="max-w-2xl bg-card border-border"
data-testid="setup-token-modal"
>
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-foreground">
<Terminal className="w-5 h-5 text-brand-500" />
Claude Subscription Authentication
</DialogTitle>
<DialogDescription className="text-muted-foreground">
{authState === "idle" &&
"Click Start to begin the authentication process."}
{authState === "running" &&
"Complete the sign-in in your browser..."}
{authState === "success" &&
"Authentication successful! Your token has been captured."}
{authState === "error" &&
"Authentication failed. Please try again or enter the token manually."}
{authState === "manual" &&
"Copy the token from your terminal and paste it below."}
</DialogDescription>
</DialogHeader>
{/* Terminal Output */}
<div
ref={scrollRef}
className="bg-zinc-900 rounded-lg p-4 font-mono text-sm max-h-48 overflow-y-auto border border-border mt-3"
>
{output.map((line, index) => (
<div key={index} className="text-zinc-300 whitespace-pre-wrap">
{line.startsWith("Error") || line.startsWith("⚠") ? (
<span className="text-yellow-400">{line}</span>
) : line.startsWith("✓") ? (
<span className="text-green-400">{line}</span>
) : (
line
)}
</div>
))}
{output.length === 0 && (
<div className="text-zinc-500 italic">Waiting to start...</div>
)}
{authState === "running" && (
<div className="flex items-center gap-2 text-brand-400 mt-2">
<Loader2 className="w-4 h-4 animate-spin" />
<span>Waiting for authentication...</span>
</div>
)}
</div>
{/* Manual Token Input (for fallback) */}
{(authState === "manual" || authState === "error") && (
<div className="space-y-3 pt-2">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>Run this command in your terminal:</span>
<code className="bg-muted px-2 py-1 rounded font-mono text-foreground">
claude setup-token
</code>
<Button
variant="ghost"
size="icon"
onClick={copyCommand}
className="h-7 w-7"
>
<Copy className="w-4 h-4" />
</Button>
</div>
<div className="space-y-2">
<Label htmlFor="manual-token" className="text-foreground">
Paste your token:
</Label>
<Input
id="manual-token"
type="password"
placeholder="Paste token here..."
value={manualToken}
onChange={(e) => setManualToken(e.target.value)}
className="bg-input border-border text-foreground"
data-testid="manual-token-input"
/>
</div>
</div>
)}
{/* Success State */}
{authState === "success" && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-green-500/10 border border-green-500/20">
<CheckCircle2 className="w-6 h-6 text-green-500 shrink-0" />
<div>
<p className="font-medium text-foreground">
Token captured successfully!
</p>
<p className="text-sm text-muted-foreground">
Click &quot;Use Token&quot; to save and continue.
</p>
</div>
</div>
)}
{/* Error State */}
{error && authState === "error" && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-red-500/10 border border-red-500/20">
<XCircle className="w-6 h-6 text-red-500 shrink-0" />
<div>
<p className="font-medium text-foreground">Error</p>
<p className="text-sm text-muted-foreground">{error}</p>
</div>
</div>
)}
<DialogFooter className="mt-5 flex gap-2">
<Button
variant="outline"
onClick={onClose}
className="text-muted-foreground hover:text-foreground"
>
Cancel
</Button>
{authState === "idle" && (
<Button
onClick={startAuth}
className="bg-brand-500 hover:bg-brand-600 text-white"
data-testid="start-auth-button"
>
<Terminal className="w-4 h-4 mr-2" />
Start Authentication
</Button>
)}
{authState === "running" && (
<Button disabled className="bg-brand-500">
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Authenticating...
</Button>
)}
{authState === "success" && (
<Button
onClick={handleUseToken}
className="bg-green-500 hover:bg-green-600 text-white"
data-testid="use-token-button"
>
<CheckCircle2 className="w-4 h-4 mr-2" />
Use Token
</Button>
)}
{authState === "manual" && (
<Button
onClick={handleUseToken}
disabled={!manualToken.trim()}
className="bg-brand-500 hover:bg-brand-600 text-white disabled:opacity-50"
data-testid="use-manual-token-button"
>
<CheckCircle2 className="w-4 h-4 mr-2" />
Use Token
</Button>
)}
{authState === "error" && (
<>
{manualToken.trim() && (
<Button
onClick={handleUseToken}
className="bg-green-500 hover:bg-green-600 text-white"
>
Use Manual Token
</Button>
)}
<Button
onClick={handleRetry}
className="bg-brand-500 hover:bg-brand-600 text-white"
>
<RotateCcw className="w-4 h-4 mr-2" />
Retry
</Button>
</>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -31,6 +31,7 @@ import {
Shield,
} from "lucide-react";
import { toast } from "sonner";
import { SetupTokenModal } from "./setup-token-modal";
// Step indicator component
function StepIndicator({
@@ -212,6 +213,7 @@ function ClaudeSetupStep({
const [oauthToken, setOAuthToken] = useState("");
const [apiKey, setApiKey] = useState("");
const [isSaving, setIsSaving] = useState(false);
const [showTokenModal, setShowTokenModal] = useState(false);
const checkStatus = useCallback(async () => {
console.log("[Claude Setup] Starting status check...");
@@ -470,6 +472,49 @@ function ClaudeSetupStep({
toast.success("Command copied to clipboard");
};
// Handle token obtained from the OAuth modal
const handleTokenFromModal = useCallback(
async (token: string) => {
setOAuthToken(token);
setShowTokenModal(false);
// Auto-save the token
setIsSaving(true);
try {
const api = getElectronAPI();
const setupApi = api.setup;
if (setupApi?.storeApiKey) {
const result = await setupApi.storeApiKey(
"anthropic_oauth_token",
token
);
console.log("[Claude Setup] Store OAuth token result:", result);
if (result.success) {
setClaudeAuthStatus({
authenticated: true,
method: "oauth_token",
hasCredentialsFile: false,
oauthTokenValid: true,
});
toast.success("Claude subscription token saved");
setAuthMethod(null);
await checkStatus();
} else {
toast.error("Failed to save token", { description: result.error });
}
}
} catch (error) {
console.error("[Claude Setup] Failed to save OAuth token:", error);
toast.error("Failed to save token");
} finally {
setIsSaving(false);
}
},
[checkStatus, setClaudeAuthStatus]
);
const isAuthenticated = claudeAuthStatus?.authenticated || apiKeys.anthropic;
const getAuthMethodLabel = () => {
@@ -666,31 +711,40 @@ function ClaudeSetupStep({
{claudeCliStatus?.installed ? (
<>
<div className="mb-3">
<p className="text-sm text-muted-foreground mb-2">
1. Run this command in your terminal:
</p>
<div className="flex items-center gap-2">
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
claude setup-token
</code>
<Button
variant="ghost"
size="icon"
onClick={() => copyCommand("claude setup-token")}
>
<Copy className="w-4 h-4" />
</Button>
{/* Primary: Automated OAuth setup */}
<Button
onClick={() => setShowTokenModal(true)}
className="w-full bg-brand-500 hover:bg-brand-600 text-white mb-4"
data-testid="setup-oauth-button"
>
<Terminal className="w-4 h-4 mr-2" />
Setup with OAuth
</Button>
{/* Divider */}
<div className="relative my-4">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-border" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-brand-500/5 px-2 text-muted-foreground">
or paste manually
</span>
</div>
</div>
{/* Fallback: Manual token entry */}
<div className="space-y-2">
<Label className="text-foreground">
2. Paste the token here:
<Label className="text-foreground text-sm">
Paste token from{" "}
<code className="bg-muted px-1 py-0.5 rounded text-xs">
claude setup-token
</code>
:
</Label>
<Input
type="password"
placeholder="Paste token from claude setup-token..."
placeholder="Paste token here..."
value={oauthToken}
onChange={(e) => setOAuthToken(e.target.value)}
className="bg-input border-border text-foreground"
@@ -893,6 +947,13 @@ function ClaudeSetupStep({
</Button>
</div>
</div>
{/* OAuth Setup Modal */}
<SetupTokenModal
open={showTokenModal}
onClose={() => setShowTokenModal(false)}
onTokenObtained={handleTokenFromModal}
/>
</div>
);
}

View File

@@ -369,9 +369,13 @@ export interface ElectronAPI {
}>;
authClaude: () => Promise<{
success: boolean;
token?: string;
requiresManualAuth?: boolean;
terminalOpened?: boolean;
command?: string;
error?: string;
message?: string;
output?: string;
}>;
authCodex: (apiKey?: string) => Promise<{
success: boolean;
@@ -781,9 +785,13 @@ interface SetupAPI {
}>;
authClaude: () => Promise<{
success: boolean;
token?: string;
requiresManualAuth?: boolean;
terminalOpened?: boolean;
command?: string;
error?: string;
message?: string;
output?: string;
}>;
authCodex: (apiKey?: string) => Promise<{
success: boolean;