Compare commits

..

71 Commits

Author SHA1 Message Date
85eb20c4cb stuff 2026-04-10 15:41:24 +02:00
276378b57c flk 2026-04-09 08:33:04 +00:00
03b968513b flk 2026-04-08 08:07:51 +00:00
e545d38314 sm 2026-04-08 08:07:51 +00:00
564ccd2559 keys 2026-04-07 11:43:58 +00:00
f32f51970b up 2026-04-07 11:41:55 +00:00
0e50839ce0 up 2026-04-07 11:02:37 +00:00
ed995a1edd flk 2026-04-07 11:02:37 +00:00
2424b87b46 flk 2026-04-07 11:02:37 +00:00
37ef245374 flk 2026-04-02 14:46:44 +00:00
ac40abe696 flk 2026-04-02 14:45:05 +00:00
a611d7fb99 clean 2026-04-02 13:01:08 +00:00
89c430b940 review more 2026-04-02 12:57:50 +00:00
1dd7d8a2d8 fix review 2026-04-02 12:57:50 +00:00
69697c822c model 2026-04-02 11:12:13 +00:00
8def00f368 review 2026-04-02 10:58:32 +00:00
c907354a4f flk 2026-04-02 10:09:06 +00:00
80ff1f8b03 fuck 2026-04-01 16:11:02 +00:00
9b5069693a fix opensrc mcp command 2026-04-01 16:11:02 +00:00
2cad84ff26 rm direnv 2026-04-01 16:11:02 +00:00
bbfcc366c2 up 2026-04-01 11:40:32 +00:00
8b09aa9705 cleanup 2026-04-01 11:40:32 +00:00
0eaa830050 Add jj-review TUI plugin to global opencode config 2026-04-01 11:40:32 +00:00
8577807650 Add task classification taxonomy to moonshot agent 2026-04-01 11:40:32 +00:00
a186c136f0 Rename anvil agent to moonshot, bump reasoningEffort to xhigh 2026-04-01 11:40:32 +00:00
e6b5ff0fb8 Add anvil agent — autonomous GPT-5.4 deep worker 2026-04-01 11:40:32 +00:00
6a8bda9031 flk 2026-04-01 11:40:32 +00:00
073cc1aa38 fix TUI plugin: use default export with id (required by plugin loader) 2026-04-01 11:40:32 +00:00
cea666f3d8 rename review plugin slash command to /jj-review to avoid built-in conflict 2026-04-01 11:40:32 +00:00
813fd347d5 Remove pi agent infrastructure 2026-04-01 11:40:32 +00:00
66ff22f9e6 add opencode review plugin (port from pi-coding-agent extension) 2026-04-01 11:40:32 +00:00
86afae7d6c sneaky 2026-04-01 11:40:32 +00:00
5e46938488 bring oc back 2026-04-01 11:40:32 +00:00
8f3951522c chatgpt 2026-04-01 11:40:32 +00:00
6e5af04278 up 2026-04-01 11:40:32 +00:00
01cf320c2e jj 2026-03-30 14:40:06 +00:00
b69cc789b1 flk 2026-03-30 10:35:50 +00:00
642598bbab chore: update cog-cli to 0.24.1 2026-03-30 10:35:50 +00:00
31b87c7177 tuist-pr clone 2026-03-30 12:35:39 +02:00
aae96c7b5a fix breW 2026-03-29 21:09:43 +02:00
980bd7c993 rm wipr2 2026-03-29 21:04:50 +02:00
dd77ed07e5 fix: make pi-harness pnpm deps reproducible across platforms 2026-03-29 18:36:38 +00:00
724abba247 fix: regenerate flake inputs for deploy 2026-03-29 18:17:27 +00:00
9239e8dc6d flk 2026-03-29 18:17:27 +00:00
11c816b2c2 fix(hosts): restore user home-manager wiring and refresh pi-harness deps hash 2026-03-29 18:17:27 +00:00
901059d0cd fix: update qmd deps and deploy hosts 2026-03-29 18:17:27 +00:00
4c69dddcd3 flk 2026-03-29 18:17:27 +00:00
447d7f1dd7 refactor: dedupe theme values and app helpers 2026-03-29 18:17:27 +00:00
a8b07b0c30 up 2026-03-29 18:17:27 +00:00
94baea90d6 refactor(modules): reduce host repetition 2026-03-29 18:17:27 +00:00
1bb97448a4 refactor(hosts): rename jason to janet 2026-03-29 18:17:27 +00:00
3ede8cd2c2 flk 2026-03-29 18:17:27 +00:00
c9e986121b fix 2026-03-26 19:57:46 +01:00
067312dddf up 2026-03-26 18:52:13 +00:00
a5e387a81d up 2026-03-26 13:49:19 +00:00
1005313bd0 up 2026-03-26 12:02:43 +00:00
2d3e15231a flk 2026-03-26 07:56:26 +00:00
a6cc5dcc4a up 2026-03-26 07:56:26 +00:00
958c332bf1 up 2026-03-26 07:56:26 +00:00
49fa4d623e refactor notability ingest stack 2026-03-26 07:56:26 +00:00
4eefa6b337 feat(notes): add Notability WebDAV ingest pipeline 2026-03-26 07:56:26 +00:00
bef2afed66 flk 2026-03-26 07:56:26 +00:00
5ad97d97a7 up 2026-03-26 07:56:26 +00:00
51b0bd4b1d fix 2026-03-26 07:56:26 +00:00
19c770c163 fix 2026-03-26 07:56:26 +00:00
9bd22bc5de fix(zellij): inline rose-pine theme for 0.44 UI 2026-03-24 21:07:47 +00:00
85f2d5c19f fix(zellij): update rose-pine-dawn theme for 0.44 2026-03-24 09:04:09 +00:00
d3ceac88fc update hash 2026-03-24 09:04:09 +00:00
799207efaa flk 2026-03-24 07:51:45 +00:00
260da9cbfc fix 2026-03-24 08:49:54 +01:00
34ecdaf528 fix stuff 2026-03-23 21:32:37 +01:00
85 changed files with 3610 additions and 4239 deletions

View File

@@ -2,7 +2,7 @@ keys:
- &user_cschmatzler age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7
- &host_tahani age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm
- &host_michael age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j
- &host_jason age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2
- &host_janet age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8
- &host_chidi age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3
creation_rules:
- path_regex: secrets/[^/]+$
@@ -11,5 +11,5 @@ creation_rules:
- *user_cschmatzler
- *host_tahani
- *host_michael
- *host_jason
- *host_janet
- *host_chidi

View File

@@ -5,7 +5,7 @@
### Local Development
```bash
nix run .#build # Build current host config
nix run .#build -- <hostname> # Build specific host (chidi, jason, michael, tahani)
nix run .#build -- <hostname> # Build specific host (chidi, janet, michael, tahani)
nix run .#apply # Build and apply locally (darwin-rebuild/nixos-rebuild switch)
nix flake check # Validate flake
```
@@ -62,7 +62,7 @@ alejandra . # Format all Nix files
**Imports**: Auto-imported by import-tree; underscore-prefixed dirs (`_lib/`, `_darwin/`, etc.) are excluded from auto-import
**Deployment**: deploy-rs for NixOS hosts (michael, tahani); darwin hosts (chidi, jason) are local-only
**Deployment**: deploy-rs for NixOS hosts (michael, tahani); darwin hosts (chidi, janet) are local-only
### Nix Language Conventions
@@ -70,7 +70,7 @@ alejandra . # Format all Nix files
```nix
{inputs, pkgs, lib, ...}:
```
Destructure arguments on separate lines. Use `...` to capture remaining args.
Use `...` to capture remaining args. Let Alejandra control the exact layout.
**Attribute Sets**:
```nix
@@ -111,7 +111,6 @@ in {
};
}
```
- Destructure args on separate lines
- Use `with lib;` for brevity with NixOS lib functions
- Define `cfg` for config options
- Use `mkIf`, `mkForce`, `mkDefault` appropriately
@@ -129,7 +128,7 @@ in {
### Naming Conventions
- **Aspect names**: `den.aspects.<name>.<class>` for feature configuration
- **Hostnames**: Lowercase, descriptive (e.g., `michael`, `tahani`, `chidi`, `jason`)
- **Hostnames**: Lowercase, descriptive (e.g., `michael`, `tahani`, `chidi`, `janet`)
- **Module files**: Descriptive, lowercase with hyphens (e.g., `neovim-config.nix`)
### Secrets Management

View File

@@ -5,15 +5,16 @@ Personal Nix flake for four machines:
- `michael` - x86_64 Linux server
- `tahani` - x86_64 Linux home server / workstation
- `chidi` - aarch64 Darwin work laptop
- `jason` - aarch64 Darwin personal laptop
- `janet` - aarch64 Darwin personal laptop
## Repository Map
- `modules/` - flake-parts modules, auto-imported via `import-tree`
- `modules/hosts/` - per-host composition modules
- `modules/hosts/_parts/` - host-private leaf modules like hardware, disks, and services
- `modules/hosts/_parts/` - host-private leaf modules like hardware, disks, and literal networking
- `modules/profiles/` - shared host and user profile bundles
- `modules/_lib/` - local helper functions
- `modules/_notability/`, `modules/_paperless/` - feature-owned scripts and templates
- `apps/` - Nushell apps exposed through the flake
- `secrets/` - SOPS-encrypted secrets
- `flake.nix` - generated flake entrypoint
@@ -27,7 +28,8 @@ This repo uses `den` and organizes configuration around aspects instead of putti
- the machine inventory lives in `modules/inventory.nix`
- shared bundles live in `modules/profiles/{host,user}/`
- host composition happens in `modules/hosts/<host>.nix`
- host-private imports live in `modules/hosts/_parts/<host>/`
- host-private imports live in `modules/hosts/_parts/<host>/` and stay limited to true machine leaf files
- feature-owned services live in top-level modules like `modules/gitea.nix`, `modules/notability.nix`, and `modules/paperless.nix`
- user-level config mostly lives in Home Manager aspects
Common examples:
@@ -35,6 +37,8 @@ Common examples:
- `modules/core.nix` - shared Nix and shell foundation
- `modules/dev-tools.nix` - VCS, language, and developer tooling
- `modules/network.nix` - SSH, fail2ban, and tailscale aspects
- `modules/gitea.nix` - Gitea, Litestream, and backup stack for `michael`
- `modules/notability.nix` - Notability ingest services and user tooling for `tahani`
- `modules/profiles/user/workstation.nix` - shared developer workstation user bundle
- `modules/hosts/michael.nix` - server composition for `michael`
- `modules/hosts/tahani.nix` - server/workstation composition for `tahani`

View File

@@ -3,17 +3,5 @@
use ../common.nu *
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
try { scutil --get LocalHostName | str trim } catch { hostname -s | str trim }
} else { $hostname }
print_info $"Building configuration for ($host)"
nix build $".#darwinConfigurations.($host).system" --show-trace ...$rest
if ("./result" | path exists) {
rm ./result
}
print_success "Build completed successfully"
build-config "darwin" $hostname ...$rest
}

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env nu
use ../common.nu *
def main [...inputs: string] {
if ($inputs | is-empty) {
print_info "Updating all flake inputs"
nix flake update
} else {
print_info $"Updating flake inputs: ($inputs | str join ', ')"
nix flake update ...$inputs
}
print_info "Regenerating flake.nix"
nix run .#write-flake
print_info "Formatting"
alejandra .
print_success "Flake updated"
}

View File

@@ -2,18 +2,8 @@
use ./common.nu *
def get-hostname [] {
if $nu.os-info.name == "macos" {
try { ^scutil --get LocalHostName | str trim } catch { ^hostname -s | str trim }
} else {
^hostname | str trim
}
}
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
get-hostname
} else { $hostname }
let host = resolve-host $hostname
print_info $"Applying configuration for ($host)"

View File

@@ -15,3 +15,58 @@ export def print_error [msg: string] {
export def print_warning [msg: string] {
print $"(ansi yellow)[WARN](ansi reset) ($msg)"
}
export def get-hostname [] {
if $nu.os-info.name == "macos" {
try { ^scutil --get LocalHostName | str trim } catch { ^hostname -s | str trim }
} else {
^hostname | str trim
}
}
export def resolve-host [hostname?: string] {
if ($hostname | is-empty) {
get-hostname
} else {
$hostname
}
}
export def cleanup-result-link [] {
if ("./result" | path exists) {
rm ./result
}
}
export def build-config [kind: string, hostname?: string, ...rest: string] {
let host = resolve-host $hostname
print_info $"Building configuration for ($host)"
if $kind == "darwin" {
nix build $".#darwinConfigurations.($host).system" --show-trace ...$rest
} else {
nix build $".#nixosConfigurations.($host).config.system.build.toplevel" --show-trace ...$rest
}
cleanup-result-link
print_success "Build completed successfully"
}
export def update-flake [inputs: list<string>] {
if ($inputs | is-empty) {
print_info "Updating all flake inputs"
nix flake update
} else {
print_info $"Updating flake inputs: ($inputs | str join ', ')"
nix flake update ...$inputs
}
print_info "Regenerating flake.nix"
nix run .#write-flake
print_info "Formatting"
alejandra .
print_success "Flake updated"
}

7
apps/update Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env nu
use ./common.nu *
def main [...inputs: string] {
update-flake $inputs
}

View File

@@ -3,17 +3,5 @@
use ../common.nu *
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
hostname | str trim
} else { $hostname }
print_info $"Building configuration for ($host)"
nix build $".#nixosConfigurations.($host).config.system.build.toplevel" --show-trace ...$rest
if ("./result" | path exists) {
rm ./result
}
print_success "Build completed successfully"
build-config "nixos" $hostname ...$rest
}

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env nu
use ../common.nu *
def main [...inputs: string] {
if ($inputs | is-empty) {
print_info "Updating all flake inputs"
nix flake update
} else {
print_info $"Updating flake inputs: ($inputs | str join ', ')"
nix flake update ...$inputs
}
print_info "Regenerating flake.nix"
nix run .#write-flake
print_info "Formatting"
alejandra .
print_success "Flake updated"
}

220
flake.lock generated
View File

