import { mkdir } from 'node:fs/promises'; import { join, resolve, sep } from 'node:path'; import { getDataDir } from 'ee-core/ps'; /** Codex 的 HOME:配置(config.toml)、skills、会话记录都落在这里 */ export function getCodexHome(): string { return join(getDataDir(), 'codex-home'); } /** 用户自装 skill 的目录;内置 skill 在同级的 .system/.curated/.experimental 下,不允许删改 */ export function getSkillsDir(): string { return join(getCodexHome(), 'skills'); } /** 我方自己的运行数据目录,刻意放在 CODEX_HOME 之外,避免被 Codex 当成项目内容扫描 */ export function getCodexDataDir(): string { return join(getDataDir(), 'codex-data'); } export function getEventsDir(): string { return join(getCodexDataDir(), 'events'); } export async function ensureCodexHome(): Promise { const codexHome = getCodexHome(); // 0700:目录里有 config.toml 与用户装的 skill,不给同机其它账户读 await mkdir(codexHome, { recursive: true, mode: 0o700 }); await mkdir(getSkillsDir(), { recursive: true }); await mkdir(getEventsDir(), { recursive: true }); return codexHome; } /** 目录名合法性:小写连字符,最长 64 */ export const SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+){0,9}$/u; export const MAX_SKILL_NAME_LENGTH = 64; /** 路径包含判断,用于拦住 skill 安装/删除时的越界与 `..` 穿越 */ export function isInsideDir(parentDir: string, target: string): boolean { const parent = resolve(parentDir); const child = resolve(target); if (child === parent) return false; return child.startsWith(parent.endsWith(sep) ? parent : parent + sep); }