@@ -12,29 +12,37 @@ import { getClaudeOAuthTokenFromEnv } from './env'
1212import type { ClientEnv } from '@codebuff/common/types/contracts/env'
1313import type { User } from '@codebuff/common/util/credentials'
1414
15- const credentialsSchema = z
16- . object ( {
17- default : userSchema ,
18- } )
19- . catchall ( userSchema )
15+ /**
16+ * Schema for Claude OAuth credentials.
17+ */
18+ const claudeOAuthSchema = z . object ( {
19+ accessToken : z . string ( ) ,
20+ refreshToken : z . string ( ) ,
21+ expiresAt : z . number ( ) ,
22+ connectedAt : z . number ( ) ,
23+ } )
24+
25+ /**
26+ * Unified schema for the credentials file.
27+ * Contains both Codebuff user credentials and Claude OAuth credentials.
28+ */
29+ const credentialsFileSchema = z . object ( {
30+ default : userSchema . optional ( ) ,
31+ claudeOAuth : claudeOAuthSchema . optional ( ) ,
32+ } )
2033
2134const ensureDirectoryExistsSync = ( dir : string ) => {
2235 if ( ! fs . existsSync ( dir ) ) {
2336 fs . mkdirSync ( dir , { recursive : true } )
2437 }
2538}
2639
27- export const userFromJson = (
28- json : string ,
29- profileName : string = 'default' ,
30- ) : User | undefined => {
40+ export const userFromJson = ( json : string ) : User | null => {
3141 try {
32- const allCredentials = credentialsSchema . parse ( JSON . parse ( json ) )
33- const profile = allCredentials [ profileName ]
34- return profile
35- } catch ( error ) {
36- console . error ( 'Error parsing user JSON:' , error )
37- return
42+ const credentials = credentialsFileSchema . parse ( JSON . parse ( json ) )
43+ return credentials . default ?? null
44+ } catch {
45+ return null
3846 }
3947}
4048
@@ -58,11 +66,6 @@ export const getCredentialsPath = (clientEnv: ClientEnv = env): string => {
5866 return path . join ( getConfigDir ( clientEnv ) , 'credentials.json' )
5967}
6068
61- // Legacy exports for backward compatibility - use getConfigDir() and getCredentialsPath() for testability
62- export const CONFIG_DIR = getConfigDir ( )
63- ensureDirectoryExistsSync ( CONFIG_DIR )
64- export const CREDENTIALS_PATH = getCredentialsPath ( )
65-
6669export const getUserCredentials = ( clientEnv : ClientEnv = env ) : User | null => {
6770 const credentialsPath = getCredentialsPath ( clientEnv )
6871 if ( ! fs . existsSync ( credentialsPath ) ) {
@@ -89,24 +92,6 @@ export interface ClaudeOAuthCredentials {
8992 connectedAt : number // Unix timestamp in milliseconds
9093}
9194
92- /**
93- * Schema for Claude OAuth credentials in the credentials file.
94- */
95- const claudeOAuthSchema = z . object ( {
96- accessToken : z . string ( ) ,
97- refreshToken : z . string ( ) ,
98- expiresAt : z . number ( ) ,
99- connectedAt : z . number ( ) ,
100- } )
101-
102- /**
103- * Extended credentials file schema that includes Claude OAuth.
104- */
105- const extendedCredentialsSchema = z . object ( {
106- default : userSchema . optional ( ) ,
107- claudeOAuth : claudeOAuthSchema . optional ( ) ,
108- } ) . catchall ( z . unknown ( ) )
109-
11095/**
11196 * Get Claude OAuth credentials from file or environment variable.
11297 * Environment variable takes precedence.
@@ -135,7 +120,7 @@ export const getClaudeOAuthCredentials = (
135120
136121 try {
137122 const credentialsFile = fs . readFileSync ( credentialsPath , 'utf8' )
138- const parsed = extendedCredentialsSchema . safeParse ( JSON . parse ( credentialsFile ) )
123+ const parsed = credentialsFileSchema . safeParse ( JSON . parse ( credentialsFile ) )
139124 if ( ! parsed . success || ! parsed . data . claudeOAuth ) {
140125 return null
141126 }
@@ -201,9 +186,7 @@ export const clearClaudeOAuthCredentials = (
201186 * Check if Claude OAuth credentials are valid (not expired).
202187 * Returns true if credentials exist and haven't expired.
203188 */
204- export const isClaudeOAuthValid = (
205- clientEnv : ClientEnv = env ,
206- ) : boolean => {
189+ export const isClaudeOAuthValid = ( clientEnv : ClientEnv = env ) : boolean => {
207190 const credentials = getClaudeOAuthCredentials ( clientEnv )
208191 if ( ! credentials ) {
209192 return false
@@ -237,17 +220,20 @@ export const refreshClaudeOAuthToken = async (
237220 // Start the refresh and store the promise
238221 refreshPromise = ( async ( ) => {
239222 try {
240- const response = await fetch ( 'https://console.anthropic.com/v1/oauth/token' , {
241- method : 'POST' ,
242- headers : {
243- 'Content-Type' : 'application/json' ,
223+ const response = await fetch (
224+ 'https://console.anthropic.com/v1/oauth/token' ,
225+ {
226+ method : 'POST' ,
227+ headers : {
228+ 'Content-Type' : 'application/json' ,
229+ } ,
230+ body : JSON . stringify ( {
231+ grant_type : 'refresh_token' ,
232+ refresh_token : credentials . refreshToken ,
233+ client_id : CLAUDE_OAUTH_CLIENT_ID ,
234+ } ) ,
244235 } ,
245- body : JSON . stringify ( {
246- grant_type : 'refresh_token' ,
247- refresh_token : credentials . refreshToken ,
248- client_id : CLAUDE_OAUTH_CLIENT_ID ,
249- } ) ,
250- } )
236+ )
251237
252238 if ( ! response . ok ) {
253239 // Refresh failed, clear credentials
@@ -284,7 +270,7 @@ export const refreshClaudeOAuthToken = async (
284270/**
285271 * Get valid Claude OAuth credentials, refreshing if necessary.
286272 * This is the main function to use when you need credentials for an API call.
287- *
273+ *
288274 * - Returns credentials immediately if valid (>5 min until expiry)
289275 * - Attempts refresh if token is expired or near-expiry
290276 * - Returns null if no credentials or refresh fails
0 commit comments