2026-07-12 16:21:38 +03:00
import fs from 'node:fs' ;
import os from 'node:os' ;
import path from 'node:path' ;
import { execFileSync } from 'node:child_process' ;
2026-09-01 19:22:09 +03:00
import { fetchExeDevUsage } from './exeDevQuota' ;
2026-09-07 19:41:40 +03:00
import { fetchOllamaUsage } from './ollamaQuota' ;
2026-07-12 16:21:38 +03:00
2026-09-01 19:22:09 +03:00
export type ManagedProvider = 'exe-dev' | 'ollama-cloud' | 'cursor' ;
2026-07-12 16:21:38 +03:00
export type ManagedCredential = Record < string , string >;
2026-09-01 19:22:09 +03:00
const providers = new Set < ManagedProvider >([ 'exe-dev' , 'ollama-cloud' , 'cursor' ]);
2026-07-12 16:21:38 +03:00
const directory = () => path . join ( process . env . OPENCHAMBER_DATA_DIR ? path . resolve ( process . env . OPENCHAMBER_DATA_DIR ) : path . join ( os . homedir (), '.config' , 'openchamber' ), 'quota' );
const target = ( provider : ManagedProvider ) => {
if ( ! providers . has ( provider )) throw new Error ( 'Unsupported credential provider' );
return path . join ( directory (), ` ${ provider } .json` );
};
const clean = ( value : unknown ) => typeof value === 'string' && ! /[\r\n]/ . test ( value ) ? value . trim () : '' ;
export const normalizeCredential = ( provider : ManagedProvider , value : unknown ) : ManagedCredential | null => {
const data = value && typeof value === 'object' ? value as Record < string , unknown > : {};
2026-09-01 19:22:09 +03:00
if ( provider === 'exe-dev' ) return clean ( data . usageToken ) ? { usageToken : clean ( data . usageToken ) } : null ;
2026-07-12 16:21:38 +03:00
if ( provider === 'ollama-cloud' ) return clean ( data . cookie ) ? { cookie : clean ( data . cookie ) } : null ;
const accessToken = clean ( data . accessToken );
const refreshToken = clean ( data . refreshToken );
return accessToken || refreshToken ? { accessToken , refreshToken } : null ;
};
export const readCredential = ( provider : ManagedProvider ) => {
try { return normalizeCredential ( provider , JSON . parse ( fs . readFileSync ( target ( provider ), 'utf8' ))); }
catch ( error ) { if (( error as { code? : string }). code !== 'ENOENT' ) console . warn ( `Failed to read ${ provider } quota credentials` ); return null ; }
};
export const credentialStatus = ( provider : ManagedProvider ) => {
const value = readCredential ( provider );
if ( ! value ) return { configured : false };
2026-08-12 01:49:22 +03:00
return { configured : true , ...( provider === 'cursor' ? { hasRefreshToken : Boolean ( value . refreshToken ) } : {}), secretMasked : '••••••••' };
2026-07-12 16:21:38 +03:00
};
export const writeCredential = ( provider : ManagedProvider , value : ManagedCredential ) => {
const dir = directory (); const file = target ( provider ); const temp = ` ${ file } . ${ process . pid } . ${ Date . now () } .tmp` ;
fs . mkdirSync ( dir , { recursive : true , mode : 0o700 }); fs . chmodSync ( dir , 0 o700 );
try { fs . writeFileSync ( temp , ` ${ JSON . stringify ( value , null , 2 ) } \ n` , { mode : 0o600 }); fs . renameSync ( temp , file ); fs . chmodSync ( file , 0 o600 ); }
finally { if ( fs . existsSync ( temp )) fs . unlinkSync ( temp ); }
return credentialStatus ( provider );
};
export const deleteCredential = ( provider : ManagedProvider ) => { try { fs . unlinkSync ( target ( provider )); } catch ( error ) { if (( error as { code? : string }). code !== 'ENOENT' ) throw error ; } };
2026-08-12 01:49:22 +03:00
export const deleteLegacyOpenCodeGoCredential = () => {
try { fs . unlinkSync ( path . join ( directory (), 'opencode-go.json' )); } catch ( error ) { if (( error as { code? : string }). code !== 'ENOENT' ) throw error ; }
};
2026-07-12 16:21:38 +03:00
export const importCursorCredential = () => {
const db = path . join ( os . homedir (), 'Library' , 'Application Support' , 'Cursor' , 'User' , 'globalStorage' , 'state.vscdb' );
if ( process . platform !== 'darwin' || ! fs . existsSync ( db )) throw new Error ( 'Cursor credential import is unavailable' );
const rows = JSON . parse ( execFileSync ( 'sqlite3' , [ '-json' , db , "SELECT key,value FROM ItemTable WHERE key IN ('cursorAuth/accessToken','cursorAuth/refreshToken');" ], { encoding : 'utf8' , windowsHide : true , stdio : [ 'ignore' , 'pipe' , 'ignore' ] }) || '[]' ) as Array < { key : string ; value : string } > ;
const credential = normalizeCredential ( 'cursor' , { accessToken : rows.find (( row ) => row . key . endsWith ( 'accessToken' )) ? . value , refreshToken : rows.find (( row ) => row . key . endsWith ( 'refreshToken' )) ? . value });
if ( ! credential ) throw new Error ( 'Cursor credentials are unavailable' );
return credential ;
};
2026-09-07 19:41:40 +03:00
export const validateCredential = async ( provider : ManagedProvider , credential : ManagedCredential , fetchImpl : ( url : string , init : RequestInit ) => Promise < Response > = fetch ) => {
2026-09-01 19:22:09 +03:00
if ( provider === 'exe-dev' ) await fetchExeDevUsage ( credential . usageToken );
2026-07-12 16:21:38 +03:00
if ( provider === 'ollama-cloud' ) {
2026-09-07 19:41:40 +03:00
await fetchOllamaUsage ( credential . cookie , fetchImpl );
2026-07-12 16:21:38 +03:00
}
if ( provider === 'cursor' ) {
if ( ! credential . accessToken && credential . refreshToken ) {
const refresh = await fetch ( 'https://api2.cursor.sh/oauth/token' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON.stringify ({ grant_type : 'refresh_token' , client_id : 'KbZUR41cY7W6zRSdpSUJ7I7mLYBKOCmB' , refresh_token : credential.refreshToken }), signal : AbortSignal.timeout ( 15 _000 ) });
const payload = await refresh . json (). catch (() => null ) as { access_token? : string } | null ;
if ( ! refresh . ok || ! payload ? . access_token ) throw new Error ( 'Cursor authentication failed' );
credential . accessToken = payload . access_token ;
}
if ( ! credential . accessToken ) throw new Error ( 'Cursor access token is required' );
const response = await fetch ( 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage' , { method : 'POST' , headers : { Authorization : `Bearer ${ credential . accessToken } ` , 'Content-Type' : 'application/json' , 'Connect-Protocol-Version' : '1' }, body : '{}' , signal : AbortSignal.timeout ( 15 _000 ) });
if ( ! response . ok ) throw new Error ( 'Cursor authentication failed' );
}
};