atagen

roundabout

public
Star0
Watch0
4e5ca2e2040139a56cdd8bc50993e0f82ceab09a
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git
nix/nixos-module.nixNix155 lines5.0 KB
1
self:
2
{
3
  config,
4
  lib,
5
  pkgs,
6
  ...
7
}:
8
9
let
10
  cfg = config.programs.roundabout;
11
  toml = pkgs.formats.toml { };
12
  plugin = pkgs.callPackage ./plugin.nix { roundabout = cfg.package; };
13
  codexPlugin = pkgs.callPackage ./codex-plugin.nix { roundabout = cfg.package; };
14
  ompPlugin = pkgs.callPackage ./omp-plugin.nix { roundabout = cfg.package; };
15
in
16
{
17
  options.programs.roundabout = {
18
    enable = lib.mkEnableOption "roundabout, build admission for agents";
19
20
    package = lib.mkOption {
21
      type = lib.types.package;
22
      default = self.packages.${pkgs.stdenv.hostPlatform.system}.roundabout;
23
      defaultText = lib.literalExpression "roundabout.packages.\${system}.roundabout";
24
      description = "the roundabout package";
25
    };
26
27
    socketActivation = lib.mkOption {
28
      type = lib.types.bool;
29
      default = true;
30
      description = ''
31
        let a systemd user socket own `$XDG_RUNTIME_DIR/roundabout.sock` and start the
32
        daemon on demand. when disabled, the first client spawns daemon instead
33
      '';
34
    };
35
36
    settings = lib.mkOption {
37
      inherit (toml) type;
38
      default = { };
39
      example = lib.literalExpression ''
40
        {
41
          budget.ram = "16GiB";
42
          enforce.unwrapped = "wrap";
43
          artifacts.stop = "block";
44
        }
45
      '';
46
      description = ''
47
        written to `/etc/roundabout/config.toml`, which anyone without a
48
        `~/.config/roundabout/config.toml` of their own gets. see readme
49
      '';
50
    };
51
52
    claude = {
53
      hooks = lib.mkOption {
54
        type = lib.types.attrs;
55
        readOnly = true;
56
        default = import ./hooks.nix {
57
          inherit lib;
58
          roundabout = cfg.package;
59
        };
60
        defaultText = lib.literalMD "roundabout's hooks, keyed by event";
61
        description = ''
62
          hooks in `settings.json` shape, for whatever writes your claude config.
63
          lists concatenate under the json format type, so they sit alongside
64
          hooks you already have
65
        '';
66
      };
67
68
      skill = lib.mkOption {
69
        type = lib.types.path;
70
        readOnly = true;
71
        default = "${plugin}/plugins/roundabout/skills/roundabout";
72
        defaultText = lib.literalMD "the roundabout skill directory";
73
        description = "the skill directory, for configs that manage `~/.claude/skills` themselves";
74
      };
75
76
      plugin = lib.mkOption {
77
        type = lib.types.package;
78
        readOnly = true;
79
        default = plugin;
80
        defaultText = lib.literalMD "the roundabout plugin marketplace";
81
        description = "hooks and skill as a marketplace, for `claude plugin marketplace add`";
82
      };
83
    };
84
85
    codex = {
86
      hooks = lib.mkOption {
87
        type = lib.types.attrs;
88
        readOnly = true;
89
        default = import ./codex-hooks.nix {
90
          inherit lib;
91
          roundabout = cfg.package;
92
        };
93
        defaultText = lib.literalMD "roundabout's Codex hooks, keyed by event";
94
        description = "hooks in Codex hooks.json shape, for configs that manage Codex themselves";
95
      };
96
97
      skill = lib.mkOption {
98
        type = lib.types.path;
99
        readOnly = true;
100
        default = "${codexPlugin}/plugins/roundabout/skills/roundabout";
101
        defaultText = lib.literalMD "the roundabout skill directory for Codex";
102
        description = "the skill directory, for configs that manage Codex skills themselves";
103
      };
104
105
      plugin = lib.mkOption {
106
        type = lib.types.package;
107
        readOnly = true;
108
        default = codexPlugin;
109
        defaultText = lib.literalMD "the roundabout Codex plugin marketplace";
110
        description = "hooks and skill as a marketplace for Codex";
111
      };
112
    };
113
114
    omp = {
115
      plugin = lib.mkOption {
116
        type = lib.types.package;
117
        readOnly = true;
118
        default = ompPlugin;
119
        defaultText = lib.literalMD "the roundabout OMP plugin";
120
        description = "the extension and skill for omp plugin link";
121
      };
122
123
      skill = lib.mkOption {
124
        type = lib.types.path;
125
        readOnly = true;
126
        default = "${ompPlugin}/skills/roundabout";
127
        defaultText = lib.literalMD "the roundabout skill directory for OMP";
128
        description = "the skill directory, for configs that manage OMP skills themselves";
129
      };
130
    };
131
  };
132
133
  config = lib.mkIf cfg.enable {
134
    environment.systemPackages = [ cfg.package ];
135
136
    # a real file rather than the usual /etc/static symlink, so a rebuild
137
    # renames over it where the daemon's inotify watch can see
138
    environment.etc."roundabout/config.toml" = lib.mkIf (cfg.settings != { }) {
139
      source = toml.generate "roundabout-config.toml" cfg.settings;
140
      mode = "0444";
141
    };
142
143
    systemd.user.sockets.roundabout = lib.mkIf cfg.socketActivation {
144
      description = "roundabout build admission socket";
145
      wantedBy = [ "sockets.target" ];
146
      listenStreams = [ "%t/roundabout.sock" ];
147
      socketConfig.SocketMode = "0600";
148
    };
149
150
    systemd.user.services.roundabout = lib.mkIf cfg.socketActivation {
151
      description = "roundabout build admission daemon";
152
      serviceConfig.ExecStart = "${lib.getExe cfg.package} daemon";
153
    };
154
  };
155
}