Compare commits

...

62 Commits

Author SHA1 Message Date
Naïm Camille Favier
7d5927b63c xfconf: fix package 2026-01-04 12:26:39 +01:00
Ivan Isakov
8f7d6bacb6 launchd: Restore errexit after setting up launchd agents
At the beginning of the setup script `errexit` is disabled, but never
properly enabled again at the end. This causes potential issues/errors
in activation scripts following `setupLaunchAgents` to go unnoticed,
since the build doesn't actually fail.
2026-01-04 11:30:04 +01:00
Alex Martens
12cc14271b hyprshell: fix broken links 2026-01-04 09:32:19 +01:00
Benedikt Rips
c211298f7e tree-wide: use lib.hm.assertions.assertPlatform 2026-01-04 09:31:10 +01:00
Tim Kleinschmidt
1cfa305fba opencode: add agent skills support
Adds support for OpenCode Agent Skills by managing skill definitions
under "/opencode/skill/<name>/SKILL.md" via `programs.opencode.skills`.

Documentation: https://opencode.ai/docs/skills/
2026-01-03 11:47:20 +01:00
Tim Kleinschmidt
3e87b442b5 opencode: update docs
.config is already part of $XDG_CONFIG_HOME
2026-01-03 11:47:20 +01:00
leiserfg
99a037de18 vicinae: fix settings example 2026-01-03 11:31:39 +01:00
Robert Helgesson
3b3164dfe3 zsh: avoid escaping showing up in dotDir docs 2026-01-03 11:29:29 +01:00
Robert Helgesson
bc43546503 docs: turn option references into links 2026-01-03 11:29:29 +01:00
Aguirre Matteo
78a8fae57f yt-dlp: remove redundant extraConfig test 2026-01-03 11:23:26 +01:00
Aguirre Matteo
b558d54215 yt-dlp: allow multiple declarations of the same option 2026-01-03 11:23:26 +01:00
Robert Helgesson
9ef24320f1 emacs: remove unnecessary rec 2026-01-03 11:10:00 +01:00
Vladislav
18f9d668aa anki: fix UI scale configuration
Use `numbers.between 1.0 2.0` for the UI scale type to match Anki's
actual behavior where ProfileManager.uiScale clamps values below 1.0
to 1.0.
2026-01-03 10:49:37 +01:00
bandithedoge
2f06b72606 mpd: set network settings as environment variables
https://mpd.readthedocs.io/en/stable/client.html#connecting-to-mpd

These environment variables are picked up by, amongst others, clients
based on libmpdclient:

- 2a42a10067/src/settings.c (L108)
- 2a42a10067/src/settings.c (L135)
2026-01-02 23:05:42 +01:00
PerchunPak
d4e4d5cfa3 lib: make toHyprconf support strings and attrs in sections
Adds support for

    section = [
      "abc"
      { a = 123; }
    ];

Which gets generated to

    section=abc

    section {
      a=123
    }

This is very useful with the new windowrule syntax, where you can
create anonymous window rules as strings and named rules as attribute
sets. See <https://wiki.hypr.land/Configuring/Window-Rules/>.
2026-01-02 15:56:43 +01:00
Austin Horstman
e4e78a2cbe tests/linux-wallpaperengine: add execstart test
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-30 08:22:50 -06:00
Austin Horstman
ae1ea768ef linux-wallpaperengine: misc cleanup
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-30 08:22:50 -06:00
Austin Horstman
365cbc13c4 linux-wallpaperengine: fix ExecStart command
Make sure to properly separate command args.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-30 08:22:50 -06:00
teto
d99b4ca5de meli: support jmap
Enabled when flavor == fastmail.com
2025-12-29 23:55:10 +01:00
teto
64f4dadb80 neovim: fix extraLuaConfig example
reported by bruno
2025-12-29 16:00:00 -06:00
Austin Horstman
7a7b43c723 treewide: drop foo-dogsquared
Removed in https://github.com/NixOS/nixpkgs/pull/472804

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-28 22:03:17 -06:00
home-manager-ci[bot]
a84cccefd4 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/3e2499d5539c16d0d173ba53552a4ff8547f4539?narHash=sha256-QEhk0eXgyIqTpJ/ehZKg9IKS7EtlWxF3N7DXy42zPfU%3D' (2025-12-25)
  → 'github:NixOS/nixpkgs/c0b0e0fddf73fd517c3471e546c0df87a42d53f4?narHash=sha256-coBu0ONtFzlwwVBzmjacUQwj3G%2BlybcZ1oeNSQkgC0M%3D' (2025-12-28)
2025-12-28 22:03:17 -06:00
teto
9d32c214db neovim: let user insert snippets in init.lua
It is a common complaint for users that they can't insert their own code
where they want in the init.lua.
2025-12-29 01:51:32 +01:00
leiserfg
87785ddbc7 vicinae: Add tests for old vicinae version 2025-12-28 17:17:29 -06:00
leiserfg
113b155fe8 vicinae: Update to post 0.17 settings 2025-12-28 17:17:29 -06:00
teto
398bc87bc8 neovim: refactor to get rid of makeNeovimConfig 2025-12-28 20:13:09 +01:00
home-manager-ci[bot]
3613abcbd7 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/a6531044f6d0bef691ea18d4d4ce44d0daa6e816?narHash=sha256-3xY8CZ4rSnQ0NqGhMKAy5vgC%2B2IVK0NoVEzDoOh4DA4%3D' (2025-12-21)
  → 'github:NixOS/nixpkgs/3e2499d5539c16d0d173ba53552a4ff8547f4539?narHash=sha256-QEhk0eXgyIqTpJ/ehZKg9IKS7EtlWxF3N7DXy42zPfU%3D' (2025-12-25)
2025-12-28 10:30:53 -06:00
Austin Horstman
d7e794fe12 tests/anyrun: add empty css test
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-28 10:29:55 -06:00
Austin Horstman
8969535f1c anyrun: default extraCss to null
Prevent creating a file when not configured.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-28 10:29:55 -06:00
teto
80cca72314 meli: support account specific settings
also assert when overriding accounts. I had overriden settings when
testing this module, forgot about it and then wondered why my email
accounts were ignored.
2025-12-28 16:49:26 +01:00
Austin Horstman
d2e0458d65 zsh: don't suggest stateVersion update
Generally best to avoid telling normal users to adjust their
stateVersion to avoid other home-manager module logic changes based on
stateVersion.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-27 18:30:08 -06:00
Austin Horstman
b3ae822959 tests/zsh: add xdg dotDir tests
Verify we respect xdg.enable behavior when placing files.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-27 17:58:46 -06:00
Austin Horstman
d761c0ce89 news: add zsh dotDir default change
Default has changed to respect `xdg.enable`. Let user's know in case it
affects them surprisingly.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-27 17:58:46 -06:00
Austin Horstman
f84f474c1b zsh: respect xdg.enable for dotDir
Make sure we actually respect a user's `xdg.enable` preference.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-27 17:58:46 -06:00
Austin Horstman
ea6dfabe3c tests/zsh: add more test cases for dotdir
Adds tests for various dotDir configurations including trailing slashes
and spaces.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-27 17:58:46 -06:00
Austin Horstman
5432dc5bc4 zsh: fix dotDir path normalization and quoting
Fixes issue where dotDir with trailing slash caused failures.
Refactors lib.nix to use raw paths internally, fixing issues with spaces
in paths.
Updates history.path logic to correctly handle raw absolute paths.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-27 17:58:46 -06:00
Bruno BELANYI
bec08ef6e3 programs/ssh: DRY address/port options
The end goal is to hoist the forwarded path assertion directly into this
module rather than the top-level.
2025-12-27 15:12:05 -06:00
Sandro Marques
4067ca1ffb easyeffects: Fix race condition on Wayland session startup
Add systemd service ordering dependencies to prevent EasyEffects from
starting before the Wayland compositor is ready.

**Problem:** EasyEffects would crash during login with "Failed to create
wl_display (No such file or directory)" if started before the Wayland
display server was fully initialized.

**Solution:** Declaring the "After" property ensures EasyEffects waits
for the graphical session to be fully ready. The "PartOf" properly stops
EasyEffects when logging out. Got the tip from
https://github.com/wwmm/easyeffects/issues/1310.

Tested on NixOS 25.11 (Xantusia, 20251223.76701a1) and Home Manager 25.11
(0999ed8) with GNOME 49 on Wayland.
2025-12-27 12:56:02 -06:00
Oliver Geneser
2d36a6de2f ssh: add kexAlgorithms to matchBlocks
This commit adds the option to add kexAlgorithms as an matchBlock
option with updated test case.
2025-12-27 16:54:14 +01:00
Heitor Augusto
91cdb0e2d5 nixGL: patch systemd and d-bus services references 2025-12-25 11:16:13 -06:00
home-manager-ci[bot]
7eca7f7081 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/c6245e83d836d0433170a16eb185cefe0572f8b8?narHash=sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc%3D' (2025-12-18)
  → 'github:NixOS/nixpkgs/a6531044f6d0bef691ea18d4d4ce44d0daa6e816?narHash=sha256-3xY8CZ4rSnQ0NqGhMKAy5vgC%2B2IVK0NoVEzDoOh4DA4%3D' (2025-12-21)
2025-12-23 23:24:11 -06:00
Austin Horstman
20728df08f release/25.11: add darwin copy apps change
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-23 16:36:16 -06:00
Austin Horstman
af3c24de76 release/25.05: add git signing format change
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-23 16:36:16 -06:00
Austin Horstman
624c7e80fb release/23.05: add swaylock stateVersion change
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-23 16:36:16 -06:00
home-manager-ci[bot]
9c790e687e maintainers: update all-maintainers.nix
Automated update of the master maintainers list combining:
- Home Manager specific maintainers from modules/lib/maintainers.nix
- Nixpkgs maintainers referenced in Home Manager modules

**Added:** 0 maintainers
**Removed:** 0 maintainers
**Total:** 282 → 282 maintainers

Generated by: lib/python/generate-all-maintainers.py
2025-12-23 13:45:25 -06:00
Austin Horstman
527ad07e66 tests/radicle: stub radicle-node
Failing build is blocking CI, test only verifies generated config.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-22 01:11:39 -06:00
home-manager-ci[bot]
57a02fd7d9 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/1306659b587dc277866c7b69eb97e5f07864d8c4?narHash=sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4%3D' (2025-12-15)
  → 'github:NixOS/nixpkgs/c6245e83d836d0433170a16eb185cefe0572f8b8?narHash=sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc%3D' (2025-12-18)
2025-12-22 01:11:39 -06:00
aleksana
3fe66908e0 television: fix keybindings 2025-12-21 23:41:33 -06:00
Austin Horstman
61fcc9de76 tests/neovim: add test to catch config eval
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 19:55:46 -06:00
Austin Horstman
7b73a6e98f neovim: fix eval
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 19:51:30 -06:00
Austin Horstman
4dc3c91c50 neovim: remove deprecated type coercion for extraXPackages
3 years old, no need to keep around.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:31 -06:00
Austin Horstman
a7d6bba358 neovim: remove old removal options
3-4 years old, no need to keep around.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:31 -06:00
Austin Horstman
bdb807dc28 neovim: modernize
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:31 -06:00
Austin Horstman
0a583021ea tests/neovim: add wrapper args test
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:31 -06:00
Austin Horstman
ab01ea24b2 neovim: add missing wrapper args
Add support for extraName, autowrapRuntimeDeps, waylandSupport, and
withPerl options in the neovim module. These options are passed to the
neovim wrapper to allow for more granular configuration.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:31 -06:00
Austin Horstman
c764a377a0 gemini-cli: fix settings example
Supposed to represent the settigns to pass in, not the generated json.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:06 -06:00
Austin Horstman
28b3622b80 tests/gemini-cli: verify defaultModel behavior
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:06 -06:00
Austin Horstman
c848303f1e gemini-cli: don't force GEMINI_MODEL
Default to null so user's can opt in to using this variable that will
override configurations.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-20 17:11:06 -06:00
Austin Horstman
bb35f07cc9 tests/claude-code: add rules tests
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-19 13:19:35 -06:00
Austin Horstman
9bf54edf10 claude-code: add rules support
Support the `rules` directory for modular memory files.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-12-19 13:19:35 -06:00
Benedikt Rips
89c9508bbe ssh-agent: set $SSH_AUTH_SOCK in non-interactive shells
Since PR #8099, the module sets `$SSH_AUTH_SOCK` through shells' options
for interactive shell initialization instead of
`home.sessionVariablesExtra`. The replacement was not faithful, however,
since `home.sessionVariablesExtra` is sourced also in non-interactive
shells. With this commit, the shells' profile options (where
`home.sessionVariablesExtra` is sourced) are used to set
`$SSH_AUTH_SOCK`.

