diff --git a/AGENTS.md b/AGENTS.md index 02168f8b..c13499ba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ This flake manages NixOS hosts, macOS via nix-darwin, shared Home Manager profiles, and Colmena deployments. Keep repository guidance here; reusable global rules live in `agents/AGENTS.md`. See -[agents/README.md](./agents/README.md) for their scope and symlink installation targets. +[agents/README.md](./agents/README.md) for their scope and deployment targets. ## Where Changes Belong diff --git a/agents/README.md b/agents/README.md index b4e4f752..4264081a 100644 --- a/agents/README.md +++ b/agents/README.md @@ -1,9 +1,10 @@ # agents -Reusable, symlink-first agent resources shared across projects. +Portable agent resources shared across projects: the global baseline rules, behavioral scenarios for +those rules, and reference snippets for external tooling. -This directory is the canonical source for baseline agent rules and supporting command references. -The primary workflow is to symlink files from here into each agent runtime/config location. +Home Manager links the rules from here into each agent's config location, so `agents/` itself stays +plain Markdown with no Nix dependency. It is shared across projects. Repo-scoped task procedures for one repository belong elsewhere: put them in that repository's `.agents/skills/` (note the leading dot), which OpenCode and compatible @@ -20,27 +21,23 @@ Keep one canonical home for each fact; link between layers instead of copying pa - `AGENTS.md`: global baseline rules for coding agents. - `evals/global-rules.md`: behavioral scenarios for validating changes to the global rules. -- `install-rules.py`: installs the baseline by creating symlinks in supported agent config dirs. -- `test_install_rules.py`: regression tests for file preservation and independent target handling. -- `install-cli.md`: curated CLI install/update command snippets. -- `install-skills.md`: curated `npx skills` command snippets. +- `install-skills.md`: curated `npx skills` command snippets, plus the `npx ctx7` docs-tool setup. + +The Nix side of the agents — deploying these rules, the agent CLIs, and their environment — lives in +the Home Manager module [`home/base/tui/agents/`](../home/base/tui/agents/README.md). ## Core workflow 1. Maintain shared rules in `agents/AGENTS.md`. 2. Configure permissions directly in the agent runtime; auto-approval is generally used. -3. Run `install-rules.py` to refresh symlinks in local agent homes. -4. Use `install-cli.md` and `install-skills.md` as reference snippets when needed. +3. Edit the rules; Home Manager links them out-of-store, so the change reaches the next agent + session without a rebuild. Run a Home Manager switch only when the deployed target set changes. +4. Use `install-skills.md` as a reference when installing external skills. -## Install baseline rules (symlink-based) +## Deployment -Run: - -```bash -python3 agents/install-rules.py -``` - -Current targets: +[`home/base/tui/agents/rules.nix`](../home/base/tui/agents/rules.nix) links `AGENTS.md` into every +supported agent config directory as an out-of-store symlink, so edits apply without a rebuild: - Codex: `AGENTS.md` -> `${CODEX_HOME:-~/.codex}/AGENTS.md` - OpenCode: `AGENTS.md` -> `${XDG_CONFIG_HOME:-~/.config}/opencode/AGENTS.md` @@ -48,30 +45,20 @@ Current targets: - OMP: `AGENTS.md` -> `~/.omp/agent/AGENTS.md` - Generic cross-tool (read by Kimi Code): `AGENTS.md` -> `~/.agents/AGENTS.md` -Behavior: +The module is imported through `home/base/tui`, so it covers the hosts that import +`home/linux/gui.nix` or the macOS `home/darwin` stack; core-only servers are unchanged. -- Each target is handled independently. -- Target errors are reported while remaining targets are attempted; any error yields a nonzero exit. -- Missing destination directories are skipped. -- Existing regular files are preserved as `.bak` backups (numbered when a backup already exists). -- Destination links are replaced atomically; a failed link creation leaves the destination intact. - -The installer links only `AGENTS.md`; it does not install permission configuration, skills, or CLIs. -The repository-root `AGENTS.md` contains guidance for this Nix configuration repository. It is not -the global rules source and is not installed by this script. +Only `AGENTS.md` is deployed to the agents. Permission configuration and skills are not installed by +Nix; the CLIs themselves are, via the same module's `packages.nix`. The repository-root `AGENTS.md` +contains guidance for this Nix configuration repository. It is not the global rules source and is +not deployed. Auto-approval controls tool prompting. The global rules still define task authorization, safety, and secret handling. -Run installer regression tests without changing installed rules: +## About `install-skills.md` -```bash -python3 -B -m unittest discover -s agents -p test_install_rules.py -``` - -## About `install-cli.md` and `install-skills.md` - -Use them as snippet libraries: +Use it as a snippet library: - review the commands - select what you need @@ -79,8 +66,7 @@ Use them as snippet libraries: ## TODO -Ideas worth adopting once a concrete need appears; nothing here is implemented yet, and the install -scripts above do not cover it. +Ideas worth adopting once a concrete need appears; nothing here is implemented yet. - **Shared instructions and skills.** Deploy one rules body plus declared skills to every agent instead of maintaining a copy per agent. Reference: `mirkolenz/infra` diff --git a/agents/install-cli.md b/agents/install-cli.md deleted file mode 100644 index b5f539cb..00000000 --- a/agents/install-cli.md +++ /dev/null @@ -1,25 +0,0 @@ -# Agent CLI Commands - -Reference commands for installing and updating agent CLIs. Run only the commands you need. - -## Install CLIs - -Installed via Nix: - -- codex -- opencode -- kimi-code -- pi -- omp - -## Optional tooling - -```bash -# context7: up-to-date docs and code examples for LLMs and agents -npx ctx7 setup -``` - -## Update - -The agent CLIs come from Nix (`home/base/gui/dev-tools.nix`) through the `llm-agents` flake input, -so update them with the flake rather than npm. diff --git a/agents/install-rules.py b/agents/install-rules.py deleted file mode 100644 index 2f7ab317..00000000 --- a/agents/install-rules.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import tempfile -from pathlib import Path - - -def install_one(target_dir: Path, source_file: Path, target_name: str) -> None: - if not target_dir.exists(): - print(f"skipped {target_dir} (not found)") - return - - target_file = target_dir / target_name - - if target_file.is_file() and not target_file.is_symlink(): - # Preserve the old inode without reading its contents or replacing an earlier backup. - index = 0 - while True: - suffix = ".bak" if index == 0 else f".bak.{index}" - backup = target_file.with_name(target_file.name + suffix) - try: - os.link(target_file, backup) - break - except FileExistsError: - index += 1 - print(f"backed up {target_file} -> {backup}") - - # Create the link on the same filesystem before atomically replacing the destination. - with tempfile.TemporaryDirectory(prefix=".install-rules-", dir=target_dir) as staging: - link = Path(staging) / target_name - link.symlink_to(source_file) - link.replace(target_file) - print(f"linked {target_file} -> {source_file}") - - -def main() -> int: - script_dir = Path(__file__).resolve().parent - agents_file = script_dir / "AGENTS.md" - - if not agents_file.is_file(): - print(f"Missing source file: {agents_file}", file=sys.stderr) - return 1 - - codex_dir = Path(os.environ.get("CODEX_HOME", "~/.codex")).expanduser() - xdg_config_home = Path(os.environ.get("XDG_CONFIG_HOME", "~/.config")).expanduser() - opencode_dir = xdg_config_home / "opencode" - pi_dir = Path("~/.pi/agent").expanduser() - omp_dir = Path("~/.omp/agent").expanduser() - agents_dir = Path("~/.agents").expanduser() - targets = ( - (codex_dir, "AGENTS.md"), - (opencode_dir, "AGENTS.md"), - (pi_dir, "AGENTS.md"), - (omp_dir, "AGENTS.md"), - (agents_dir, "AGENTS.md"), - ) - failed = False - for target_dir, target_name in targets: - try: - install_one(target_dir, agents_file, target_name) - except OSError as error: - print(f"failed {target_dir / target_name}: {error}", file=sys.stderr) - failed = True - - return 1 if failed else 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/agents/install-skills.md b/agents/install-skills.md index 9d21ff17..4374953d 100644 --- a/agents/install-skills.md +++ b/agents/install-skills.md @@ -81,6 +81,13 @@ npx skills add anthropics/skills --skill 'pdf' npx skills add trailofbits/skills --skill 'codeql' --skill 'semgrep' ``` +## Other agent tooling (npx) + +```bash +# context7: up-to-date library docs and code examples for LLMs and agents +npx ctx7 setup +``` + References: - https://github.com/obra/superpowers diff --git a/agents/test_install_rules.py b/agents/test_install_rules.py deleted file mode 100644 index 956816e9..00000000 --- a/agents/test_install_rules.py +++ /dev/null @@ -1,87 +0,0 @@ -import importlib.util -import io -import os -import tempfile -import unittest -from pathlib import Path -from unittest.mock import patch - - -spec = importlib.util.spec_from_file_location( - "install_rules", Path(__file__).with_name("install-rules.py") -) -installer = importlib.util.module_from_spec(spec) -spec.loader.exec_module(installer) - - -class InstallRulesTests(unittest.TestCase): - def test_main_links_all_supported_agent_targets(self): - with tempfile.TemporaryDirectory() as directory: - home = Path(directory) - target_names = ( - (home / ".codex", "AGENTS.md"), - (home / ".config" / "opencode", "AGENTS.md"), - (home / ".pi" / "agent", "AGENTS.md"), - (home / ".omp" / "agent", "AGENTS.md"), - (home / ".agents", "AGENTS.md"), - ) - for target_dir, _ in target_names: - target_dir.mkdir(parents=True) - - with patch.dict(os.environ, {"HOME": str(home)}, clear=True): - self.assertEqual(installer.main(), 0) - - for target_dir, target_name in target_names: - target = target_dir / target_name - self.assertTrue(target.is_symlink(), target) - self.assertEqual(target.resolve(), installer.Path(__file__).with_name("AGENTS.md")) - - def test_target_failure_does_not_stop_remaining_targets(self): - with ( - patch.object( - installer, - "install_one", - side_effect=[PermissionError("denied"), None, None, None, None], - ) as install, - patch("sys.stderr", new_callable=io.StringIO) as stderr, - ): - self.assertEqual(installer.main(), 1) - self.assertEqual(install.call_count, 5) - self.assertIn("denied", stderr.getvalue()) - - def test_successful_targets_return_success(self): - with patch.object(installer, "install_one") as install: - self.assertEqual(installer.main(), 0) - self.assertEqual(install.call_count, 5) - - def test_existing_file_is_backed_up_without_overwriting_backup(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - source = root / "source.md" - source.write_text("global rules") - target = root / "AGENTS.md" - target.write_text("personal rules") - backup = root / "AGENTS.md.bak" - backup.write_text("older rules") - installer.install_one(root, source, target.name) - self.assertEqual(target.resolve(), source) - self.assertEqual(backup.read_text(), "older rules") - self.assertEqual((root / "AGENTS.md.bak.1").read_text(), "personal rules") - - def test_failed_link_creation_preserves_existing_file(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - source = root / "source.md" - source.write_text("global rules") - target = root / "AGENTS.md" - target.write_text("personal rules") - with patch.object(Path, "symlink_to", side_effect=OSError("link failed")): - with self.assertRaises(OSError): - installer.install_one(root, source, target.name) - self.assertTrue(target.is_file()) - self.assertFalse(target.is_symlink()) - self.assertEqual(target.read_text(), "personal rules") - - -if __name__ == "__main__": - unittest.main() diff --git a/home/README.md b/home/README.md index 8741d3bb..76a7572b 100644 --- a/home/README.md +++ b/home/README.md @@ -15,6 +15,7 @@ home/ │ │ ├── terminal/ # Terminal emulators (Ghostty, Kitty, etc.) │ │ └── ... │ ├── tui/ # Terminal/TUI applications +│ │ ├── agents/ # AI coding agents (rules deployment, CLIs, env) │ │ ├── editors/ # Heavy editor/tooling pkgs only (`packages.nix`; core editors live under `core/editors`) │ │ ├── encryption/ # Encryption and security tooling │ │ └── ... diff --git a/home/base/README.md b/home/base/README.md index 3da4769a..127df849 100644 --- a/home/base/README.md +++ b/home/base/README.md @@ -24,7 +24,7 @@ systems. ### Desktop Environment - **gui/**: Cross-platform GUI applications and configurations - - **dev-tools.nix**: Development tools and IDEs + - **dev-tools.nix**: Misc development and network tools (mitmproxy, wireshark) - **media.nix**: Media players and utilities - **zed-editor.nix**: Zed editor configuration (primary GUI editor) - **terminal/**: Terminal emulator configurations @@ -34,7 +34,8 @@ systems. ### Terminal Interface - **tui/**: Terminal-based interface configurations - - **agent-env.nix**: Telemetry/auto-update opt-outs for AI coding agents + - **agents/**: AI coding agents — global rules deployment, CLIs, and telemetry/auto-update + opt-outs - **cloud/**: Cloud development tools (Terraform, etc.) - **container.nix**: Container tools (Docker, Podman) - **dev-tools.nix**: Terminal-based development tools diff --git a/home/base/gui/dev-tools.nix b/home/base/gui/dev-tools.nix index 2583f366..7490af29 100644 --- a/home/base/gui/dev-tools.nix +++ b/home/base/gui/dev-tools.nix @@ -1,12 +1,8 @@ { pkgs, lib, - llm-agents, ... }: -let - agentPackages = llm-agents.packages.${pkgs.stdenv.hostPlatform.system}; -in { home.packages = with pkgs; @@ -17,17 +13,5 @@ in ++ lib.optionals pkgs.stdenv.hostPlatform.isLinux [ mitmproxy # http/https proxy tool wireshark # network analyzer - ] - # AI Agent Tools - ++ [ - # Agents - agentPackages.codex - agentPackages.opencode2 - agentPackages.kimi-code - agentPackages.pi - agentPackages.omp - - # MCP servers, configured project-scoped in opencode.jsonc / .codex/config.toml - mcp-grafana ]; } diff --git a/home/base/tui/agents/README.md b/home/base/tui/agents/README.md new file mode 100644 index 00000000..5a8fcd56 --- /dev/null +++ b/home/base/tui/agents/README.md @@ -0,0 +1,36 @@ +# Agents + +Home Manager module that owns the Nix side of the AI coding agents: deploying the global rules, the +agent CLIs, and their environment. + +The canonical rules text, behavioral scenarios, and external-tool snippets live in the top-level +[`agents/`](../../../../agents/README.md) directory. That directory stays portable Markdown with no +Nix dependency; this module only wires it into place. + +## Files + +- `rules.nix`: links the global rules to every agent's config location. +- `packages.nix`: agent CLIs (`codex`, `opencode2`, `kimi-code`, `pi`, `omp`) and the Grafana MCP + server, from the `llm-agents` flake input. +- `env.nix`: telemetry and auto-update opt-outs for the agents. + +## Deployed rule targets + +`rules.nix` creates one out-of-store symlink per target, all pointing at +`~/nix-config/agents/AGENTS.md`: + +| Agent | Target | +| ---------------------- | ------------------------------ | +| Codex | `~/.codex/AGENTS.md` | +| OpenCode | `~/.config/opencode/AGENTS.md` | +| Pi | `~/.pi/agent/AGENTS.md` | +| OMP | `~/.omp/agent/AGENTS.md` | +| Cross-tool (Kimi Code) | `~/.agents/AGENTS.md` | + +Out-of-store means an edit to `agents/AGENTS.md` takes effect on the next agent session without a +Home Manager switch. The trade-off is that the rules stay writable in the checkout; use a store +symlink (`home.file..source = ../../../../agents/AGENTS.md`) if immutability matters more +than fast iteration. + +The module is imported through `home/base/tui`, so it applies to every host that imports +`home/linux/gui.nix` or the macOS `home/darwin` stack. Core-only servers do not get it. diff --git a/home/base/tui/agents/default.nix b/home/base/tui/agents/default.nix new file mode 100644 index 00000000..049dda93 --- /dev/null +++ b/home/base/tui/agents/default.nix @@ -0,0 +1,4 @@ +{ mylib, ... }: +{ + imports = mylib.scanPaths ./.; +} diff --git a/home/base/tui/agent-env.nix b/home/base/tui/agents/env.nix similarity index 100% rename from home/base/tui/agent-env.nix rename to home/base/tui/agents/env.nix diff --git a/home/base/tui/agents/packages.nix b/home/base/tui/agents/packages.nix new file mode 100644 index 00000000..c9b16edf --- /dev/null +++ b/home/base/tui/agents/packages.nix @@ -0,0 +1,21 @@ +{ + pkgs, + llm-agents, + ... +}: +let + agentPackages = llm-agents.packages.${pkgs.stdenv.hostPlatform.system}; +in +{ + home.packages = [ + # Agents + agentPackages.codex + agentPackages.opencode2 + agentPackages.kimi-code + agentPackages.pi + agentPackages.omp + + # MCP servers, configured project-scoped in opencode.jsonc / .codex/config.toml + pkgs.mcp-grafana + ]; +} diff --git a/home/base/tui/agents/rules.nix b/home/base/tui/agents/rules.nix new file mode 100644 index 00000000..ce08aec2 --- /dev/null +++ b/home/base/tui/agents/rules.nix @@ -0,0 +1,20 @@ +{ config, ... }: +let + # The global rules are edited often, so link them out-of-store: an edit reaches + # the next agent session without a Home Manager switch. Same rationale as + # `home/base/tui/tuios`; the checkout path is hardcoded, like the repo's other + # out-of-store links. + rules = "${config.home.homeDirectory}/nix-config/agents/AGENTS.md"; + link = config.lib.file.mkOutOfStoreSymlink rules; +in +{ + # One canonical rules file, projected into every agent's config location. + home.file = { + ".codex/AGENTS.md".source = link; # Codex + ".pi/agent/AGENTS.md".source = link; # Pi + ".omp/agent/AGENTS.md".source = link; # OMP + ".agents/AGENTS.md".source = link; # cross-tool, read by Kimi Code + }; + + xdg.configFile."opencode/AGENTS.md".source = link; # OpenCode +} diff --git a/outputs/x86_64-linux/src/12kingdoms-shoryu.nix b/outputs/x86_64-linux/src/12kingdoms-shoryu.nix index 3de592d2..008047ab 100644 --- a/outputs/x86_64-linux/src/12kingdoms-shoryu.nix +++ b/outputs/x86_64-linux/src/12kingdoms-shoryu.nix @@ -32,9 +32,6 @@ let modules.secrets.preservation.enable = true; } ]; - # home-modules = map mylib.relativeToRoot [ - # "home/linux/tui.nix" - # ]; }; systemArgs = modules // args;