@@ -28,16 +28,16 @@
"brew-src": {
"flake": false,
"locked": {
"lastModified": 1769363988,
"narHash": "sha256-BiGPeulrDVetXP+tjxhMcGLUROZAtZIhU5m4MqawCfM=",
"lastModified": 1774235677,
"narHash": "sha256-0ryNYmzDAeRlrzPTAgmzGH/Cgc8iv/LBN6jWGUANvIk=",
"owner": "Homebrew",
"repo": "brew",
"rev": "d01011cac6d72032c75fd2cd9489909e95d9faf2",
"rev": "894a3d23ac0c8aaf561b9874b528b9cb2e839201",
"type": "github"
},
"original": {
"owner": "Homebrew",
"ref": "5.0.12",
"ref": "5.1.1",
"repo": "brew",
"type": "github"
}
@@ -114,11 +114,11 @@
]
},
"locked": {
"lastModified": 1773000227,
"narHash": "sha256-zm3ftUQw0MPumYi91HovoGhgyZBlM4o3Zy0LhPNwzXE=",
"lastModified": 1775037210,
"narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "da529ac9e46f25ed5616fd634079a5f3c579135f",
"rev": "06648f4902343228ce2de79f291dd5a58ee12146",
"type": "github"
},
"original": {
@@ -130,11 +130,11 @@
},
"den": {
"locked": {
"lastModified": 1774223159,
"narHash": "sha256-aPfuEzOcd1Jaj+XkELOgDSX8DpM8YQCX1z8KKpKGJtY=",
"lastModified": 1775702491,
"narHash": "sha256-5BCNtE/zCLSheltliy4hTdwsq0Boj/W1XRIX8n89nqA=",
"owner": "vic",
"repo": "den",
"rev": "5728cf32f2f2a3c4b0e34ecfb211ce8c0131a3a7",
"rev": "d267c458e384b57317d06d45f7c65f7fb03fae4b",
"type": "github"
},
"original": {
@@ -191,11 +191,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1774250935,
"narHash": "sha256-mWID0WFgTnd9hbEeaPNX+YYWF70JN3r7zBouEqERJOE=",
"lastModified": 1775721346,
"narHash": "sha256-ogqjruvVBYEj8sWM3viOucSo1Pna9c147EKQOfA+p3I=",
"owner": "nix-community",
"repo": "fenix",
"rev": "64d7705e8c37d650cfb1aa99c24a8ce46597f29e",
"rev": "99fde43dfee2a672e4e37ef211e0844337e5b725",
"type": "github"
},
"original": {
@@ -282,11 +282,11 @@
},
"flake-file": {
"locked": {
"lastModified": 1773637821,
"narHash": "sha256-Cp1x/5/97iNjXOI5hLCA51BwR6hc1dWJiZp4VPogkTw=",
"lastModified": 1774886516,
"narHash": "sha256-w2LoQVM6DXrSdGUZBZqa1nYkMzHoB0t82DrptzZKhTs=",
"owner": "vic",
"repo": "flake-file",
"rev": "bb9fbe00ba6a1945fbdd6973c2585ab770f404b0",
"rev": "3daadf37de2bb85b0ff34e2a7ab0d71e077c2b9e",
"type": "github"
},
"original": {
@@ -302,11 +302,11 @@
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -323,11 +323,11 @@
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -344,11 +344,11 @@
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -441,11 +441,11 @@
]
},
"locked": {
"lastModified": 1774293042,
"narHash": "sha256-OEBV+Y5I4Ldu98k0KvGXRfJYh+jjE8ocCSL/dxTGs1s=",
"lastModified": 1775683737,
"narHash": "sha256-oBYyowo6yfgb95Z78s3uTnAd9KkpJpwzjJbfnpLaM2Y=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "bc357c75e3142a31b849ba49c5299fb52c61cf59",
"rev": "7ba4ee4228ed36123c7cb75d50524b43514ef992",
"type": "github"
},
"original": {
@@ -457,11 +457,11 @@
"homebrew-cask": {
"flake": false,
"locked": {
"lastModified": 1774294203,
"narHash": "sha256-s8oUPYIkF4JGCGhfQgfVf/LwlIJiCnza5FbGuHzEuYU=",
"lastModified": 1775700724,
"narHash": "sha256-qQm9uIF+tI7gamLMa7DSXSQQzLQalEtOa7PHPxNkbr8=",
"owner": "homebrew",
"repo": "homebrew-cask",
"rev": "d1c95a8e5a66a1bf1453577e5ff050c9bc26fa48",
"rev": "c622bff3b88557e3c870104db0426b93e0767a8f",
"type": "github"
},
"original": {
@@ -473,11 +473,11 @@
"homebrew-core": {
"flake": false,
"locked": {
"lastModified": 1774294431,
"narHash": "sha256-pc4lRV8vz7ixbgAfj+KOJlsJtGBhYV2xZPe3t7weA/o=",
"lastModified": 1775721921,
"narHash": "sha256-s6K2QbKa4OJlblFp3zMSh0/2PM2zpWpAd4ZnREirj/I=",
"owner": "homebrew",
"repo": "homebrew-core",
"rev": "dbc9371db7ad629f8184407efea3b87b20f87db8",
"rev": "70028a68b515145bbeccb2961240275ab6eb9e82",
"type": "github"
},
"original": {
@@ -535,11 +535,11 @@
"jj-nvim": {
"flake": false,
"locked": {
"lastModified": 1773914813,
"narHash": "sha256-UuNcOfgsWuHu9hx6NT/FbQ0E8T6nRY1X6O6CDRtH8Sk=",
"lastModified": 1775551442,
"narHash": "sha256-hoU+DenrgxNwvLNmDtIsJ5yB5fhRjDRPOOL8WW9bpZM=",
"owner": "NicolasGB",
"repo": "jj.nvim",
"rev": "a6e163bcc3a6b75e5b6d4190b64ed4b39f8ddb0c",
"rev": "2dbe2c73c599a29e86e4123b42e430828b1f01d9",
"type": "github"
},
"original": {
@@ -593,11 +593,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1774286543,
"narHash": "sha256-p/uG7RgY9JfE7YUgDHZ1FlHONpoPuPIb+PwOEdcPBRE=",
"lastModified": 1775705124,
"narHash": "sha256-OUtgrn0k7DYnAP9skY2rOJSWJyn4w5tnUcF3lSJdfME=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "94da705b8baa5c78d39e8c092bccfbe3a015b8de",
"rev": "ca76524952b00135dba57da62ce2dd123a1ba4be",
"type": "github"
},
"original": {
@@ -637,11 +637,11 @@
]
},
"locked": {
"lastModified": 1774224385,
"narHash": "sha256-VQPMdAUOhDqb6AUAn6oQYPvU2DVGHIc3iRdAHlDhSHQ=",
"lastModified": 1775693082,
"narHash": "sha256-nnhkpfWsRutQh//KmVoIV7e9Gk90tBezjcoRr775BfU=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "701c0a6174fde5de4b9424c0d1e5a4306b73baac",
"rev": "21b2795e6aeb4a0110bdc7bd81bad59c022c9986",
"type": "github"
},
"original": {
@@ -653,11 +653,11 @@
"neovim-src": {
"flake": false,
"locked": {
"lastModified": 1774221289,
"narHash": "sha256-nxFkSVa268w237r0i0xxCpzyIVtfXocm1xI+vsjJlgo=",
"lastModified": 1775689880,
"narHash": "sha256-savZYhFAaBm3BQUdTrPOv7i5K18JFANJvyHv0uuvaWM=",
"owner": "neovim",
"repo": "neovim",
"rev": "6cd1fe9a66947511f59226d51dd70197d80513e5",
"rev": "eefb50e352a689ec1a0a55d6827abea79960cd3d",
"type": "github"
},
"original": {
@@ -671,11 +671,11 @@
"brew-src": "brew-src"
},
"locked": {
"lastModified": 1769437432,
"narHash": "sha256-8d7KnCpT2LweRvSzZYEGd9IM3eFX+A78opcnDM0+ndk=",
"lastModified": 1774720267,
"narHash": "sha256-YYftFe8jyfpQI649yfr0E+dqEXE2jznZNcYvy/lKV1U=",
"owner": "zhaofengli-wip",
"repo": "nix-homebrew",
"rev": "a5409abd0d5013d79775d3419bcac10eacb9d8c5",
"rev": "a7760a3a83f7609f742861afb5732210fdc437ed",
"type": "github"
},
"original": {
@@ -734,11 +734,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1773840656,
"narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=",
"lastModified": 1775639890,
"narHash": "sha256-9O9gNidrdzcb7vgKGtff7QiLtr0IsVaCi0pAXm8anhQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512",
"rev": "456e8a9468b9d46bd8c9524425026c00745bc4d2",
"type": "github"
},
"original": {
@@ -750,11 +750,11 @@
},
"nixpkgs_5": {
"locked": {
"lastModified": 1774294110,
"narHash": "sha256-5n0qlcCumjXk4wW+QTMmt2jW5CGGRFtlTR2AP+jfEyc=",
"lastModified": 1775722436,
"narHash": "sha256-Z7QmfL80jmUPoSQkMlCc+1MGfkugf7bG47H3UTsyi7Q=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2a326c4bc0987aad1b93a5bd652e169fe9546cc0",
"rev": "e73a61d035ee91f95bb0a6b95ce0b9d2866bd332",
"type": "github"
},
"original": {
@@ -803,11 +803,11 @@
"systems": "systems_4"
},
"locked": {
"lastModified": 1772402258,
"narHash": "sha256-3DmCFOdmbkFML1/G9gj8Wb+rCCZFPOQtNoMCpqOF8SA=",
"lastModified": 1775307257,
"narHash": "sha256-y9hEecHH4ennFwIcw1n480YCGh73DkEmizmQnyXuvgg=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "21ae25e13b01d3b4cdc750b5f9e7bad68b150c10",
"rev": "2e008bb941f72379d5b935d5bfe70ed8b7c793ff",
"type": "github"
},
"original": {
@@ -816,87 +816,6 @@
"type": "github"
}
},
"pi-agent-stuff": {
"flake": false,
"locked": {
"lastModified": 1774288055,
"narHash": "sha256-VZFa3PCOYM3Iz8b+TXPTnQDHNpRK5wvkateuQz0lu0E=",
"owner": "mitsuhiko",
"repo": "agent-stuff",
"rev": "7ca2deb75f4a1853bad7d93416c72838080c5e55",
"type": "github"
},
"original": {
"owner": "mitsuhiko",
"repo": "agent-stuff",
"type": "github"
}
},
"pi-elixir": {
"flake": false,
"locked": {
"lastModified": 1772900407,
"narHash": "sha256-QoCPVdN5CYGe5288cJQmB10ds/UOucHIyG9z9E/4hsw=",
"owner": "dannote",
"repo": "pi-elixir",
"rev": "3b8f667beb696ce6ed456e762bfcf61e7326f5c4",
"type": "github"
},
"original": {
"owner": "dannote",
"repo": "pi-elixir",
"type": "github"
}
},
"pi-harness": {
"flake": false,
"locked": {
"lastModified": 1774276819,
"narHash": "sha256-BrbgnFFd4GHJhKa0MuaT2o/LvwLf4GXPbJi6j3H9AHk=",
"owner": "aliou",
"repo": "pi-harness",
"rev": "5bbf0cd6d6ee35d978ca32a070d9d53fbbfe2571",
"type": "github"
},
"original": {
"owner": "aliou",
"repo": "pi-harness",
"type": "github"
}
},
"pi-mcp-adapter": {
"flake": false,
"locked": {
"lastModified": 1773642170,
"narHash": "sha256-E6Kf+OyTN/pF8pKADJO0B1+buAPqNcXnZl9ssZwSP8U=",
"owner": "nicobailon",
"repo": "pi-mcp-adapter",
"rev": "01ba9a4e86bd16d895db319b913d73754a473acb",
"type": "github"
},
"original": {
"owner": "nicobailon",
"ref": "v2.2.0",
"repo": "pi-mcp-adapter",
"type": "github"
}
},
"pi-rose-pine": {
"flake": false,
"locked": {
"lastModified": 1770936151,
"narHash": "sha256-6TzuWJPAn8zz+lUjZ3slFCNdPVd/Z2C+WoXFsLopk1g=",
"owner": "zenobi-us",
"repo": "pi-rose-pine",
"rev": "9b342f6e16d6b28c00c2f888ba2f050273981bdb",
"type": "github"
},
"original": {
"owner": "zenobi-us",
"repo": "pi-rose-pine",
"type": "github"
}
},
"pimalaya": {
"flake": false,
"locked": {
@@ -942,11 +861,6 @@
"nixpkgs"
],
"nixvim": "nixvim",
"pi-agent-stuff": "pi-agent-stuff",
"pi-elixir": "pi-elixir",
"pi-harness": "pi-harness",
"pi-mcp-adapter": "pi-mcp-adapter",
"pi-rose-pine": "pi-rose-pine",
"sops-nix": "sops-nix",
"zjstatus": "zjstatus"
}
@@ -954,11 +868,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1774221325,
"narHash": "sha256-aEIdkqB8gtQZtEbogdUb5iyfcZpKIlD3FkG8ANu73/I=",
"lastModified": 1775663707,
"narHash": "sha256-3cSvpBETRa8aDSrUCX1jGc6FSse3OWB7cXACIZW8BYI=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "b42b63f390a4dab14e6efa34a70e67f5b087cc62",
"rev": "8c5af725817905e462052d91a8d229b85ffa83a5",
"type": "github"
},
"original": {
@@ -1030,11 +944,11 @@
]
},
"locked": {
"lastModified": 1774154798,
"narHash": "sha256-zsTuloDSdKf+PrI1MsWx5z/cyGEJ8P3eERtAfdP8Bmg=",
"lastModified": 1775682595,
"narHash": "sha256-0E9PohY/VuESLq0LR4doaH7hTag513sDDW5n5qmHd1Q=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "3e0d543e6ba6c0c48117a81614e90c6d8c425170",
"rev": "d2e8438d5886e92bc5e7c40c035ab6cae0c41f76",
"type": "github"
},
"original": {
@@ -1126,11 +1040,11 @@
]
},
"locked": {
"lastModified": 1773297127,
"narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
"lastModified": 1775636079,
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
"type": "github"
},
"original": {

View File

@@ -68,26 +68,6 @@
nixpkgs.url = "github:nixos/nixpkgs/master";
nixpkgs-lib.follows = "nixpkgs";
nixvim.url = "github:nix-community/nixvim";
pi-agent-stuff = {
url = "github:mitsuhiko/agent-stuff";
flake = false;
};
pi-elixir = {
url = "github:dannote/pi-elixir";
flake = false;
};
pi-harness = {
url = "github:aliou/pi-harness";
flake = false;
};
pi-mcp-adapter = {
url = "github:nicobailon/pi-mcp-adapter/v2.2.0";
flake = false;
};
pi-rose-pine = {
url = "github:zenobi-us/pi-rose-pine";
flake = false;
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";

View File

@@ -1,190 +0,0 @@
/**
* No Git Extension
*
* Blocks direct git invocations and tells the LLM to use jj (Jujutsu) instead.
* Mentions of the word "git" in search patterns, strings, comments, etc. are allowed.
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
type ShellToken =
| { type: "word"; value: string }
| { type: "operator"; value: string };
const COMMAND_PREFIXES = new Set(["env", "command", "builtin", "time", "sudo", "nohup", "nice"]);
const SHELL_KEYWORDS = new Set(["if", "then", "elif", "else", "do", "while", "until", "case", "in"]);
const SHELL_INTERPRETERS = new Set(["bash", "sh", "zsh", "fish", "nu"]);
function isAssignmentWord(value: string): boolean {
return /^[A-Za-z_][A-Za-z0-9_]*=.*/.test(value);
}
function tokenizeShell(command: string): ShellToken[] {
const tokens: ShellToken[] = [];
let current = "";
let quote: "'" | '"' | null = null;
const pushWord = () => {
if (!current) return;
tokens.push({ type: "word", value: current });
current = "";
};
for (let i = 0; i < command.length; i++) {
const char = command[i];
if (quote) {
if (quote === "'") {
if (char === "'") {
quote = null;
} else {
current += char;
}
continue;
}
if (char === '"') {
quote = null;
continue;
}
if (char === "\\") {
if (i + 1 < command.length) {
current += command[i + 1];
i += 1;
}
continue;
}
current += char;
continue;
}
if (char === "'" || char === '"') {
quote = char;
continue;
}
if (char === "\\") {
if (i + 1 < command.length) {
current += command[i + 1];
i += 1;
}
continue;
}
if (/\s/.test(char)) {
pushWord();
if (char === "\n") {
tokens.push({ type: "operator", value: "\n" });
}
continue;
}
const twoCharOperator = command.slice(i, i + 2);
if (twoCharOperator === "&&" || twoCharOperator === "||") {
pushWord();
tokens.push({ type: "operator", value: twoCharOperator });
i += 1;
continue;
}
if (char === ";" || char === "|" || char === "(" || char === ")") {
pushWord();
tokens.push({ type: "operator", value: char });
continue;
}
current += char;
}
pushWord();
return tokens;
}
function findCommandWord(words: string[]): { word?: string; index: number } {
for (let i = 0; i < words.length; i++) {
const word = words[i];
if (SHELL_KEYWORDS.has(word)) {
continue;
}
if (isAssignmentWord(word)) {
continue;
}
if (COMMAND_PREFIXES.has(word)) {
continue;
}
return { word, index: i };
}
return { index: words.length };
}
function getInlineShellCommand(words: string[], commandIndex: number): string | null {
for (let i = commandIndex + 1; i < words.length; i++) {
const word = words[i];
if (/^(?:-[A-Za-z]*c[A-Za-z]*|--command)$/.test(word)) {
return words[i + 1] ?? null;
}
}
return null;
}
function segmentContainsBlockedGit(words: string[]): boolean {
const { word, index } = findCommandWord(words);
if (!word) {
return false;
}
if (word === "git") {
return true;
}
if (word === "jj") {
return false;
}
if (SHELL_INTERPRETERS.has(word)) {
const inlineCommand = getInlineShellCommand(words, index);
return inlineCommand ? containsBlockedGitInvocation(inlineCommand) : false;
}
return false;
}
function containsBlockedGitInvocation(command: string): boolean {
const tokens = tokenizeShell(command);
let words: string[] = [];
for (const token of tokens) {
if (token.type === "operator") {
if (segmentContainsBlockedGit(words)) {
return true;
}
words = [];
continue;
}
words.push(token.value);
}
return segmentContainsBlockedGit(words);
}
export default function (pi: ExtensionAPI) {
pi.on("tool_call", async (event, _ctx) => {
if (!isToolCallEventType("bash", event)) return;
const command = event.input.command.trim();
if (containsBlockedGitInvocation(command)) {
return {
block: true,
reason: "git is not used in this project. Use jj (Jujutsu) instead.",
};
}
});
}

View File

@@ -1,28 +0,0 @@
/**
* No Scripting Extension
*
* Blocks python, perl, ruby, php, lua, node -e, and inline bash/sh scripts.
* Tells the LLM to use `nu -c` instead.
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
const SCRIPTING_PATTERN =
/(?:^|[;&|]\s*|&&\s*|\|\|\s*|\$\(\s*|`\s*)(?:python[23]?|perl|ruby|php|lua|node\s+-e|bash\s+-c|sh\s+-c)\s/;
export default function (pi: ExtensionAPI) {
pi.on("tool_call", async (event, _ctx) => {
if (!isToolCallEventType("bash", event)) return;
const command = event.input.command.trim();
if (SCRIPTING_PATTERN.test(command)) {
return {
block: true,
reason:
"Do not use python, perl, ruby, php, lua, node -e, or inline bash/sh for scripting. Use `nu -c` instead.",
};
}
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,260 +0,0 @@
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import {
createAgentSession,
DefaultResourceLoader,
getAgentDir,
SessionManager,
SettingsManager,
} from "@mariozechner/pi-coding-agent";
interface SessionNameState {
hasAutoNamed: boolean;
}
const TITLE_MODEL = {
provider: "openai-codex",
id: "gpt-5.4-mini",
} as const;
const MAX_TITLE_LENGTH = 50;
const MAX_RETRIES = 2;
const FALLBACK_LENGTH = 50;
const TITLE_ENTRY_TYPE = "vendored-session-title";
const TITLE_SYSTEM_PROMPT = `You are generating a succinct title for a coding session based on the provided conversation.
Requirements:
- Maximum 50 characters
- Sentence case (capitalize only first word and proper nouns)
- Capture the main intent or task
- Reuse the user's exact words and technical terms
- Match the user's language
- No quotes, colons, or markdown formatting
- No generic titles like "Coding session" or "Help with code"
- No explanations or commentary
Output ONLY the title text. Nothing else.`;
function isTurnCompleted(event: unknown): boolean {
if (!event || typeof event !== "object") return false;
const message = (event as { message?: unknown }).message;
if (!message || typeof message !== "object") return false;
const stopReason = (message as { stopReason?: unknown }).stopReason;
return typeof stopReason === "string" && stopReason.toLowerCase() === "stop";
}
function buildFallbackTitle(userText: string): string {
const text = userText.trim();
if (text.length <= FALLBACK_LENGTH) return text;
const truncated = text.slice(0, FALLBACK_LENGTH - 3);
const lastSpace = truncated.lastIndexOf(" ");
return `${lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated}...`;
}
function postProcessTitle(raw: string): string {
let title = raw;
title = title.replace(/<thinking[\s\S]*?<\/thinking>\s*/g, "");
title = title.replace(/^["'`]+|["'`]+$/g, "");
title = title.replace(/^#+\s*/, "");
title = title.replace(/\*{1,2}(.*?)\*{1,2}/g, "$1");
title = title.replace(/_{1,2}(.*?)_{1,2}/g, "$1");
title = title.replace(/^(Title|Summary|Session)\s*:\s*/i, "");
title =
title
.split("\n")
.map((line) => line.trim())
.find((line) => line.length > 0) ?? title;
title = title.trim();
if (title.length > MAX_TITLE_LENGTH) {
const truncated = title.slice(0, MAX_TITLE_LENGTH - 3);
const lastSpace = truncated.lastIndexOf(" ");
title = `${lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated}...`;
}
return title;
}
function getLatestUserText(ctx: ExtensionContext): string | null {
const entries = ctx.sessionManager.getEntries();
for (let i = entries.length - 1; i >= 0; i -= 1) {
const entry = entries[i];
if (!entry || entry.type !== "message") continue;
if (entry.message.role !== "user") continue;
const { content } = entry.message as { content: unknown };
if (typeof content === "string") return content;
if (!Array.isArray(content)) return null;
return content
.filter(
(part): part is { type: string; text?: string } =>
typeof part === "object" && part !== null && "type" in part,
)
.filter((part) => part.type === "text" && typeof part.text === "string")
.map((part) => part.text ?? "")
.join(" ");
}
return null;
}
function getLatestAssistantText(ctx: ExtensionContext): string | null {
const entries = ctx.sessionManager.getEntries();
for (let i = entries.length - 1; i >= 0; i -= 1) {
const entry = entries[i];
if (!entry || entry.type !== "message") continue;
if (entry.message.role !== "assistant") continue;
const { content } = entry.message as { content: unknown };
if (typeof content === "string") return content;
if (!Array.isArray(content)) return null;
return content
.filter(
(part): part is { type: string; text?: string } =>
typeof part === "object" && part !== null && "type" in part,
)
.filter((part) => part.type === "text" && typeof part.text === "string")
.map((part) => part.text ?? "")
.join("\n");
}
return null;
}
function resolveModel(ctx: ExtensionContext) {
const available = ctx.modelRegistry.getAvailable();
const model = available.find(
(candidate) => candidate.provider === TITLE_MODEL.provider && candidate.id === TITLE_MODEL.id,
);
if (model) return model;
const existsWithoutKey = ctx.modelRegistry
.getAll()
.some((candidate) => candidate.provider === TITLE_MODEL.provider && candidate.id === TITLE_MODEL.id);
if (existsWithoutKey) {
throw new Error(
`Model ${TITLE_MODEL.provider}/${TITLE_MODEL.id} exists but has no configured API key.`,
);
}
throw new Error(`Model ${TITLE_MODEL.provider}/${TITLE_MODEL.id} is not available.`);
}
async function generateTitle(userText: string, assistantText: string, ctx: ExtensionContext): Promise<string> {
const agentDir = getAgentDir();
const settingsManager = SettingsManager.create(ctx.cwd, agentDir);
const resourceLoader = new DefaultResourceLoader({
cwd: ctx.cwd,
agentDir,
settingsManager,
noExtensions: true,
noPromptTemplates: true,
noThemes: true,
noSkills: true,
systemPromptOverride: () => TITLE_SYSTEM_PROMPT,
appendSystemPromptOverride: () => [],
agentsFilesOverride: () => ({ agentsFiles: [] }),
});
await resourceLoader.reload();
const { session } = await createAgentSession({
model: resolveModel(ctx),
thinkingLevel: "off",
sessionManager: SessionManager.inMemory(),
modelRegistry: ctx.modelRegistry,
resourceLoader,
});
let accumulated = "";
const unsubscribe = session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
accumulated += event.assistantMessageEvent.delta;
}
});
const description = assistantText
? `<user>${userText}</user>\n<assistant>${assistantText}</assistant>`
: `<user>${userText}</user>`;
const userMessage = `<conversation>\n${description}\n</conversation>\n\nGenerate a title:`;
try {
await session.prompt(userMessage);
} finally {
unsubscribe();
session.dispose();
}
return postProcessTitle(accumulated);
}
async function generateAndSetTitle(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
const userText = getLatestUserText(ctx);
if (!userText?.trim()) return;
const assistantText = getLatestAssistantText(ctx) ?? "";
if (!assistantText.trim()) return;
let lastError: Error | null = null;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt += 1) {
try {
const title = await generateTitle(userText, assistantText, ctx);
if (!title) continue;
pi.setSessionName(title);
pi.appendEntry(TITLE_ENTRY_TYPE, {
title,
rawUserText: userText,
rawAssistantText: assistantText,
attempt,
model: `${TITLE_MODEL.provider}/${TITLE_MODEL.id}`,
});
ctx.ui.notify(`Session: ${title}`, "info");
return;
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
}
}
const fallback = buildFallbackTitle(userText);
pi.setSessionName(fallback);
pi.appendEntry(TITLE_ENTRY_TYPE, {
title: fallback,
fallback: true,
error: lastError?.message ?? "Unknown error",
rawUserText: userText,
rawAssistantText: assistantText,
model: `${TITLE_MODEL.provider}/${TITLE_MODEL.id}`,
});
ctx.ui.notify(`Title generation failed, using fallback: ${fallback}`, "warning");
}
export default function setupSessionNameHook(pi: ExtensionAPI) {
const state: SessionNameState = {
hasAutoNamed: false,
};
pi.on("session_start", async () => {
state.hasAutoNamed = false;
});
pi.on("session_switch", async () => {
state.hasAutoNamed = false;
});
pi.on("turn_end", async (event, ctx) => {
if (state.hasAutoNamed) return;
if (pi.getSessionName()) {
state.hasAutoNamed = true;
return;
}
if (!isTurnCompleted(event)) return;
await generateAndSetTitle(pi, ctx);
state.hasAutoNamed = true;
});
}

View File

@@ -1,21 +0,0 @@
{
"mcpServers": {
"opensrc": {
"command": "npx",
"args": ["-y", "opensrc-mcp"],
"directTools": true
},
"context7": {
"url": "https://mcp.context7.com/mcp",
"directTools": true
},
"grep_app": {
"url": "https://mcp.grep.app",
"directTools": true
},
"sentry": {
"url": "https://mcp.sentry.dev/mcp",
"auth": "oauth"
}
}
}

View File

@@ -1,143 +0,0 @@
---
name: jujutsu
description: Manages version control with Jujutsu (jj), including rebasing, conflict resolution, and Git interop. Use when tracking changes, navigating history, squashing/splitting commits, or pushing to Git remotes.
---
# Jujutsu
Git-compatible VCS focused on concurrent development and ease of use.
> ⚠️ **Not Git!** Jujutsu syntax differs from Git:
>
> - Parent: `@-` not `@~1` or `@^`
> - Grandparent: `@--` not `@~2`
> - Child: `@+` not `@~-1`
> - Use `jj log` not `jj changes`
## Key Commands
| Command | Description |
| -------------------------- | -------------------------------------------- |
| `jj st` | Show working copy status |
| `jj log` | Show change log |
| `jj diff` | Show changes in working copy |
| `jj new` | Create new change |
| `jj desc` | Edit change description |
| `jj squash` | Move changes to parent |
| `jj split` | Split current change |
| `jj rebase -s src -d dest` | Rebase changes |
| `jj absorb` | Move changes into stack of mutable revisions |
| `jj bisect` | Find bad revision by bisection |
| `jj fix` | Update files with formatting fixes |
| `jj sign` | Cryptographically sign a revision |
| `jj metaedit` | Modify metadata without changing content |
## Basic Workflow
```bash
jj new # Create new change
jj desc -m "feat: add feature" # Set description
jj log # View history
jj edit change-id # Switch to change
jj new --before @ # Time travel (create before current)
jj edit @- # Go to parent
```
## Time Travel
```bash
jj edit change-id # Switch to specific change
jj next --edit # Next child change
jj edit @- # Parent change
jj new --before @ -m msg # Insert before current
```
## Merging & Rebasing
```bash
jj new x yz -m msg # Merge changes
jj rebase -s src -d dest # Rebase source onto dest
jj abandon # Delete current change
```
## Conflicts
```bash
jj resolve # Interactive conflict resolution
# Edit files, then continue
```
## Revset Syntax
**Parent/child operators:**
| Syntax | Meaning | Example |
| ------ | ---------------- | -------------------- |
| `@-` | Parent of @ | `jj diff -r @-` |
| `@--` | Grandparent | `jj log -r @--` |
| `x-` | Parent of x | `jj diff -r abc123-` |
| `@+` | Child of @ | `jj log -r @+` |
| `x::y` | x to y inclusive | `jj log -r main::@` |
| `x..y` | x to y exclusive | `jj log -r main..@` |
| `x\|y` | Union (or) | `jj log -r 'a \| b'` |
**⚠️ Common mistakes:**
-`@~1` → ✅ `@-` (parent)
-`@^` → ✅ `@-` (parent)
-`@~-1` → ✅ `@+` (child)
-`jj changes` → ✅ `jj log` or `jj diff`
-`a,b,c` → ✅ `a | b | c` (union uses pipe, not comma)
**Functions:**
```bash
jj log -r 'heads(all())' # All heads
jj log -r 'remote_bookmarks()..' # Not on remote
jj log -r 'author(name)' # By author
jj log -r 'description(regex)' # By description
jj log -r 'mine()' # My commits
jj log -r 'committer_date(after:"7 days ago")' # Recent commits
jj log -r 'mine() & committer_date(after:"yesterday")' # My recent
```
## Templates
```bash
jj log -T 'commit_id ++ "\n" ++ description'
```
## Git Interop
```bash
jj bookmark create main -r @ # Create bookmark
jj git push --bookmark main # Push bookmark
jj git fetch # Fetch from remote
jj bookmark track main@origin # Track remote
```
## Advanced Commands
```bash
jj absorb # Auto-move changes to relevant commits in stack
jj bisect start # Start bisection
jj bisect good # Mark current as good
jj bisect bad # Mark current as bad
jj fix # Run configured formatters on files
jj sign -r @ # Sign current revision
jj metaedit -r @ -m "new message" # Edit metadata only
```
## Tips
- No staging: changes are immediate
- Use conventional commits: `type(scope): desc`
- `jj undo` to revert operations
- `jj op log` to see operation history
- Bookmarks are like branches
- `jj absorb` is powerful for fixing up commits in a stack
## Related Skills
- **gh**: GitHub CLI for PRs and issues
- **review**: Code review before committing

View File

@@ -6,7 +6,9 @@
}:
with lib; let
cfg = config.local.dock;
inherit (pkgs) stdenv dockutil;
inherit (pkgs) dockutil stdenv;
local = import ../_lib/local.nix;
userHome = "/Users/${local.user.name}";
in {
options = {
local.dock = {
@@ -37,7 +39,7 @@ in {
};
});
default = [
{path = "/Applications/Helium.app/";}
{path = "/Applications/Safari.app/";}
{path = "/Applications/Ghostty.app/";}
{path = "/System/Applications/Calendar.app/";}
{path = "/System/Applications/Mail.app/";}
@@ -45,7 +47,7 @@ in {
{path = "/System/Applications/Music.app/";}
{path = "/System/Applications/System Settings.app/";}
{
path = "/Users/cschmatzler/Downloads";
path = "${userHome}/Downloads";
section = "others";
options = "--sort name --view grid --display stack";
}
@@ -56,7 +58,7 @@ in {
mkOption {
description = "Username to apply the dock settings to";
type = types.str;
default = "cschmatzler";
default = local.user.name;
};
};
};

View File

@@ -0,0 +1,67 @@
#!/usr/bin/env nu
const repo = "tuist/tuist"
def fail [msg: string] {
error make {msg: $msg}
}
def clean [s: string] {
$s | str replace --all "\t" " " | str replace --all "\n" " "
}
def pick-pr [] {
let prs = (
gh pr list --repo $repo --state open --limit 200 --json number,title,headRefName,author
| from json
)
if ($prs | is-empty) {
fail $"No open PRs found for ($repo)"
}
let choice = (
$prs
| each {|pr|
let title = (clean $pr.title)
let branch = (clean $pr.headRefName)
let author = $pr.author.login
$"($pr.number)\t($title)\t($author)\t($branch)"
}
| str join (char newline)
| fzf --prompt "tuist pr > " --delimiter "\t" --with-nth "1,2,3,4" --preview "gh pr view --repo tuist/tuist {1}" --preview-window "right:70%"
)
if ($choice | str trim | is-empty) {
exit 130
}
$choice | split row "\t" | first | into int
}
def main [pr_number?: int] {
let number = if ($pr_number | is-empty) { pick-pr } else { $pr_number }
let pr = (
gh pr view --repo $repo $number --json number,title,url
| from json
)
let base = ([$env.HOME "Projects" "Work"] | path join)
let dest = ([$base $"tuist-pr-($pr.number)"] | path join)
if ($dest | path exists) {
fail $"Destination already exists: ($dest)"
}
^mkdir -p $base
print $"Cloning ($repo) PR #($pr.number): ($pr.title)"
jj git clone $"https://github.com/($repo).git" $dest
do {
cd $dest
gh pr checkout $pr.number
}
print $"Ready: ($dest)"
}

19
modules/_lib/caddy.nix Normal file
View File

@@ -0,0 +1,19 @@
let
local = import ./local.nix;
in {
inherit (local) tailscaleHost;
mkTailscaleVHost = {
name,
configText,
}: {
"${local.tailscaleHost name}" = {
extraConfig = ''
tls {
get_certificate tailscale
}
${configText}
'';
};
};
}

37
modules/_lib/hosts.nix Normal file
View File

@@ -0,0 +1,37 @@
{
den,
lib,
}: let
merge = lib.recursiveUpdate;
in {
mkUserHost = {
system,
host,
user,
userAspect ? "${host}-${user}",
includes ? [],
homeManager ? null,
}:
merge
(lib.setAttrByPath ["den" "hosts" system host "users" user "aspect"] userAspect)
(lib.setAttrByPath ["den" "aspects" userAspect] ({inherit includes;}
// lib.optionalAttrs (homeManager != null) {
inherit homeManager;
}));
mkPerHostAspect = {
host,
includes ? [],
darwin ? null,
nixos ? null,
}:
lib.setAttrByPath ["den" "aspects" host "includes"] [
(den.lib.perHost ({inherit includes;}
// lib.optionalAttrs (darwin != null) {
inherit darwin;
}
// lib.optionalAttrs (nixos != null) {
inherit nixos;
}))
];
}

33
modules/_lib/local.nix Normal file
View File

@@ -0,0 +1,33 @@
rec {
user = {
name = "cschmatzler";
fullName = "Christoph Schmatzler";
emails = {
personal = "christoph@schmatzler.com";
work = "christoph@tuist.dev";
icloud = "christoph.schmatzler@icloud.com";
};
};
secretPath = name: "/run/secrets/${name}";
mkHome = system:
if builtins.match ".*-darwin" system != null
then "/Users/${user.name}"
else "/home/${user.name}";
mkHost = system: {
inherit system;
home = mkHome system;
};
hosts = {
chidi = mkHost "aarch64-darwin";
janet = mkHost "aarch64-darwin";
michael = mkHost "x86_64-linux";
tahani = mkHost "x86_64-linux";
};
tailscaleDomain = "manticore-hippocampus.ts.net";
tailscaleHost = name: "${name}.${tailscaleDomain}";
}

44
modules/_lib/secrets.nix Normal file
View File

@@ -0,0 +1,44 @@
{lib}: let
local = import ./local.nix;
in rec {
mkBinarySecret = {
name,
sopsFile,
owner ? null,
group ? null,
path ? local.secretPath name,
}:
{
inherit path sopsFile;
format = "binary";
}
// lib.optionalAttrs (owner != null) {
inherit owner;
}
// lib.optionalAttrs (group != null) {
inherit group;
};
mkUserBinarySecret = {
name,
sopsFile,
owner ? local.user.name,
path ? local.secretPath name,
}:
mkBinarySecret {
inherit name owner path sopsFile;
};
mkServiceBinarySecret = {
name,
sopsFile,
serviceUser,
serviceGroup ? serviceUser,
path ? local.secretPath name,
}:
mkBinarySecret {
inherit name path sopsFile;
group = serviceGroup;
owner = serviceUser;
};
}

