File size: 4,938 Bytes
4e7af9b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | /**
* CLI Auth Provider REST API
*
* GET /api/cli-auth — read current CLI auth provider config from ~/.claude.json
* PUT /api/cli-auth — update CLI auth provider config in ~/.claude.json
* POST /api/cli-auth/invalidate — invalidate the cliProviderModelCache
*/
import { homedir } from 'node:os'
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { ApiError, errorResponse } from '../middleware/errorHandler.js'
type AuthProvider = 'anthropic' | 'openai' | 'openrouter' | 'local' | 'opencode' | 'nvidia'
type CliAuthConfig = {
authProvider: AuthProvider | null
nvidiaApiKey?: string
openRouterApiKey?: string
openAiApiKey?: string
openAiAccessToken?: string
openCodeApiKey?: string
openCodeModelName?: string
localBaseUrl?: string
localModelName?: string
}
function getClaudeJsonPath(): string {
return join(homedir(), '.claude.json')
}
function readCliAuthConfig(): CliAuthConfig {
try {
const raw = readFileSync(getClaudeJsonPath(), 'utf8')
const config = JSON.parse(raw) as Record<string, unknown>
return {
authProvider: (config.authProvider as AuthProvider) || null,
nvidiaApiKey: config.nvidiaApiKey as string | undefined,
openRouterApiKey: config.openRouterApiKey as string | undefined,
openAiApiKey: config.openAiApiKey as string | undefined,
openAiAccessToken: config.openAiAccessToken as string | undefined,
openCodeApiKey: config.openCodeApiKey as string | undefined,
openCodeModelName: config.openCodeModelName as string | undefined,
localBaseUrl: config.localBaseUrl as string | undefined,
localModelName: config.localModelName as string | undefined,
}
} catch {
return { authProvider: null }
}
}
function writeCliAuthConfig(updates: Partial<CliAuthConfig>): void {
const filePath = getClaudeJsonPath()
let config: Record<string, unknown> = {}
try {
const raw = readFileSync(filePath, 'utf8')
config = JSON.parse(raw) as Record<string, unknown>
} catch {
// file doesn't exist yet, start fresh
}
if (updates.authProvider !== undefined) {
config.authProvider = updates.authProvider || undefined
}
if (updates.nvidiaApiKey !== undefined) {
config.nvidiaApiKey = updates.nvidiaApiKey || undefined
}
if (updates.openRouterApiKey !== undefined) {
config.openRouterApiKey = updates.openRouterApiKey || undefined
}
if (updates.openAiApiKey !== undefined) {
config.openAiApiKey = updates.openAiApiKey || undefined
config.openAiAccessToken = undefined
config.openAiRefreshToken = undefined
config.openAiTokenExpiresAt = undefined
config.openAiWorkspaceId = undefined
}
if (updates.openAiAccessToken !== undefined) {
config.openAiAccessToken = updates.openAiAccessToken || undefined
config.openAiApiKey = undefined
config.openAiRefreshToken = undefined
config.openAiTokenExpiresAt = undefined
config.openAiWorkspaceId = undefined
}
if (updates.openCodeApiKey !== undefined) {
config.openCodeApiKey = updates.openCodeApiKey || undefined
}
if (updates.openCodeModelName !== undefined) {
config.openCodeModelName = updates.openCodeModelName || undefined
}
if (updates.localBaseUrl !== undefined) {
config.localBaseUrl = updates.localBaseUrl || undefined
}
if (updates.localModelName !== undefined) {
config.localModelName = updates.localModelName || undefined
}
writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n')
}
export async function handleCliAuthApi(
req: Request,
_url: URL,
segments: string[],
): Promise<Response> {
try {
const sub = segments[2]
if (sub === 'invalidate') {
if (req.method !== 'POST') throw methodNotAllowed(req.method)
const { invalidateCliProviderModelCache } = await import('./models.js')
invalidateCliProviderModelCache()
return Response.json({ ok: true })
}
if (!sub) {
if (req.method === 'GET') {
const config = readCliAuthConfig()
return Response.json(config)
}
if (req.method === 'PUT') {
const body = await parseJsonBody(req)
writeCliAuthConfig(body as Partial<CliAuthConfig>)
const { invalidateCliProviderModelCache } = await import('./models.js')
invalidateCliProviderModelCache()
return Response.json({ ok: true })
}
throw methodNotAllowed(req.method)
}
throw ApiError.notFound(`Unknown cli-auth endpoint: ${sub}`)
} catch (error) {
return errorResponse(error)
}
}
async function parseJsonBody(req: Request): Promise<Record<string, unknown>> {
try {
return (await req.json()) as Record<string, unknown>
} catch {
throw ApiError.badRequest('Invalid JSON body')
}
}
function methodNotAllowed(method: string): ApiError {
return new ApiError(405, `Method ${method} not allowed`, 'METHOD_NOT_ALLOWED')
}
|