Fixes #8129.
2025-12-17 08:15:55 -06:00
home-manager-ci[bot]
22202ff0d8 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/2fbfb1d73d239d2402a8fe03963e37aab15abe8b?narHash=sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0%3D' (2025-12-11)
  → 'github:NixOS/nixpkgs/1306659b587dc277866c7b69eb97e5f07864d8c4?narHash=sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4%3D' (2025-12-15)
2025-12-17 07:57:35 -06:00
92 changed files with 1518 additions and 535 deletions

View File

@@ -1097,19 +1097,6 @@
name = "Hoang Nguyen";
source = "home-manager";
};
foo-dogsquared = {
email = "foodogsquared@foodogsquared.one";
github = "foo-dogsquared";
githubId = 34962634;
keys = [
{
fingerprint = "DDD7 D0BD 602E 564B AA04 FC35 1431 0D91 4115 2B92";
}
];
matrix = "@foodogsquared:matrix.org";
name = "Gabriel Arazas";
source = "nixpkgs";
};
fpob = {
email = "fpob@proton.me";
github = "fpob";
@@ -2078,7 +2065,7 @@
source = "nixpkgs";
};
shikanime = {
email = "deva.shikanime@protonmail.com";
email = "william.phetsinorath@shikanime.studio";
github = "shikanime";
githubId = 22115108;
name = "William Phetsinorath";

View File

@@ -57,3 +57,9 @@ changes are only active if the `home.stateVersion` option is set to
now default to `true` which is consistent with the default values
for those options used by `i3` and `sway`.
- The [](#opt-programs.swaylock.enable) option now defaults to `false`
and must be explicitly enabled. Previously, it would be implicitly
enabled when `programs.swaylock.settings` was non-empty. Users with
`home.stateVersion` set to earlier versions will continue to get the
old implicit behavior.

View File

@@ -27,4 +27,8 @@ The state version in this release includes the changes below. These
changes are only active if the `home.stateVersion` option is set to
\"25.05\" or later.
- No changes.
- The [](#opt-programs.git.signing.format) option no longer defaults to
`"openpgp"`. Users who use Git signing with GPG should explicitly set
this option to `"openpgp"` to maintain the previous behavior. Users
with `home.stateVersion` set to earlier versions will continue to get
the `"openpgp"` default for backwards compatibility.

View File

@@ -80,3 +80,10 @@ changes are only active if the `home.stateVersion` option is set to
`{ PASSWORD_STORE_DIR = $XDG_DATA_HOME/password-store; }` anymore by its
default value. This will revert to the default behaviour of the program,
namely `$HOME/.password-store` to be used as the store path.
- On macOS, [](#opt-targets.darwin.copyApps.enable) is now enabled by
default instead of [](#opt-targets.darwin.linkApps.enable). This means
applications from `home.packages` will be copied to
`~/Applications/Home Manager Apps` rather than symlinked, making them
work properly with Spotlight. Users with `home.stateVersion` set to
earlier versions will continue to use `linkApps` by default.

View File

@@ -7,11 +7,19 @@ section is therefore not final.
This release has the following notable changes:
- The [](#opt-programs.anki.uiScale) option now expects a value in the
range 1.02.0, previously it erroneously expected values in the
range `0.01.0`.
## State Version Changes {#sec-release-26.05-state-version-changes}
The state version in this release includes the changes below. These
changes are only active if the `home.stateVersion` option is set to
\"26.05\" or later.
- The `gtk.gtk4.theme` option does not mirror `gtk.theme` by default
anymore.
- The [](#opt-gtk.gtk4.theme) option does not mirror
[](#opt-gtk.theme) by default anymore.
- The [](#opt-programs.zsh.dotDir) option now defaults to the XDG
configuration directory (usually `~/.config/zsh`) when
[](#opt-xdg.enable) is true.

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1765472234,
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"lastModified": 1766902085,
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4",
"type": "github"
},
"original": {

View File

@@ -270,9 +270,7 @@ in
setupLaunchAgents
# Restore errexit
if [[ -o errexit ]]; then
set -e
fi
set -e
'';
})
];

View File

@@ -21,6 +21,7 @@
isList
mapAttrsToList
replicate
attrNames
;
initialIndent = concatStrings (replicate indentLevel " ");
@@ -28,31 +29,36 @@
toHyprconf' =
indent: attrs:
let
sections = filterAttrs (n: v: isAttrs v || (isList v && all isAttrs v)) attrs;
isImportantField =
n: _: foldl (acc: prev: if hasPrefix prev n then true else acc) false importantPrefixes;
importantFields = filterAttrs isImportantField attrs;
withoutImportantFields = fields: removeAttrs fields (attrNames importantFields);
allSections = filterAttrs (n: v: isAttrs v || isList v) attrs;
sections = withoutImportantFields allSections;
mkSection =
n: attrs:
if lib.isList attrs then
(concatMapStringsSep "\n" (a: mkSection n a) attrs)
else
if isList attrs then
let
separator = if all isAttrs attrs then "\n" else "";
in
(concatMapStringsSep separator (a: mkSection n a) attrs)
else if isAttrs attrs then
''
${indent}${n} {
${toHyprconf' " ${indent}" attrs}${indent}}
'';
''
else
toHyprconf' indent { ${n} = attrs; };
mkFields = generators.toKeyValue {
listsAsDuplicateKeys = true;
inherit indent;
};
allFields = filterAttrs (n: v: !(isAttrs v || (isList v && all isAttrs v))) attrs;
isImportantField =
n: _: foldl (acc: prev: if hasPrefix prev n then true else acc) false importantPrefixes;
importantFields = filterAttrs isImportantField allFields;
fields = builtins.removeAttrs allFields (mapAttrsToList (n: _: n) importantFields);
allFields = filterAttrs (n: v: !(isAttrs v || isList v)) attrs;
fields = withoutImportantFields allFields;
in
mkFields importantFields
+ concatStringsSep "\n" (mapAttrsToList mkSection sections)

View File

@@ -0,0 +1,20 @@
{ config, ... }:
{
time = "2025-12-27T19:00:00+00:00";
condition = config.programs.zsh.enable;
message = ''
The default value of `programs.zsh.dotDir` has changed.
When `home.stateVersion` is set to "26.05" or later, and `xdg.enable` is
`true` (the default), `programs.zsh.dotDir` now defaults to
`''${config.xdg.configHome}/zsh`. Previously, it defaulted to the home
directory.
This means your Zsh configuration files (`.zshrc`, `.zshenv`, etc.) will be
moved to `~/.config/zsh` (or your configured XDG config home).
If you prefer the old behavior, you can explicitly set:
`programs.zsh.dotDir = config.home.homeDirectory;`
'';
}

View File

@@ -0,0 +1,10 @@
{ config, ... }:
{
time = "2026-01-02T00:03:48+00:00";
condition = config.services.mpd.enable;
message = ''
`MPD_HOST` and `MPD_PORT` environment variables are now set automatically.
This can be disabled with `services.mpd.enableSessionVariables = false`.
'';
}

View File

@@ -135,7 +135,7 @@ in
home.activation.xfconfSettings = lib.hm.dag.entryAfter [ "installPackages" ] (
let
mkCommand = channel: property: value: ''
run ${pkgs.xfce.xfconf}/bin/xfconf-query \
run ${pkgs.xfconf}/bin/xfconf-query \
${lib.escapeShellArgs (
[
"-c"

View File

@@ -82,7 +82,7 @@ in
};
uiScale = lib.mkOption {
type = with lib.types; nullOr (numbers.between 0.0 1.0);
type = with lib.types; nullOr (numbers.between 1.0 2.0);
default = null;
example = 1.0;
description = "User interface scale.";

View File

@@ -185,7 +185,7 @@ in
extraCss = mkOption {
type = nullOr lines;
default = "";
default = null;
description = ''
Extra CSS lines to add to {file}`~/.config/anyrun/style.css`.
'';

View File

@@ -197,6 +197,47 @@ in
};
};
rules = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path);
default = { };
description = ''
Modular rule files for Claude Code.
The attribute name becomes the rule filename, and the value is either:
- Inline content as a string
- A path to a file containing the rule content
Rules are stored in .claude/rules/ directory.
All markdown files in .claude/rules/ are automatically loaded as project memory.
'';
example = lib.literalExpression ''
{
code-style = '''
# Code Style Guidelines
- Use consistent formatting
- Follow language conventions
''';
testing = '''
# Testing Conventions
- Write tests for all new features
- Maintain test coverage above 80%
''';
security = ./rules/security.md;
}
'';
};
rulesDir = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
Path to a directory containing rule files for Claude Code.
Rule files from this directory will be symlinked to .claude/rules/.
All markdown files in this directory are automatically loaded as project memory.
'';
example = lib.literalExpression "./rules";
};
agentsDir = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
@@ -325,6 +366,10 @@ in
assertion = !(cfg.memory.text != null && cfg.memory.source != null);
message = "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`";
}
{
assertion = !(cfg.rules != { } && cfg.rulesDir != null);
message = "Cannot specify both `programs.claude-code.rules` and `programs.claude-code.rulesDir`";
}
{
assertion = !(cfg.agents != { } && cfg.agentsDir != null);
message = "Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`";
@@ -386,6 +431,11 @@ in
if cfg.memory.text != null then { text = cfg.memory.text; } else { source = cfg.memory.source; }
);
".claude/rules" = lib.mkIf (cfg.rulesDir != null) {
source = cfg.rulesDir;
recursive = true;
};
".claude/agents" = lib.mkIf (cfg.agentsDir != null) {
source = cfg.agentsDir;
recursive = true;
@@ -406,6 +456,12 @@ in
recursive = true;
};
}
// lib.mapAttrs' (
name: content:
lib.nameValuePair ".claude/rules/${name}.md" (
if lib.isPath content then { source = content; } else { text = content; }
)
) cfg.rules
// lib.mapAttrs' (
name: content:
lib.nameValuePair ".claude/agents/${name}.md" (

View File

@@ -21,14 +21,19 @@ in
settings = lib.mkOption {
inherit (jsonFormat) type;
default = { };
example = lib.literalExpression ''
{
"theme": "Default",
"vimMode": true,
"preferredEditor": "nvim",
"autoAccept": true
}
'';
example = {
ui.theme = "Default";
general = {
vimMode = true;
preferredEditor = "nvim";
previewFeatures = true;
};
ide.enabled = true;
privacy.usageStatisticsEnabled = false;
tools.autoAccept = false;
context.loadMemoryFromIncludeDirectories = true;
security.auth.selectedType = "oauth-personal";
};
description = "JSON config for gemini-cli";
};
@@ -81,12 +86,12 @@ in
};
defaultModel = lib.mkOption {
type = lib.types.str;
default = "gemini-2.5-pro";
type = lib.types.nullOr lib.types.str;
default = null;
example = "gemini-2.5-flash";
description = ''
The default model to use for the CLI.
Will be set as $GEMINI_MODEL.
Will be set as $GEMINI_MODEL when configured.
'';
};
@@ -138,7 +143,9 @@ in
file.".gemini/settings.json" = lib.mkIf (cfg.settings != { }) {
source = jsonFormat.generate "gemini-cli-settings.json" cfg.settings;
};
sessionVariables.GEMINI_MODEL = cfg.defaultModel;
sessionVariables = lib.mkIf (cfg.defaultModel != null) {
GEMINI_MODEL = cfg.defaultModel;
};
};
}
{

View File

@@ -24,7 +24,8 @@ let
meliAccounts = (lib.attrsets.mapAttrs (name: value: (mkMeliAccounts name value)) enabledAccounts);
mkMeliAccounts = (
name: account: {
name: account:
{
root_mailbox = "${config.accounts.email.maildirBasePath}/${account.maildir.path}";
format = "Maildir";
identity = account.address;
@@ -33,6 +34,14 @@ let
send_mail = mkSmtp account;
mailboxes = account.meli.mailboxAliases;
}
// lib.optionalAttrs (account.flavor == "fastmail.com") {
server_username = account.userName;
server_password_command = lib.concatMapStringsSep " " lib.escapeShellArg account.passwordCommand;
format = "jmap";
server_url = "https://api.fastmail.com/jmap/session";
use_token = true;
}
// account.meli.settings
);
mkSmtp = account: {
@@ -153,6 +162,14 @@ in
};
description = "Folder display name";
};
settings = mkOption {
type = types.submodule {
freeformType = tomlFormat.type;
};
default = { };
description = "Account specific meli configuration";
};
};
}
)
@@ -160,6 +177,15 @@ in
};
};
config = mkIf config.programs.meli.enable {
assertions = [
{
assertion = cfg.settings ? accounts == false;
message = ''
programs.meli.settings.accounts override the accounts.email values.
Use per-email accounts.email.<ACCOUNT>.meli.settings instead'';
}
];
home.packages = [ config.programs.meli.package ];
# Generate meli configuration from email accounts

View File

@@ -7,121 +7,46 @@
let
inherit (lib)
concatMapStringsSep
literalExpression
mkEnableOption
mkIf
mkOption
mkRemovedOptionModule
mkPackageOption
optionals
types
;
cfg = config.programs.neovim;
fileType =
(import ../lib/file-type.nix {
inherit (config.home) homeDirectory;
inherit lib pkgs;
}).fileType;
inherit
(
(import ../lib/file-type.nix {
inherit (config.home) homeDirectory;
inherit lib pkgs;
})
)
fileType
;
jsonFormat = pkgs.formats.json { };
pluginWithConfigType = types.submodule {
options = {
config = mkOption {
type = types.nullOr types.lines;
description = "Script to configure this plugin. The scripting language should match type.";
default = null;
};
type = mkOption {
type = types.either (types.enum [
"lua"
"viml"
"teal"
"fennel"
]) types.str;
description = "Language used in config. Configurations are aggregated per-language.";
default = "viml";
};
optional = mkEnableOption "optional" // {
description = "Don't load by default (load with :packadd)";
};
plugin = lib.mkPackageOption pkgs.vimPlugins "plugin" {
default = null;
example = "pkgs.vimPlugins.nvim-treesitter";
pkgsText = "pkgs.vimPlugins";
};
runtime = mkOption {
default = { };
# passing actual "${xdg.configHome}/nvim" as basePath was a bit tricky
# due to how fileType.target is implemented
type = fileType "programs.neovim.plugins._.runtime" "{var}`xdg.configHome/nvim`" "nvim";
example = literalExpression ''
{ "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
'';
description = ''
Set of files that have to be linked in nvim config folder.
'';
};
};
};
allPlugins =
cfg.plugins
++ lib.optional cfg.coc.enable {
type = "viml";
plugin = cfg.coc.package;
config = cfg.coc.pluginConfig;
optional = false;
};
luaPackages = cfg.finalPackage.unwrapped.lua.pkgs;
resolvedExtraLuaPackages = cfg.extraLuaPackages luaPackages;
extraMakeWrapperArgs = lib.optionalString (
cfg.extraPackages != [ ]
) ''--suffix PATH : "${lib.makeBinPath cfg.extraPackages}"'';
extraMakeWrapperLuaCArgs =
lib.optionalString (resolvedExtraLuaPackages != [ ])
''--suffix LUA_CPATH ";" "${
lib.concatMapStringsSep ";" luaPackages.getLuaCPath resolvedExtraLuaPackages
}"'';
extraMakeWrapperLuaArgs =
lib.optionalString (resolvedExtraLuaPackages != [ ])
''--suffix LUA_PATH ";" "${
lib.concatMapStringsSep ";" luaPackages.getLuaPath resolvedExtraLuaPackages
}"'';
in
{
meta.maintainers = with lib.maintainers; [ khaneliman ];
imports = [
(mkRemovedOptionModule [
"programs"
"neovim"
"withPython"
] "Python2 support has been removed from neovim.")
(mkRemovedOptionModule [
"programs"
"neovim"
"extraPythonPackages"
] "Python2 support has been removed from neovim.")
(mkRemovedOptionModule [ "programs" "neovim" "configure" ] ''
programs.neovim.configure is deprecated.
Other programs.neovim options can override its settings or ignore them.
Please use the other options at your disposal:
configure.packages.*.opt -> programs.neovim.plugins = [ { plugin = ...; optional = true; }]
configure.packages.*.start -> programs.neovim.plugins = [ { plugin = ...; }]
configure.customRC -> programs.neovim.extraConfig
'')
];
options = {
programs.neovim = {
enable = mkEnableOption "Neovim";
package = mkPackageOption pkgs "neovim" { default = "neovim-unwrapped"; };
finalPackage = mkOption {
type = types.package;
readOnly = true;
description = "Resulting customized neovim package.";
};
# Aliases
viAlias = mkOption {
type = types.bool;
default = false;
@@ -146,6 +71,17 @@ in
'';
};
defaultEditor = mkOption {
type = types.bool;
default = false;
description = ''
Whether to configure {command}`nvim` as the default
editor using the {env}`EDITOR` and {env}`VISUAL`
environment variables.
'';
};
# Providers & Runtimes
withNodeJs = mkOption {
type = types.bool;
default = false;
@@ -155,11 +91,12 @@ in
'';
};
withRuby = mkOption {
type = types.nullOr types.bool;
default = true;
withPerl = mkOption {
type = types.bool;
default = false;
description = ''
Enable ruby provider.
Enable perl provider. Set to `true` to
use Perl plugins.
'';
};
@@ -172,21 +109,16 @@ in
'';
};
withRuby = mkOption {
type = types.nullOr types.bool;
default = true;
description = ''
Enable ruby provider.
'';
};
extraPython3Packages = mkOption {
# In case we get a plain list, we need to turn it into a function,
# as expected by the function in nixpkgs.
# The only way to do so is to call `const`, which will ignore its input.
type =
let
fromType = types.listOf types.package;
in
types.coercedTo fromType (lib.flip lib.warn lib.const ''
Assigning a plain list to extraPython3Packages is deprecated.
Please assign a function taking a package set as argument, so
extraPython3Packages = [ pkgs.python3Packages.xxx ];
should become
extraPython3Packages = ps: [ ps.xxx ];
'') (types.functionTo fromType);
type = types.functionTo (types.listOf types.package);
default = _: [ ];
defaultText = literalExpression "ps: [ ]";
example = literalExpression "pyPkgs: with pyPkgs; [ python-language-server ]";
@@ -198,21 +130,8 @@ in
'';
};
# We get the Lua package from the final package and use its
# Lua packageset to evaluate the function that this option was set to.
# This ensures that we always use the same Lua version as the Neovim package.
extraLuaPackages = mkOption {
type =
let
fromType = types.listOf types.package;
in
types.coercedTo fromType (lib.flip lib.warn lib.const ''
Assigning a plain list to extraLuaPackages is deprecated.
Please assign a function taking a package set as argument, so
extraLuaPackages = [ pkgs.lua51Packages.xxx ];
should become
extraLuaPackages = ps: [ ps.xxx ];
'') (types.functionTo fromType);
type = types.functionTo (types.listOf types.package);
default = _: [ ];
defaultText = literalExpression "ps: [ ]";
example = literalExpression "luaPkgs: with luaPkgs; [ luautf8 ]";
@@ -224,8 +143,34 @@ in
'';
};
# Wrapper Configuration
extraName = mkOption {
type = types.str;
default = "";
description = ''
Extra name appended to the wrapper package name.
'';
};
autowrapRuntimeDeps = mkOption {
type = types.bool;
default = true;
description = ''
Whether to automatically wrap the binary with the runtime dependencies of the plugins.
'';
};
waylandSupport = mkOption {
type = types.bool;
default = pkgs.stdenv.isLinux;
defaultText = literalExpression "pkgs.stdenv.isLinux";
description = ''
Whether to enable Wayland clipboard support.
'';
};
extraWrapperArgs = mkOption {
type = with types; listOf str;
type = types.listOf types.str;
default = [ ];
example = literalExpression ''
[
@@ -246,54 +191,14 @@ in
'';
};
generatedConfigViml = mkOption {
type = types.lines;
visible = true;
readOnly = true;
description = ''
Generated vimscript config.
'';
};
generatedConfigs = mkOption {
type = types.attrsOf types.lines;
visible = true;
readOnly = true;
example = literalExpression ''
{
viml = '''
" Generated by home-manager
map <leader> ,
''';
lua = '''
-- Generated by home-manager
vim.opt.background = "dark"
''';
}'';
description = ''
Generated configurations with as key their language (set via type).
'';
};
package = lib.mkPackageOption pkgs "neovim" { default = "neovim-unwrapped"; };
finalPackage = mkOption {
type = types.package;
readOnly = true;
description = "Resulting customized neovim package.";
};
defaultEditor = mkOption {
type = types.bool;
default = false;
description = ''
Whether to configure {command}`nvim` as the default
editor using the {env}`EDITOR` and {env}`VISUAL`
environment variables.
'';
extraPackages = mkOption {
type = types.listOf types.package;
default = [ ];
example = literalExpression "[ pkgs.shfmt ]";
description = "Extra packages available to nvim.";
};
# Configuration & Plugins
extraConfig = mkOption {
type = types.lines;
default = "";
@@ -308,45 +213,92 @@ in
extraLuaConfig = mkOption {
type = types.lines;
default = "";
example = ''
vim.opt.nobackup = true
example = lib.literalExpression ''
let
nvimEarlyInit = lib.mkOrder 500 "set rtp+=vim.opt.rtp:prepend('/home/user/myplugin')";
nvimLateInit = lib.mkAfter "vim.opt.signcolumn = 'auto:1-3'";
in
lib.mkMerge [ nvimEarlyInit nvimLateInit ];
'';
description = ''
Custom lua lines.
Content to be added to {file}`init.lua`.
To specify the order, use `lib.mkOrder`, `lib.mkBefore`, `lib.mkAfter`.
'';
};
extraPackages = mkOption {
type = with types; listOf package;
default = [ ];
example = literalExpression "[ pkgs.shfmt ]";
description = "Extra packages available to nvim.";
};
plugins =
let
pluginWithConfigType = types.submodule {
options = {
config = mkOption {
type = types.nullOr types.lines;
description = "Script to configure this plugin. The scripting language should match type.";
default = null;
};
plugins = mkOption {
type = with types; listOf (either package pluginWithConfigType);
default = [ ];
example = literalExpression ''
with pkgs.vimPlugins; [
yankring
vim-nix
{ plugin = vim-startify;
config = "let g:startify_change_to_vcs_root = 0";
}
]
'';
description = ''
List of vim plugins to install optionally associated with
configuration to be placed in init.vim.
type = mkOption {
type = types.either (types.enum [
"lua"
"viml"
"teal"
"fennel"
]) types.str;
description = "Language used in config. Configurations are aggregated per-language.";
default = "viml";
};
This option is mutually exclusive with {var}`configure`.
'';
};
optional = mkEnableOption "optional" // {
description = "Don't load by default (load with :packadd)";
};
plugin = mkPackageOption pkgs.vimPlugins "plugin" {
default = null;
example = "pkgs.vimPlugins.nvim-treesitter";
pkgsText = "pkgs.vimPlugins";
};
runtime = mkOption {
default = { };
# passing actual "${xdg.configHome}/nvim" as basePath was a bit tricky
# due to how fileType.target is implemented
type = fileType "programs.neovim.plugins._.runtime" "{var}`xdg.configHome/nvim`" "nvim";
example = literalExpression ''
{ "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
'';
description = ''
Set of files that have to be linked in nvim config folder.
'';
};
};
};
in
mkOption {
type = types.listOf (types.either types.package pluginWithConfigType);
default = [ ];
example = literalExpression ''
with pkgs.vimPlugins;
[
yankring
vim-nix
{ plugin = vim-startify;
config = "let g:startify_change_to_vcs_root = 0";
}
]
'';
description = ''
List of vim plugins to install optionally associated with
configuration to be placed in init.vim.
This option is mutually exclusive with {var}`configure`.
'';
};
coc = {
enable = mkEnableOption "Coc";
package = lib.mkPackageOption pkgs "coc-nvim" {
package = mkPackageOption pkgs "coc-nvim" {
default = [
"vimPlugins"
"coc-nvim"
@@ -376,7 +328,7 @@ in
filetypes = [ "haskell" "lhaskell" ];
};
};
};
}
'';
description = ''
Extra configuration lines to add to
@@ -393,11 +345,52 @@ in
description = "Script to configure CoC. Must be viml.";
};
};
# Generated / Read-Only
generatedConfigViml = mkOption {
type = types.lines;
visible = true;
readOnly = true;
description = ''
Generated vimscript config.
'';
};
generatedConfigs = mkOption {
type = types.attrsOf types.lines;
visible = true;
readOnly = true;
example = literalExpression ''
{
viml = '''
" Generated by home-manager
map <leader> ,
''';
lua = '''
-- Generated by home-manager
vim.opt.background = "dark"
''';
}
'';
description = ''
Generated configurations with as key their language (set via type).
'';
};
};
};
config =
config = mkIf cfg.enable (
let
allPlugins =
cfg.plugins
++ lib.optional cfg.coc.enable {
type = "viml";
plugin = cfg.coc.package;
config = cfg.coc.pluginConfig;
optional = false;
};
defaultPlugin = {
type = "viml";
plugin = null;
@@ -413,81 +406,100 @@ in
suppressNotVimlConfig = p: if p.type != "viml" then p // { config = null; } else p;
neovimConfig = pkgs.neovimUtils.makeNeovimConfig {
# Lua & Python Package Resolution
luaPackages = cfg.finalPackage.unwrapped.lua.pkgs;
resolvedExtraLuaPackages = cfg.extraLuaPackages luaPackages;
# Wrapper Arguments Construction
extraMakeWrapperArgs = optionals (cfg.extraPackages != [ ]) [
"--suffix"
"PATH"
":"
(lib.makeBinPath cfg.extraPackages)
];
extraMakeWrapperLuaCArgs = optionals (resolvedExtraLuaPackages != [ ]) [
"--suffix"
"LUA_CPATH"
";"
(concatMapStringsSep ";" luaPackages.getLuaCPath resolvedExtraLuaPackages)
];
extraMakeWrapperLuaArgs = optionals (resolvedExtraLuaPackages != [ ]) [
"--suffix"
"LUA_PATH"
";"
(concatMapStringsSep ";" luaPackages.getLuaPath resolvedExtraLuaPackages)
];
wrappedNeovim' = pkgs.wrapNeovimUnstable cfg.package {
withNodeJs = cfg.withNodeJs || cfg.coc.enable;
plugins = map suppressNotVimlConfig pluginsNormalized;
inherit (cfg)
extraPython3Packages
withPython3
withRuby
withPerl
viAlias
vimAlias
extraName
autowrapRuntimeDeps
waylandSupport
;
withNodeJs = cfg.withNodeJs || cfg.coc.enable;
plugins = map suppressNotVimlConfig pluginsNormalized;
customRC = cfg.extraConfig;
neovimRcContent = cfg.extraConfig;
wrapperArgs =
cfg.extraWrapperArgs ++ extraMakeWrapperArgs ++ extraMakeWrapperLuaCArgs ++ extraMakeWrapperLuaArgs;
wrapRc = false;
};
wrappedNeovim' = pkgs.wrapNeovimUnstable cfg.package (
neovimConfig
// {
wrapperArgs =
(lib.escapeShellArgs (neovimConfig.wrapperArgs ++ cfg.extraWrapperArgs))
+ " "
+ extraMakeWrapperArgs
+ " "
+ extraMakeWrapperLuaCArgs
+ " "
+ extraMakeWrapperLuaArgs;
wrapRc = false;
}
);
in
mkIf cfg.enable {
{
programs.neovim = {
generatedConfigViml = cfg.extraConfig;
programs.neovim.generatedConfigViml = neovimConfig.neovimRcContent;
generatedConfigs =
let
grouped = builtins.groupBy (x: x.type) pluginsNormalized;
configsOnly = lib.foldl (acc: p: if p.config != null then acc ++ [ p.config ] else acc) [ ];
in
lib.mapAttrs (_name: vals: lib.concatStringsSep "\n" (configsOnly vals)) grouped;
programs.neovim.generatedConfigs =
let
grouped = lib.lists.groupBy (x: x.type) pluginsNormalized;
configsOnly = lib.foldl (acc: p: if p.config != null then acc ++ [ p.config ] else acc) [ ];
in
lib.mapAttrs (name: vals: lib.concatStringsSep "\n" (configsOnly vals)) grouped;
home.packages = [ cfg.finalPackage ];
home.sessionVariables = mkIf cfg.defaultEditor {
EDITOR = "nvim";
VISUAL = "nvim";
finalPackage = wrappedNeovim';
};
home.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
home = {
packages = [ cfg.finalPackage ];
xdg.configFile =
let
hasLuaConfig = lib.hasAttr "lua" config.programs.neovim.generatedConfigs;
in
lib.mkMerge (
# writes runtime
(map (x: x.runtime) pluginsNormalized)
++ [
{
"nvim/init.lua" =
let
luaRcContent =
lib.optionalString (
wrappedNeovim'.initRc != ""
) "vim.cmd [[source ${pkgs.writeText "nvim-init-home-manager.vim" wrappedNeovim'.initRc}]]\n"
+ config.programs.neovim.extraLuaConfig
+ lib.optionalString hasLuaConfig config.programs.neovim.generatedConfigs.lua;
in
mkIf (luaRcContent != "") { text = luaRcContent; };
sessionVariables = mkIf cfg.defaultEditor {
EDITOR = "nvim";
VISUAL = "nvim";
};
"nvim/coc-settings.json" = mkIf cfg.coc.enable {
source = jsonFormat.generate "coc-settings.json" cfg.coc.settings;
};
}
]
);
shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
};
programs.neovim.finalPackage = wrappedNeovim';
};
programs.neovim.extraLuaConfig = lib.mkMerge [
(lib.mkIf (wrappedNeovim'.initRc != "") (
lib.mkBefore "vim.cmd [[source ${pkgs.writeText "nvim-init-home-manager.vim" wrappedNeovim'.initRc}]]"
))
(lib.mkIf (lib.hasAttr "lua" cfg.generatedConfigs) (lib.mkAfter cfg.generatedConfigs.lua))
];
xdg.configFile = lib.mkMerge (
# writes runtime
(map (x: x.runtime) pluginsNormalized)
++ [
{
"nvim/init.lua" = mkIf (cfg.extraLuaConfig != "") {
text = cfg.extraLuaConfig;
};
"nvim/coc-settings.json" = mkIf cfg.coc.enable {
source = jsonFormat.generate "coc-settings.json" cfg.coc.settings;
};
}
]
);
}
);
}

View File

@@ -66,10 +66,7 @@ in
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = pkgs.stdenv.hostPlatform.isLinux;
message = "niriswitcher is only available on Linux.";
}
(lib.hm.assertions.assertPlatform "programs.niriswitcher" pkgs lib.platforms.linux)
];
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];

