mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
Merge main into massive-terminal-upgrade
Resolves merge conflicts: - apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger - apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions - apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling) - apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes - apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,15 +2,12 @@
|
||||
* Common utilities and state for setup routes
|
||||
*/
|
||||
|
||||
import { createLogger } from "../../lib/logger.js";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import {
|
||||
getErrorMessage as getErrorMessageShared,
|
||||
createLogError,
|
||||
} from "../common.js";
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||
|
||||
const logger = createLogger("Setup");
|
||||
const logger = createLogger('Setup');
|
||||
|
||||
// Storage for API keys (in-memory cache) - private
|
||||
const apiKeys: Record<string, string> = {};
|
||||
@@ -39,22 +36,19 @@ export function getAllApiKeys(): Record<string, string> {
|
||||
/**
|
||||
* Helper to persist API keys to .env file
|
||||
*/
|
||||
export async function persistApiKeyToEnv(
|
||||
key: string,
|
||||
value: string
|
||||
): Promise<void> {
|
||||
const envPath = path.join(process.cwd(), ".env");
|
||||
export async function persistApiKeyToEnv(key: string, value: string): Promise<void> {
|
||||
const envPath = path.join(process.cwd(), '.env');
|
||||
|
||||
try {
|
||||
let envContent = "";
|
||||
let envContent = '';
|
||||
try {
|
||||
envContent = await fs.readFile(envPath, "utf-8");
|
||||
envContent = await fs.readFile(envPath, 'utf-8');
|
||||
} catch {
|
||||
// .env file doesn't exist, we'll create it
|
||||
}
|
||||
|
||||
// Parse existing env content
|
||||
const lines = envContent.split("\n");
|
||||
const lines = envContent.split('\n');
|
||||
const keyRegex = new RegExp(`^${key}=`);
|
||||
let found = false;
|
||||
const newLines = lines.map((line) => {
|
||||
@@ -70,7 +64,7 @@ export async function persistApiKeyToEnv(
|
||||
newLines.push(`${key}=${value}`);
|
||||
}
|
||||
|
||||
await fs.writeFile(envPath, newLines.join("\n"));
|
||||
await fs.writeFile(envPath, newLines.join('\n'));
|
||||
logger.info(`[Setup] Persisted ${key} to .env file`);
|
||||
} catch (error) {
|
||||
logger.error(`[Setup] Failed to persist ${key} to .env:`, error);
|
||||
|
||||
@@ -2,20 +2,18 @@
|
||||
* GET /api-keys endpoint - Get API keys status
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import { getApiKey, getErrorMessage, logError } from "../common.js";
|
||||
import type { Request, Response } from 'express';
|
||||
import { getApiKey, getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createApiKeysHandler() {
|
||||
return async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
res.json({
|
||||
success: true,
|
||||
hasAnthropicKey:
|
||||
!!getApiKey("anthropic") || !!process.env.ANTHROPIC_API_KEY,
|
||||
hasGoogleKey: !!getApiKey("google") || !!process.env.GOOGLE_API_KEY,
|
||||
hasAnthropicKey: !!getApiKey('anthropic') || !!process.env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, "Get API keys failed");
|
||||
logError(error, 'Get API keys failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,43 +2,43 @@
|
||||
* POST /delete-api-key endpoint - Delete a stored API key
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import { createLogger } from "../../../lib/logger.js";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import type { Request, Response } from 'express';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
const logger = createLogger("Setup");
|
||||
const logger = createLogger('Setup');
|
||||
|
||||
// In-memory storage reference (imported from common.ts pattern)
|
||||
// We need to modify common.ts to export a deleteApiKey function
|
||||
import { setApiKey } from "../common.js";
|
||||
import { setApiKey } from '../common.js';
|
||||
|
||||
/**
|
||||
* Remove an API key from the .env file
|
||||
*/
|
||||
async function removeApiKeyFromEnv(key: string): Promise<void> {
|
||||
const envPath = path.join(process.cwd(), ".env");
|
||||
const envPath = path.join(process.cwd(), '.env');
|
||||
|
||||
try {
|
||||
let envContent = "";
|
||||
let envContent = '';
|
||||
try {
|
||||
envContent = await fs.readFile(envPath, "utf-8");
|
||||
envContent = await fs.readFile(envPath, 'utf-8');
|
||||
} catch {
|
||||
// .env file doesn't exist, nothing to delete
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse existing env content and remove the key
|
||||
const lines = envContent.split("\n");
|
||||
const lines = envContent.split('\n');
|
||||
const keyRegex = new RegExp(`^${key}=`);
|
||||
const newLines = lines.filter((line) => !keyRegex.test(line));
|
||||
|
||||
// Remove empty lines at the end
|
||||
while (newLines.length > 0 && newLines[newLines.length - 1].trim() === "") {
|
||||
while (newLines.length > 0 && newLines[newLines.length - 1].trim() === '') {
|
||||
newLines.pop();
|
||||
}
|
||||
|
||||
await fs.writeFile(envPath, newLines.join("\n") + (newLines.length > 0 ? "\n" : ""));
|
||||
await fs.writeFile(envPath, newLines.join('\n') + (newLines.length > 0 ? '\n' : ''));
|
||||
logger.info(`[Setup] Removed ${key} from .env file`);
|
||||
} catch (error) {
|
||||
logger.error(`[Setup] Failed to remove ${key} from .env:`, error);
|
||||
@@ -54,7 +54,7 @@ export function createDeleteApiKeyHandler() {
|
||||
if (!provider) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: "Provider is required",
|
||||
error: 'Provider is required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -63,22 +63,20 @@ export function createDeleteApiKeyHandler() {
|
||||
|
||||
// Map provider to env key name
|
||||
const envKeyMap: Record<string, string> = {
|
||||
anthropic: "ANTHROPIC_API_KEY",
|
||||
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
||||
openai: "OPENAI_API_KEY",
|
||||
anthropic: 'ANTHROPIC_API_KEY',
|
||||
};
|
||||
|
||||
const envKey = envKeyMap[provider];
|
||||
if (!envKey) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: `Unknown provider: ${provider}`,
|
||||
error: `Unknown provider: ${provider}. Only anthropic is supported.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear from in-memory storage
|
||||
setApiKey(provider, "");
|
||||
setApiKey(provider, '');
|
||||
|
||||
// Remove from environment
|
||||
delete process.env[envKey];
|
||||
@@ -93,14 +91,11 @@ export function createDeleteApiKeyHandler() {
|
||||
message: `API key for ${provider} has been deleted`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("[Setup] Delete API key error:", error);
|
||||
logger.error('[Setup] Delete API key error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Failed to delete API key",
|
||||
error: error instanceof Error ? error.message : 'Failed to delete API key',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
* POST /store-api-key endpoint - Store API key
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import {
|
||||
setApiKey,
|
||||
persistApiKeyToEnv,
|
||||
getErrorMessage,
|
||||
logError,
|
||||
} from "../common.js";
|
||||
import { createLogger } from "../../../lib/logger.js";
|
||||
import type { Request, Response } from 'express';
|
||||
import { setApiKey, persistApiKeyToEnv, getErrorMessage, logError } from '../common.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
|
||||
const logger = createLogger("Setup");
|
||||
const logger = createLogger('Setup');
|
||||
|
||||
export function createStoreApiKeyHandler() {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
@@ -22,28 +17,29 @@ export function createStoreApiKeyHandler() {
|
||||
};
|
||||
|
||||
if (!provider || !apiKey) {
|
||||
res
|
||||
.status(400)
|
||||
.json({ success: false, error: "provider and apiKey required" });
|
||||
res.status(400).json({ success: false, error: 'provider and apiKey required' });
|
||||
return;
|
||||
}
|
||||
|
||||
setApiKey(provider, apiKey);
|
||||
|
||||
// Also set as environment variable and persist to .env
|
||||
if (provider === "anthropic" || provider === "anthropic_oauth_token") {
|
||||
if (provider === 'anthropic' || provider === 'anthropic_oauth_token') {
|
||||
// Both API key and OAuth token use ANTHROPIC_API_KEY
|
||||
process.env.ANTHROPIC_API_KEY = apiKey;
|
||||
await persistApiKeyToEnv("ANTHROPIC_API_KEY", apiKey);
|
||||
logger.info("[Setup] Stored API key as ANTHROPIC_API_KEY");
|
||||
} else if (provider === "google") {
|
||||
process.env.GOOGLE_API_KEY = apiKey;
|
||||
await persistApiKeyToEnv("GOOGLE_API_KEY", apiKey);
|
||||
await persistApiKeyToEnv('ANTHROPIC_API_KEY', apiKey);
|
||||
logger.info('[Setup] Stored API key as ANTHROPIC_API_KEY');
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: `Unsupported provider: ${provider}. Only anthropic is supported.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, "Store API key failed");
|
||||
logError(error, 'Store API key failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,50 +3,50 @@
|
||||
* Supports verifying either CLI auth or API key auth independently
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import { query } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { createLogger } from "../../../lib/logger.js";
|
||||
import { getApiKey } from "../common.js";
|
||||
import type { Request, Response } from 'express';
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { getApiKey } from '../common.js';
|
||||
|
||||
const logger = createLogger("Setup");
|
||||
const logger = createLogger('Setup');
|
||||
|
||||
// Known error patterns that indicate auth failure
|
||||
const AUTH_ERROR_PATTERNS = [
|
||||
"OAuth token revoked",
|
||||
"Please run /login",
|
||||
"please run /login",
|
||||
"token revoked",
|
||||
"invalid_api_key",
|
||||
"authentication_error",
|
||||
"unauthorized",
|
||||
"not authenticated",
|
||||
"authentication failed",
|
||||
"invalid api key",
|
||||
"api key is invalid",
|
||||
'OAuth token revoked',
|
||||
'Please run /login',
|
||||
'please run /login',
|
||||
'token revoked',
|
||||
'invalid_api_key',
|
||||
'authentication_error',
|
||||
'unauthorized',
|
||||
'not authenticated',
|
||||
'authentication failed',
|
||||
'invalid api key',
|
||||
'api key is invalid',
|
||||
];
|
||||
|
||||
// Patterns that indicate billing/credit issues - should FAIL verification
|
||||
const BILLING_ERROR_PATTERNS = [
|
||||
"credit balance is too low",
|
||||
"credit balance too low",
|
||||
"insufficient credits",
|
||||
"insufficient balance",
|
||||
"no credits",
|
||||
"out of credits",
|
||||
"billing",
|
||||
"payment required",
|
||||
"add credits",
|
||||
'credit balance is too low',
|
||||
'credit balance too low',
|
||||
'insufficient credits',
|
||||
'insufficient balance',
|
||||
'no credits',
|
||||
'out of credits',
|
||||
'billing',
|
||||
'payment required',
|
||||
'add credits',
|
||||
];
|
||||
|
||||
// Patterns that indicate rate/usage limits - should FAIL verification
|
||||
// Users need to wait or upgrade their plan
|
||||
const RATE_LIMIT_PATTERNS = [
|
||||
"limit reached",
|
||||
"rate limit",
|
||||
"rate_limit",
|
||||
"resets", // Only valid if it's a temporary reset, not a billing issue
|
||||
"/upgrade",
|
||||
"extra-usage",
|
||||
'limit reached',
|
||||
'rate limit',
|
||||
'rate_limit',
|
||||
'resets', // Only valid if it's a temporary reset, not a billing issue
|
||||
'/upgrade',
|
||||
'extra-usage',
|
||||
];
|
||||
|
||||
function isRateLimitError(text: string): boolean {
|
||||
@@ -55,43 +55,33 @@ function isRateLimitError(text: string): boolean {
|
||||
if (isBillingError(text)) {
|
||||
return false;
|
||||
}
|
||||
return RATE_LIMIT_PATTERNS.some((pattern) =>
|
||||
lowerText.includes(pattern.toLowerCase())
|
||||
);
|
||||
return RATE_LIMIT_PATTERNS.some((pattern) => lowerText.includes(pattern.toLowerCase()));
|
||||
}
|
||||
|
||||
function isBillingError(text: string): boolean {
|
||||
const lowerText = text.toLowerCase();
|
||||
return BILLING_ERROR_PATTERNS.some((pattern) =>
|
||||
lowerText.includes(pattern.toLowerCase())
|
||||
);
|
||||
return BILLING_ERROR_PATTERNS.some((pattern) => lowerText.includes(pattern.toLowerCase()));
|
||||
}
|
||||
|
||||
function containsAuthError(text: string): boolean {
|
||||
const lowerText = text.toLowerCase();
|
||||
return AUTH_ERROR_PATTERNS.some((pattern) =>
|
||||
lowerText.includes(pattern.toLowerCase())
|
||||
);
|
||||
return AUTH_ERROR_PATTERNS.some((pattern) => lowerText.includes(pattern.toLowerCase()));
|
||||
}
|
||||
|
||||
export function createVerifyClaudeAuthHandler() {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// Get the auth method from the request body
|
||||
const { authMethod } = req.body as { authMethod?: "cli" | "api_key" };
|
||||
const { authMethod } = req.body as { authMethod?: 'cli' | 'api_key' };
|
||||
|
||||
logger.info(
|
||||
`[Setup] Verifying Claude authentication using method: ${
|
||||
authMethod || "auto"
|
||||
}`
|
||||
);
|
||||
logger.info(`[Setup] Verifying Claude authentication using method: ${authMethod || 'auto'}`);
|
||||
|
||||
// Create an AbortController with a 30-second timeout
|
||||
const abortController = new AbortController();
|
||||
const timeoutId = setTimeout(() => abortController.abort(), 30000);
|
||||
|
||||
let authenticated = false;
|
||||
let errorMessage = "";
|
||||
let errorMessage = '';
|
||||
let receivedAnyContent = false;
|
||||
|
||||
// Save original env values
|
||||
@@ -99,25 +89,23 @@ export function createVerifyClaudeAuthHandler() {
|
||||
|
||||
try {
|
||||
// Configure environment based on auth method
|
||||
if (authMethod === "cli") {
|
||||
if (authMethod === 'cli') {
|
||||
// For CLI verification, remove any API key so it uses CLI credentials only
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
logger.info(
|
||||
"[Setup] Cleared API key environment for CLI verification"
|
||||
);
|
||||
} else if (authMethod === "api_key") {
|
||||
logger.info('[Setup] Cleared API key environment for CLI verification');
|
||||
} else if (authMethod === 'api_key') {
|
||||
// For API key verification, ensure we're using the stored API key
|
||||
const storedApiKey = getApiKey("anthropic");
|
||||
const storedApiKey = getApiKey('anthropic');
|
||||
if (storedApiKey) {
|
||||
process.env.ANTHROPIC_API_KEY = storedApiKey;
|
||||
logger.info("[Setup] Using stored API key for verification");
|
||||
logger.info('[Setup] Using stored API key for verification');
|
||||
} else {
|
||||
// Check env var
|
||||
if (!process.env.ANTHROPIC_API_KEY) {
|
||||
res.json({
|
||||
success: true,
|
||||
authenticated: false,
|
||||
error: "No API key configured. Please enter an API key first.",
|
||||
error: 'No API key configured. Please enter an API key first.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -128,7 +116,7 @@ export function createVerifyClaudeAuthHandler() {
|
||||
const stream = query({
|
||||
prompt: "Reply with only the word 'ok'",
|
||||
options: {
|
||||
model: "claude-sonnet-4-20250514",
|
||||
model: 'claude-sonnet-4-20250514',
|
||||
maxTurns: 1,
|
||||
allowedTools: [],
|
||||
abortController,
|
||||
@@ -141,50 +129,50 @@ export function createVerifyClaudeAuthHandler() {
|
||||
for await (const msg of stream) {
|
||||
const msgStr = JSON.stringify(msg);
|
||||
allMessages.push(msgStr);
|
||||
logger.info("[Setup] Stream message:", msgStr.substring(0, 500));
|
||||
logger.info('[Setup] Stream message:', msgStr.substring(0, 500));
|
||||
|
||||
// Check for billing errors FIRST - these should fail verification
|
||||
if (isBillingError(msgStr)) {
|
||||
logger.error("[Setup] Found billing error in message");
|
||||
logger.error('[Setup] Found billing error in message');
|
||||
errorMessage =
|
||||
"Credit balance is too low. Please add credits to your Anthropic account at console.anthropic.com";
|
||||
'Credit balance is too low. Please add credits to your Anthropic account at console.anthropic.com';
|
||||
authenticated = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if any part of the message contains auth errors
|
||||
if (containsAuthError(msgStr)) {
|
||||
logger.error("[Setup] Found auth error in message");
|
||||
if (authMethod === "cli") {
|
||||
logger.error('[Setup] Found auth error in message');
|
||||
if (authMethod === 'cli') {
|
||||
errorMessage =
|
||||
"CLI authentication failed. Please run 'claude login' in your terminal to authenticate.";
|
||||
} else {
|
||||
errorMessage = "API key is invalid or has been revoked.";
|
||||
errorMessage = 'API key is invalid or has been revoked.';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check specifically for assistant messages with text content
|
||||
if (msg.type === "assistant" && (msg as any).message?.content) {
|
||||
if (msg.type === 'assistant' && (msg as any).message?.content) {
|
||||
const content = (msg as any).message.content;
|
||||
if (Array.isArray(content)) {
|
||||
for (const block of content) {
|
||||
if (block.type === "text" && block.text) {
|
||||
if (block.type === 'text' && block.text) {
|
||||
const text = block.text;
|
||||
logger.info("[Setup] Assistant text:", text);
|
||||
logger.info('[Setup] Assistant text:', text);
|
||||
|
||||
if (containsAuthError(text)) {
|
||||
if (authMethod === "cli") {
|
||||
if (authMethod === 'cli') {
|
||||
errorMessage =
|
||||
"CLI authentication failed. Please run 'claude login' in your terminal to authenticate.";
|
||||
} else {
|
||||
errorMessage = "API key is invalid or has been revoked.";
|
||||
errorMessage = 'API key is invalid or has been revoked.';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Valid text response that's not an error
|
||||
if (text.toLowerCase().includes("ok") || text.length > 0) {
|
||||
if (text.toLowerCase().includes('ok') || text.length > 0) {
|
||||
receivedAnyContent = true;
|
||||
}
|
||||
}
|
||||
@@ -193,34 +181,30 @@ export function createVerifyClaudeAuthHandler() {
|
||||
}
|
||||
|
||||
// Check for result messages
|
||||
if (msg.type === "result") {
|
||||
if (msg.type === 'result') {
|
||||
const resultStr = JSON.stringify(msg);
|
||||
|
||||
// First check for billing errors - these should FAIL verification
|
||||
if (isBillingError(resultStr)) {
|
||||
logger.error(
|
||||
"[Setup] Billing error detected - insufficient credits"
|
||||
);
|
||||
logger.error('[Setup] Billing error detected - insufficient credits');
|
||||
errorMessage =
|
||||
"Credit balance is too low. Please add credits to your Anthropic account at console.anthropic.com";
|
||||
'Credit balance is too low. Please add credits to your Anthropic account at console.anthropic.com';
|
||||
authenticated = false;
|
||||
break;
|
||||
}
|
||||
// Check if it's a rate limit error - should FAIL verification
|
||||
else if (isRateLimitError(resultStr)) {
|
||||
logger.warn(
|
||||
"[Setup] Rate limit detected - treating as unverified"
|
||||
);
|
||||
logger.warn('[Setup] Rate limit detected - treating as unverified');
|
||||
errorMessage =
|
||||
"Rate limit reached. Please wait a while before trying again or upgrade your plan.";
|
||||
'Rate limit reached. Please wait a while before trying again or upgrade your plan.';
|
||||
authenticated = false;
|
||||
break;
|
||||
} else if (containsAuthError(resultStr)) {
|
||||
if (authMethod === "cli") {
|
||||
if (authMethod === 'cli') {
|
||||
errorMessage =
|
||||
"CLI authentication failed. Please run 'claude login' in your terminal to authenticate.";
|
||||
} else {
|
||||
errorMessage = "API key is invalid or has been revoked.";
|
||||
errorMessage = 'API key is invalid or has been revoked.';
|
||||
}
|
||||
} else {
|
||||
// Got a result without errors
|
||||
@@ -236,60 +220,48 @@ export function createVerifyClaudeAuthHandler() {
|
||||
authenticated = true;
|
||||
} else {
|
||||
// No content received - might be an issue
|
||||
logger.warn("[Setup] No content received from stream");
|
||||
logger.warn("[Setup] All messages:", allMessages.join("\n"));
|
||||
errorMessage =
|
||||
"No response received from Claude. Please check your authentication.";
|
||||
logger.warn('[Setup] No content received from stream');
|
||||
logger.warn('[Setup] All messages:', allMessages.join('\n'));
|
||||
errorMessage = 'No response received from Claude. Please check your authentication.';
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const errMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
const errMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
logger.error("[Setup] Claude auth verification exception:", errMessage);
|
||||
logger.error('[Setup] Claude auth verification exception:', errMessage);
|
||||
|
||||
// Check for billing errors FIRST - these always fail
|
||||
if (isBillingError(errMessage)) {
|
||||
authenticated = false;
|
||||
errorMessage =
|
||||
"Credit balance is too low. Please add credits to your Anthropic account at console.anthropic.com";
|
||||
'Credit balance is too low. Please add credits to your Anthropic account at console.anthropic.com';
|
||||
}
|
||||
// Check for rate limit in exception - should FAIL verification
|
||||
else if (isRateLimitError(errMessage)) {
|
||||
authenticated = false;
|
||||
errorMessage =
|
||||
"Rate limit reached. Please wait a while before trying again or upgrade your plan.";
|
||||
logger.warn(
|
||||
"[Setup] Rate limit in exception - treating as unverified"
|
||||
);
|
||||
'Rate limit reached. Please wait a while before trying again or upgrade your plan.';
|
||||
logger.warn('[Setup] Rate limit in exception - treating as unverified');
|
||||
}
|
||||
// If we already determined auth was successful, keep it
|
||||
else if (authenticated) {
|
||||
logger.info("[Setup] Auth already confirmed, ignoring exception");
|
||||
logger.info('[Setup] Auth already confirmed, ignoring exception');
|
||||
}
|
||||
// Check for auth-related errors in exception
|
||||
else if (containsAuthError(errMessage)) {
|
||||
if (authMethod === "cli") {
|
||||
if (authMethod === 'cli') {
|
||||
errorMessage =
|
||||
"CLI authentication failed. Please run 'claude login' in your terminal to authenticate.";
|
||||
} else {
|
||||
errorMessage = "API key is invalid or has been revoked.";
|
||||
errorMessage = 'API key is invalid or has been revoked.';
|
||||
}
|
||||
} else if (
|
||||
errMessage.includes("abort") ||
|
||||
errMessage.includes("timeout")
|
||||
) {
|
||||
errorMessage = "Verification timed out. Please try again.";
|
||||
} else if (
|
||||
errMessage.includes("exit") &&
|
||||
errMessage.includes("code 1")
|
||||
) {
|
||||
} else if (errMessage.includes('abort') || errMessage.includes('timeout')) {
|
||||
errorMessage = 'Verification timed out. Please try again.';
|
||||
} else if (errMessage.includes('exit') && errMessage.includes('code 1')) {
|
||||
// Process exited with code 1 but we might have gotten rate limit info in the stream
|
||||
// Check if we received any content that indicated auth worked
|
||||
if (receivedAnyContent && !errorMessage) {
|
||||
authenticated = true;
|
||||
logger.info(
|
||||
"[Setup] Process exit 1 but content received - auth valid"
|
||||
);
|
||||
logger.info('[Setup] Process exit 1 but content received - auth valid');
|
||||
} else if (!errorMessage) {
|
||||
errorMessage = errMessage;
|
||||
}
|
||||
@@ -301,13 +273,13 @@ export function createVerifyClaudeAuthHandler() {
|
||||
// Restore original environment
|
||||
if (originalAnthropicKey !== undefined) {
|
||||
process.env.ANTHROPIC_API_KEY = originalAnthropicKey;
|
||||
} else if (authMethod === "cli") {
|
||||
} else if (authMethod === 'cli') {
|
||||
// If we cleared it and there was no original, keep it cleared
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("[Setup] Verification result:", {
|
||||
logger.info('[Setup] Verification result:', {
|
||||
authenticated,
|
||||
errorMessage,
|
||||
authMethod,
|
||||
@@ -319,11 +291,11 @@ export function createVerifyClaudeAuthHandler() {
|
||||
error: errorMessage || undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("[Setup] Verify Claude auth endpoint error:", error);
|
||||
logger.error('[Setup] Verify Claude auth endpoint error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
authenticated: false,
|
||||
error: error instanceof Error ? error.message : "Verification failed",
|
||||
error: error instanceof Error ? error.message : 'Verification failed',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user