46
modules/_lib/theme.nix Normal file
View File

@@ -0,0 +1,46 @@
{
rosePineDawn = {
slug = "rose-pine-dawn";
displayName = "Rosé Pine Dawn";
ghosttyName = "Rose Pine Dawn";
hex = {
love = "#b4637a";
gold = "#ea9d34";
rose = "#d7827e";
pine = "#286983";
foam = "#56949f";
iris = "#907aa9";
leaf = "#6d8f89";
text = "#575279";
subtle = "#797593";
muted = "#9893a5";
highlightHigh = "#cecacd";
highlightMed = "#dfdad9";
highlightLow = "#f4ede8";
overlay = "#f2e9e1";
surface = "#fffaf3";
base = "#faf4ed";
};
rgb = {
love = "180 99 122";
gold = "234 157 52";
rose = "215 130 126";
pine = "40 105 131";
foam = "86 148 159";
iris = "144 122 169";
leaf = "109 143 137";
text = "87 82 121";
subtle = "121 117 147";
muted = "152 147 165";
highlightHigh = "206 202 205";
highlightMed = "223 218 217";
highlightLow = "244 237 232";
overlay = "242 233 225";
surface = "255 250 243";
base = "250 244 237";
black = "0 0 0";
};
};
}

View File

@@ -6,18 +6,23 @@
- `jj tug` is an alias for `jj bookmark move --from closest_bookmark(@-) --to @-`.
- Never attempt historically destructive Git commands.
- Make small, frequent commits.
- "Commit" means `jj commit`, not `jj desc`; `desc` stays on the same working copy.
## Scripting
- Use Nushell (`nu`) for scripting.
- Do not use Python, Perl, Lua, awk, or any other scripting language. You are programatically blocked from doing so.
## Workflow
- Always complete the requested work.
- If there is any ambiguity about what to do next, do NOT make a decision yourself. Stop your work and ask.
- Do not end with “If you want me to…” or “I can…”; take the next necessary step and finish the job without waiting for additional confirmation.
- Do not future-proof things. Stick to the original plan.
- Do not add fallbacks or backward compatibility unless explicitly required by the user. By default, replace the previous implementation with the new one entirely.
## Validation
- Do not ignore failing tests or checks, even if they appear unrelated to your changes.
- After completing and validating your work, the final step is to run the project's full validation and test commands and ensure they all pass.
## Workflow
- Always complete the requested work.
- Do not end with “If you want me to…” or “I can…”; take the next necessary step and finish the job without waiting for additional confirmation.

View File

@@ -0,0 +1,49 @@
---
description: Turn pasted Albanian lesson into translated notes and solved exercises in zk
---
Process the pasted Albanian lesson content and create two `zk` notes: one for lesson material and one for exercises.
<lesson-material>
$ARGUMENTS
</lesson-material>
Requirements:
1. Parse the lesson content and produce two markdown outputs:
- `material` output: lesson material only.
- `exercises` output: exercises and solutions.
2. Use today's date in both notes (date in title and inside content).
3. In the `material` output:
- Keep clean markdown structure with headings and bullet points.
- Do not add a top-level title heading (no `# ...`) because `zk new --title` already sets the note title.
- Translate examples, dialogues, and all lesson texts into English when not already translated.
- For bigger reading passages, include a word-by-word breakdown.
- For declension/conjugation/grammar tables, provide a complete table of possibilities relevant to the topic.
- Spell out numbers only when the source token is Albanian; do not spell out English numbers.
4. In the `exercises` output:
- Include every exercise in markdown.
- Do not add a top-level title heading (no `# ...`) because `zk new --title` already sets the note title.
- Translate each exercise to English.
- Solve all non-free-writing tasks (multiple choice, fill in the blanks, etc.) and include example solutions.
- For free-writing tasks, provide expanded examples using basic vocabulary from the lesson (if prompted for 3, provide 10).
- Translate free-writing example answers into English.
- Spell out numbers only when the source token is Albanian; do not spell out English numbers.
Execution steps:
1. Generate two markdown contents in memory (do not create temporary files):
- `MATERIAL_CONTENT`
- `EXERCISES_CONTENT`
2. Set `TODAY="$(date +%F)"` once and reuse it for both notes.
3. Create note 1 with `zk` by piping markdown directly to stdin:
- Title format: `Albanian Lesson Material - YYYY-MM-DD`
- Command pattern:
- `printf "%s\n" "$MATERIAL_CONTENT" | zk new --interactive --title "Albanian Lesson Material - $TODAY" --date "$TODAY" --print-path`
4. Create note 2 with `zk` by piping markdown directly to stdin:
- Title format: `Albanian Lesson Exercises - YYYY-MM-DD`
- Command pattern:
- `printf "%s\n" "$EXERCISES_CONTENT" | zk new --interactive --title "Albanian Lesson Exercises - $TODAY" --date "$TODAY" --print-path`
5. Print both created note paths and a short checklist of what was included.
If no lesson material was provided in `$ARGUMENTS`, stop and ask the user to paste it.

View File

@@ -0,0 +1,108 @@
---
description: Triage inbox one message at a time with himalaya only
---
Process email with strict manual triage using Himalaya only.
Hard requirements:
- Use `himalaya` for every mailbox interaction (folders, listing, reading, moving, deleting, attachments).
- Process exactly one message ID at a time. Never run bulk actions on multiple IDs.
- Do not use pattern-matching commands or searches (`grep`, `rg`, `awk`, `sed`, `himalaya envelope list` query filters, etc.).
- Always inspect current folders first, then triage.
- Treat this as a single deterministic run over a snapshot of message IDs discovered during this run.
- Ingest valuable document attachments into Paperless (see Document Ingestion section below).
Workflow:
1. Run `himalaya folder list` first and use those folders as the primary taxonomy.
2. Use this existing folder set as defaults when it fits:
- `INBOX`
- `Correspondence`
- `Orders and Invoices`
- `Payments`
- `Outgoing Shipments`
- `Newsletters and Marketing`
- `Junk`
- `Deleted Messages`
3. Determine source folder:
- If `$ARGUMENTS` is a single known folder name (matches a folder from step 1), use that as source.
- Otherwise use `INBOX`.
4. Build a run scope safely:
- List with fixed page size `20` and JSON output: `himalaya envelope list -f "<source>" -p 1 -s 20 --output json`.
- Start at page `1`. Enumerate IDs in returned order.
- Process each ID fully before touching the next ID.
- Keep an in-memory reviewed set for this run to avoid reprocessing IDs already handled or intentionally left untouched.
- When all IDs on the current page are in the reviewed set, advance to the next page.
- Stop when a page returns fewer results than the page size (end of folder) and all its IDs are in the reviewed set.
5. For each single envelope ID, do all checks before any move/delete:
- Check envelope flags from the JSON listing (seen/answered/flagged) before reading.
- Read the message: `himalaya message read -f "<source>" <id>`.
- If needed for classification or ingestion, download attachments: `himalaya attachment download -f "<source>" <id> --dir /tmp/himalaya-triage`.
- If the message qualifies for document ingestion (see Document Ingestion below), copy eligible attachments to the Paperless consume directory before cleanup.
- Always `rm` downloaded files from `/tmp/himalaya-triage` after processing (whether ingested or not).
- Move: `himalaya message move -f "<source>" "<destination>" <id>`.
- Delete: `himalaya message delete -f "<source>" <id>`.
6. Classification precedence (higher rule wins on conflict):
- **Actionable and unhandled** — if the message needs a reply, requires manual payment, needs a confirmation, or demands any human action, AND has NOT been replied to (no `answered` flag), leave it in the source folder untouched. This is the highest-priority rule: anything that still needs attention stays in `INBOX`.
- Human correspondence already handled — freeform natural-language messages written by a human that have been replied to (`answered` flag set): move to `Correspondence`.
- Human communication not yet replied to but not clearly actionable — when in doubt whether a human message requires action, leave it untouched.
- Clearly ephemeral automated/system message (alerts, bot/status updates, OTP/2FA, password reset codes, login codes) with no archival value: move to `Deleted Messages`.
- Automatic payment transaction notifications (charge/payment confirmations, receipts, failed-payment notices, provider payment events such as Klarna/PayPal/Stripe) that are purely informational and require no action: move to `Payments`.
- Subscription renewal notifications (auto-renew reminders, "will renew soon", price-change notices without a concrete transaction) are operational alerts, not payment records: move to `Deleted Messages`.
- Installment plan activation notifications (e.g. Barclays installment purchase confirmations) are operational confirmations, not payment records: move to `Deleted Messages`.
- "Kontoauszug verfügbar/ist online" notifications are availability alerts, not payment records: move to `Deleted Messages`.
- Orders/invoices/business records: move to `Orders and Invoices`.
- Shipping/tracking notifications (dispatch confirmations, carrier updates, delivery ETAs) without invoice or order-document value: move to `Deleted Messages`.
- Marketing/newsletters: move to `Newsletters and Marketing`.
- Delivery/submission confirmations for items you shipped outbound: move to `Outgoing Shipments`.
- Long-term but uncategorized messages: create a concise new folder and move there.
7. Folder creation rule:
- Create a new folder only if no existing folder fits and the message should be kept.
- Naming constraints: concise topic name, avoid duplicates, and avoid broad catch-all names.
- Command: `himalaya folder add "<new-folder>"`.
Document Ingestion (Paperless):
- **Purpose**: Automatically archive valuable document attachments into Paperless via its consumption directory.
- **Ingestion path**: `/var/lib/paperless/consume/inbox-triage/`
- **When to ingest**: Only for messages whose attachments have long-term archival value. Eligible categories:
- Invoices, receipts, and billing statements (messages going to `Orders and Invoices` or `Payments`)
- Contracts, agreements, and legal documents
- Tax documents, account statements, and financial summaries
- Insurance documents and policy papers
- Official correspondence with document attachments (government, institutions)
- **When NOT to ingest**:
- Marketing emails, newsletters, promotional material
- Shipping/tracking notifications without invoice attachments
- OTP codes, login alerts, password resets, ephemeral notifications
- Subscription renewal reminders without actual invoices
- Duplicate documents already seen in this run
- Inline images, email signatures, logos, and non-document attachments
- **Eligible file types**: PDF, PNG, JPG/JPEG, TIFF, WEBP (documents and scans only). Skip archive files (ZIP, etc.), calendar invites (ICS), and other non-document formats.
- **Procedure**:
1. After downloading attachments to `/tmp/himalaya-triage`, check if any are eligible documents.
2. Copy eligible files: `cp /tmp/himalaya-triage/<filename> /var/lib/paperless/consume/inbox-triage/`
3. If multiple messages could produce filename collisions, prefix the filename with the message ID: `<id>-<filename>`.
4. Log each ingested file in the action log at the end of the run.
- **Conservative rule**: When in doubt whether an attachment is worth archiving, skip it. Paperless storage is cheap, but noise degrades searchability. Prefer false negatives over false positives for marketing material, but prefer false positives over false negatives for anything that looks like a financial or legal document.
Execution rules:
- Never perform bulk operations. One message ID per `read`, `move`, `delete`, and attachment command.
- Always use page size 20 for envelope listing (`-s 20`).
- If any single-ID command fails, log the error and continue with the next unreviewed ID.
- Never skip reading message content before deciding.
- Keep decisions conservative: when in doubt about whether something needs action, leave it in `INBOX`.
- Never move or delete unhandled actionable messages.
- Never move human communications that haven't been replied to, unless clearly non-actionable.
- Define "processed" as "reviewed once in this run" (including intentionally untouched human messages).
- Include only messages observed during this run's listings; if new mail arrives mid-run, leave it for the next run.
- Report a compact action log at the end with:
- source folder,
- total reviewed IDs,
- counts by action (untouched/moved-to-folder/deleted),
- per-destination-folder counts,
- created folders,
- documents ingested to Paperless (count and filenames),
- short rationale for non-obvious classifications.
<user-request>
$ARGUMENTS
</user-request>

View File

@@ -0,0 +1,150 @@
---
description: Initialize Supermemory with comprehensive codebase knowledge
---
# Initializing Supermemory
You are initializing persistent memory for this codebase. This is not just data collection - you're building context that will make you significantly more effective across all future sessions.
## Understanding Context
You are a **stateful** coding agent. Users expect to work with you over extended periods - potentially the entire lifecycle of a project. Your memory is how you get better over time and maintain continuity.
## What to Remember
### 1. Procedures (Rules & Workflows)
Explicit rules that should always be followed:
- "Never commit directly to main - always use feature branches"
- "Always run lint before tests"
- "Use conventional commits format"
### 2. Preferences (Style & Conventions)
Project and user coding style:
- "Prefer functional components over class components"
- "Use early returns instead of nested conditionals"
- "Always add JSDoc to exported functions"
### 3. Architecture & Context
How the codebase works and why:
- "Auth system was refactored in v2.0 - old patterns deprecated"
- "The monorepo used to have 3 modules before consolidation"
- "This pagination bug was fixed before - similar to PR #234"
## Memory Scopes
**Project-scoped** (\`scope: "project"\`):
- Build/test/lint commands
- Architecture and key directories
- Team conventions specific to this codebase
- Technology stack and framework choices
- Known issues and their solutions
**User-scoped** (\`scope: "user"\`):
- Personal coding preferences across all projects
- Communication style preferences
- General workflow habits
## Research Approach
This is a **deep research** initialization. Take your time and be thorough (~50+ tool calls). The goal is to genuinely understand the project, not just collect surface-level facts.
**What to uncover:**
- Tech stack and dependencies (explicit and implicit)
- Project structure and architecture
- Build/test/deploy commands and workflows
- Contributors & team dynamics (who works on what?)
- Commit conventions and branching strategy
- Code evolution (major refactors, architecture changes)
- Pain points (areas with lots of bug fixes)
- Implicit conventions not documented anywhere
## Research Techniques
### File-based
- README.md, CONTRIBUTING.md, AGENTS.md, CLAUDE.md
- Package manifests (package.json, Cargo.toml, pyproject.toml, go.mod)
- Config files (.eslintrc, tsconfig.json, .prettierrc)
- CI/CD configs (.github/workflows/)
### Git-based
- \`git log --oneline -20\` - Recent history
- \`git branch -a\` - Branching strategy
- \`git log --format="%s" -50\` - Commit conventions
- \`git shortlog -sn --all | head -10\` - Main contributors
### Explore Agent
Fire parallel explore queries for broad understanding:
\`\`\`
Task(explore, "What is the tech stack and key dependencies?")
Task(explore, "What is the project structure? Key directories?")
Task(explore, "How do you build, test, and run this project?")
Task(explore, "What are the main architectural patterns?")
Task(explore, "What conventions or patterns are used?")
\`\`\`
## How to Do Thorough Research
**Don't just collect data - analyze and cross-reference.**
Bad (shallow):
- Run commands, copy output
- List facts without understanding
Good (thorough):
- Cross-reference findings (if inconsistent, dig deeper)
- Resolve ambiguities (don't leave questions unanswered)
- Read actual file content, not just names
- Look for patterns (what do commits tell you about workflow?)
- Think like a new team member - what would you want to know?
## Saving Memories
Use the \`supermemory\` tool for each distinct insight:
\`\`\`
supermemory(mode: "add", content: "...", type: "...", scope: "project")
\`\`\`
**Types:**
- \`project-config\` - tech stack, commands, tooling
- \`architecture\` - codebase structure, key components, data flow
- \`learned-pattern\` - conventions specific to this codebase
- \`error-solution\` - known issues and their fixes
- \`preference\` - coding style preferences (use with user scope)
**Guidelines:**
- Save each distinct insight as a separate memory
- Be concise but include enough context to be useful
- Include the "why" not just the "what" when relevant
- Update memories incrementally as you research (don't wait until the end)
**Good memories:**
- "Uses Bun runtime and package manager. Commands: bun install, bun run dev, bun test"
- "API routes in src/routes/, handlers in src/handlers/. Hono framework."
- "Auth uses Redis sessions, not JWT. Implementation in src/lib/auth.ts"
- "Never use \`any\` type - strict TypeScript. Use \`unknown\` and narrow."
- "Database migrations must be backward compatible - we do rolling deploys"
## Upfront Questions
Before diving in, ask:
1. "Any specific rules I should always follow?"
2. "Preferences for how I communicate? (terse/detailed)"
## Reflection Phase
Before finishing, reflect:
1. **Completeness**: Did you cover commands, architecture, conventions, gotchas?
2. **Quality**: Are memories concise and searchable?
3. **Scope**: Did you correctly separate project vs user knowledge?
Then ask: "I've initialized memory with X insights. Want me to continue refining, or is this good?"
## Your Task
1. Ask upfront questions (research depth, rules, preferences)
2. Check existing memories: \`supermemory(mode: "list", scope: "project")\`
3. Research based on chosen depth
4. Save memories incrementally as you discover insights
5. Reflect and verify completeness
6. Summarize what was learned and ask if user wants refinement

View File

@@ -0,0 +1,49 @@
import type { Plugin } from "@opencode-ai/plugin";
const COMMAND_PREFIXES = new Set([
"env",
"command",
"builtin",
"time",
"sudo",
"nohup",
"nice",
]);
function findCommandWord(words: string[]): string | undefined {
for (const word of words) {
if (COMMAND_PREFIXES.has(word)) continue;
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(word)) continue;
return word;
}
return undefined;
}
function segmentHasGit(words: string[]): boolean {
const cmd = findCommandWord(words);
return cmd === "git";
}
function containsBlockedGit(command: string): boolean {
const segments = command.split(/\s*(?:&&|\|\||[;&|]|\$\(|`)\s*/);
for (const segment of segments) {
const words = segment.trim().split(/\s+/).filter(Boolean);
if (segmentHasGit(words)) return true;
}
return false;
}
export const BlockGitPlugin: Plugin = async () => {
return {
"tool.execute.before": async (input, output) => {
if (input.tool === "bash") {
const command = output.args.command as string;
if (containsBlockedGit(command)) {
throw new Error(
"This project uses jj, only use `jj` commands, not `git`.",
);
}
}
},
};
};

