// codex_data_dir.js —— 会话相关功能使用的 Codex 数据目录,可由用户覆盖默认 ~/.codex。 const { app } = require("electron"); const fs = require("fs"); const os = require("os"); const path = require("path"); const DEFAULT_DIR = path.join(os.homedir(), ".codex"); let loaded = false; let configuredDir = null; function configFile() { return path.join(app.getPath("userData"), "codex-data-dir.json"); } function normalize(dir) { if (typeof dir !== "string" || !dir.trim() || !path.isAbsolute(dir.trim())) return null; return path.resolve(dir.trim()); } function load() { if (loaded) return; loaded = true; try { configuredDir = normalize(JSON.parse(fs.readFileSync(configFile(), "utf-8")).path); } catch {} } function get() { load(); return configuredDir || DEFAULT_DIR; } function isCodexDataDir(dir) { try { const names = fs.readdirSync(dir); return names.includes("sessions") || names.includes("archived_sessions") || names.includes("config.toml") || names.includes("auth.json") || names.some((name) => /^state_.*\.sqlite(?:-(?:wal|shm))?$/.test(name)); } catch { return false; } } function isDirectory(dir) { try { return fs.statSync(dir).isDirectory(); } catch { return false; } } function writeAtomic(file, text) { fs.mkdirSync(path.dirname(file), { recursive: true }); const tmp = file + ".tmp-" + process.pid + "-" + Date.now(); const fd = fs.openSync(tmp, "w", 0o600); try { fs.writeFileSync(fd, text, "utf-8"); fs.fsyncSync(fd); } finally { fs.closeSync(fd); } try { fs.renameSync(tmp, file); } finally { try { fs.rmSync(tmp, { force: true }); } catch {} } } function set(dir) { const next = normalize(dir); if (!next || !isDirectory(next)) { throw new Error("请选择存在的 Codex 数据目录"); } if (!isCodexDataDir(next)) { throw new Error("所选目录未找到 Codex 数据(sessions、config.toml 或 state_*.sqlite)"); } configuredDir = next === DEFAULT_DIR ? null : next; loaded = true; writeAtomic(configFile(), JSON.stringify({ path: configuredDir || "", updatedAt: new Date().toISOString() }, null, 2)); return info(); } function reset() { configuredDir = null; loaded = true; writeAtomic(configFile(), JSON.stringify({ path: "", updatedAt: new Date().toISOString() }, null, 2)); return info(); } function info() { const dir = get(); return { path: dir, isDefault: dir === DEFAULT_DIR, exists: isDirectory(dir) }; } module.exports = { get, set, reset, info, isCodexDataDir };