From d024a4c805e591a0441e7de9d9bc27198fe243fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Sun, 27 Sep 2026 12:38:54 +0200 Subject: [PATCH] Add packages: tools on every launcher's PATH --- README.md | 3 ++- checks/default.nix | 23 +++++++++++++++++++++++ modules/core.nix | 8 +++++++- modules/launcher.nix | 2 +- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b21cd501eff05f6abc997c333214577defbb3c05..ea1ac478eb400ff93c6a77684194c1ecfbc076a6 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Runnable examples: [`examples/basic`](examples/basic) and | `codex.settings` | TOML linked as `$CODEX_HOME/agent-environments.config.toml` and selected with `--profile agent-environments` (layered over the account's `config.toml`). | | `instructions..{text,order}` | Fragments of `CLAUDE.md` / `AGENTS.md`, sorted by `order` (default 1000), then name. | | `skills.packages` | `mkSkill` packages to enable. Their skill dependencies are added automatically. | +| `packages` | Packages put on every launcher's `PATH`, e.g. tools that hooks in `claude.settings` call. | | `modules.skills..enable` | Declared by `mkSkillModule`; adds that skill. | | `launchers..{agent,id,stateDir}` | A command. `id` defaults to the command name; `stateDir` defaults to `~/.local/agent-environments/`; it must be absolute or start with `~/`, and is taken literally. | | `companions.enable` | Put every launcher of the environment on the agent's `PATH` (as companions, which cannot start companions themselves) and add a generated `companions` skill listing each one with its non-interactive call. Launchers export `AGENT_ENV_LAUNCHER` (their command name). | @@ -86,7 +87,7 @@ On every start, under a lock on the state dir: 3. Refuses to start if a managed path is occupied by something it does not own, such as a hand-written `CLAUDE.md`. Move it away (or turn it into an `instructions` fragment) and start again. -4. Sets `CLAUDE_CONFIG_DIR` / `CODEX_HOME`, puts the skills' runtime inputs on +4. Sets `CLAUDE_CONFIG_DIR` / `CODEX_HOME`, puts `packages` and the skills' runtime inputs on `PATH`, and runs the agent. Launchers that share an `id` share a login and managed files. The most recent diff --git a/checks/default.nix b/checks/default.nix index 4580eed19007a05d059d36301515c9da5cc99fe3..70e3e1da7f8570f5192e7e1655a087e75b2937a0 100644 --- a/checks/default.nix +++ b/checks/default.nix @@ -156,6 +156,29 @@ in [ "$b" = "CLAUDE_CONFIG_DIR=$HOME/.local/agent-environments/claude-pers" ] || fail "$b" ''; + # Hooks in agent settings call tools that no skill provides; `packages` puts + # them on PATH, and an environment without them must not have them. + packages-on-path = + let + env = mkEnvironment { + inherit pkgs; + modules = [ + stubs + { + codex.enable = true; + packages = [ pkgs.hello ]; + launchers.codex-tools.agent = "codex"; + } + ]; + }; + in + runTest "packages-on-path" '' + ${bare.launchers.codex-bare}/bin/codex-bare + grep -qxF "HELLO=no" $STUB_LOG.env || fail "hello on PATH without packages" + ${env.launchers.codex-tools}/bin/codex-tools + grep -qxF "HELLO=yes" $STUB_LOG.env || fail "packages not on PATH" + ''; + # Without settings, no flags are injected and no files are managed. no-settings = runTest "no-settings" '' ${bare.launchers.claude-work}/bin/claude-work diff --git a/modules/core.nix b/modules/core.nix index 0100f39f94a3bf9c5dbf7d33d31b20a11e3ef7e8..2491e0176f791ac480332f0418819120f9f31393 100644 --- a/modules/core.nix +++ b/modules/core.nix @@ -53,6 +53,12 @@ in description = "Skills built with lib.mkSkill. Their skill dependencies are added automatically."; }; + packages = mkOption { + type = types.listOf types.package; + default = [ ]; + description = "Packages put on the PATH of every launcher, e.g. tools that hooks in the agent settings call."; + }; + # Read-only results used by the agent and launcher modules. build = mkOption { type = types.attrsOf types.anything; @@ -73,7 +79,7 @@ in instructionsText = lib.concatMapStringsSep "\n\n" (i: lib.removeSuffix "\n" i.text) sortedInstructions + "\n"; skills = lib.mapAttrsToList (_: ss: lib.head ss) skillsByName; - skillRuntimeInputs = lib.unique (lib.concatMap (s: s.runtimeInputs) skillClosure); + runtimeInputs = lib.unique (config.packages ++ lib.concatMap (s: s.runtimeInputs) skillClosure); }; assertions = lib.mapAttrsToList (name: ss: { diff --git a/modules/launcher.nix b/modules/launcher.nix index 77029ad960a5960f472e7da736c70b7feb55603c..65ed8388e76db42cfb8234cf86d5fd5b38f44ea8 100644 --- a/modules/launcher.nix +++ b/modules/launcher.nix @@ -42,7 +42,7 @@ let agent = build.agents.${launcher.agent}; links = managedLinks agent; runtimePath = lib.makeBinPath ( - lib.optional (companionBin != null) companionBin ++ build.skillRuntimeInputs + lib.optional (companionBin != null) companionBin ++ build.runtimeInputs ); in pkgs.writeShellApplication {