feature/codex-cli

This commit is contained in:
DhanushSantosh
2026-01-06 04:52:25 +05:30
parent 4d4025ca06
commit a57dcc170d
54 changed files with 5562 additions and 91 deletions

View File

@@ -93,6 +93,9 @@ export {
getClaudeSettingsPath,
getClaudeStatsCachePath,
getClaudeProjectsDir,
getCodexCliPaths,
getCodexConfigDir,
getCodexAuthPath,
getShellPaths,
getExtendedPath,
// Node.js paths
@@ -120,6 +123,9 @@ export {
findClaudeCliPath,
getClaudeAuthIndicators,
type ClaudeAuthIndicators,
findCodexCliPath,
getCodexAuthIndicators,
type CodexAuthIndicators,
// Electron userData operations
setElectronUserDataPath,
getElectronUserDataPath,

View File

@@ -71,6 +71,49 @@ export function getClaudeCliPaths(): string[] {
];
}
/**
* Get common paths where Codex CLI might be installed
*/
export function getCodexCliPaths(): string[] {
const isWindows = process.platform === 'win32';
if (isWindows) {
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
return [
path.join(os.homedir(), '.local', 'bin', 'codex.exe'),
path.join(appData, 'npm', 'codex.cmd'),
path.join(appData, 'npm', 'codex'),
path.join(appData, '.npm-global', 'bin', 'codex.cmd'),
path.join(appData, '.npm-global', 'bin', 'codex'),
];
}
return [
path.join(os.homedir(), '.local', 'bin', 'codex'),
'/opt/homebrew/bin/codex',
'/usr/local/bin/codex',
path.join(os.homedir(), '.npm-global', 'bin', 'codex'),
];
}
const CODEX_CONFIG_DIR_NAME = '.codex';
const CODEX_AUTH_FILENAME = 'auth.json';
const CODEX_TOKENS_KEY = 'tokens';
/**
* Get the Codex configuration directory path
*/
export function getCodexConfigDir(): string {
return path.join(os.homedir(), CODEX_CONFIG_DIR_NAME);
}
/**
* Get path to Codex auth file
*/
export function getCodexAuthPath(): string {
return path.join(getCodexConfigDir(), CODEX_AUTH_FILENAME);
}
/**
* Get the Claude configuration directory path
*/
@@ -413,6 +456,11 @@ function getAllAllowedSystemPaths(): string[] {
getClaudeSettingsPath(),
getClaudeStatsCachePath(),
getClaudeProjectsDir(),
// Codex CLI paths
...getCodexCliPaths(),
// Codex config directory and files
getCodexConfigDir(),
getCodexAuthPath(),
// Shell paths
...getShellPaths(),
// Node.js system paths
@@ -432,6 +480,8 @@ function getAllAllowedSystemDirs(): string[] {
// Claude config
getClaudeConfigDir(),
getClaudeProjectsDir(),
// Codex config
getCodexConfigDir(),
// Version managers (need recursive access for version directories)
...getNvmPaths(),
...getFnmPaths(),
@@ -740,6 +790,10 @@ export async function findClaudeCliPath(): Promise<string | null> {
return findFirstExistingPath(getClaudeCliPaths());
}
export async function findCodexCliPath(): Promise<string | null> {
return findFirstExistingPath(getCodexCliPaths());
}
/**
* Get Claude authentication status by checking various indicators
*/
@@ -818,3 +872,56 @@ export async function getClaudeAuthIndicators(): Promise<ClaudeAuthIndicators> {
return result;
}
export interface CodexAuthIndicators {
hasAuthFile: boolean;
hasOAuthToken: boolean;
hasApiKey: boolean;
}
const CODEX_OAUTH_KEYS = ['access_token', 'oauth_token'] as const;
const CODEX_API_KEY_KEYS = ['api_key', 'OPENAI_API_KEY'] as const;
function hasNonEmptyStringField(record: Record<string, unknown>, keys: readonly string[]): boolean {
return keys.some((key) => typeof record[key] === 'string' && record[key]);
}
function getNestedTokens(record: Record<string, unknown>): Record<string, unknown> | null {
const tokens = record[CODEX_TOKENS_KEY];
if (tokens && typeof tokens === 'object' && !Array.isArray(tokens)) {
return tokens as Record<string, unknown>;
}
return null;
}
export async function getCodexAuthIndicators(): Promise<CodexAuthIndicators> {
const result: CodexAuthIndicators = {
hasAuthFile: false,
hasOAuthToken: false,
hasApiKey: false,
};
try {
const authContent = await systemPathReadFile(getCodexAuthPath());
result.hasAuthFile = true;
try {
const authJson = JSON.parse(authContent) as Record<string, unknown>;
result.hasOAuthToken = hasNonEmptyStringField(authJson, CODEX_OAUTH_KEYS);
result.hasApiKey = hasNonEmptyStringField(authJson, CODEX_API_KEY_KEYS);
const nestedTokens = getNestedTokens(authJson);
if (nestedTokens) {
result.hasOAuthToken =
result.hasOAuthToken || hasNonEmptyStringField(nestedTokens, CODEX_OAUTH_KEYS);
result.hasApiKey =
result.hasApiKey || hasNonEmptyStringField(nestedTokens, CODEX_API_KEY_KEYS);
}
} catch {
// Ignore parse errors; file exists but contents are unreadable
}
} catch {
// Auth file not found or inaccessible
}
return result;
}