Author SHA1 Message Date
moons-14 a2ee175609 fix(noctalia): split taskbar overview hook patch 2026-08-05 04:01:29 +09:00
moons-14 66bf009afd fix(noctalia): restore valid window switcher patch 2026-08-05 04:01:14 +09:00
moons-14 33e09f8e93 normcap-translate 2026-08-05 03:56:16 +09:00
moons-14 eff32fddcd background-opacity 2026-08-05 03:56:16 +09:00
moons-14 8ce3a6082a dns 2026-08-05 03:56:16 +09:00
moons-14 a8246261ad noctalia: add taskbar overview command hook (#61) 2026-08-05 03:54:14 +09:00
moons-14 8b51b5c55f noctalia taskbar 2026-08-05 02:55:46 +09:00
moons-14 e24d85da56 echo cancel 2026-08-05 02:10:48 +09:00
moons-14 01ead9a385 labwc suspend 2026-08-05 02:10:36 +09:00
moons-14 328f11d0ed screencast 2026-08-05 01:40:35 +09:00
moons-14 d877ffc76c nh 2026-08-05 01:40:18 +09:00
moons-14 fe40adaeee activity watch 2026-08-05 00:59:32 +09:00
moons-14 991dde5305 noctalia patch 2026-08-05 00:40:02 +09:00
moons-14 96ce4d768c nani wayland 2026-08-05 00:16:12 +09:00
moons-14 30b1480e50 hazkey 2026-08-04 23:06:25 +09:00
moons-14 71011d7bd1 thunderbird 2026-08-04 23:06:11 +09:00
moons-14 9cced59e58 thunderbird 2026-08-04 23:06:01 +09:00
moons-14 20a601402e rate 2026-08-04 17:03:03 +09:00
moons-14 1b4a5fa5a2 wallpaper engine 2026-08-04 16:58:40 +09:00
moons-14 5fb55f2e6a open ghostty 2026-08-04 16:49:56 +09:00
moons-14 2a3f7ee6ff nani 2026-08-04 16:16:32 +09:00
moons-14 247db71f2d nani 2026-08-04 16:04:44 +09:00
moons-14 a44a83a587 handy 2026-08-04 15:43:12 +09:00
moons-14 1f1d46ea2a find-cursor 2026-08-04 15:26:43 +09:00
moons-14 29c4815b88 screenshot 2026-08-04 14:59:21 +09:00
moons-14 28a46d9990 skill 2026-08-04 14:42:31 +09:00
52 changed files with 1653 additions and 154 deletions
+1
View File
@@ -30,3 +30,4 @@
!/modules/ !/modules/
!/tests/ !/tests/
!/skills/
+5
View File
@@ -24,6 +24,11 @@ makes any statement here stale, update `AGENTS.md` in the same change.
| `overlays/` | Package replacements and additions | | `overlays/` | Package replacements and additions |
| `shells/` | Development shells | | `shells/` | Development shells |
| `flake/` | Supporting flake outputs such as formatters, checks, and Git hooks | | `flake/` | Supporting flake outputs such as formatters, checks, and Git hooks |
| `skills/` | Repository-specific Codex workflows that enforce this contract for recurring changes |
Before adding or materially extending an application or service, read and
follow `skills/add-application-or-service/SKILL.md`. `AGENTS.md` remains the
authoritative contract when the skill and repository ever disagree.
Use **unit** as the generic internal term for a Registry-managed component and Use **unit** as the generic internal term for a Registry-managed component and
**profile** for a unit that composes multiple units. Do not introduce a **profile** for a unit that composes multiple units. Do not introduce a
Generated
+248 -80
View File
@@ -91,6 +91,32 @@
} }
}, },
"bun2nix": { "bun2nix": {
"inputs": {
"flake-parts": "flake-parts_2",
"import-tree": "import-tree",
"nixpkgs": [
"handy",
"nixpkgs"
],
"systems": "systems_4",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1770895533,
"narHash": "sha256-v3QaK9ugy9bN9RXDnjw0i2OifKmz2NnKM82agtqm/UY=",
"owner": "nix-community",
"repo": "bun2nix",
"rev": "c843f477b15f51151f8c6bcc886954699440a6e1",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "2.0.8",
"repo": "bun2nix",
"type": "github"
}
},
"bun2nix_2": {
"inputs": { "inputs": {
"flake-parts": [ "flake-parts": [
"llm-agents", "llm-agents",
@@ -129,11 +155,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1785771258, "lastModified": 1785831553,
"narHash": "sha256-4qwZF7xxLBokf6gfIBkymFMQiJ36fbJ9SPkOAp5VI4I=", "narHash": "sha256-9q7WKbhq6bOGUdeo9Q/e3PmapJew0b2LCpaCio1X17k=",
"owner": "ilysenko", "owner": "ilysenko",
"repo": "codex-desktop-linux", "repo": "codex-desktop-linux",
"rev": "ab314923b5bf9b786a16cee67605d9eea2433d84", "rev": "7166d1153fd99647fb080605c8b2a8f22b50b08f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -292,6 +318,24 @@
} }
}, },
"flake-parts_2": { "flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
"lastModified": 1769996383,
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_3": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"llm-agents", "llm-agents",
@@ -312,7 +356,7 @@
"type": "github" "type": "github"
} }
}, },
"flake-parts_3": { "flake-parts_4": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"nixvim", "nixvim",
@@ -333,7 +377,7 @@
"type": "github" "type": "github"
} }
}, },
"flake-parts_4": { "flake-parts_5": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"stylix", "stylix",
@@ -419,11 +463,11 @@
"zon2nix": "zon2nix" "zon2nix": "zon2nix"
}, },
"locked": { "locked": {
"lastModified": 1785770443, "lastModified": 1785853205,
"narHash": "sha256-VZ9UpfgG5+Dy17eTLJ8ryV5e7Ki0/0JTA5MhWnfSriw=", "narHash": "sha256-rHvUeXh4druPSNPg2EMvY9F+av/NSQzjNLsf7s6lNnI=",
"owner": "ghostty-org", "owner": "ghostty-org",
"repo": "ghostty", "repo": "ghostty",
"rev": "863fc9531ae0b8e09b7103a9089dfc00319a7d9c", "rev": "594ee212bc3c048ffa06ba90623eaf207a4d145c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -470,6 +514,27 @@
"type": "gitlab" "type": "gitlab"
} }
}, },
"handy": {
"inputs": {
"bun2nix": "bun2nix",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1785763469,
"narHash": "sha256-YHfOchlPBKbBbeoaT1amrjKBHJTYMXmcR1Gm31E7TLE=",
"owner": "cjpais",
"repo": "Handy",
"rev": "b1b2d9f9072e55902a46ca6cc655e5893ed7a91d",
"type": "github"
},
"original": {
"owner": "cjpais",
"repo": "Handy",
"type": "github"
}
},
"home-manager": { "home-manager": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -533,6 +598,21 @@
"type": "github" "type": "github"
} }
}, },
"import-tree": {
"locked": {
"lastModified": 1763762820,
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
"owner": "vic",
"repo": "import-tree",
"rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"lanzaboote": { "lanzaboote": {
"inputs": { "inputs": {
"crane": "crane", "crane": "crane",
@@ -558,18 +638,18 @@
}, },
"llm-agents": { "llm-agents": {
"inputs": { "inputs": {
"bun2nix": "bun2nix", "bun2nix": "bun2nix_2",
"flake-parts": "flake-parts_2", "flake-parts": "flake-parts_3",
"nixpkgs": "nixpkgs_4", "nixpkgs": "nixpkgs_4",
"systems": "systems_4", "systems": "systems_5",
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix_2"
}, },
"locked": { "locked": {
"lastModified": 1785753922, "lastModified": 1785826862,
"narHash": "sha256-cRnp7DSvT4BwRI04YSbA0Ocn1iY6+fN9KTfnsADHm3I=", "narHash": "sha256-UOPf9uQHGoNqEMZ2kii8DqlPBd5dYR6uERzdU8wdroQ=",
"owner": "numtide", "owner": "numtide",
"repo": "llm-agents.nix", "repo": "llm-agents.nix",
"rev": "8a86f04f5cfbffc5b5d0b38ac24795ab4909b46b", "rev": "fd0508ef842609ad0c6f5af46c6f572214efebd9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -578,11 +658,29 @@
"type": "github" "type": "github"
} }
}, },
"nani-translate-linux": {
"inputs": {
"nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1785857772,
"narHash": "sha256-JMEQH9x8Pb9cHW4JbWfFBcqxsh4JkPDR5k8dCnphUTU=",
"ref": "refs/heads/main",
"rev": "9d6fec627a620841c27c7e67e8e7b5719ce9b465",
"revCount": 8,
"type": "git",
"url": "https://github.com/zunoser/nani-translate-linux.git"
},
"original": {
"type": "git",
"url": "https://github.com/zunoser/nani-translate-linux.git"
}
},
"niri-flake": { "niri-flake": {
"inputs": { "inputs": {
"niri-stable": "niri-stable", "niri-stable": "niri-stable",
"niri-unstable": "niri-unstable", "niri-unstable": "niri-unstable",
"nixpkgs": "nixpkgs_5", "nixpkgs": "nixpkgs_6",
"nixpkgs-stable": "nixpkgs-stable", "nixpkgs-stable": "nixpkgs-stable",
"xwayland-satellite-stable": "xwayland-satellite-stable", "xwayland-satellite-stable": "xwayland-satellite-stable",
"xwayland-satellite-unstable": "xwayland-satellite-unstable" "xwayland-satellite-unstable": "xwayland-satellite-unstable"
@@ -698,7 +796,7 @@
}, },
"nixos-hardware": { "nixos-hardware": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_6" "nixpkgs": "nixpkgs_7"
}, },
"locked": { "locked": {
"lastModified": 1785232496, "lastModified": 1785232496,
@@ -718,7 +816,7 @@
"nixos-wsl": { "nixos-wsl": {
"inputs": { "inputs": {
"flake-compat": "flake-compat_4", "flake-compat": "flake-compat_4",
"nixpkgs": "nixpkgs_7" "nixpkgs": "nixpkgs_8"
}, },
"locked": { "locked": {
"lastModified": 1784642409, "lastModified": 1784642409,
@@ -765,6 +863,21 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-lib_2": {
"locked": {
"lastModified": 1769909678,
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "72716169fe93074c333e8d0173151350670b824c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-libxml2": { "nixpkgs-libxml2": {
"locked": { "locked": {
"lastModified": 1751274312, "lastModified": 1751274312,
@@ -814,6 +927,19 @@
} }
}, },
"nixpkgs_10": { "nixpkgs_10": {
"locked": {
"lastModified": 1785571196,
"narHash": "sha256-xwSqxTsama0YTtS78+4YyCm6jJg09v78QWfP1cAyoVA=",
"rev": "148bab9c1c3c53136ecb44a6ea356a0ed5b39b06",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1045728.148bab9c1c3c/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
}
},
"nixpkgs_11": {
"locked": { "locked": {
"lastModified": 1770107345, "lastModified": 1770107345,
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=", "narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
@@ -829,7 +955,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_11": { "nixpkgs_12": {
"locked": { "locked": {
"lastModified": 1772542754, "lastModified": 1772542754,
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=", "narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
@@ -845,7 +971,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_12": { "nixpkgs_13": {
"locked": { "locked": {
"lastModified": 1778869304, "lastModified": 1778869304,
"narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=",
@@ -892,11 +1018,11 @@
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1785602060, "lastModified": 1785747939,
"narHash": "sha256-z7D96eESRM4CPV/XtwpwFn8IDdfLAmxz6lVWrGYXvR4=", "narHash": "sha256-D740uKsMbgsfK2oaDenJLLPIZfq7W0/g4KN/Fls8eKs=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a5cbcfe954791221bfffe2307f7d1a1bf61a871e", "rev": "104240a772428cc2e20d8fd86c9ddbb886bbaff2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -907,6 +1033,22 @@
} }
}, },
"nixpkgs_5": { "nixpkgs_5": {
"locked": {
"lastModified": 1785454630,
"narHash": "sha256-LQy14TZp77TwbQf40gg1V3jo8FwJG0jGDkAH+zRHqg8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1559d3daa3ecc813a650b79375ea61b6741b8746",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_6": {
"locked": { "locked": {
"lastModified": 1785692966, "lastModified": 1785692966,
"narHash": "sha256-vUfIeBEfpbAfZ5zjgIkYk7eHBeVfCYVjLbWnMkseYnk=", "narHash": "sha256-vUfIeBEfpbAfZ5zjgIkYk7eHBeVfCYVjLbWnMkseYnk=",
@@ -922,7 +1064,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_6": { "nixpkgs_7": {
"locked": { "locked": {
"lastModified": 1767892417, "lastModified": 1767892417,
"narHash": "sha256-8bW3q88CEg2u4hSP66Vf4lpbLonHz7hqDNBMcCY7E9U=", "narHash": "sha256-8bW3q88CEg2u4hSP66Vf4lpbLonHz7hqDNBMcCY7E9U=",
@@ -935,7 +1077,7 @@
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz" "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
} }
}, },
"nixpkgs_7": { "nixpkgs_8": {
"locked": { "locked": {
"lastModified": 1783776592, "lastModified": 1783776592,
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=", "narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
@@ -951,13 +1093,13 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_8": { "nixpkgs_9": {
"locked": { "locked": {
"lastModified": 1785599192, "lastModified": 1785734586,
"narHash": "sha256-dg4RTtDxnXY13UkJNdhmgTUTl0n/IJBlCigfO7nutZw=", "narHash": "sha256-ODZkEK9Gy50yg6h98u7KkitZ3oc/uuTFK00bh1CRdNA=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "6d65bfc1bcef2ef39a239d38e577e92a89fb0f07", "rev": "531670d871c0e29724a02f3cbcac170adc65b58c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -967,26 +1109,13 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_9": {
"locked": {
"lastModified": 1785571196,
"narHash": "sha256-xwSqxTsama0YTtS78+4YyCm6jJg09v78QWfP1cAyoVA=",
"rev": "148bab9c1c3c53136ecb44a6ea356a0ed5b39b06",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1045728.148bab9c1c3c/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
}
},
"nixvim": { "nixvim": {
"inputs": { "inputs": {
"flake-parts": "flake-parts_3", "flake-parts": "flake-parts_4",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"systems": "systems_5" "systems": "systems_6"
}, },
"locked": { "locked": {
"lastModified": 1782919967, "lastModified": 1782919967,
@@ -1005,14 +1134,14 @@
}, },
"noctalia": { "noctalia": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_9" "nixpkgs": "nixpkgs_10"
}, },
"locked": { "locked": {
"lastModified": 1785767538, "lastModified": 1785809412,
"narHash": "sha256-9foQ31PSKpIu8UWGalt+4G5o4TU21g4a1qwMwJmeOy4=", "narHash": "sha256-QbFIduUhpNIucJy/WcwRxsvTQocnwIXlSwnYEjmGMSQ=",
"owner": "noctalia-dev", "owner": "noctalia-dev",
"repo": "noctalia", "repo": "noctalia",
"rev": "50f0f095053a4b5c10874c4e7080f55dc15d3b38", "rev": "e41c99439605df6d2fa62409139f96ded6ac7345",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -1077,24 +1206,26 @@
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"ghostty": "ghostty", "ghostty": "ghostty",
"git-hooks-nix": "git-hooks-nix", "git-hooks-nix": "git-hooks-nix",
"handy": "handy",
"home-manager": "home-manager_2", "home-manager": "home-manager_2",
"lanzaboote": "lanzaboote", "lanzaboote": "lanzaboote",
"llm-agents": "llm-agents", "llm-agents": "llm-agents",
"nani-translate-linux": "nani-translate-linux",
"niri-flake": "niri-flake", "niri-flake": "niri-flake",
"nix-darwin": "nix-darwin", "nix-darwin": "nix-darwin",
"nix-hazkey": "nix-hazkey", "nix-hazkey": "nix-hazkey",
"nix-index-database": "nix-index-database", "nix-index-database": "nix-index-database",
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixos-wsl": "nixos-wsl", "nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_8", "nixpkgs": "nixpkgs_9",
"nixpkgs-unstable": "nixpkgs-unstable", "nixpkgs-unstable": "nixpkgs-unstable",
"nixvim": "nixvim", "nixvim": "nixvim",
"noctalia": "noctalia", "noctalia": "noctalia",
"services-flake": "services-flake", "services-flake": "services-flake",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
"stylix": "stylix", "stylix": "stylix",
"systems": "systems_7", "systems": "systems_8",
"treefmt-nix": "treefmt-nix_2", "treefmt-nix": "treefmt-nix_3",
"vicinae": "vicinae", "vicinae": "vicinae",
"vicinae-extensions": "vicinae-extensions" "vicinae-extensions": "vicinae-extensions"
} }
@@ -1157,7 +1288,7 @@
}, },
"soulver-cpp": { "soulver-cpp": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_12", "nixpkgs": "nixpkgs_13",
"nixpkgs-libxml2": "nixpkgs-libxml2" "nixpkgs-libxml2": "nixpkgs-libxml2"
}, },
"locked": { "locked": {
@@ -1181,24 +1312,24 @@
"base16-helix": "base16-helix", "base16-helix": "base16-helix",
"base16-vim": "base16-vim", "base16-vim": "base16-vim",
"firefox-gnome-theme": "firefox-gnome-theme", "firefox-gnome-theme": "firefox-gnome-theme",
"flake-parts": "flake-parts_4", "flake-parts": "flake-parts_5",
"gnome-shell": "gnome-shell", "gnome-shell": "gnome-shell",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"nur": "nur", "nur": "nur",
"systems": "systems_6", "systems": "systems_7",
"tinted-kitty": "tinted-kitty", "tinted-kitty": "tinted-kitty",
"tinted-schemes": "tinted-schemes", "tinted-schemes": "tinted-schemes",
"tinted-tmux": "tinted-tmux", "tinted-tmux": "tinted-tmux",
"tinted-zed": "tinted-zed" "tinted-zed": "tinted-zed"
}, },
"locked": { "locked": {
"lastModified": 1785680312, "lastModified": 1785794750,
"narHash": "sha256-e26BuraMqnara0VXVk9nsQLgGC+8nvEgYNc6odv5Yi8=", "narHash": "sha256-OqIrGVL7AX462ISyJFAqjbqTc1RZlBkVHCa4dwPEZU4=",
"owner": "nix-community", "owner": "nix-community",
"repo": "stylix", "repo": "stylix",
"rev": "04b284060ffa638f080ab1b2d05cf1f236217995", "rev": "cb5eb3a7343faba61fd694fac8040a326485a339",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -1222,6 +1353,21 @@
"type": "github" "type": "github"
} }
}, },
"systems_10": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": { "systems_2": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
@@ -1284,6 +1430,21 @@
} }
}, },
"systems_6": { "systems_6": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_7": {
"locked": { "locked": {
"lastModified": 1774449309, "lastModified": 1774449309,
"narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=",
@@ -1299,21 +1460,6 @@
"type": "github" "type": "github"
} }
}, },
"systems_7": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_8": { "systems_8": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
@@ -1409,6 +1555,28 @@
} }
}, },
"treefmt-nix": { "treefmt-nix": {
"inputs": {
"nixpkgs": [
"handy",
"bun2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1770228511,
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
},
"treefmt-nix_2": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"llm-agents", "llm-agents",
@@ -1429,9 +1597,9 @@
"type": "github" "type": "github"
} }
}, },
"treefmt-nix_2": { "treefmt-nix_3": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_10" "nixpkgs": "nixpkgs_11"
}, },
"locked": { "locked": {
"lastModified": 1785360170, "lastModified": 1785360170,
@@ -1449,16 +1617,16 @@
}, },
"vicinae": { "vicinae": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_11", "nixpkgs": "nixpkgs_12",
"soulver-cpp": "soulver-cpp", "soulver-cpp": "soulver-cpp",
"systems": "systems_8" "systems": "systems_9"
}, },
"locked": { "locked": {
"lastModified": 1785733880, "lastModified": 1785823793,
"narHash": "sha256-Rv4rVFSvxF7ViOQOrv+N8IEvweS5MCqUM2fnaQVIR+w=", "narHash": "sha256-iGKyp+peQzXI0T9LzYmRHr2tevkbhF5GfzDlVldVm2Q=",
"owner": "vicinaehq", "owner": "vicinaehq",
"repo": "vicinae", "repo": "vicinae",
"rev": "61bd7cd5c9cdd6d8df6e6d34531beb344219ff71", "rev": "b3130be7b480817d2255b4781476a91593267afb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -1473,7 +1641,7 @@
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"systems": "systems_9", "systems": "systems_10",
"vicinae": "vicinae_2" "vicinae": "vicinae_2"
}, },
"locked": { "locked": {
+9
View File
@@ -38,6 +38,12 @@
url = "github:ghostty-org/ghostty"; url = "github:ghostty-org/ghostty";
}; };
# Speech to text
handy = {
url = "github:cjpais/Handy";
inputs.nixpkgs.follows = "nixpkgs";
};
# Shell / Launcher # Shell / Launcher
vicinae.url = "github:vicinaehq/vicinae"; vicinae.url = "github:vicinaehq/vicinae";
@@ -104,6 +110,9 @@
url = "github:nix-community/browser-previews"; url = "github:nix-community/browser-previews";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nani-translate-linux.url = "git+https://github.com/zunoser/nani-translate-linux.git";
}; };
outputs = outputs =
@@ -0,0 +1,8 @@
{
homebrew.casks = [ "activitywatch" ];
launchd.agents.activitywatch = {
command = "/usr/bin/open -gja ActivityWatch";
serviceConfig.RunAtLoad = true;
};
}
@@ -0,0 +1,11 @@
{ pkgs, ... }:
{
services.activitywatch = {
enable = true;
watchers.aw-awatcher = {
package = pkgs.awatcher;
executable = "awatcher";
};
};
}
@@ -0,0 +1,3 @@
{
description = "ActivityWatch automated time tracker";
}
+7 -2
View File
@@ -1,7 +1,12 @@
{ pkgs, ... }: { inputs, pkgs, ... }:
{ {
services.hazkey.enable = true; services.hazkey = {
enable = true;
server.package =
inputs.nix-hazkey.packages.${pkgs.stdenv.hostPlatform.system}.hazkey-server.override
{ enableVulkan = true; };
};
i18n.inputMethod = { i18n.inputMethod = {
enable = true; enable = true;
@@ -6,6 +6,7 @@ _: {
theme = "dracula"; theme = "dracula";
background-blur-radius = 20; background-blur-radius = 20;
background-opacity = 0.9; background-opacity = 0.9;
background-opacity-cells = true;
font-family = "BlexMono Nerd Font Mono"; font-family = "BlexMono Nerd Font Mono";
mouse-hide-while-typing = true; mouse-hide-while-typing = true;
window-decoration = "auto"; window-decoration = "auto";
+8
View File
@@ -0,0 +1,8 @@
{
homebrew.casks = [ "handy" ];
launchd.agents.handy = {
command = "/usr/bin/open -gja Handy --args --start-hidden";
serviceConfig.RunAtLoad = true;
};
}
+74
View File
@@ -0,0 +1,74 @@
{
config,
inputs,
lib,
pkgs,
...
}:
let
handy = inputs.handy.packages.${pkgs.stdenv.hostPlatform.system}.handy;
settingsFile = "${config.xdg.configHome}/com.pais.handy/settings_store.json";
in
{
home.packages = [
handy
pkgs.wtype
];
# Handy has no disabled-shortcut setting. Empty bindings are rejected before
# registration, leaving niri and labwc as the sole owners of Ctrl+Space.
home.activation.disableHandyGlobalShortcuts = {
after = [ "writeBoundary" ];
before = [ ];
data = ''
settingsFile=${lib.escapeShellArg settingsFile}
mkdir -p "$(dirname "$settingsFile")"
if [ -f "$settingsFile" ]; then
${lib.getExe pkgs.jq} \
'.settings.bindings.transcribe.current_binding = ""
| .settings.bindings.transcribe_with_post_process.current_binding = ""' \
"$settingsFile" > "$settingsFile.tmp"
else
${lib.getExe pkgs.jq} -n '
{
settings: {
bindings: {
transcribe: {
id: "transcribe",
name: "Transcribe",
description: "Converts your speech into text.",
default_binding: "ctrl+space",
current_binding: ""
},
transcribe_with_post_process: {
id: "transcribe_with_post_process",
name: "Transcribe with Post-Processing",
description: "Converts your speech into text and applies AI post-processing.",
default_binding: "ctrl+shift+space",
current_binding: ""
}
}
}
}
' > "$settingsFile.tmp"
fi
mv "$settingsFile.tmp" "$settingsFile"
'';
};
systemd.user.services.handy = {
Unit = {
Description = "Handy speech-to-text";
After = [ "graphical-session.target" ];
PartOf = [ "graphical-session.target" ];
};
Service = {
ExecStart = "${lib.getExe handy} --start-hidden";
Restart = "on-failure";
RestartSec = 5;
};
Install.WantedBy = [ "graphical-session.target" ];
};
}
+3
View File
@@ -0,0 +1,3 @@
{
description = "Handy offline speech-to-text";
}
+8 -7
View File
@@ -1,7 +1,5 @@
{ lib, pkgs, ... }: { lib, pkgs, ... }:
let let
screenshot = import ./screenshot.nix { inherit pkgs; };
systemctl = lib.getExe' pkgs.systemd "systemctl"; systemctl = lib.getExe' pkgs.systemd "systemctl";
keybind = key: actionAttrs: { keybind = key: actionAttrs: {
@@ -62,8 +60,6 @@ let
}; };
in in
{ {
home.packages = [ screenshot ];
wayland.windowManager.labwc = { wayland.windowManager.labwc = {
enable = true; enable = true;
# NixOS owns the package so Home Manager only generates the user config. # NixOS owns the package so Home Manager only generates the user config.
@@ -117,9 +113,9 @@ in
(snapToEdge "W-C-Up" "up") (snapToEdge "W-C-Up" "up")
(snapToEdge "W-C-Down" "down") (snapToEdge "W-C-Down" "down")
(confirmAction "W-S-e" "Exit labwc?" "Exit") (confirmAction "W-S-e" "Exit labwc?" "Exit")
(execute "Print" "labwc-screenshot region") (execute "Print" "screenshot region")
(execute "C-Print" "labwc-screenshot output") (execute "C-Print" "screenshot output")
(execute "A-Print" "labwc-screenshot all") (execute "A-Print" "screenshot all")
# spawn applications (sync with niri modules/applications/niri/home.nix) # spawn applications (sync with niri modules/applications/niri/home.nix)
(execute "W-t" "ghostty") (execute "W-t" "ghostty")
@@ -128,8 +124,13 @@ in
(execute "W-e" "nautilus --new-window") (execute "W-e" "nautilus --new-window")
(execute "W-l" "loginctl lock-session") (execute "W-l" "loginctl lock-session")
(execute "W-v" "vicinae vicinae://launch/clipboard/history?toggle=true") (execute "W-v" "vicinae vicinae://launch/clipboard/history?toggle=true")
(execute "W-j" "nani-translate-primary")
(execute "W-S-j" "normcap-translate")
(execute "W-C-j" "${lib.getExe' pkgs.xdg-utils "xdg-open"} naniapp://translate")
(execute "W-space" "ghostty +toggle-quick-terminal") (execute "W-space" "ghostty +toggle-quick-terminal")
(execute "W-p" "wdisplays") (execute "W-p" "wdisplays")
(execute "W-z" "wl-find-cursor -c 0xCCFF453A -s 160 -d 1200")
(execute "C-space" "handy --toggle-transcription")
# Function keys (sync with niri modules/applications/niri/home.nix) # Function keys (sync with niri modules/applications/niri/home.nix)
(execute "XF86AudioRaiseVolume" "noctalia msg volume-up") (execute "XF86AudioRaiseVolume" "noctalia msg volume-up")
+5 -1
View File
@@ -1,5 +1,9 @@
{ {
description = "labwc Wayland stacking compositor"; description = "labwc Wayland stacking compositor";
includes = [ "systems.wayland" ]; includes = [
"systems.wayland"
"applications.screenshot"
"applications.wl-find-cursor"
];
} }
+26
View File
@@ -1,10 +1,36 @@
{ {
inputs,
lib, lib,
pkgs,
... ...
}: }:
let
unstable = inputs.nixpkgs-unstable.legacyPackages.${pkgs.stdenv.hostPlatform.system};
labwc = unstable.labwc.overrideAttrs (oldAttrs: {
patches = (oldAttrs.patches or [ ]) ++ [
./patches/remove-ext-workspace-output-on-destroy.patch
];
});
in
{ {
programs.labwc = { programs.labwc = {
enable = true; enable = true;
package = labwc;
};
xdg.portal.config.labwc.default = [
"wlr"
"gtk"
];
# xdg-desktop-portal-wlr implements screencasting for wlroots compositors.
# Keep it with Labwc: Niri uses xdg-desktop-portal-gnome instead.
xdg.portal.wlr = {
enable = true;
settings.screencast = {
chooser_type = "dmenu";
chooser_cmd = "${pkgs.fuzzel}/bin/fuzzel --dmenu";
};
}; };
# NixOS decides whether a graphical session manages graphical-session.target # NixOS decides whether a graphical session manages graphical-session.target
@@ -0,0 +1,28 @@
From: Codex <[email protected]>
Subject: [PATCH] output: remove destroyed output from ext-workspace group
The DRM backend destroys and recreates outputs when a session is paused and
resumed. Remove the output from the ext-workspace group before wlroots
finishes it, otherwise the group's output_bind listener remains attached and
wlr_output_finish() aborts.
---
src/output.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/output.c b/src/output.c
index 2eab8ec..f19c3ff 100644
--- a/src/output.c
+++ b/src/output.c
@@ -277,6 +277,9 @@ handle_output_destroy(struct wl_listener *listener, void *data)
struct seat *seat = &server.seat;
regions_evacuate_output(output);
regions_destroy(seat, &output->regions);
+ wlr_ext_workspace_group_handle_v1_output_leave(
+ server.workspaces.ext_group, output->wlr_output);
+
if (seat->overlay.active.output == output) {
overlay_finish(seat);
}
--
2.51.0
-50
View File
@@ -1,50 +0,0 @@
{ pkgs }:
pkgs.writeShellApplication {
name = "labwc-screenshot";
runtimeInputs = with pkgs; [
coreutils
grim
slurp
wl-clipboard
xdg-user-dirs
];
text = ''
mode="''${1:-region}"
pictures_dir="$(xdg-user-dir PICTURES)"
if [[ -z "$pictures_dir" ]]; then
pictures_dir="$HOME/Pictures"
fi
output_dir="$pictures_dir/Screenshots"
output_file="$output_dir/Screenshot from $(date '+%Y-%m-%d %H-%M-%S').png"
mkdir -p "$output_dir"
case "$mode" in
region)
geometry="$(slurp)" || exit 0
grim -g "$geometry" "$output_file"
;;
output)
geometry="$(slurp -o)" || exit 0
grim -g "$geometry" "$output_file"
;;
all)
grim "$output_file"
;;
*)
echo "Unknown screenshot mode: $mode" >&2
exit 2
;;
esac
wl-copy --type image/png < "$output_file"
'';
}
@@ -0,0 +1,167 @@
# shellcheck shell=bash
set -o errexit -o nounset -o pipefail
playlist_file="$1"
default_id="$2"
configuration_id="$3"
shift 3
state_dir="${XDG_RUNTIME_DIR:?}/linux-wallpaperengine"
state_file="$state_dir/current"
usage() {
printf '%s\n' \
'Usage: wallpaperengine-wallpaper COMMAND [ID]' \
'' \
'Commands:' \
' select ID Select a declaratively configured ID for this session' \
' next Select the next configured ID' \
' previous Select the previous configured ID' \
' list List configured IDs and mark the selected one' \
' current Print the selected ID' \
' restart Restart the selected wallpaper' \
' stop Stop Wallpaper Engine for this session'
}
load_playlist() {
mapfile -t wallpaper_ids < <(awk '/^[0-9]+$/ && !seen[$0]++' "$playlist_file")
if [ "${#wallpaper_ids[@]}" -eq 0 ]; then
echo "wallpaperengine-wallpaper: no wallpaper IDs are configured in settings.nix" >&2
exit 1
fi
}
contains_id() {
local candidate="$1"
local id
for id in "${wallpaper_ids[@]}"; do
[ "$id" = "$candidate" ] && return 0
done
return 1
}
current_id() {
local saved_configuration=""
local saved_id=""
local -a saved_state=()
if [ -s "$state_file" ]; then
mapfile -t saved_state <"$state_file"
saved_configuration="${saved_state[0]:-}"
saved_id="${saved_state[1]:-}"
fi
if [ "$saved_configuration" = "$configuration_id" ] && contains_id "$saved_id"; then
printf '%s\n' "$saved_id"
else
printf '%s\n' "$default_id"
fi
}
select_id() {
local id="$1"
if ! contains_id "$id"; then
echo "wallpaperengine-wallpaper: ID is not declared in settings.nix: $id" >&2
exit 2
fi
mkdir -p "$state_dir"
printf '%s\n%s\n' "$configuration_id" "$id" >"$state_file"
systemctl --user restart linux-wallpaperengine.service
}
cycle() {
local step="$1"
local current
local index=0
local next_index
current=$(current_id)
for i in "${!wallpaper_ids[@]}"; do
if [ "${wallpaper_ids[$i]}" = "$current" ]; then
index="$i"
break
fi
done
next_index=$(((index + step + ${#wallpaper_ids[@]}) % ${#wallpaper_ids[@]}))
select_id "${wallpaper_ids[$next_index]}"
}
load_playlist
command="${1:-}"
if [ -z "$command" ]; then
usage
exit 2
fi
shift
case "$command" in
select)
[ "$#" -eq 1 ] || {
usage >&2
exit 2
}
select_id "$1"
;;
next)
[ "$#" -eq 0 ] || {
usage >&2
exit 2
}
cycle 1
;;
previous)
[ "$#" -eq 0 ] || {
usage >&2
exit 2
}
cycle -1
;;
list)
[ "$#" -eq 0 ] || {
usage >&2
exit 2
}
current=$(current_id)
for id in "${wallpaper_ids[@]}"; do
if [ "$id" = "$current" ]; then
printf '* %s\n' "$id"
else
printf ' %s\n' "$id"
fi
done
;;
current)
[ "$#" -eq 0 ] || {
usage >&2
exit 2
}
current_id
;;
restart)
[ "$#" -eq 0 ] || {
usage >&2
exit 2
}
systemctl --user restart linux-wallpaperengine.service
;;
stop)
[ "$#" -eq 0 ] || {
usage >&2
exit 2
}
systemctl --user stop linux-wallpaperengine.service
;;
help | -h | --help)
usage
;;
*)
usage >&2
exit 2
;;
esac
@@ -0,0 +1,57 @@
# shellcheck shell=bash
set -o errexit -o nounset -o pipefail
playlist_file="$1"
default_id="$2"
configuration_id="$3"
assets_dir="$4"
workshop_dir="$5"
scaling="$6"
shift 6
state_file="${XDG_RUNTIME_DIR:?}/linux-wallpaperengine/current"
wallpaper_id="$default_id"
saved_configuration=""
saved_id=""
saved_state=()
outputs=()
if [ -s "$state_file" ]; then
mapfile -t saved_state <"$state_file"
saved_configuration="${saved_state[0]:-}"
saved_id="${saved_state[1]:-}"
if [ "$saved_configuration" = "$configuration_id" ] &&
awk -v id="$saved_id" '$0 == id { found = 1 } END { exit !found }' "$playlist_file"; then
wallpaper_id="$saved_id"
fi
fi
wallpaper_path="$workshop_dir/$wallpaper_id"
if [ ! -d "$wallpaper_path" ]; then
echo "linux-wallpaperengine: missing declared wallpaper: $wallpaper_path" >&2
exit 1
fi
if [ ! -d "$assets_dir" ]; then
echo "linux-wallpaperengine: missing Wallpaper Engine assets: $assets_dir" >&2
exit 1
fi
mapfile -t outputs < <(wlr-randr --json | jq -r '.[] | select(.enabled == true) | .name')
if [ "${#outputs[@]}" -eq 0 ]; then
echo "linux-wallpaperengine: no enabled Wayland outputs were detected" >&2
exit 1
fi
engine_args=("$@" --assets-dir "$assets_dir")
for output in "${outputs[@]}"; do
engine_args+=(
--screen-root "$output"
--bg "$wallpaper_path"
--scaling "$scaling"
)
done
exec linux-wallpaperengine "${engine_args[@]}"
@@ -0,0 +1,158 @@
{
config,
lib,
pkgs,
...
}:
let
settings = import ../settings.nix;
inherit (settings)
assetsDirectory
fps
mute
scaling
selectedWallpaperId
switchIntervalSeconds
wallpaperIds
workshopDirectory
;
resolveHomePath =
path:
if lib.hasPrefix "~/" path then
"${config.home.homeDirectory}/${lib.removePrefix "~/" path}"
else if lib.hasPrefix "/" path then
path
else
"${config.home.homeDirectory}/${path}";
hasWallpapers = wallpaperIds != [ ];
defaultWallpaperId =
if selectedWallpaperId != null then
selectedWallpaperId
else if hasWallpapers then
lib.head wallpaperIds
else
"";
configurationId = builtins.hashString "sha256" (builtins.toJSON settings);
resolvedAssetsDirectory = resolveHomePath assetsDirectory;
resolvedWorkshopDirectory = resolveHomePath workshopDirectory;
playlist = pkgs.writeText "linux-wallpaperengine-playlist" (
lib.concatMapStringsSep "\n" (id: id) wallpaperIds + "\n"
);
engineArguments = lib.optional mute "--silent" ++ [
"--fps"
(toString fps)
];
controller = pkgs.writeShellApplication {
name = "wallpaperengine-wallpaper";
runtimeInputs = [
pkgs.coreutils
pkgs.gawk
pkgs.systemd
];
text = ''
exec ${lib.getExe pkgs.bash} ${./controller.sh} \
${lib.escapeShellArg playlist} \
${lib.escapeShellArg defaultWallpaperId} \
${lib.escapeShellArg configurationId} \
"$@"
'';
};
launcher = pkgs.writeShellApplication {
name = "linux-wallpaperengine-launcher";
runtimeInputs = [
pkgs.coreutils
pkgs.gawk
pkgs.jq
pkgs.linux-wallpaperengine
pkgs.wlr-randr
];
text = ''
exec ${lib.getExe pkgs.bash} ${./launcher.sh} \
${lib.escapeShellArg playlist} \
${lib.escapeShellArg defaultWallpaperId} \
${lib.escapeShellArg configurationId} \
${lib.escapeShellArg resolvedAssetsDirectory} \
${lib.escapeShellArg resolvedWorkshopDirectory} \
${lib.escapeShellArg scaling} \
${lib.escapeShellArgs engineArguments}
'';
};
in
assert lib.assertMsg (lib.all (
id: builtins.isString id && builtins.match "[0-9]+" id != null
) wallpaperIds) "linux-wallpaperengine: wallpaperIds must contain only numeric strings";
assert lib.assertMsg (
lib.unique wallpaperIds == wallpaperIds
) "linux-wallpaperengine: wallpaperIds must not contain duplicates";
assert lib.assertMsg (
selectedWallpaperId == null || builtins.elem selectedWallpaperId wallpaperIds
) "linux-wallpaperengine: selectedWallpaperId must be null or a member of wallpaperIds";
assert lib.assertMsg (
switchIntervalSeconds == null || (builtins.isInt switchIntervalSeconds && switchIntervalSeconds > 0)
) "linux-wallpaperengine: switchIntervalSeconds must be null or a positive integer";
assert lib.assertMsg (
builtins.isInt fps && fps > 0
) "linux-wallpaperengine: fps must be a positive integer";
assert lib.assertMsg (
builtins.isString assetsDirectory && assetsDirectory != ""
) "linux-wallpaperengine: assetsDirectory must be a non-empty string";
assert lib.assertMsg (
builtins.isString workshopDirectory && workshopDirectory != ""
) "linux-wallpaperengine: workshopDirectory must be a non-empty string";
assert lib.assertMsg (builtins.elem scaling [
"default"
"fill"
"fit"
"stretch"
]) "linux-wallpaperengine: scaling must be default, fill, fit, or stretch";
{
home.packages = [
controller
pkgs.linux-wallpaperengine
];
systemd.user.services = lib.optionalAttrs hasWallpapers {
linux-wallpaperengine = {
Unit = {
Description = "Linux Wallpaper Engine";
After = [ "graphical-session.target" ];
PartOf = [ "graphical-session.target" ];
};
Service = {
ExecStart = lib.getExe launcher;
Restart = "on-abnormal";
};
Install.WantedBy = [ "graphical-session.target" ];
};
linux-wallpaperengine-switch = {
Unit.Description = "Switch Linux Wallpaper Engine wallpaper";
Service = {
Type = "oneshot";
ExecStart = "${lib.getExe controller} next";
};
};
};
systemd.user.timers =
lib.optionalAttrs
(hasWallpapers && builtins.length wallpaperIds > 1 && switchIntervalSeconds != null)
{
linux-wallpaperengine-switch = {
Unit = {
Description = "Periodically switch Linux Wallpaper Engine wallpaper";
PartOf = [ "graphical-session.target" ];
};
Timer = {
OnActiveSec = "${toString switchIntervalSeconds}s";
OnUnitActiveSec = "${toString switchIntervalSeconds}s";
Unit = "linux-wallpaperengine-switch.service";
};
Install.WantedBy = [ "graphical-session.target" ];
};
};
}
@@ -0,0 +1,3 @@
{
description = "Wallpaper Engine backgrounds for Linux";
}
@@ -0,0 +1,23 @@
{
assetsDirectory = "~/.local/share/Steam/steamapps/common/wallpaper_engine/assets";
workshopDirectory = "~/.local/share/Steam/steamapps/workshop/content/431960";
wallpaperIds = [
"2833810156"
"2834355367"
"2836628501"
"2845095254"
"2859848175"
"2861661119"
"2982896307"
];
# null selects the first ID above.
selectedWallpaperId = null;
# Set a number such as 300 to rotate through multiple IDs.
switchIntervalSeconds = 300;
fps = 30;
mute = true;
scaling = "fill";
}
+6
View File
@@ -0,0 +1,6 @@
{
homebrew = {
enable = true;
casks = [ "nani" ];
};
}
+5
View File
@@ -0,0 +1,5 @@
_: {
programs.naniTranslateLinux.enable = true;
xdg.mimeApps.defaultApplications."x-scheme-handler/naniapp" = "nani.desktop";
}
+8
View File
@@ -0,0 +1,8 @@
{ inputs, ... }:
{
description = "Nani Translate application";
imports.home = [
inputs.nani-translate-linux.homeManagerModules.default
];
}
@@ -25,6 +25,7 @@ in
systemd.user.sessionVariables.NAUTILUS_4_EXTENSION_DIR = nautilusExtensionDir; systemd.user.sessionVariables.NAUTILUS_4_EXTENSION_DIR = nautilusExtensionDir;
xdg.dataFile."nautilus-python/extensions/open-in-editor.py".text = '' xdg.dataFile."nautilus-python/extensions/open-in-editor.py".text = ''
import os
import subprocess import subprocess
from gi.repository import GObject, Nautilus from gi.repository import GObject, Nautilus
@@ -75,6 +76,12 @@ in
return paths return paths
@staticmethod
def get_working_directory(paths):
path = paths[0]
return path if os.path.isdir(path) else os.path.dirname(path)
@staticmethod @staticmethod
def launch(_item, command): def launch(_item, command):
subprocess.Popen( subprocess.Popen(
@@ -101,6 +108,22 @@ in
items.append(item) items.append(item)
item = Nautilus.MenuItem(
name=f"OpenInEditor::{context}::ghostty",
label="Ghostty で開く",
)
item.connect(
"activate",
self.launch,
[
"${lib.getExe pkgs.ghostty}",
f"--working-directory={self.get_working_directory(paths)}",
],
)
items.append(item)
return items return items
def get_file_items(self, files): def get_file_items(self, files):
+34
View File
@@ -5,4 +5,38 @@
NH_FLAKE = "${config.home.homeDirectory}/dotfiles"; NH_FLAKE = "${config.home.homeDirectory}/dotfiles";
NH_SHOW_ACTIVATION_LOGS = "1"; NH_SHOW_ACTIVATION_LOGS = "1";
}; };
programs.zsh.initContent = ''
export SUDO_PROMPT=$'\a[sudo] authenticate for %u: '
nh() {
local notify=false
local argument
case "$1:$2" in
os:switch | os:build) notify=true ;;
esac
for argument in "$@"; do
if [[ "$argument" == "--update" ]]; then
notify=true
break
fi
done
command nh "$@"
local status=$?
if [[ "$notify" == true ]]; then
printf '\a'
if ((status == 0)); then
print -P "%F{green}nh completed%f"
else
print -P "%F{red}nh failed (exit $status)%f"
fi
fi
return "$status"
}
'';
} }
@@ -1,3 +1,4 @@
# niri のデフォルトキーバインド。編集しないこと。
{ {
"Mod+Shift+Slash".action.show-hotkey-overlay = { }; "Mod+Shift+Slash".action.show-hotkey-overlay = { };
+66
View File
@@ -1,8 +1,24 @@
{ lib, pkgs, ... }: { lib, pkgs, ... }:
let let
keybindings = import ./keybindings.nix; keybindings = import ./keybindings.nix;
naniTranslatePrimary = pkgs.writeShellApplication {
name = "nani-translate-primary";
runtimeInputs = with pkgs; [
jq
wl-clipboard
xdg-utils
];
text = ''
selected_text="$(wl-paste --primary --no-newline)" || exit 0
[ -n "$selected_text" ] || exit 0
encoded_text="$(printf '%s' "$selected_text" | jq -sRr @uri)"
exec xdg-open "naniapp://translate?source=$encoded_text"
'';
};
in in
{ {
home.packages = [ naniTranslatePrimary ];
programs.niri.settings = { programs.niri.settings = {
binds = keybindings // { binds = keybindings // {
@@ -41,6 +57,19 @@ in
hotkey-overlay.title = "Move Window to Monitor Down"; hotkey-overlay.title = "Move Window to Monitor Down";
}; };
"Print".action.spawn = [
"screenshot"
"region"
];
"Ctrl+Print".action.spawn = [
"screenshot"
"output"
];
"Alt+Print".action.spawn = [
"screenshot"
"all"
];
# spawn applications (sync with labwc modules/applications/labwc/home.nix) # spawn applications (sync with labwc modules/applications/labwc/home.nix)
"Mod+T" = { "Mod+T" = {
action.spawn = "ghostty"; action.spawn = "ghostty";
@@ -83,6 +112,24 @@ in
]; ];
hotkey-overlay.title = "Clipboard History"; hotkey-overlay.title = "Clipboard History";
}; };
"Mod+J" = {
repeat = false;
action.spawn = [ "nani-translate-primary" ];
hotkey-overlay.title = "Translate Primary Selection";
};
"Mod+Shift+J" = {
repeat = false;
action.spawn = [ "normcap-translate" ];
hotkey-overlay.title = "OCR and Translate with Nani";
};
"Mod+Ctrl+J" = {
repeat = false;
action.spawn = [
(lib.getExe' pkgs.xdg-utils "xdg-open")
"naniapp://translate"
];
hotkey-overlay.title = "Open Nani Translate";
};
"Mod+Space" = { "Mod+Space" = {
action.spawn = [ action.spawn = [
"ghostty" "ghostty"
@@ -94,6 +141,25 @@ in
action.spawn = "wdisplays"; action.spawn = "wdisplays";
hotkey-overlay.title = "Display Settings: wdisplays"; hotkey-overlay.title = "Display Settings: wdisplays";
}; };
"Mod+Z" = {
action.spawn = [
"wl-find-cursor"
"-c"
"0xCCFF453A"
"-s"
"160"
"-d"
"1200"
];
hotkey-overlay.title = "Find Cursor";
};
"Ctrl+Space" = {
action.spawn = [
"handy"
"--toggle-transcription"
];
hotkey-overlay.title = "Toggle Handy Transcription";
};
# Function keys (sync with labwc modules/applications/labwc/home.nix) # Function keys (sync with labwc modules/applications/labwc/home.nix)
"XF86AudioRaiseVolume".action.spawn = [ "XF86AudioRaiseVolume".action.spawn = [
+5 -1
View File
@@ -2,7 +2,11 @@
{ {
description = "niri Wayland compositor"; description = "niri Wayland compositor";
includes = [ "systems.wayland" ]; includes = [
"systems.wayland"
"applications.screenshot"
"applications.wl-find-cursor"
];
imports = { imports = {
nixos = [ nixos = [
+14 -2
View File
@@ -1,6 +1,7 @@
{ {
lib, lib,
pkgs, pkgs,
inputs,
... ...
}: }:
let let
@@ -12,6 +13,14 @@ let
--replace-fail "@codexbarPath@" "${lib.getExe codexbar}" --replace-fail "@codexbarPath@" "${lib.getExe codexbar}"
''; '';
noctaliaPatched =
inputs.noctalia.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs
(oldAttrs: {
patches = (oldAttrs.patches or [ ]) ++ [
./patches/window-switcher-labwc.patch
];
});
wallpapers = pkgs.fetchFromGitHub { wallpapers = pkgs.fetchFromGitHub {
owner = "moons-14"; owner = "moons-14";
repo = "wallpapers"; repo = "wallpapers";
@@ -41,6 +50,8 @@ in
programs.noctalia = { programs.noctalia = {
enable = true; enable = true;
package = noctaliaPatched;
systemd.enable = true; systemd.enable = true;
settings = { settings = {
@@ -171,10 +182,11 @@ in
"org.gnome.Nautilus" "org.gnome.Nautilus"
"org.gnome.TextEditor" "org.gnome.TextEditor"
"com.mitchellh.ghostty" "com.mitchellh.ghostty"
"thunderbird"
"google-chrome" "google-chrome"
"code" "code"
"dev.zed.Zed" "dev.zed.Zed"
"codex-desktop.desktop" "codex-desktop"
"vesktop" "vesktop"
]; ];
show_all_outputs = true; show_all_outputs = true;
@@ -193,7 +205,7 @@ in
}; };
wallpaper = { wallpaper = {
enabled = true; enabled = lib.mkDefault true;
directory = "~/.wallpapers"; directory = "~/.wallpapers";
fill_mode = "crop"; fill_mode = "crop";
automation = { automation = {
@@ -0,0 +1,43 @@
diff --git a/src/shell/bar/widgets/taskbar_widget.cpp b/src/shell/bar/widgets/taskbar_widget.cpp
--- a/src/shell/bar/widgets/taskbar_widget.cpp
+++ b/src/shell/bar/widgets/taskbar_widget.cpp
@@ -5,6 +5,7 @@
#include "compositors/workspace_backend.h"
#include "config/config_service.h"
#include "core/deferred_call.h"
+#include "core/process/process.h"
#include "i18n/i18n.h"
#include "render/core/color.h"
#include "render/core/renderer.h"
@@ -36,6 +37,18 @@
namespace {
+ [[nodiscard]] bool launchTaskbarOverview(const std::string& appId) {
+ constexpr const char* command = "noctalia-taskbar-overview";
+ if (appId.empty() || !process::commandExists(command)) {
+ return false;
+ }
+ return process::runAsync({
+ command,
+ "--app-id",
+ appId.c_str(),
+ });
+ }
+
// Integer centering; optional odd spare pixel on the end side (right/bottom).
[[nodiscard]] float centeredOffset(float extent, float content, float inset = 0.0F, bool oddSpareOnEnd = true) {
const float inner = std::max(0.0F, extent - inset * 2.0F);
@@ -373,6 +386,12 @@ void TaskbarWidget::activateOrLaunchPinned(const TaskModel& task) {
return;
}
+ const std::string overviewAppId =
+ !task.appId.empty() ? task.appId : (!task.desktopEntryId.empty() ? task.desktopEntryId : task.idLower);
+ if (launchTaskbarOverview(overviewAppId)) {
+ return;
+ }
+
const std::string cycleKey = !task.desktopEntryId.empty() ? task.desktopEntryId : task.idLower;
std::size_t& cursor = m_groupedAppCycleCursor[cycleKey];
if (cursor >= windows.size()) {
@@ -0,0 +1,26 @@
diff --git a/src/shell/switcher/window_switcher.cpp b/src/shell/switcher/window_switcher.cpp
--- a/src/shell/switcher/window_switcher.cpp
+++ b/src/shell/switcher/window_switcher.cpp
@@ -286,12 +286,19 @@ indexLiveToplevelsByWindowId(const CompositorPlatform& platform, std::unordered_
continue;
}
- const auto mappedId = platform.compositorWindowIdForToplevelInfo(info);
- if (!mappedId.has_value() || mappedId->empty()) {
- continue;
+ std::string key;
+ if (const auto mappedId = platform.compositorWindowIdForToplevelInfo(info);
+ mappedId.has_value() && !mappedId->empty()) {
+ key = canonicalWindowId(*mappedId);
}
- const std::string key = canonicalWindowId(*mappedId);
+
+ // Generic wlroots compositors such as labwc do not provide a
+ // compositor-specific window ID. The wlr toplevel handle is stable
+ // for the lifetime of the window and is sufficient for switcher identity.
+ if (key.empty() && wlrHandle != 0) {
+ key = "toplevel:" + std::to_string(wlrHandle);
+ }
if (key.empty()) {
continue;
}
@@ -0,0 +1,44 @@
{ pkgs, ... }:
let
normcap = pkgs.normcap.override {
tesseract4 = pkgs.tesseract4.override {
enableLanguages = [
"chi_sim"
"jpn"
"eng"
];
};
};
normcapTranslate = pkgs.writeShellApplication {
name = "normcap-translate";
runtimeInputs = [
normcap
pkgs.jq
pkgs.xdg-utils
];
text = ''
captured_text="$(
normcap \
--cli-mode \
--screenshot-handler grim \
--show-introduction False \
--update False \
--detect-codes False \
--notification False \
-l jpn eng chi_sim
)" || exit 0
[ -n "$captured_text" ] || exit 0
encoded_text="$(printf '%s' "$captured_text" | jq -sRr @uri)"
exec xdg-open "naniapp://translate?source=$encoded_text"
'';
};
in
{
home.packages = [
normcap
normcapTranslate
];
}
+7
View File
@@ -0,0 +1,7 @@
{
description = "NormCap OCR screen capture";
includes = [
"applications.nani"
];
}
+53
View File
@@ -0,0 +1,53 @@
{ pkgs, ... }:
{
home.packages = [
(pkgs.writeShellApplication {
name = "screenshot";
runtimeInputs = with pkgs; [
coreutils
grim
slurp
wl-clipboard
xdg-user-dirs
];
text = ''
mode="''${1:-region}"
pictures_dir="$(xdg-user-dir PICTURES)"
if [[ -z "$pictures_dir" ]]; then
pictures_dir="$HOME/Pictures"
fi
output_dir="$pictures_dir/Screenshots"
output_file="$output_dir/Screenshot from $(date '+%Y-%m-%d %H-%M-%S').png"
mkdir -p "$output_dir"
case "$mode" in
region)
geometry="$(slurp)" || exit 0
grim -g "$geometry" "$output_file"
;;
output)
geometry="$(slurp -o)" || exit 0
grim -g "$geometry" "$output_file"
;;
all)
grim "$output_file"
;;
*)
echo "Unknown screenshot mode: $mode" >&2
exit 2
;;
esac
wl-copy --type image/png < "$output_file"
'';
})
];
}
+3
View File
@@ -0,0 +1,3 @@
{
description = "Wayland screenshot command";
}
@@ -0,0 +1,24 @@
{ pkgs, ... }:
{
programs.thunderbird = {
enable = true;
package = pkgs.thunderbird;
languagePacks = [ "ja" ];
policies = {
DisableTelemetry = true;
DisableAppUpdate = true;
InAppNotification_Disabled = true;
};
settings = {
"mailnews.start_page.enabled" = false;
"mail.shell.checkDefaultClient" = false;
};
profiles.default = {
isDefault = true;
};
};
}
@@ -0,0 +1,10 @@
_: {
xdg.mimeApps = {
enable = true;
defaultApplications = {
"x-scheme-handler/mailto" = "thunderbird.desktop";
"message/rfc822" = "thunderbird.desktop";
};
};
}
@@ -0,0 +1,7 @@
{ inputs, pkgs, ... }:
let
unstable = inputs.nixpkgs-unstable.legacyPackages.${pkgs.stdenv.hostPlatform.system};
in
{
home.packages = [ unstable.wl-find-cursor ];
}
@@ -0,0 +1,3 @@
{
description = "Wayland cursor locator";
}
+11
View File
@@ -63,6 +63,17 @@ enables performance improvements, synced lyrics, tracker blocking, the album
color theme, and custom output-device selection while preserving user-owned color theme, and custom output-device selection while preserving user-owned
settings such as the selected device. settings such as the selected device.
`workload.personal` also provides Handy for offline speech-to-text. It starts
hidden when the graphical session begins. On niri and labwc, `Ctrl+Space` is
owned by the compositor and invokes Handy's `--toggle-transcription` command;
Handy's own shortcut bindings are disabled on NixOS so it does not monitor
keyboard events under Wayland. Handy uses the Homebrew cask and starts at login
on macOS.
`workload.personal` provides ActivityWatch for local activity tracking. On
NixOS, its server and Wayland-compatible watcher run as Home Manager user
services. On macOS, the Homebrew cask starts at login.
On NixOS, `workload.game` provides Steam with Valve Proton and Proton-GE, On NixOS, `workload.game` provides Steam with Valve Proton and Proton-GE,
Protontricks, Wayland-compatible Steam Input, GameMode, Gamescope, and MangoHud. Protontricks, Wayland-compatible Steam Input, GameMode, Gamescope, and MangoHud.
Enabling Steam also activates the 32-bit graphics and PipeWire support required Enabling Steam also activates the 32-bit graphics and PipeWire support required
@@ -2,8 +2,6 @@
{ {
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
alacritty alacritty
grim
slurp
wf-recorder wf-recorder
]; ];
} }
@@ -1,7 +1,11 @@
{ pkgs, ... }: { { pkgs, ... }:
{
home.packages = [ home.packages = [
pkgs.playerctl pkgs.playerctl
]; ];
programs.noctalia.settings.wallpaper.enabled = false;
xdg.userDirs = { xdg.userDirs = {
enable = true; enable = true;
createDirectories = true; createDirectories = true;
@@ -11,7 +11,9 @@
"applications.ghostty" "applications.ghostty"
"applications.gtk" "applications.gtk"
"applications.loupe" "applications.loupe"
"applications.linux-wallpaperengine"
"applications.nautilus" "applications.nautilus"
"applications.normcap"
"applications.papers" "applications.papers"
"applications.qalculate-gtk" "applications.qalculate-gtk"
"applications.resources" "applications.resources"
@@ -2,8 +2,6 @@
{ {
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
alacritty alacritty
grim
slurp
wf-recorder wf-recorder
]; ];
} }
@@ -3,13 +3,17 @@
includes = [ includes = [
"applications.1password" "applications.1password"
"applications.activitywatch"
"applications.chrome" "applications.chrome"
"applications.discord" "applications.discord"
"applications.gnome-text-editor" "applications.gnome-text-editor"
"applications.handy"
"applications.kde" "applications.kde"
"applications.moonlight" "applications.moonlight"
"applications.slack" "applications.slack"
"applications.zoom" "applications.zoom"
"applications.obs" "applications.obs"
"applications.nani"
"applications.thunderbird"
]; ];
} }
+24
View File
@@ -21,5 +21,29 @@ in
pulse.enable = true; pulse.enable = true;
jack.enable = true; jack.enable = true;
wireplumber.enable = true; wireplumber.enable = true;
extraConfig.pipewire."90-echo-cancel.conf" = {
"context.modules" = [
{
name = "libpipewire-module-echo-cancel";
args = {
"library.name" = "aec/libspa-aec-webrtc";
"monitor.mode" = true;
"capture.props" = {
"node.name" = "echo_cancel_capture";
"node.description" = "Echo Cancel Capture";
};
"source.props" = {
"node.name" = "echo_cancel_source";
"node.description" = "Echo Cancelled Microphone";
};
};
}
];
};
}; };
} }
+92 -6
View File
@@ -1,12 +1,98 @@
{ lib, ... }: { lib, ... }:
{ let
networking.nameservers = lib.mkDefault [ dohPort = 5300;
"1.1.1.1"
"1.0.0.1" dohLocalUpstream = "127.0.0.1#${toString dohPort}";
internalDns = [
"10.50.80.53" "10.50.80.53"
"10.50.80.54" "10.50.80.54"
# "fd00:50:80::53"
# "fd00:50:80::54"
]; ];
services.resolved.enable = lib.mkDefault false; internalZones = [
security.pki.certificateFiles = [ ./root_ca.crt ]; "app.homelabs.run"
];
internalDnsServers = lib.concatMap (zone: map (dns: "/${zone}/${dns}") internalDns) internalZones;
in
{
services.resolved.enable = false;
networking.networkmanager.dns = "none";
services.dnscrypt-proxy = {
enable = true;
upstreamDefaults = true;
settings = {
listen_addresses = [
"127.0.0.1:${toString dohPort}"
];
server_names = [
"cloudflare"
"cloudflare-ipv6"
];
ipv4_servers = true;
ipv6_servers = true;
dnscrypt_servers = false;
doh_servers = true;
odoh_servers = false;
cache = false;
block_ipv6 = false;
ignore_system_dns = true;
bootstrap_resolvers = [
"9.9.9.11:53"
"149.112.112.11:53"
"[2620:fe::11]:53"
"[2620:fe::fe:11]:53"
];
netprobe_address = "1.1.1.1:443";
timeout = 5000;
keepalive = 30;
};
};
services.dnsmasq = {
enable = true;
resolveLocalQueries = true;
settings = {
no-resolv = true;
local-service = "host";
server = [ dohLocalUpstream ] ++ internalDnsServers;
all-servers = true;
cache-size = 10000;
dns-loop-detect = true;
domain-needed = true;
bogus-priv = true;
};
};
systemd.services.dnsmasq = {
wants = [ "dnscrypt-proxy.service" ];
after = [ "dnscrypt-proxy.service" ];
};
security.pki.certificateFiles = [
./root_ca.crt
];
} }
+137
View File
@@ -0,0 +1,137 @@
---
name: add-application-or-service
description: Add, install, configure, or enable an application or long-running service in this NixOS, nix-darwin, and Home Manager flake while preserving its Registry architecture and quality bar. Use for new GUI or CLI applications, packages, daemons, background services, application-service pairs, cross-platform installations, profile adoption, or substantial extensions to an existing application or service unit.
---
# Add Application or Service
Add the smallest complete Registry unit change that has a clear owner, an
explicit dependency path, and evidence that every affected host class
evaluates. Treat `AGENTS.md` as the authoritative repository contract; never
replace it with generic Nix conventions.
## Follow the workflow
### 1. Establish the baseline
1. Read `AGENTS.md` completely before editing.
2. Run `git status --short`. Preserve all pre-existing user changes and identify
which later diffs belong to this task.
3. Translate the request into observable outcomes: package or program, desired
configuration, supported host classes, required daemon or permissions, and
the profile or user intent that should select it.
4. Inspect the nearest existing units, relevant profiles, `hosts/default.nix`,
and Registry implementation. Prefer repository evidence over memory.
5. Verify current package names, module options, external module exports, and
Homebrew cask names from the locked inputs or authoritative upstream
documentation. Do not guess an option path.
6. Read [references/review-checklist.md](references/review-checklist.md) before
choosing files or dependencies.
### 2. Choose ownership before code
Classify each concern independently:
- Put the user-facing program and its settings in
`modules/applications/<name>/`.
- Put a daemon, long-running process, firewall rule, permission, or user/group
membership in `modules/services/<name>/`.
- Split an application and independently meaningful daemon into two units.
Let the application include the service only when the service is a technical
requirement of that application.
- Put adoption of otherwise independent units in the narrowest coherent
`modules/profiles/` composition.
- Use another documented owner when the request is actually a system,
hardware, user, overlay, or host concern. Do not force it into an application
or service directory merely because this skill was invoked.
Choose only the reserved fragments that contain real configuration. Use
`common.nix`, `nixos.nix`, and `darwin.nix` for system-side configuration; use
`home.nix` or `home/{common,nixos,darwin}.nix` for Home Manager. Use `meta.nix`
only for description, fully qualified `includes`, and external module imports.
Before editing, formulate a short implementation contract containing:
- the unit ID and owner;
- each file to create or change and why;
- technical dependencies versus profile-level choices;
- supported and affected host classes;
- the evaluations or builds that will prove the change.
Rework the design if an ordinary addition appears to require Registry changes,
new global `specialArgs`, `_module.args`, direct host selection, or an overlay.
Use those mechanisms only with concrete evidence that the documented extension
points cannot express the requirement.
### 3. Implement the minimum complete change
1. Return configuration directly from every reserved fragment. Do not add
top-level `imports`, `options`, or `config`, and do not reproduce Registry
`mkEnableOption`, `cfg`, or `mkIf` boilerplate.
2. Put upstream NixOS, nix-darwin, or Home Manager modules in
`meta.imports.<class>`. Import ordinary helper files explicitly from the
fragment that uses them.
3. Declare unit-to-unit technical dependencies only through fully qualified
`meta.includes`. Never enable another unit by assigning its
`my.<path>.enable` option inside a fragment.
4. Add an independent application or service to an existing coherent profile,
or create a justified profile when no existing one expresses the user
intent. Do not use `hosts/default.nix` application or unit escape hatches for
normal composition.
5. Keep cross-platform purpose shared and installation differences in the
owning unit. Do not create thin `*-linux` profiles.
6. Use existing module arguments and standard options. Do not inject a
dependency through global arguments, Registry internals, import ordering, or
`lib.mkForce`. Use explicit module priorities only when a real ownership
boundary requires them and make that reason visible in the code or handoff.
7. Avoid speculative abstraction. Create a helper only when it separates
meaningful configuration or prevents real duplication. Do not add empty
fragments, compatibility aliases, unused options, redundant comments, or
copied boilerplate.
8. Update `modules/profiles/README.md`, `AGENTS.md`, profile selections, or
other contract documentation whenever the change makes an existing
statement stale. Do not edit them performatively when their meaning remains
accurate.
### 4. Prove the change
Run the validation matrix in
[references/review-checklist.md](references/review-checklist.md). At minimum:
1. Format the task-owned files with the repository formatter and run
`git diff --check`.
2. Inspect the complete task diff for accidental files, duplication, leaked
secrets, forced values, direct enable assignments, and unrelated rewrites.
3. Run `nix flake check`.
4. Run `pre-commit run --all-files`.
5. Evaluate every affected real host without switching it. For a
cross-platform unit or profile, evaluate both NixOS and nix-darwin even if
only one class changed. Build an affected configuration with `--no-link`
when the current platform can build it.
6. Verify selection as well as syntax: confirm that the expected package,
program, service, group, cask, or external module appears in the resulting
configuration.
If a command is unavailable, blocked by the environment, or fails for a
pre-existing reason, diagnose it and report the exact gap. Never silently skip
a required check or weaken the implementation to make a check pass.
### 5. Audit before completion
Reject the change until all of the following are true:
- Every line has one clear owner and is required by the requested behavior.
- Every dependency is either technical and declared in `meta.includes`, or a
user choice owned by a profile.
- The unit is reachable from the intended profile or has an explicit reason to
remain independently selectable.
- No host, Registry, flake root, global argument, or overlay was changed as a
shortcut.
- Reserved fragments, metadata, and profile documentation satisfy the current
repository contract.
- Validation covers every affected host class and all failures are resolved or
explicitly reported.
Conclude with the owner and selection rationale, affected hosts or profiles,
validation commands and results, and any manual activation or runtime check
that remains. Do not claim runtime behavior that was only evaluated.
@@ -0,0 +1,4 @@
interface:
display_name: "Add Application or Service"
short_description: "Add clean, validated Registry units"
default_prompt: "Use $add-application-or-service to add an application or service cleanly and verify every affected host class."
@@ -0,0 +1,139 @@
# Application and Service Review Checklist
Use this reference during design and again during final review.
## Ownership and fragment matrix
| Concern | Owner | Typical fragment |
| -------------------------------------------------------- | ----------------------------------- | ----------------------------------------- |
| User-facing GUI, CLI, editor, or compositor | `modules/applications/<name>/` | `home*.nix`, `nixos.nix`, or `darwin.nix` |
| User-scoped package and program settings | Application unit | `home.nix` or `home/<class>.nix` |
| macOS Homebrew package or cask | Owning application or service | `darwin.nix` |
| Daemon or long-running service | `modules/services/<name>/` | `nixos.nix` or `darwin.nix` |
| Firewall, group, permission, or service account | Owning service | `nixos.nix` or `darwin.nix` |
| Upstream module defining options | Owning unit metadata | `meta.nix` under `imports.<class>` |
| Technical prerequisite unit | Owning unit metadata | Fully qualified `meta.includes` |
| A set of independent tools chosen for one purpose | Narrowest coherent profile | Profile `meta.includes` |
| Machine fact such as UUID, monitor ID, or static address | `hosts/<name>/` | Normal host module |
| Missing or replaced package | `overlays/` only after proving need | Overlay definition |
Use `home/common.nix` when Home Manager configuration is truly shared between
NixOS and Darwin. Use `home/nixos.nix` or `home/darwin.nix` for class-specific
Home Manager behavior. Root `common.nix` is system-side and never Home Manager.
Do not create an unused counterpart for symmetry.
## Dependency review
For every edge from unit A to unit B, answer these questions:
1. Does A fail to work without B? If yes, put the fully qualified ID of B in
A's `meta.includes`.
2. Are A and B merely useful together for a particular workflow? If yes, let a
profile include both.
3. Does the dependency exist only on one host? Keep the machine fact in the
host, but keep reusable behavior in its unit or profile.
4. Is a new `specialArgs`, `_module.args`, Registry field, or direct
`my.*.enable` assignment being proposed? Reject it unless the repository's
normal module and `meta.includes` mechanisms provably cannot model the
requirement.
5. Would adding the dependency make the depended-on application select a
compositor, desktop, personal workload, or unrelated tool? Reverse or remove
the edge; application metadata contains technical requirements, not taste.
Accept no circular dependency, shortened unit ID, duplicate include, stale
unit ID, or ordering-dependent override.
## Minimality and code-quality review
Reject any of these patterns:
- Empty or placeholder reserved fragments.
- Hand-written enable options or guards already generated by the Registry.
- Top-level `imports`, `options`, or `config` in a configuration fragment.
- External module imports hidden in a guarded configuration fragment.
- Helper files assumed to be auto-imported.
- A helper abstraction used once without reducing meaningful complexity.
- Configuration duplicated across fragments when a shared fragment can express
it cleanly.
- Direct host application or unit selection where a profile expresses the
concern.
- A new flake input or overlay when the locked package set already provides the
package and required module.
- Global argument injection for a value owned by one unit.
- `lib.mkForce` used to win an ordering fight instead of resolving ownership.
- Secret material, generated state, machine IDs, or mutable user preferences
committed as reusable configuration.
- Comments that repeat the code, compatibility aliases, dead options, or
opportunistic unrelated cleanup.
Prefer standard upstream module options over hand-written service definitions.
Prefer existing repository arguments and helpers over new plumbing. Preserve
user-owned mutable state unless the requested policy explicitly owns it.
## Validation matrix
Run checks from the repository root and keep the exact results for the handoff.
Do not switch or activate a live system merely to validate a change.
### Always
1. Format task-owned files. If the worktree contains unrelated user changes,
pass only task-owned paths to the configured formatter when supported.
2. Run `git diff --check`.
3. Review `git status --short`, `git diff --stat`, and the complete `git diff`.
4. Run `nix flake check`.
5. Run `pre-commit run --all-files`.
### NixOS or Home Manager on NixOS
- Evaluate each affected host's system toplevel derivation.
- Build at least one affected NixOS configuration with `--no-link` when the
current system supports it.
- Inspect the resulting option that proves selection: for example
`environment.systemPackages`, the user's `home.packages`,
`systemd.services`, `users.users.<name>.extraGroups`, or the upstream
`programs`/`services` option.
Typical build shape:
```sh
nix build .#nixosConfigurations.<host>.config.system.build.toplevel --no-link
```
### nix-darwin or Home Manager on Darwin
- Evaluate every affected Darwin host even when running on Linux.
- Inspect `homebrew.casks` or `homebrew.brews` for Homebrew-backed additions.
- Evaluate the relevant Home Manager program or package option.
- Build a Darwin configuration only on a compatible Darwin builder; otherwise
report that build as an explicit runtime-validation gap.
Typical evaluation shapes:
```sh
nix eval --raw .#darwinConfigurations.<host>.system.drvPath
nix eval --json .#darwinConfigurations.<host>.config.homebrew.casks
```
Confirm the exact output attribute against the current flake before using a
command; do not paste these shapes blindly.
### Profiles and cross-platform changes
- Determine transitive selection through `meta.includes`, not only direct
mentions.
- Evaluate every real host selecting the changed profile.
- Evaluate both host classes for a cross-platform profile, even if only one
current fragment changed.
- Re-read `modules/profiles/README.md` and `hosts/default.nix` for stale meaning,
compatibility, or role statements.
- If no real host selects the new unit, construct a non-persistent evaluation
that enables it or explain why the unit is intentionally dormant. Do not add
a fake host or permanent direct selection as a test harness.
### Runtime-dependent behavior
Evaluation and builds cannot prove GUI appearance, credentials, network access,
hardware behavior, or successful daemon interaction. State the precise manual
post-activation check needed for those behaviors. Never describe evaluation as
a runtime test.