mirror of
https://github.com/eggent-ai/eggent.git
synced 2026-03-07 01:53:08 +00:00
Auto-discover Codex/Gemini OAuth files across common homes
This commit is contained in:
@@ -38,6 +38,7 @@ APP_PORT=3000
|
||||
# XDG_CACHE_HOME=/app/data/.cache
|
||||
|
||||
# Optional CLI OAuth credential file overrides (for codex-cli / gemini-cli provider auth checks).
|
||||
# If unset, Eggent auto-discovers files in common home directories.
|
||||
# Useful when Eggent runs under a different user than the CLI login user.
|
||||
# CODEX_AUTH_FILE=/home/node/.codex/auth.json
|
||||
# GEMINI_OAUTH_CREDS_FILE=/home/node/.gemini/oauth_creds.json
|
||||
|
||||
@@ -216,9 +216,9 @@ Main environment variables:
|
||||
| `PLAYWRIGHT_BROWSERS_PATH` | No | Browser install/cache path for Playwright (default: `/app/data/ms-playwright`) |
|
||||
| `NPM_CONFIG_CACHE` | No | npm cache directory for runtime installs (default: `/app/data/npm-cache`) |
|
||||
| `XDG_CACHE_HOME` | No | Generic CLI cache directory (default: `/app/data/.cache`) |
|
||||
| `CODEX_AUTH_FILE` | No | Explicit path to Codex OAuth file (default: `$HOME/.codex/auth.json`) |
|
||||
| `GEMINI_OAUTH_CREDS_FILE` | No | Explicit path to Gemini OAuth creds file (default: `$HOME/.gemini/oauth_creds.json`) |
|
||||
| `GEMINI_SETTINGS_FILE` | No | Explicit path to Gemini settings file (default: `$HOME/.gemini/settings.json`) |
|
||||
| `CODEX_AUTH_FILE` | No | Explicit path to Codex OAuth file (if unset, Eggent auto-discovers `.codex/auth.json` in common home dirs) |
|
||||
| `GEMINI_OAUTH_CREDS_FILE` | No | Explicit path to Gemini OAuth creds file (if unset, Eggent auto-discovers `.gemini/oauth_creds.json` in common home dirs) |
|
||||
| `GEMINI_SETTINGS_FILE` | No | Explicit path to Gemini settings file (if unset, Eggent auto-discovers `.gemini/settings.json` in common home dirs) |
|
||||
|
||||
## Data Persistence
|
||||
|
||||
|
||||
@@ -83,11 +83,88 @@ function resolveAuthPath(envName: string, defaultPath: string): string {
|
||||
return trimmed ? trimmed : defaultPath;
|
||||
}
|
||||
|
||||
function isReadableFile(filePath: string): boolean {
|
||||
try {
|
||||
return fs.statSync(filePath).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function listChildDirs(baseDir: string): string[] {
|
||||
try {
|
||||
return fs
|
||||
.readdirSync(baseDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
|
||||
.map((entry) => path.join(baseDir, entry.name));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function collectHomeCandidates(): string[] {
|
||||
const candidates = new Set<string>();
|
||||
|
||||
const addCandidate = (value: string | undefined | null) => {
|
||||
if (!value) return;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return;
|
||||
candidates.add(trimmed);
|
||||
};
|
||||
|
||||
addCandidate(os.homedir());
|
||||
addCandidate(process.env.HOME);
|
||||
addCandidate("/home/node");
|
||||
addCandidate("/root");
|
||||
addCandidate(path.join(process.cwd(), "data"));
|
||||
|
||||
for (const dir of listChildDirs("/home")) {
|
||||
addCandidate(dir);
|
||||
}
|
||||
for (const dir of listChildDirs("/Users")) {
|
||||
addCandidate(dir);
|
||||
}
|
||||
|
||||
return Array.from(candidates);
|
||||
}
|
||||
|
||||
function firstExistingFile(paths: string[]): string | null {
|
||||
for (const filePath of paths) {
|
||||
if (isReadableFile(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function discoverPath(defaultPath: string, relativePath: string): string {
|
||||
if (isReadableFile(defaultPath)) {
|
||||
return defaultPath;
|
||||
}
|
||||
const discovered =
|
||||
firstExistingFile(
|
||||
collectHomeCandidates().map((homeDir) => path.join(homeDir, relativePath))
|
||||
) || null;
|
||||
return discovered || defaultPath;
|
||||
}
|
||||
|
||||
function deriveGeminiSettingsPathFromCreds(credsPath: string): string | null {
|
||||
const parsed = path.parse(credsPath);
|
||||
if (parsed.base !== "oauth_creds.json") return null;
|
||||
if (path.basename(parsed.dir) !== ".gemini") return null;
|
||||
return path.join(parsed.dir, "settings.json");
|
||||
}
|
||||
|
||||
function readCodexAuth(): { path: string; parsed: CodexAuthFile | null } {
|
||||
const authPath = resolveAuthPath(
|
||||
const defaultPath = path.join(os.homedir(), ".codex", "auth.json");
|
||||
const configuredPath = resolveAuthPath(
|
||||
"CODEX_AUTH_FILE",
|
||||
path.join(os.homedir(), ".codex", "auth.json")
|
||||
defaultPath
|
||||
);
|
||||
const authPath =
|
||||
configuredPath === defaultPath
|
||||
? discoverPath(defaultPath, path.join(".codex", "auth.json"))
|
||||
: configuredPath;
|
||||
const parsed = readJsonObject(authPath) as CodexAuthFile | null;
|
||||
return { path: authPath, parsed };
|
||||
}
|
||||
@@ -173,25 +250,47 @@ function checkCodexOauthStatus(): ProviderAuthStatus {
|
||||
}
|
||||
|
||||
function readGeminiSettings(): { path: string; parsed: Record<string, unknown> | null } {
|
||||
const defaultPath = path.join(os.homedir(), ".gemini", "settings.json");
|
||||
const settingsPath = resolveAuthPath(
|
||||
"GEMINI_SETTINGS_FILE",
|
||||
path.join(os.homedir(), ".gemini", "settings.json")
|
||||
defaultPath
|
||||
);
|
||||
return { path: settingsPath, parsed: readJsonObject(settingsPath) };
|
||||
const resolvedSettingsPath =
|
||||
settingsPath === defaultPath
|
||||
? discoverPath(defaultPath, path.join(".gemini", "settings.json"))
|
||||
: settingsPath;
|
||||
return { path: resolvedSettingsPath, parsed: readJsonObject(resolvedSettingsPath) };
|
||||
}
|
||||
|
||||
function readGeminiOauthCreds(): { path: string; parsed: GeminiOauthCreds | null } {
|
||||
const defaultPath = path.join(os.homedir(), ".gemini", "oauth_creds.json");
|
||||
const credsPath = resolveAuthPath(
|
||||
"GEMINI_OAUTH_CREDS_FILE",
|
||||
path.join(os.homedir(), ".gemini", "oauth_creds.json")
|
||||
defaultPath
|
||||
);
|
||||
const parsed = readJsonObject(credsPath) as GeminiOauthCreds | null;
|
||||
return { path: credsPath, parsed };
|
||||
const resolvedCredsPath =
|
||||
credsPath === defaultPath
|
||||
? discoverPath(defaultPath, path.join(".gemini", "oauth_creds.json"))
|
||||
: credsPath;
|
||||
const parsed = readJsonObject(resolvedCredsPath) as GeminiOauthCreds | null;
|
||||
return { path: resolvedCredsPath, parsed };
|
||||
}
|
||||
|
||||
function resolveGeminiCredential(): ResolvedCliOAuthCredential {
|
||||
const { parsed: creds } = readGeminiOauthCreds();
|
||||
const { path: settingsPath, parsed: settings } = readGeminiSettings();
|
||||
const { path: credsPath, parsed: creds } = readGeminiOauthCreds();
|
||||
const settingsFromCreds = deriveGeminiSettingsPathFromCreds(credsPath);
|
||||
const settingsConfigured = process.env.GEMINI_SETTINGS_FILE?.trim();
|
||||
const { path: discoveredSettingsPath, parsed: discoveredSettings } = readGeminiSettings();
|
||||
const settingsPath =
|
||||
!settingsConfigured &&
|
||||
settingsFromCreds &&
|
||||
isReadableFile(settingsFromCreds)
|
||||
? settingsFromCreds
|
||||
: discoveredSettingsPath;
|
||||
const settings =
|
||||
settingsPath === discoveredSettingsPath
|
||||
? discoveredSettings
|
||||
: readJsonObject(settingsPath);
|
||||
if (!creds) {
|
||||
throw new Error("Gemini OAuth file is missing. Run `gemini` and login with Google.");
|
||||
}
|
||||
@@ -238,7 +337,19 @@ function resolveGeminiCredential(): ResolvedCliOAuthCredential {
|
||||
|
||||
function checkGeminiOauthStatus(): ProviderAuthStatus {
|
||||
const { path: credsPath, parsed: creds } = readGeminiOauthCreds();
|
||||
const { path: settingsPath, parsed: settings } = readGeminiSettings();
|
||||
const settingsFromCreds = deriveGeminiSettingsPathFromCreds(credsPath);
|
||||
const settingsConfigured = process.env.GEMINI_SETTINGS_FILE?.trim();
|
||||
const { path: discoveredSettingsPath, parsed: discoveredSettings } = readGeminiSettings();
|
||||
const settingsPath =
|
||||
!settingsConfigured &&
|
||||
settingsFromCreds &&
|
||||
isReadableFile(settingsFromCreds)
|
||||
? settingsFromCreds
|
||||
: discoveredSettingsPath;
|
||||
const settings =
|
||||
settingsPath === discoveredSettingsPath
|
||||
? discoveredSettings
|
||||
: readJsonObject(settingsPath);
|
||||
|
||||
if (!creds) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user