View File

@@ -0,0 +1,19 @@
import type { Plugin } from "@opencode-ai/plugin";
const SCRIPTING_PATTERN =
/(?:^|[;&|]\s*|&&\s*|\|\|\s*|\$\(\s*|`\s*)(?:python[23]?|perl|ruby|php|lua|node\s+-e|bash\s+-c|sh\s+-c)\s/;
export const BlockScriptingPlugin: Plugin = async () => {
return {
"tool.execute.before": async (input, output) => {
if (input.tool === "bash") {
const command = output.args.command as string;
if (SCRIPTING_PATTERN.test(command)) {
throw new Error(
"Do not use python, perl, ruby, php, lua, or inline bash/sh for scripting. Use `nu -c` instead.",
);
}
}
},
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
## Frontend Aesthetics Guidelines
Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.

View File

@@ -1,15 +1,15 @@
{inputs, ...}: final: prev: let
version = "0.22.1";
version = "0.24.1";
srcs = {
x86_64-linux =
prev.fetchurl {
url = "https://github.com/trycog/cog-cli/releases/download/v${version}/cog-linux-x86_64.tar.gz";
hash = "sha256-ET+sNXisUrHShR1gxqdumegXycXcxGzJcQOdTr5005w=";
hash = "sha256-/ioEuM58F3ppO0wlc5nw7ZNHunoweOXL/Gda65r0Ig4=";
};
aarch64-darwin =
prev.fetchurl {
url = "https://github.com/trycog/cog-cli/releases/download/v${version}/cog-darwin-arm64.tar.gz";
hash = "sha256-jcN+DtOqr3or5C71jp7AIAz0wh73FYybCC4FRBykKO4=";
hash = "sha256-o/A2hVU3Jzmlzx5RbGLFCpfGAghcLGTD8Bm+bVR5OkQ=";
};
};
in {

View File

@@ -1,10 +0,0 @@
{inputs, ...}: final: prev: {
pi-agent-stuff =
prev.buildNpmPackage {
pname = "pi-agent-stuff";
version = "1.5.0";
src = inputs.pi-agent-stuff;
npmDepsHash = "sha256-pyXMNdlie8vAkhz2f3GUGT3CCYuwt+xkWnsijBajXIo=";
dontNpmBuild = true;
};
}

View File

@@ -1,33 +0,0 @@
{inputs, ...}: final: prev: {
pi-harness =
prev.stdenvNoCC.mkDerivation {
pname = "pi-harness";
version = "0.0.0";
src = inputs.pi-harness;
pnpmDeps =
prev.fetchPnpmDeps {
pname = "pi-harness";
version = "0.0.0";
src = inputs.pi-harness;
pnpm = prev.pnpm_10;
fetcherVersion = 1;
hash = "sha256-FgtJnmJ0/udz2A9N2DQns+a2CspMDEDk0DPUAxmCVY4=";
};
nativeBuildInputs = [
prev.pnpmConfigHook
prev.pnpm_10
prev.nodejs
];
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out/lib/node_modules/@aliou/pi-harness
cp -r . $out/lib/node_modules/@aliou/pi-harness
runHook postInstall
'';
};
}

View File

@@ -1,10 +0,0 @@
{inputs, ...}: final: prev: {
pi-mcp-adapter =
prev.buildNpmPackage {
pname = "pi-mcp-adapter";
version = "2.2.0";
src = inputs.pi-mcp-adapter;
npmDepsHash = "sha256-myJ9h/zC/KDddt8NOVvJjjqbnkdEN4ZR+okCR5nu7hM=";
dontNpmBuild = true;
};
}

View File

@@ -0,0 +1,181 @@
{
"document": {
"block_prefix": "\n",
"block_suffix": "\n",
"color": "#575279",
"margin": 2
},
"block_quote": {
"color": "#797593",
"italic": true,
"indent": 1,
"indent_token": "│ "
},
"list": {
"color": "#575279",
"level_indent": 2
},
"heading": {
"block_suffix": "\n",
"color": "#907aa9",
"bold": true
},
"h1": {
"prefix": "# ",
"bold": true
},
"h2": {
"prefix": "## "
},
"h3": {
"prefix": "### "
},
"h4": {
"prefix": "#### "
},
"h5": {
"prefix": "##### "
},
"h6": {
"prefix": "###### "
},
"strikethrough": {
"crossed_out": true
},
"emph": {
"italic": true,
"color": "#d7827e"
},
"strong": {
"bold": true,
"color": "#286983"
},
"hr": {
"color": "#dfdad9",
"format": "\n--------\n"
},
"item": {
"block_prefix": "• "
},
"enumeration": {
"block_prefix": ". ",
"color": "#286983"
},
"task": {
"ticked": "[✓] ",
"unticked": "[ ] "
},
"link": {
"color": "#286983",
"underline": true
},
"link_text": {
"color": "#56949f"
},
"image": {
"color": "#286983",
"underline": true
},
"image_text": {
"color": "#56949f",
"format": "Image: {{.text}} →"
},
"code": {
"color": "#ea9d34",
"background_color": "#f2e9e1",
"prefix": " ",
"suffix": " "
},
"code_block": {
"color": "#ea9d34",
"margin": 2,
"chroma": {
"text": {
"color": "#575279"
},
"error": {
"color": "#faf4ed",
"background_color": "#b4637a"
},
"comment": {
"color": "#9893a5"
},
"comment_preproc": {
"color": "#56949f"
},
"keyword": {
"color": "#b4637a"
},
"keyword_reserved": {
"color": "#b4637a"
},
"keyword_namespace": {
"color": "#b4637a"
},
"keyword_type": {
"color": "#907aa9"
},
"operator": {
"color": "#56949f"
},
"punctuation": {
"color": "#797593"
},
"name": {
"color": "#286983"
},
"name_constant": {
"color": "#907aa9"
},
"name_builtin": {
"color": "#d7827e"
},
"name_tag": {
"color": "#b4637a"
},
"name_attribute": {
"color": "#d7827e"
},
"name_class": {
"color": "#907aa9"
},
"name_decorator": {
"color": "#56949f"
},
"name_function": {
"color": "#286983"
},
"literal_number": {
"color": "#ea9d34"
},
"literal_string": {
"color": "#ea9d34"
},
"literal_string_escape": {
"color": "#d7827e"
},
"generic_deleted": {
"color": "#b4637a"
},
"generic_emph": {
"italic": true
},
"generic_inserted": {
"color": "#286983"
},
"generic_strong": {
"bold": true
},
"generic_subheading": {
"color": "#907aa9"
},
"background": {
"background_color": "#f2e9e1"
}
}
},
"table": {},
"definition_description": {
"block_prefix": "\n🠶 "
}
}

62
modules/adguardhome.nix Normal file
View File

@@ -0,0 +1,62 @@
{...}: let
caddyLib = import ./_lib/caddy.nix;
in {
den.aspects.adguardhome.nixos = {config, ...}: {
services.adguardhome = {
enable = true;
host = "127.0.0.1";
port = 10000;
settings = {
dhcp.enabled = false;
dns.upstream_dns = [
"1.1.1.1"
"1.0.0.1"
];
filtering = {
protection_enabled = true;
filtering_enabled = true;
safe_search.enabled = false;
safebrowsing_enabled = true;
blocked_response_ttl = 10;
filters_update_interval = 24;
blocked_services.ids = [
"reddit"
"twitter"
];
};
filters = [
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/pro.txt";
name = "HaGeZi Multi PRO";
id = 1;
}
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/tif.txt";
name = "HaGeZi Threat Intelligence Feeds";
id = 2;
}
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/gambling.txt";
name = "HaGeZi Gambling";
id = 3;
}
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/nsfw.txt";
name = "HaGeZi NSFW";
id = 4;
}
];
};
};
services.caddy.virtualHosts =
caddyLib.mkTailscaleVHost {
name = "adguard";
configText = "reverse_proxy localhost:${toString config.services.adguardhome.port}";
};
};
}

View File

@@ -1,14 +1,16 @@
{inputs, ...}: {
{inputs, ...}: let
local = import ./_lib/local.nix;
inherit (local) secretPath;
opencodeSecretPath = secretPath "opencode-api-key";
in {
den.aspects.ai-tools.homeManager = {
lib,
pkgs,
inputs',
...
}: let
opencodeSecretPath = "/run/secrets/opencode-api-key";
in {
}: {
home.packages = [
inputs'.llm-agents.packages.pi
inputs'.llm-agents.packages.claude-code
pkgs.cog-cli
];
@@ -19,60 +21,160 @@
}
'';
home.file = {
"AGENTS.md".source = ./_ai-tools/AGENTS.md;
".pi/agent/extensions/pi-elixir" = {
source = inputs.pi-elixir;
recursive = true;
programs.opencode = {
enable = true;
package = inputs'.llm-agents.packages.opencode;
tui = {
theme = "rosepine";
plugin = ["./plugin/review.ts"];
};
".pi/agent/extensions/pi-mcp-adapter" = {
source = "${pkgs.pi-mcp-adapter}/lib/node_modules/pi-mcp-adapter";
recursive = true;
};
".pi/agent/extensions/no-git.ts".source = ./_ai-tools/extensions/no-git.ts;
".pi/agent/extensions/no-scripting.ts".source = ./_ai-tools/extensions/no-scripting.ts;
".pi/agent/extensions/review.ts".source = ./_ai-tools/extensions/review.ts;
".pi/agent/extensions/session-name.ts".source = ./_ai-tools/extensions/session-name.ts;
".pi/agent/skills/elixir-dev" = {
source = "${inputs.pi-elixir}/skills/elixir-dev";
recursive = true;
};
".pi/agent/skills/jujutsu/SKILL.md".source = ./_ai-tools/skills/jujutsu/SKILL.md;
".pi/agent/themes" = {
source = "${inputs.pi-rose-pine}/themes";
recursive = true;
};
".pi/agent/settings.json".text =
builtins.toJSON {
theme = "rose-pine-dawn";
quietStartup = true;
hideThinkingBlock = true;
defaultProvider = "openai-codex";
defaultModel = "gpt-5.4";
defaultThinkingLevel = "high";
packages = [
{
source = "${pkgs.pi-agent-stuff}/lib/node_modules/mitsupi";
extensions = [
"pi-extensions/answer.ts"
"pi-extensions/context.ts"
"pi-extensions/multi-edit.ts"
"pi-extensions/todos.ts"
];
skills = [];
prompts = [];
themes = [];
}
{
source = "${pkgs.pi-harness}/lib/node_modules/@aliou/pi-harness";
extensions = ["extensions/breadcrumbs/index.ts"];
skills = [];
prompts = [];
themes = [];
}
settings = {
model = "openai/gpt-5.4";
small_model = "openai/gpt-5.1-codex-mini";
plugin = [
"opencode-claude-auth"
"opencode-supermemory"
];
permission = {
external_directory = {
"*" = "allow";
"**/.gnupg/**" = "deny";
"**/.ssh/**" = "deny";
"~/.config/gh/hosts.yml" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.local/share/opencode/mcp-auth.json" = "deny";
"/etc/ssh/ssh_host_*" = "deny";
"/run/secrets/*" = "deny";
};
".pi/agent/mcp.json".source = ./_ai-tools/mcp.json;
bash = {
"*" = "allow";
env = "deny";
"env *" = "deny";
printenv = "deny";
"printenv *" = "deny";
"export *" = "deny";
"gh auth *" = "deny";
ssh = "ask";
"ssh *" = "ask";
mosh = "ask";
"mosh *" = "ask";
"cat *.env" = "deny";
"cat *.env.*" = "deny";
"cat **/.env" = "deny";
"cat **/.env.*" = "deny";
"cat *.envrc" = "deny";
"cat **/.envrc" = "deny";
"cat .dev.vars" = "deny";
"cat **/.dev.vars" = "deny";
"cat *.pem" = "deny";
"cat *.key" = "deny";
"cat **/.gnupg/**" = "deny";
"cat **/.ssh/**" = "deny";
"cat ~/.config/gh/hosts.yml" = "deny";
"cat ~/.config/sops/age/keys.txt" = "deny";
"cat ~/.local/share/opencode/mcp-auth.json" = "deny";
"cat /etc/ssh/ssh_host_*" = "deny";
"cat /run/secrets/*" = "deny";
};
edit = {
"*" = "allow";
"**/.gnupg/**" = "deny";
"**/.ssh/**" = "deny";
"**/secrets/**" = "deny";
"secrets/*" = "deny";
"~/.config/gh/hosts.yml" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.local/share/opencode/mcp-auth.json" = "deny";
"/etc/ssh/ssh_host_*" = "deny";
"/run/secrets/*" = "deny";
};
glob = "allow";
grep = "allow";
list = "allow";
lsp = "allow";
question = "allow";
read = {
"*" = "allow";
"*.env" = "deny";
"*.env.*" = "deny";
"*.envrc" = "deny";
"**/.env" = "deny";
"**/.env.*" = "deny";
"**/.envrc" = "deny";
".dev.vars" = "deny";
"**/.dev.vars" = "deny";
"**/.gnupg/**" = "deny";
"**/.ssh/**" = "deny";
"*.key" = "deny";
"*.pem" = "deny";
"**/secrets/**" = "deny";
"secrets/*" = "deny";
"~/.config/gh/hosts.yml" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.local/share/opencode/mcp-auth.json" = "deny";
"/etc/ssh/ssh_host_*" = "deny";
"/run/secrets/*" = "deny";
};
skill = "allow";
task = "allow";
webfetch = "allow";
websearch = "allow";
codesearch = "allow";
};
agent = {
explore = {
model = "openai/gpt-5.1-codex-mini";
};
};
instructions = [
"CLAUDE.md"
"AGENT.md"
# "AGENTS.md"
"AGENTS.local.md"
];
formatter = {
mix = {
disabled = true;
};
};
mcp = {
opensrc = {
enabled = true;
type = "local";
command = ["node" "/home/cschmatzler/.bun/bin/opensrc-mcp"];
};
context7 = {
enabled = true;
type = "remote";
url = "https://mcp.context7.com/mcp";
};
grep_app = {
enabled = true;
type = "remote";
url = "https://mcp.grep.app";
};
};
};
};
xdg.configFile = {
# "opencode/agent" = {
# source = ./_opencode/agent;
# recursive = true;
# };
"opencode/command" = {
source = ./_opencode/command;
recursive = true;
};
"opencode/skill" = {
source = ./_opencode/skill;
recursive = true;
};
"opencode/plugin" = {
source = ./_opencode/plugin;
recursive = true;
};
"opencode/AGENTS.md".source = ./_opencode/AGENTS.md;
};
};
}

View File

@@ -26,8 +26,8 @@
'')}/bin/${name}";
meta.description = descriptions.${name};
};
platformAppNames = ["build" "rollback" "update"];
sharedAppNames = ["apply"];
platformAppNames = ["build" "rollback"];
sharedAppNames = ["apply" "update"];
in {
apps =
pkgs.lib.genAttrs platformAppNames mkPlatformApp

11
modules/cache.nix Normal file
View File

@@ -0,0 +1,11 @@
{...}: let
caddyLib = import ./_lib/caddy.nix;
in {
den.aspects.cache.nixos = {
services.caddy.virtualHosts =
caddyLib.mkTailscaleVHost {
name = "cache";
configText = "reverse_proxy localhost:32843";
};
};
}

View File

@@ -1,4 +1,7 @@
{inputs, ...}: {
{inputs, ...}: let
local = import ./_lib/local.nix;
userHome = "/Users/${local.user.name}";
in {
den.aspects.darwin-system.darwin = {pkgs, ...}: {
imports = [
inputs.nix-homebrew.darwinModules.nix-homebrew
@@ -6,7 +9,7 @@
./_darwin/dock.nix
];
system.primaryUser = "cschmatzler";
system.primaryUser = local.user.name;
# Darwin system utilities
environment.systemPackages = with pkgs; [
@@ -41,7 +44,7 @@
autohide = true;
show-recents = false;
launchanim = true;
orientation = "bottom";
orientation = "left";
tilesize = 60;
minimize-to-application = true;
mru-spaces = false;
@@ -111,7 +114,7 @@
};
nix = {
settings.trusted-users = ["cschmatzler"];
settings.trusted-users = [local.user.name];
gc.interval = {
Weekday = 0;
Hour = 2;
@@ -119,18 +122,16 @@
};
};
users.users.cschmatzler = {
name = "cschmatzler";
home = "/Users/cschmatzler";
users.users.${local.user.name} = {
name = local.user.name;
home = userHome;
isHidden = false;
shell = pkgs.nushell;
};
home-manager.useGlobalPkgs = true;
nix-homebrew = {
enable = true;
user = "cschmatzler";
user = local.user.name;
mutableTaps = true;
taps = {
"homebrew/homebrew-core" = inputs.homebrew-core;
@@ -140,15 +141,24 @@
homebrew = {
enable = true;
onActivation = {
autoUpdate = true;
cleanup = "uninstall";
upgrade = true;
};
taps = [
"homebrew/cask"
];
casks = [
"1password"
"alcove"
"aerospace"
"aqua-voice"
"chatgpt"
"ghostty@tip"
"helium-browser"
"raycast"
"tidal"
"spotify"
"tailscale"
"whatsapp"
];
};
};

View File

@@ -25,8 +25,18 @@
flake.flakeModules = {
# Shared system foundations
core = ./core.nix;
darwin = ./darwin.nix;
network = ./network.nix;
nixos-system = ./nixos-system.nix;
overlays = ./overlays.nix;
secrets = ./secrets.nix;
# Shared host features
adguardhome = ./adguardhome.nix;
cache = ./cache.nix;
gitea = ./gitea.nix;
opencode = ./opencode.nix;
paperless = ./paperless.nix;
# User environment
ai-tools = ./ai-tools.nix;
@@ -34,7 +44,6 @@
desktop = ./desktop.nix;
dev-tools = ./dev-tools.nix;
email = ./email.nix;
finance = ./finance.nix;
neovim = ./neovim.nix;
shell = ./shell.nix;
ssh-client = ./ssh-client.nix;
@@ -44,7 +53,12 @@
};
den.default.nixos.system.stateVersion = "25.11";
den.default.darwin.system.stateVersion = 6;
den.default.homeManager.home.stateVersion = "25.11";
den.default.homeManager = {
home.stateVersion = "25.11";
programs.home-manager.enable = true;
};
den.default.nixos.home-manager.useGlobalPkgs = true;
den.default.darwin.home-manager.useGlobalPkgs = true;
den.default.includes = [
den.provides.define-user

View File

@@ -54,26 +54,6 @@
inputs.nixpkgs.follows = "nixpkgs";
};
llm-agents.url = "github:numtide/llm-agents.nix";
pi-agent-stuff = {
url = "github:mitsuhiko/agent-stuff";
flake = false;
};
pi-elixir = {
url = "github:dannote/pi-elixir";
flake = false;
};
pi-rose-pine = {
url = "github:zenobi-us/pi-rose-pine";
flake = false;
};
pi-harness = {
url = "github:aliou/pi-harness";
flake = false;
};
pi-mcp-adapter = {
url = "github:nicobailon/pi-mcp-adapter/v2.2.0";
flake = false;
};
# Overlay inputs
himalaya.url = "github:pimalaya/himalaya";
jj-ryu = {

View File

@@ -2,23 +2,35 @@
inputs,
config,
...
}: {
}: let
local = import ./_lib/local.nix;
acceptNewHostKeys = [
"-o"
"StrictHostKeyChecking=accept-new"
];
mkSystemNode = {
hostname,
host,
}: {
inherit hostname;
sshUser = local.user.name;
sshOpts = acceptNewHostKeys;
profiles.system = {
user = "root";
path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos config.flake.nixosConfigurations.${host};
};
};
in {
flake.deploy.nodes = {
michael = {
hostname = "michael";
sshUser = "cschmatzler";
profiles.system = {
user = "root";
path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos config.flake.nixosConfigurations.michael;
};
};
tahani = {
hostname = "tahani";
sshUser = "cschmatzler";
profiles.system = {
user = "root";
path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos config.flake.nixosConfigurations.tahani;
michael =
mkSystemNode {
hostname = "git.schmatzler.com";
host = "michael";
};
tahani =
mkSystemNode {
hostname = "127.0.0.1";
host = "tahani";
};
};

View File

@@ -3,19 +3,9 @@
lib,
pkgs,
...
}: let
aerospaceApp = "/Applications/AeroSpace.app/Contents/MacOS/AeroSpace";
in {
}: {
programs.aerospace = {
enable = true;
package =
pkgs.emptyDirectory.overrideAttrs (old: {
meta =
(old.meta or {})
// {
mainProgram = "aerospace";
};
});
launchd.enable = true;
settings = {
start-at-login = true;
@@ -66,7 +56,7 @@
}
{
"if" = {
"app-id" = "net.imput.helium";
"app-id" = "com.apple.Safari";
};
run = "move-node-to-workspace 2";
}
@@ -154,15 +144,5 @@
};
};
};
home.file.".aerospace.toml".onChange =
lib.mkForce ''
if [ -x "${aerospaceApp}" ]; then
echo "AeroSpace config changed, reloading..."
"${aerospaceApp}" reload-config || true
fi
'';
launchd.agents.aerospace.config.Program = lib.mkForce aerospaceApp;
};
}

View File

@@ -1,10 +1,13 @@
{...}: {
{...}: let
local = import ./_lib/local.nix;
palette = (import ./_lib/theme.nix).rosePineDawn.hex;
in {
den.aspects.dev-tools.homeManager = {
pkgs,
lib,
...
}: let
name = "Christoph Schmatzler";
name = local.user.fullName;
in {
home.packages = with pkgs;
[
@@ -33,6 +36,13 @@
tea
tokei
tree-sitter
(pkgs.writeShellApplication {
name = "tuist-pr";
runtimeInputs = with pkgs; [coreutils fzf gh git nushell];
text = ''
exec nu ${./_dev-tools/tuist-pr.nu} "$@"
'';
})
]
++ lib.optionals stdenv.isDarwin [
xcodes
@@ -85,7 +95,7 @@
settings = {
user = {
name = name;
email = "christoph@schmatzler.com";
email = local.user.emails.personal;
};
git = {
sign-on-push = true;
@@ -117,7 +127,7 @@
revset-aliases = {
"closest_bookmark(to)" = "heads(::to & bookmarks())";
"closest_pushable(to)" = "heads(::to & mutable() & ~description(exact:\"\") & (~empty() | merges()))";
"mine()" = "author(\"christoph@schmatzler.com\")";
"mine()" = "author(\"${local.user.emails.personal}\")";
"wip()" = "mine() ~ immutable()";
"open()" = "mine() ~ ::trunk()";
"current()" = "@:: & mutable()";
@@ -143,76 +153,76 @@
programs.jjui = {
enable = true;
settings.ui.colors = {
text = {fg = "#575279";};
dimmed = {fg = "#9893a5";};
text = {fg = palette.text;};
dimmed = {fg = palette.muted;};
selected = {
bg = "#f2e9e1";
fg = "#575279";
bg = palette.overlay;
fg = palette.text;
bold = true;
};
border = {fg = "#9893a5";};
border = {fg = palette.muted;};
title = {
fg = "#907aa9";
fg = palette.iris;
bold = true;
};
shortcut = {
fg = "#286983";
fg = palette.pine;
bold = true;
};
matched = {
fg = "#ea9d34";
fg = palette.gold;
bold = true;
};
"revisions selected" = {
bg = "#f2e9e1";
fg = "#575279";
bg = palette.overlay;
fg = palette.text;
bold = true;
};
"status" = {bg = "#f2e9e1";};
"status" = {bg = palette.overlay;};
"status title" = {
bg = "#907aa9";
fg = "#faf4ed";
bg = palette.iris;
fg = palette.base;
bold = true;
};
"status shortcut" = {fg = "#286983";};
"status dimmed" = {fg = "#9893a5";};
"menu" = {bg = "#faf4ed";};
"status shortcut" = {fg = palette.pine;};
"status dimmed" = {fg = palette.muted;};
"menu" = {bg = palette.base;};
"menu selected" = {
bg = "#f2e9e1";
fg = "#575279";
bg = palette.overlay;
fg = palette.text;
bold = true;
};
"menu border" = {fg = "#9893a5";};
"menu border" = {fg = palette.muted;};
"menu title" = {
fg = "#907aa9";
fg = palette.iris;
bold = true;
};
"menu shortcut" = {fg = "#286983";};
"menu shortcut" = {fg = palette.pine;};
"menu matched" = {
fg = "#ea9d34";
fg = palette.gold;
bold = true;
};
"preview border" = {fg = "#9893a5";};
"help" = {bg = "#faf4ed";};
"help border" = {fg = "#9893a5";};
"preview border" = {fg = palette.muted;};
"help" = {bg = palette.base;};
"help border" = {fg = palette.muted;};
"help title" = {
fg = "#907aa9";
fg = palette.iris;
bold = true;
};
"confirmation" = {bg = "#faf4ed";};
"confirmation border" = {fg = "#9893a5";};
"confirmation" = {bg = palette.base;};
"confirmation border" = {fg = palette.muted;};
"confirmation selected" = {
bg = "#f2e9e1";
fg = "#575279";
bg = palette.overlay;
fg = palette.text;
bold = true;
};
"confirmation dimmed" = {fg = "#9893a5";};
"confirmation dimmed" = {fg = palette.muted;};
source_marker = {
fg = "#56949f";
fg = palette.foam;
bold = true;
};
target_marker = {
fg = "#d7827e";
fg = palette.rose;
bold = true;
};
};

View File

@@ -1,5 +1,12 @@
{...}: {
{...}: let
local = import ./_lib/local.nix;
in {
den.aspects.email.homeManager = {pkgs, ...}: {
programs.aerc = {
enable = true;
extraConfig.general.unsafe-accounts-conf = true;
};
programs.himalaya = {
enable = true;
package =
@@ -19,13 +26,13 @@
};
accounts.email = {
accounts."christoph@schmatzler.com" = {
accounts.${local.user.emails.personal} = {
primary = true;
maildir.path = "christoph@schmatzler.com";
address = "christoph@schmatzler.com";
userName = "christoph.schmatzler@icloud.com";
realName = "Christoph Schmatzler";
passwordCommand = ["${pkgs.coreutils}/bin/cat" "/run/secrets/tahani-email-password"];
maildir.path = local.user.emails.personal;
address = local.user.emails.personal;
userName = local.user.emails.icloud;
realName = local.user.fullName;
passwordCommand = ["${pkgs.coreutils}/bin/cat" (local.secretPath "tahani-email-password")];
folders = {
inbox = "INBOX";
drafts = "Drafts";
@@ -48,6 +55,7 @@
port = 993;
tls.enable = true;
};
aerc.enable = true;
};
};
};

View File

@@ -1,5 +0,0 @@
{...}: {
den.aspects.finance.homeManager = {pkgs, ...}: {
home.packages = [pkgs.hledger];
};
}

166
modules/gitea.nix Normal file
View File

@@ -0,0 +1,166 @@
{lib, ...}: let
secretLib = import ./_lib/secrets.nix {inherit lib;};
in {
den.aspects.gitea.nixos = {
config,
lib,
pkgs,
...
}: {
sops.secrets = {
michael-gitea-litestream =
secretLib.mkServiceBinarySecret {
name = "michael-gitea-litestream";
serviceUser = "gitea";
sopsFile = ../secrets/michael-gitea-litestream;
};
michael-gitea-restic-password =
secretLib.mkServiceBinarySecret {
name = "michael-gitea-restic-password";
serviceUser = "gitea";
sopsFile = ../secrets/michael-gitea-restic-password;
};
michael-gitea-restic-env =
secretLib.mkServiceBinarySecret {
name = "michael-gitea-restic-env";
serviceUser = "gitea";
sopsFile = ../secrets/michael-gitea-restic-env;
};
};
networking.firewall.allowedTCPPorts = [80 443];
services.redis.servers.gitea = {
enable = true;
port = 6380;
bind = "127.0.0.1";
settings = {
maxmemory = "64mb";
maxmemory-policy = "allkeys-lru";
};
};
services.gitea = {
enable = true;
database = {
type = "sqlite3";
path = "/var/lib/gitea/data/gitea.db";
};
settings = {
server = {
ROOT_URL = "https://git.schmatzler.com/";
DOMAIN = "git.schmatzler.com";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3000;
LANDING_PAGE = "explore";
};
service.DISABLE_REGISTRATION = true;
security.INSTALL_LOCK = true;
cache = {
ADAPTER = "redis";
HOST = "redis://127.0.0.1:6380/0?pool_size=100&idle_timeout=180s";
ITEM_TTL = "16h";
};
"cache.last_commit" = {
ITEM_TTL = "8760h";
COMMITS_COUNT = 100;
};
session = {
PROVIDER = "redis";
PROVIDER_CONFIG = "redis://127.0.0.1:6380/1?pool_size=100&idle_timeout=180s";
COOKIE_SECURE = true;
SAME_SITE = "strict";
};
api.ENABLE_SWAGGER = false;
};
};
services.litestream = {
enable = true;
environmentFile = config.sops.secrets.michael-gitea-litestream.path;
settings.dbs = [
{
path = "/var/lib/gitea/data/gitea.db";
replicas = [
{
type = "s3";
bucket = "michael-gitea-litestream";
path = "gitea";
endpoint = "s3.eu-central-003.backblazeb2.com";
}
];
}
];
};
systemd.services.litestream.serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
services.caddy = {
enable = true;
virtualHosts."git.schmatzler.com".extraConfig = ''
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy localhost:3000
'';
};
services.restic.backups.gitea = {
repository = "s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories";
paths = ["/var/lib/gitea"];
exclude = [
"/var/lib/gitea/log"
"/var/lib/gitea/data/gitea.db"
"/var/lib/gitea/data/gitea.db-shm"
"/var/lib/gitea/data/gitea.db-wal"
];
passwordFile = config.sops.secrets.michael-gitea-restic-password.path;
environmentFile = config.sops.secrets.michael-gitea-restic-env.path;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 6"
];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
systemd.services.restic-backups-gitea = {
wants = ["restic-init-gitea.service"];
after = ["restic-init-gitea.service"];
serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
};
systemd.services.restic-init-gitea = {
description = "Initialize Restic repository for Gitea backups";
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
wants = ["network-online.target"];
path = [pkgs.restic];
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "gitea";
RemainAfterExit = true;
EnvironmentFile = config.sops.secrets.michael-gitea-restic-env.path;
};
script = ''
export RESTIC_PASSWORD=$(cat ${config.sops.secrets.michael-gitea-restic-password.path})
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories snapshots &>/dev/null || \
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories init
'';
};
};
}

View File

@@ -1,58 +0,0 @@
{
config,
lib,
pkgs,
...
}: {
services.restic.backups.gitea = {
repository = "s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories";
paths = ["/var/lib/gitea"];
exclude = [
"/var/lib/gitea/log"
"/var/lib/gitea/data/gitea.db"
"/var/lib/gitea/data/gitea.db-shm"
"/var/lib/gitea/data/gitea.db-wal"
];
passwordFile = config.sops.secrets.michael-gitea-restic-password.path;
environmentFile = config.sops.secrets.michael-gitea-restic-env.path;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 6"
];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
systemd.services.restic-backups-gitea = {
wants = ["restic-init-gitea.service"];
after = ["restic-init-gitea.service"];
serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
};
systemd.services.restic-init-gitea = {
description = "Initialize Restic repository for Gitea backups";
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
wants = ["network-online.target"];
path = [pkgs.restic];
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "gitea";
RemainAfterExit = true;
EnvironmentFile = config.sops.secrets.michael-gitea-restic-env.path;
};
script = ''
export RESTIC_PASSWORD=$(cat ${config.sops.secrets.michael-gitea-restic-password.path})
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories snapshots &>/dev/null || \
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories init
'';
};
}

View File

@@ -1,114 +0,0 @@
{
config,
lib,
...
}: {
sops.secrets = {
michael-gitea-litestream = {
sopsFile = ../../../../secrets/michael-gitea-litestream;
format = "binary";
owner = "gitea";
group = "gitea";
path = "/run/secrets/michael-gitea-litestream";
};
michael-gitea-restic-password = {
sopsFile = ../../../../secrets/michael-gitea-restic-password;
format = "binary";
owner = "gitea";
group = "gitea";
path = "/run/secrets/michael-gitea-restic-password";
};
michael-gitea-restic-env = {
sopsFile = ../../../../secrets/michael-gitea-restic-env;
format = "binary";
owner = "gitea";
group = "gitea";
path = "/run/secrets/michael-gitea-restic-env";
};
};
networking.firewall.allowedTCPPorts = [80 443];
services.redis.servers.gitea = {
enable = true;
port = 6380;
bind = "127.0.0.1";
settings = {
maxmemory = "64mb";
maxmemory-policy = "allkeys-lru";
};
};
services.gitea = {
enable = true;
database = {
type = "sqlite3";
path = "/var/lib/gitea/data/gitea.db";
};
settings = {
server = {
ROOT_URL = "https://git.schmatzler.com/";
DOMAIN = "git.schmatzler.com";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3000;
LANDING_PAGE = "explore";
};
service.DISABLE_REGISTRATION = true;
security.INSTALL_LOCK = true;
cache = {
ADAPTER = "redis";
HOST = "redis://127.0.0.1:6380/0?pool_size=100&idle_timeout=180s";
ITEM_TTL = "16h";
};
"cache.last_commit" = {
ITEM_TTL = "8760h";
COMMITS_COUNT = 100;
};
session = {
PROVIDER = "redis";
PROVIDER_CONFIG = "redis://127.0.0.1:6380/1?pool_size=100&idle_timeout=180s";
COOKIE_SECURE = true;
SAME_SITE = "strict";
};
api.ENABLE_SWAGGER = false;
};
};
services.litestream = {
enable = true;
environmentFile = config.sops.secrets.michael-gitea-litestream.path;
settings = {
dbs = [
{
path = "/var/lib/gitea/data/gitea.db";
replicas = [
{
type = "s3";
bucket = "michael-gitea-litestream";
path = "gitea";
endpoint = "s3.eu-central-003.backblazeb2.com";
}
];
}
];
};
};
systemd.services.litestream.serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
services.caddy = {
enable = true;
virtualHosts."git.schmatzler.com".extraConfig = ''
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy localhost:3000
'';
};
}

View File

@@ -1,69 +0,0 @@
{config, ...}: {
services.adguardhome = {
enable = true;
host = "127.0.0.1";
port = 10000;
settings = {
dhcp = {
enabled = false;
};
dns = {
upstream_dns = [
"1.1.1.1"
"1.0.0.1"
];
};
filtering = {
protection_enabled = true;
filtering_enabled = true;
safe_search = {
enabled = false;
};
safebrowsing_enabled = true;
blocked_response_ttl = 10;
filters_update_interval = 24;
blocked_services = {
ids = [
"reddit"
"twitter"
];
};
};
filters = [
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/pro.txt";
name = "HaGeZi Multi PRO";
id = 1;
}
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/tif.txt";
name = "HaGeZi Threat Intelligence Feeds";
id = 2;
}
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/gambling.txt";
name = "HaGeZi Gambling";
id = 3;
}
{
enabled = true;
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/nsfw.txt";
name = "HaGeZi NSFW";
id = 4;
}
];
};
};
services.caddy.virtualHosts."adguard.manticore-hippocampus.ts.net" = {
extraConfig = ''
tls {
get_certificate tailscale
}
reverse_proxy localhost:${toString config.services.adguardhome.port}
'';
};
}

View File

@@ -1,10 +0,0 @@
{...}: {
services.caddy.virtualHosts."cache.manticore-hippocampus.ts.net" = {
extraConfig = ''
tls {
get_certificate tailscale
}
reverse_proxy localhost:32843
'';
};
}

View File

@@ -1,87 +0,0 @@
{config, ...}: {
services.caddy = {
enable = true;
enableReload = false;
globalConfig = ''
admin off
'';
virtualHosts."docs.manticore-hippocampus.ts.net" = {
extraConfig = ''
tls {
get_certificate tailscale
}
reverse_proxy localhost:${toString config.services.paperless.port}
'';
};
virtualHosts."docs-ai.manticore-hippocampus.ts.net" = {
extraConfig = ''
tls {
get_certificate tailscale
}
reverse_proxy localhost:8081
'';
};
};
virtualisation.oci-containers = {
backend = "docker";
containers.paperless-gpt = {
image = "icereed/paperless-gpt:latest";
autoStart = true;
ports = [
"127.0.0.1:8081:8080"
];
volumes = [
"paperless-gpt-data:/app/data"
"paperless-gpt-prompts:/app/prompts"
"${./paperless-gpt-prompts/tag_prompt.tmpl}:/app/prompts/tag_prompt.tmpl:ro"
"${./paperless-gpt-prompts/title_prompt.tmpl}:/app/prompts/title_prompt.tmpl:ro"
];
environment = {
PAPERLESS_BASE_URL = "http://host.docker.internal:${toString config.services.paperless.port}";
LLM_PROVIDER = "openai";
LLM_MODEL = "gpt-5.4";
LLM_LANGUAGE = "German";
VISION_LLM_PROVIDER = "openai";
VISION_LLM_MODEL = "gpt-5.4";
LOG_LEVEL = "info";
};
environmentFiles = [
config.sops.secrets.tahani-paperless-gpt-env.path
];
extraOptions = [
"--add-host=host.docker.internal:host-gateway"
];
};
};
services.redis.servers.paperless = {
enable = true;
port = 6379;
bind = "127.0.0.1";
settings = {
maxmemory = "256mb";
maxmemory-policy = "allkeys-lru";
};
};
services.paperless = {
enable = true;
address = "0.0.0.0";
consumptionDir = "/var/lib/paperless/consume";
passwordFile = config.sops.secrets.tahani-paperless-password.path;
settings = {
PAPERLESS_DBENGINE = "sqlite";
PAPERLESS_REDIS = "redis://127.0.0.1:6379";
PAPERLESS_CONSUMER_IGNORE_PATTERN = [
".DS_STORE/*"
"desktop.ini"
];
PAPERLESS_CONSUMER_POLLING = 30;
PAPERLESS_CONSUMER_RECURSIVE = true;
PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS = true;
PAPERLESS_OCR_LANGUAGE = "deu+eng";
PAPERLESS_CSRF_TRUSTED_ORIGINS = "https://docs.manticore-hippocampus.ts.net";
};
};
}

View File

@@ -1,33 +1,35 @@
{den, ...}: {
den.hosts.aarch64-darwin.chidi.users.cschmatzler.aspect = "chidi-cschmatzler";
den.aspects.chidi-cschmatzler = {
{
den,
lib,
...
}: let
hostLib = import ../_lib/hosts.nix {inherit den lib;};
local = import ../_lib/local.nix;
host = "chidi";
hostMeta = local.hosts.chidi;
in
lib.recursiveUpdate
(hostLib.mkUserHost {
system = hostMeta.system;
inherit host;
user = local.user.name;
includes = [den.aspects.user-darwin-laptop];
homeManager = {...}: {
programs.git.settings.user.email = "christoph@tuist.dev";
programs.git.settings.user.email = local.user.emails.work;
};
};
den.aspects.chidi.includes = [
(den.lib.perHost {
includes = [den.aspects.host-darwin-base];
})
(hostLib.mkPerHostAspect {
inherit host;
includes = [
den.aspects.host-darwin-base
den.aspects.opencode-api-key
];
darwin = {...}: {
networking.hostName = "chidi";
networking.computerName = "chidi";
sops.secrets.opencode-api-key = {
sopsFile = ../../secrets/opencode-api-key;
format = "binary";
owner = "cschmatzler";
path = "/run/secrets/opencode-api-key";
};
networking.hostName = host;
networking.computerName = host;
homebrew.casks = [
"slack"
];
};
})
];
}

31
modules/hosts/janet.nix Normal file
View File

@@ -0,0 +1,31 @@
{
den,
lib,
...
}: let
hostLib = import ../_lib/hosts.nix {inherit den lib;};
local = import ../_lib/local.nix;
host = "janet";
hostMeta = local.hosts.janet;
in
lib.recursiveUpdate
(hostLib.mkUserHost {
system = hostMeta.system;
inherit host;
user = local.user.name;
includes = [
den.aspects.user-darwin-laptop
den.aspects.user-personal
];
})
(hostLib.mkPerHostAspect {
inherit host;
includes = [
den.aspects.host-darwin-base
den.aspects.opencode-api-key
];
darwin = {...}: {
networking.hostName = host;
networking.computerName = host;
};
})

View File

@@ -1,28 +0,0 @@
{den, ...}: {
den.hosts.aarch64-darwin.jason.users.cschmatzler.aspect = "jason-cschmatzler";
den.aspects.jason-cschmatzler = {
includes = [
den.aspects.user-darwin-laptop
den.aspects.user-personal
];
};
den.aspects.jason.includes = [
(den.lib.perHost {
includes = [den.aspects.host-darwin-base];
darwin = {...}: {
networking.hostName = "jason";
networking.computerName = "jason";
sops.secrets.opencode-api-key = {
sopsFile = ../../secrets/opencode-api-key;
format = "binary";
owner = "cschmatzler";
path = "/run/secrets/opencode-api-key";
};
};
})
];
}

View File

@@ -1,30 +1,35 @@
{
den,
inputs,
lib,
...
}: {
den.hosts.x86_64-linux.michael.users.cschmatzler.aspect = "michael-cschmatzler";
den.aspects.michael-cschmatzler = {
}: let
hostLib = import ../_lib/hosts.nix {inherit den lib;};
local = import ../_lib/local.nix;
host = "michael";
hostMeta = local.hosts.michael;
in
lib.recursiveUpdate
(hostLib.mkUserHost {
system = hostMeta.system;
inherit host;
user = local.user.name;
includes = [den.aspects.user-minimal];
};
den.aspects.michael.includes = [
(den.lib.perHost {
includes = [den.aspects.host-public-server];
})
(hostLib.mkPerHostAspect {
inherit host;
includes = [
den.aspects.host-public-server
den.aspects.gitea
];
nixos = {modulesPath, ...}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
./_parts/michael/backups.nix
./_parts/michael/disk-config.nix
./_parts/michael/gitea.nix
./_parts/michael/hardware-configuration.nix
inputs.disko.nixosModules.default
];
networking.hostName = "michael";
networking.hostName = host;
};
})
];
}

View File

@@ -1,14 +1,36 @@
{den, ...}: {
den.hosts.x86_64-linux.tahani.users.cschmatzler.aspect = "tahani-cschmatzler";
den.aspects.tahani-cschmatzler = {
{
den,
lib,
...
}: let
hostLib = import ../_lib/hosts.nix {inherit den lib;};
local = import ../_lib/local.nix;
secretLib = import ../_lib/secrets.nix {inherit lib;};
host = "tahani";
hostMeta = local.hosts.tahani;
in
lib.recursiveUpdate
(hostLib.mkUserHost {
system = hostMeta.system;
inherit host;
user = local.user.name;
includes = [
den.aspects.user-workstation
den.aspects.user-personal
den.aspects.email
];
homeManager = {
config,
inputs',
...
}: let
opencode = inputs'.llm-agents.packages.opencode;
in {
programs.opencode.settings.permission.external_directory = {
"/tmp/himalaya-triage/*" = "allow";
"/var/lib/paperless/consume/inbox-triage/*" = "allow";
};
programs.nushell.extraConfig = ''
if $nu.is-interactive and ('SSH_CONNECTION' in ($env | columns)) and ('ZELLIJ' not-in ($env | columns)) {
try {
@@ -19,49 +41,59 @@
}
}
'';
systemd.user.services.opencode-inbox-triage = {
Unit = {
Description = "OpenCode inbox triage";
};
Service = {
Type = "oneshot";
ExecStart = "${opencode}/bin/opencode run --command inbox-triage --model opencode-go/glm-5";
Environment = "PATH=${config.home.profileDirectory}/bin:/run/current-system/sw/bin";
};
};
den.aspects.tahani.includes = [
(den.lib.perHost {
includes = [den.aspects.host-nixos-base];
systemd.user.timers.opencode-inbox-triage = {
Unit = {
Description = "Run OpenCode inbox triage every 12 hours";
};
Timer = {
OnCalendar = "*-*-* 0/12:00:00";
Persistent = true;
};
Install = {
WantedBy = ["timers.target"];
};
};
};
})
(hostLib.mkPerHostAspect {
inherit host;
includes = [
den.aspects.host-nixos-base
den.aspects.opencode-api-key
den.aspects.adguardhome
den.aspects.cache
den.aspects.paperless
];
nixos = {...}: {
imports = [
./_parts/tahani/adguardhome.nix
./_parts/tahani/cache.nix
./_parts/tahani/networking.nix
./_parts/tahani/paperless.nix
];
networking.hostName = "tahani";
networking.hostName = host;
sops.secrets = {
opencode-api-key = {
sopsFile = ../../secrets/opencode-api-key;
format = "binary";
owner = "cschmatzler";
path = "/run/secrets/opencode-api-key";
};
tahani-paperless-password = {
sopsFile = ../../secrets/tahani-paperless-password;
format = "binary";
path = "/run/secrets/tahani-paperless-password";
};
tahani-paperless-gpt-env = {
sopsFile = ../../secrets/tahani-paperless-gpt-env;
format = "binary";
path = "/run/secrets/tahani-paperless-gpt-env";
};
tahani-email-password = {
sops.secrets.tahani-email-password =
secretLib.mkUserBinarySecret {
name = "tahani-email-password";
sopsFile = ../../secrets/tahani-email-password;
format = "binary";
owner = "cschmatzler";
path = "/run/secrets/tahani-email-password";
};
};
virtualisation.docker.enable = true;
users.users.cschmatzler.extraGroups = ["docker" "paperless"];
users.users.${local.user.name}.extraGroups = [
"docker"
"paperless"
];
systemd.tmpfiles.rules = [
"d /var/lib/paperless/consume 2775 paperless paperless -"
@@ -75,5 +107,3 @@
];
};
})
];
}

View File

@@ -1,6 +1,10 @@
{...}: {
den.hosts.aarch64-darwin.chidi.users.cschmatzler = {};
den.hosts.aarch64-darwin.jason.users.cschmatzler = {};
den.hosts.x86_64-linux.michael.users.cschmatzler = {};
den.hosts.x86_64-linux.tahani.users.cschmatzler = {};
}
{lib, ...}: let
local = import ./_lib/local.nix;
in
lib.foldl' lib.recursiveUpdate {} (
lib.mapAttrsToList (
host: hostMeta:
lib.setAttrByPath ["den" "hosts" hostMeta.system host "users" local.user.name] {}
)
local.hosts
)

View File

@@ -21,16 +21,13 @@
overalljails = true;
};
jails = {
sshd = {
settings = {
sshd.settings = {
enabled = true;
port = "ssh";
filter = "sshd";
maxretry = 3;
};
};
gitea = {
settings = {
gitea.settings = {
enabled = true;
filter = "gitea";
logpath = "/var/lib/gitea/log/gitea.log";
@@ -41,7 +38,6 @@
};
};
};
};
environment.etc."fail2ban/filter.d/gitea.local".text = ''
[Definition]
@@ -53,30 +49,28 @@
den.aspects.tailscale.nixos = {
services.tailscale = {
enable = true;
extraSetFlags = ["--ssh"];
openFirewall = true;
permitCertUid = "caddy";
useRoutingFeatures = "server";
};
};
den.aspects.tailscale.darwin = {
services.tailscale = {
den.aspects.mosh.nixos = {
programs.mosh = {
enable = true;
};
openFirewall = false;
};
# Network tools
den.aspects.network.homeManager = {
pkgs,
lib,
...
}: {
home.packages = with pkgs;
[
dig
]
++ lib.optionals stdenv.isDarwin [
tailscale
networking.firewall.interfaces.tailscale0.allowedUDPPortRanges = [
{
from = 60000;
to = 61000;
}
];
};
den.aspects.tailscale.darwin = {
services.tailscale.enable = true;
};
}

View File

@@ -1,11 +1,14 @@
{inputs, ...}: {
{inputs, ...}: let
local = import ./_lib/local.nix;
userHome = "/home/${local.user.name}";
in {
den.aspects.nixos-system.nixos = {pkgs, ...}: {
imports = [inputs.home-manager.nixosModules.home-manager];
security.sudo.enable = true;
security.sudo.extraRules = [
{
users = ["cschmatzler"];
users = [local.user.name];
commands = [
{
command = "/run/current-system/sw/bin/nix-env";
@@ -46,9 +49,9 @@
time.timeZone = "UTC";
nix = {
settings.trusted-users = ["cschmatzler"];
settings.trusted-users = [local.user.name];
gc.dates = "weekly";
nixPath = ["nixos-config=/home/cschmatzler/.local/share/src/nixos-config:/etc/nixos"];
nixPath = ["nixos-config=${userHome}/.local/share/src/nixos-config:/etc/nixos"];
};
boot = {
@@ -71,9 +74,9 @@
};
users.users = {
cschmatzler = {
${local.user.name} = {
isNormalUser = true;
home = "/home/cschmatzler";
home = userHome;
extraGroups = [
"wheel"
"sudo"
@@ -93,7 +96,5 @@
];
};
};
home-manager.useGlobalPkgs = true;
};
}

11
modules/opencode.nix Normal file
View File

@@ -0,0 +1,11 @@
{lib, ...}: let
secretLib = import ./_lib/secrets.nix {inherit lib;};
in {
den.aspects.opencode-api-key.os = {
sops.secrets.opencode-api-key =
secretLib.mkUserBinarySecret {
name = "opencode-api-key";
sopsFile = ../secrets/opencode-api-key;
};
};
}

View File

@@ -2,13 +2,14 @@
overlays = [
# himalaya
(import ./_overlays/himalaya.nix {inherit inputs;})
# direnv (darwin upstream makefile forces external linking)
(final: prev: {
# direnv (Go 1.26 on darwin disables cgo, but direnv forces external linking)
(final: prev:
prev.lib.optionalAttrs prev.stdenv.hostPlatform.isDarwin {
direnv =
prev.direnv.overrideAttrs (old: {
env =
(old.env or {})
// final.lib.optionalAttrs final.stdenv.isDarwin {
// {
CGO_ENABLED = 1;
};
});
@@ -19,12 +20,6 @@
(import ./_overlays/jj-ryu.nix {inherit inputs;})
# cog-cli
(import ./_overlays/cog-cli.nix {inherit inputs;})
# pi-agent-stuff (mitsuhiko)
(import ./_overlays/pi-agent-stuff.nix {inherit inputs;})
# pi-harness (aliou)
(import ./_overlays/pi-harness.nix {inherit inputs;})
# pi-mcp-adapter
(import ./_overlays/pi-mcp-adapter.nix {inherit inputs;})
# jj-starship (passes through upstream overlay)
(import ./_overlays/jj-starship.nix {inherit inputs;})
# zjstatus

100
modules/paperless.nix Normal file
View File

@@ -0,0 +1,100 @@
{lib, ...}: let
caddyLib = import ./_lib/caddy.nix;
local = import ./_lib/local.nix;
secretLib = import ./_lib/secrets.nix {inherit lib;};
paperlessPrompts = ./_paperless;
in {
den.aspects.paperless.nixos = {config, ...}: {
sops.secrets = {
tahani-paperless-password =
secretLib.mkBinarySecret {
name = "tahani-paperless-password";
sopsFile = ../secrets/tahani-paperless-password;
};
tahani-paperless-gpt-env =
secretLib.mkBinarySecret {
name = "tahani-paperless-gpt-env";
sopsFile = ../secrets/tahani-paperless-gpt-env;
};
};
services.caddy = {
enable = true;
enableReload = false;
globalConfig = ''
admin off
'';
virtualHosts =
caddyLib.mkTailscaleVHost {
name = "docs";
configText = "reverse_proxy localhost:${toString config.services.paperless.port}";
}
// caddyLib.mkTailscaleVHost {
name = "docs-ai";
configText = "reverse_proxy localhost:8081";
};
};
virtualisation.oci-containers = {
backend = "docker";
containers.paperless-gpt = {
image = "icereed/paperless-gpt:latest";
autoStart = true;
ports = [
"127.0.0.1:8081:8080"
];
volumes = [
"paperless-gpt-data:/app/data"
"paperless-gpt-prompts:/app/prompts"
"${paperlessPrompts}/tag_prompt.tmpl:/app/prompts/tag_prompt.tmpl:ro"
"${paperlessPrompts}/title_prompt.tmpl:/app/prompts/title_prompt.tmpl:ro"
];
environment = {
PAPERLESS_BASE_URL = "http://host.docker.internal:${toString config.services.paperless.port}";
LLM_PROVIDER = "openai";
LLM_MODEL = "gpt-5.4";
LLM_LANGUAGE = "German";
VISION_LLM_PROVIDER = "openai";
VISION_LLM_MODEL = "gpt-5.4";
LOG_LEVEL = "info";
};
environmentFiles = [
config.sops.secrets.tahani-paperless-gpt-env.path
];
extraOptions = [
"--add-host=host.docker.internal:host-gateway"
];
};
};
services.redis.servers.paperless = {
enable = true;
port = 6379;
bind = "127.0.0.1";
settings = {
maxmemory = "256mb";
maxmemory-policy = "allkeys-lru";
};
};
services.paperless = {
enable = true;
address = "0.0.0.0";
consumptionDir = "/var/lib/paperless/consume";
passwordFile = config.sops.secrets.tahani-paperless-password.path;
settings = {
PAPERLESS_DBENGINE = "sqlite";
PAPERLESS_REDIS = "redis://127.0.0.1:6379";
PAPERLESS_CONSUMER_IGNORE_PATTERN = [
".DS_STORE/*"
"desktop.ini"
];
PAPERLESS_CONSUMER_POLLING = 30;
PAPERLESS_CONSUMER_RECURSIVE = true;
PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS = true;
PAPERLESS_OCR_LANGUAGE = "deu+eng";
PAPERLESS_CSRF_TRUSTED_ORIGINS = "https://${local.tailscaleHost "docs"}";
};
};
};
}

View File

@@ -2,6 +2,7 @@
den.aspects.host-nixos-base.includes = [
den.aspects.nixos-system
den.aspects.core
den.aspects.mosh
den.aspects.openssh
den.aspects.tailscale
];

View File

@@ -1,6 +1,5 @@
{den, ...}: {
den.aspects.user-base = {
includes = [
den.aspects.user-base.includes = [
den.aspects.shell
den.aspects.ssh-client
den.aspects.terminal
@@ -9,9 +8,4 @@
den.aspects.zellij
den.aspects.zk
];
homeManager = {
programs.home-manager.enable = true;
};
};
}

View File

@@ -1,11 +1,5 @@
{den, ...}: {
den.aspects.user-minimal = {
includes = [
den.aspects.user-minimal.includes = [
den.aspects.shell
];
homeManager = {
programs.home-manager.enable = true;
};
};
}

View File

@@ -1,5 +1,7 @@
{...}: {
{...}: let
local = import ../../_lib/local.nix;
in {
den.aspects.user-personal.homeManager = {
programs.git.settings.user.email = "christoph@schmatzler.com";
programs.git.settings.user.email = local.user.emails.personal;
};
}

View File

@@ -1,4 +1,6 @@
{inputs, ...}: {
{inputs, ...}: let
local = import ./_lib/local.nix;
in {
# Import sops-nix modules into den.default per-class
den.default.nixos.imports = [inputs.sops-nix.nixosModules.sops];
den.default.darwin.imports = [inputs.sops-nix.darwinModules.sops];
@@ -8,7 +10,7 @@
# Configure Darwin SOPS defaults
den.default.darwin = {
sops.age.keyFile = "/Users/cschmatzler/.config/sops/age/keys.txt";
sops.age.keyFile = "/Users/${local.user.name}/.config/sops/age/keys.txt";
sops.age.sshKeyPaths = [];
sops.gnupg.sshKeyPaths = [];
};

View File

@@ -1,4 +1,9 @@
{...}: {
{...}: let
local = import ./_lib/local.nix;
theme = (import ./_lib/theme.nix).rosePineDawn;
palette = theme.hex;
pineAnsi = builtins.replaceStrings [" "] [";"] theme.rgb.pine;
in {
den.aspects.shell.homeManager = {
lib,
pkgs,
@@ -32,7 +37,7 @@
extraEnv =
''
$env.LS_COLORS = (${pkgs.vivid}/bin/vivid generate rose-pine-dawn)
$env.LS_COLORS = (${pkgs.vivid}/bin/vivid generate ${theme.slug})
''
+ lib.optionalString pkgs.stdenv.isDarwin ''
# Nushell on Darwin doesn't source /etc/zprofile or path_helper,
@@ -43,22 +48,22 @@
extraConfig = ''
# --- Rosé Pine Dawn Theme ---
let theme = {
love: "#b4637a"
gold: "#ea9d34"
rose: "#d7827e"
pine: "#286983"
foam: "#56949f"
iris: "#907aa9"
leaf: "#6d8f89"
text: "#575279"
subtle: "#797593"
muted: "#9893a5"
highlight_high: "#cecacd"
highlight_med: "#dfdad9"
highlight_low: "#f4ede8"
overlay: "#f2e9e1"
surface: "#fffaf3"
base: "#faf4ed"
love: "${palette.love}"
gold: "${palette.gold}"
rose: "${palette.rose}"
pine: "${palette.pine}"
foam: "${palette.foam}"
iris: "${palette.iris}"
leaf: "${palette.leaf}"
text: "${palette.text}"
subtle: "${palette.subtle}"
muted: "${palette.muted}"
highlight_high: "${palette.highlightHigh}"
highlight_med: "${palette.highlightMed}"
highlight_low: "${palette.highlightLow}"
overlay: "${palette.overlay}"
surface: "${palette.surface}"
base: "${palette.base}"
}
let scheme = {
@@ -231,7 +236,7 @@
# Vi mode indicators Starship handles the character (green/red for
# success/error), nushell adds a dot for normal mode.
$env.PROMPT_INDICATOR_VI_INSERT = "· "
$env.PROMPT_INDICATOR_VI_NORMAL = "\e[1;38;2;40;105;131m·\e[0m "
$env.PROMPT_INDICATOR_VI_NORMAL = "\e[1;38;2;${pineAnsi}m·\e[0m "
'';
};
@@ -270,7 +275,7 @@
};
custom.scm = {
when = "jj-starship detect";
shell = ["jj-starship" "--strip-bookmark-prefix" "cschmatzler/" "--truncate-name" "20" "--bookmarks-display-limit" "1"];
shell = ["jj-starship" "--strip-bookmark-prefix" "${local.user.name}/" "--truncate-name" "20" "--bookmarks-display-limit" "1"];
format = "$output ";
};
lua = {

View File

@@ -1,27 +1,17 @@
{...}: {
den.aspects.ssh-client.homeManager = {
config,
pkgs,
...
}: let
homeDir = "${
if pkgs.stdenv.hostPlatform.isDarwin
then "/Users"
else "/home"
}/${config.home.username}";
in {
den.aspects.ssh-client.homeManager = {config, ...}: {
programs.ssh = {
enable = true;
enableDefaultConfig = false;
includes = [
"${homeDir}/.ssh/config_external"
"${config.home.homeDirectory}/.ssh/config_external"
];
matchBlocks = {
"*" = {};
"github.com" = {
identitiesOnly = true;
identityFile = [
"${homeDir}/.ssh/id_ed25519"
"${config.home.homeDirectory}/.ssh/id_ed25519"
];
};
};

View File

@@ -1,4 +1,7 @@
{...}: {
{...}: let
theme = (import ./_lib/theme.nix).rosePineDawn;
palette = theme.hex;
in {
den.aspects.terminal.darwin = {pkgs, ...}: {
fonts.packages = [
pkgs.nerd-fonts.iosevka
@@ -6,6 +9,7 @@
};
den.aspects.terminal.homeManager = {
config,
pkgs,
lib,
...
@@ -20,6 +24,7 @@
jq
killall
lsof
mosh
ouch
ov
sd
@@ -39,17 +44,17 @@
--preview-window='border-rounded' --prompt=' ' --marker=' ' --pointer=' '
--separator='' --scrollbar='' --layout='reverse'
--color=bg+:#f2e9e1,bg:#faf4ed,spinner:#ea9d34,hl:#d7827e
--color=fg:#797593,header:#286983,info:#56949f,pointer:#907aa9
--color=marker:#b4637a,fg+:#575279,prompt:#797593,hl+:#d7827e
--color=selected-bg:#f2e9e1
--color=border:#dfdad9,label:#575279
--color=bg+:${palette.overlay},bg:${palette.base},spinner:${palette.gold},hl:${palette.rose}
--color=fg:${palette.subtle},header:${palette.pine},info:${palette.foam},pointer:${palette.iris}
--color=marker:${palette.love},fg+:${palette.text},prompt:${palette.subtle},hl+:${palette.rose}
--color=selected-bg:${palette.overlay}
--color=border:${palette.highlightMed},label:${palette.text}
'';
};
xdg.configFile."ghostty/config".text = ''
command = ${pkgs.nushell}/bin/nu
theme = Rose Pine Dawn
theme = ${theme.ghosttyName}
window-padding-x = 12
window-padding-y = 3
window-padding-balance = true
@@ -64,14 +69,32 @@
clipboard-write = allow
'';
xdg.configFile = {
"glow/glow.yml".text =
lib.concatStringsSep "\n" [
"# style name or JSON path (default \"auto\")"
"style: \"${config.xdg.configHome}/glow/${theme.slug}.json\""
"# mouse support (TUI-mode only)"
"mouse: false"
"# use pager to display markdown"
"pager: false"
"# word-wrap at width"
"width: 80"
"# show all files, including hidden and ignored."
"all: false"
""
];
"glow/${theme.slug}.json".source = ./_terminal/rose-pine-dawn-glow.json;
};
programs.bat = {
enable = true;
config = {
theme = "Rosé Pine Dawn";
theme = theme.displayName;
pager = "ov";
};
themes = {
"Rosé Pine Dawn" = {
"${theme.displayName}" = {
src =
pkgs.fetchFromGitHub {
owner = "rose-pine";
@@ -79,7 +102,7 @@
rev = "23bb25b9c421cdc9ea89ff3ad3825840cd19d65d";
hash = "sha256-GUFdv5V5OZ2PG+gfsbiohMT23LWsrZda34ReHBr2Xy0=";
};
file = "dist/rose-pine-dawn.tmTheme";
file = "dist/${theme.slug}.tmTheme";
};
};
};

View File

@@ -1,31 +1,137 @@
{...}: {
{...}: let
theme = (import ./_lib/theme.nix).rosePineDawn;
palette = theme.hex;
rgb = theme.rgb;
in {
den.aspects.zellij.homeManager = {pkgs, ...}: {
programs.zellij = {
enable = true;
settings = {
theme = "rose-pine-dawn";
default_layout = "default";
default_shell = "${pkgs.nushell}/bin/nu";
pane_frames = false;
show_startup_tips = false;
show_release_notes = false;
};
};
programs.zellij.enable = true;
xdg.configFile."zellij/config.kdl".text = ''
default_layout "default"
default_shell "${pkgs.nushell}/bin/nu"
pane_frames false
show_release_notes false
show_startup_tips false
theme "${theme.slug}"
xdg.configFile."zellij/themes/rose-pine-dawn.kdl".text = ''
themes {
rose-pine-dawn {
fg "#575279"
bg "#f2e9e1"
black "#faf4ed"
red "#b4637a"
green "#6d8f89"
yellow "#ea9d34"
blue "#286983"
magenta "#907aa9"
cyan "#56949f"
white "#575279"
orange "#d7827e"
${theme.slug} {
text_unselected {
base ${rgb.text}
background ${rgb.highlightLow}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.pine}
emphasis_3 ${rgb.iris}
}
text_selected {
base ${rgb.text}
background ${rgb.highlightMed}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.pine}
emphasis_3 ${rgb.iris}
}
ribbon_selected {
base ${rgb.highlightLow}
background ${rgb.pine}
emphasis_0 ${rgb.gold}
emphasis_1 ${rgb.rose}
emphasis_2 ${rgb.iris}
emphasis_3 ${rgb.foam}
}
ribbon_unselected {
base ${rgb.base}
background ${rgb.text}
emphasis_0 ${rgb.gold}
emphasis_1 ${rgb.rose}
emphasis_2 ${rgb.iris}
emphasis_3 ${rgb.foam}
}
table_title {
base ${rgb.pine}
background ${rgb.black}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.pine}
emphasis_3 ${rgb.iris}
}
table_cell_selected {
base ${rgb.text}
background ${rgb.highlightMed}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.pine}
emphasis_3 ${rgb.iris}
}
table_cell_unselected {
base ${rgb.text}
background ${rgb.highlightLow}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.pine}
emphasis_3 ${rgb.iris}
}
list_selected {
base ${rgb.text}
background ${rgb.highlightMed}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.pine}
emphasis_3 ${rgb.iris}
}
list_unselected {
base ${rgb.text}
background ${rgb.highlightLow}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.pine}
emphasis_3 ${rgb.iris}
}
frame_selected {
base ${rgb.pine}
background ${rgb.black}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.foam}
emphasis_2 ${rgb.iris}
emphasis_3 ${rgb.black}
}
frame_highlight {
base ${rgb.rose}
background ${rgb.black}
emphasis_0 ${rgb.rose}
emphasis_1 ${rgb.rose}
emphasis_2 ${rgb.rose}
emphasis_3 ${rgb.rose}
}
exit_code_success {
base ${rgb.pine}
background ${rgb.black}
emphasis_0 ${rgb.foam}
emphasis_1 ${rgb.highlightLow}
emphasis_2 ${rgb.iris}
emphasis_3 ${rgb.pine}
}
exit_code_error {
base ${rgb.love}
background ${rgb.black}
emphasis_0 ${rgb.gold}
emphasis_1 ${rgb.black}
emphasis_2 ${rgb.black}
emphasis_3 ${rgb.black}
}
multiplayer_user_colors {
player_1 ${rgb.iris}
player_2 ${rgb.pine}
player_3 ${rgb.rose}
player_4 ${rgb.gold}
player_5 ${rgb.foam}
player_6 ${rgb.love}
player_7 ${rgb.black}
player_8 ${rgb.black}
player_9 ${rgb.black}
player_10 ${rgb.black}
}
}
}
'';
@@ -41,26 +147,26 @@
plugin location="file:${pkgs.zjstatus}/bin/zjstatus.wasm" {
hide_frame_for_single_pane "true"
format_left "{mode}#[fg=#286983,bg=#faf4ed,bold] {session}#[bg=#faf4ed] {tabs}"
format_left "{mode}#[fg=${palette.pine},bg=${palette.base},bold] {session}#[bg=${palette.base}] {tabs}"
format_right "{datetime}"
format_space "#[bg=#faf4ed]"
format_space "#[bg=${palette.base}]"
mode_normal "#[fg=#faf4ed,bg=#286983] "
mode_locked "#[fg=#faf4ed,bg=#ea9d34] L "
mode_tab "#[fg=#faf4ed,bg=#6d8f89] T "
mode_pane "#[fg=#faf4ed,bg=#907aa9] P "
mode_session "#[fg=#faf4ed,bg=#56949f] S "
mode_resize "#[fg=#faf4ed,bg=#ea9d34] R "
mode_move "#[fg=#faf4ed,bg=#d7827e] M "
mode_search "#[fg=#faf4ed,bg=#b4637a] S "
mode_normal "#[fg=${palette.base},bg=${palette.pine}] "
mode_locked "#[fg=${palette.base},bg=${palette.gold}] L "
mode_tab "#[fg=${palette.base},bg=${palette.leaf}] T "
mode_pane "#[fg=${palette.base},bg=${palette.iris}] P "
mode_session "#[fg=${palette.base},bg=${palette.foam}] S "
mode_resize "#[fg=${palette.base},bg=${palette.gold}] R "
mode_move "#[fg=${palette.base},bg=${palette.rose}] M "
mode_search "#[fg=${palette.base},bg=${palette.love}] S "
tab_normal "#[fg=#9893a5,bg=#faf4ed] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_active "#[fg=#faf4ed,bg=#286983,bold,underline] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_normal "#[fg=${palette.muted},bg=${palette.base}] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_active "#[fg=${palette.base},bg=${palette.pine},bold,underline] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_fullscreen_indicator "󰊓 "
tab_sync_indicator "󰓦 "
tab_floating_indicator "󰉈 "
datetime "#[fg=#575279,bg=#faf4ed] {format} "
datetime "#[fg=${palette.text},bg=${palette.base}] {format} "
datetime_format "%A, %d %b %Y %H:%M"
datetime_timezone "Europe/Berlin"
}

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKWXVhTEw0em1CZVB3d2U3\nSWRjMlZpTlFYSXZJZVE5SWttSFV2K3l3VFJjCjZZbG05a1RsUlRORDNaRXduVHBC\ndHZ2aG1FRnJhbFQxNkNxTEwzV0NRdHMKLS0tIFplbXhERjFhRGh0YUtuVG5IZWVh\ndEV6SkhTWnUrVkFmR0NYRS8xZ05zRTQK8qcapJ2Z2AukAUSFagChNGt7BTnIXchr\nociqhWE+BVPROduihwpsQlAWxEARJP/5sqhOAZpNrnna9Psg4m1MFA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0cmFCRDVFcFFORVpsK2Fo\nYWg5S3VzSGlCYVp1S1Q3NGF4SDZqMVFmTFc0CkZQUDJwQ3AvUDNpMUZNaUlRTkNt\nQVR2UzI0Snp3SVZqN2phRzE1K0ErZ3cKLS0tIDE4dndRWXVycitxSVNEN28vN29s\nWnRIT24yT3Z6R1ZiYURYd1BiSzBIOU0Kn7jrvq06CxRxn7XbOIMoEIjMDIr6A089\ns1ymMJO8DmeKdNaL5s3EwG7BZBAimdmcjANST1kXFy7oc4eSk4ETwg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJTzlPdWcyQlFPSWdhVnVB\nR08yVmdaeFRNYUo2clA4ellicU9rVnlZMVFBCnRjOTZGdHBOMW94OGRyaE9HMktq\nQ1V0WmQycndnSHh4K1JiZllqSG5oRVUKLS0tIGZ6VTNUd2xmR1N1bmtDU1k2Q0ox\nRDAwQytvL3JQUjh0MHBFV09kdVpJdkUKC1CigRs4k/uSXC4zMWL60xaHFoyvzYlu\nP2olBA4CerGEoMc0ZIBEEr78hB0j//06se68BreZcD4FcDC77IWNXQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqZFVRMDVVaEJWUWQ1bkRD\nWmQ1dTRMK2FpMmZLTERuOHc1OHUramFTZkFzCnlSUTUzUktrdFJyVnk0SnpFOURT\nME1wckdyUHFmeE0rN1BneUhIR3JuZHMKLS0tIGJCTHZuWTdGQVhoU3JnSmxIcm83\nc0hwNGUvVEljWWVwOWpCS1BJckhjRUkKDD22XytVN58yR22InpsUEN6y1r2AQqLn\nu1fmWC7R4AJcisHqVSEOBW3qQVQdFWIptBN/5urkb2yAKNtP7WUUbg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRcVJ0WFoxN2dQSy8xN2ox\nU09GbUd0UG03Wlc0RnpTNkpYbUo2ZFg1Q1hRCmZyUTZiYklMbnBFczhkSTNFaTNi\nSy9vb1VXOFBqQVMySHRVUng0c083VFEKLS0tIFplV3JVSmV4TmJHSkRPMFMzZkZl\ncmVZWjRCZWxlamxUdnp6ZnA3bXNaV1EK4ZXEJBXHGTaEbNq81RRm+GfElyJekrX+\nxrWHGTkJ4golaI39p3j/F8sF40Sa036ZJmQU8uX9fq1cnuSRv4lb1g==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlc0NibDBuUVJYLzRQY0dP\nWlRzWVo0Rk5NVXh0SGZjYnNza2g3M3E0U0cwCmc5K2d5UzZvR1c2QmJickg3Uk5X\nQm9GMkZnYUVGS1lPRklrcDJRdmMvM2MKLS0tIHBiclZyTVU5emVka05xNXJTTnhF\nN3plRmN2eEl0Y012eGNBbXBsc054Wm8KIB6pNlSM63UvoaPsHNYhSuhZsfM/VQfw\nTxITGnK0Lkjw91SDVzuCGPTtZxuUTFR2q0SyGbZU9IrLQWmBTaENhw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5a3gxLzQzQXl6OThJekYr\nVE5zQ0tHd2l4K1NnZEJvSkw0WVptdE9VQ3g0CjcrdkgyUGczZTY5SFdQNFdneTVR\naTdjTUhSSzZvQnJmc2g1NGJhN3Y3VjQKLS0tIDk4ckszblk5QUFkNzVVZ3czYnpk\ncVZlTlJxK3ZLRmxZRmtaZzNkME9UZWcK+GVFoiY5qKX8DdKbGxUoLxF3gnnG3Cbl\nk/57ZH1xVJT4jCCqdploZCSwSLdGrUDGs8I4FixKVMKGT6Ce2xaPcg==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEUUhvblozMyt0UDJpZ2dx\nNWJBZG5wQzFEM2RFTTJjb3FtcE1ISkd3cEhFCjJOdDJwOTJrdWc2c0NYWGQwWk9y\nREM0Qm1iK3FHUE54ZVI5L2k0RmtESHMKLS0tIEtRdTQvY1AxZHhSZWlhSThFV2FN\nQ0wyUTRaRnh1cDVlVmNjekNuNUN2bUUKOnTcpcaN7cuQXceJmPtKJj2pyIxC3Lj7\npedLV126Q07i3aRrQgm92O8NW2lMaM2Z3FldooTsQgIieQeMCJ6G9w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxUHJtZ3VuRVZnU0RXeStx\nNUQrakgxWm5oOFlmbXZGL1UxQnhvYU1sUkZNCmlucWQyeFJDSFJZYWlSbkd2eCt6\nS2liQ2g3VXpING81OHRPUGRocnNqa1UKLS0tIG9tdXdna3RhYm8xRHRsS2tQYlUx\najVwbndFMDUyVG0weGtGdWxpQnpsSVkKdTnOnyKDkDfDtDA4jdF1JZ6wXdf/Rucv\nT4YYvjbh7JTnOl6wMg37uy29GVlI0hjOCMyX0j2BmKis/awCJ4bxUA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqUVhZanBIR294ek1keWtZ\nM3puaC8zWGE3UHpmZy9vbzh3YkRyTW5rcG5zCmQ1SnR5NzZLcXNZZVUzTWV1YkFn\ncE5CaHpSeEZnWlNkak1MbWM2UUJJT0kKLS0tIEl1T2lsYndYeHkxalpBYUtwY1Yv\nMGJLWWEwVW5lNnNwNEZhTnRMTVdJdmMK3mg0hzN6KjmR288U09USPmyoQd5ixmCY\nOZb6qjx1O9TUmuho6j+zR4BmMcZ9kIv6uEuURKxGFwzZy+l5a3e3Cg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-01-04T19:45:21Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2Ly9XQlRDZzgrY2tiTmRi\nNmhBNDQ2TDV4eGZsUUFvTmlHR2tCN29IUGljCnBvb2RKNUdHTnEwQ1RzS05uOTVK\neElUUmdRdnRaYUpIWEljdlN6REgwUm8KLS0tIGpJRVNKTkR3bEcrcks3ek5LNVV3\nYzhOUFRvY1RPYkM5V0hXenFHd2NSdWsKokyuyPXjMAJIfyeWsBHGWaLID7Oorc9d\nQE9Veh2aramAH1xb30BVLa40Dpu0jI/Wgxoo0iDf5OypNXBiBH+FMA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4WmNWMnQ2NEg1MnFvOS9n\neHBnMHpvWllvM2taeFM5SXRVT0wvMTRaMUJFCnk5MVpGRjN1cUxiYkdLSlZ1aTB0\nTkduQkFZcjkxQzUyWHhlQ0tFRnZFTWsKLS0tIDVLdXdySWxqb20zZ3RKSE1qc2Nh\nSC82UmxGN2VUY2pZY3pnWDlKTkRKSEUKFSQqZIzGXpCzpWVWko7J3hxglRJmOuF4\nmE/lC55uekxcochjgPT9uDJfG1/kskWyJTBb5Ndx3YFkUcnfVOZc4Q==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMNUVHeWg1N1ZPKzBqMnBn\nY2VaTlp0K3ppbFdmN0JuQmRnVUlzQ0hMZTI4CkxORVJ0NUIxVXZMcHVRcXFSV01l\naFFVOEtJSFY0MUVWOXYxbFlqRmNuVHMKLS0tIEYydDB1ZHUvQS8yTENmZkZHeXFV\nU1N3ZC9zNEtZSnE2Wk9TREN3Y3JwNEkKyFqj+1kGjqKyslllunUl8dVQwFAe/MdL\n5PNKIfVErxbnPtlc+yfRSWsfOQPdNV9GVM9rtOxA+51QStiGlLU12Q==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5U2plZGxEVy90NUMzellJ\nYVVsTDUvL1o5OXZ4YjR3c0tPY2RjQWpVd0M4Ci9kUFA3OEFoRjlUa1g4Nnl3K2Rq\nNHA1NkRtVzdXM21QNTI5ZFJsNFVCMncKLS0tIFRQbDdXeWxIdzJpNng0QVc3bStT\nQ2VPNFA4NDlmU3hscjJ0MjdGRGQxakUKM9H04Ezw/KoAJVjFB56v0vjIHJiO+aW+\nWWdBlsZoeTYChkMGcQ74SaLkPMEkD3GU8Ua9RZBIqFzOZwsOdAkWBA==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTREx3SU02RU1adU5Ielkz\nVUFrdUZkQzJwZG1aQlZEcDdxWXVFM1JtRHk0CmNTamFhcjJEMklYbkpxWHFzekhY\nQ1E1STZ3U0RMNkNnVnVvWXJkZUhxRm8KLS0tIE1ZaFBORVRMZkYwNnBkTjFxSWNG\nVWJnYjgzM3IyMDJ3Z3o1QkV0RGdjL0EKQ1G/uJ8HcVDMUk8NXBC0MRcJOyLzQRlT\nL5Sf6Es6UYrTp5tIWleR6u48aUGi8HvcOOeCrK2S5utj5xPl6E+bqQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSSTNhc0xrOXRXOWZVdWNh\nNGQxNFBXUHNrL2NsMnBkcDdSVHVYc1RPM25zClh0L2FrMEhZcWt3RGtHa0ErNjUz\ndDlZaC9kZVVHSkFOK3dBWGwwZStQeUkKLS0tIE1VRnJCbTFWUkhoQ0c0dkppbVRL\nSDZldkdXV2U5Z3ZycmEySTJ5WDFTbVEKgHq5Xa86l+1e87p2KodJN+8/IOz2y0w7\n3xHjoZ8LaP7aQ5PWBTHUk6KeOjJQnTMzimQC+YOs4XkmeItBt+6Gjg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBT25zTE9Na21GbHJOeVZw\nU3Uzck0yN0J3Und4bExsb1VGSkNleFdZbFdZCkx1TG5VU2RyZGlWT29wd1QxV1Rs\nM20rd2hWN09wQkc2K2dWS1JUenMwUW8KLS0tIGlLQ3FNL1NrODVmNENpd3F1OGlt\nVDBhNThoR015N01UTGp6MDUxdHJFWnMKvuFBvzz1k8Tg1J2GrhEO2yvJEfwxYrmX\nDQ1/qcoSOWde7N1Mvx9ih2mFlK4JiD51tWF351C0VOKNdnBBzO46VA==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBybnprUGZPREdpZzllYjMy\nTU5DVnRrTkxEREZQUFpiM0E4SUtsSlpqOGdnCktuaGtSSHpzTnl3c2ZuMlV6MzBo\nLzdzYWhPR1RtNnFmdVg2cTUwNE5CNEUKLS0tIHRHaXlnTlFIU1lVT1dkVUdOUnNG\nWnl1OXFpVmo0WkVlSVpKYlZaMC96encKNBnnwC2/5n2ZWYvRRoCQjOPg9b3+fFJj\nhJA45nhoO38zg0Wdv4OPA+Zrw0K0lgllk02BNXMDSv8+IthlzN5F0Q==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTYysrSjZSZDYycmtNa0h6\nZHM4VGpzdDQ3ZXlYWG9ia2F2RXpOOTVnMlcwCmpTazNzeWt0RVdPT1d5b0hqQ2pp\nd3BiU0poRm53MGFFakJvYkhxVklhWGcKLS0tIGdncmxRZVB4dGVzOVMxdVM5MkN1\nTkVXRVhqbXIxa1FERjFBYnhlTkJsVmcKOUwliiZ/tMzelHQGCYi3dW/Kq0rKqW7k\nz6kSVA6vTZGFTqiK6uLqYL56BToBY1iWM4skUYTctrcXO1eEGwnbfQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTeDllU1RoL3VndEpGMG8z\neFl0TEtPL0VXUXFBVDhwRTV2TkdyaDlRS3k4Cm1hZHRDUXRFT1RPajY0Vm9oZ1BC\nbWhla2swcHdLdFdRK2NTZ1B3SGgrVjQKLS0tIGhpNE0yV2JTeHV4emdmSU5PM2ZV\nWkh5ZzY1eW5jSVF2N0Rndk0vQzlpam8K9++sBqLwmDs4WMhHADbSwlUd1te4t3/S\nDk9ENUmvu3xRX8cpT0VELjfXRf3ne6HTbG7K7Lxj5jbmfX6aSRpIsg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-01-04T19:47:06Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5SVUveHVXUDVTakJ1QUlj\nOWlmakVlY3JucVJGaVljVUwrL25MaU5PSGxzCk02akVDWHhjUWs0aFZrdVpjUER3\nVXFsYnJCWXFSZFNrR2tLSzRBek9mRXMKLS0tIGFJZzFFV3hkeTdmYWFKWVpnWXl2\nbm9naXJiRkdSL1UzTE9xQXBXR0xENGcKxYnDk+n4u94whP0mNNSxK3KsY8RAq+w4\nbVaxdcomXo6NJq1izMdVkQFIp1hBZniaktPGsDUMWLpJ4mSrVwPbPw==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQRlFyYjFaNHJ0RDB1azRW\nY1ZvdnV5MEdWb3UwQnZuZU9sT2JleElkNmdZCm9CWVB6V0JUTE4xQWtYTU9hb0N2\nVUVtdXFVcHlTaXJBNlM0SXJac096dmsKLS0tIFdEdDZlTndXVmtTbjV3eEVsb2xG\nbzkrbU5jY3l1d3VFZExsVnB3dnFUdU0KKpmLWWgxnUfH62N0ibnqtGjvtRC6/2q7\nD/h4VozD7oZh1lC1S9SDl6J7j54frJ//AtsDdbIviVNTg4JzJ50kwQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmWGNtM0RGTHR3M2c5RnFt\nVjd1MFM1Q3hvK0xid3BnZ3VWWmpySlN0WkRvCmM3WFlUajBaN1B4cEVUVWRUOS9M\ncXVQZHZ6a21xaG53UzdKdHR5Y1JJeU0KLS0tIHRTRHpZbW1IQ2FUMDBldUtxRi8x\nN202KzhjQ3MxSERhZEsrUm00RUlGZG8Kz0BdZXtPOGe/lwiSrecSho9zPn68TZ5P\nkH3YOpAHmQZrm3P876fg8ChMv5Rgd3F679d6h334fwZDfiu2h9mFBA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3WWNCR04zV0FhT3NhNUQ2\ndVN3am4xY1oxa0tXOGlLdjFMU0cwQWo4MWlnCklPRGl5YVYyMTRVcGhhNVRSaUdo\nYjZmbTRTaGZ2SFB6d0tUeEdEVFFBU1EKLS0tIHZDOFRDTkhqbG41MGNYU2lkc2NV\nWjNYV3kwRVFrWkdNZitYZlpnUlF3NGsKne7cflZE3HpP2TbtsSSQfOLucOq16Pof\n5vQ7M8lIqi0jqL/k+siWIspilbhpkknTgUzcBsui4FxKCfEPxLFMHg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLVmRUczBEQ3RLbXByeWpU\nN3EwUm9aTXRwTlp5dG1weEpKOENXTkhrMW1FCmllRUFTZGdhWXRhWUp0dG03TnFu\nN1ZjYUJ6ZGVYNnVJZTBlcTFGUkNXZ2cKLS0tIENLZmo0aHIvbjNxbllZa2tkbHR5\neHZiRjcwd2VGbEovYm5HcEdHRXRxV0EKx6A57Usvle5ItD3XysZI41M/9s9l719i\n1fpP9W/5i+dDCgE6/ui8WLVpoj62eaArFBEN7OH1Xt04DkJ/G8Cytg==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvYkxnekd6VnJ4MEo2d0tx\nSlhmeGhPenQzSkJLSkNzWnB1d2pvTHI1QVJVCkJRNTVZeWpUV0dzNXdMclNCUlVW\nQWFoa0oxMkNBUERVMVFGdG4ySlV6akUKLS0tIFhRdkZ6ZWdqVS91U3JPd2p6OENj\neHpCc3UydXF4SFZCSTl5SzcwL2dwVUEKLT37+0UdWfe26cyQ5gRwubqN8nBv0iwp\nBRDwEBw5H6rgzUEDaAv3fRy/AxT1TvtiavuKhlJdCsJqDpJmBjgIRw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiMEo1NFhoei9vV3hlaDVo\nZjNteStQTzl2VU5WaVZFaEpmdWpzUEtVNlFFCjVUREVBZ3ZxdlBBdHplRzRxUGph\nYldyY0tpNUVqclh1TE1xemlZYm5QeGsKLS0tIE1oZ2VYSDFqZTNvK25EclRPTUZT\nbVIwdjFBQmRZTEhISTcxM1BRSk9iSWsK5/xZkLijy4p/N9WygTCIxlH0SdIiIz5W\nkGqvG27RZGqR3qwNn3vSoUBH5yOzhJAP9RAvAu5EET0jwuuG/3L0Vw==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyU1pZT01HdkhZQ0FSbXdS\nQVdlTlhlcDcvdlZZcnJJTVNSQ0Z2NTZueVVVCmpRVWR4aTF1NDZ3NUV0RkpRMjMx\nY3EzYUZRSUtMVWtsVzlhQ0MvVmgyN2sKLS0tIHIzOTUrdEVWQ1l4RXQvV01IUy95\na1pvK0pLSFRYMGFyQjhCSDJmaERhSGsK1vg2a05yGfgp1Uo12+1nCv6cN4oU1s6h\nwvrCtejM1z79f5ZGJaAK8Tz/KOKQIA402QKp9eTWf5Y4Ix6OUd1t1w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOMExJbWh4MldLckw5YjJK\nbGJyLzgxU1RUQldGVXZIcDRZL3BaODRDSEhFCkQ3TUszSWZHbkRuV0RCNWFaZDFl\nUmQvVlVydDMwL3JKVXpoNG1sdGtFMVkKLS0tIFJGK1AvZlYybVJIS0d0TlNpVU9o\nVkFXK25Ub015YWp6V3VBaStkRDM1RjgKrP91fgB60MlnXjKs9jvSnkUlSHIU6niJ\n4VveJwqCXO9zrAcS2tZaQFQ7uwga1r+iKflzLJxMPsbvshj4sfLsrg==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5a2YrMjR3SjFGZ3dtb1JN\nNGxCcDBTOVVVcHVyYzlXblBKR1F2blNVelc4ClBDZHdqNTNKckpXYzlwVFhZVktk\ncExnNTNka09ZK1lWclRudnU3N3ZDQlkKLS0tIENBR3NwSk9BdFMwejNybXYvN0tV\nU3VEVC9lTkxJbFZXWUZDRG9QMGdab3cKRYgccR5zCKEZW08FHb8/PTibpTPChG7o\nHT23XeVAf524+QpExJKsQX4Qpr+2UEG3/hUoRaisuS3TMyRUtKRoMw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-01-04T19:44:05Z",

View File

@@ -4,27 +4,28 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1SktQSmxuSG5MY2ErNUEx\neEd0Q1RsSzhaUjgyQnViQ2hhd09UNDFzcVJBCjg0R0M4ZnB1UFBEcDNJQXFjWnlO\nQVhSeURHVnorcUVnczBtdU04WDhRODgKLS0tIEJxNm9teWIyUXhzbU1EY2l1WVBk\nZG9xUlh1cDhiQmdsYnZpNVNOTUY5ajAKPyt8ZIKTfu0azAFezj7rtSJX8X4rO712\n0w8MAvnLM8k5ij6nJtR3HylwLmZ9AfMSq4Aikl+oRu7rXs26JvPbZA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJdjZ2ZEcvM3RFbThNL3VV\ncHlOek8zS05sOGJGYlJ5U0J2VDJqaHA4Vm5vClB1b2NNZk5Lc2ZscDdnd0k3TGZS\nWHllM0l4TTFuWWdtUWtidDVaRU91M28KLS0tIHh5UDZiVXEyV2lYSTQ0Rk5peGx3\nNG00OFhPUWZQamdEOWVGZVpFSkc2MFkKSPQ+6c1AyWObFZQcoH5ImtFNl8lILz59\nao3ZQ23yIiSYAZ2pAeOPh4WuNtguZ5S6dnbRDr4e/8Vzw/1qgU994w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSWU5qSDBNY2d2YnNvVHU4\ncElNT0s0R0N3U3pjYW9kVGxFV2thY3QzYTB3CkdGRUhVMUdvR1dwdVdnZ3o0M0ZH\neXV0VUZyaFRBbEN1RXR6RGJ6RmIySjgKLS0tIGZGc3Voa09CNDFoMXVyZTJmME1Z\neldyMVAyd3pTZzB4RVhTRzZVOGs3NVkKyP8sIk/Oy1GXxG0tw8Ocjerfze+eIrNW\n5XYA96ct/2M2jiPdTxg2yEI5a9wycDkzNIzE95Xyfl3LkY8864wAMQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6Q29GRkdZdzNjUDAzUWU3\nTlRvUm5URWd5a3MrMDE0WmFsWFRZS2NkQ2s0CktHZHpOaUFnWUhHUDdWK3FPMi9L\nMkhweU84OFp6bFhnME1hbk92M0pGMUUKLS0tIGdkYjN5VHl5L2hlL29wa2p6VU9t\nVzluZzdFNnRuL0dNUmtjZ3lOQTRCL1EKvvrXH1IxL4PVIZam2k9XRNBTLKahNCUh\nhMdfb4shNBtUs1bAiicJRPT1eAAWk4CWO5xpS3uJ0fODabrOozXdnw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJVHF0N2hHTHprQlpSYW8r\nMmpIZmZIT2QzNXYxTE4vc05wcXhTQzQ5clg0CktzRGFpeEVYMXA4RnV4TVdJNk04\nM3ltL2ZyczloR3NNWm01cE16NmJNemsKLS0tIDBHczRiUVhnZHlrTmdocmNQY2NK\naU5VYWZ4QWFuK0h4cUZGOGxUL3QzQ0kKtsuW7yl1/t7q9kUhTtK0G5G950Bi5n5w\n7cxX/pfMtgPhOh3NMoeuTxc9sH4pTIthRmaLVJ+GzEc4KsMJhOp+rA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLalFBanFCMWtaakVmQWNh\nc3Q2UmZ1OG1MOTNaTzFmcy9lbEFZdDZMSEVzCisrV3hMZndQekZtR3ZORjRkWHdm\nZG85M2Y1L2tPcWc1ZHpkd2R6TDJ2VXMKLS0tIEZ3Zmk0eWVsUTJwNHZJbWJhY3VJ\neVdxMVdqcTdxeUxhU0UzYThhOWhnc3MKOkedT3Tifi+6Q+C0ur1+TQuVwq75fAUa\n0cdXexCI2UIyLv+Ggbv1YIYcUiNeJNiF8JttpGAyOoVnUfIUcLsY9g==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLK0Z5UHpwdTFRM3dNL2FC\najdScHp1R1pHM0ZyMk1reENnTFlnTldVNTJJCmxVcVY1OHlrWExIeSs4bTBwQklE\nU0NkUnVmdlVuOHcrWjJpRFU4WTc5ZlEKLS0tIFhheExxWHBIcDBqL290WEpJK0sw\nRTZKbzRWMmJhVVVGT3A1UWJQUE1QS3cKp+jmuHUvZKbPx+/gxQUSz7QV1jLuIzP5\ne1jkJ2rJT2i8snAvihd8bsjRSFmoUnEg6kV8f0OteezNbkZoNhd30Q==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4RDZ1K2RUaU4vTnFsbnpM\nRTRCUkg3RUFGS0pnK0NJSWxXTWVsaTZ3ZVhrCldDWkd4L0Y3Sm5uM0FZZnZqclJq\nUEpNRElGbEUwM0ZHRzJWNG0xT1ozZzAKLS0tIDBiOTRJSlRRbkxuRm9aTGVoZEoy\nU25iVjhxTXhvSTVqV2kwb2doMXNBeWcKK1wDpzC6aYSVJ/z0wGvSDjgXDiPeW+t7\ncgD9a1nCB/N1QZQn2IbnnkL6EsCuIQpRlzlLL9FhdRdLP+3ij+i8eg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzRmpkdloxdU1DRmF1Tzg4\nYSt3bks0Y21OQW5DS2JRU0ZMMHZJV2xLc0dRCmRORFliS3A0QTR1Uzc0ckd3cHA4\nRnNrdVBISG5NcjhrNDRoUnl4c2dPL1UKLS0tIHA5aW9GQkdXU0VNRHd6aEpoSzhJ\nSmd5OU1ESGJqMFVZdGhBMkdYTmlsRWsKLN36pDsdf06Rn9RLxfh46nX5u0dfyoe8\n/VvQiaWoj2/pv8NmwdFzdJQ0mTKkvEdxxY/Jk0YK+GQA/NGIVIIoWQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkN2kxR1BSUVBHSDhuYVVu\nREZGeHFNT1lUVGljMUpmV05FcHROdEcxK1hRCjhYYkJ1U0JaUkdRZG1tMXFudlRM\nSllqZVBsd3JDemFITVVlWkZ4Uis0NnMKLS0tIHRjWHFzZEgwSmlnWFVpQk1tcnJC\naytMVk92U3lGbCtWdXA2UDFXY29Ndk0KPmSHHP4Nyb4dkyz9a60OP5Lc8MlbPGxo\nLReOVEJ86yGbL4ARFptqrpcJ6ILEagWSYLEkqoZ1xiG1BSvs6J7dHg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-03-23T09:09:07Z",
"mac": "ENC[AES256_GCM,data:phFpHUzJ/7rd1k1fr9YFD2FplXV3Qv5zFni00fAgG2VtVoIdFYeNRE0EEh2ulnKcIXjB/5lZuMss2bIoBt4i46BB2ZHTpnWksbeHowdgkHL+eXT1F7b11S1y9NEKc/ug3jarPwyj3usmVQJlllAzANCQHGrYQdBrFXvFae3cH40=,iv:4v3k4q0SxyTvHoqr2Abf6OhAcANCT9oWTa5Kwlb5GCs=,tag:Hn+fUEmOu7fWc7SSBe5yfA==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.12.2"
}
}

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhVVBSMm5hUmlnUUhrSmxL\nNVVZY2dZajlKSWMxZ3dMVlBORjNUaHBOTEZnCmNpNFRodDFFb2xNdnlYVHRiK2ZT\namV4YXFOd3FCK1ZiK0NIbW56Sm1wNm8KLS0tIHk5TVlxZHB4NzFiQTFQeThUY0JG\nV0huc2FzQU9WWUFJWUFNS3JoTzFaTjQK/T7aFSWnkpv1Qbx47LFp85bkaCOt5vrs\nLjiAGimUOVkr7ZcJz3540JWvBBUNNRKmM3QDNlgPg5luI0fa7+pNRQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3NmNaR1dvaXA4QUVqbkNP\nbFlpTzlHMXd0SGNsd0wyZ2FwZDFBR0tuaWxnClZuNzZma29BWURibzVUZndUc2hV\nZUpUdE94TC9Pa1dLQitLbE1qRXZJNVEKLS0tIG00ZGJyNHp0eEtQeDNsemErNVlo\nclBid2Y0TysyZ1RtK3F2RS92QjJJWDAKUruZmiTB5tkBF5VOO7dv+GijgAO6AVNe\n6t/xyot/Gc3WFOeNkYUk1K7fo0oLFfRG1tZBW+4cwFi/FMUpyjCxig==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSU3ZFZkZqUjcycmtYalRw\nYXlSQlFvcXFwS0xYQ3BVUXlPeDBHeUl0R1J3CkRGRHNaZWxQN1hISmh0VzNwZnlM\nbUFYVWw3dU5jNVorOVFKYUtyWHl5Q28KLS0tIGU1MkhrNHptZUZldHM1Y1FZTytV\nY0VvQjRmUmpIbW12WDlkLzVwSE9uZkkKMczm0x03kPRAD+pdXyDRTqaQckU4MKxg\nfIJAAwRAbskLF37SUzvefa0DCdKpyK876km7nOyIZunTE1j7xIwMNQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKcW5Ld3BjdnYvanJYeHM4\nNk1vS2o4WEg5Ni9aUlBuNmRKMHJpOHh3dTJnCnlhTFRDdXoyS3kyVEM4Ukc5N0pB\nazBwQlIycHliaEt0Unk3WUZtcURJRXcKLS0tIEpoVHlFLzBCSDdGRVZSeCs3SW1n\nK3k4T0VkeGhBd3NSYlNYT3JudXRQVWMKPu0HGvtYlgdih3vNrqvEvke0Fg7hr9xm\n1f3G7GpeRBLZlab6UKKzbFFgf0NADnGEBmWMP7e9taVYqYy1FFf7lQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnVE9tNEhSRUFydEhvL2tu\nbWVuVXhVYlhTVVFnMTZrOEFBOEZKZ1A5TFRjCnNSSGVjNmtYUnVsdGZRcjg5SmNW\nbys5alQxWVRRNS9rV1V5aU9nNEF5ZHMKLS0tIFl2MjgwclFZOEtvVmR5TDFzL0lm\nblBnekRPRzhqem1CSEgyM2YzTnJvck0KwITv/a+Z0XQ0FeI7rPti9IGZPuILWVhC\nTC0dWuJ3uSflAxgRvGpaQyvsKMLgkXCRf58kx17cHjPT+Z8EsXeBSQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQUFgzR1hFemRNeHorN0Fm\nNld4eWhwMWJHdWhBYVQxNUtGemlEeU5jcUcwCldlSENWSXBxYjN6TXZUM2Nuc1Mr\nN1Zlc0lWY29KcGI0bWVwYzVnS3ZBM2MKLS0tIE1rVnhhNFJkeFZjeDh0RVM3eHU5\ndkJPZm1xQTdWZTdXNVJrdHpXV1dIWVEKZlyGzrce2W4M8LVDbYGXIpK+cVqqB86t\nqibOGTNdSAhd2Y+IWb2FdPr0uxoixC4j6Mk+Nw2bXhDKXp+VAAlWog==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxdEpHdzlVbDdBcWJyNDJC\nQnpTaFZLMno1Q0ZJcXVjMUIvcTZ0K3lDY0RzClJlbHhqbWNVemFpRFh2WXRGV1VZ\nRENTV1ppN0J5SVFDdUR1aDRRdTFKUUUKLS0tIGRFMGIwVkliRitaeXdHTm55QzQy\neGdzYStQalVFb3ZPdkp2VFJ5SUJ6RkkKeeOJ1MjTKqevHHl+5dXL8n5o05KV6HvS\nMf5yO3rxYDLzO5ore63G3KjqCS9i36mTzLoyJgYIKQK4IAh/AKcI/A==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKN0x6QnJCZklFTis0QVJE\nbWdJZmxJWmxRYUtNdmFmaHM4bTFQOGZENVY0CnkxbW5Qd3lQTHlNV3dEWHJVS0Qr\nNUhDMzBmdzI2dzNGY3J2dVJwd0wzYXcKLS0tIEVjb3JNODY2ckNUZDVtN0VYaTNm\nSnZWRUpFQkFXWWRBVVNVcUVNYjRxb2MKGa0tBfwyEWrXmkCIupwHID2j357UMiSe\nsg70OdmszMZ+MDjcLIXVkjQfrLTbtIKU+u3jA7MDzECZTm+nAk8cTg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxZE1lU0UzUFQ5Z1ZQRXBj\nbktnN0lWNXZ0QVhPS1Z5cFNPeDJlVCtLdWlvCmFPSHdkeUlONHptbE0xcjg5SUQ2\ncnZKY0JWY0haZFEzcWNnUHdnN250WmMKLS0tIEhlbG91K0tabmVVSDhBeStoUU5j\ncFRlUk5xZjRRVC93UzNJUUhhTVQySlEKB2uBSMXOJd1ufB3i66ldhXlnbquWcXgi\nXjuJb5ud4Nz97wlSnqAADoY8V9aYJM30byL4VJeXiMq1oppDptbQzQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4YTdqUXp3eVdmV1drQ00v\nMkxFUEIrdTBoRDREbndMc2hkekdWZDlSSFZnCndmMmtvTDRpRjNvUENQaVY3S29k\nRGFaUStJRmlSSEJxMG82Nm1Oc0puOU0KLS0tIFdUdEZNR2tZdlBoTFhjK0pNNnNW\nVGZIRytNNnFOQXZMTlhHMDRMUnJ6MG8KDaPfk8dcE3ijfwPhbeN909I6zPdjxX1M\nFnQiSGQuKvWuVzn2qjl8AAFxw1nVO/3l+7eu/PN2yeKIb3rMUvWpDQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-03-01T17:47:46Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2UDczc281UGU2aTlHeEp5\naEdiZGgzK01VODE4enMrRTIzNmNwa0srRm1rCnd3K1NnOEpXVDJHWGp4VXhFTUJr\nM1c5cXVza3NQOTZjTElTcFRhY0ZDTE0KLS0tIGp1Y3d0dXg3NnBnQmpmeXI4a2NQ\nZ1lIU1hCMUJMR3lidkxRNWV6SzJkeGsK93sF6uCKKFh12hnlSZ9DMxBv8/j0LIp5\nMGeUbbX3sWdk4I04QjXl2Yi9ER5d9W3zDfrQ1u5CqM3IqSPHQ7VgpA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2UGRKeHloRTVjcHUyOGJ3\nM3FKajJTZXE1d2pnUXEwa25BMytSZjlZV0YwCjFBUjhtUDFRckcxK20zcmxpOVdX\neGhVaC9jaHJuSm9tRktZVlFLTk5yS1EKLS0tIG1CRkdhVW9LSU9sY3dRUS85eHFU\nRnpxdVg3dzM1bldQS0JQY2hYbkhXSVEKREVnO8OsmWfrJ19Vr7KY3O97XG2LXrSP\nxvGOZIDMI2UIfTFWm5TcFDOOD34HV0fWtzjn9GYSx9XN5fQX1YK5vQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPcE55WTAxSERXdUZudmxJ\nODQzaWFBRXl4Ly93MjM3K211OXBtL3A1SUZRCjBjUnRmL0FVdXgvWExsM1E5bUhs\neHAvcEwvUmxDeWFoMWJ4RDQ2VEpzcGMKLS0tIHJUNWE3Z3drMVYwSkV4Tll5emxN\nc3VnZFllaTJLckE1M2ZYaE4vdFNTSGsKvQhxtlSjH9+tjaT77ZiuMQeb0RPotSeS\ncoYf0R8TCvy8c2CP0YYs99bIJP1zx4RirlC/80Ji9eXBYkJaoDRkew==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3bzdOOWRzMm9BOVNoaTkz\nbW1YZWZjVkF1MHhMcy9nTFhPUjdpamRLZmprCmtIK0VxNVNSR1MrbGlqakZzV3Jw\ndE8yZ2lyOE92MStpRWExUENDSGNCbHcKLS0tIGMyS05FUlk1a2N3WmFQcGdpZytD\nRWxQc0hucmxCK2psSzMrWmt4cTlxMGsK95lvBRK4daCbixLEJkEToAg7/yL2iJ+j\newP0caUe3E9UyhuFmZCiN9VsYaQy0q12KcSTq/KMybj6UmDThaOMRw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMS3dYenBFalBySUJKcDZl\nQXRBMTFXN0tCQTM1bFl2SDNma0xRZnBJbG5nCm5hTXlIZVVpREpLUUoyRitnWWZJ\nZ2VPSFBBK2h1aFZldGRNZm9JVDRvd2cKLS0tIEg5cHgrRE1sS0lFZkxSNHdweXJz\nb3p3Y1lZUjBxYWUxZjNBbDlFUkd6VVUKLaUOi7OESO8yTZyBcgOt0LhjM6QKMwlm\nl6MSVJYBtmcp4wdZdaCsiC28dlHg98qXauLppgnkda8LyhDUliP5lQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByTnVqbkNSbW1nNUxpNlNQ\nekRWWEJBeHdmMG1jYk0xeGNUMnBlSk5xNVRnCmNhTlZEcGtUbnlNaU12MGpVVDhp\nOHRRbkVBZXg1bVZiNmtXVHlxU0dDL28KLS0tIDRhV2R3akMzZHFZcytwSHJ3QU9I\namhWREJHZGcvMk9DeUdsb3FtOW1wUzQKhRDjz4lv9BPjOl0ZUstNlMVAw5x2Dgql\nQ06h5a1qm6YqktDvJLiKiUA64ZqDFHvB61x5qVn0Wc913vLayoEIwQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkUTJEcFp4dWxzR3BuRnJw\nNy9vcStGT3ZnOHBYczZSUHgvN2Fzc0MzaG1nCjdCNXRnTHN0YUZZKzBPVmI0UTBn\nbnRNZGxyM0tMSW9HVG1yMmxQeUFXUHMKLS0tIEYrOTJMWlZndHNxeHA4U0ZFajJM\nclRPdVAzWThwNGRpSzJkU0pxY2llbnMKpMQuw76xRJ162EJ91ui6jLNeBY6+XCiG\npKJB9YCTnh+JlxFzm9LU5s8bI4XzsywdrdKMYck1G4A8NW4MoQCrig==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFdmIzR1pjNnBnckpEMzcx\naXhyZHNXNUZ6SFdnSFRoWWIzUVFFNExvSXdFCkNXQXhDY0h3UURPRlJSeGFrVC9B\nblFrTitZVllxYmVwbEZRSk8xelpYY3cKLS0tIExkeVlMNWtjZ2VkL1AreTRHUXNj\nYS9LNTNLd3BhUlpNdWp3UUdNQmhLYkkKpYAbuft1WSROAm+3iGvU1TRhBP3HgiB8\nuDOdv9NTw5AhhTK2VIixhJHZI3gIAvCwndmQWV41PDgoDqmeVwxrsg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMWnYzbWVNM081WnJ2MHh2\nbE56TUZDU1JqdkJHczVaUi9ReTFsNENRdlFnCnArZEYrcmtIMFZEZVhUcG9MdWNO\ndVRNS0w3S2FqMzNkOGVqcTJwTFQyK0EKLS0tIDRnSjBaRC9xd0ZsUy9CWm13MWZu\nTXQ1WEdZR2h0UGM0cmZOMFZwdzdzd28Kv+KkGEfWvQVgOtiJMqEwbIgQcQI9U4fC\ngh+9QrN8blwbt0OVqyu9tPWrP9bPOEhM6U13wj4Q8BqzrNVsLix4Lg==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByVDBtZzJibHNpV01WUTF3\nWm1OajJnVjF6TFluK05UWXptdWJFekY2Wng0CnliUGhmTHZZUHNRY0piM0ZXT3J6\nc2puem5VbVczOVlKZ3RqM084N3JCMEEKLS0tIHppbHdvYk1LTXRMeXp1eEZYeGd1\nT0FJZlNJNnAvQWNGcnBCdnBNTTg4UzAKTwMtaAQk5/8qGO4M3kmjfP8yJmrDiWiJ\nsD23c6S7wwAZdltK1spIRMkSS2AhJBN+ePXBiAGWjatSWsBKlDZqww==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-03-13T17:29:46Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqMHVFbUFpOGdXc0t4Q2dj\na1lxeWFBR3ROTDN6RVlwYlgxZmdhd1ozNmdvCnVpZFFnaGFqWGs3UmRtZytlUEZK\nK05lM2ttWmNOSGU5ZlBTYWZ6K0dCbzgKLS0tIEpHQXRmaVVhbXJRRUI4REdCZWI5\nTmFDY1gvWVVkUExpeWYxTjlCb3MwUUUKnHzWgaUvDXH1I3TPyY38h9Pbjqk4Whma\n8ctECOpg1obtMr+a9Bdw11IPwAe3R9K0ZfE681HroETCHRxw2+38yw==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDMWRWUElvQ3ZRTjFKRVQ3\nYmZINCt2eWJBbWcvLzQvWWlURlpzMldSWkZFCnhidzFMZjliUkt0MUVML0pLQ21L\nQ0pwL2pteEFlaW0wRFJBZk9BSHRXa3cKLS0tIEc1dXlNdDQxUFZrVUc1Z0VBMHQ1\ncXNaYnd5dVVvTU41bTRnRUdGVjEzczAKjfqq9yciDRBW+N2M+5mRRztlW5+JRL0n\nRL5aZlsDVo3SJAxjaaGx4zgwN7dUX6JPrTGW6sYd5LCGuHZWX8phfQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFRU43QXpWb1JDUVJCck9z\nNkFXWXdWY3pJZmx0Mjc5SDgwcVFXWkRua3pVCjAxQkJBUXF5T3hxek00ZzhRcmF2\nb2tWQlYwVXpiM1dMRFdqNXIyQjRhb1UKLS0tIDB2Q003YjlCTXUxa2k5RmMrWVds\neXhLSzViMHl6Smo3dDljRnRsT3lRb1EKKxaXLegA0V6vTnYF7l7MsJD42XWidB/h\nGiOojZ6r3JVV4Wx4Xl/MPk7lYvYN9VH//PUlA7h0Q9iVaQS+HA+aZw==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJNXBjRGdmWGFEMmY3T0F3\nS2lFcXRKWnl5T0JnZkRxTzJUK0FlWlIvRVNvClVESkhEdFgzZnhKTTdaZEd3N3Yr\nWWJYTWI2M2Mwd3F3ZWdJVkdLMWwrRlkKLS0tIFFwcEdQUVA1dUFRRUtkUDN5RUZt\nTjlQNEx1ei9UeVpRTjcxL2t5NVBUajAKaZOQ1GQ3vtgZdxxoXQKU5Q3jSUnakW9F\n6bHKbhBGSFwCb3u9+mgVaefBU11vT1ue1kA8sCM03MkYBmM+8cPv7g==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBob0MwUkJvRDRnQ05XMVRh\nODY5WE8wVlBkTW8xWUlLWUwxNHV6Y0FJTW5zCkdVV1dJTGlxREdGbWowak1HL3pH\nejNKV0VvejRadTVCeVdxdFkyMk0yOG8KLS0tIEllWldkaXZ1TzYzSmFoQ21nMkV4\nZlFxN0wxWlpvSHdreTdxQ1NZcit0dGsK+23q1QS5L1JzqFcKUygqfmYc64qVOO1R\nSiGa7O7ZoRMnn0Zfjas3K+byJ4PzNitEEU64s1518L/e78oHKQ9vVQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2a01mMmNPckZTbmJvQUVC\ncDBrVCtlRmpJMVByeE5zWFMrUCtHSkdDRFNBCmpZazA4Q0duejNYcmoxWDVMR3A5\nclh5OXZNNWd0VjI3cGlWbk5JTUw5NHcKLS0tIEVma1VsQ3cwSzdGZTlpanA2ZWNF\nQ2xLSjNkdjJyby95aTdTYk9HSHFvM1EK29MPNiB5GKqBFPgLcIp1giWzYRpPZ7ur\nDSKqvZ4kbEyxgdLxJ/i2P2iv4/4bb14LWQno9iEXsVeZ/iKf1mDigg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaakJHOXRxeGRLZldZVmVi\nNHpFNmtYU1BCUnVKR1I3ZC9CSW5ORXlCbG5JCnpyUFA5NDNzbExob0s2R1EybzVJ\nb2tqSWtmZk4zQ1RZQmhKUmFvTFhTUjgKLS0tIG1DV04zMzVXdk9yQ1FFQzZsakM1\nTXQ0dCsrN0Z5RUVRamJUZEo5UmRiZWsK/av25ulEhPih+AK2rnktJsK6/SYS4IDx\nBuI2hBRZTdw1m4bBcCwMkLn/F1Rxd37xI1NZKKE3+CgnIs5G9XxhAw==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBteGpDZkpLcFIyVzNMbXFV\nMlY4SExrTThkdEZJczdLdlVwUWpVUWVuTUJvCmFXVG1ESHlIOExSdFJJOXhtb1JZ\nSVBocEtJTjRkckNwYktOdEdBQVQ5TVkKLS0tIE9iZ1pzMGhVc3F4THJwb2pFK2dm\nWnZ0SkhyNFMyQ1VVa0tWbDlXR1ZwK0EKPJq69mAeq3ghUpY+VuS+xCKeJVwmYnGm\nQ8mtmk/5skqbeOSKAbIiEEY5avPRy+Uy3YTD7g6vpSCqnpNSLEc/qg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEV1dtckt6Wi9HQ3Fxd0Ev\nRnRzcWs5SWRmNEpYbmZQTnEzbnliVDRkZ3pnCjUyTEFOYjd5SC9STVd2V1o0NGVR\nelphZE1ISGlkemdXSTBlcURDYUh3OGsKLS0tIHUxZFRKbVFsM0FWZ2ZEa0FaYjl5\nTWhTMnptYU4xNmp4aVNYMDRORkk5NVUKiwwbLVe3mtVe9sgeSA/FUhkowfFeirbA\nXXL9ct+lizSNXsFG7w3xpZsEaNGbHF28maZHWOpZotKRbdx4w6UJGQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIZENwNHhLdzNYYmEvOHNW\ndEZJbVpUMGJyaVU5WkR0bUc0NzlsMWtmdjN3ClMyM2dGa3BlVTFqeDB6ZHRGSTNu\nREZSN0JXUkU4ektnWis0L3lCOHdpTmMKLS0tIGQxZTFRYjYvMS9uM2lYUThwYmtp\nbkYrTlNQeEd6aVNTY25aMHFVbnJXSWMKulwDyWezqDUIlv/aHMMGzGqOFU3VGaw3\nYvm/e2wFWPenFH0gfALkdC8upRghE8r9jkXcj1pSmDBbfjNghz+2oQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-12-20T21:46:17Z",