View File

@@ -133,7 +133,7 @@ in
The attribute name becomes the command filename, and the value is either:
- Inline content as a string
- A path to a file containing the command content
Commands are stored in {file}`$XDG_CONFIG_HOME/.config/opencode/command/` directory.
Commands are stored in {file}`$XDG_CONFIG_HOME/opencode/command/` directory.
'';
example = lib.literalExpression ''
{
@@ -162,7 +162,7 @@ in
The attribute name becomes the agent filename, and the value is either:
- Inline content as a string
- A path to a file containing the agent content
Agents are stored in {file}`$XDG_CONFIG_HOME/.config/opencode/agent/` directory.
Agents are stored in {file}`$XDG_CONFIG_HOME/opencode/agent/` directory.
'';
example = lib.literalExpression ''
{
@@ -183,6 +183,49 @@ in
'';
};
skills = lib.mkOption {
type = lib.types.either (lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path)) lib.types.path;
default = { };
description = ''
Custom agent skills for opencode.
This option can either be:
- An attribute set defining skills
- A path to a directory containing multiple skill folders
If an attribute set is used, the attribute name becomes the skill directory name,
and the value is either:
- Inline content as a string (creates `opencode/skill/<name>/SKILL.md`)
- A path to a file (creates `opencode/skill/<name>/SKILL.md`)
- A path to a directory (creates `opencode/skill/<name>/` with all files)
If a path is used, it is expected to contain one folder per skill name, each
containing a {file}`SKILL.md`. The directory is symlinked to
{file}`$XDG_CONFIG_HOME/opencode/skill/`.
See <https://opencode.ai/docs/skills/> for the documentation.
'';
example = lib.literalExpression ''
{
git-release = '''
---
name: git-release
description: Create consistent releases and changelogs
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command
''';
# A skill can also be a directory containing SKILL.md and other files.
data-analysis = ./skills/data-analysis;
}
'';
};
themes = mkOption {
type = lib.types.attrsOf (lib.types.either jsonFormat.type lib.types.path);
default = { };
@@ -199,6 +242,13 @@ in
};
config = mkIf cfg.enable {
assertions = [
{
assertion = !lib.isPath cfg.skills || lib.pathIsDirectory cfg.skills;
message = "`programs.opencode.skills` must be a directory when set to a path";
}
];
home.packages = mkIf (cfg.package != null) [ cfg.package ];
xdg.configFile = {
@@ -227,6 +277,11 @@ in
text = cfg.rules;
})
);
"opencode/skill" = mkIf (lib.isPath cfg.skills) {
source = cfg.skills;
recursive = true;
};
}
// lib.mapAttrs' (
name: content:
@@ -240,6 +295,18 @@ in
if lib.isPath content then { source = content; } else { text = content; }
)
) cfg.agents
// lib.mapAttrs' (
name: content:
if lib.isPath content && lib.pathIsDirectory content then
lib.nameValuePair "opencode/skill/${name}" {
source = content;
recursive = true;
}
else
lib.nameValuePair "opencode/skill/${name}/SKILL.md" (
if lib.isPath content then { source = content; } else { text = content; }
)
) (if builtins.isAttrs cfg.skills then cfg.skills else { })
// lib.mapAttrs' (
name: content:
lib.nameValuePair "opencode/themes/${name}.json" (

View File

@@ -30,44 +30,42 @@ let
mapAttrsToList (name: value: ''${name}="${lib.escape [ ''"'' "\\" ] (toString value)}"'') envStr
);
bindOptions = {
address = mkOption {
type = types.str;
default = "localhost";
example = "example.org";
description = "The address where to bind the port.";
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 8080;
description = "Specifies port number to bind on bind address.";
};
};
dynamicForwardModule = types.submodule { options = bindOptions; };
forwardModule = types.submodule {
options = {
bind = bindOptions;
host = {
mkAddressPortModule =
{
actionType,
nullableAddress ? actionType == "forward",
}:
types.submodule {
options = {
address = mkOption {
type = types.nullOr types.str;
default = null;
type = if nullableAddress then types.nullOr types.str else types.str;
default = if nullableAddress then null else "localhost";
example = "example.org";
description = "The address where to forward the traffic to.";
description = "The address to ${actionType} to.";
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 80;
description = "Specifies port number to forward the traffic to.";
example = 8080;
description = "Specifies port number to ${actionType} to.";
};
};
};
dynamicForwardModule = mkAddressPortModule { actionType = "bind"; };
forwardModule = types.submodule {
options = {
bind = mkOption {
type = mkAddressPortModule { actionType = "bind"; };
description = "Local port binding options";
};
host = mkOption {
type = mkAddressPortModule { actionType = "forward"; };
description = "Host port binding options";
};
};
};
matchBlockModule = types.submodule {
@@ -383,6 +381,18 @@ let
example = "10m";
description = "Whether control socket should remain open in the background.";
};
kexAlgorithms = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
"curve25519-sha256@libssh.org"
"diffie-hellman-group-exchange-sha256"
];
description = ''
Specifies the available KEX (Key Exchange) algorithms.
'';
};
};
# config.host = mkDefault dagName;
@@ -430,6 +440,9 @@ let
++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards
++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards
++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards
++ optional (
cf.kexAlgorithms != null
) " KexAlgorithms ${builtins.concatStringsSep "," cf.kexAlgorithms}"
++ [
(lib.generators.toKeyValue {
mkKeyValue = lib.generators.mkKeyValueDefault { } " ";

View File

@@ -106,13 +106,13 @@ in
];
programs.bash.initExtra = lib.mkIf cfg.enableBashIntegration ''
eval "$(${lib.getExe cfg.package} init bash)"
source ${cfg.package}/share/television/completion.bash
'';
programs.zsh.initContent = lib.mkIf cfg.enableZshIntegration ''
eval "$(${lib.getExe cfg.package} init zsh)"
source ${cfg.package}/share/television/completion.zsh
'';
programs.fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration ''
${lib.getExe cfg.package} init fish | source
source ${cfg.package}/share/television/completion.fish
'';
};
}

