From 153879db4365fe2b88fc626894ce7cf73283090c Mon Sep 17 00:00:00 2001 From: Adam0 Date: Thu, 16 Jul 2026 21:42:07 -0400 Subject: [PATCH] Migrate to pi from opencode --- flake.lock | 310 ++++++++++++++-- flake.nix | 8 + modules/development/treefmt.nix | 7 - modules/nix/ncps.nix | 10 +- modules/profiles/personal.nix | 2 +- modules/programs/gui/tangled.nix | 27 +- modules/programs/{cli => tui}/diffnav.nix | 2 + modules/programs/tui/opencode/default.nix | 100 ------ modules/programs/tui/opencode/env.nix | 42 --- modules/programs/tui/opencode/formatter.nix | 162 --------- modules/programs/tui/opencode/lsp.nix | 150 -------- .../programs/tui/opencode/mcp-wrappers.nix | 81 ----- modules/programs/tui/opencode/plugins.nix | 34 -- modules/programs/tui/opencode/settings.nix | 38 -- .../programs/tui/opencode/skills/default.nix | 39 -- .../tui/opencode/skills/lazy-mcp/authentik.md | 23 -- .../tui/opencode/skills/lazy-mcp/context7.md | 25 -- .../tui/opencode/skills/lazy-mcp/github.md | 24 -- .../tui/opencode/skills/lazy-mcp/grep-app.md | 25 -- .../programs/tui/opencode/skills/uncodixfy.md | 157 --------- modules/programs/tui/opencode/theme.nix | 13 - modules/programs/tui/pi/agents/coding.md | 11 + modules/programs/tui/pi/agents/explore.md | 35 ++ .../programs/tui/pi/agents/general-purpose.md | 6 + modules/programs/tui/pi/agents/plan.md | 31 ++ modules/programs/tui/pi/default.nix | 77 ++++ modules/programs/tui/pi/extensions/atuin.nix | 8 + modules/programs/tui/pi/extensions/btw.nix | 5 + modules/programs/tui/pi/extensions/cache.nix | 38 ++ modules/programs/tui/pi/extensions/cwd.nix | 5 + modules/programs/tui/pi/extensions/fff.nix | 9 + modules/programs/tui/pi/extensions/footer.nix | 56 +++ .../programs/tui/pi/extensions/formatter.nix | 147 ++++++++ modules/programs/tui/pi/extensions/header.nix | 18 + modules/programs/tui/pi/extensions/lsp.nix | 328 +++++++++++++++++ modules/programs/tui/pi/extensions/mcp.nix | 119 +++++++ modules/programs/tui/pi/extensions/qol.nix | 44 +++ .../programs/tui/pi/extensions/question.nix | 5 + modules/programs/tui/pi/extensions/rewind.nix | 30 ++ modules/programs/tui/pi/extensions/rtk.nix | 29 ++ modules/programs/tui/pi/extensions/speeed.nix | 8 + .../programs/tui/pi/extensions/subagents.nix | 14 + .../tui/pi/extensions/tool-renderer.nix | 56 +++ modules/programs/tui/pi/extensions/web.nix | 57 +++ .../tui/pi/extensions/web/package-lock.json | 258 ++++++++++++++ .../tui/{opencode => pi}/instructions.md | 29 +- modules/programs/tui/pi/settings.nix | 7 + .../skills/create-readme/SKILL.md} | 0 modules/programs/tui/pi/skills/default.nix | 33 ++ .../skills/find-skills/SKILL.md} | 0 .../programs/tui/pi/skills/frontend/SKILL.md | 184 ++++++++++ .../programs/tui/pi/skills/jujutsu/SKILL.md | 332 ++++++++++++++++++ .../tui/pi/skills/jujutsu/agents/openai.yaml | 7 + .../pi/skills/jujutsu/references/COLOCATED.md | 81 +++++ .../skills/jujutsu/references/COMMIT_MSG.md | 26 ++ .../pi/skills/jujutsu/references/CONFLICTS.md | 176 ++++++++++ .../pi/skills/jujutsu/references/GOTCHAS.md | 47 +++ .../skills/jujutsu/references/NEW_CHANGE.md | 52 +++ .../pi/skills/jujutsu/references/NONLINEAR.md | 65 ++++ .../tui/pi/skills/jujutsu/references/PUSH.md | 39 ++ .../jujutsu/references/QUERY_LANGUAGES.md | 91 +++++ .../pi/skills/jujutsu/references/RECOVERY.md | 118 +++++++ .../jujutsu/references/REFINE_COMMIT.md | 78 ++++ .../pi/skills/jujutsu/references/REMOTES.md | 169 +++++++++ .../pi/skills/jujutsu/references/TEMPLATES.md | 84 +++++ .../skills/jujutsu/references/WORKSPACES.md | 209 +++++++++++ .../debug.md => pi/skills/nix-debug/SKILL.md} | 0 .../index.md => pi/skills/nix-index/SKILL.md} | 0 .../skills/nix-search/SKILL.md} | 0 .../skills/nvf-config/SKILL.md} | 0 modules/programs/tui/pi/theme.nix | 78 ++++ modules/services/network/network.nix | 3 + modules/services/server/podman.nix | 36 +- secrets.yaml | 8 +- vars.nix | 1 + 75 files changed, 3604 insertions(+), 1022 deletions(-) rename modules/programs/{cli => tui}/diffnav.nix (93%) delete mode 100644 modules/programs/tui/opencode/default.nix delete mode 100644 modules/programs/tui/opencode/env.nix delete mode 100644 modules/programs/tui/opencode/formatter.nix delete mode 100644 modules/programs/tui/opencode/lsp.nix delete mode 100644 modules/programs/tui/opencode/mcp-wrappers.nix delete mode 100644 modules/programs/tui/opencode/plugins.nix delete mode 100644 modules/programs/tui/opencode/settings.nix delete mode 100644 modules/programs/tui/opencode/skills/default.nix delete mode 100644 modules/programs/tui/opencode/skills/lazy-mcp/authentik.md delete mode 100644 modules/programs/tui/opencode/skills/lazy-mcp/context7.md delete mode 100644 modules/programs/tui/opencode/skills/lazy-mcp/github.md delete mode 100644 modules/programs/tui/opencode/skills/lazy-mcp/grep-app.md delete mode 100644 modules/programs/tui/opencode/skills/uncodixfy.md delete mode 100644 modules/programs/tui/opencode/theme.nix create mode 100644 modules/programs/tui/pi/agents/coding.md create mode 100644 modules/programs/tui/pi/agents/explore.md create mode 100644 modules/programs/tui/pi/agents/general-purpose.md create mode 100644 modules/programs/tui/pi/agents/plan.md create mode 100644 modules/programs/tui/pi/default.nix create mode 100644 modules/programs/tui/pi/extensions/atuin.nix create mode 100644 modules/programs/tui/pi/extensions/btw.nix create mode 100644 modules/programs/tui/pi/extensions/cache.nix create mode 100644 modules/programs/tui/pi/extensions/cwd.nix create mode 100644 modules/programs/tui/pi/extensions/fff.nix create mode 100644 modules/programs/tui/pi/extensions/footer.nix create mode 100644 modules/programs/tui/pi/extensions/formatter.nix create mode 100644 modules/programs/tui/pi/extensions/header.nix create mode 100644 modules/programs/tui/pi/extensions/lsp.nix create mode 100644 modules/programs/tui/pi/extensions/mcp.nix create mode 100644 modules/programs/tui/pi/extensions/qol.nix create mode 100644 modules/programs/tui/pi/extensions/question.nix create mode 100644 modules/programs/tui/pi/extensions/rewind.nix create mode 100644 modules/programs/tui/pi/extensions/rtk.nix create mode 100644 modules/programs/tui/pi/extensions/speeed.nix create mode 100644 modules/programs/tui/pi/extensions/subagents.nix create mode 100644 modules/programs/tui/pi/extensions/tool-renderer.nix create mode 100644 modules/programs/tui/pi/extensions/web.nix create mode 100644 modules/programs/tui/pi/extensions/web/package-lock.json rename modules/programs/tui/{opencode => pi}/instructions.md (71%) create mode 100644 modules/programs/tui/pi/settings.nix rename modules/programs/tui/{opencode/skills/create-readme.md => pi/skills/create-readme/SKILL.md} (100%) create mode 100644 modules/programs/tui/pi/skills/default.nix rename modules/programs/tui/{opencode/skills/find-skills.md => pi/skills/find-skills/SKILL.md} (100%) create mode 100644 modules/programs/tui/pi/skills/frontend/SKILL.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/SKILL.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/agents/openai.yaml create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/COLOCATED.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/COMMIT_MSG.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/CONFLICTS.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/GOTCHAS.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/NEW_CHANGE.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/NONLINEAR.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/PUSH.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/QUERY_LANGUAGES.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/RECOVERY.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/REFINE_COMMIT.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/REMOTES.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/TEMPLATES.md create mode 100644 modules/programs/tui/pi/skills/jujutsu/references/WORKSPACES.md rename modules/programs/tui/{opencode/skills/nix/debug.md => pi/skills/nix-debug/SKILL.md} (100%) rename modules/programs/tui/{opencode/skills/nix/index.md => pi/skills/nix-index/SKILL.md} (100%) rename modules/programs/tui/{opencode/skills/nix/search.md => pi/skills/nix-search/SKILL.md} (100%) rename modules/programs/tui/{opencode/skills/nvf-config.md => pi/skills/nvf-config/SKILL.md} (100%) create mode 100644 modules/programs/tui/pi/theme.nix diff --git a/flake.lock b/flake.lock index 60871ae..83b51a7 100644 --- a/flake.lock +++ b/flake.lock @@ -162,6 +162,34 @@ "type": "github" } }, + "bun2nix": { + "inputs": { + "flake-parts": "flake-parts_4", + "nixpkgs": [ + "pi-nix", + "nixpkgs" + ], + "systems": [ + "pi-nix", + "systems" + ], + "treefmt-nix": "treefmt-nix_2" + }, + "locked": { + "lastModified": 1778446047, + "narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=", + "owner": "nix-community", + "repo": "bun2nix", + "rev": "f2bc12af1a6369648aac41041ceeaa0b866599c6", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "2.1.0", + "repo": "bun2nix", + "type": "github" + } + }, "cachyos-kernel": { "flake": false, "locked": { @@ -460,13 +488,29 @@ "url": "https://git.lix.systems/lix-project/flake-compat.git" } }, + "flake-compat_8": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, "flake-file": { "locked": { - "lastModified": 1781217157, - "narHash": "sha256-N3q/SP2Ropk336e9KSgLh7kpROY6P70dprYdbPIfd5c=", + "lastModified": 1784255282, + "narHash": "sha256-srM+PTqxfyvbQ0BevsOtP8vQv/Ox6OMtY4DktoDxLKg=", "owner": "vic", "repo": "flake-file", - "rev": "ce63eaf7ebfe04a176653f66385a7f0a36380cee", + "rev": "66ddd2f69a5c4677f0095c6f70eea1217dc45749", "type": "github" }, "original": { @@ -534,6 +578,46 @@ "type": "github" } }, + "flake-parts_4": { + "inputs": { + "nixpkgs-lib": [ + "pi-nix", + "bun2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1777988971, + "narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_5": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1782949081, + "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": [ @@ -597,6 +681,28 @@ "url": "https://flakehub.com/f/cachix/git-hooks.nix/0.1.941" } }, + "git-hooks-nix_2": { + "inputs": { + "flake-compat": "flake-compat_8", + "nixpkgs": [ + "tg", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1783008725, + "narHash": "sha256-jGiy6+sxjNWXSjp25uoJuNfyH9zBK1PEDY0lVoL4ibQ=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "bca82caa46d5ec0f5d422c61fb1e30bc51313cbe", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, "gitignore": { "inputs": { "nixpkgs": [ @@ -665,11 +771,11 @@ ] }, "locked": { - "lastModified": 1784129366, - "narHash": "sha256-N5JiyICSeQF14x+OQebNyPpYowOT9Rs1iKyeCylSzOA=", + "lastModified": 1784351324, + "narHash": "sha256-By+kuRJZRqs2TuXgtR8vJ8cTKWXw33YG/Yollu5cO1U=", "owner": "nix-community", "repo": "home-manager", - "rev": "165228b0efefc3e635e5174020c40ea64271dc25", + "rev": "460108009ca1ff69ca2ff19079ca2c838d6e3080", "type": "github" }, "original": { @@ -1057,11 +1163,11 @@ }, "import-tree": { "locked": { - "lastModified": 1778781969, - "narHash": "sha256-Jjuz5CmSkur8KvLDoGa+vylEp+RkQtv4mt/qcMznpH0=", + "lastModified": 1784254960, + "narHash": "sha256-iI88R3wHz8wTKQb5orvpc51L/Xr64AJyxid/0MKa/b8=", "owner": "vic", "repo": "import-tree", - "rev": "d321337efd0f23a9eb14a42adb7b2c29313ab274", + "rev": "4ebb10ae17d5f1ad366e7aef5b92cb8eecf24f69", "type": "github" }, "original": { @@ -1070,6 +1176,21 @@ "type": "github" } }, + "jail-nix": { + "locked": { + "lastModified": 1776230864, + "narHash": "sha256-YsEjjdOsGEzTeD+iT7ONh071BqWAOQWpzYVei3okAXE=", + "owner": "~alexdavid", + "repo": "jail.nix", + "rev": "404e7da9da5ab9aa643666682b2ba1312fa5fbe8", + "type": "sourcehut" + }, + "original": { + "owner": "~alexdavid", + "repo": "jail.nix", + "type": "sourcehut" + } + }, "lanzaboote": { "inputs": { "crane": "crane", @@ -1254,11 +1375,11 @@ "nixpkgs-nixcord": "nixpkgs-nixcord" }, "locked": { - "lastModified": 1784137280, - "narHash": "sha256-hZeXaYv8enaEtc+8/hhPx8g+ul22xRJMOYwwWkBWquE=", + "lastModified": 1784344906, + "narHash": "sha256-aEMNpfC0Tz46TfCXcxD+x4p7+sQO6+Dwacsif0L3hUc=", "owner": "kaylorben", "repo": "nixcord", - "rev": "e6bab6344dc9f48a0b3764760ff53bbc2b9e34c3", + "rev": "c1285ad3cd7619bb58069b647086113f7e22597a", "type": "github" }, "original": { @@ -1317,11 +1438,11 @@ }, "nixpkgs-crowdsec-blocklist-import": { "locked": { - "lastModified": 1778009350, - "narHash": "sha256-TxHxJ0Gg7iGpPDXGYUBrGa2eDa+ZiyuAooQ3BuGbNPI=", + "lastModified": 1784322939, + "narHash": "sha256-7VcGiAou7zrFyOGc6phhkZBSLG2yrLa82FKRJ/F5yiY=", "owner": "gaelj", "repo": "nixpkgs", - "rev": "c018e9c39209205cf02bc60bb59da29fc05c1c37", + "rev": "7117e4d984aaaaed6998206404eceb7e6172dd81", "type": "github" }, "original": { @@ -1346,6 +1467,21 @@ "type": "github" } }, + "nixpkgs-lib_2": { + "locked": { + "lastModified": 1782614948, + "narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, "nixpkgs-nixcord": { "locked": { "lastModified": 1782999065, @@ -1456,11 +1592,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1784115452, - "narHash": "sha256-sV32iDQooUstJItsEiqRmWuhOuMcOR/vkMpHsT+87fI=", - "rev": "35d3407a3816f3b341d8cf1d60abaf2b7b8166ac", + "lastModified": 1784347607, + "narHash": "sha256-oJoPGsmXO7Xm5YagCr0La+dSTu5Lpfx35WN8vNG18BU=", + "rev": "31cd72fdba8fa052e437ce7e6879c4fe62def10f", "type": "tarball", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1035098.35d3407a3816/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1036759.31cd72fdba8f/nixexprs.tar.xz" }, "original": { "type": "tarball", @@ -1523,11 +1659,11 @@ ] }, "locked": { - "lastModified": 1784143765, - "narHash": "sha256-dBMNMSuB2pCGFY2AOCcUEKxcxR6ADyCEl1wkDx/nQ7E=", + "lastModified": 1784382342, + "narHash": "sha256-HGxlmI2ZrRoeVB/no0+Kgqg57zg7x19KXnoMyAiWPZc=", "owner": "nix-community", "repo": "NUR", - "rev": "f5fafa2e4263c03cb8641a574866a945329b88d3", + "rev": "d72fa0cd80530a3eef161b809ee0da8a65d2f250", "type": "github" }, "original": { @@ -1545,11 +1681,11 @@ ] }, "locked": { - "lastModified": 1784074700, - "narHash": "sha256-4oxCCHC/t13BxiFstM0simEC5VDK7Kpkjd8cXEcuSEU=", + "lastModified": 1784244801, + "narHash": "sha256-yBLN0aqIafMwl027R0KknTQh84VwyGao/Kj3zKv0fg8=", "owner": "adam01110", "repo": "nvf", - "rev": "cc6e2c6535dbe093861ec18c87038fe3a4d176c4", + "rev": "529c170b817e55a8395e1e2e57a5514a049a3996", "type": "github" }, "original": { @@ -1587,6 +1723,29 @@ "url": "https://tangled.org/adam0.dev/overzicht/archive/main?format=tar.gz" } }, + "pi-nix": { + "inputs": { + "bun2nix": "bun2nix", + "jail-nix": "jail-nix", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems_7" + }, + "locked": { + "lastModified": 1784355639, + "narHash": "sha256-E0II0cOOKLgiyRgexYchAHLQM/rCnZA/bFJhYkheKSQ=", + "owner": "lukasl-dev", + "repo": "pi.nix", + "rev": "b50dcf5050f73f2afaa5709433f5fbf0e4b1f683", + "type": "github" + }, + "original": { + "owner": "lukasl-dev", + "repo": "pi.nix", + "type": "github" + } + }, "pre-commit": { "inputs": { "flake-compat": "flake-compat_4", @@ -1731,10 +1890,12 @@ "nur": "nur", "nvf": "nvf", "overzicht": "overzicht", + "pi-nix": "pi-nix", "sops-nix": "sops-nix", "spicetify-nix": "spicetify-nix", "stylix": "stylix", - "treefmt-nix": "treefmt-nix_2", + "tg": "tg", + "treefmt-nix": "treefmt-nix_4", "tuigreet": "tuigreet", "zen-browser": "zen-browser" } @@ -1785,7 +1946,7 @@ "nixpkgs": [ "nixpkgs" ], - "systems": "systems_7" + "systems": "systems_8" }, "locked": { "lastModified": 1783838433, @@ -1818,7 +1979,7 @@ "nur": [ "nur" ], - "systems": "systems_8", + "systems": "systems_9", "tinted-kitty": "tinted-kitty", "tinted-schemes": "tinted-schemes", "tinted-tmux": "tinted-tmux", @@ -1958,6 +2119,21 @@ "type": "github" } }, + "systems_9": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "tangled-catppuccin": { "flake": false, "locked": { @@ -1971,6 +2147,29 @@ "url": "https://tangled.org/csw.im/tangled-catppuccin/archive/main?format=tar.gz" } }, + "tg": { + "inputs": { + "flake-parts": "flake-parts_5", + "git-hooks-nix": "git-hooks-nix_2", + "nixpkgs": [ + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix_3" + }, + "locked": { + "lastModified": 1784342465, + "narHash": "sha256-Cuvc6i5yb5Dc0MwfKhkNGS2i35H5GRjr8dHrNoox81g=", + "ref": "refs/heads/master", + "rev": "e8501f500a7c90f84aeacf00380df7163e5cec60", + "revCount": 49, + "type": "git", + "url": "https://tangled.org/aly.codes/tg" + }, + "original": { + "type": "git", + "url": "https://tangled.org/aly.codes/tg" + } + }, "tinted-kitty": { "flake": false, "locked": { @@ -2059,6 +2258,29 @@ "treefmt-nix_2": { "inputs": { "nixpkgs": [ + "pi-nix", + "bun2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775636079, + "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_3": { + "inputs": { + "nixpkgs": [ + "tg", "nixpkgs" ] }, @@ -2076,6 +2298,26 @@ "type": "github" } }, + "treefmt-nix_4": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1784369104, + "narHash": "sha256-47cxbcZODibHv3rELFQ9vZly0vUNkND/atn/U7HLeb0=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "df3c0640565d04a0261253cdd89fce78ec50168a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "tuigreet": { "inputs": { "crane": "crane_2", @@ -2085,11 +2327,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1783493073, - "narHash": "sha256-aZttkaBzM0vjC/WDz5a/uVeq57hGur/CqfBD7P9V1es=", + "lastModified": 1784381607, + "narHash": "sha256-l68bPsd9OznjeDL7tS9kmwmKu9+ZR2ktROsisWjkrLM=", "owner": "notashelf", "repo": "tuigreet", - "rev": "2bf070a0b6bb66e76d2e32500dec851e2254ab2f", + "rev": "1edddd452ee100193ec6f96c52f472be7d0640a4", "type": "github" }, "original": { @@ -2174,11 +2416,11 @@ ] }, "locked": { - "lastModified": 1784140961, - "narHash": "sha256-jG6ZDco1zQTxIUAVw7Gc+m71pX4q/It7fiso1ozLFlI=", + "lastModified": 1784287306, + "narHash": "sha256-N0uYliEuDJ7BCH9th1QKmlz1ddGjBwwL8LIvTpM/pt4=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "8f3cc4017a7bf96eb2e143597d474112433b4878", + "rev": "b039696b48f7796a753f9848cf004dbe072541f5", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1f2869b..f0a5417 100644 --- a/flake.nix +++ b/flake.nix @@ -97,6 +97,10 @@ treefmt-nix.follows = "treefmt-nix"; }; }; + pi-nix = { + url = "github:lukasl-dev/pi.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; sops-nix = { url = "github:Mic92/sops-nix"; inputs.nixpkgs.follows = "nixpkgs"; @@ -113,6 +117,10 @@ nur.follows = "nur"; }; }; + tg = { + url = "git+https://tangled.org/aly.codes/tg"; + inputs.nixpkgs.follows = "nixpkgs"; + }; treefmt-nix = { url = "github:numtide/treefmt-nix"; inputs.nixpkgs.follows = "nixpkgs"; diff --git a/modules/development/treefmt.nix b/modules/development/treefmt.nix index fdcce8e..2d7f1e1 100644 --- a/modules/development/treefmt.nix +++ b/modules/development/treefmt.nix @@ -27,7 +27,6 @@ # keep-sorted start keep-sorted.enable = true; - oxfmt.enable = true; rumdl-format.enable = true; stylua.enable = true; yamllint.enable = true; @@ -38,12 +37,6 @@ settings = { # keep-sorted start block=yes newline_separated=yes - # Keep markdown on rumdl-format. - formatter.oxfmt.excludes = [ - "*.md" - "*.mdx" - ]; - global.excludes = [ # keep-sorted start ".direnv/*" diff --git a/modules/nix/ncps.nix b/modules/nix/ncps.nix index 030778e..3a35b6a 100644 --- a/modules/nix/ncps.nix +++ b/modules/nix/ncps.nix @@ -6,10 +6,10 @@ # keep-sorted end ... }: let - inherit (lib) mkForce; + inherit (lib) mkBefore; cacheHost = "ncps.${vars.groundDomain}"; - cacheUrl = "https://${cacheHost}"; + cacheUrl = "https://${cacheHost}?priority=-10"; uploadKeySecret = "nix_ncps_upload_secret_key"; ncpsPublicKey = "${cacheHost}:jhlVJBu6bD3n1MCG153qnMFXLNfzqh7aBDK/gRULvdc="; @@ -18,9 +18,9 @@ nix.settings = { secret-key-files = [config.sops.secrets.${uploadKeySecret}.path]; - substituters = mkForce [cacheUrl]; - trusted-public-keys = mkForce [ncpsPublicKey]; - trusted-substituters = mkForce [cacheUrl]; + substituters = mkBefore [cacheUrl]; + trusted-public-keys = mkBefore [ncpsPublicKey]; + trusted-substituters = mkBefore [cacheUrl]; }; }; } diff --git a/modules/profiles/personal.nix b/modules/profiles/personal.nix index e02b33b..219be4c 100644 --- a/modules/profiles/personal.nix +++ b/modules/profiles/personal.nix @@ -95,6 +95,7 @@ gen-license gitfetch onefetch + pi pipes ripgrep-all rumdl @@ -103,7 +104,6 @@ # TUI # keep-sorted start block=yes cava - opencode wiremix # keep-sorted end diff --git a/modules/programs/gui/tangled.nix b/modules/programs/gui/tangled.nix index d6b32fa..3362ede 100644 --- a/modules/programs/gui/tangled.nix +++ b/modules/programs/gui/tangled.nix @@ -1,4 +1,9 @@ -{ +{inputs, ...}: { + flake-file.inputs.tg = { + url = "git+https://tangled.org/aly.codes/tg"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake.modules.homeManager.tangled = {pkgs, ...}: let inherit (pkgs) @@ -8,26 +13,26 @@ # keep-sorted end ; - tang = pkgs.nur.repos.adam0.tang; - tang-completions = runCommand "tang-completions" {nativeBuildInputs = [installShellFiles tang];} '' - installShellCompletion --cmd tang \ - --bash <(tang completion bash) \ - --fish <(tang completion fish) \ - --zsh <(tang completion zsh) + tg = inputs.tg.packages.${pkgs.stdenv.hostPlatform.system}.tg; + tg-completions = runCommand "tg-completions" {nativeBuildInputs = [installShellFiles tg];} '' + installShellCompletion --cmd tg \ + --bash <(tg completion bash) \ + --fish <(tg completion fish) \ + --zsh <(tg completion zsh) ''; in { - home.packages = [tang]; + home.packages = [tg]; programs.bash.initExtra = '' - source ${tang-completions}/share/bash-completion/completions/tang + source ${tg-completions}/share/bash-completion/completions/tg ''; programs.fish.interactiveShellInit = '' - source ${tang-completions}/share/fish/vendor_completions.d/tang.fish + source ${tg-completions}/share/fish/vendor_completions.d/tg.fish ''; programs.zsh.initContent = '' - source ${tang-completions}/share/zsh/site-functions/_tang + source ${tg-completions}/share/zsh/site-functions/_tg ''; }; } diff --git a/modules/programs/cli/diffnav.nix b/modules/programs/tui/diffnav.nix similarity index 93% rename from modules/programs/cli/diffnav.nix rename to modules/programs/tui/diffnav.nix index 0e18211..6240121 100644 --- a/modules/programs/cli/diffnav.nix +++ b/modules/programs/tui/diffnav.nix @@ -33,8 +33,10 @@ jujutsu.settings."--scope" = [ { "--when".commands = [ + # keep-sorted start "diff" "show" + # keep-sorted end ]; ui = { diff --git a/modules/programs/tui/opencode/default.nix b/modules/programs/tui/opencode/default.nix deleted file mode 100644 index d3489cb..0000000 --- a/modules/programs/tui/opencode/default.nix +++ /dev/null @@ -1,100 +0,0 @@ -{ - flake.modules.homeManager.opencode = { - # keep-sorted start - config, - lib, - pkgs, - # keep-sorted end - ... - }: let - inherit (builtins) attrValues; - inherit - (lib) - # keep-sorted start - concatStringsSep - escapeShellArg - getExe - getExe' - makeBinPath - mapAttrsToList - # keep-sorted end - ; - inherit (config.lib.file) mkOutOfStoreSymlink; - inherit - (pkgs) - # keep-sorted start - makeWrapper - symlinkJoin - # keep-sorted end - ; - - inherit - (config.programs.opencode) - # keep-sorted start - env - mcpServers - # keep-sorted end - ; - - home = config.home.homeDirectory; - wrapEnvArgs = concatStringsSep " \\" ( - mapAttrsToList - (name: value: "--set ${escapeShellArg name} ${escapeShellArg (toString value)}") - env - ); - in { - # keep-sorted start block=yes newline_separated=yes - home.shellAbbreviations.oc = "opencode"; - - programs.opencode = { - enable = true; - - # Wrap opencode with mcp's, and other dependencies. - package = symlinkJoin { - name = "opencode-wrapped"; - paths = [pkgs.nur.repos.adam0.opencode]; - nativeBuildInputs = [makeWrapper]; - - # Set feature flags and prepend runtime tools before launching opencode. - postBuild = '' - wrapProgram $out/bin/opencode \ - ${wrapEnvArgs} \ - --prefix PATH : ${makeBinPath ((with pkgs; [ - # keep-sorted start - libnotify - wl-clipboard - # keep-sorted end - ]) - ++ attrValues mcpServers)} - ''; - }; - - context = ./instructions.md; - }; - - xdg = { - # Point opencode at the shared agent skills directory. - configFile."opencode/skills".source = mkOutOfStoreSymlink "${home}/.agents/skills"; - - desktopEntries.opencode = { - name = "Opencode"; - genericName = "AI Coding Assistant"; - - exec = let - # keep-sorted start - opencode = getExe' config.programs.opencode.package "opencode"; - terminalCommand = getExe config.xdg.terminal-exec.package; - # keep-sorted end - in "${terminalCommand} --title=Opencode ${opencode}"; - - categories = [ - # keep-sorted start - "ConsoleOnly" - "Development" - # keep-sorted end - ]; - }; - }; - # keep-sorted end - }; -} diff --git a/modules/programs/tui/opencode/env.nix b/modules/programs/tui/opencode/env.nix deleted file mode 100644 index 6d8b17b..0000000 --- a/modules/programs/tui/opencode/env.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - flake.modules.homeManager.opencode = {lib, ...}: let - inherit - (lib) - # keep-sorted start - mkOption - types - # keep-sorted end - ; - inherit (lib.self) envFlags; - in { - options.programs.opencode.env = mkOption { - description = "Environment variables set by the wrapped opencode launcher."; - - type = types.attrsOf types.anything; - default = {}; - }; - - config.programs.opencode.env = let - experimentalFeatures = [ - # keep-sorted start - "FILEWATCHER" - "ICON_DISCOVERY" - "LSP_TOOL" - "LSP_TY" - # keep-sorted end - ]; - - # Keep builtin lsp downloads disabled and rely on nix instead. - disabledFeatures = [ - # keep-sorted start - "AUTOCOMPACT" - "LSP_DOWNLOAD" - # keep-sorted end - ]; - enabledFeatures = ["EXA"]; - in - envFlags "OPENCODE_EXPERIMENTAL" experimentalFeatures - // envFlags "OPENCODE_DISABLE" disabledFeatures - // envFlags "OPENCODE_ENABLE" enabledFeatures; - }; -} diff --git a/modules/programs/tui/opencode/formatter.nix b/modules/programs/tui/opencode/formatter.nix deleted file mode 100644 index d5c47d7..0000000 --- a/modules/programs/tui/opencode/formatter.nix +++ /dev/null @@ -1,162 +0,0 @@ -{ - flake.modules.homeManager.opencode = { - # keep-sorted start - lib, - pkgs, - # keep-sorted end - ... - }: let - inherit - (lib) - # keep-sorted start - getExe - getExe' - # keep-sorted end - ; - in { - programs.opencode.settings.formatter = { - # keep-sorted start block=yes newline_separated=yes - alejandra = { - command = [(getExe pkgs.alejandra) "$FILE"]; - extensions = [".nix"]; - }; - - fish_indent = { - command = [ - (getExe' pkgs.fish "fish_indent") - "--write" - "$FILE" - ]; - extensions = [".fish"]; - }; - - ktlint = { - command = [ - (getExe pkgs.ktlint) - "-F" - "$FILE" - ]; - extensions = [ - # keep-sorted start - ".kt" - ".kts" - # keep-sorted end - ]; - }; - - oxfmt = { - command = [ - (getExe pkgs.oxfmt) - "$FILE" - ]; - extensions = [ - # keep-sorted start - ".cjs" - ".css" - ".hbs" - ".html" - ".js" - ".json" - ".json5" - ".jsonc" - ".jsx" - ".less" - ".mjs" - ".scss" - ".toml" - ".ts" - ".tsx" - ".yaml" - ".yml" - # keep-sorted end - ]; - }; - - oxlint = { - command = [ - (getExe pkgs.oxlint) - "--fix" - "$FILE" - ]; - extensions = [ - # keep-sorted start - ".cjs" - ".js" - ".jsx" - ".mjs" - ".ts" - ".tsx" - # keep-sorted end - ]; - }; - - ruff = { - command = [ - (getExe pkgs.ruff) - "format" - "$FILE" - ]; - extensions = [ - # keep-sorted start - ".py" - ".pyi" - # keep-sorted end - ]; - }; - - rustfmt = { - command = [(getExe pkgs.rustfmt) "$FILE"]; - extensions = [".rs"]; - }; - - shfmt = { - command = [ - (getExe pkgs.shfmt) - "--write" - "$FILE" - ]; - extensions = [ - # keep-sorted start - ".bash" - ".sh" - # keep-sorted end - ]; - }; - - stylua = { - command = [ - (getExe pkgs.stylua) - "--respect-ignores" - "$FILE" - ]; - extensions = [".lua"]; - }; - - rumdl = { - command = [ - (getExe pkgs.rumdl) - "fmt" - "--fix" - "$FILE" - ]; - extensions = [ - # keep-sorted start - ".markdown" - ".md" - # keep-sorted end - ]; - }; - - selene = { - command = [ - (getExe pkgs.selene) - "--display-style" - "quiet" - "$FILE" - ]; - extensions = [".lua"]; - }; - # keep-sorted end - }; - }; -} diff --git a/modules/programs/tui/opencode/lsp.nix b/modules/programs/tui/opencode/lsp.nix deleted file mode 100644 index 761831a..0000000 --- a/modules/programs/tui/opencode/lsp.nix +++ /dev/null @@ -1,150 +0,0 @@ -{ - flake.modules.homeManager.opencode = { - # keep-sorted start - lib, - pkgs, - # keep-sorted end - ... - }: let - inherit - (lib) - # keep-sorted start - getExe - getExe' - # keep-sorted end - ; - in { - programs.opencode.settings.lsp = { - # keep-sorted start block=yes newline_separated=yes - bash = { - command = [(getExe pkgs.bash-language-server) "start"]; - extensions = [ - # keep-sorted start - ".bash" - ".sh" - # keep-sorted end - ]; - }; - - cssls = { - command = [(getExe' pkgs.vscode-langservers-extracted "vscode-css-language-server")]; - extensions = ["css"]; - }; - - csharp = { - command = [(getExe pkgs.csharp-ls)]; - extensions = [ - # keep-sorted start - ".cs" - ".csx" - # keep-sorted end - ]; - }; - - json = { - command = [(getExe pkgs.vscode-json-languageserver) "--stdio"]; - extensions = [ - # keep-sorted start - ".json" - ".jsonc" - # keep-sorted end - ]; - }; - - jdtls = { - command = [(getExe pkgs.jdt-language-server)]; - extensions = [".java"]; - }; - - lemminx = { - command = [(getExe pkgs.lemminx)]; - extensions = [".xml"]; - }; - - kotlin-ls = { - command = [(getExe pkgs.kotlin-language-server)]; - extensions = [".kt" ".kts"]; - }; - - lua = { - command = [(getExe pkgs.lua-language-server)]; - extensions = [".lua"]; - }; - - nixd = { - command = [(getExe pkgs.nixd)]; - extensions = [".nix"]; - }; - - oxlint = { - command = [(getExe pkgs.oxlint)]; - extensions = [ - # keep-sorted start - ".js" - ".jsx" - ".ts" - ".tsx" - # keep-sorted end - ]; - }; - - rumdl = { - command = [(getExe pkgs.rumdl) "server"]; - extensions = [ - # keep-sorted start - ".markdown" - ".md" - # keep-sorted end - ]; - }; - - rust = { - command = [(getExe pkgs.rust-analyzer)]; - extensions = [".rs"]; - }; - - superhtml = { - command = [(getExe pkgs.superhtml) "lsp"]; - extensions = [".html"]; - }; - - tombi = { - command = [(getExe pkgs.tombi) "lsp"]; - extensions = [".toml"]; - }; - - ty = { - command = [(getExe pkgs.ty)]; - extensions = [ - # keep-sorted start - ".py" - ".pyi" - # keep-sorted end - ]; - }; - - typescript = { - command = [(getExe pkgs.typescript-language-server) "--stdio"]; - extensions = [ - # keep-sorted start - ".js" - ".jsx" - ".ts" - ".tsx" - # keep-sorted end - ]; - }; - - yaml-ls = { - command = [(getExe pkgs.yaml-language-server) "--stdio"]; - extensions = [ - # keep-sorted start - ".yaml" - ".yml" - # keep-sorted end - ]; - }; - # keep-sorted end - }; - }; -} diff --git a/modules/programs/tui/opencode/mcp-wrappers.nix b/modules/programs/tui/opencode/mcp-wrappers.nix deleted file mode 100644 index f642895..0000000 --- a/modules/programs/tui/opencode/mcp-wrappers.nix +++ /dev/null @@ -1,81 +0,0 @@ -{ - flake.modules.homeManager.opencode = { - # keep-sorted start - config, - lib, - pkgs, - vars, - # keep-sorted end - ... - }: let - inherit - (lib) - # keep-sorted start - getExe - getExe' - mkOption - types - # keep-sorted end - ; - inherit (pkgs) writeShellApplication; - - # keep-sorted start block=yes newline_separated=yes - authentikMcpWrapper = writeShellApplication { - name = "authentik-mcp-wrapper"; - runtimeInputs = [pkgs.coreutils]; - text = '' - AUTHENTIK_TOKEN="$(cat "${config.sops.secrets."ai/authentik_token".path}")" - export AUTHENTIK_TOKEN - exec "${getExe' pkgs.uv "uvx"}" authentik-mcp --base-url "https://authentik.${vars.groundDomain}" --token "$AUTHENTIK_TOKEN" "$@" - ''; - }; - - context7McpWrapper = writeShellApplication { - name = "context7-mcp-wrapper"; - runtimeInputs = [pkgs.coreutils]; - text = '' - CONTEXT7_API_KEY="$(cat "${config.sops.secrets."ai/context7_key".path}")" - export CONTEXT7_API_KEY - exec "${getExe pkgs.context7-mcp}" "$@" - ''; - }; - - githubMcpServerWrapper = writeShellApplication { - name = "github-mcp-server-wrapper"; - runtimeInputs = [pkgs.coreutils]; - text = '' - GITHUB_PERSONAL_ACCESS_TOKEN="$(cat "${config.sops.secrets."ai/github_token".path}")" - export GITHUB_PERSONAL_ACCESS_TOKEN - exec "${getExe pkgs.github-mcp-server}" stdio --read-only "$@" - ''; - }; - # keep-sorted end - in { - options.programs.opencode.mcpServers = mkOption { - description = "MCP server packages added to the wrapped opencode launcher PATH."; - - type = types.attrsOf types.package; - default = {}; - }; - - config = { - sops.secrets = { - # keep-sorted start - "ai/authentik_token" = {}; - "ai/context7_key" = {}; - "ai/github_token" = {}; - # keep-sorted end - }; - - programs.opencode.mcpServers = { - inherit - # keep-sorted start - authentikMcpWrapper - context7McpWrapper - githubMcpServerWrapper - # keep-sorted end - ; - }; - }; - }; -} diff --git a/modules/programs/tui/opencode/plugins.nix b/modules/programs/tui/opencode/plugins.nix deleted file mode 100644 index 29751e2..0000000 --- a/modules/programs/tui/opencode/plugins.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - flake.modules.homeManager.opencode = {pkgs, ...}: { - imports = [pkgs.nur.repos.adam0.hmModules.opencode-plugins]; - - programs.opencode.plugins = { - # keep-sorted start - cc-safety-net.enable = true; - changelog.enable = true; - ignore.enable = true; - lazy-mcp.enable = true; - unmoji.enable = true; - # keep-sorted end - - # keep-sorted start block=yes newline_separated=yes - notifier = { - enable = true; - settings.sound = false; - }; - - quota = { - #enable = true; - #sidebar.enable = true; - - settings = { - # keep-sorted start - enableToast = false; - formatStyle = "allWindows"; - # keep-sorted end - }; - }; - # keep-sorted end - }; - }; -} diff --git a/modules/programs/tui/opencode/settings.nix b/modules/programs/tui/opencode/settings.nix deleted file mode 100644 index 16d1882..0000000 --- a/modules/programs/tui/opencode/settings.nix +++ /dev/null @@ -1,38 +0,0 @@ -{ - flake.modules.homeManager.opencode = {config, ...}: let - home = config.home.homeDirectory; - in { - programs.opencode = { - settings = { - # keep-sorted start block=yes newline_separated=yes - autoupdate = false; - - permission.external_directory = { - # keep-sorted start - "${home}/Infra/**" = "allow"; - "/nix/store/**" = "allow"; - # keep-sorted end - }; - - watcher.ignore = [ - # keep-sorted start - ".direnv/**" - ".git/**" - ".rumdl_cache/**" - "dist/**" - "node_modules/**" - "target/**" - # keep-sorted end - ]; - # keep-sorted end - - # keep-sorted start - agent.explore.reasoningEffort = "low"; - agent.general.reasoningEffort = "low"; - # keep-sorted end - }; - - tui.scroll_acceleration.enabled = true; - }; - }; -} diff --git a/modules/programs/tui/opencode/skills/default.nix b/modules/programs/tui/opencode/skills/default.nix deleted file mode 100644 index 26e5c73..0000000 --- a/modules/programs/tui/opencode/skills/default.nix +++ /dev/null @@ -1,39 +0,0 @@ -{ - flake.modules.homeManager.opencode = {lib, ...}: let - inherit - (builtins) - # keep-sorted start - attrNames - foldl' - readDir - # keep-sorted end - ; - inherit - (lib) - # keep-sorted start - filterAttrs - hasSuffix - mapAttrs' - nameValuePair - removeSuffix - # keep-sorted end - ; - - mkSkills = prefix: dir: - mapAttrs' ( - name: _: - nameValuePair "${prefix}${removeSuffix ".md" name}" { - source = dir + "/${name}"; - } - ) (filterAttrs (name: type: type == "regular" && hasSuffix ".md" name) (readDir dir)); - - rootEntries = readDir ./.; - skillDirectories = attrNames (filterAttrs (_: type: type == "directory") rootEntries); - rootSkills = mkSkills "" ./.; - prefixedSkills = foldl' (acc: dir: acc // mkSkills "${dir}-" (./. + "/${dir}")) {} skillDirectories; - in { - home.file = mapAttrs' ( - name: value: nameValuePair ".agents/skills/${name}/SKILL.md" value - ) (rootSkills // prefixedSkills); - }; -} diff --git a/modules/programs/tui/opencode/skills/lazy-mcp/authentik.md b/modules/programs/tui/opencode/skills/lazy-mcp/authentik.md deleted file mode 100644 index aa9323e..0000000 --- a/modules/programs/tui/opencode/skills/lazy-mcp/authentik.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: lazy-mcp-authentik -description: Use this skill when you need to inspect or administer Authentik users, groups, applications, flows, providers, events, tokens, or system health through the Authentik MCP server. -mcp: - authentik: - command: ["authentik-mcp-wrapper"] ---- - -# Authentik MCP - -Use this skill to query and administer Authentik through the Authentik MCP server. - -## Workflow - -1. Identify the Authentik object type and exact target first. -2. Prefer read-only inspection before making changes. -3. Use write operations only when the user explicitly asks for an Authentik change. - -## Usage Rules - -- Be explicit about names, IDs, slugs, or filters before calling tools. -- Confirm destructive operations before invoking delete or revoke actions. -- Keep follow-up queries scoped to the same Authentik object until the user switches context. diff --git a/modules/programs/tui/opencode/skills/lazy-mcp/context7.md b/modules/programs/tui/opencode/skills/lazy-mcp/context7.md deleted file mode 100644 index 2b482bf..0000000 --- a/modules/programs/tui/opencode/skills/lazy-mcp/context7.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: lazy-mcp-context7 -description: Use this skill when you need up-to-date library or framework documentation. Resolve the correct Context7 library ID, then query the docs for APIs, configuration, setup steps, or code generation. -mcp: - context7: - command: ["context7-mcp-wrapper"] ---- - -# Context7 Documentation Search - -Use this skill to search current, library-specific documentation through Context7. - -## Workflow - -1. Resolve the library ID with `resolve-library-id` when you do not already know it. -2. Query the documentation with `query-docs` using the resolved ID. - -If you already know the exact Context7 library ID in `/org/project` format, skip the resolution step. - -## Usage Rules - -- Prefer Context7 over memory when the answer depends on current library behavior. -- Include the library name, feature, and version in the query when possible. -- Keep queries specific enough to retrieve code and configuration details. -- Use the resolved library ID directly for follow-up lookups in the same task. diff --git a/modules/programs/tui/opencode/skills/lazy-mcp/github.md b/modules/programs/tui/opencode/skills/lazy-mcp/github.md deleted file mode 100644 index 6900654..0000000 --- a/modules/programs/tui/opencode/skills/lazy-mcp/github.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: lazy-mcp-github -description: Use this skill when you need GitHub repository data. Query repositories, code, issues, pull requests, commits, branches, releases, workflows, Actions, CI/CD state, collaborators, permissions, or repository metadata. -mcp: - github: - command: ["github-mcp-server-wrapper"] ---- - -# GitHub MCP - -Use this skill to query and inspect GitHub data through the GitHub MCP server. - -## Workflow - -1. Identify the owner and repository first. -2. Query the GitHub MCP tool that matches the requested GitHub object. -3. Reuse repository context across follow-up lookups in the same task. - -## Usage Rules - -- Prefer the GitHub MCP server over memory for repository state and metadata. -- Be explicit about `owner`, `repo`, branch, PR number, issue number, or commit SHA. -- Use read-only inspection workflows unless the user explicitly asks for a write action and the available tools support it. -- Keep follow-up queries scoped to the same repository until the user switches context. diff --git a/modules/programs/tui/opencode/skills/lazy-mcp/grep-app.md b/modules/programs/tui/opencode/skills/lazy-mcp/grep-app.md deleted file mode 100644 index d97a876..0000000 --- a/modules/programs/tui/opencode/skills/lazy-mcp/grep-app.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: lazy-mcp-grep-app -description: Use this skill when you need real-world code examples from public GitHub repositories. Search implementation patterns, API usage, error strings, or language-specific examples through grep.app. -mcp: - grep-app: - type: remote - url: https://mcp.grep.app ---- - -# grep.app Code Search - -Use this skill to search real code examples from public GitHub repositories through grep.app. - -## Workflow - -1. Search for a concrete code pattern instead of a generic topic. -2. Narrow the query by language or repository when needed. -3. Reuse the strongest matching pattern for follow-up searches in the same task. - -## Usage Rules - -- Prefer code-shaped queries such as function names, imports, types, or error strings. -- Use language filters when the user needs examples in a specific ecosystem. -- Use repository filters when the user wants patterns from a known project. -- Treat grep.app as a source of examples, not as authoritative documentation. diff --git a/modules/programs/tui/opencode/skills/uncodixfy.md b/modules/programs/tui/opencode/skills/uncodixfy.md deleted file mode 100644 index b88888c..0000000 --- a/modules/programs/tui/opencode/skills/uncodixfy.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -name: uncodixfy -description: Use this skill whenever generating frontend UI code. Avoid generic AI UI patterns and enforce clean, human-designed HTML, CSS, React, Vue, or Svelte interfaces. -license: MIT ---- - -# Uncodixify - -This document exists to teach you how to act as non-Codex as possible when building UI. - -Codex UI is the default AI aesthetic: soft gradients, floating panels, eyebrow labels, decorative copy, hero sections in dashboards, oversized rounded corners, transform animations, dramatic shadows, and layouts that try too hard to look premium. It's the visual language that screams "an AI made this" because it follows the path of least resistance. - -This file is your guide to break that pattern. Everything listed below is what Codex UI does by default. Your job is to recognize these patterns, avoid them completely, and build interfaces that feel human-designed, functional, and honest. - -When you read this document, you're learning what NOT to do. The banned patterns are your red flags. The normal implementations are your blueprint. Follow them strictly, and you'll create UI that feels like Linear, Raycast, Stripe, or GitHub—not like another generic AI dashboard. - -This is how you Uncodixify. - -## Keep It Normal (Uncodexy-UI Standard) - -- Sidebars: normal (240-260px fixed width, solid background, simple border-right, no floating shells, no rounded outer corners) -- Headers: normal (simple text, no eyebrows, no uppercase labels, no gradient text, just h1/h2 with proper hierarchy) -- Sections: normal (standard padding 20-30px, no hero blocks inside dashboards, no decorative copy) -- Navigation: normal (simple links, subtle hover states, no transform animations, no badges unless functional) -- Buttons: normal (solid fills or simple borders, 8-10px radius max, no pill shapes, no gradient backgrounds) -- Cards: normal (simple containers, 8-12px radius max, subtle borders, no shadows over 8px blur, no floating effect) -- Forms: normal (standard inputs, clear labels above fields, no fancy floating labels, simple focus states) -- Inputs: normal (solid borders, simple focus ring, no animated underlines, no morphing shapes) -- Modals: normal (centered overlay, simple backdrop, no slide-in animations, straightforward close button) -- Dropdowns: normal (simple list, subtle shadow, no fancy animations, clear selected state) -- Tables: normal (clean rows, simple borders, subtle hover, no zebra stripes unless needed, left-aligned text) -- Lists: normal (simple items, consistent spacing, no decorative bullets, clear hierarchy) -- Tabs: normal (simple underline or border indicator, no pill backgrounds, no sliding animations) -- Badges: normal (small text, simple border or background, 6-8px radius, no glows, only when needed) -- Avatars: normal (simple circle or rounded square, no decorative borders, no status rings unless functional) -- Icons: normal (simple shapes, consistent size 16-20px, no decorative icon backgrounds, monochrome or subtle color) -- Typography: normal (system fonts or simple sans-serif, clear hierarchy, no mixed serif/sans combos, readable sizes 14-16px body) -- Spacing: normal (consistent scale 4/8/12/16/24/32px, no random gaps, no excessive padding) -- Borders: normal (1px solid, subtle colors, no thick decorative borders, no gradient borders) -- Shadows: normal (subtle 0 2px 8px rgba(0,0,0,0.1) max, no dramatic drop shadows, no colored shadows) -- Transitions: normal (100-200ms ease, no bouncy animations, no transform effects, simple opacity/color changes) -- Layouts: normal (standard grid/flex, no creative asymmetry, predictable structure, clear content hierarchy) -- Grids: normal (consistent columns, standard gaps, no creative overlaps, responsive breakpoints) -- Flexbox: normal (simple alignment, standard gaps, no creative justify tricks) -- Containers: normal (max-width 1200-1400px, centered, standard padding, no creative widths) -- Wrappers: normal (simple containing divs, no decorative purposes, functional only) -- Panels: normal (simple background differentiation, subtle borders, no floating detached panels, no glass effects) -- Toolbars: normal (simple horizontal layout, standard height 48-56px, clear actions, no decorative elements) -- Footers: normal (simple layout, standard links, no decorative sections, minimal height) -- Breadcrumbs: normal (simple text with separators, no fancy styling, clear hierarchy) - -Think Linear. Think Raycast. Think Stripe. Think GitHub. They don't try to grab attention. They just work. Stop playing hard to get. Make normal UI. - -- A landing page needs its sections. If hero needs full sections, if dashboard needs full sections with sidebar and everything else laid out properly. DO NOT invent a new layout. -- In your internal reasoning act as if you dont see this, list all the stuff you would do, AND DONT DO IT! -- Try to replicate figma/designer made components dont invent your own - -## Hard No - -- Everything you are used to doing and is a basic "YES" to you. -- No oversized rounded corners. -- No pill overload. -- No floating glassmorphism shells as the default visual language. -- No soft corporate gradients used to fake taste. -- No generic dark SaaS UI composition. -- No decorative sidebar blobs. -- No "control room" cosplay unless explicitly requested. -- No serif headline + system sans fallback combo as a shortcut to "premium." -- No `Segoe UI`, `Trebuchet MS`, `Arial`, `Inter`, `Roboto`, or safe default stacks unless the product already uses them. -- No sticky left rail unless the information architecture truly needs it. -- No metric-card grid as the first instinct. -- No fake charts that exist only to fill space. -- No random glows, blur haze, frosted panels, or conic-gradient donuts as decoration. -- No "hero section" inside an internal UI unless there is a real product reason. -- No alignment that creates dead space just to look expensive. -- No overpadded layouts. -- No mobile collapse that just stacks everything into one long beige sandwich. -- No ornamental labels like "live pulse", "night shift", "operator checklist" unless they come from the product voice. -- No generic startup copy. -- No style decisions made because they are easy to generate. -- No Headlines of any sort - -```html -
- Team Command -

One place to track what matters today.

-

- The layout stays strict and readable: live project health, - team activity, and near-term priorities without the usual - dashboard filler. -

-
-``` - -This is not allowed. - -- `` headers are NOT allowed -- Big no to rounded `span`s -- Colors going towards blue — **NOPE, bad.** Dark muted colors are best. -- Anything in the structure of this card is a **BIG no**. - -```html -
- Focus - - Keep updates brief, blockers visible, and next actions easy to spot. - -
-``` - -This one is **THE BIGGEST NO**. - -## Specifically Banned (Based on Mistakes) - -- Border radii in the 20px to 32px range across everything ( uses 12px everywhere - too much) -- Repeating the same rounded rectangle on sidebar, cards, buttons, and panels -- Sidebar width around 280px with a brand block on top and nav links below (: 248px with brand block) -- Floating detached sidebar with rounded outer shell -- Canvas chart placed in a glass card with no product-specific reason -- Donut chart paired with hand-wavy percentages -- UI cards using glows instead of hierarchy -- Mixed alignment logic where some content hugs the left edge and some content floats in center-ish blocks -- Overuse of muted gray-blue text that weakens contrast and clarity -- "Premium dark mode" that really means blue-black gradients plus cyan accents ( has radial gradients in background) -- UI typography that feels like a template instead of a brand -- Eyebrow labels (: "MARCH SNAPSHOT" uppercase with letter-spacing) -- Hero sections inside dashboards ( has full hero-strip with decorative copy) -- Decorative copy like "Operational clarity without the clutter" as page headers -- Section notes and mini-notes everywhere explaining what the UI does -- Transform animations on hover (: translateX(2px) on nav links) -- Dramatic box shadows (: 0 24px 60px rgba(0,0,0,0.35)) -- Status indicators with ::before pseudo-elements creating colored dots -- Muted labels with uppercase + letter-spacing ( overuses this pattern) -- Pipeline bars with gradient fills (: linear-gradient(90deg, var(--primary), #4fe0c0)) -- KPI cards in a grid as the default dashboard layout ( has 3-column kpi-grid) -- "Team focus" or "Recent activity" panels with decorative internal copy -- Tables with tag badges for every status ( overuses .tag class) -- Workspace blocks in sidebar with call-to-action buttons -- Brand marks with gradient backgrounds (: linear-gradient(135deg, #2a2a2a, #171717)) -- Nav badges showing counts or "Live" status ( has nav-badge class) -- Quota/usage panels with progress bars ( has three quota sections) -- Footer lines with meta information (: "Northstar dashboard • dark mode • single-file HTML") -- Trend indicators with colored text (: trend-up, trend-flat classes) -- Rail panels on the right side with "Today" schedule ( has full right rail) -- Multiple nested panel types (panel, panel-2, rail-panel, table-panel) - -## Rule - -If a UI choice feels like a default AI UI move, ban it and pick the harder, cleaner option. - -- Colors should stay calm, not fight. - -- You are bad at picking colors follow this priority order when selecting colors: - -1. **Highest priority:** Use the existing colors from the user's project if they are provided (You can search for them by reading a few files). -2. If the project does not provide a palette, **get inspired from one of the predefined palettes below**. -3. Do **not invent random color combinations** unless explicitly requested. diff --git a/modules/programs/tui/opencode/theme.nix b/modules/programs/tui/opencode/theme.nix deleted file mode 100644 index d73ce3a..0000000 --- a/modules/programs/tui/opencode/theme.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ - flake.modules.homeManager.opencode = { - # Keep the opencode background transparent to match stylix. - stylix.targets.opencode = { - enable = true; - - colors.override.withHashtag = { - base00 = "none"; - base06 = "none"; - }; - }; - }; -} diff --git a/modules/programs/tui/pi/agents/coding.md b/modules/programs/tui/pi/agents/coding.md new file mode 100644 index 0000000..652391a --- /dev/null +++ b/modules/programs/tui/pi/agents/coding.md @@ -0,0 +1,11 @@ +--- +description: Coding agent for implementing and verifying changes +display_name: Coding +model: sol +prompt_mode: append +--- + +# Coding + +Implement assigned changes completely. Follow the inherited project instructions +and verify the result with the relevant checks. diff --git a/modules/programs/tui/pi/agents/explore.md b/modules/programs/tui/pi/agents/explore.md new file mode 100644 index 0000000..83eb51a --- /dev/null +++ b/modules/programs/tui/pi/agents/explore.md @@ -0,0 +1,35 @@ +--- +description: Fast read-only search agent for locating code +display_name: Explore +tools: read, bash, grep, find, ls +model: luna +--- + +# Critical: Read-Only Mode + +You are a file search specialist. Your role is exclusively to search and +analyze existing code. You do not have access to file editing tools. + +You are prohibited from: + +- Creating, modifying, deleting, moving, or copying files +- Creating temporary files +- Using redirects, pipes, or heredocs to write files +- Running commands that change system state + +Use Bash only for read-only operations such as `git status`, `git log`, and +`git diff`. + +## Tool Usage + +- Use `find` for file pattern matching, not Bash `find`. +- Use `grep` for content searches, not Bash `grep` or `rg`. +- Use `read` for reading files, not Bash `cat`, `head`, or `tail`. +- Make independent tool calls in parallel. +- Adapt the search approach to the requested thoroughness. + +## Output + +- Use absolute paths in references. +- Report findings as regular messages. +- Be thorough and precise. diff --git a/modules/programs/tui/pi/agents/general-purpose.md b/modules/programs/tui/pi/agents/general-purpose.md new file mode 100644 index 0000000..5fb0121 --- /dev/null +++ b/modules/programs/tui/pi/agents/general-purpose.md @@ -0,0 +1,6 @@ +--- +description: General-purpose agent for research and multi-step tasks +display_name: Agent +model: terra +prompt_mode: append +--- diff --git a/modules/programs/tui/pi/agents/plan.md b/modules/programs/tui/pi/agents/plan.md new file mode 100644 index 0000000..ba6dfea --- /dev/null +++ b/modules/programs/tui/pi/agents/plan.md @@ -0,0 +1,31 @@ +--- +description: Read-only software architect for implementation planning +display_name: Plan +tools: read, bash, grep, find, ls +model: sol +--- + +# Critical: Read-Only Mode + +You are a software architect and planning specialist. Explore the codebase and +design implementation plans without modifying files or system state. + +## Planning Process + +1. Understand the requirements. +2. Explore the relevant code and existing patterns. +3. Design a solution and evaluate its tradeoffs. +4. Provide a sequenced implementation plan. + +## Tool Usage + +- Use `find` for file pattern matching, not Bash `find`. +- Use `grep` for content searches, not Bash `grep` or `rg`. +- Use `read` for reading files, not Bash `cat`, `head`, or `tail`. +- Use Bash only for read-only operations. + +## Output + +- Use absolute paths in references. +- Identify dependencies, risks, and sequencing constraints. +- End with a list of the three to five files most critical to implementation. diff --git a/modules/programs/tui/pi/default.nix b/modules/programs/tui/pi/default.nix new file mode 100644 index 0000000..8e9d661 --- /dev/null +++ b/modules/programs/tui/pi/default.nix @@ -0,0 +1,77 @@ +{inputs, ...}: { + flake-file.inputs.pi-nix = { + url = "github:lukasl-dev/pi.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + flake.modules.homeManager.pi = { + # keep-sorted start + config, + lib, + pkgs, + # keep-sorted end + ... + }: let + inherit (builtins) attrValues; + inherit + (lib) + # keep-sorted start + getExe + makeBinPath + # keep-sorted end + ; + inherit + (pkgs) + # keep-sorted start + makeWrapper + symlinkJoin + # keep-sorted end + ; + + runtimePackages = + (with pkgs; [ + # keep-sorted start + rtk + wl-clipboard + # keep-sorted end + ]) + ++ attrValues config.programs.pi.mcpServers; + in { + imports = [inputs.pi-nix.homeModules.default]; + + programs.pi.coding-agent = { + enable = true; + + # Provide runtime commands used by Pi and its extensions. + package = symlinkJoin { + name = "pi-coding-agent-wrapped"; + paths = [inputs.pi-nix.packages.${pkgs.stdenv.hostPlatform.system}.coding-agent]; + nativeBuildInputs = [makeWrapper]; + postBuild = '' + wrapProgram $out/bin/pi \ + --prefix PATH : ${makeBinPath runtimePackages} + ''; + meta.mainProgram = "pi"; + }; + + rules = ./instructions.md; + }; + + xdg.desktopEntries.pi = { + name = "Pi"; + genericName = "AI Coding Assistant"; + + exec = let + pi = getExe config.programs.pi.coding-agent.finalPackage; + terminalCommand = getExe config.xdg.terminal-exec.package; + in "${terminalCommand} --title=Pi ${pi}"; + + categories = [ + # keep-sorted start + "ConsoleOnly" + "Development" + # keep-sorted end + ]; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/atuin.nix b/modules/programs/tui/pi/extensions/atuin.nix new file mode 100644 index 0000000..145a918 --- /dev/null +++ b/modules/programs/tui/pi/extensions/atuin.nix @@ -0,0 +1,8 @@ +{ + flake.modules.homeManager.pi = {lib, ...}: let + inherit (lib) mkOrder; + in { + # Replace QOL's editor while retaining its other features. + programs.pi.coding-agent.extensions = mkOrder 1250 ["npm:pi-atuin@0.1.8"]; + }; +} diff --git a/modules/programs/tui/pi/extensions/btw.nix b/modules/programs/tui/pi/extensions/btw.nix new file mode 100644 index 0000000..5a7e53e --- /dev/null +++ b/modules/programs/tui/pi/extensions/btw.nix @@ -0,0 +1,5 @@ +{ + flake.modules.homeManager.pi = { + programs.pi.coding-agent.extensions = ["npm:pi-btw@0.4.1"]; + }; +} diff --git a/modules/programs/tui/pi/extensions/cache.nix b/modules/programs/tui/pi/extensions/cache.nix new file mode 100644 index 0000000..4217189 --- /dev/null +++ b/modules/programs/tui/pi/extensions/cache.nix @@ -0,0 +1,38 @@ +{ + flake.modules.homeManager.pi = {pkgs, ...}: let + cacheStatusColor = pkgs.writeText "cache-status-color.ts" '' + import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + + export default function (pi: ExtensionAPI): void { + let restoreStatus: (() => void) | undefined; + + pi.on("session_start", (_event, ctx) => { + if (!ctx.hasUI) return; + + const ui = ctx.ui; + const setStatus = ui.setStatus; + const styledSetStatus = (key: string, text: string | undefined): void => + setStatus.call( + ui, + key, + key === "pi-cache-stats" && text + ? ui.theme.fg("dim", text) + : text, + ); + + ui.setStatus = styledSetStatus; + restoreStatus = () => { + if (ui.setStatus === styledSetStatus) ui.setStatus = setStatus; + }; + }); + + pi.on("session_shutdown", () => restoreStatus?.()); + } + ''; + in { + programs.pi.coding-agent.extensions = [ + cacheStatusColor + "npm:pi-cache-optimizer@2.6.17" + ]; + }; +} diff --git a/modules/programs/tui/pi/extensions/cwd.nix b/modules/programs/tui/pi/extensions/cwd.nix new file mode 100644 index 0000000..48a29d5 --- /dev/null +++ b/modules/programs/tui/pi/extensions/cwd.nix @@ -0,0 +1,5 @@ +{ + flake.modules.homeManager.pi = { + programs.pi.coding-agent.extensions = ["npm:@harms-haus/pi-cwd@1.0.0"]; + }; +} diff --git a/modules/programs/tui/pi/extensions/fff.nix b/modules/programs/tui/pi/extensions/fff.nix new file mode 100644 index 0000000..e298438 --- /dev/null +++ b/modules/programs/tui/pi/extensions/fff.nix @@ -0,0 +1,9 @@ +{ + flake.modules.homeManager.pi = { + programs.pi.coding-agent = { + extensions = ["npm:@ff-labs/pi-fff@0.10.0"]; + + environment.PI_FFF_MODE.value = "override"; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/footer.nix b/modules/programs/tui/pi/extensions/footer.nix new file mode 100644 index 0000000..e7b78fb --- /dev/null +++ b/modules/programs/tui/pi/extensions/footer.nix @@ -0,0 +1,56 @@ +{ + flake.modules.homeManager.pi = {pkgs, ...}: let + jsonFormat = pkgs.formats.json {}; + in { + programs.pi.coding-agent.extensions = ["npm:pi-footer@0.4.2"]; + + home.file.".pi/agent/extensions/pi-footer.json".source = jsonFormat.generate "pi-footer.json" { + iconMode = "nerd"; + + lines = [ + [ + { + id = "model"; + type = "model-provider"; + } + + { + id = "thinking"; + type = "thinking-level"; + } + + { + id = "verbosity"; + type = "text-verbosity"; + } + + { + id = "tokens"; + type = "tokens"; + options.tokenFormatStyle = "compact"; + } + + { + id = "cost"; + type = "cost"; + } + + { + id = "time"; + type = "total-time"; + } + + { + id = "context"; + type = "context-bar"; + options = { + contextBarMode = "medium"; + fg = "blue"; + tokenFormatStyle = "compact"; + }; + } + ] + ]; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/formatter.nix b/modules/programs/tui/pi/extensions/formatter.nix new file mode 100644 index 0000000..358e677 --- /dev/null +++ b/modules/programs/tui/pi/extensions/formatter.nix @@ -0,0 +1,147 @@ +{ + flake.modules.homeManager.pi = { + # keep-sorted start + lib, + pkgs, + # keep-sorted end + ... + }: let + inherit + (lib) + # keep-sorted start + getExe + getExe' + # keep-sorted end + ; + jsonFormat = pkgs.formats.json {}; + autoformatRenderer = pkgs.writeText "pi-autoformat-renderer.ts" '' + import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + import { Text } from "@earendil-works/pi-tui"; + + export default function (pi: ExtensionAPI): void { + pi.registerMessageRenderer("autoformat-steering", (message, { expanded }, theme) => { + const content = message.content.replace(/^\[pi-autoformat\]\s*/, ""); + const formatted = content.match(/^Formatted (\d+) file\(s\):\s*([^\n]+)/); + const failed = content.includes("Failures:"); + const count = formatted?.[1]; + const label = count === "1" ? "file" : "files"; + const status = failed + ? theme.fg("warning", "formatter failures") + : theme.fg("success", `formatted ''${count ?? "0"} ''${label}`); + const hint = expanded ? "" : theme.fg("dim", " · ctrl+o to expand"); + let text = theme.fg("accent", "● ") + theme.fg("text", theme.bold("Autoformat ")); + text += theme.fg("dim", "· ") + status + hint; + + if (expanded) { + const trailing = formatted ? content.slice(formatted[0].length).trim() : content; + const lines = [ + ...(formatted?.[2].split(/,\s*/) ?? []), + ...trailing.split("\n"), + ].filter(Boolean); + for (const [index, line] of lines.entries()) { + const connector = index === lines.length - 1 ? "└─ " : "├─ "; + text += `\n''${theme.fg("muted", connector)}''${theme.fg(failed ? "warning" : "dim", line)}`; + } + } + + return new Text(text, 0, 0); + }); + } + ''; + in { + programs.pi.coding-agent.extensions = [ + "npm:@gotgenes/pi-autoformat@5.1.6" + autoformatRenderer + ]; + + home.file.".pi/agent/extensions/pi-autoformat/config.json".source = jsonFormat.generate "pi-autoformat.json" { + formatterOutput.onFailure = "both"; + hideSummariesInTui = true; + + formatters = { + # keep-sorted start block=yes newline_separated=yes + alejandra.command = [(getExe pkgs.alejandra)]; + + fish_indent.command = [ + (getExe' pkgs.fish "fish_indent") + "--write" + ]; + + ktlint.command = [ + (getExe pkgs.ktlint) + "-F" + ]; + + oxfmt.command = [(getExe pkgs.oxfmt)]; + + oxlint.command = [ + (getExe pkgs.oxlint) + "--fix" + ]; + + ruff.command = [ + (getExe pkgs.ruff) + "format" + ]; + + rumdl.command = [ + (getExe pkgs.rumdl) + "fmt" + ]; + + rustfmt.command = [(getExe pkgs.rustfmt)]; + + selene.command = [ + (getExe pkgs.selene) + "--display-style" + "quiet" + ]; + + shfmt.command = [ + (getExe pkgs.shfmt) + "--write" + ]; + + stylua.command = [ + (getExe pkgs.stylua) + "--respect-ignores" + ]; + # keep-sorted end + }; + + chains = { + # keep-sorted start + ".bash" = ["shfmt"]; + ".cjs" = ["oxfmt" "oxlint"]; + ".css" = ["oxfmt"]; + ".fish" = ["fish_indent"]; + ".hbs" = ["oxfmt"]; + ".html" = ["oxfmt"]; + ".js" = ["oxfmt" "oxlint"]; + ".json" = ["oxfmt"]; + ".json5" = ["oxfmt"]; + ".jsonc" = ["oxfmt"]; + ".jsx" = ["oxfmt" "oxlint"]; + ".kt" = ["ktlint"]; + ".kts" = ["ktlint"]; + ".less" = ["oxfmt"]; + ".lua" = ["stylua" "selene"]; + ".markdown" = ["rumdl"]; + ".md" = ["rumdl"]; + ".mjs" = ["oxfmt" "oxlint"]; + ".nix" = ["alejandra"]; + ".py" = ["ruff"]; + ".pyi" = ["ruff"]; + ".rs" = ["rustfmt"]; + ".scss" = ["oxfmt"]; + ".sh" = ["shfmt"]; + ".toml" = ["oxfmt"]; + ".ts" = ["oxfmt" "oxlint"]; + ".tsx" = ["oxfmt" "oxlint"]; + ".yaml" = ["oxfmt"]; + ".yml" = ["oxfmt"]; + # keep-sorted end + }; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/header.nix b/modules/programs/tui/pi/extensions/header.nix new file mode 100644 index 0000000..7123a58 --- /dev/null +++ b/modules/programs/tui/pi/extensions/header.nix @@ -0,0 +1,18 @@ +{ + flake.modules.homeManager.pi = { + programs.pi.coding-agent = { + extensions = ["npm:pi-cc-header@0.8.5"]; + + settings = { + quietStartup = true; + rsl = true; + ccHeader = { + color = "y"; + grad = true; + lines = false; + ver = 1; + }; + }; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/lsp.nix b/modules/programs/tui/pi/extensions/lsp.nix new file mode 100644 index 0000000..30b35d3 --- /dev/null +++ b/modules/programs/tui/pi/extensions/lsp.nix @@ -0,0 +1,328 @@ +{ + flake.modules.homeManager.pi = { + # keep-sorted start + lib, + pkgs, + # keep-sorted end + ... + }: let + inherit + (lib) + # keep-sorted start + genAttrs + getExe + getExe' + # keep-sorted end + ; + jsonFormat = pkgs.formats.json {}; + mkServer = { + args ? [], + bin, + extensions, + id, + languageId, + languageIdByExtension ? genAttrs extensions (_: languageId), + rootMarkers ? [".git"], + }: { + inherit + args + bin + id + languageIdByExtension + rootMarkers + ; + enabled = true; + include = map (extension: "**/*${extension}") extensions; + cwd = "{root}"; + startupTimeoutMs = 45000; + diagnosticsWaitMs = 2000; + initializationOptions = {}; + settings = {}; + }; + in { + programs.pi.coding-agent.extensions = ["npm:pi-lsp@0.1.7"]; + + home.file.".pi/agent/lsp.json".source = jsonFormat.generate "pi-lsp.json" { + version = 1; + servers = [ + # keep-sorted start block=yes newline_separated=yes + (mkServer { + id = "bash"; + bin = getExe pkgs.bash-language-server; + args = ["start"]; + extensions = [ + # keep-sorted start + ".bash" + ".sh" + # keep-sorted end + ]; + languageId = "shellscript"; + }) + + (mkServer { + id = "csharp"; + bin = getExe pkgs.csharp-ls; + extensions = [ + # keep-sorted start + ".cs" + ".csx" + # keep-sorted end + ]; + languageId = "csharp"; + }) + + (mkServer { + id = "cssls"; + bin = getExe' pkgs.vscode-langservers-extracted "vscode-css-language-server"; + args = ["--stdio"]; + extensions = [".css"]; + languageId = "css"; + rootMarkers = [ + # keep-sorted start + ".git" + "package.json" + # keep-sorted end + ]; + }) + + (mkServer { + id = "jdtls"; + bin = getExe pkgs.jdt-language-server; + extensions = [".java"]; + languageId = "java"; + rootMarkers = [ + # keep-sorted start + ".git" + "build.gradle" + "build.gradle.kts" + "pom.xml" + "settings.gradle" + "settings.gradle.kts" + # keep-sorted end + ]; + }) + + (mkServer { + id = "json"; + bin = getExe pkgs.vscode-json-languageserver; + args = ["--stdio"]; + extensions = [ + # keep-sorted start + ".json" + ".jsonc" + # keep-sorted end + ]; + languageId = "json"; + languageIdByExtension = { + ".json" = "json"; + ".jsonc" = "jsonc"; + }; + rootMarkers = [ + # keep-sorted start + ".git" + "package.json" + # keep-sorted end + ]; + }) + + (mkServer { + id = "kotlin-ls"; + bin = getExe pkgs.kotlin-language-server; + extensions = [ + # keep-sorted start + ".kt" + ".kts" + # keep-sorted end + ]; + languageId = "kotlin"; + rootMarkers = [ + # keep-sorted start + ".git" + "build.gradle" + "build.gradle.kts" + "settings.gradle" + "settings.gradle.kts" + # keep-sorted end + ]; + }) + + (mkServer { + id = "lemminx"; + bin = getExe pkgs.lemminx; + extensions = [".xml"]; + languageId = "xml"; + }) + + (mkServer { + id = "lua"; + bin = getExe pkgs.lua-language-server; + extensions = [".lua"]; + languageId = "lua"; + rootMarkers = [ + # keep-sorted start + ".git" + ".luarc.json" + ".luarc.jsonc" + # keep-sorted end + ]; + }) + + (mkServer { + id = "nixd"; + bin = getExe pkgs.nixd; + extensions = [".nix"]; + languageId = "nix"; + rootMarkers = [ + # keep-sorted start + ".git" + "flake.nix" + # keep-sorted end + ]; + }) + + (mkServer { + id = "oxlint"; + bin = getExe pkgs.oxlint; + args = ["--lsp"]; + extensions = [ + # keep-sorted start + ".js" + ".jsx" + ".ts" + ".tsx" + # keep-sorted end + ]; + languageId = "javascript"; + languageIdByExtension = { + ".js" = "javascript"; + ".jsx" = "javascriptreact"; + ".ts" = "typescript"; + ".tsx" = "typescriptreact"; + }; + rootMarkers = [ + # keep-sorted start + ".git" + "oxlint.json" + "package.json" + # keep-sorted end + ]; + }) + + (mkServer { + id = "rumdl"; + bin = getExe pkgs.rumdl; + args = ["server"]; + extensions = [ + # keep-sorted start + ".markdown" + ".md" + # keep-sorted end + ]; + languageId = "markdown"; + rootMarkers = [ + # keep-sorted start + ".git" + ".rumdl.toml" + # keep-sorted end + ]; + }) + + (mkServer { + id = "rust"; + bin = getExe pkgs.rust-analyzer; + extensions = [".rs"]; + languageId = "rust"; + rootMarkers = [ + # keep-sorted start + ".git" + "Cargo.toml" + # keep-sorted end + ]; + }) + + (mkServer { + id = "superhtml"; + bin = getExe pkgs.superhtml; + args = ["lsp"]; + extensions = [".html"]; + languageId = "html"; + }) + + (mkServer { + id = "tombi"; + bin = getExe pkgs.tombi; + args = ["lsp"]; + extensions = [".toml"]; + languageId = "toml"; + rootMarkers = [ + # keep-sorted start + ".git" + "tombi.toml" + # keep-sorted end + ]; + }) + + (mkServer { + id = "ty"; + bin = getExe pkgs.ty; + args = ["server"]; + extensions = [ + # keep-sorted start + ".py" + ".pyi" + # keep-sorted end + ]; + languageId = "python"; + rootMarkers = [ + # keep-sorted start + ".git" + "pyproject.toml" + # keep-sorted end + ]; + }) + + (mkServer { + id = "typescript"; + bin = getExe pkgs.typescript-language-server; + args = ["--stdio"]; + extensions = [ + # keep-sorted start + ".js" + ".jsx" + ".ts" + ".tsx" + # keep-sorted end + ]; + languageId = "javascript"; + languageIdByExtension = { + ".js" = "javascript"; + ".jsx" = "javascriptreact"; + ".ts" = "typescript"; + ".tsx" = "typescriptreact"; + }; + rootMarkers = [ + # keep-sorted start + ".git" + "package.json" + "tsconfig.json" + # keep-sorted end + ]; + }) + + (mkServer { + id = "yaml-ls"; + bin = getExe pkgs.yaml-language-server; + args = ["--stdio"]; + extensions = [ + # keep-sorted start + ".yaml" + ".yml" + # keep-sorted end + ]; + languageId = "yaml"; + }) + # keep-sorted end + ]; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/mcp.nix b/modules/programs/tui/pi/extensions/mcp.nix new file mode 100644 index 0000000..4e2e0cf --- /dev/null +++ b/modules/programs/tui/pi/extensions/mcp.nix @@ -0,0 +1,119 @@ +{ + flake.modules.homeManager.pi = { + # keep-sorted start + config, + lib, + pkgs, + vars, + # keep-sorted end + ... + }: let + inherit + (lib) + # keep-sorted start + escapeShellArg + getExe + mkOption + types + # keep-sorted end + ; + inherit (pkgs) writeShellApplication; + + jsonFormat = pkgs.formats.json {}; + + # keep-sorted start block=yes newline_separated=yes + context7McpWrapper = writeShellApplication { + name = "context7-mcp-wrapper"; + runtimeInputs = [pkgs.coreutils]; + text = '' + CONTEXT7_API_KEY="$(cat "${config.sops.secrets."ai/context7_key".path}")" + export CONTEXT7_API_KEY + exec "${getExe pkgs.context7-mcp}" "$@" + ''; + }; + + githubMcpServerWrapper = writeShellApplication { + name = "github-mcp-server-wrapper"; + runtimeInputs = [pkgs.coreutils]; + text = '' + GITHUB_PERSONAL_ACCESS_TOKEN="$(cat "${config.sops.secrets."ai/github_token".path}")" + export GITHUB_PERSONAL_ACCESS_TOKEN + exec "${getExe pkgs.github-mcp-server}" stdio "$@" + ''; + }; + + tangledMcpWrapper = writeShellApplication { + name = "tangled-mcp-wrapper"; + runtimeInputs = [pkgs.coreutils]; + text = '' + TANGLED_HANDLE=${escapeShellArg vars.atprotoHandle} + TANGLED_PASSWORD="$(cat "${config.sops.secrets."atproto_app_password".path}")" + TANGLED_PDS_URL=${escapeShellArg "https://pds.${vars.groundDomain}"} + export TANGLED_HANDLE TANGLED_PASSWORD TANGLED_PDS_URL + exec "${getExe pkgs.nur.repos.adam0.tangled-mcp}" "$@" + ''; + }; + # keep-sorted end + in { + options.programs.pi.mcpServers = mkOption { + description = "MCP server packages added to the wrapped Pi launcher PATH."; + + type = types.attrsOf types.package; + default = {}; + }; + + config = { + sops.secrets = { + # keep-sorted start + "ai/context7_key" = {}; + "ai/github_token" = {}; + "atproto_app_password" = {}; + # keep-sorted end + }; + + programs.pi = { + coding-agent.extensions = ["npm:pi-mcp-adapter@2.11.0"]; + + mcpServers = { + inherit + # keep-sorted start + context7McpWrapper + githubMcpServerWrapper + tangledMcpWrapper + # keep-sorted end + ; + }; + }; + + home.file.".pi/agent/mcp.json".source = jsonFormat.generate "pi-mcp.json" { + settings = { + directTools = false; + idleTimeout = 1; + }; + + mcpServers = { + context7 = { + command = "context7-mcp-wrapper"; + directTools = false; + exposeResources = false; + lifecycle = "lazy"; + }; + + github = { + command = "github-mcp-server-wrapper"; + directTools = false; + exposeResources = false; + lifecycle = "lazy"; + }; + + tangled = { + command = "tangled-mcp-wrapper"; + directTools = false; + exposeResources = false; + lifecycle = "lazy"; + }; + }; + }; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/qol.nix b/modules/programs/tui/pi/extensions/qol.nix new file mode 100644 index 0000000..8a329e8 --- /dev/null +++ b/modules/programs/tui/pi/extensions/qol.nix @@ -0,0 +1,44 @@ +{ + flake.modules.homeManager.pi = {lib, ...}: let + inherit + (lib) + # keep-sorted start + concatMapAttrs + isAttrs + # keep-sorted end + ; + + flattenSettings = prefix: + concatMapAttrs (name: value: let + key = + if prefix == "" + then name + else "${prefix}.${name}"; + in + if isAttrs value + then flattenSettings key value + else {${key} = value;}); + in { + programs.pi.coding-agent = { + extensions = ["npm:@vanillagreen/pi-qol@1.7.3"]; + + settings.vstack.extensionManager.config."@vanillagreen/pi-qol" = flattenSettings "" { + # keep-sorted start + enableHandoffCommand = false; + enableScheduleCommand = false; + replaceFooter = false; + # keep-sorted end + + notification = { + # keep-sorted start + bell = false; + muteBellSound = true; + # keep-sorted end + }; + + sessionAutoRename.model = "openai-codex/gpt-5.6-luna"; + statusline.enabled = false; + }; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/question.nix b/modules/programs/tui/pi/extensions/question.nix new file mode 100644 index 0000000..858ae7f --- /dev/null +++ b/modules/programs/tui/pi/extensions/question.nix @@ -0,0 +1,5 @@ +{ + flake.modules.homeManager.pi = { + programs.pi.coding-agent.extensions = ["npm:pi-ask-user@0.13.0"]; + }; +} diff --git a/modules/programs/tui/pi/extensions/rewind.nix b/modules/programs/tui/pi/extensions/rewind.nix new file mode 100644 index 0000000..bc08dcf --- /dev/null +++ b/modules/programs/tui/pi/extensions/rewind.nix @@ -0,0 +1,30 @@ +{ + flake.modules.homeManager.pi = {pkgs, ...}: let + piRewind = pkgs.stdenvNoCC.mkDerivation { + pname = "pi-rewind"; + version = "0.5.0"; + + src = pkgs.fetchFromGitHub { + owner = "arpagon"; + repo = "pi-rewind"; + rev = "v0.5.0"; + hash = "sha256-fkMabHbwD5AuvOi4/f5GsbzOqrF5EbEwdCYBUWk06RQ="; + }; + + postPatch = '' + substituteInPlace src/core.ts \ + --replace-fail \ + $'export const IGNORED_DIR_NAMES = new Set([\n "node_modules",' \ + $'export const IGNORED_DIR_NAMES = new Set([\n ".direnv",\n ".git",\n ".rumdl_cache",\n "target",\n "vendor",\n "node_modules",' + ''; + + installPhase = '' + runHook preInstall + cp -r . "$out" + runHook postInstall + ''; + }; + in { + programs.pi.coding-agent.extensions = ["${piRewind}/src/index.ts"]; + }; +} diff --git a/modules/programs/tui/pi/extensions/rtk.nix b/modules/programs/tui/pi/extensions/rtk.nix new file mode 100644 index 0000000..5a4a0e3 --- /dev/null +++ b/modules/programs/tui/pi/extensions/rtk.nix @@ -0,0 +1,29 @@ +{ + flake.modules.homeManager.pi = {pkgs, ...}: let + tomlFormat = pkgs.formats.toml {}; + in { + programs.pi.coding-agent.extensions = ["${pkgs.rtk.src}/hooks/pi/rtk.ts"]; + + xdg.configFile."rtk/config.toml".source = tomlFormat.generate "rtk-config.toml" { + # keep-sorted start block=yes newline_separated=yes + display.colors = false; + + filters.ignore_dirs = [ + # keep-sorted start + ".direnv" + ".git" + ".rumdl_cache" + ".venv" + "__pycache__" + "dist" + "node_modules" + "target" + "vendor" + # keep-sorted end + ]; + + telemetry.enabled = false; + # keep-sorted end + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/speeed.nix b/modules/programs/tui/pi/extensions/speeed.nix new file mode 100644 index 0000000..d05f65e --- /dev/null +++ b/modules/programs/tui/pi/extensions/speeed.nix @@ -0,0 +1,8 @@ +{ + flake.modules.homeManager.pi = {lib, ...}: let + inherit (lib) mkOrder; + in { + # Load after other UI extensions so RunCat owns the working indicator. + programs.pi.coding-agent.extensions = mkOrder 1750 ["npm:pi-speeed@0.4.0"]; + }; +} diff --git a/modules/programs/tui/pi/extensions/subagents.nix b/modules/programs/tui/pi/extensions/subagents.nix new file mode 100644 index 0000000..93647c1 --- /dev/null +++ b/modules/programs/tui/pi/extensions/subagents.nix @@ -0,0 +1,14 @@ +{ + flake.modules.homeManager.pi = { + programs.pi.coding-agent.extensions = ["npm:@tintinweb/pi-subagents@0.14.1"]; + + home.file = { + # keep-sorted start + ".pi/agent/agents/Explore.md".source = ../agents/explore.md; + ".pi/agent/agents/Plan.md".source = ../agents/plan.md; + ".pi/agent/agents/coding.md".source = ../agents/coding.md; + ".pi/agent/agents/general-purpose.md".source = ../agents/general-purpose.md; + # keep-sorted end + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/tool-renderer.nix b/modules/programs/tui/pi/extensions/tool-renderer.nix new file mode 100644 index 0000000..82d9af9 --- /dev/null +++ b/modules/programs/tui/pi/extensions/tool-renderer.nix @@ -0,0 +1,56 @@ +{ + flake.modules.homeManager.pi = { + # keep-sorted start + lib, + pkgs, + # keep-sorted end + ... + }: let + inherit (lib) mkAfter; + + toolRenderer = pkgs.stdenvNoCC.mkDerivation { + pname = "pi-tool-renderer"; + version = "1.6.3"; + + src = pkgs.fetchurl { + url = "https://registry.npmjs.org/@vanillagreen/pi-tool-renderer/-/pi-tool-renderer-1.6.3.tgz"; + hash = "sha256-WzZGoBZ9bh0F/gm/kh+F6UH+r2eGVbW01JVIwqAcfJo="; + }; + + postPatch = '' + substituteInPlace extensions/tool-renderer.ts \ + --replace-fail \ + $'\tregisterReadOnly(pi, agent, cwd, "grep");\n\tregisterReadOnly(pi, agent, cwd, "find");' \ + $'\tif (settingBoolean("renderGrepFindTools", true, cwd)) {\n\t\tregisterReadOnly(pi, agent, cwd, "grep");\n\t\tregisterReadOnly(pi, agent, cwd, "find");\n\t}' + + substituteInPlace extensions/tool-renderer/generic.ts \ + --replace-fail \ + $'\t"get_search_content",\n\t"code_search",' \ + $'\t"get_search_content",\n\t"lsp_definition",\n\t"lsp_diagnostics",\n\t"lsp_hover",\n\t"lsp_references",\n\t"lsp_symbols",\n\t"code_search",' + ''; + + installPhase = '' + runHook preInstall + cp -r . "$out" + runHook postInstall + ''; + }; + in { + programs.pi.coding-agent = { + # Load after QOL so its tool renderers take precedence. + extensions = mkAfter ["${toolRenderer}/extensions/tool-renderer.ts"]; + + settings.vstack.extensionManager.config."@vanillagreen/pi-tool-renderer" = { + # keep-sorted start + compactUserMessages = true; + pendingStatusAnimation = true; + renderBashDiffs = true; + renderGitDiffCommandDiffs = true; + renderGrepFindTools = false; + renderMutationTools = true; + workingIndicator = "default"; + # keep-sorted end + }; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/web.nix b/modules/programs/tui/pi/extensions/web.nix new file mode 100644 index 0000000..8dfd9c7 --- /dev/null +++ b/modules/programs/tui/pi/extensions/web.nix @@ -0,0 +1,57 @@ +{ + flake.modules.homeManager.pi = {pkgs, ...}: let + jsonFormat = pkgs.formats.json {}; + + webAccess = pkgs.buildNpmPackage { + pname = "pi-web-access"; + version = "0.13.0"; + + src = pkgs.fetchurl { + url = "https://registry.npmjs.org/pi-web-access/-/pi-web-access-0.13.0.tgz"; + hash = "sha256-GmPsueJdqj4Ny+fxlwMWRVnehe4bv1GeiBo0i5uAQAA="; + }; + + npmDepsHash = "sha256-8onTvv7nUrTXMGvwkMkPEYc+mtpxolzF6Z9EuuB9pbs="; + npmInstallFlags = [ + "--legacy-peer-deps" + "--omit=dev" + ]; + + dontNpmBuild = true; + + postPatch = '' + cp ${./web/package-lock.json} package-lock.json + + substituteInPlace index.ts \ + --replace-fail \ + $'if (initConfig.webSearch?.enabled !== false) pi.registerTool({\n\t\tname: "web_search",' \ + $'if (initConfig.webSearch?.enabled !== false) pi.registerTool({\n\t\trenderShell: "self",\n\t\tname: "web_search",' \ + --replace-fail \ + $'\tpi.registerTool({\n\t\tname: "fetch_content",' \ + $'\tpi.registerTool({\n\t\trenderShell: "self",\n\t\tname: "fetch_content",' \ + --replace-fail \ + $'\tpi.registerTool({\n\t\tname: "get_search_content",' \ + $'\tpi.registerTool({\n\t\trenderShell: "self",\n\t\tname: "get_search_content",' \ + --replace-fail \ + $'export default function (pi: ExtensionAPI) {\n\tconst initConfig' \ + $'export default function (pi: ExtensionAPI) {\n\tconst renderStatus = (label: string, content: unknown, theme: any, color: "error" | "success") => {\n\t\tconst text = typeof content === "string" ? content : "";\n\t\treturn new Text(theme.fg(color, "● ") + theme.fg("text", theme.bold(label + " ")) + theme.fg("muted", text), 0, 0);\n\t};\n\tpi.registerMessageRenderer("web-search-content-ready", (message, _options, theme) =>\n\t\trenderStatus("Web Search", message.content, theme, "success"));\n\tpi.registerMessageRenderer("web-search-error", (message, _options, theme) =>\n\t\trenderStatus("Web Search", message.content, theme, "error"));\n\n\tconst initConfig' + ''; + + installPhase = '' + runHook preInstall + cp -r . "$out" + runHook postInstall + ''; + }; + in { + programs.pi.coding-agent.extensions = ["${webAccess}/index.ts"]; + + home.file.".pi/web-search.json".source = jsonFormat.generate "web-search.json" { + summaryModel = "openai-codex/gpt-5.6-luna"; + + githubClone.enabled = true; + video.enabled = false; + youtube.enabled = false; + }; + }; +} diff --git a/modules/programs/tui/pi/extensions/web/package-lock.json b/modules/programs/tui/pi/extensions/web/package-lock.json new file mode 100644 index 0000000..8f8c552 --- /dev/null +++ b/modules/programs/tui/pi/extensions/web/package-lock.json @@ -0,0 +1,258 @@ +{ + "name": "pi-web-access", + "version": "0.13.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-web-access", + "version": "0.13.0", + "license": "MIT", + "dependencies": { + "@mozilla/readability": "^0.6.0", + "linkedom": "^0.16.0", + "p-limit": "^6.1.0", + "turndown": "^7.2.0", + "unpdf": "^1.6.2" + }, + "peerDependencies": { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + "typebox": "*" + } + }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mozilla/readability": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz", + "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/linkedom": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.16.11.tgz", + "integrity": "sha512-WgaTVbj7itjyXTsCvgerpneERXShcnNJF5VIV+/4SLtyRLN+HppPre/WDHRofAr2IpEuujSNgJbCBd5lMl6lRw==", + "license": "ISC", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^9.1.0", + "uhyphen": "^0.2.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/turndown": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.4.tgz", + "integrity": "sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==", + "license": "MIT", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + }, + "engines": { + "node": ">=18", + "npm": ">=9" + } + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + }, + "node_modules/unpdf": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/unpdf/-/unpdf-1.6.2.tgz", + "integrity": "sha512-zQ80ySoPuPHOsvIoRp/nJyQt8TOUoTh1+WBCGcBvlddQNgKDLRwm0AY3x8Q35I7+kIiRSgqMx+Ma2pl9McIp7A==", + "license": "MIT", + "peerDependencies": { + "@napi-rs/canvas": "^0.1.69" + }, + "peerDependenciesMeta": { + "@napi-rs/canvas": { + "optional": true + } + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/modules/programs/tui/opencode/instructions.md b/modules/programs/tui/pi/instructions.md similarity index 71% rename from modules/programs/tui/opencode/instructions.md rename to modules/programs/tui/pi/instructions.md index 03ed4c4..ff5b9d7 100644 --- a/modules/programs/tui/opencode/instructions.md +++ b/modules/programs/tui/pi/instructions.md @@ -22,10 +22,13 @@ - Do not mirror the user's tone unless explicitly required. - Stay strictly aligned with the requested task. -### Writing artifacts +## NixOS command availability -- Match the tone and format to the requested artifact type - instead of enforcing assistant voice. +- Do not assume development tools are installed globally. +- When a command is unavailable, use `, command args...` to run it through + comma. +- If comma cannot resolve it, use `nix run nixpkgs#package -- args...`. +- Do not permanently install packages unless explicitly requested. ## Tools @@ -43,9 +46,8 @@ optional enhancement. additional skill if the task expands. - Do not skip a relevant skill just because the task looks small; still load it when it changes the workflow, tool choice, or quality bar. - -Prefer a false positive over missing a useful skill. Only skip loading a skill -when you are confident none of the available skills materially help. +- Always load the `jj-vcs` skill when starting a programming task. Do not rely + on general knowledge for jj workflows or commit message policy. ### Always use the TODO tool @@ -61,15 +63,8 @@ When you are only explaining, skip the TODO tool. When you need to ask the user a question, use the question tool and skip the TODO tool for that interaction. -### Use shell fallbacks when needed - -If a required tool is unavailable, use shell helpers such as `nix run nixpkgs#` or -`comma ` to access the missing command. - -### Prefer shell for file and git operations +### Ignore keep-sorted blocks -- Use bash for simple filesystem operations such as renaming, moving, copying, - creating, or deleting files and directories. -- Use git commands through bash for repository workflows such as porting changes - to forks, cherry-picking, applying patches, merging, rebasing, and inspecting - branches, remotes, history, or diffs. +In repositories that use `keep-sorted`, preserve its start and end control +comments, but do not manually sort, reorder, or review the ordering of content +inside those blocks. The `keep-sorted` tool manages their ordering automatically. diff --git a/modules/programs/tui/pi/settings.nix b/modules/programs/tui/pi/settings.nix new file mode 100644 index 0000000..966b09a --- /dev/null +++ b/modules/programs/tui/pi/settings.nix @@ -0,0 +1,7 @@ +{ + flake.modules.homeManager.pi = { + programs.pi.coding-agent.settings = { + enableInstallTelemetry = false; + }; + }; +} diff --git a/modules/programs/tui/opencode/skills/create-readme.md b/modules/programs/tui/pi/skills/create-readme/SKILL.md similarity index 100% rename from modules/programs/tui/opencode/skills/create-readme.md rename to modules/programs/tui/pi/skills/create-readme/SKILL.md diff --git a/modules/programs/tui/pi/skills/default.nix b/modules/programs/tui/pi/skills/default.nix new file mode 100644 index 0000000..8fc67dd --- /dev/null +++ b/modules/programs/tui/pi/skills/default.nix @@ -0,0 +1,33 @@ +{ + flake.modules.homeManager.pi = {lib, ...}: let + inherit + (builtins) + # keep-sorted start + pathExists + readDir + # keep-sorted end + ; + inherit + (lib) + # keep-sorted start + filterAttrs + mapAttrs' + nameValuePair + # keep-sorted end + ; + + skills = filterAttrs ( + name: type: type == "directory" && pathExists (./. + "/${name}/SKILL.md") + ) (readDir ./.); + in { + home.file = + mapAttrs' ( + name: _: + nameValuePair ".agents/skills/${name}" { + source = ./. + "/${name}"; + recursive = true; + } + ) + skills; + }; +} diff --git a/modules/programs/tui/opencode/skills/find-skills.md b/modules/programs/tui/pi/skills/find-skills/SKILL.md similarity index 100% rename from modules/programs/tui/opencode/skills/find-skills.md rename to modules/programs/tui/pi/skills/find-skills/SKILL.md diff --git a/modules/programs/tui/pi/skills/frontend/SKILL.md b/modules/programs/tui/pi/skills/frontend/SKILL.md new file mode 100644 index 0000000..1127a9d --- /dev/null +++ b/modules/programs/tui/pi/skills/frontend/SKILL.md @@ -0,0 +1,184 @@ +--- +name: frontend-skill +description: Use when the task asks for a visually strong landing page, website, app, prototype, demo, or game UI. This skill enforces restrained composition, image-led hierarchy, cohesive content structure, and tasteful motion while avoiding generic cards, weak branding, and UI clutter. +--- + +# Frontend skill + +Use this skill when the quality of the work depends on art direction, hierarchy, restraint, imagery, and motion rather than component count. + +Goal: ship interfaces that feel deliberate, premium, and current. Default toward award-level composition: one big idea, strong imagery, sparse copy, rigorous spacing, and a small number of memorable motions. + +## Working Model + +Before building, write three things: + +- visual thesis: one sentence describing mood, material, and energy +- content plan: hero, support, detail, final CTA +- interaction thesis: 2-3 motion ideas that change the feel of the page + +Each section gets one job, one dominant visual idea, and one primary takeaway or action. + +## Beautiful Defaults + +- Start with composition, not components. +- Prefer a full-bleed hero or full-canvas visual anchor. +- Make the brand or product name the loudest text. +- Keep copy short enough to scan in seconds. +- Use whitespace, alignment, scale, cropping, and contrast before adding chrome. +- Limit the system: two typefaces max, one accent color by default. +- Default to cardless layouts. Use sections, columns, dividers, lists, and media blocks instead. +- Treat the first viewport as a poster, not a document. + +## Landing Pages + +Default sequence: + +1. Hero: brand or product, promise, CTA, and one dominant visual +2. Support: one concrete feature, offer, or proof point +3. Detail: atmosphere, workflow, product depth, or story +4. Final CTA: convert, start, visit, or contact + +Hero rules: + +- One composition only. +- Full-bleed image or dominant visual plane. +- Canonical full-bleed rule: on branded landing pages, the hero itself must run edge-to-edge with no inherited page gutters, framed container, or shared max-width; constrain only the inner text/action column. +- Brand first, headline second, body third, CTA fourth. +- No hero cards, stat strips, logo clouds, pill soup, or floating dashboards by default. +- Keep headlines to roughly 2-3 lines on desktop and readable in one glance on mobile. +- Keep the text column narrow and anchored to a calm area of the image. +- All text over imagery must maintain strong contrast and clear tap targets. + +If the first viewport still works after removing the image, the image is too weak. If the brand disappears after hiding the nav, the hierarchy is too weak. + +Viewport budget: + +- If the first screen includes a sticky/fixed header, that header counts against the hero. The combined header + hero content must fit within the initial viewport at common desktop and mobile sizes. +- When using `100vh`/`100svh` heroes, subtract persistent UI chrome (`calc(100svh - header-height)`) or overlay the header instead of stacking it in normal flow. + +## Apps + +Default to Linear-style restraint: + +- calm surface hierarchy +- strong typography and spacing +- few colors +- dense but readable information +- minimal chrome +- cards only when the card is the interaction + +For app UI, organize around: + +- primary workspace +- navigation +- secondary context or inspector +- one clear accent for action or state + +Avoid: + +- dashboard-card mosaics +- thick borders on every region +- decorative gradients behind routine product UI +- multiple competing accent colors +- ornamental icons that do not improve scanning + +If a panel can become plain layout without losing meaning, remove the card treatment. + +## Imagery + +Imagery must do narrative work. + +- Use at least one strong, real-looking image for brands, venues, editorial pages, and lifestyle products. +- Prefer in-situ photography over abstract gradients or fake 3D objects. +- Choose or crop images with a stable tonal area for text. +- Do not use images with embedded signage, logos, or typographic clutter fighting the UI. +- Do not generate images with built-in UI frames, splits, cards, or panels. +- If multiple moments are needed, use multiple images, not one collage. + +The first viewport needs a real visual anchor. Decorative texture is not enough. + +## Copy + +- Write in product language, not design commentary. +- Let the headline carry the meaning. +- Supporting copy should usually be one short sentence. +- Cut repetition between sections. +- Do not include prompt language or design commentary into the UI. +- Give every section one responsibility: explain, prove, deepen, or convert. + +If deleting 30 percent of the copy improves the page, keep deleting. + +## Utility Copy For Product UI + +When the work is a dashboard, app surface, admin tool, or operational workspace, default to utility copy over marketing copy. + +- Prioritize orientation, status, and action over promise, mood, or brand voice. +- Start with the working surface itself: KPIs, charts, filters, tables, status, or task context. Do not introduce a hero section unless the user explicitly asks for one. +- Section headings should say what the area is or what the user can do there. +- Good: "Selected KPIs", "Plan status", "Search metrics", "Top segments", "Last sync". +- Avoid aspirational hero lines, metaphors, campaign-style language, and executive-summary banners on product surfaces unless specifically requested. +- Supporting text should explain scope, behavior, freshness, or decision value in one sentence. +- If a sentence could appear in a homepage hero or ad, rewrite it until it sounds like product UI. +- If a section does not help someone operate, monitor, or decide, remove it. +- Litmus check: if an operator scans only headings, labels, and numbers, can they understand the page immediately? + +## Motion + +Use motion to create presence and hierarchy, not noise. + +Ship at least 2-3 intentional motions for visually led work: + +- one entrance sequence in the hero +- one scroll-linked, sticky, or depth effect +- one hover, reveal, or layout transition that sharpens affordance + +Prefer Framer Motion when available for: + +- section reveals +- shared layout transitions +- scroll-linked opacity, translate, or scale shifts +- sticky storytelling +- carousels that advance narrative, not just fill space +- menus, drawers, and modal presence effects + +Motion rules: + +- noticeable in a quick recording +- smooth on mobile +- fast and restrained +- consistent across the page +- removed if ornamental only + +## Hard Rules + +- No cards by default. +- No hero cards by default. +- No boxed or center-column hero when the brief calls for full bleed. +- No more than one dominant idea per section. +- No section should need many tiny UI devices to explain itself. +- No headline should overpower the brand on branded pages. +- No filler copy. +- No split-screen hero unless text sits on a calm, unified side. +- No more than two typefaces without a clear reason. +- No more than one accent color unless the product already has a strong system. + +## Reject These Failures + +- Generic SaaS card grid as the first impression +- Beautiful image with weak brand presence +- Strong headline with no clear action +- Busy imagery behind text +- Sections that repeat the same mood statement +- Carousel with no narrative purpose +- App UI made of stacked cards instead of layout + +## Litmus Checks + +- Is the brand or product unmistakable in the first screen? +- Is there one strong visual anchor? +- Can the page be understood by scanning headlines only? +- Does each section have one job? +- Are cards actually necessary? +- Does motion improve hierarchy or atmosphere? +- Would the design still feel premium if all decorative shadows were removed? diff --git a/modules/programs/tui/pi/skills/jujutsu/SKILL.md b/modules/programs/tui/pi/skills/jujutsu/SKILL.md new file mode 100644 index 0000000..3bc8e20 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/SKILL.md @@ -0,0 +1,332 @@ +--- +name: jujutsu +description: Use this skill for any version control operations (commit, log, diff, push, fetch, bookmark, workspace, rebase, undo, etc.). If a `.jj` directory exists, the repo is jujutsu and git mutations will corrupt it — use `jj` for all mutations; read-only `git log/diff/show/blame/grep` are allowed. Covers co-located repos, multi-agent parallel workspaces, and operation-log recovery. **DO NOT IGNORE** +allowed-tools: Bash(jj *) +license: Apache-2.0 +metadata: + author: johnstegeman + version: "1.3" +--- + +# Jujutsu (jj) Version Control System + +This skill helps you work with Jujutsu, a Git-compatible VCS with mutable commits and automatic rebasing. + +**Tested with jj v0.41.0** - Commands may differ in other versions. + +For token-sensitive inspection commands, prefer the compact commands in `references/TEMPLATES.md` over jj's default output. + +The compact commands in `references/TEMPLATES.md` intentionally use `jj --no-pager ...`. + +For revsets, filesets, and template syntax, see `references/QUERY_LANGUAGES.md`. + +For sibling or unrelated work that should start from `trunk()`, see `references/NONLINEAR.md`. + +For common surprises and easy mistakes, see `references/GOTCHAS.md`. + + + +## Detecting a jj repo + +Check whether a directory is part of a jj repo: + +```bash +jj root +``` + +If it returns a path, the repo is jj-managed. If it returns `Error: There is no jj repo in `, it isn't. The presence of a `.jj/` folder is another giveaway. If the repo is not a jj one, do not further use this skill. + + +## Critical: never use git for mutations in a jj repo + +In a jj repo (including co-located repos that also have `.git/`): + +- **Forbidden** (corrupts jj state): `git commit`, `git add`, `git stash`, `git reset`, `git checkout `, `git switch`, `git rebase`, `git merge`, `git cherry-pick`, `git push`, `git pull`. +- **Allowed** (read-only): `git log`, `git show`, `git diff`, `git blame`, `git grep`, `git status`. Prefer the `jj` equivalents (`jj log`, `jj show`, `jj diff`, etc.) but read-only git commands won't break anything. +- **Use jj instead**: `jj git push`, `jj git fetch`, `jj edit ` (not `git checkout`), `jj rebase`, `jj new ` (for merges). + +See `references/COLOCATED.md` for the full co-located repo workflow. + +## Important: Automated/Agent Environment + +When running as an agent or running on behalf of the user: + +1. **Always use `-m` flags** to provide commit messages inline rather than relying on editor prompts: + +```bash +# Always use -m to avoid editor prompts +jj desc -m "message" # NOT: jj desc +jj squash --into @- -m "message" # NOT: jj squash (which opens editor or prompts for hunks) +``` + +Editor-based commands will fail in non-interactive environments. + +2. **Verify operations with `jj st`** after mutations (`squash`, `abandon`, `rebase`, `restore`) to confirm the operation succeeded. + +## Important Principles + +1. Each commit should represent one logical change. See [commit messages](references/COMMIT_MSG.md) for details on good commit messages. Before starting work on a new change, make sure the working copy is clean (no changes) + +```bash +jj st +``` + +## Core Concepts + +### The Working Copy is a Commit + +In jj, your working directory is always a commit (referenced as `@`). Changes are automatically snapshotted when you run any jj command. There is no staging area. + +There is no need to run `jj commit`. + +### Commits Are Mutable + +**CRITICAL**: Unlike git, jj commits can be freely modified. This enables a high-quality commit workflow: + +1. Before starting work, run `jj st`. If `@` already has changes, run `jj new` first. If `@` is empty, use it as-is. +2. Describe your intended changes with `jj desc -m "Message"` +3. Make your changes. +4. Do NOT run `jj new` when finished — leave that to the next task's step 1. + +You may refine the commit using `jj squash` or `jj absorb` as needed + +### Change IDs vs Commit IDs + +- **Change ID**: A stable identifier (like `tqpwlqmp`) that persists when a commit is rewritten +- **Commit ID**: A content hash (like `3ccf7581`) that changes when commit content changes + +Prefer using Change IDs when referencing commits in commands. + +## Essential Workflow + +### Starting Work: Describe First, Then Code + +**Always create your commit message before writing code:** + +```bash +# First, describe what you intend to do +jj desc -m "feat: Add user authentication to login endpoint" + +# Then make your changes - they automatically become part of this commit +# ... edit files ... + +# Check status +jj st +``` + +For the full starting-a-change workflow (when to reuse `@` vs `jj new`, anti-patterns, decision logic), see `references/NEW_CHANGE.md`. + +For unrelated or sibling work that should branch from `trunk()`, see `references/NONLINEAR.md`. + +### Creating Atomic Commits + +Each commit should represent ONE logical change. + +### Viewing History + +```bash +# View recent commits +jj log + +# View with patches +jj log -p + +# View specific commit +jj show + +# View diff of working copy +jj diff +``` + +If you need to run `jj log`, `jj show`, or `jj diff` as an agent, use the matching compact command from `references/TEMPLATES.md` unless the full default output is explicitly needed. + +For the revsets, filesets, and template syntax behind those compact commands, see `references/QUERY_LANGUAGES.md`. + +### Moving Between Commits + +```bash +# Create a new empty commit on top of current +jj new + +# Create new commit with message +jj new && jj desc -m "Commit message" + +# Edit an existing commit (working copy becomes that commit) +jj edit + +# Edit the previous commit +jj prev -e + +# Edit the next commit +jj next -e +``` + +## Refining Commits + +If you need to refine a commit (squash, abandon, undo, split, etc), see [Refining commits](references/REFINE_COMMIT.md) + +## Working with Bookmarks (Branches) + +Bookmarks are jj's equivalent to git branches: + +```bash +# Create a bookmark at current commit +jj bookmark create my-feature -r@ + +# Move bookmark to a different commit +jj bookmark move my-feature --to + +# List bookmarks +jj bookmark list + +# Delete a bookmark +jj bookmark delete my-feature +``` + +If you need to run `jj bookmark list`, use the matching compact command from `references/TEMPLATES.md`. + + +## Parallel Workspaces + +For running multiple agents in parallel against the same repo without working-copy collisions, use `jj workspace`. Each workspace gets its own `@` while sharing the underlying repo store. + +```bash +# Quick reference (full convention in references/WORKSPACES.md) +jj workspace add .workspaces/- # Create +jj workspace list # List +jj workspace forget # Untrack (files stay on disk) +``` + +If you need to run `jj workspace list`, use the matching compact command from `references/TEMPLATES.md`. + +See `references/WORKSPACES.md` for the full convention: the `.workspaces/` directory layout, `.gitignore` safety check, naming, environment bootstrapping, agent handoff, monitoring, stale-workspace recovery, and merging results from multiple workspaces back together. + +## Working with tags + +jujutsu does not yet support tags and pushing them to a remote. If you need to tag (such as for a release), you will need to use git to create and push the tags. + +## Git Integration (Co-located Repos) + +Always use co-located repos when working with existing git projects. They give you jj locally while keeping the repo remote-compatible with git tooling. + +```bash +jj git clone --colocate # Clone a git repo +jj git init --colocate # Adopt an existing git repo +``` + +See `references/COLOCATED.md` for the complete workflow: the mutation rule (`git` is forbidden for mutations, allowed read-only), the git-command allowlist, common gotchas, and switching modes when absolutely necessary. + +See `references/GOTCHAS.md` when the behavior feels surprising and you want the short list of jj-specific footguns before trying a git-style workaround. + +### Pushing Changes + +For pushing bookmarks to a remote, see `references/PUSH.md`. For named remotes, fork+upstream setups, and other multi-remote workflows, see `references/REMOTES.md`. + +## Handling Conflicts + +jj treats conflicts as first-class objects — a commit can contain a conflict, and that conflict persists through rebases until resolved. `jj` refuses to push conflicted commits, so they must be resolved before `jj git push`. + +```bash +jj st # Reports if @ has unresolved conflicts +jj resolve --list # Lists conflicted paths +jj resolve --tool :ours # Take side #1 non-interactively (agent-safe) +jj resolve --tool :theirs # Take side #2 non-interactively (agent-safe) +``` + +**Never run bare `jj resolve` (no `--tool`)** — it's interactive and will hang in non-interactive agent environments. Either pass `--tool :ours` / `--tool :theirs`, or edit conflict markers directly in the file and let `jj st` snapshot the resolution. + +See `references/CONFLICTS.md` for the full resolution playbook: marker formats (diff vs snapshot), five resolution paths, ancestor-level resolution to avoid re-resolving across descendants, verification (beyond `jj st`), and anti-patterns. + + +## Recovery and the Operation Log + +The op log records every mutation. Nothing is ever lost. + +```bash +jj undo # Reverse the last operation (primary recovery tool) +jj op log # See all operations +jj op restore # Jump back to any past state +jj evolog -r # See how a specific change evolved +``` + +If you need to run `jj op log` or `jj evolog`, use the matching compact commands from `references/TEMPLATES.md` first. Add `-p` only after you have narrowed the scope. + +**Always reach for `jj undo` first** when something looks wrong — it's always correct and safe. See `references/RECOVERY.md` for recovery patterns by scenario (bad squash, accidental abandon, lost work, etc.). + +## Preserving Commit Quality + +**IMPORTANT**: Because commits are mutable, always refine them: + +1. **Review your commit**: `jj show @` or `jj diff` +2. **Is it atomic?** One logical change per commit +3. **Is the message clear?** Use imperative verb phrase in sentence case format with no full stop: "Verb object" +4. **Are there unrelated changes?** Use `jj restore` to move changes out, then create separate commits +5. **Should changes be elsewhere?** Use `jj squash` or `jj absorb` + +## Quick Reference + +| Action | Command | +|--------|---------| +| Describe commit | `jj desc -m "message"` | +| View status | `jj st` | +| View log | `jj log` | +| View diff | `jj diff` | +| New commit | `jj st` then `jj new` only if `@` has changes, then `jj desc -m "message"` | +| Edit commit | `jj edit ` | +| Squash to parent | `jj squash --into @- -m "message"` | +| Auto-distribute | `jj absorb` | +| Abandon commit | `jj abandon ` | +| Undo last operation | `jj undo` | +| Restore files | `jj restore [paths]` | +| Create bookmark | `jj bookmark create ` | +| Push bookmark | `jj git push -b ` | +| Add workspace | `jj workspace add .workspaces/-` | +| List workspaces | `jj workspace list` | +| Forget workspace | `jj workspace forget ` | +| See operation log | `jj op log` | +| Jump to past state | `jj op restore ` | +| See change evolution | `jj evolog -r ` | + +## Best Practices Summary + +1. **Describe first**: Set the commit message before coding +2. **One change per commit**: Keep commits atomic and focused +3. **Use change IDs**: They're stable across rewrites +4. **Refine commits**: Leverage mutability for clean history +5. **Embrace the workflow**: No staging area, no stashing — the op log replaces both +6. **Workspaces for parallel agents**: Isolate concurrent agent work in `.workspaces/-` (see `references/WORKSPACES.md`) +7. **`jj undo` first**: When something looks wrong, undo before fixing manually (see `references/RECOVERY.md`) diff --git a/modules/programs/tui/pi/skills/jujutsu/agents/openai.yaml b/modules/programs/tui/pi/skills/jujutsu/agents/openai.yaml new file mode 100644 index 0000000..cfb6f5c --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/agents/openai.yaml @@ -0,0 +1,7 @@ +--- +interface: + display_name: "Jujutsu" + short_description: "Help with Jujutsu version control workflows" + default_prompt: >- + Use $jujutsu to inspect this repo, choose the right jj workflow, and carry + the change through safely. diff --git a/modules/programs/tui/pi/skills/jujutsu/references/COLOCATED.md b/modules/programs/tui/pi/skills/jujutsu/references/COLOCATED.md new file mode 100644 index 0000000..3e8b0cb --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/COLOCATED.md @@ -0,0 +1,81 @@ +# Co-located jj + git Repositories + +A **co-located** repo has both `.jj/` and `.git/` at the same root. jj manages local operations; git stays in sync underneath so remote-side tooling (CI, GitHub, hooks, IDE plugins, `gh` CLI) continues to work normally. + +This is the **strongly recommended** setup for working with an existing git project. + +## Why co-located + +- **Local: jj's superior model.** Mutable commits, no staging area, the op log as a safety net. +- **Remote: still git.** GitHub sees ordinary commits and refs. Collaborators don't need to know you use jj. +- **Tooling parity.** IDE git integrations, pre-commit hooks, `gh`/`git` CLI for read-only inspection all continue to work. + +## Creating a co-located repo + +```bash +# From scratch +jj git init --colocate + +# From a remote +jj git clone --colocate + +# Adopt an existing git repo (run from inside it) +cd existing-git-repo +jj git init --colocate +``` + +After this, both `.jj/` and `.git/` exist and stay synchronized automatically. **Never run `jj git init` without `--colocate`** on a project you already have git history for — non-colocated init creates a separate git store inside `.jj/repo/store/git` which is harder to work with. + +## The mutation rule + +> **Never use `git` for mutations in a co-located jj repo.** Read-only `git` is fine. + +| git command | Allowed in jj repo? | +|---|---| +| `git log`, `git show`, `git diff`, `git blame`, `git grep` | ✅ Allowed (jj equivalents preferred) | +| `git status` | ✅ Allowed (shows git's view; may differ from `jj st`) | +| `git commit`, `git add`, `git stash`, `git reset` | ❌ Forbidden — corrupts jj state | +| `git checkout `, `git switch` | ❌ Forbidden — use `jj edit` instead | +| `git rebase`, `git merge`, `git cherry-pick` | ❌ Forbidden — use `jj rebase`, `jj new `, `jj duplicate` | +| `git fetch` | ⚠️ Use `jj git fetch` — it keeps jj in sync | +| `git pull` | ❌ Forbidden — `git pull` does fetch+merge; use `jj git fetch` + `jj rebase` | +| `git push` | ❌ Forbidden — use `jj git push -b ` | + +The reason: jj snapshots the working copy on every jj command. If git mutates state behind jj's back, jj's next snapshot will be confused about what changed when, and the op log can get out of sync. + +## Switching to git mode (rare; usually a smell) + +If you genuinely need to use a git workflow for one operation (e.g., an external tool insists), do it carefully: + +```bash +# 1. Ensure jj working copy is committed and clean +jj st +# (resolve anything pending — describe, refine, etc.) + +# 2. Now run the git operation +git + +# 3. Return to jj — jj will re-sync on the next command +jj st # Triggers re-sync +jj edit # Resume work +``` + +**Almost every git operation has a jj equivalent that's safer.** Reach for git mode only as a last resort. + +## Common gotchas + +- **Git complains about uncommitted changes**: jj's working copy is always a commit; git sees that as "uncommitted." Use `jj st` (not `git status`) to assess state. +- **HEAD detachment**: jj manages `HEAD` per change. `git checkout` will detach HEAD in confusing ways — use `jj edit` instead. +- **`.gitignore` is respected by jj** automatically. `.jjignore` is only needed if you want jj-only ignore patterns the outer git doesn't see. +- **Branches vs bookmarks**: git branches become jj bookmarks on fetch. Bookmarks don't auto-advance like git branches — you must `jj bookmark move` before pushing. + +## Pushing from a co-located repo + +Use `jj git push`, not `git push`: + +```bash +jj bookmark move main --to @ # Move bookmark to your latest change +jj git push -b main # Push to remote +``` + +See `references/PUSH.md` for the full push workflow. diff --git a/modules/programs/tui/pi/skills/jujutsu/references/COMMIT_MSG.md b/modules/programs/tui/pi/skills/jujutsu/references/COMMIT_MSG.md new file mode 100644 index 0000000..829c84c --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/COMMIT_MSG.md @@ -0,0 +1,26 @@ +# Creating good commit messages + +1. Use the following format: + +```text +: + + +``` + +The valid values are: fix, feat, chore, docs +Type and subject are required +Body is optional + +2. If there is a body, separate it from the subject with a blank line +3. Limit the subject line to 50 characters. This is not a hard limit but a rule of thumb. The subject line should explain the "what" of the logical change +4. Capitalize the subject line +5. Do not end the subject line with a period +6. Use the imperative mood in the subject line + Imperative mood just means “spoken or written as if giving a command or instruction”. A few examples: + + * Clean your room + * Close the door + * Take out the trash +7. Wrap the body at 72 characters +8. Use the body to explain what and why vs. how diff --git a/modules/programs/tui/pi/skills/jujutsu/references/CONFLICTS.md b/modules/programs/tui/pi/skills/jujutsu/references/CONFLICTS.md new file mode 100644 index 0000000..326e0e3 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/CONFLICTS.md @@ -0,0 +1,176 @@ +# Resolving Conflicts + +jj treats conflicts as **first-class objects**: a commit can contain a conflict, and that conflict **persists through rebases** until someone resolves it. This is the headline mental-model difference from git, where a working tree must be conflict-free before committing. + +For an agent, this changes the rhythm: + +- A `jj rebase` or `jj squash` that produces a conflict **does not fail**. It succeeds; the resulting commit is just marked conflicted. +- You can keep working — even keep rebasing the conflicted commit further — and resolve it later. +- `jj` won't let you push a conflicted commit to a remote, so conflicts must be resolved before `jj git push`. + +## Detecting conflicts + +```bash +jj st # Shows "There are unresolved conflicts" if any +jj resolve --list # Lists all conflicted paths in @ +jj resolve --list -r # Lists conflicts in a specific change +jj log # Conflicted changes are shown with a "conflict" marker +``` + +## Conflict marker formats + +jj supports three marker styles (set via `ui.conflict-marker-style`): + +- **`diff`** (modern default) — shows base→side as a diff +- **`snapshot`** — full content of each side, like git's `<<<<<<<` style +- **`git`** — legacy git-compatible 3-way + +Check your style: + +```bash +jj config get ui.conflict-marker-style +``` + +### Example: `diff` style + +```text +<<<<<<< Conflict 1 of 1 +%%%%%%% Changes from base to side #1 +-base content here ++side 1 content here ++++++++ Contents of side #2 +side 2 content here +>>>>>>> Conflict 1 of 1 ends +``` + +- Side #1 = "ours" (the destination — typically your current branch/change) +- Side #2 = "theirs" (the source — typically what's being rebased/merged in) +- The `%%%%%%%` block shows what side #1 changed *from the common base*; side #2's content is shown directly. + +### Example: `snapshot` style + +```text +<<<<<<< Conflict 1 of 1 ++++++++ Contents of side #1 +side 1 content here +%%%%%%% Contents of base +base content here ++++++++ Contents of side #2 +side 2 content here +>>>>>>> Conflict 1 of 1 ends +``` + +Both styles delimit conflict regions identically. To resolve: replace the entire `<<<<<<< ... >>>>>>>` block with the desired final content. + +## Five resolution paths (agent-friendly) + +`jj resolve` with no `--tool` launches an interactive merge tool and **will hang in non-interactive agent environments**. Use one of these instead: + +### 1. Edit conflict markers directly (most common) + +Read the file, replace each `<<<<<<<` block with the resolved content, save. Then snapshot: + +```bash +jj st # Triggers snapshot; should report no remaining conflicts +``` + +This is the canonical agent path. Works for any merge complexity. + +### 2. Take one side wholesale: `:ours` / `:theirs` + +For conflicts where one side simply wins (e.g., a rebase brings in unrelated formatting changes you don't want): + +```bash +jj resolve --tool :ours # Keep side #1; drop side #2's conflicting changes +jj resolve --tool :theirs # Keep side #2; drop side #1's conflicting changes + +# Scope to specific paths +jj resolve --tool :ours path/to/file.rs +``` + +These built-in tools are **non-interactive** and safe to use in agent contexts. They apply to all conflicts in `@` (or the paths given). + +### 3. Restore from a specific version: `jj restore --from` + +When neither side is right but a *different* revision has the content you want: + +```bash +jj restore --from path/to/file.txt +``` + +This pulls the file contents from `` into the working copy, replacing conflict markers entirely. + +### 4. Abandon a hopelessly conflicted change + +If the conflicted change wasn't worth keeping: + +```bash +jj abandon +``` + +Descendants are rebased onto the parent. Their own conflicts (if any) remain to be resolved separately. + +### 5. Resolve at the ancestor, let jj re-propagate + +If a rebase produced the same conflict in many descendants, **don't resolve each descendant individually**. Resolve once at the change where the conflict first appeared, and jj will re-propagate the resolution forward: + +```bash +# Find where the conflict first appeared +jj log -r 'conflicts()' + +# Edit that change directly +jj edit +# ... fix conflict markers ... +jj st # Snapshot the resolution +jj edit @- # Move back to where you were +``` + +Descendants pick up the resolution automatically. + +## Verifying a resolution + +After editing, **don't trust `jj st` alone**: + +```bash +# 1. Status should report no conflicts +jj st + +# 2. Grep for residual markers (catches typos that left markers in place) +git grep -E '^(<{7}|>{7}|\\%{7}|\\+{7})' || echo 'no residual conflict markers' + +# 3. Run the project's tests/linter — markers may be syntactically valid in some +# languages (e.g. inside strings) and parse-tests catch what eyeballing misses. +``` + +In a colocated repo, `git grep` is allowed (read-only). In a non-colocated repo, use `grep -rE` or `rg`. + +## Pushing conflicted commits is blocked + +```bash +jj git push -b main +# Refused: branch main contains conflicts +``` + +You'll need to resolve before pushing. There's no `--force-push-conflicts` option — this is by design. + +## Anti-patterns + +- ❌ **`jj resolve` (no `--tool`)** — interactive; will hang the agent. Always pass `--tool :ours` / `--tool :theirs` or edit markers manually. +- ❌ **Resolving descendants individually after a rebase** — wasteful and error-prone; resolve at the ancestor (path 5). +- ❌ **Relying on `jj st` to verify resolution** — also grep for residual markers and run tests. +- ❌ **Trying to `git checkout` to "drop" a conflict** in a colocated repo — corrupts jj state. Use `jj abandon` or resolve. +- ❌ **`jj squash` to "hide" a conflict into another commit** — the conflict propagates; it doesn't disappear. + +## Quick reference + +| Action | Command | +|---|---| +| List conflicts in `@` | `jj resolve --list` | +| List conflicts everywhere | `jj log -r 'conflicts()'` | +| Resolve manually | Edit file, then `jj st` | +| Take "ours" (side #1) | `jj resolve --tool :ours [paths...]` | +| Take "theirs" (side #2) | `jj resolve --tool :theirs [paths...]` | +| Restore from another rev | `jj restore --from ` | +| Drop the change | `jj abandon ` | +| Find conflict's ancestor | `jj log -r 'conflicts() ~ ::conflicts()-'` | +| Check marker style | `jj config get ui.conflict-marker-style` | diff --git a/modules/programs/tui/pi/skills/jujutsu/references/GOTCHAS.md b/modules/programs/tui/pi/skills/jujutsu/references/GOTCHAS.md new file mode 100644 index 0000000..5b18171 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/GOTCHAS.md @@ -0,0 +1,47 @@ +# Gotchas + +Short list of jj behaviors that regularly surprise agents. + +**Tested with jj 0.41.0** + +## 1. Working Copy Is a Commit + +`@` is always a commit, not a staging area. `jj new` starts a different change; it does not finish the current one. + +## 2. Most jj Commands Snapshot the Working Copy + +Running `jj st`, `jj log`, and many other commands can record filesystem changes into the working-copy commit. If you intentionally need a stale view, use `--ignore-working-copy`. + +## 3. Change IDs and Commit IDs Are Different + +Change IDs are stable across rewrites. Commit IDs change whenever content or metadata changes. Prefer change IDs when handing work to another agent. + +## 4. `jj undo` Is the First Recovery Tool + +Do not manually reverse a bad `rebase`, `squash`, or `abandon` before trying `jj undo`. Use `references/RECOVERY.md` for deeper recovery. + +## 5. Pager Output Is Not a Good Agent Default + +Use the compact commands in `references/TEMPLATES.md`. Those commands intentionally use `jj --no-pager ...`. + +## 6. Co-located Repos Still Forbid Git Mutations + +`git log`, `git show`, `git diff`, `git blame`, `git grep`, and `git status` are fine. `git commit`, `git add`, `git rebase`, `git push`, and similar mutations can corrupt jj state. + +## 7. `jj workspace forget` Does Not Delete Files + +It only unregisters the workspace. Delete the directory separately if cleanup is intended. + +## 8. Stale Workspaces Are Normal in Parallel Work + +If another workspace rewrites your base, use `jj workspace update-stale`. Verify the result with `jj st` instead of improvising a manual repair. + +## 9. Bare `jj resolve` Is Interactive + +For agent-safe conflict handling, use `jj resolve --tool :ours`, `jj resolve --tool :theirs`, or edit conflict markers directly. + +## 10. Default Output Is Often Too Large + +Prefer compact templates, targeted revsets, and narrow filesets before reaching for full `jj log -p` or `jj show`. + +When behavior feels surprising, stop and check `references/NEW_CHANGE.md`, `references/TEMPLATES.md`, `references/WORKSPACES.md`, or `references/RECOVERY.md` before inventing a git-style workaround. diff --git a/modules/programs/tui/pi/skills/jujutsu/references/NEW_CHANGE.md b/modules/programs/tui/pi/skills/jujutsu/references/NEW_CHANGE.md new file mode 100644 index 0000000..7212d8f --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/NEW_CHANGE.md @@ -0,0 +1,52 @@ +# Starting a New Change + +The jj workflow is: **describe first, then code, then refine**. The working copy is always a commit (`@`), so you're already in one when you start. The question is whether to reuse it or start fresh. + +## The opening sequence + +```bash +# 1. Check what @ currently looks like +jj st +``` + +**If `@` is empty** (no changes, no description set, or description is fine to discard): + +- Reuse it. Just describe and start. + +**If `@` has changes you want to keep** (it's someone else's WIP, or work you haven't finished): + +- Start fresh with `jj new` — this creates a new empty commit on top of `@`. + +```bash +# 2. Describe before coding (only with -m; never editor-based) +jj desc -m "feat: Add user authentication to login endpoint" + +# 3. Make changes — they snapshot to @ automatically on the next jj command +# (no `jj add`, no `jj commit` needed) + +# 4. Verify +jj st +``` + +## Why describe first + +Setting the message up front: + +- Forces you to articulate the change as one logical unit before writing code (atomic commits) +- Means there's never a phase where `@` says "(no description set)" +- Makes the change navigable in `jj log` while it's still being built + +See `references/COMMIT_MSG.md` for the message format conventions. + +## After the change is done + +**Do NOT run `jj new` to "close" the change.** In jj, the working copy is always a commit — there's no separate commit step. Leave `@` as-is; the *next* task's step 1 (`jj st` → reuse or `jj new`) handles transition. + +If refinement is needed (split, squash, absorb, abandon), see `references/REFINE_COMMIT.md`. + +## Anti-patterns + +- ❌ `jj commit` — doesn't exist; the working copy is already a commit +- ❌ `jj desc` without `-m` — opens an editor; will hang in non-interactive agent environments +- ❌ Running `jj new` at the end of a task — leaves an empty `@` for the next agent to clean up +- ❌ Starting code before describing — you'll forget to describe, or describe vaguely after the fact diff --git a/modules/programs/tui/pi/skills/jujutsu/references/NONLINEAR.md b/modules/programs/tui/pi/skills/jujutsu/references/NONLINEAR.md new file mode 100644 index 0000000..aa559a3 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/NONLINEAR.md @@ -0,0 +1,65 @@ +# Nonlinear Work + +This skill supports sibling commits and unrelated work. Do not force everything onto the current stack if the work is logically independent. + +**Tested with jj 0.41.0** + +## When to Start a Sibling from `trunk()` + +Use a fresh sibling when: + +- `@` already represents one logical change and the new work is unrelated +- the user wants multiple independent lines of work +- you need to compare or merge two alternative implementations +- a quick fix appears while a feature change is already in progress + +## Preferred Flow + +This skill keeps the local "describe first" rule, even for sibling work: + +```bash +jj st +jj new trunk() +jj desc -m "fix: handle stale workspace recovery" +# make changes +jj st +``` + +Use `trunk()` instead of guessing `main` or `master`. If the new work should build on the current change, use plain `jj new` instead of `jj new trunk()`. + +## Child vs Sibling + +Use a child (`jj new`) when: + +- the new work depends on the current change +- the user wants a stacked sequence +- the current commit is intentionally being split into follow-up commits + +Use a sibling from `trunk()` when: + +- the work should review, land, or be abandoned independently +- you are branching off to do an unrelated fix, doc change, or experiment +- parallel agents need clearly separated lines of history + +## Merge-Shaped Work + +If two independently meaningful lines of work should remain visible as a merge, create an explicit merge commit: + +```bash +jj new -m "merge: combine related lines of work" +``` + +Ask the user before choosing between: + +- rebasing one line onto the other +- keeping a visible merge +- pushing each line separately + +## Anti-Patterns + +- Do not keep piling unrelated work into the current `@` just because it is convenient. +- Do not adopt a `describe -> code -> jj new -> repeat` loop. In this skill, `jj new` starts the next change; it does not close the current one. +- Do not default to `jj new trunk() --no-edit -m ...` here. Keep the local sequence: choose the base first, then run `jj desc -m ...`. +- Do not hardcode `main` or `master` when `trunk()` expresses the intent. + +See `references/NEW_CHANGE.md` for the default change-starting workflow and `references/WORKSPACES.md` for multi-workspace parallelism. diff --git a/modules/programs/tui/pi/skills/jujutsu/references/PUSH.md b/modules/programs/tui/pi/skills/jujutsu/references/PUSH.md new file mode 100644 index 0000000..07b06fe --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/PUSH.md @@ -0,0 +1,39 @@ +### Pushing Changes + +When the user asks you to push changes: + +```bash +# Push a specific bookmark to the remote +jj git push -b + +# Example: push the main bookmark +jj git push -b main +``` + +**Before pushing, ensure:** + +1. Your bookmark points to the correct commit (bookmarks don't auto-advance like git branches) +2. The commits are refined and atomic +3. The user has explicitly requested the push + +**IMPORTANT**: Unlike git branches, jj bookmarks do not automatically move when you create new commits. You must manually update them before pushing: + +```bash +# Move an existing bookmark to the current commit +jj bookmark move my-feature --to @ + +# Then push it +jj git push -b my-feature +``` + +If no bookmark exists for your changes, create one first: + +```bash +# Create a bookmark at the current commit +jj bookmark create my-feature + +# Then push it +jj git push -b my-feature +``` + +For named remotes, multi-remote setups (e.g. fork + upstream), tracking remote bookmarks, and one-time configuration for the two canonical multi-remote workflows, see `references/REMOTES.md`. diff --git a/modules/programs/tui/pi/skills/jujutsu/references/QUERY_LANGUAGES.md b/modules/programs/tui/pi/skills/jujutsu/references/QUERY_LANGUAGES.md new file mode 100644 index 0000000..e014493 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/QUERY_LANGUAGES.md @@ -0,0 +1,91 @@ +# Query Languages + +Use jj's query languages when you need to select revisions, paths, or render compact output without falling back to the default verbose UI. + +**Tested with jj 0.41.0** + +## Revsets + +Use revsets to select commits and workspaces. + +Common selectors: + +- `@`: current working-copy change +- `@-`: parent of the current working-copy change +- `trunk()`: configured mainline; use this instead of guessing `main` vs `master` +- `::@`: all ancestors of `@` +- `trunk()..@`: work reachable from `@` that is not on `trunk()` +- `working_copies()`: all workspace working-copy commits +- `@`: another workspace's working-copy commit +- ``: a stable change ID such as `wqzktpqm` + +Agent patterns: + +```bash +# Current work relative to trunk +jj --no-pager log -r 'trunk()..@' + +# All active workspace tips +jj --no-pager log -r 'working_copies()' + +# One specific workspace tip +jj --no-pager log -r '@' +``` + +Quote revsets that contain punctuation or function calls. + +## Filesets + +Use filesets when a command should operate on only part of the tree. + +Common patterns: + +- `path` or `"path"`: cwd-relative path prefix +- `file:"path"`: exact cwd-relative file path +- `glob:"pattern"`: cwd-relative glob +- `root:"path"`: workspace-relative path prefix +- `~x`: everything except `x` +- `x & y`: intersection +- `x | y`: union +- `x ~ y`: subtract `y` from `x` + +Examples: + +```bash +# Only markdown files in the current directory +jj diff 'glob:"*.md"' + +# Everything in src except snapshots +jj diff 'src ~ glob:"src/**/*.snap"' + +# Exact file path +jj diff 'file:"README.md"' +``` + +## Templates + +Use templates when default output is too large or too decorative for agent work. + +Common building blocks: + +- `++`: concatenate output pieces +- `if(cond, a, b)`: conditional formatting +- `description.first_line()`: one-line summary +- `change_id.shortest(8)`: stable short ID +- `bookmarks`: bookmark names for a commit +- `current_working_copy`, `empty`, `conflict`: useful booleans in `jj log` + +Example: + +```bash +jj --no-pager log --no-graph -n 5 -T 'change_id.shortest(8) ++ " " ++ if(description, description.first_line(), "(no description)") ++ "\n"' +``` + +Start from `references/TEMPLATES.md` instead of inventing new templates unless you need something substantially different. + +## Rules + +- Prefer `trunk()` over hardcoding `main` or `master`. +- Quote revsets and filesets in shell commands when punctuation could be interpreted by the shell. +- Prefer compact `jj --no-pager` inspection commands before widening the query. +- If a query fails to parse, check `jj --help`, `jj help -k revsets`, `jj help -k filesets`, or `jj help -k templates`. diff --git a/modules/programs/tui/pi/skills/jujutsu/references/RECOVERY.md b/modules/programs/tui/pi/skills/jujutsu/references/RECOVERY.md new file mode 100644 index 0000000..f6775a2 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/RECOVERY.md @@ -0,0 +1,118 @@ +# Recovery and the Operation Log + +The **operation log** records every mutation to the repo — every `jj` command that changed state. Nothing is ever truly lost. This is jj's safety net: an agent that makes a mistake (bad squash, accidental abandon, wrong rebase) can always go back. + +## The three recovery primitives + +### 1. `jj undo` — reverse the last operation + +```bash +jj undo +``` + +The first thing to reach for. Reverses the most recent operation atomically — works for `abandon`, `squash`, `rebase`, `restore`, `describe`, and most other mutations. + +**Prefer `jj undo` over manually reversing changes** — it's always correct and safe. + +### 2. `jj op log` — see the full operation history + +```bash +jj op log # All operations, newest first +jj op log -n 20 # Limit to 20 +jj op log -p # Include diffs of what each op changed +``` + +If you are inspecting first and do not yet need full diffs, start with the compact `jj op log` command from `references/TEMPLATES.md`. Add `-p` only after you have narrowed the relevant op IDs. + +Each operation has an `op-id`. Use these to navigate further back than a single `jj undo`. + +### 3. `jj op restore` — jump to any past state + +```bash +jj op restore +``` + +Restores the entire repo to the state it was in at that op. **This is the nuke-from-orbit recovery** — it can undo many operations at once. Safe because the restore itself becomes a new op in the log (you can `jj undo` it if you change your mind). + +## Per-change history: `jj evolog` + +The op log shows everything; `jj evolog` shows how a *single change* evolved (was rewritten, squashed into, rebased, etc.): + +```bash +jj evolog # Evolution of the current @ +jj evolog -r # Evolution of a specific change +jj evolog -p # Include patches between versions +``` + +If you are inspecting first and do not yet need patches, start with the compact `jj evolog` command from `references/TEMPLATES.md`. Add `-p` only after you have narrowed the relevant version. + +Useful when you want to recover an earlier *version* of a specific commit (e.g., "what did this commit look like before I squashed into it?"). + +## Common recovery scenarios + +### Accidentally abandoned the wrong change + +```bash +jj undo # If it was just now +# or +jj op log # Find the abandon op +jj op restore # Restore to before it +``` + +### A `jj squash` merged work into the wrong parent + +```bash +jj undo # Single-step undo +``` + +If you've done more work since: + +```bash +jj op log # Locate the squash op +jj op restore # Jump back; ops after that point are dropped +``` + +### A rebase went sideways + +```bash +jj undo # Reverse the rebase entirely +``` + +### Lost work after a complex sequence + +```bash +jj op log -p # Diffs show exactly what each op did + # Find the op id where the work still existed +jj op restore +``` + +### Need a previous version of a commit's content + +```bash +jj evolog -r -p # Find the version you want +jj restore --from # Restore working-copy content from it +``` + +### Restored the wrong op-id + +```bash +jj undo # The restore is itself a recorded op +``` + +## Notes for agents + +- **Always reach for `jj undo` first** when something looks wrong. Don't try to manually unwind with new commits or `jj abandon` — `undo` is the canonical safe path. +- **The op log isn't pruned aggressively** — even a week of work is fully retrievable. +- **`jj op restore` is recorded** — it's not destructive in the way `git reset --hard` is. You can always undo a restore. +- **There is no stash/pop in jj** because every state is automatically a commit in the op log. Treat `jj undo` / `jj op restore` as the equivalent. + +## Quick reference + +| Action | Command | +|---|---| +| Undo last operation | `jj undo` | +| See all operations | `jj op log` | +| See ops with diffs | `jj op log -p` | +| Jump to past state | `jj op restore ` | +| Change evolution | `jj evolog -r ` | +| Restore file from past version | `jj restore --from ` | diff --git a/modules/programs/tui/pi/skills/jujutsu/references/REFINE_COMMIT.md b/modules/programs/tui/pi/skills/jujutsu/references/REFINE_COMMIT.md new file mode 100644 index 0000000..3ef1229 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/REFINE_COMMIT.md @@ -0,0 +1,78 @@ +### Squashing Changes + +`jj squash` moves changes from one revision to another. The default is `@` → its parent; pass `--into` to target any other change. + +```bash +# Default: squash @ into its parent +jj squash + +# Squash @ into a specific change (not the parent) +jj squash --into +jj squash -t # short form + +# Squash a non-@ change into its parent +jj squash -r + +# Full explicit form: any source, any destination +jj squash --from --into + +# Squash only specific paths (the rest stay in the source) +jj squash path/to/file.txt +jj squash --into src/auth/ +``` + +**Combined description prompt**: if both source and destination have non-empty descriptions, `jj squash` opens an editor to pick a combined description — and hangs in agent environments. Always pass `-m` inline when squashing two described commits together: + +```bash +jj squash --into -m "feat: Combine auth changes" +``` + +**Empty source**: when squashing leaves the source revision empty (no content vs. its parent), jj abandons it by default. Pass `--keep-emptied` to keep an empty placeholder instead. + +**Interactive forms to avoid**: `jj squash -i` / `jj squash --interactive` open a TUI and will hang in agent environments. Use `--from` / `--into` / paths instead. + +### Splitting Commits + +**Warning**: `jj split` is interactive and will hang in agent environments. To divide a commit, use `jj restore` to move changes out, then create separate commits manually. + +### Absorbing Changes + +Automatically distribute changes to the commits that last modified those lines: + +```bash +# Absorb working copy changes into appropriate ancestor commits +jj absorb +``` + +### Abandoning Commits + +Remove a commit entirely (descendants are rebased to its parent): + +```bash +jj abandon +``` + +### Undoing Operations + +Reverse the last jj operation: + +```bash +jj undo +``` + +This reverts the repository to its state before the previous command. Useful for recovering from mistakes like accidental `abandon`, `squash`, or `rebase`. + +### Restoring Files + +Discard changes to specific files or restore files from another revision: + +```bash +# Discard all uncommitted changes in working copy (restore from parent) +jj restore + +# Discard changes to specific files +jj restore path/to/file.txt + +# Restore files from a specific revision +jj restore --from path/to/file.txt +``` diff --git a/modules/programs/tui/pi/skills/jujutsu/references/REMOTES.md b/modules/programs/tui/pi/skills/jujutsu/references/REMOTES.md new file mode 100644 index 0000000..9c6c25e --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/REMOTES.md @@ -0,0 +1,169 @@ +# Named Remotes and Multi-Remote Workflows + +For pushing to a single remote (the common case), see `references/PUSH.md`. This file covers everything else: managing named remotes, selecting which remote to push to, tracking remote bookmarks, and the two canonical multi-remote setups. + +## Named remotes + +When a repo has more than one remote (e.g. `origin` = your fork, `upstream` = the canonical source), you select the target with `--remote `. + +```bash +jj git remote list # See all remotes and their URLs +jj git remote add upstream # Add a second remote +jj git remote rename origin fork # Rename a remote +jj git remote set-url origin # Change a remote's URL +jj git remote remove old-name # Remove a remote (and forget its bookmarks) +``` + +**Default remote selection**: when `--remote` is omitted, `jj git push` uses: + +1. The `git.push` config setting (if set), or +2. The remote named `origin` (if multiple remotes exist), or +3. The single remote (if only one exists) + +There is **no built-in option to push to multiple remotes in one command** — call `jj git push` once per remote. + +### Pushing the same bookmark to two remotes + +```bash +jj bookmark move my-feature --to @ # Move the bookmark to current change +jj git push -b my-feature --remote fork # Push to your fork +jj git push -b my-feature --remote upstream # Push to canonical (e.g. for a PR target branch) +``` + +### Setting a default remote + +To avoid typing `--remote` repeatedly: + +```bash +# Per-repo (writes to .jj/repo/config.toml) +jj config set --repo git.push fork + +# Globally (writes to ~/.config/jj/config.toml) +jj config set --user git.push origin +``` + +### Tracking a remote bookmark + +By default, when you `jj git fetch`, remote bookmarks appear as `@` (e.g. `main@upstream`). To get a local bookmark that follows it: + +```bash +jj bookmark track main@upstream # Track upstream/main as a local 'main' +jj bookmark track 'glob:release/*@upstream' # Track all upstream release branches +``` + +Once tracked, `jj git push --tracked --remote upstream` will push all tracked bookmarks back to that remote. + +### Fetching from a specific remote + +```bash +jj git fetch --remote upstream # Fetch only from upstream +jj git fetch --all-remotes # Fetch from every configured remote +``` + +### When a push is rejected + +If the remote has moved since your last fetch, `jj git push` refuses (similar to `git push --force-with-lease`). Re-sync first: + +```bash +jj git fetch --remote # Pull the remote's current state +# (resolve any bookmark conflicts or rebase if needed) +jj git push -b --remote # Try again +``` + +### Quick reference + +| Action | Command | +|---|---| +| List remotes | `jj git remote list` | +| Add a remote | `jj git remote add ` | +| Push to specific remote | `jj git push -b --remote ` | +| Push all tracked bookmarks to a remote | `jj git push --tracked --remote ` | +| Set default push remote (repo) | `jj config set --repo git.push ` | +| Track a remote bookmark | `jj bookmark track @` | +| Fetch one remote | `jj git fetch --remote ` | +| Fetch all remotes | `jj git fetch --all-remotes` | + +## Common multi-remote workflows + +Two canonical setups documented in [the jj docs](https://docs.jj-vcs.dev/latest/guides/multiple-remotes/). Pick the one that matches your relationship to the remotes; the config is one-time per repo. + +**Nomenclature** (jj convention): + +- `origin` = a remote you have write access to (typically where you push) +- `upstream` = the more canonical / well-known repo (you may not have push access) +- Trunk is assumed to be `main`, so remote bookmarks are `main@origin` and `main@upstream` + +### Scenario 1: GitHub-style fork contributing upstream + +`upstream` is the canonical project; `origin` is your fork; you open PRs from `origin` against `upstream`. + +Typical actions: + +- Fetch from `upstream` to get the latest changes +- Push `main` to `origin` to keep your fork's `main` in sync +- Push `my-feature` to `origin`, then open a PR targeting `upstream` + +One-time setup: + +```bash +# Fetch from both remotes when you run `jj git fetch` +jj config set --repo git.fetch '["upstream", "origin"]' + +# Default push target is your fork +jj config set --repo git.push origin + +# Track both remote bookmarks named `main` +jj bookmark track main + +# Make upstream's main the trunk (treated as immutable; rebases stop here) +jj config set --repo 'revset-aliases."trunk()"' main@upstream +``` + +Why these specifically: + +- Tracking `main@upstream` keeps your local `main` updated whenever you fetch from upstream +- Tracking `main@origin` means `jj git push` will also update your fork's `main` +- `trunk() = main@upstream` prevents jj from rewriting commits that are already in the canonical repo + +### Scenario 2: Independent repository, periodically integrating from upstream + +`origin` is a long-lived repository with its own divergent history; `upstream` is the source you occasionally pull changes from but don't contribute back to. + +Typical actions: + +- Fetch from `origin` to get the latest of your own work +- Push bookmarks to `origin`; merge PRs into `main@origin` +- Periodically fetch from `main@upstream` and merge/rebase/duplicate its changes into `main@origin` + +One-time setup: + +```bash +# Fetch only from origin (typical case) +jj config set --repo git.fetch '["origin"]' +# Or fetch from both if you want upstream changes visible on every fetch: +# jj config set --repo git.fetch '["upstream", "origin"]' + +# Default push target is origin +jj config set --repo git.push origin + +# Track only origin's main — do NOT track upstream's main +jj bookmark track main --remote=origin +jj bookmark untrack main --remote=upstream + +# Your origin defines the trunk (your main is the canonical line) +jj config set --repo 'revset-aliases."trunk()"' main@origin +``` + +Why these specifically: + +- Tracking only `main@origin` keeps `main@upstream` as a clearly separate reference you can `rebase`/`merge` from intentionally — it won't silently overwrite your local `main` +- `trunk() = main@origin` treats your divergent line (not upstream's) as the immutable history boundary + +### Choosing between the scenarios + +| Question | Scenario 1 | Scenario 2 | +|---|---|---| +| Do you push features back to upstream as PRs? | Yes | No | +| Should fetching from upstream update your local `main`? | Yes | No (you integrate manually) | +| Is `upstream/main` immutable from your repo's perspective? | Yes | No (you may rewrite when integrating) | +| Is `origin/main` allowed to diverge from `upstream/main`? | Minimally (just for syncing) | Yes (it's the point) | diff --git a/modules/programs/tui/pi/skills/jujutsu/references/TEMPLATES.md b/modules/programs/tui/pi/skills/jujutsu/references/TEMPLATES.md new file mode 100644 index 0000000..435283d --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/TEMPLATES.md @@ -0,0 +1,84 @@ +# Token-Reducing Templates + +Use inline `-T` templates for compact, agent-friendly jj output. Prefer inline `-T` use over editing repo or global jj config so the skill stays explicit and portable. + +Assumptions: + +- Assume `jj 0.41.0` or later. +- Each template was tested with `jj 0.41.0`. +- If a template fails to parse, treat that as jj version drift and verify with `jj --help` plus `jj help -k templates`. +- Use the commands below as written. They intentionally put `--no-pager` at the top-level `jj` invocation so agent output does not depend on pager configuration. + +## `jj log` + +Tested with: `jj 0.41.0` + +```bash +jj --no-pager log --no-graph -n 20 -T 'change_id.shortest(8) ++ if(bookmarks, " " ++ bookmarks, "") ++ if(current_working_copy, " @", "") ++ if(empty, " (empty)", "") ++ if(conflict, " (conflict)", "") ++ " " ++ if(description, description.first_line(), "(no description)") ++ "\n"' +``` + +This keeps the change ID, bookmark or `@`, empty/conflict markers, and the first description line. Use `shortest(8)` instead of bare `shortest()` so IDs do not collapse to one character on `jj 0.41.0`. + +## `jj show` + +Tested with: `jj 0.41.0` + +```bash +jj --no-pager show --stat -T 'change_id.shortest(8) ++ if(bookmarks, " " ++ bookmarks, "") ++ if(current_working_copy, " @", "") ++ if(empty, " (empty)", "") ++ if(conflict, " (conflict)", "") ++ " " ++ if(description, description.first_line(), "(no description)") ++ "\n"' +``` + +Prefer `--stat` for a compact file summary. Swap `--stat` for `--no-patch` if you want the commit summary without any file-level diff summary. + +## `jj diff` + +Tested with: `jj 0.41.0` + +```bash +jj --no-pager diff -T 'self.status_char() ++ " " ++ self.display_diff_path() ++ "\n"' +``` + +Add `-r ` if you want a diff for a specific revision instead of the working copy. + +## `jj bookmark list` + +Tested with: `jj 0.41.0` + +```bash +jj --no-pager bookmark list -T 'if(self.normal_target(), self.name() ++ ": " ++ self.normal_target().change_id().shortest(8) ++ " " ++ if(self.normal_target().description(), self.normal_target().description().first_line(), "(no description)") ++ "\n", self.name() ++ ": (conflicted)\n")' +``` + +Use this for ordinary bookmark inspection. If you are explicitly debugging conflicted bookmarks and need every old/new target, fall back to jj's default output. + +## `jj workspace list` + +Tested with: `jj 0.41.0` + +```bash +jj --no-pager workspace list -T 'self.name() ++ ": " ++ self.target().change_id().shortest(8) ++ " " ++ if(self.target().description(), self.target().description().first_line(), "(no description)") ++ if(self.target().empty(), " (empty)", "") ++ "\n"' +``` + +This shows workspace name, working-copy change ID, description, and whether that working copy is empty. + +## `jj op log` + +Tested with: `jj 0.41.0` + +```bash +jj --no-pager op log --no-graph -n 20 -T 'id.short(8) ++ " " ++ time.start().ago() ++ " " ++ description.first_line() ++ "\n"' +``` + +Use this first when you need recovery context but do not yet need full diffs. Add `-p` only after you have narrowed down the interesting op IDs. + +## `jj evolog` + +Tested with: `jj 0.41.0` + +```bash +jj --no-pager evolog -r --no-graph -n 20 -T 'commit.change_id().shortest(8) ++ " " ++ if(commit.description(), commit.description().first_line(), "(no description)") ++ " | " ++ operation.id().short(8) ++ " " ++ operation.description().first_line() ++ "\n"' +``` + +This keeps the evolving change summary on the left and the rewriting operation on the right. + +## Not Templated + +`jj st` and `jj status` do not expose a template flag in `jj 0.41.0`, so leave status output untemplated. The same applies to commands like `jj undo`, `jj op restore`, and `jj resolve` where the value is in the side effect, not in custom rendering. diff --git a/modules/programs/tui/pi/skills/jujutsu/references/WORKSPACES.md b/modules/programs/tui/pi/skills/jujutsu/references/WORKSPACES.md new file mode 100644 index 0000000..ed9b960 --- /dev/null +++ b/modules/programs/tui/pi/skills/jujutsu/references/WORKSPACES.md @@ -0,0 +1,209 @@ +# Parallel Workspaces + +Use `jj workspace` when multiple agents need independent working copies of the same repo. Each workspace gets its own `@` while sharing the same repo store and operation log. + +**Tested with jj 0.41.0** + +This skill intentionally prefers project-local workspace directories when they are safely ignored. Directory priority for this skill: + +1. `.workspaces/` +2. `workspaces/` +3. Repo-local instructions such as `AGENTS.md` or `CLAUDE.md` +4. Ask the user + +Do not replace this with a blanket "sibling directories only" rule. + +## Before Creating a Workspace + +First, make sure project-local workspaces will not leak into Git history: + +```bash +grep -E '^\.?workspaces/?$' .gitignore 2>/dev/null +``` + +If the entry is missing, add it before `jj workspace add` and record that change using the normal workflow from `references/NEW_CHANGE.md`: + +```bash +echo ".workspaces/" >> .gitignore +``` + +Never skip this check. `jj workspace add` creates directories that contain `.jj/` metadata, and the outer Git repo must ignore them. + +Use descriptive names so `jj workspace list` and `jj log` stay readable: + +```bash +REPO_NAME=$(basename "$(jj workspace root)") +WORKSPACE_PATH=".workspaces/${REPO_NAME}-" +``` + +Examples: + +- `.workspaces/myrepo-test-runner` +- `.workspaces/myrepo-reviewer-agent` +- `.workspaces/myrepo-refactor-experiment` + +Avoid generic names such as `tmp`, `wip`, or `new`. + +## Creating and Bootstrapping + +Create the workspace and move into it: + +```bash +jj workspace add "$WORKSPACE_PATH" +cd "$WORKSPACE_PATH" +``` + +After creation: + +- the workspace has its own working-copy commit +- it appears in log output as `@` +- it shares history and the operation log with the main workspace +- snapshots in this workspace affect only this workspace's `@` + +If the repo needs environment setup, bootstrap it in the new workspace before handing it to another agent: + +| Marker file | Setup command | +| --- | --- | +| `package.json` | `npm install` | +| `Cargo.toml` | `cargo build` | +| `pyproject.toml` | `uv sync` | +| `Gemfile` | `bundle install` | +| `flake.nix` | `nix develop` | +| `go.mod` | `go mod download` | +| `mix.exs` | `mix deps.get` | + +## Agent Handoff + +When one agent creates a workspace for another, always report: + +- the absolute workspace path +- the change ID the agent should edit +- the exact `cd` command +- the exact `jj edit ` command +- the scope boundaries + +Use a handoff like this: + +```text +Workspace: /absolute/path/to/.workspaces/myrepo-tests +Change ID: abcdefgh + +cd /absolute/path/to/.workspaces/myrepo-tests +jj edit abcdefgh + +Rules: +- Always use -m for messages. +- Run jj st after every mutation. +- Do not modify files outside the assigned scope. +``` + +Absolute paths matter. Agents lose track of relative locations more often than they lose track of change IDs. + +## Monitoring Progress + +Start from the compact `jj log` command in `references/TEMPLATES.md`, then swap in the revset you need: + +- all workspace tips: `-r 'working_copies()'` +- one named workspace: `-r '@'` + +Examples of the revsets themselves: + +```bash +working_copies() +reviewer@ +``` + +See `references/QUERY_LANGUAGES.md` for the revset syntax behind those examples. + +## Combining Results + +When a workspace finishes its work, bring that change back to the main line and retire the workspace. Do not decide silently; ask the user which strategy they want. + +### Strategy 1: Rebase into the Default Workspace + +Use this when one developer is orchestrating multiple agents and the result should land as ordinary commits in the default workspace. + +```bash +jj rebase -s -d @ +jj workspace forget +rm -rf "$WORKSPACE_PATH" +``` + +If the repo uses bookmarks for shipping work, move or create the bookmark after the rebase using the normal push workflow from `references/PUSH.md`. + +### Strategy 2: Keep an Explicit Merge + +Use this when both lines of work are independently meaningful and the merge structure should remain visible: + +```bash +jj new -m "merge: combine parallel work" +``` + +### Strategy 3: Push a Bookmark and Open a PR + +Use this when the work needs human review before landing: + +```bash +jj bookmark create my-feature -r +jj git push -b my-feature +``` + +Decision guide: + +| Situation | Default suggestion | +| --- | --- | +| Solo developer running multiple local agents | Strategy 1 | +| Long-lived feature with checkpoints | Strategy 1, then push a bookmark when ready | +| Human review required | Strategy 3 | +| Both lines of work are independently meaningful | Strategy 2 | + +If the context is ambiguous, suggest Strategy 1 first and ask whether the user wants the result to land in the default workspace or as a PR. + +## Listing and Cleaning Up + +```bash +jj workspace list +jj workspace forget +rm -rf "$WORKSPACE_PATH" +``` + +If you need to inspect `jj workspace list` output as an agent, use the compact command from `references/TEMPLATES.md`. + +`jj workspace forget` only unregisters the workspace. It does not delete files from disk. + +## `update-stale` + +If another workspace rewrites the commit your workspace had checked out, jj marks that workspace as stale. Use this first: + +```bash +jj workspace update-stale +jj st +``` + +Do not improvise a manual repair before trying `update-stale`. If the old operation is gone, jj can create a recovery commit with the current working-copy contents instead of silently discarding them. + +## Conflict Avoidance + +| Risk | Recommended mitigation | +| --- | --- | +| Build outputs or generated files | Ignore them or give each workspace separate output paths | +| Shared config files | Assign a single owner for that file or change it serially | +| Lockfiles / dependency updates | Let one task own dependency changes | +| Same source files across workspaces | Redesign the task boundary or serialize the work | + +## Critical Rules + +1. Never skip the `.gitignore` check for project-local workspace directories. +2. Always follow the directory priority: `.workspaces/` -> `workspaces/` -> repo instructions -> ask. +3. Use descriptive names: `${REPO_NAME}-`, not `tmp`. +4. Bootstrap the workspace before handing it to another agent. +5. Report the absolute path, change ID, and next commands when a workspace is ready. +6. Run `jj workspace forget` before deleting the directory. +7. Ask before choosing between rebase, merge, or PR integration. +8. Use `jj workspace update-stale` as the first response to a stale workspace. + +## Reference + +Convention adapted from [edmundmiller/dotfiles `using-jj-workspaces`](https://lobehub.com/skills/edmundmiller-dotfiles-using-jj-workspaces). + +Additional handoff, monitoring, stale-workspace, and conflict-avoidance patterns were adapted from [joshuadavidthomas/agent-skills `jj`](https://github.com/joshuadavidthomas/agent-skills/tree/main/jj) and its [`workspaces.md`](https://raw.githubusercontent.com/joshuadavidthomas/agent-skills/main/jj/workspaces.md), while intentionally preserving this skill's local `.workspaces/` / `workspaces/` directory priority and local change-creation workflow. diff --git a/modules/programs/tui/opencode/skills/nix/debug.md b/modules/programs/tui/pi/skills/nix-debug/SKILL.md similarity index 100% rename from modules/programs/tui/opencode/skills/nix/debug.md rename to modules/programs/tui/pi/skills/nix-debug/SKILL.md diff --git a/modules/programs/tui/opencode/skills/nix/index.md b/modules/programs/tui/pi/skills/nix-index/SKILL.md similarity index 100% rename from modules/programs/tui/opencode/skills/nix/index.md rename to modules/programs/tui/pi/skills/nix-index/SKILL.md diff --git a/modules/programs/tui/opencode/skills/nix/search.md b/modules/programs/tui/pi/skills/nix-search/SKILL.md similarity index 100% rename from modules/programs/tui/opencode/skills/nix/search.md rename to modules/programs/tui/pi/skills/nix-search/SKILL.md diff --git a/modules/programs/tui/opencode/skills/nvf-config.md b/modules/programs/tui/pi/skills/nvf-config/SKILL.md similarity index 100% rename from modules/programs/tui/opencode/skills/nvf-config.md rename to modules/programs/tui/pi/skills/nvf-config/SKILL.md diff --git a/modules/programs/tui/pi/theme.nix b/modules/programs/tui/pi/theme.nix new file mode 100644 index 0000000..1e40a59 --- /dev/null +++ b/modules/programs/tui/pi/theme.nix @@ -0,0 +1,78 @@ +{ + flake.modules.homeManager.pi = { + # keep-sorted start + config, + pkgs, + # keep-sorted end + ... + }: let + colors = config.lib.stylix.colors.withHashtag; + jsonFormat = pkgs.formats.json {}; + in { + programs.pi.coding-agent = { + settings.theme = "stylix"; + themes = [ + (jsonFormat.generate "pi-stylix-theme.json" { + "$schema" = "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json"; + name = "stylix"; + colors = with colors; { + # keep-sorted start + accent = base0D; + bashMode = base0A; + border = base03; + borderAccent = base0D; + borderMuted = base02; + customMessageBg = base01; + customMessageLabel = base0E; + customMessageText = base05; + dim = base03; + error = base08; + mdCode = base0B; + mdCodeBlock = base05; + mdCodeBlockBorder = base03; + mdHeading = base0D; + mdHr = base03; + mdLink = base0C; + mdLinkUrl = base04; + mdListBullet = base0E; + mdQuote = base04; + mdQuoteBorder = base03; + muted = base04; + selectedBg = base02; + success = base0B; + syntaxComment = base03; + syntaxFunction = base0D; + syntaxKeyword = base0E; + syntaxNumber = base09; + syntaxOperator = base0C; + syntaxPunctuation = base05; + syntaxString = base0B; + syntaxType = base0A; + syntaxVariable = base08; + text = base05; + thinkingHigh = base0E; + thinkingLow = base0D; + thinkingMax = base09; + thinkingMedium = base0C; + thinkingMinimal = base04; + thinkingOff = base03; + thinkingText = base04; + thinkingXhigh = base08; + toolDiffAdded = base0B; + toolDiffContext = base04; + toolDiffRemoved = base08; + toolErrorBg = base01; + toolOutput = base05; + toolPendingBg = base01; + toolSuccessBg = base01; + toolTitle = base0D; + userMessageBg = base01; + userMessageText = base05; + warning = base0A; + # keep-sorted end + }; + }) + ]; + }; + }; +} diff --git a/modules/services/network/network.nix b/modules/services/network/network.nix index d66ac39..2f82859 100644 --- a/modules/services/network/network.nix +++ b/modules/services/network/network.nix @@ -58,6 +58,9 @@ settings.Resolve = { DNSOverTLS = "opportunistic"; + # Prefer the global servers over per-link DNS from DHCP. + Domains = ["~."]; + FallbackDNS = [ # keep-sorted start "1.0.0.1#cloudflare-dns.com" diff --git a/modules/services/server/podman.nix b/modules/services/server/podman.nix index 8c21ce1..56e536d 100644 --- a/modules/services/server/podman.nix +++ b/modules/services/server/podman.nix @@ -1,19 +1,17 @@ { flake.modules.nixos.podman = { # keep-sorted start - config, pkgs, vars, # keep-sorted end ... }: let inherit (vars) username; - hostname = config.networking.hostName; - secret = config.sops.placeholder; in { virtualisation = { podman = { enable = true; + defaultNetwork.settings.dns_enabled = true; dockerSocket.enable = true; dockerCompat = true; }; @@ -22,33 +20,11 @@ containers.containersConf.settings.engine.compose_warning_logs = false; }; - sops.templates."podman-dns.conf" = { - mode = "0444"; - content = '' - [containers] - dns_servers = [ - "${secret."dns/${hostname}/dns_1"}", - "${secret."dns/${hostname}/dns_2"}", - "${secret."dns/${hostname}/dns_3"}", - "${secret."dns/${hostname}/dns_4"}", - ] - ''; - }; - - systemd.services.podman-dns-conf = { - after = ["sops-install-secrets.service"]; - wantedBy = ["multi-user.target"]; - - serviceConfig = { - ExecStart = pkgs.writeShellScript "podman-dns-conf" '' - set -eu - mkdir -p /etc/containers/containers.conf.d - ${pkgs.gnused}/bin/sed 's/#.*"/"/' ${config.sops.templates."podman-dns.conf".path} > /etc/containers/containers.conf.d/00-dns.conf - ''; - RemainAfterExit = true; - Type = "oneshot"; - }; - }; + # Forward container queries through the host's resolved stub. + environment.etc."containers/containers.conf.d/00-dns.conf".text = '' + [containers] + dns_servers = ["127.0.0.53"] + ''; environment.systemPackages = [pkgs.podman-compose]; diff --git a/secrets.yaml b/secrets.yaml index fc9eccd..47a3479 100644 --- a/secrets.yaml +++ b/secrets.yaml @@ -1,7 +1,6 @@ ai: github_token: ENC[AES256_GCM,data:dh+u/SZoSxp7pnfXT0im7JBmN52z4rufO0ZI8+f0Uw1K+JmWmBTCmw==,iv:f0ayiDvdhhEMfehGKOaahejKq+7XRYla9nXDXTkDf3o=,tag:kaflBbP1GeNMvt9T+pK/uQ==,type:str] context7_key: ENC[AES256_GCM,data:+quPFuvi7Td/5Nyb1GRNUZU6DgID3HJKzsyL0IoMezrIlz5gDMVHsJ6cBA==,iv:Y0FYNWHGIuEZGp3p2bYygOelWZMuqKQbh+Lk70Hjh2o=,tag:WiQkC98/WZ5r/wvxrqCfVQ==,type:str] - authentik_token: ENC[AES256_GCM,data:Yd2pErRQTkDsw9HEVE3YgEE7WlpfwaPF0FaaEPGxyGWx8w9b7BWBMmE/Na2h9d9qrBHpqXJQK/ztVfDp,iv:pMJdLbW0dYoZL2IggzvjyJ8gk7Bvc9tkGA4d9xQULyQ=,tag:XYMHJb1UnLXRfXB+M/6Cig==,type:str] dns: desktop: dns_1: ENC[AES256_GCM,data:a5aSp9NnYwrsSW9IupP5hlbT1vRZmJMuRWUFQwzhqTldJ0BPW3/GMg==,iv:cjW5I7kfhT6UCH15+GzgW164pWza3wCfrUnJEqaECfA=,tag:ifcFGgjDVx0uxDTRJqA+gw==,type:str] @@ -86,6 +85,7 @@ nix_ncps_upload_secret_key: ENC[AES256_GCM,data:8GuPgrLyM86WyLDb3MbfH4ZLAHq21YBv gpg: private_keys: ENC[AES256_GCM,data:AOqwRtNcmYsdUe+DqSTRXAC70ASE4uK9+oLsKmwqn/HCWuZnkuWY0k3xSTskPp+GsUKDmQXGmPZmXOwiwcRpZia23msEObXXBz0JmgMV1WAZxByYVQu+DWN0Ez+7tGG35Am67XAKlktRqE5DhS4uFIyjZTJB4K2Lff3+uduX1pUtJFcn98vsabAEhrJq4hg8aHgqSUtRp1RXbzU2plnH95nY65H3+E//wnm6PgQsjw2LGWQcHoeafYGkPNTWJvR07o59TraSKlkEyz/uB0AL+42sVFwDUWRNIG3ti/OSZl++TewyZ0wNDSYyyRmCdEgRWBrRooRMAYTQKQAfS7+bQfrQUse6cJXatW33I62ysJ57kV0htpYPm1x+ghGZPq1oVIXP3EXWPLWMFwaW33KhEZxypra+louTcPATbVLqxxg9flc5ENEgOGVNkz0cTWGD6elejph0UQSvtd3/qdjTHFCuCUIU9CLasRkut5PUhE2y7fE3Vgwfh2FzGv0RRIB9OHD/HVsboHN8P/9XgWE1cc7umUtwdvs0fYU05gClBcTe5XWSPiZ/Q8ND0IQTYYy9+ag6c+iT/GGMcIJESnv3FPYGw3iH/krFdtnQpybLAed/uQVdk6KDcNwu67wjegq1RME46s+R3wanMS7gKDQIJ8KlNlJaFDhvbl0GeVWHCdlr9qrcDTALAaVmr5C6vcVUJU+QjczWm59ie5av6ViGWnKLn0AoXEtTsUGU0ZLqutMRbebSx3QoBQ6vpCzsl9Xrldo+MMmTIEpBzTu5SRZuELIyjJ5MN4UDfTMae9NFQht3nHA91ywfnUa9nBIHz0MNjlmEEzt5O6kHCtSX3xHhRJVH7Zf1o2h7PzP/39XorbyHMBh/m+yGmiJgA0LeRt5SAX9iz6izS9RZ2xtFnWOsfl2ZSSZRAX0nKeACXEy29BQPjEUnL3ScWRWUmBV4GvmwvAD2QSINLE8Jzx09Te5Tgw7k4bzsHJavMFkmsAwFN1xZ4gelHhW73LErMjPfPyXOd3dOnrXQ4cNJ+BVgcnJtlUI7wV1bcEkFCIhhy33+awLdnrU4MWtSO3avH/RMOI80n/bEkERbljHEpEtBlrmXhjFKGPczgFGYh7nrPZLCzxiEOYmbdak0NcmGXQkK4P0AGYESSMNbGbtL/Fh10MKrkEnCgVaqpkc60W1evh2CWZ4vh7BiRCTdJSIuxgMaWhN5RdQe81grBkETI0fEaSsXa3aCpgPH/wtFvTnbj8UmnasdCmVG5oR990szJH1RgvsRbKTLhYIcpBZsjaz9OUF7OB/5NuqC65w/nEKLzQn2DfQSxgf3jF4KWcqUlUt8aWMPvMgYFL8f/0HQhCmrb2fz+hnWTymwqiYBsFiSCnDrZhkFlLSldECQ2oqrzw7Jx326x5K8ZUbAEZumb6rNns3REnqbORgbZz42WZn6GyNF4bt7SNvpbB7XTFX56WgxV4uzDFJk5UCuyOixEgkAbpDTuIiTJP2oiG2Uu/Vg4cQ7kxXK5T1T4feTc6dhpU9h8JSUCRdz/RC328aen6IOeE5hJ/Lwx+krgVJfYnPPtACpyTkfg6YM2IkHN7slUjNoBcL/0Np4/DOHcN6prCox71/8FVfscBuRFl7SaZwGhLSTZt5o3pTiEIsnjMRBoNOmw6rG6dECPKxZHuwiBN/DZksTeOaFAniX+BbRdfQXqNYKBqm4/HjfUxDVd1qo0l7ry60q+x5BAyND+I7bEvizuC9dL+SKyYuM0hkSR1ypBehldH60rNdbqyF16vkkJfBgRfqSmLkpDyjq5NH+R/kTZ5gljh7FsrulDhGPf+SzUGNx6fmajjJ1UmiR4iLWyNY3j694dOreIL8TH5aN1jKi7zxEfoZSBhct99Cbjhu82VhZhPqhdzKUoX7dx1axlX6GTWAPcMYW/LtVeDupNpp5y50q+wVnMhFc9lz23GbZMgXoOBy+lblX1wO2gL96hnSf9GttqkAa97mHLvmFVBKZuEGQnq/s8qxF9x1uSbfKy5YFh/vIvaxwxGDw7DbPeZYJP4lAuoUZoql7pBvzi7C5x03UenBLgjr111pPV2b9cOP3hgOX8dMjNSqE40Eh6J8DmJ2BKDImPjo3SXdCMHnKFyV5hoJxdnQroaKoD9FXzTVTCvYa+vmcVx6o7Tru2cEcSnqBqUTKOGu7f4cyqIrpPinxFXq1XdHGanU0UnoC0CzgZ6Z9h18Nt/sX/yrWCg/fC4QWeLuivT/E,iv:cWOG/YEJ/vbk41rD+yeVurIb5uFUeU1bc9WA8KuD/CQ=,tag:wXMcN0KOb7PJn0FijBeWvw==,type:str] ownertrust: ENC[AES256_GCM,data:lkCOnk4Fe+DTKeTuBtjrhD4y/OsrJVW9z74ihfGLjRxXfh8aKapruUx0CNyiFGzeCNM9Cy1MeA+nFa5xodUDd7g8ZZrnG+4/s8N84UfDzlzR+2v2mhg/a8Os5R5hYVLyP22Dzn2Qye2eVPtV2VQoCnKwShHlrIA3sUipRKDm2HaZZ8Rv0g7tzsqec2vN99CKXpe60kq5zNaeK26UIdRar3YBqqlM1j2rE4VmjWyauztKOvCgwRZpM0ymlgxetmH/tPng4heL85CIvdOhCrnu+8pRVg==,iv:4LpN7y5Q6uwrt6dS5Pst/gWIPzBICyLyOc/cD9yAGMQ=,tag:3DXppR87QE7E7LqCkxfYdg==,type:str] +atproto_app_password: ENC[AES256_GCM,data:3BOPzIRh130dzmSK6CYH24fE2w==,iv:GbpKDv5l/RL5CTmoJqP5FR8dD1b0b9UahgEkkRY9hS0=,tag:wXeclV3wCeZCJ4a/jl5vOw==,type:str] sops: age: - enc: | @@ -124,8 +124,8 @@ sops: w2ToACYVigKMSlqPNG9bKcK75XI/iAONG48DQm1Nse3yB9/q3jwd+Q== -----END AGE ENCRYPTED FILE----- recipient: age1yl52c6aemw9anmfuqayamvsnkvlu6fmy0kk3gzvrraexxd9pr90qvs8pe5 - lastmodified: "2026-07-15T23:16:37Z" - mac: ENC[AES256_GCM,data:VedEHG6Ij27zRV/dqOZd36SRxnAIDA4re6b901Q9Ucna1nYv0ODCghoSLHgX1dXDw+TkZ9DGqJgjGlaSnNbzvYN/88vRottr9iX+Y+80zlW6M2mbVh2pUmhYk1kugYRA9YnvCwlnoSaRYthCSZah9cSH0Al9F+bnvafob4lAfGw=,iv:qDXeoJNGh7kK4RKjSi/IDLfKGrFVzhLYPeEyZ8unVow=,tag:qeaGivNWVguXBfnDEO+S9A==,type:str] + lastmodified: "2026-07-18T04:03:09Z" + mac: ENC[AES256_GCM,data:VRIAu9Cm9Nn8dG3I91MCTWP1wz47INq2OE2QoKWlkRccqgcHp22CvgNHhS6qSQ2NHNSgCAGI/OZ5RPczn3l2yzu7KDV9IZ4uzBa/Oj6cJpKUTGWzVyROvhnIX1xWBNt/6i6t99SVDE1Udyir9aScJXyj2uIlFD87oY0Ao4cWn0E=,iv:FmOQh04XKRZl9fGKGTxwuDy8y/2+/8n3yZlYQKHFT5E=,tag:7cLLKqApEVvrTXcQHHyLJA==,type:str] pgp: - created_at: "2026-06-02T01:50:38Z" enc: |- @@ -139,4 +139,4 @@ sops: -----END PGP MESSAGE----- fp: 3666B440B0E872931B8E78596CFFE3FBA5DD84B9 unencrypted_suffix: _unencrypted - version: 3.13.1 + version: 3.13.2 diff --git a/vars.nix b/vars.nix index 94749f1..e6fab82 100644 --- a/vars.nix +++ b/vars.nix @@ -1,6 +1,7 @@ rec { # Identity shared across modules. # keep-sorted start + atprotoHandle = "adam0.dev"; fullName = "Adam0"; username = "adam0"; # keep-sorted end -- 2.51.2