codex: add support for managed rules files
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
time = "2026-04-02T00:30:59+00:00";
|
||||
condition = config.programs.codex.enable;
|
||||
message = ''
|
||||
The `programs.codex.rules` option was added to manage Codex `.rules`
|
||||
files declaratively.
|
||||
|
||||
Each rule is written under `CODEX_HOME/rules/`, with attribute names
|
||||
mapped to `.rules` filenames automatically. Codex uses these rules for
|
||||
persistent command-prefix decisions such as allowing recurring safe
|
||||
escalations without prompting every time.
|
||||
'';
|
||||
}
|
||||
@@ -137,6 +137,30 @@ in
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
rules = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path);
|
||||
default = { };
|
||||
description = ''
|
||||
Codex rules files to manage under {file}`CODEX_HOME/rules/`.
|
||||
|
||||
The attribute name becomes the filename, with a {file}`.rules`
|
||||
extension added automatically. The value is either:
|
||||
- Inline content as a string
|
||||
- A path to an existing rules file
|
||||
|
||||
This is useful for declaratively managing persistent
|
||||
`prefix_rule()` definitions, including the default
|
||||
{file}`default.rules` allow-list Codex writes when you accept
|
||||
recurring approvals interactively.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
default = "prefix_rule(pattern = [\"nix\", \"build\"], decision = \"allow\")\n";
|
||||
github = ./codex/github.rules;
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
@@ -174,6 +198,11 @@ in
|
||||
lib.nameValuePair "${skillsDir}/${name}" {
|
||||
source = mkSkillDir content;
|
||||
};
|
||||
mkRuleEntry =
|
||||
name: content:
|
||||
lib.nameValuePair "${configDir}/rules/${name}.rules" (
|
||||
if isPathLikeContent content then { source = content; } else { text = content; }
|
||||
);
|
||||
|
||||
transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) (
|
||||
lib.mapAttrs (
|
||||
@@ -207,6 +236,12 @@ in
|
||||
assertion = !lib.isPath cfg.skills || lib.pathIsDirectory cfg.skills;
|
||||
message = "`programs.codex.skills` must be a directory when set to a path";
|
||||
}
|
||||
{
|
||||
assertion = lib.all (content: !(isPathLikeContent content && lib.pathIsDirectory content)) (
|
||||
lib.attrValues cfg.rules
|
||||
);
|
||||
message = "`programs.codex.rules` attribute values must be files when set to paths";
|
||||
}
|
||||
];
|
||||
|
||||
home = {
|
||||
@@ -220,7 +255,8 @@ in
|
||||
text = cfg.custom-instructions;
|
||||
};
|
||||
}
|
||||
// lib.mapAttrs' mkSkillEntry skillSources;
|
||||
// lib.mapAttrs' mkSkillEntry skillSources
|
||||
// lib.mapAttrs' mkRuleEntry cfg.rules;
|
||||
|
||||
sessionVariables = mkIf useXdgDirectories {
|
||||
CODEX_HOME = "${config.xdg.configHome}/codex";
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
codex-empty-settings = ./empty-settings.nix;
|
||||
codex-mcp-integration = ./mcp-integration.nix;
|
||||
codex-mcp-integration-with-override = ./mcp-integration-with-override.nix;
|
||||
codex-rules = ./rules.nix;
|
||||
codex-skills-inline = ./skills-inline.nix;
|
||||
codex-skills-inline-null-package = ./skills-inline-null-package.nix;
|
||||
codex-skills-inline-legacy-path = ./skills-inline-legacy-path.nix;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
programs.codex = {
|
||||
enable = true;
|
||||
rules = {
|
||||
default = builtins.toFile "default.rules" ''
|
||||
prefix_rule(
|
||||
pattern = ["git", "status"],
|
||||
decision = "allow",
|
||||
justification = "Allow routine status checks",
|
||||
)
|
||||
'';
|
||||
github = ''
|
||||
prefix_rule(
|
||||
pattern = ["gh", "pr", "view"],
|
||||
decision = "prompt",
|
||||
justification = "Review PRs with confirmation",
|
||||
)
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.codex/rules/default.rules
|
||||
assertFileContent home-files/.codex/rules/default.rules \
|
||||
${builtins.toFile "expected-default.rules" ''
|
||||
prefix_rule(
|
||||
pattern = ["git", "status"],
|
||||
decision = "allow",
|
||||
justification = "Allow routine status checks",
|
||||
)
|
||||
''}
|
||||
|
||||
assertFileExists home-files/.codex/rules/github.rules
|
||||
assertFileContent home-files/.codex/rules/github.rules \
|
||||
${builtins.toFile "expected-github.rules" ''
|
||||
prefix_rule(
|
||||
pattern = ["gh", "pr", "view"],
|
||||
decision = "prompt",
|
||||
justification = "Review PRs with confirmation",
|
||||
)
|
||||
''}
|
||||
'';
|
||||
}
|
||||
@@ -11,6 +11,13 @@ in
|
||||
programs.codex = {
|
||||
enable = true;
|
||||
package = codexPackage;
|
||||
rules.default = ''
|
||||
prefix_rule(
|
||||
pattern = ["nix", "build"],
|
||||
decision = "allow",
|
||||
justification = "Allow local builds",
|
||||
)
|
||||
'';
|
||||
settings = {
|
||||
model = "gemma3:latest";
|
||||
model_provider = "ollama";
|
||||
@@ -36,5 +43,14 @@ in
|
||||
assertFileExists home-files/.config/codex/AGENTS.md
|
||||
assertFileContent home-files/.config/codex/AGENTS.md \
|
||||
${./AGENTS.md}
|
||||
assertFileExists home-files/.config/codex/rules/default.rules
|
||||
assertFileContent home-files/.config/codex/rules/default.rules \
|
||||
${builtins.toFile "expected-xdg-default.rules" ''
|
||||
prefix_rule(
|
||||
pattern = ["nix", "build"],
|
||||
decision = "allow",
|
||||
justification = "Allow local builds",
|
||||
)
|
||||
''}
|
||||
'';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user