diff --git a/.env.example b/.env.example index 94ecea8..e813fa9 100644 --- a/.env.example +++ b/.env.example @@ -37,4 +37,10 @@ APP_PORT=3000 # NPM_CONFIG_CACHE=/app/data/npm-cache # XDG_CACHE_HOME=/app/data/.cache +# Optional CLI OAuth credential file overrides (for codex-cli / gemini-cli provider auth checks). +# 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 +# GEMINI_SETTINGS_FILE=/home/node/.gemini/settings.json + APP_BASE_URL=http://localhost:3000 diff --git a/README.md b/README.md index b64ddd6..00dea00 100644 --- a/README.md +++ b/README.md @@ -216,6 +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`) | ## Data Persistence @@ -258,13 +261,17 @@ Use one host consistently. Browser storage/cookies are origin-scoped. 2. Docker container does not become healthy Run `docker compose logs --tail 200 app` and verify `.env` values. -3. Linux Docker permissions issues +3. Codex/Gemini OAuth says "token file was not found" on VPS +Eggent reads OAuth files from the runtime user home (for Docker default user this is `/home/node`). +Run CLI login as that same user (`docker compose exec -u node app codex login`, `docker compose exec -u node app gemini`) or set `CODEX_AUTH_FILE` / `GEMINI_OAUTH_CREDS_FILE` / `GEMINI_SETTINGS_FILE` in `.env`. + +4. Linux Docker permissions issues Try with `sudo docker ...` or add your user to the `docker` group. -4. Build fails after dependency changes +5. Build fails after dependency changes Run `npm install` and retry `npm run build`. -5. Large downloads fail with `No space left on device` despite free server disk +6. Large downloads fail with `No space left on device` despite free server disk This usually means temp/cache paths are constrained in the runtime environment. Rebuild and restart with current compose defaults, then verify inside container: ```bash docker compose build --no-cache app @@ -272,7 +279,7 @@ docker compose up -d app docker compose exec app sh -lc 'df -h /tmp /app/data && echo "TMPDIR=$TMPDIR" && echo "PLAYWRIGHT_BROWSERS_PATH=$PLAYWRIGHT_BROWSERS_PATH"' ``` -6. `Process error: spawn python3 ENOENT` in Code Execution +7. `Process error: spawn python3 ENOENT` in Code Execution `python3` is missing in runtime environment. For Docker deploys: @@ -288,7 +295,7 @@ sudo apt-get update && sudo apt-get install -y python3 python3 --version ``` -7. `sh: 1: curl: not found` in Code Execution (terminal runtime) +8. `sh: 1: curl: not found` in Code Execution (terminal runtime) `curl` is missing in runtime environment. For Docker deploys: @@ -304,13 +311,13 @@ sudo apt-get update && sudo apt-get install -y curl curl --version ``` -8. `command not found` for common terminal/skill commands (`git`, `jq`, `rg`) +9. `command not found` for common terminal/skill commands (`git`, `jq`, `rg`) Install recommended CLI utilities: ```bash sudo apt-get update && sudo apt-get install -y git jq ripgrep ``` -9. `ModuleNotFoundError: No module named 'requests'` in Python Code Execution +10. `ModuleNotFoundError: No module named 'requests'` in Python Code Execution `requests` is missing in runtime environment. For Docker deploys: @@ -326,7 +333,7 @@ sudo apt-get update && sudo apt-get install -y python3-requests python3 -c "import requests; print(requests.__version__)" ``` -10. `/usr/bin/python3: No module named pip` when trying to install Python packages +11. `/usr/bin/python3: No module named pip` when trying to install Python packages `pip` is missing in runtime environment. For Docker deploys: @@ -342,7 +349,7 @@ sudo apt-get update && sudo apt-get install -y python3-pip python3 -m pip --version ``` -11. `apt-get install ...` fails in Code Execution with `Permission denied` +12. `apt-get install ...` fails in Code Execution with `Permission denied` Use sudo in terminal runtime: ```bash sudo apt-get update && sudo apt-get install -y ffmpeg diff --git a/src/lib/providers/provider-auth.ts b/src/lib/providers/provider-auth.ts index a1f351f..bf59e28 100644 --- a/src/lib/providers/provider-auth.ts +++ b/src/lib/providers/provider-auth.ts @@ -74,8 +74,20 @@ function readJsonObject(filePath: string): Record | null { } } +function resolveAuthPath(envName: string, defaultPath: string): string { + const envValue = process.env[envName]; + if (typeof envValue !== "string") { + return defaultPath; + } + const trimmed = envValue.trim(); + return trimmed ? trimmed : defaultPath; +} + function readCodexAuth(): { path: string; parsed: CodexAuthFile | null } { - const authPath = path.join(os.homedir(), ".codex", "auth.json"); + const authPath = resolveAuthPath( + "CODEX_AUTH_FILE", + path.join(os.homedir(), ".codex", "auth.json") + ); const parsed = readJsonObject(authPath) as CodexAuthFile | null; return { path: authPath, parsed }; } @@ -133,7 +145,7 @@ function checkCodexOauthStatus(): ProviderAuthStatus { message: "Codex CLI is not in OAuth mode.", detail: authMode ? `auth_mode=${authMode}. Run \`codex login\` with ChatGPT.` - : "auth_mode is missing in ~/.codex/auth.json", + : `auth_mode is missing in ${authPath}`, }; } @@ -160,20 +172,26 @@ function checkCodexOauthStatus(): ProviderAuthStatus { }; } -function readGeminiSettings(): Record | null { - const settingsPath = path.join(os.homedir(), ".gemini", "settings.json"); - return readJsonObject(settingsPath); +function readGeminiSettings(): { path: string; parsed: Record | null } { + const settingsPath = resolveAuthPath( + "GEMINI_SETTINGS_FILE", + path.join(os.homedir(), ".gemini", "settings.json") + ); + return { path: settingsPath, parsed: readJsonObject(settingsPath) }; } function readGeminiOauthCreds(): { path: string; parsed: GeminiOauthCreds | null } { - const credsPath = path.join(os.homedir(), ".gemini", "oauth_creds.json"); + const credsPath = resolveAuthPath( + "GEMINI_OAUTH_CREDS_FILE", + path.join(os.homedir(), ".gemini", "oauth_creds.json") + ); const parsed = readJsonObject(credsPath) as GeminiOauthCreds | null; return { path: credsPath, parsed }; } function resolveGeminiCredential(): ResolvedCliOAuthCredential { const { parsed: creds } = readGeminiOauthCreds(); - const settings = readGeminiSettings(); + const { path: settingsPath, parsed: settings } = readGeminiSettings(); if (!creds) { throw new Error("Gemini OAuth file is missing. Run `gemini` and login with Google."); } @@ -192,7 +210,9 @@ function resolveGeminiCredential(): ResolvedCliOAuthCredential { selectedTypeValue.startsWith("oauth"); if (!selectedOauth) { - throw new Error("Gemini CLI is not in OAuth mode. Switch auth to OAuth in Gemini CLI."); + throw new Error( + `Gemini CLI is not in OAuth mode. Switch auth to OAuth in Gemini CLI (settings: ${settingsPath}).` + ); } const accessToken = asNonEmptyString(creds.access_token); @@ -218,7 +238,7 @@ function resolveGeminiCredential(): ResolvedCliOAuthCredential { function checkGeminiOauthStatus(): ProviderAuthStatus { const { path: credsPath, parsed: creds } = readGeminiOauthCreds(); - const settings = readGeminiSettings(); + const { path: settingsPath, parsed: settings } = readGeminiSettings(); if (!creds) { return { @@ -256,7 +276,7 @@ function checkGeminiOauthStatus(): ProviderAuthStatus { message: "Gemini CLI is not in OAuth mode.", detail: selectedTypeValue ? `selectedType=${selectedTypeValue}; switch to OAuth in Gemini CLI` - : "selectedType is missing in ~/.gemini/settings.json", + : `selectedType is missing in ${settingsPath}`, }; }