diff --git a/server/__tests__/codex-home.test.js b/server/__tests__/codex-home.test.js new file mode 100644 index 00000000..15a3f9ab --- /dev/null +++ b/server/__tests__/codex-home.test.js @@ -0,0 +1,41 @@ +/** + * @file Tests for codex-home.js: CODEX_HOME resolution and related path + * resolutions for Codex project and surface directories. + * @author Nguyễn Ngọc Trí Vĩ + */ + +const path = require("node:path"); +const os = require("node:os"); + +const { describe, it, beforeEach } = require("node:test"); +const assert = require("node:assert/strict"); + +describe("codex-home", () => { + beforeEach(() => { + // Clear the require cache so getCodexHome() reads the fresh env var + delete require.cache[require.resolve("../lib/codex-home")]; + }); + + it("honours CODEX_HOME over the home directory", () => { + process.env.CODEX_HOME = "/tmp/injected-codex"; + delete require.cache[require.resolve("../lib/codex-home")]; + assert.equal(require("../lib/codex-home").getCodexHome(), "/tmp/injected-codex"); + }); + + it("puts the project surfaces under /.codex", () => { + const { getProjectCodexDir } = require("../lib/codex-home"); + assert.equal(getProjectCodexDir("/repo"), path.join("/repo", ".codex")); + }); + + it("returns the cwd as the project root", () => { + const { getProjectRoot } = require("../lib/codex-home"); + assert.equal(getProjectRoot("/repo"), "/repo"); + }); + + it("defaults to ~/.codex when CODEX_HOME is not set", () => { + delete process.env.CODEX_HOME; + delete require.cache[require.resolve("../lib/codex-home")]; + const { getCodexHome } = require("../lib/codex-home"); + assert.equal(getCodexHome(), path.join(os.homedir(), ".codex")); + }); +}); diff --git a/server/lib/codex-home.js b/server/lib/codex-home.js new file mode 100644 index 00000000..448cffad --- /dev/null +++ b/server/lib/codex-home.js @@ -0,0 +1,49 @@ +/** + * @file Centralized Codex home directory path management. Resolves the Codex + * home directory, project-level Codex surfaces, and project roots. Supports a + * custom root via the CODEX_HOME environment variable so the dashboard can + * track non-standard Codex installations. + * @author Nguyễn Ngọc Trí Vĩ + */ + +const path = require("node:path"); +const os = require("node:os"); + +/** + * The Codex home directory. Resolves to $CODEX_HOME if set, else + * ~/.codex. Always reads process.env at call time so tests can inject. + * + * @returns {string} Absolute path to the Codex home directory. + */ +function getCodexHome() { + return process.env.CODEX_HOME || path.join(os.homedir(), ".codex"); +} + +/** + * The directory where Codex stores per-project surfaces (rollout journals, + * SQLite mirrors, etc.) for a project at the given working directory. + * + * @param {string} cwd Absolute path to a project root. + * @returns {string} Absolute path to /.codex. + */ +function getProjectCodexDir(cwd) { + return path.join(cwd, ".codex"); +} + +/** + * The project root. For now, this is just an identity function — cwd is + * the project root. Exists as a contract for consistency with claude-home.js + * and to anchor the path-resolution hierarchy. + * + * @param {string} cwd Absolute path to a project root (where AGENTS.md lives). + * @returns {string} The same cwd. + */ +function getProjectRoot(cwd) { + return cwd; +} + +module.exports = { + getCodexHome, + getProjectCodexDir, + getProjectRoot, +}; diff --git a/server/lib/codex-ingest.js b/server/lib/codex-ingest.js index bcc0ddf6..c40257ae 100644 --- a/server/lib/codex-ingest.js +++ b/server/lib/codex-ingest.js @@ -33,6 +33,7 @@ const fs = require("node:fs"); const path = require("node:path"); const os = require("node:os"); +const { getCodexHome } = require("./codex-home"); // `*** Update File: ` lines inside an apply_patch heredoc. const PATCH_FILE_RE = /^\*\*\* (Add|Update|Delete) File: (.+)$/gm; @@ -98,10 +99,6 @@ function eventsFromPayload(payload) { return [{ tool_name: "Bash", tool_input: { command: commandFromPayload(payload, text) } }]; } -function codexHome() { - return process.env.CODEX_HOME || path.join(os.homedir(), ".codex"); -} - /** Every rollout journal under sessions/, newest mtime first, capped at MAX_FILES. */ function rolloutFiles() { const out = []; @@ -126,7 +123,7 @@ function rolloutFiles() { } } }; - walk(path.join(codexHome(), "sessions"), 0); + walk(path.join(getCodexHome(), "sessions"), 0); return out.sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES); }