codex: symlink directories for all skills

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
This commit is contained in:
Austin Horstman 2026-03-22 11:42:43 -05:00
parent 932ca46013
commit c6fe2944ad
5 changed files with 68 additions and 20 deletions

View file

@ -99,12 +99,14 @@ in
If an attribute set is used, the attribute name becomes the skill directory name,
and the value is either:
- Inline content as a string (creates {file}`<skills-dir>/<name>/SKILL.md`)
- A path to a file (creates {file}`<skills-dir>/<name>/SKILL.md`)
- A path to a directory (creates {file}`<skills-dir>/<name>/` with all files)
- Inline content as a string (creates a generated skill directory at {file}`<skills-dir>/<name>/`)
- A path to a file (creates a generated skill directory at {file}`<skills-dir>/<name>/`)
- A path to a directory (symlinks {file}`<skills-dir>/<name>/` to that directory)
If a path is used, it is expected to contain one folder per skill name, each
containing a {file}`SKILL.md`. The directory is symlinked to {file}`<skills-dir>/`.
containing a {file}`SKILL.md`. Each top-level skill entry is symlinked into
{file}`<skills-dir>/`, leaving {file}`<skills-dir>/` itself as a normal
directory so unmanaged skills can coexist.
The skills target directory depends on Codex version:
- {file}`~/.agents/skills` for Codex >= 0.94.0
@ -145,6 +147,30 @@ in
configFileName = if isTomlConfig then "config.toml" else "config.yaml";
skillsDir = if isAgentsSkillsSupported then ".agents/skills" else "${configDir}/skills";
# TODO: Remove this workaround once Codex supports symlinked SKILL.md
# files again. Upstream only supports symlinking the containing skill
# directory today: https://github.com/openai/codex/issues/10470
mkSkillDir =
content:
pkgs.writeTextDir "SKILL.md" (if lib.isPath content then builtins.readFile content else content);
skillSources =
if builtins.isAttrs cfg.skills then
cfg.skills
else if lib.isPath cfg.skills && lib.pathIsDirectory cfg.skills then
lib.mapAttrs (name: _type: cfg.skills + "/${name}") (builtins.readDir cfg.skills)
else
{ };
mkSkillEntry =
name: content:
if lib.isPath content && lib.pathIsDirectory content then
lib.nameValuePair "${skillsDir}/${name}" {
source = content;
}
else
lib.nameValuePair "${skillsDir}/${name}" {
source = mkSkillDir content;
};
transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) (
lib.mapAttrs (
_name: server:
@ -189,23 +215,8 @@ in
"${configDir}/AGENTS.md" = lib.mkIf (cfg.custom-instructions != "") {
text = cfg.custom-instructions;
};
"${skillsDir}" = lib.mkIf (lib.isPath cfg.skills) {
source = cfg.skills;
recursive = true;
};
}
// (lib.mapAttrs' (
name: content:
if lib.isPath content && lib.pathIsDirectory content then
lib.nameValuePair "${skillsDir}/${name}" {
source = content;
recursive = true;
}
else
lib.nameValuePair "${skillsDir}/${name}/SKILL.md" (
if lib.isPath content then { source = content; } else { text = content; }
)
) (if builtins.isAttrs cfg.skills then cfg.skills else { }));
// lib.mapAttrs' mkSkillEntry skillSources;
sessionVariables = mkIf useXdgDirectories {
CODEX_HOME = "${config.xdg.configHome}/codex";

View file

@ -13,7 +13,14 @@ in
};
nmt.script = ''
if [[ -L home-files/.agents/skills ]]; then
fail "Expected home-files/.agents/skills to remain a normal directory so unmanaged skills can coexist."
fi
assertLinkExists home-files/.agents/skills/skill-one
assertFileExists home-files/.agents/skills/skill-one/SKILL.md
if [[ -L home-files/.agents/skills/skill-one/SKILL.md ]]; then
fail "Expected home-files/.agents/skills/skill-one/SKILL.md to be a regular file inside a symlinked skill directory."
fi
assertFileContent home-files/.agents/skills/skill-one/SKILL.md \
${./skills-dir/skill-one/SKILL.md}
'';

View file

@ -17,7 +17,14 @@ in
};
nmt.script = ''
if [[ -L home-files/.codex/skills ]]; then
fail "Expected home-files/.codex/skills to remain a normal directory so unmanaged skills can coexist."
fi
assertLinkExists home-files/.codex/skills/inline-skill
assertFileExists home-files/.codex/skills/inline-skill/SKILL.md
if [[ -L home-files/.codex/skills/inline-skill/SKILL.md ]]; then
fail "Expected home-files/.codex/skills/inline-skill/SKILL.md to be a regular file inside a symlinked skill directory."
fi
assertFileContent home-files/.codex/skills/inline-skill/SKILL.md \
${builtins.toFile "expected-inline-skill.md" ''
# Inline Skill

View file

@ -10,7 +10,11 @@
};
nmt.script = ''
assertLinkExists home-files/.agents/skills/inline-skill
assertFileExists home-files/.agents/skills/inline-skill/SKILL.md
if [[ -L home-files/.agents/skills/inline-skill/SKILL.md ]]; then
fail "Expected home-files/.agents/skills/inline-skill/SKILL.md to be a regular file inside a symlinked skill directory."
fi
assertFileContent home-files/.agents/skills/inline-skill/SKILL.md \
${builtins.toFile "expected-inline-skill.md" ''
# Inline Skill

View file

@ -22,15 +22,34 @@ in
skills = {
inline-skill = inlineSkill;
file-skill = ./skill-file.md;
dir-skill = ./skills-dir/skill-one;
};
};
nmt.script = ''
if [[ -L home-files/.agents/skills ]]; then
fail "Expected home-files/.agents/skills to remain a normal directory so unmanaged skills can coexist."
fi
assertLinkExists home-files/.agents/skills/inline-skill
assertFileExists home-files/.agents/skills/inline-skill/SKILL.md
if [[ -L home-files/.agents/skills/inline-skill/SKILL.md ]]; then
fail "Expected home-files/.agents/skills/inline-skill/SKILL.md to be a regular file inside a symlinked skill directory."
fi
assertFileContent home-files/.agents/skills/inline-skill/SKILL.md \
${builtins.toFile "expected-inline-skill.md" inlineSkill}
assertLinkExists home-files/.agents/skills/file-skill
assertFileExists home-files/.agents/skills/file-skill/SKILL.md
if [[ -L home-files/.agents/skills/file-skill/SKILL.md ]]; then
fail "Expected home-files/.agents/skills/file-skill/SKILL.md to be a regular file inside a symlinked skill directory."
fi
assertFileContent home-files/.agents/skills/file-skill/SKILL.md \
${./skill-file.md}
assertLinkExists home-files/.agents/skills/dir-skill
assertFileExists home-files/.agents/skills/dir-skill/SKILL.md
if [[ -L home-files/.agents/skills/dir-skill/SKILL.md ]]; then
fail "Expected home-files/.agents/skills/dir-skill/SKILL.md to be a regular file inside a symlinked skill directory."
fi
assertFileContent home-files/.agents/skills/dir-skill/SKILL.md \
${./skills-dir/skill-one/SKILL.md}
'';
}