Files
gitea-axi/home-manager-module.nix
alexion e77a1f5e22
All checks were successful
CI / test (22) (pull_request) Successful in 49s
CI / test (true, 24) (pull_request) Successful in 1m5s
CI / flake (pull_request) Successful in 3s
CI / test (22) (push) Successful in 49s
CI / test (true, 24) (push) Successful in 1m5s
CI / flake (push) Successful in 3s
feat(nix): expose declarative outputs and a home-manager module (task 0045)
Let a Nix configuration declare gitea-axi's ambient context instead of
running a command that writes it. `setup` and `setup hooks` are write-only
against files the operator is assumed to own, so an operator whose agent
configuration is generated cannot use them at all.

The package installs the bundled Agent Skill to share/gitea-axi/skills and
publishes it as `passthru.skill`, alongside `passthru.sessionStartHook` read
from `session-start-hook.json` — a committed declaration the fast tier reads
too, so a test drives `setup hooks` and asserts the two agree.

On top of that, `homeModules.gitea-axi` declares both from those attributes
through home-manager's own Claude Code options, so an operator's existing
skills and SessionStart hooks compose rather than collide. Importing it
without enabling it yields a byte-identical generation.

The spec's Out of Scope entry deferring a home-manager module is deleted;
ADR 0020 records the reversal, and INSTALL.md describes both paths.
2026-07-20 13:51:19 -04:00

112 lines
4.1 KiB
Nix

# A home-manager module declaring gitea-axi's ambient context — the bundled
# Agent Skill and the SessionStart hook — for an operator whose agent
# configuration is generated rather than owned (ADR 0020).
#
# It is a wiring layer and nothing more. Both pieces come from the package's
# published attributes, so what a declarative configuration installs and what
# `gitea-axi setup` writes imperatively are the same two artefacts, and neither
# is restated here.
#
# Importing this module changes nothing until `programs.gitea-axi.enable` is set.
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.gitea-axi;
# The package the declarations are read out of. `package = null` opts out of
# putting the binary on PATH, not out of the configuration — an operator who
# installs gitea-axi system-wide still wants the Skill — so the declarations
# fall back to the default build, which in that arrangement is already in the
# closure anyway.
sourcePackage = if cfg.package != null then cfg.package else defaultPackage;
defaultPackage = pkgs.callPackage ./package.nix { };
# Both integrations write into files a sibling module owns, so nothing lands
# unless that module is the one writing them.
claudeCode = config.programs.claude-code;
in
{
options.programs.gitea-axi = {
enable = lib.mkEnableOption "gitea-axi, an agent-ergonomic CLI for Gitea issues and pull requests";
package = lib.mkOption {
type = lib.types.nullOr lib.types.package;
default = defaultPackage;
defaultText = lib.literalExpression "pkgs.callPackage ./package.nix { }";
description = ''
The gitea-axi package to install, or `null` to declare the
configuration without installing the binary for an operator who
supplies it another way, such as `environment.systemPackages`.
The SessionStart hook records a name resolved on `PATH`, so a binary
installed elsewhere satisfies it. With `null` the Agent Skill is still
taken from the default build.
'';
};
skill.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to install gitea-axi's bundled Agent Skill, which teaches the
agent to reach for gitea-axi over `tea` or raw API calls.
Turn this off to keep writing the Skill with `gitea-axi setup` while
managing the rest declaratively.
'';
};
sessionStartHook.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to register the SessionStart hook that renders the gitea-axi
dashboard at the start of an agent session.
Turn this off to keep writing the hook with `gitea-axi setup hooks`
while managing the rest declaratively.
'';
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
(lib.mkIf (cfg.package != null) { home.packages = [ cfg.package ]; })
# Declared through the Claude Code module's own options rather than by
# writing its files, so an operator who already declares Skills and
# SessionStart hooks gets ours merged into theirs instead of a collision.
(lib.mkIf cfg.skill.enable {
programs.claude-code.skills.gitea-axi = sourcePackage.skill;
})
(lib.mkIf cfg.sessionStartHook.enable {
programs.claude-code.settings.hooks.SessionStart = [ sourcePackage.sessionStartHook ];
})
{
# Without this the options above are set and silently dropped, leaving
# an operator with a configuration that says the Skill is installed and
# a session that never sees it.
assertions = [
{
assertion = (cfg.skill.enable || cfg.sessionStartHook.enable) -> claudeCode.enable;
message = ''
programs.gitea-axi declares a Claude Code Agent Skill and session
hook, which programs.claude-code writes. Set
programs.claude-code.enable = true, or turn off
programs.gitea-axi.skill.enable and
programs.gitea-axi.sessionStartHook.enable.
'';
}
];
}
]
);
}