View File

@@ -12,6 +12,7 @@ let
packageVersion = if cfg.package != null then lib.getVersion cfg.package else null;
themeIsToml = lib.versionAtLeast packageVersion "0.15.0";
versionPost0_17 = lib.versionAtLeast packageVersion "0.17.0";
in
{
meta.maintainers = [ lib.maintainers.leiserfg ];
@@ -43,7 +44,12 @@ in
useLayerShell = lib.mkOption {
type = lib.types.bool;
default = true;
description = "If vicinae should use the layer shell";
description = ''
Whether vicinae should use the layer shell.
If you are using version 0.17 or newer, you should use
{option}.programs.vicinae.settings.launcher_window.layer_shell.enabled = false
instead.
'';
};
extensions = lib.mkOption {
@@ -127,27 +133,22 @@ in
settings = lib.mkOption {
inherit (jsonFormat) type;
default = { };
description = "Settings written as JSON to `~/.config/vicinae/vicinae.json.";
example = lib.literalExpression ''
{
faviconService = "twenty";
font = {
size = 10;
};
popToRootOnClose = false;
rootSearch = {
searchFiles = false;
};
favicon_service = "twenty";
font.normal.size = 10;
pop_to_root_on_close=false;
search_files_in_root= false;
theme = {
name = "vicinae-dark";
};
window = {
csd = true;
opacity = 0.95;
rounding = 10;
dark.name = "vicinae-dark";
light.name = "vicinae-light";
};
}
'';
description = ''
Settings written as JSON to {file}`~/.config/vicinae/settings.json`.
See {command}`vicinae config default`.
'';
};
};
@@ -158,6 +159,10 @@ in
assertion = cfg.systemd.enable -> cfg.package != null;
message = "{option}programs.vicinae.systemd.enable requires non null {option}programs.vicinae.package";
}
{
assertion = !cfg.useLayerShell -> !versionPost0_17;
message = ''After version 0.17, if you want to explicitly disable the use of layer shell, you need to set {option}.programs.vicinae.settings.launcher_window.layer_shell.enabled = false.'';
}
];
lib.vicinae.mkExtension = (
{
@@ -223,10 +228,11 @@ in
source = themeFormat.generate "vicinae-${name}-theme" theme;
}
) cfg.themes;
settingsPath = if versionPost0_17 then "vicinae/settings.json" else "vicinae/vicinae.json";
in
{
configFile = {
"vicinae/vicinae.json" = lib.mkIf (cfg.settings != { }) {
"${settingsPath}" = lib.mkIf (cfg.settings != { }) {
source = jsonFormat.generate "vicinae-settings" cfg.settings;
};
}
@@ -250,14 +256,16 @@ in
PartOf = [ cfg.systemd.target ];
};
Service = {
EnvironmentFile = pkgs.writeText "vicinae-env" ''
USE_LAYER_SHELL=${if cfg.useLayerShell then builtins.toString 1 else builtins.toString 0}
'';
Type = "simple";
ExecStart = "${lib.getExe' cfg.package "vicinae"} server";
Restart = "always";
RestartSec = 5;
KillMode = "process";
EnvironmentFile = lib.mkIf (!versionPost0_17) (
pkgs.writeText "vicinae-env" ''
USE_LAYER_SHELL=${if cfg.useLayerShell then builtins.toString 1 else builtins.toString 0}
''
);
};
Install = lib.mkIf cfg.systemd.autoStart {
WantedBy = [ cfg.systemd.target ];

View File

@@ -9,12 +9,27 @@ let
cfg = config.programs.yt-dlp;
renderSettings = lib.mapAttrsToList (
configAtom =
with types;
oneOf [
bool
int
str
];
renderSingleOption =
name: value:
if lib.isBool value then
if value then "--${name}" else "--no-${name}"
else
"--${name} ${toString value}"
"--${name} ${toString value}";
renderSettings = lib.mapAttrsToList (
name: value:
if lib.isList value then
lib.concatStringsSep "\n" (map (renderSingleOption name) value)
else
renderSingleOption name value
);
in
@@ -27,13 +42,7 @@ in
package = lib.mkPackageOption pkgs "yt-dlp" { };
settings = mkOption {
type =
with types;
attrsOf (oneOf [
bool
int
str
]);
type = with types; attrsOf (either configAtom (listOf configAtom));
default = { };
example = lib.literalExpression ''
{
@@ -42,6 +51,10 @@ in
sub-langs = "all";
downloader = "aria2c";
downloader-args = "aria2c:'-c -x8 -s8 -k1M'";
color = [
"stdout:no_color"
"stderr:always"
];
}
'';
description = ''

View File

@@ -2,6 +2,7 @@
config,
lib,
pkgs,
options,
...
}:
let
@@ -100,9 +101,18 @@ in
};
dotDir = mkOption {
default = homeDir;
defaultText = "`config.home.homeDirectory`";
example = "`\${config.xdg.configHome}/zsh`";
default =
if config.xdg.enable && lib.versionAtLeast config.home.stateVersion "26.05" then
"${config.xdg.configHome}/zsh"
else
homeDir;
defaultText = lib.literalExpression ''
if config.xdg.enable && lib.versionAtLeast config.home.stateVersion "26.05" then
"''${config.xdg.configHome}/zsh"
else
config.home.homeDirectory
'';
example = literalExpression ''"''${config.xdg.configHome}/zsh"'';
description = ''
Directory where the zsh configuration and more should be located,
relative to the users home directory. The default is the home
@@ -391,7 +401,24 @@ in
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
]
++
lib.optionals
(
config.xdg.enable
&& !lib.versionAtLeast config.home.stateVersion "26.05"
&& options.programs.zsh.dotDir.highestPrio >= 1500
)
[
''
The default value of `programs.zsh.dotDir` will change in future versions.
You are currently using the legacy default (home directory) because `home.stateVersion` is less than "26.05".
To silence this warning and lock in the current behavior, set:
programs.zsh.dotDir = config.home.homeDirectory;
To adopt the new behavior (XDG config directory), set:
programs.zsh.dotDir = "''${config.xdg.configHome}/zsh";
''
];
}
(mkIf (cfg.envExtra != "") {
@@ -412,7 +439,7 @@ in
(mkIf (dotDirAbs != homeDir) {
home.file."${dotDirRel}/.zshenv".text = ''
export ZDOTDIR=${dotDirAbs}
${config.lib.zsh.export "ZDOTDIR" dotDirAbs}
'';
# When dotDir is set, only use ~/.zshenv to source ZDOTDIR/.zshenv,
@@ -420,7 +447,7 @@ in
# already set correctly (by e.g. spawning a zsh inside a zsh), all env
# vars still get exported
home.file.".zshenv".text = ''
source ${dotDirAbs}/.zshenv
source ${lib.escapeShellArg "${dotDirAbs}/.zshenv"}
'';
})

View File

@@ -1,9 +1,12 @@
{ config, lib, ... }:
let
cfg = config.programs.zsh;
stripSlash = lib.removeSuffix "/";
in
rec {
homeDir = config.home.homeDirectory;
# Raw home directory, no trailing slash.
homeDir = stripSlash config.home.homeDirectory;
/*
Escape a path string for shell usage and remove trailing slashes.
@@ -18,10 +21,11 @@ rec {
cleanPathStr "/path/to/dir/" => "'/path/to/dir'"
cleanPathStr "path with spaces" => "'path with spaces'"
*/
cleanPathStr = pathStr: lib.escapeShellArg (lib.removeSuffix "/" pathStr);
cleanPathStr = pathStr: lib.escapeShellArg (stripSlash pathStr);
/*
Convert an absolute path to a relative path by stripping the home directory prefix.
Returns the raw path (unescaped) for use in home.file keys.
This function converts absolute paths within the home directory to relative paths
by removing the home directory prefix. Paths already relative are returned as-is.
@@ -30,19 +34,22 @@ rec {
Type: String -> String
Example:
mkRelPathStr "/home/user/config" => "'config'"
mkRelPathStr "config" => "'config'"
mkRelPathStr "/home/user/config" => "config"
mkRelPathStr "config" => "config"
mkRelPathStr "/home/user" => "."
mkRelPathStr "/etc/config" => <error>
*/
mkRelPathStr =
pathStr:
# is already a relative path
if (!lib.hasPrefix "/" pathStr) then
cleanPathStr pathStr
# is an absolute path within home dir
else if (lib.hasPrefix homeDir pathStr) then
cleanPathStr (lib.removePrefix "${homeDir}/" pathStr)
# is an absolute path not in home dir
let
normPath = stripSlash pathStr;
in
if (!lib.hasPrefix "/" normPath) then
normPath
else if normPath == homeDir then
"."
else if (lib.hasPrefix "${homeDir}/" normPath) then
lib.removePrefix "${homeDir}/" normPath
else
throw ''
Attempted to convert an absolute path not within home directory to a
@@ -54,47 +61,47 @@ rec {
'';
/*
Convert a relative path to an absolute path by prepending the home directory.
Convert a relative path to an absolute path.
Returns RAW path (unescaped).
This function ensures paths are absolute by prepending the home directory
to relative paths. Already absolute paths are returned unchanged (after cleaning).
This function does NOT support shell variables.
Type: String -> String
Example:
mkAbsPathStr "config" => "'/home/user/config'"
mkAbsPathStr "/absolute/path" => "'/absolute/path'"
mkAbsPathStr "config" => "/home/user/config"
mkAbsPathStr "/absolute/path" => "/absolute/path"
*/
mkAbsPathStr =
pathStr: cleanPathStr ((lib.optionalString (!lib.hasPrefix "/" pathStr) "${homeDir}/") + pathStr);
pathStr:
let
normPath = stripSlash pathStr;
in
if lib.hasPrefix "/" normPath then normPath else "${homeDir}/${normPath}";
/*
Convert a path to absolute form while preserving shell variables for runtime expansion.
Convert a path to absolute form while preserving shell variables.
Returns RAW path (unescaped) unless vars are present (then preserves vars).
This function handles both literal paths and shell variable expressions.
Shell variables (containing '$') are preserved unescaped to allow runtime expansion.
Literal paths are made absolute and properly escaped for shell usage.
Literal paths are made absolute.
Type: String -> String
Example:
mkShellVarPathStr "config" => "'/home/user/config'"
mkShellVarPathStr "config" => "/home/user/config"
mkShellVarPathStr "$HOME/config" => "$HOME/config"
mkShellVarPathStr "\${XDG_CONFIG_HOME:-$HOME/.config}/app" => "\${XDG_CONFIG_HOME:-$HOME/.config}/app"
*/
mkShellVarPathStr =
pathStr:
let
cleanPath = lib.removeSuffix "/" pathStr;
hasShellVars = lib.hasInfix "$" cleanPath;
normPath = stripSlash pathStr;
hasShellVars = lib.hasInfix "$" normPath;
in
if hasShellVars then
# Does not escape shell variables, allowing them to be expanded at runtime
cleanPath
else
# For literal paths, make them absolute if needed and escape them
cleanPathStr ((lib.optionalString (!lib.hasPrefix "/" cleanPath) "${homeDir}/") + cleanPath);
if hasShellVars then normPath else mkAbsPathStr normPath;
dotDirAbs = mkAbsPathStr cfg.dotDir;
dotDirRel = mkRelPathStr cfg.dotDir;
@@ -107,5 +114,5 @@ rec {
Type: String
*/
pluginsDir = dotDirAbs + (lib.optionalString (homeDir == dotDirAbs) "/.zsh") + "/plugins";
pluginsDir = dotDirAbs + (lib.optionalString (mkRelPathStr cfg.dotDir == ".") "/.zsh") + "/plugins";
}

View File

@@ -136,7 +136,7 @@ let
);
in
{
meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
meta.maintainers = [ ];
options.services.activitywatch = {
enable = lib.mkEnableOption "ActivityWatch, an automated time tracker";

View File

@@ -117,6 +117,8 @@ in
systemd.user.services.easyeffects = {
Unit = {
Description = "Easyeffects daemon";
After = [ "graphical-session.target" ];
PartOf = [ "graphical-session.target" ];
};
Install.WantedBy = [ "graphical-session.target" ];

View File

@@ -107,10 +107,10 @@ in
'';
};
defaultEditor = mkOption rec {
defaultEditor = mkOption {
type = types.bool;
default = false;
example = !default;
example = true;
description = ''
Whether to configure {command}`emacsclient` as the default
editor using the {env}`EDITOR` and {env}`VISUAL`

View File

@@ -29,7 +29,7 @@ in
default = { };
description = ''
Configuration settings for hyprshell. All the avaiblable
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/CONFIGURE.md#config-options>
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/docs/CONFIGURE.md#config-options>
'';
};
@@ -38,7 +38,7 @@ in
default = "";
description = ''
CSS file for customizing hyprshell. All the available
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/CONFIGURE.md#css>
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/docs/CONFIGURE.md#css>
'';
};

View File

@@ -8,7 +8,6 @@ let
inherit (lib) mkOption types;
cfg = config.services.linux-wallpaperengine;
in
{
meta.maintainers = [ lib.hm.maintainers.ckgxrg ];
@@ -105,10 +104,10 @@ in
config = lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "services.linux-wallpaperengine" pkgs lib.platforms.linux)
({
{
assertion = cfg.wallpapers != null;
message = "linux-wallpaperengine: You must set at least one wallpaper";
})
}
];
home.packages = [ cfg.package ];
@@ -121,7 +120,7 @@ in
lib.cli.toGNUCommandLine { } {
screen-root = each.monitor;
inherit (each) scaling fps;
silent = each.audio.silent;
inherit (each.audio) silent;
noautomute = !each.audio.automute;
no-audio-processing = !each.audio.processing;
}
@@ -138,11 +137,12 @@ in
PartOf = [ "graphical-session.target" ];
};
Service = {
ExecStart =
lib.getExe cfg.package
+ (lib.optionalString (cfg.assetsPath != null) " --assets-dir ${cfg.assetsPath} ")
+ (lib.optionalString (cfg.clamping != null) "--clamping ${cfg.clamping} ")
+ (lib.strings.concatStringsSep " " args);
ExecStart = lib.concatStringsSep " " (
[ (lib.getExe cfg.package) ]
++ lib.optional (cfg.assetsPath != null) "--assets-dir ${cfg.assetsPath}"
++ lib.optional (cfg.clamping != null) "--clamping ${cfg.clamping}"
++ args
);
Restart = "on-failure";
};
Install = {

View File

@@ -75,7 +75,7 @@ let
in
{
meta.maintainers = [ lib.maintainers.foo-dogsquared ];
meta.maintainers = [ ];
options.services.mopidy = {
enable = lib.mkEnableOption "Mopidy music player daemon";

View File

@@ -22,6 +22,15 @@ in
package = lib.mkPackageOption pkgs "mpd" { };
enableSessionVariables = mkOption {
type = types.bool;
default = true;
description = ''
Whether to set {env}`MPD_HOST` {env}`MPD_PORT` environment variables
according to {option}`services.mpd.network`.
'';
};
musicDirectory = mkOption {
type = with types; either path str;
defaultText = lib.literalExpression ''
@@ -155,7 +164,13 @@ in
);
in
mkIf cfg.enable {
home.packages = [ cfg.package ];
home = {
packages = [ cfg.package ];
sessionVariables = mkIf cfg.enableSessionVariables {
MPD_HOST = mkIf (cfg.network.listenAddress != "any") cfg.network.listenAddress;
MPD_PORT = builtins.toString cfg.network.port;
};
};
services.mpd = lib.mkMerge [
(mkIf (lib.versionAtLeast config.home.stateVersion "22.11" && config.xdg.userDirs.enable) {

View File

@@ -103,7 +103,7 @@ let
settingsFormat = recollConfFormat { };
in
{
meta.maintainers = [ lib.maintainers.foo-dogsquared ];
meta.maintainers = [ ];
options.services.recoll = {
enable = lib.mkEnableOption "Recoll file index service";

View File

@@ -84,13 +84,11 @@ in
'';
in
{
bash.initExtra = lib.mkIf cfg.enableBashIntegration bashIntegration;
zsh.initContent = lib.mkIf cfg.enableZshIntegration bashIntegration;
fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration fishIntegration;
nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration nushellIntegration;
# $SSH_AUTH_SOCK has to be set early since other tools rely on it
bash.profileExtra = lib.mkIf cfg.enableBashIntegration (lib.mkOrder 900 bashIntegration);
fish.shellInit = lib.mkIf cfg.enableFishIntegration (lib.mkOrder 900 fishIntegration);
nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration (lib.mkOrder 900 nushellIntegration);
zsh.envExtra = lib.mkIf cfg.enableZshIntegration (lib.mkOrder 900 bashIntegration);
};
}

View File

@@ -430,10 +430,7 @@ in
# Do not install any user services if username is root.
config = mkIf (cfg.enable && config.home.username != "root") {
assertions = [
{
assertion = pkgs.stdenv.isLinux;
message = "This module is only available on Linux.";
}
(lib.hm.assertions.assertPlatform "systemd" pkgs lib.platforms.linux)
];
xdg.configFile = mkMerge [

View File

@@ -274,6 +274,26 @@ in
sed "s|${pkg.out}|$out|g" "$src" > "$dsk"
done
# Patch systemd user services
for svc in "$out/share/systemd/user"/*.service ; do
if ! grep -q "${pkg.out}" "$svc"; then
continue
fi
src="$(readlink "$svc")"
rm "$svc"
sed "s|${pkg.out}|$out|g" "$src" > "$svc"
done
# Patch DBus services
for svc in "$out/share/dbus-1/services"/*.service ; do
if ! grep -q "${pkg.out}" "$svc"; then
continue
fi
src="$(readlink "$svc")"
rm "$svc"
sed "s|${pkg.out}|$out|g" "$src" > "$svc"
done
shopt -u nullglob # Revert nullglob back to its normal default state
'';
}))

View File

@@ -130,6 +130,13 @@ let
stateVersion = lib.mkDefault "18.09";
};
# NOTE: Added 2025-12-27
# Avoid option change deprecation warning
# Remove after deprecation period
programs.zsh.dotDir = lib.mkIf (config.home.stateVersion == "18.09") (
lib.mkDefault "/home/hm-user"
);
# Avoid including documentation since this will cause
# unnecessary rebuilds of the tests.
manual.manpages.enable = lib.mkDefault false;

View File

@@ -1,4 +1,5 @@
{
generators-hyprconf = ./tohyprconf.nix;
generators-tokdl = ./tokdl.nix;
generators-toscfg-empty = ./toscfg-empty.nix;
generators-toscfg-example = ./toscfg-example.nix;

View File

@@ -0,0 +1,93 @@
$important=123
attrs-section {
bool=true
float=0.800000
int=5
null=null
string=abc
}
combined-attrs-nested-section {
a {
a=123
}
a {
b=123
}
a {
c=123
}
b {
a=123
}
b {
b=123
}
b {
c=123
}
}
combined-list-nested-section {
b {
c {
abc=123
}
}
}
combined-list-nested-section {
bar {
baz {
aaa=111
}
}
}
list-section=foo
list-section=bar
list-section=baz
list-with-strings-and-attrs=abc
list-with-strings-and-attrs {
a=123
}
list-with-strings-and-attrs=foo
list-with-strings-and-attrs {
b=321
}
nested-attrs-section {
a {
b {
c {
abc=123
}
}
}
foo {
bar {
baz {
aaa=111
}
}
}
}
nested-list-section {
a=123
}
nested-list-section {
b=123
}
nested-list-section {
c=123
}

View File

@@ -0,0 +1,70 @@
{ lib, ... }:
{
home.file."tohyprconf-result.txt".text = lib.hm.generators.toHyprconf {
attrs = rec {
"$important" = 123;
list-section = [
"foo"
"bar"
"baz"
];
attrs-section = {
string = "abc";
int = 5;
float = 0.8;
bool = true;
null = null;
};
nested-attrs-section = {
a = {
b = {
c = {
abc = 123;
};
};
};
foo = {
bar = {
baz = {
aaa = 111;
};
};
};
};
nested-list-section = [
{ a = 123; }
{ b = 123; }
{ c = 123; }
];
combined-list-nested-section = [
nested-attrs-section.a
nested-attrs-section.foo
];
combined-attrs-nested-section = {
a = nested-list-section;
b = nested-list-section;
};
list-with-strings-and-attrs = [
"abc"
{ a = 123; }
"foo"
{ b = 321; }
];
};
};
nmt.script = ''
assertFileContent \
home-files/tohyprconf-result.txt \
${./tohyprconf-result.txt}
'';
}

View File

@@ -2,4 +2,5 @@
lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
anyrun = ./basic-config.nix;
anyrun-empty-css = ./empty-css.nix;
}

View File

@@ -0,0 +1,10 @@
{
programs.anyrun = {
enable = true;
config.plugins = [ ];
};
nmt.script = ''
assertPathNotExists home-files/.config/anyrun/style.css
'';
}

View File

@@ -5,6 +5,8 @@
claude-code-assertion = ./assertion.nix;
claude-code-memory-management = ./memory-management.nix;
claude-code-memory-from-source = ./memory-from-source.nix;
claude-code-rules-dir = ./rules-dir.nix;
claude-code-rules-path = ./rules-path.nix;
claude-code-agents-dir = ./agents-dir.nix;
claude-code-commands-dir = ./commands-dir.nix;
claude-code-hooks-dir = ./hooks-dir.nix;

View File

@@ -0,0 +1,14 @@
{
programs.claude-code = {
enable = true;
rulesDir = ./rules;
};
nmt.script = ''
assertFileExists home-files/.claude/rules/test-rule.md
assertLinkExists home-files/.claude/rules/test-rule.md
assertFileContent \
home-files/.claude/rules/test-rule.md \
${./rules/test-rule.md}
'';
}

View File

@@ -0,0 +1,20 @@
{
programs.claude-code = {
enable = true;
rules = {
test-rule = ./test-rule.md;
inline-rule = ''
# Inline Rule
This is an inline rule for testing.
'';
};
};
nmt.script = ''
assertFileExists home-files/.claude/rules/test-rule.md
assertFileContent home-files/.claude/rules/test-rule.md \
${./test-rule.md}
assertFileExists home-files/.claude/rules/inline-rule.md
'';
}

View File

@@ -0,0 +1,9 @@
# Test Rule from Directory
This is a test rule loaded from a directory.
Used to verify rulesDir support functionality.
## Best Practices
- Write clean code
- Test thoroughly

View File

@@ -0,0 +1,9 @@
# Test Rule
This is a test rule loaded from a file path.
Used to verify path support functionality for rules.
## Guidelines
- Follow test conventions
- Maintain code quality

View File

@@ -41,5 +41,9 @@
assertFileExists home-files/.gemini/CONTEXT.md
assertFileContent home-files/.gemini/CONTEXT.md \
${./context-additional.md}
assertFileExists home-path/etc/profile.d/hm-session-vars.sh
assertFileNotRegex home-path/etc/profile.d/hm-session-vars.sh \
"GEMINI_MODEL"
'';
}

View File

@@ -1,6 +1,7 @@
{
programs.gemini-cli = {
enable = true;
defaultModel = "gemini-2.5-flash";
settings = {
theme = "Default";
vimMode = true;
@@ -28,5 +29,9 @@
${./changelog.toml}
assertFileContent home-files/.gemini/commands/git/fix.toml \
${./fix.toml}
assertFileExists home-path/etc/profile.d/hm-session-vars.sh
assertFileContains home-path/etc/profile.d/hm-session-vars.sh \
'export GEMINI_MODEL="gemini-2.5-flash"'
'';
}

View File

@@ -5,6 +5,9 @@ identity = "hm@example.com"
root_mailbox = "/home/hm-user/Mail/hm@example.com"
subscribed_mailboxes = ["Inbox", "Sent", "Trash", "Drafts"]
[accounts."hm@example.com".listing]
index_style = "compact"
[accounts."hm@example.com".mailboxes]
[accounts."hm@example.com".send_mail]

View File

@@ -16,7 +16,12 @@
};
accounts.email.accounts = {
"hm@example.com" = {
meli.enable = true;
meli = {
enable = true;
settings = {
listing.index_style = "compact";
};
};
smtp.port = 1848;
};
};

View File

@@ -2,6 +2,7 @@
neovim-plugin-config = ./plugin-config.nix;
neovim-coc-config = ./coc-config.nix;
neovim-runtime = ./runtime.nix;
neovim-wrapper-args = ./wrapper-args.nix;
# waiting for a nixpkgs patch
neovim-no-init = ./no-init.nix;

View File

@@ -34,15 +34,25 @@ lib.mkIf config.test.enableBig {
_module.args.pkgs = lib.mkForce realPkgs;
nmt.script = ''
vimout=$(mktemp)
echo "redir >> /dev/stdout | echo g:hmExtraConfig | echo g:hmPlugins | redir END" \
| ${pkgs.neovim}/bin/nvim -es -u "$TESTED/home-files/.config/nvim/init.lua" \
> "$vimout" || true
assertFileContains "$vimout" "HM_EXTRA_CONFIG"
assertFileContains "$vimout" "HM_PLUGINS_CONFIG"
nmt.script =
let
# Force evaluation of generatedConfigs.
luaConfig = config.programs.neovim.generatedConfigs.lua;
vimlConfig = config.programs.neovim.generatedConfigs.viml;
in
''
vimout=$(mktemp)
echo "redir >> /dev/stdout | echo g:hmExtraConfig | echo g:hmPlugins | redir END" \
| ${pkgs.neovim}/bin/nvim -es -u "$TESTED/home-files/.config/nvim/init.lua" \
> "$vimout" || true
assertFileContains "$vimout" "HM_EXTRA_CONFIG"
assertFileContains "$vimout" "HM_PLUGINS_CONFIG"
initLua="$TESTED/home-files/.config/nvim/init.lua"
assertFileContent $(normalizeStorePaths "$initLua") ${./plugin-config.expected}
'';
initLua="$TESTED/home-files/.config/nvim/init.lua"
assertFileContent $(normalizeStorePaths "$initLua") ${./plugin-config.expected}
# Verify generatedConfigs evaluated properly (issue #8371)
echo "Lua config length: ${toString (builtins.stringLength luaConfig)}"
echo "Viml config length: ${toString (builtins.stringLength vimlConfig)}"
'';
}

View File

@@ -0,0 +1,83 @@
{
lib,
pkgs,
...
}:
let
inherit (pkgs.stdenv.hostPlatform) isLinux;
dummyDep = pkgs.runCommand "dummy-dep" { } ''
mkdir -p $out/bin
echo "echo dummy" > $out/bin/dummy-dep-bin
chmod +x $out/bin/dummy-dep-bin
'';
dummyPlugin = pkgs.vimUtils.buildVimPlugin {
pname = "dummy-plugin";
version = "1.0";
src = pkgs.writeTextDir "plugin/dummy.vim" "\" dummy";
runtimeDeps = [ dummyDep ];
};
in
{
imports = [ ./stubs.nix ];
tests.stubs.wl-clipboard = { };
programs.neovim = {
enable = true;
extraName = "-my-suffix";
withPerl = true;
withPython3 = true;
withRuby = true;
withNodeJs = true;
autowrapRuntimeDeps = true;
waylandSupport = isLinux;
plugins = [ dummyPlugin ];
};
nmt.script = ''
nvimBin="home-path/bin/nvim"
assertBinaryContains() {
local file="$TESTED/$1"
if [[ $1 == /* ]]; then file="$1"; fi
if ! grep -a -qF -- "$2" "$file"; then
fail "Expected binary file '$1' to contain '$2' but it did not."
fi
}
# Ensure the main binary exists
assertFileExists "$nvimBin"
# 1. extraName: Check if the suffix is in the rplugin manifest path within the wrapper
assertBinaryContains "$nvimBin" "-my-suffix/rplugin.vim"
# 2. withPerl: Check if nvim-perl binary exists and host prog is set
assertFileExists "home-path/bin/nvim-perl"
assertBinaryContains "$nvimBin" "perl_host_prog="
# 3. withPython3: Check if nvim-python3 binary exists and host prog is set
assertFileExists "home-path/bin/nvim-python3"
assertBinaryContains "$nvimBin" "python3_host_prog="
# 4. withRuby: Check if nvim-ruby binary exists, GEM_HOME and host prog are set
assertFileExists "home-path/bin/nvim-ruby"
assertBinaryContains "$nvimBin" "GEM_HOME="
assertBinaryContains "$nvimBin" "ruby_host_prog="
# 5. withNodeJs: Check if nvim-node binary exists and host prog is set
assertFileExists "home-path/bin/nvim-node"
assertBinaryContains "$nvimBin" "node_host_prog="
# 6. waylandSupport: Check for wl-clipboard path in wrapper's PATH modification
# We check for the store path of wl-clipboard in the current pkgs
${lib.optionalString isLinux ''
assertBinaryContains "$nvimBin" "wl-clipboard-"
''}
# 7. autowrapRuntimeDeps: Check for dummyDep path in wrapper's PATH modification
assertBinaryContains "$nvimBin" "${dummyDep}/bin"
'';
}

View File

@@ -9,6 +9,10 @@
opencode-agents-path = ./agents-path.nix;
opencode-commands-path = ./commands-path.nix;
opencode-mixed-content = ./mixed-content.nix;
opencode-skills-inline = ./skills-inline.nix;
opencode-skills-path = ./skills-path.nix;
opencode-skills-directory = ./skills-directory.nix;
opencode-skills-bulk-directory = ./skills-bulk-directory.nix;
opencode-themes-inline = ./themes-inline.nix;
opencode-themes-path = ./themes-path.nix;
opencode-mcp-integration = ./mcp-integration.nix;

View File

@@ -0,0 +1,10 @@
---
name: git-release
description: Create consistent releases and changelogs
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command

View File

@@ -0,0 +1,9 @@
---
name: pdf-processing
description: Extract text and tables from PDF files
---
## What I do
- Extract text from PDFs
- Identify tables and structured data

View File

@@ -0,0 +1,9 @@
---
name: data-analysis
description: Help analyze datasets and results
---
## What I do
- Summarize datasets
- Suggest charts and metrics

View File

@@ -0,0 +1 @@
extra fixture file

View File

@@ -0,0 +1,15 @@
{
programs.opencode = {
enable = true;
skills = ./skills-bulk;
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/git-release/SKILL.md
assertFileExists home-files/.config/opencode/skill/pdf-processing/SKILL.md
assertFileContent home-files/.config/opencode/skill/git-release/SKILL.md \
${./skills-bulk/git-release/SKILL.md}
assertFileContent home-files/.config/opencode/skill/pdf-processing/SKILL.md \
${./skills-bulk/pdf-processing/SKILL.md}
'';
}

View File

@@ -0,0 +1,6 @@
---
name: git-release
description: Create consistent releases and changelogs
---
This is the bulk skillsDir fixture for git-release.

View File

@@ -0,0 +1,6 @@
---
name: pdf-processing
description: Extract text and tables from PDF files
---
This is the bulk skillsDir fixture for pdf-processing.

View File

@@ -0,0 +1,15 @@
{
programs.opencode = {
enable = true;
skills = {
data-analysis = ./skill-dir/data-analysis;
};
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/data-analysis/SKILL.md
assertFileExists home-files/.config/opencode/skill/data-analysis/notes.txt
assertFileContent home-files/.config/opencode/skill/data-analysis/SKILL.md \
${./skill-dir/data-analysis/SKILL.md}
'';
}

View File

@@ -0,0 +1,25 @@
{
programs.opencode = {
enable = true;
skills = {
git-release = ''
---
name: git-release
description: Create consistent releases and changelogs
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command
'';
};
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/git-release/SKILL.md
assertFileContent home-files/.config/opencode/skill/git-release/SKILL.md \
${./git-release-SKILL.md}
'';
}

View File

@@ -0,0 +1,14 @@
{
programs.opencode = {
enable = true;
skills = {
pdf-processing = ./pdf-processing-SKILL.md;
};
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/pdf-processing/SKILL.md
assertFileContent home-files/.config/opencode/skill/pdf-processing/SKILL.md \
${./pdf-processing-SKILL.md}
'';
}

View File

@@ -1,9 +1,19 @@
{ config, pkgs, ... }:
{
config = {
programs.radicle.enable = true;
test.stubs.radicle-node = {
buildScript = ''
mkdir -p "$out/bin"
cat > "$out/bin/rad" << 'EOF'
#!/bin/sh
# Stub rad command that does nothing
exit 0
EOF
chmod +x "$out/bin/rad"
'';
};
nmt.script = ''
assertFileContent \
home-files/.radicle/config.json \

View File

@@ -15,6 +15,7 @@ Host xyz
RemoteForward [localhost]:8081 [10.0.0.2]:80
RemoteForward /run/user/1000/gnupg/S.gpg-agent.extra /run/user/1000/gnupg/S.gpg-agent
DynamicForward [localhost]:2839
KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256
Host ordered
Port 1

View File

@@ -34,6 +34,11 @@
host.address = "/run/user/1000/gnupg/S.gpg-agent";
}
];
kexAlgorithms = [
"sntrup761x25519-sha512"
"sntrup761x25519-sha512@openssh.com"
"mlkem768x25519-sha256"
];
dynamicForwards = [ { port = 2839; } ];
setEnv = {
FOO = "foo12";

View File

@@ -1,4 +1,5 @@
{ lib, pkgs, ... }:
lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux) {
vicinae-pre17-settings = ./pre17-settings.nix;
vicinae-example-settings = ./example-settings.nix;
}

View File

@@ -8,7 +8,7 @@
programs.vicinae = {
enable = true;
systemd.enable = true;
useLayerShell = false;
settings = {
faviconService = "twenty";
font = {
@@ -80,11 +80,16 @@
];
};
test.asserts.assertions.expected = [
''After version 0.17, if you want to explicitly disable the use of layer shell, you need to set {option}.programs.vicinae.settings.launcher_window.layer_shell.enabled = false.''
];
nmt.script = ''
assertFileExists "home-files/.config/vicinae/vicinae.json"
assertFileExists "home-files/.config/vicinae/settings.json"
assertFileExists "home-files/.config/systemd/user/vicinae.service"
assertFileExists "home-files/.local/share/vicinae/themes/catppuccin-mocha.toml"
assertFileExists "home-files/.local/share/vicinae/extensions/gif-search/package.json"
assertFileExists "home-files/.local/share/vicinae/extensions/test-extension/package.json"
assertFileContent "home-files/.config/systemd/user/vicinae.service" ${./service.service}
'';
}

View File

@@ -0,0 +1,46 @@
{
pkgs,
config,
...
}:
{
programs.vicinae = {
enable = true;
systemd.enable = true;
package = pkgs.stdenv.mkDerivation {
pname = "fake-vicinae";
version = "0.10.0";
src = pkgs.emptyFile;
buildCommand = "mkdir -p $out";
meta = {
mainProgram = "vicinae";
};
};
settings = {
faviconService = "twenty";
font = {
size = 10;
};
popToRootOnClose = false;
rootSearch = {
searchFiles = false;
};
theme = {
name = "vicinae-dark";
};
window = {
csd = true;
opacity = 0.95;
rounding = 10;
};
};
};
nmt.script = ''
assertFileExists "home-files/.config/vicinae/vicinae.json"
assertFileExists "home-files/.config/systemd/user/vicinae.service"
assertFileContains "home-files/.config/systemd/user/vicinae.service" "EnvironmentFile"
'';
}

View File

@@ -0,0 +1,15 @@
[Install]
WantedBy=graphical-session.target
[Service]
ExecStart=@vicinae@/bin/vicinae server
KillMode=process
Restart=always
RestartSec=5
Type=simple
[Unit]
After=graphical-session.target
Description=Vicinae server daemon
Documentation=https://docs.vicinae.com
PartOf=graphical-session.target

View File

@@ -1,5 +1,4 @@
{ lib, pkgs, ... }:
lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
yt-dlp-simple-config = ./yt-dlp-simple-config.nix;
yt-dlp-extraConfig = ./yt-dlp-extraConfig.nix;
yt-dlp-config = ./yt-dlp-config.nix;
}

View File

@@ -1,3 +1,5 @@
--color stdout:no_color
--color stderr:always
--downloader aria2c
--downloader-args aria2c:'-c -x8 -s8 -k1M'
--no-embed-subs

View File

@@ -8,6 +8,10 @@
downloader = "aria2c";
downloader-args = "aria2c:'-c -x8 -s8 -k1M'";
trim-filenames = 30;
color = [
"stdout:no_color"
"stderr:always"
];
};
extraConfig = ''
--config-locations /home/user/.yt-dlp.conf
@@ -16,6 +20,6 @@
nmt.script = ''
assertFileExists home-files/.config/yt-dlp/config
assertFileContent home-files/.config/yt-dlp/config ${./yt-dlp-simple-config-expected}
assertFileContent home-files/.config/yt-dlp/config ${./yt-dlp-config-expected}
'';
}

View File

@@ -1,2 +0,0 @@
--config-locations /home/user/.yt-dlp.conf

View File

@@ -1,13 +0,0 @@
{
programs.yt-dlp = {
enable = true;
extraConfig = ''
--config-locations /home/user/.yt-dlp.conf
'';
};
nmt.script = ''
assertFileExists home-files/.config/yt-dlp/config
assertFileContent home-files/.config/yt-dlp/config ${./yt-dlp-extraConfig-expected}
'';
}

View File

@@ -3,6 +3,13 @@
zsh-aliases = ./aliases.nix;
zsh-dotdir-absolute = import ./dotdir.nix "absolute";
zsh-dotdir-default = import ./dotdir.nix "default";
zsh-dotdir-path-normalization-abs-no-slash = import ./dotdir.nix "abs-no-slash";
zsh-dotdir-path-normalization-abs-slash = import ./dotdir.nix "abs-slash";
zsh-dotdir-path-normalization-rel-no-slash = import ./dotdir.nix "rel-no-slash";
zsh-dotdir-path-normalization-rel-slash = import ./dotdir.nix "rel-slash";
zsh-dotdir-path-normalization-root-no-slash = import ./dotdir.nix "root-no-slash";
zsh-dotdir-path-normalization-root-slash = import ./dotdir.nix "root-slash";
zsh-dotdir-path-normalization-abs-space = import ./dotdir.nix "abs-space";
zsh-dotdir-relative = import ./dotdir.nix "relative";
zsh-dotdir-shell-variable = import ./dotdir.nix "shell-variable";
zsh-history-ignore-pattern = ./history-ignore-pattern.nix;
@@ -12,12 +19,15 @@
zsh-history-path-xdg-variable = import ./history-path.nix "xdg-variable";
zsh-history-path-zdotdir-variable = import ./history-path.nix "zdotdir-variable";
zsh-history-substring-search = ./history-substring-search.nix;
zsh-legacy-warning = ./legacy-warning.nix;
zsh-siteFunctions-mkcd = ./siteFunctions-mkcd.nix;
zsh-plugins = ./plugins.nix;
zsh-prezto = ./prezto.nix;
zsh-session-variables = ./session-variables.nix;
zsh-smart-formatting = ./smart-formatting.nix;
zsh-syntax-highlighting = ./syntax-highlighting.nix;
zsh-xdg-default = ./xdg-default.nix;
zsh-xdg-disabled = ./xdg-disabled.nix;
zsh-zprof = ./zprof.nix;
zshrc-contents-priorities = ./zshrc-content-priorities.nix;
}

View File

@@ -7,24 +7,46 @@ case:
}:
let
home = config.home.homeDirectory;
subDir = "subdir/subdir2";
dotDir =
dotDirCases = {
absolute = "${home}/${subDir}";
relative = subDir;
inherit (options.programs.zsh.dotDir) default;
shell-variable = "\${XDG_CONFIG_HOME:-$HOME/.config}/zsh";
# Path normalization cases
abs-no-slash = "${home}/subdir";
abs-slash = "${home}/subdir/";
rel-no-slash = "subdir";
rel-slash = "subdir/";
root-no-slash = "${home}";
root-slash = "${home}/";
abs-space = "${home}/subdir with space";
};
dotDir = dotDirCases.${case} or (abort "Unknown case: ${case}");
# Normalize absolute path to match module behavior (no trailing slash)
absDotDir =
let
subDir = "subdir/subdir2";
fullPath = if lib.hasPrefix "/" dotDir then dotDir else "${home}/${dotDir}";
in
if case == "absolute" then
"${home}/${subDir}"
else if case == "relative" then
subDir
else if case == "default" then
options.programs.zsh.dotDir.default
else if case == "shell-variable" then
"\${XDG_CONFIG_HOME:-\$HOME/.config}/zsh"
else
abort "Test condition not provided.";
lib.removeSuffix "/" fullPath;
absDotDir = lib.optionalString (!lib.hasPrefix home dotDir) "${home}/" + dotDir;
relDotDir = lib.removePrefix home dotDir;
# Calculate relative path for file location assertions
relDotDir =
let
# Use the normalized absDotDir to determine relative location
rawRel = lib.removePrefix home absDotDir;
in
if lib.hasPrefix "/" rawRel then lib.removePrefix "/" rawRel else rawRel;
isRelative = lib.elem case [
"relative"
"rel-no-slash"
"rel-slash"
];
in
{
config = {
@@ -33,33 +55,37 @@ in
inherit dotDir;
};
test.stubs.zsh = { };
test = {
stubs.zsh = { };
test.asserts.warnings.expected = lib.optionals (case == "relative") [
''
Using relative paths in programs.zsh.dotDir is deprecated and will be removed in a future release.
Current dotDir: subdir/subdir2
Consider using absolute paths or home-manager config options instead.
You can replace relative paths or environment variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
asserts = {
assertions.expected = lib.optionals (case == "shell-variable") [
''
programs.zsh.dotDir cannot contain shell variables as it is used for file creation at build time.
Current dotDir: ''${XDG_CONFIG_HOME:-''$HOME/.config}/zsh
Consider using an absolute path or home-manager config options instead.
You can replace shell variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
test.asserts.assertions.expected = lib.optionals (case == "shell-variable") [
''
programs.zsh.dotDir cannot contain shell variables as it is used for file creation at build time.
Current dotDir: ''${XDG_CONFIG_HOME:-''$HOME/.config}/zsh
Consider using an absolute path or home-manager config options instead.
You can replace shell variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
warnings.expected = lib.optionals isRelative [
''
Using relative paths in programs.zsh.dotDir is deprecated and will be removed in a future release.
Current dotDir: ${dotDir}
Consider using absolute paths or home-manager config options instead.
You can replace relative paths or environment variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
};
};
nmt.script =
if case == "shell-variable" then
@@ -70,17 +96,15 @@ in
else
lib.concatStringsSep "\n" [
# check dotDir entrypoint exists
"assertFileExists home-files/${relDotDir}/.zshenv"
"assertFileExists 'home-files/${if relDotDir == "" then "" else "${relDotDir}/"}.zshenv'"
# for non-default dotDir only:
(lib.optionalString (case != "default") ''
(lib.optionalString (absDotDir != home) ''
# check .zshenv in homeDirectory sources .zshenv in dotDir
assertFileRegex home-files/.zshenv \
"source [\"']\?${absDotDir}/.zshenv[\"']\?"
assertFileRegex home-files/.zshenv "source ${lib.escapeShellArg "${absDotDir}/.zshenv"}"
# check that .zshenv in dotDir exports ZDOTDIR
assertFileRegex home-files/${relDotDir}/.zshenv \
"export ZDOTDIR=[\"']\?${absDotDir}[\"']\?"
assertFileRegex 'home-files/${relDotDir}/.zshenv' "export ZDOTDIR=\"${absDotDir}\""
'')
];
};

View File

@@ -68,7 +68,7 @@ in
else if case == "zdotdir-variable" then
''
assertFileContains home-files/.config/zsh/.zshrc 'HISTFILE="$ZDOTDIR/.zsh_history"'
assertFileContains home-files/.config/zsh/.zshenv "export ZDOTDIR=${homeDir}/.config/zsh"
assertFileContains home-files/.config/zsh/.zshenv "export ZDOTDIR=\"${homeDir}/.config/zsh\""
''
else
''

View File

@@ -0,0 +1,29 @@
{
config = {
programs.zsh.enable = true;
xdg.enable = true;
# We use 25.05 to trigger the legacy warning (since < 26.05)
# AND to bypass the global fix in tests/default.nix (which checks for 18.09)
home.stateVersion = "25.05";
nmt.script = ''
assertFileExists home-files/.zshrc
# Verify that the warning is generated
# We check the evaluation output for the warning message
assertPathNotExists home-files/.config/zsh
'';
test.asserts.warnings.expected = [
''
The default value of `programs.zsh.dotDir` will change in future versions.
You are currently using the legacy default (home directory) because `home.stateVersion` is less than "26.05".
To silence this warning and lock in the current behavior, set:
programs.zsh.dotDir = config.home.homeDirectory;
To adopt the new behavior (XDG config directory), set:
programs.zsh.dotDir = "''${config.xdg.configHome}/zsh";
''
];
};
}

View File

@@ -0,0 +1,21 @@
{
config = {
programs.zsh.enable = true;
home.stateVersion = "26.05";
xdg.enable = true;
# With xdg.enable = true and new state version, dotDir should default to XDG config home
nmt.script = ''
assertFileExists home-files/.config/zsh/.zshenv
assertFileExists home-files/.config/zsh/.zshrc
# Verify global .zshenv points to the XDG location
assertFileExists home-files/.zshenv
assertFileRegex home-files/.zshenv "source /home/hm-user/.config/zsh/.zshenv"
# Verify ZDOTDIR is exported in the inner .zshenv
assertFileRegex home-files/.config/zsh/.zshenv "export ZDOTDIR=\"/home/hm-user/.config/zsh\""
'';
};
}

View File

@@ -0,0 +1,20 @@
{
config = {
programs.zsh.enable = true;
xdg.enable = false;
home.stateVersion = "26.05";
# With xdg.enable = false, dotDir should default to home directory regardless of state version
nmt.script = ''
assertFileExists home-files/.zshenv
assertFileExists home-files/.zshrc
# Should NOT exist in XDG location
assertPathNotExists home-files/.config/zsh
# Verify ZDOTDIR is NOT exported (or points to home if it is, but usually it isn't if dotDir is home)
assertFileNotRegex home-files/.zshenv "export ZDOTDIR="
'';
};
}

View File

@@ -13,6 +13,10 @@ animations {
enabled=true
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
decoration {
col.shadow=rgba(00000099)
shadow_offset=0 5
@@ -45,9 +49,6 @@ plugin {
dummy=plugin setting
}
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
# window resize
bind = $mod, S, submap, resize

View File

@@ -13,6 +13,10 @@ animations {
enabled=true
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
decoration {
col.shadow=rgba(00000099)
shadow_offset=0 5
@@ -48,9 +52,6 @@ plugin {
dummy=plugin setting
}
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
# window resize
bind = $mod, S, submap, resize

View File

@@ -10,4 +10,5 @@ input {
follow_mouse=1
kb_layout=ro
}
source=sourced.conf

View File

@@ -17,6 +17,7 @@ submap = reset
submap = resize
bind=, escape, submap, reset
bind=, return, submap, reset
binde=, right, resizeactive, 10 0
binde=, left, resizeactive, -10 0
binde=, up, resizeactive, 0 -10

View File

@@ -1,7 +1,8 @@
ipc=on
preload=/share/wallpapers/buttons.png
preload=/share/wallpapers/cat_pacman.png
splash=false
splash_offset=2.000000
wallpaper=DP-3,/share/wallpapers/buttons.png
wallpaper=DP-1,/share/wallpapers/cat_pacman.png
ipc=on
splash=false
splash_offset=2.000000

View File

@@ -3,4 +3,5 @@
lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
linux-wallpaperengine-basic-configuration = ./basic-configuration.nix;
linux-wallpaperengine-null-options = ./null-options.nix;
linux-wallpaperengine-missing-spaces = ./missing-spaces.nix;
}

View File

@@ -0,0 +1,11 @@
[Install]
WantedBy=graphical-session.target
[Service]
ExecStart=@linux-wallpaperengine@/bin/linux-wallpaperengine --screen-root HDMI-A-1 --bg 2902931482
Restart=on-failure
[Unit]
After=graphical-session.target
Description=Implementation of Wallpaper Engine on Linux
PartOf=graphical-session.target

View File

@@ -0,0 +1,17 @@
{
services.linux-wallpaperengine = {
enable = true;
wallpapers = [
{
monitor = "HDMI-A-1";
wallpaperId = "2902931482";
}
];
};
nmt.script = ''
assertFileContent \
home-files/.config/systemd/user/linux-wallpaperengine.service \
${./missing-spaces-expected.service}
'';
}

View File

@@ -8,7 +8,7 @@
nmt.script = ''
assertFileContains \
home-files/.bashrc \
home-files/.profile \
'export SSH_AUTH_SOCK=$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent'
'';
}