Commit Graph

100 Commits

Author SHA1 Message Date
0e92de7eea feat(desktop): add screen recording (task 0029)
Add a wf-recorder module, enabled by the desktop aggregator. Super+Shift+R
picks a region with slurp and toggles a video-only capture (no audio), stopped
with SIGINT so the file finalises, saved to ~/Videos/Recordings. Start and
saved notifications fire via notify-send.

A Waybar custom/recording widget samples the wf-recorder process once a second
and shows a video glyph while a capture runs.
2026-07-22 22:56:56 -04:00
111b985d7d feat(fish): restore ctrl-r history pager in vi insert mode 2026-07-22 22:37:01 -04:00
37ddf4342a fix(desktop): use windowrule v3 syntax for the satty float rule (task 0028) 2026-07-22 22:22:20 -04:00
708a3ee963 feat(desktop): float the satty annotator window (task 0028) 2026-07-22 22:09:56 -04:00
9963a0dbe4 feat(desktop): add screenshot capture (task 0028)
Add a screenshot module wiring grimblast (grim + slurp) through the satty
annotation editor, enabled by the desktop aggregator. Region, active-window,
and full-screen captures each open in satty and, on confirm, land in both the
clipboard and a dated file under ~/Pictures/Screenshots.

Bound to Print / Shift+Print / Ctrl+Print rather than the spec's Super+L
family, whose keys task 0021 already holds for hjkl focus and window movement.
2026-07-22 22:04:38 -04:00
210a260735 feat(desktop): add clipboard history (task 0027)
Add a clipboard module to the desktop group, enabled through the
aggregator. Home-manager's services.cliphist runs the text and image
watchers as systemd user services bound to the graphical-session
target, and a rofi-dmenu picker recalls history on Super+Shift+V.
wl-clipboard is on PATH so the shell can pipe to and from the clipboard.
2026-07-22 21:42:53 -04:00
005928ef88 feat(desktop): add hyprlock and hypridle (task 0026)
Add a session-lock screen and idle management to the Hyprland-native
subdirectory, enabled through the desktop aggregator.

hyprlock draws through the compositor session-lock protocol, themed by
Stylix, with a centered field and clock. Super+X locks directly through a
guarded launch so the key works without the idle daemon.

hypridle locks on idle and powers the displays off, with tunable timeouts,
and locks before every suspend. Lid-close routes through logind to suspend,
which the pre-sleep lock covers, so the lid lands at a locked screen.
2026-07-22 18:37:32 -04:00
900cb6b8e8 feat(desktop): add mako notifications (task 0025)
Add a mako notification daemon module to the desktop group, enabled by
the aggregator. Toasts auto-dismiss into history after five seconds, a
do-not-disturb mode ([mode=dnd] invisible=true) suppresses display while
still recording to history, and Super+N recalls the last notification
via makoctl restore. Colors and the popup font come from Stylix's mako
target. The bar-side do-not-disturb toggle and media controls already
live in waybar.nix, so this task adds only the daemon.
2026-07-22 17:15:32 -04:00
7d9a0dae36 feat(desktop): add the rofi launcher (task 0024)
Add a rofi module to the desktop group, enabled by the aggregator.
It runs the drun application launcher, bound on Super+R. A session menu
built on the same themed rofi is bound on Super+Shift+X, and every
rofi -dmenu call inherits the Stylix theme, so later utility menus
reuse it for free.

Also give layer surfaces their own fade-in (layersIn and fadeLayersIn)
a touch quicker than the window animation, so the launcher fades in
briskly rather than snapping into place.
2026-07-22 17:03:54 -04:00
aca09d87a1 docs: move the comment conventions to the global agent instructions
The comment 'why not what', self-contained, sentence-per-line rules are
project-agnostic, so they belong in the global agent instructions rather
than this repo's conventions. Genericise the four Nix- and domain-specific
examples to their underlying principles on the way across.
2026-07-22 15:33:07 -04:00
5a7593a793 feat(desktop): bind the hardware volume and media keys 2026-07-22 15:25:50 -04:00
ba881998bc fix(desktop): highlight the current day in the Waybar calendar 2026-07-22 15:20:32 -04:00
b21b4ff77e feat(desktop): add the Waybar status bar (task 0023)
Add a Waybar module to the desktop group, enabled through the aggregator,
showing workspaces with per-application icons plus a clock, MPRIS media
controls, audio, network, battery, and a do-not-disturb toggle.

The do-not-disturb widget drives mako's dnd mode and degrades gracefully
until the notifications module lands. Module glyphs are Nerd Font codepoints
decoded through JSON, backed by a system symbols font.

Add a PipeWire audio server so the bar's volume widget has a running sink to
read and drive.
2026-07-22 12:03:30 -04:00
6d1afa41ed feat(desktop): let Stylix manage tmux and fish theming (task 0022)
Narrow the terminal tools excluded from Stylix to nvim alone. nvim keeps
its dedicated gbprod/nord.nvim colorscheme, which is richer than the
base16 mapping Stylix would apply.

tmux and fish are now Stylix-managed. Drop the hand-written Nord colours
from the tmux status bar so Stylix themes the status and pane styles,
keeping the minimal layout (session name plus window list) reapplied
after Stylix so it still wins.
2026-07-22 11:38:34 -04:00
27a28b1ae7 feat(desktop): theme the graphical layer Nord with Stylix (task 0022)
Add Stylix as a flake input and a desktop theming module that resolves a
single Nord base16 scheme across the graphical surface: GTK, Qt, cursor,
and the system monospace font, plus a static Nord wallpaper drawn as a
Polar Night gradient.

Scope the theming to the graphical layer alone. The Stylix targets for
fish (at both system and home-manager level), tmux, and nixvim stay off
so the terminal tools keep their hand-written themes.
2026-07-22 10:28:03 -04:00
5ca717ca4a feat(desktop): toggle the tiling layout on Super+Shift+T
Flip between the dwindle and master layouts with one bind. Neither a
dispatcher nor a keyword toggles the layout on its own, so a small pinned
script reads general:layout and sets the other through hyprctl keyword.
2026-07-22 10:08:26 -04:00
c0ec330024 fix(desktop): switch the terminal from Ghostty to Alacritty
Ghostty's GTK4 window construction made every launch sluggish on neogaia's
integrated graphics (~440 ms to map a window), which a head-to-head
comparison against Alacritty confirmed. Alacritty's lightweight OpenGL
renderer opens fast on the iGPU, so it becomes the terminal on Super+Return.

The choice stays reversible per host, so a capable host such as zeus could
adopt Ghostty later.
2026-07-22 09:46:27 -04:00
b9749dd6a2 fix(desktop): correct dwindle keybinds and speed up the terminal
Two issues surfaced on the live neogaia session. Hyprland rejected two names
at load: the split toggle is a dwindle layout message reached through
layoutmsg, not a top-level dispatcher, and dwindle:pseudotile no longer exists
in this version. Bind Super+T via layoutmsg and drop the pseudotile option.

Ghostty also cold-started GTK on every launch. Configure it through
home-manager with gtk-single-instance so windows after the first open in the
existing process. Record the dispatcher/option version-drift trap in CLAUDE.md.
2026-07-22 09:02:51 -04:00
4b6fbfe732 feat(desktop): add the desktop group and Hyprland session (task 0021)
Introduce the modules/desktop/ group behind a single modules.desktop.enable
aggregator that hand-lists and turns on each piece at default priority, so a
host enables the whole desktop with one flag yet can override any single piece.

The Hyprland compositor lives in a subdirectory of the group, sourced from
nixpkgs with the program integration owning the session, portals, and polkit
and home-manager writing the config against that one package. greetd with the
tuigreet greeter provides a mouse-free text login that launches the session
through uwsm, and Ghostty is the terminal on Super+Return.

Port the operator's KDE/i3 bindings expressed entirely in hjkl and letters,
tune input (US layout, Caps->Escape, snappy repeat, touchpad taps and natural
scroll, flat mouse accel), and set a subtle feel with blur left as a host knob.
Enable the desktop on neogaia.
2026-07-22 08:03:01 -04:00
fc3380f8ce feat(pi): install the coding agent for a side-by-side trial (task 0020)
Add a `pi` module that turns on the home-manager `programs.pi-coding-agent`
integration for the primary user and freezes a single settings.json: the
default provider set to Anthropic, the default model to Opus (claude-opus-4-8,
Pi's own catalogue id), and analytics disabled. Everything else in
~/.pi/agent — the credential and all self-modification state — is left
unmanaged, so login survives rebuilds and backing Pi out is a one-line enable
flip. Enabled on neogaia alone.
2026-07-21 19:48:24 -04:00
6f34c95c51 fix(claude-code): remove the erroring attention-bell hook
The hook rang the terminal bell by walking the process ancestry for a
process named `claude`, but nix wraps the binary so its `comm` is
`.claude-wrapped`.
The match never fired: the bell never rang, and the loop fell through to
a non-zero exit that surfaced as a Stop hook error on every turn.

Remove the hook, its script, and the Stop and Notification wiring.
The tmux monitor-bell config is left in place as a general nicety.
2026-07-21 15:23:03 -04:00
908d7719fb feat(fish): restore ctrl-f autosuggestion accept in vi insert mode 2026-07-20 21:52:00 -04:00
310ff13c77 feat(gitea-axi): install the CLI and wire its agent context
Add gitea-axi as a flake input and a new modules.gitea-axi module that
installs the CLI through its home-manager module. That module also declares
the Claude Code context — the Agent Skill and the SessionStart dashboard hook
— when claude-code is enabled on the host, so neogaia gets both while a host
without the harness would get the CLI alone.

Drop the hand-written SessionStart hook from the claude-code module, which
named a gitea-axi binary nothing installed; the gitea-axi module owns it now.
Remove the vendored skill fork, which had drifted from upstream and would
collide with the copy the module writes.
2026-07-20 21:37:33 -04:00
e49fb929d8 feat(fish): rebuild the flake for the running host
`nixos-rebuild switch` alone leaves the flake implicit, which is not how
this system is built. Name it explicitly, taking the host from the
configuration that installs the shell so the two cannot disagree.

The reference stays relative, so `update` rebuilds the working tree the
shell is standing in rather than a fixed checkout.
2026-07-20 14:04:51 -04:00
6b5729b98a refactor(ssh): state the admitted keys directly instead of deriving them
The access policy was a separate fleet declaration mapping each machine to a
role and a client key, which the module looked up by hostname to derive what to
authorize. Authorizing a key needs the key text and nothing else, so the
per-machine names, the hostname lookup and the role existed only to reconstruct
a grouping that two lists already are.

The module now exposes those two lists and the set a machine admits, and a host
declares what it draws from in its own file. The fleet declaration is gone, and
with it a single-file directory at the repository root.

`authorizedKeys` defaults to the workstation keys. An option of a list type is
not mandatory the way a scalar one is: undeclared, it yields the empty list
rather than failing, and a machine admitting no key is unreachable. The default
makes the safe case the silent one.

Verified to produce the same system as the design it replaces, and against the
running machine in both directions.
2026-07-20 13:05:07 -04:00
f18b40091c feat(ssh): adopt the client key as a secret and derive access from roles (task 0019)
The operator's SSH client key existed only as a file created by hand on one
laptop, so a reimage would destroy it and lock the operator out of the remote.
It now lives in neogaia's own secrets file, encrypted to the admin identity and
neogaia alone, and the client is pointed at the decrypted path rather than a
copy in the user's home.

Access becomes a policy over roles instead of a per-host list of keys. A new
fleet declaration names each machine's role and client public key, and every
machine derives what it authorizes from that: a workstation admits workstations
alone, a server admits both, so a compromised server reaches no machine of the
operator's own. Registering a machine is an entry in that one file.

Only neogaia exists, so the server half of the policy is built rather than
exercised. Two assertions reject a machine missing from the fleet and any entry
whose role no policy defines.
2026-07-20 13:05:07 -04:00
62eb6286b4 feat(git): enable the module per host rather than by default
Defaulting the module on hid it from a host's config, leaving no single
place that lists what a host carries. Each host enables it explicitly
instead, at the cost of a step when adding one.
2026-07-20 12:20:13 -04:00
75c5745cbd feat(git): carry the commit identity on every host
Default the module on rather than restating it per host, so a new host
commits without remembering the line. A host that should not carry a
personal identity sets enable to false.
2026-07-20 11:15:15 -04:00
c9fc17ecf5 feat(git): declare the operator's commit identity (task 0015)
Git identity lived only in one checkout's local configuration on one
machine, so it was invisible to every other checkout and lost on a
reimage. Declare it as a module instead, enabled on neogaia.

It is a module rather than base plumbing so a host that should not carry
a personal commit identity can decline it.
2026-07-20 10:53:47 -04:00
ec40892560 refactor(ssh): flatten the module to a single file
Every module folder here carries companion files alongside its .nix. This
one has none, so the folder added a level for nothing.
2026-07-20 09:29:13 -04:00
42ff195556 refactor(ssh): extract the host key wiring into a module
The sops secret declarations, the empty `hostKeys`, and the `HostKey`
lines were boilerplate sitting in the host, and every future host
restoring its identity would have repeated them.

`modules.ssh` takes the encrypted file and the key types, deriving the
secret names and the `HostKey` lines from one list, so the daemon and the
secrets cannot disagree about where a key lives. The resulting system
derivation is unchanged.

Also record two gotchas: reading PR review comments needs the Gitea API
rather than tea, and host keys are not user authentication keys.
2026-07-20 07:40:47 -04:00
54c191f801 feat(claude-code): share sudo's credential cache across sessions (task 0018)
Key sudo's credential cache per user rather than per terminal, holding it
for 60 minutes. An authentication made in the operator's own terminal then
covers commands issued by processes holding no terminal of their own, which
previously failed with a bare non-zero exit and no output.

No command is made passwordless. The password remains required; only the
cache holding it is shared, and any process running as the primary user can
spend that credential until it lapses.

A PreToolUse hook refuses a privileged command while the cache is cold,
naming the command that warms it, so the condition announces itself rather
than presenting as a stall. Both states were exercised against the running
system.
2026-07-19 22:18:58 -04:00
e5d8f69f16 feat(claude-code): ask questions in prose, never via AskUserQuestion 2026-07-19 19:04:48 -04:00
7809e079e3 docs(claude-code): teach the skill-management skills the Nix layout
~/.claude/skills is generated by home-manager: the directories are real
but every leaf file is a read-only symlink into the store. The skills
that author and install skills assumed it was an ordinary writable tree.

- craft-skill: personal skills are authored in modules/claude-code/skills
  and applied by a rebuild, never edited under ~/.claude/skills; writing
  there succeeds silently and strands the skill outside the repo.
- setup-skills, update-skills: copy out of the library with cp -rL and
  chmod -R u+w. A plain cp -r copies the symlinks, committing store paths
  into the project, and dereferenced files keep the store's read-only mode.
- craft-skill also staged through `dot add`, a fish function this repo no
  longer carries; plain git add replaces it.
2026-07-19 08:34:22 -04:00
91d0a7d8e4 style: trim verbose comments to concise, self-contained notes
Cut restated "what", domain-glossary framing, cross-file consumption
narration, and against-alternative justification from in-file comments;
keep only non-obvious "why" and load-bearing pointers. Drop the
`generateCompletions` line (a no-op restatement of the upstream default)
and its comment.
2026-07-19 07:57:44 -04:00
b7363ed7e1 feat(claude-code): manage the user's global config in the Module
Bring the declarative half of ~/.claude into modules/claude-code and apply
it when the Module is enabled: the global agent instructions (context =
./CLAUDE.md), the skills tree (skills = ./skills), the attention-bell hook,
and settings.json (model = opus plus the Stop/Notification/SessionStart
hook wiring).

Runtime state (projects, plugins, cache, history, sessions) and the
.credentials.json secret are left out, so login survives rebuilds and no
secret enters the repo. Verified against the built home-files that
~/.claude/{CLAUDE.md,settings.json,skills,hooks/attention-bell.sh} are
generated, the hook executable.
2026-07-19 07:57:43 -04:00
505002bb2b feat(claude-code): add a Module and enable it on neogaia
Install Claude Code through home-manager's native programs.claude-code
module, matching how the tmux and fish Modules use their home-manager
options rather than a raw home.packages entry. The feature Module at
modules/claude-code/claude-code.nix stays thin — just the enable option
and the delegation — and writes no settings, so login and first-run
configuration remain interactive and no auth material enters the repo.

Signing in without a browser (needed over the console or SSH) is
documented in modules/claude-code/authentication.md: the paste-code OAuth
flow, where the printed URL is opened on another device and the code
pasted back, and the ANTHROPIC_API_KEY path for non-interactive use.

The neogaia toplevel builds with claude-code-2.1.209 included.
2026-07-19 07:57:43 -04:00
6f9309d329 feat(nvim): configure Neovim via nixvim with Nix-managed plugins
Add an nvim Module that configures Neovim declaratively through nixvim,
wired as a flake input and consumed as its home-manager module. Options,
globals, keymaps, and plugin settings are typed Nix; the colorscheme
call and two autocmds live in modules/nvim/config.lua via extraConfigLua.
Plugins come from nixpkgs (no plugin manager, no runtime cloning); git,
ripgrep, and fd are provided from Nix; treesitter grammars are built by
Nix so no runtime compiler is needed.

Functionally matches the previous config (plugins, keymaps, options, the
nord colorscheme, markdown conceal, the Neogit blame toggle), verified
headless against the generated init.
2026-07-19 07:57:43 -04:00
80d1587189 feat(tmux): add a native Module and enable it on neogaia
Configure tmux through home-manager's programs.tmux: the settings it exposes
as options (prefix, keyMode, mouse, baseIndex, clock24, escapeTime,
historyLimit, terminal) are set as options, and every setting it has no option
for is read verbatim from modules/tmux/extra.conf. No tmux plugin manager is
used. The generated config is behaviourally identical to the reference, verified
by parsing it with a live tmux binary.
2026-07-19 07:57:21 -04:00
7e53ecd946 style(fish): split the plugin comment and drop a redundant one 2026-07-19 07:57:21 -04:00
2d6eb929d7 refactor(fish): merge the config fragments back into one config.fish
The interactive init is small; four fragment files was over-splitting it.
Keep the mechanic (a real fish file inlined by Nix into config.fish) but
merge the fragments into a single modules/fish/config.fish read with
builtins.readFile. Rendered config is unchanged.
2026-07-19 07:57:21 -04:00
f54d0460ad refactor(fish): split config.fish into fragments concatenated by Nix
Break the interactive init into concern-scoped fish files under
modules/fish/config/ (bindings, env, done, path) and assemble them with
lib.concatMapStringsSep + readFile into a single interactiveShellInit, so
home-manager still writes one ~/.config/fish/config.fish. The pieces stay
editable as separate fish files; the rendered file is unchanged.
2026-07-19 07:57:21 -04:00
95869fb11f refactor(fish): assemble the interactive init in Nix, not at runtime
Fold the done plugin tuning back into config.fish and drop the conf.d
fragment: config.fish is read whole into interactiveShellInit, so Nix
assembles the interactive init at build time instead of fish autoloading
a separate conf.d file. functions/copy.fish stays a function file, the
idiomatic home for a lazily-autoloaded function.
2026-07-19 07:57:21 -04:00
5a89d4addb refactor(fish): lay the Module out like ~/.config/fish
Move the Module to modules/fish/fish.nix and mirror a real fish config
directory beside it: config.fish (interactive init), conf.d/done.fish
(the done plugin tuning, now a real conf.d file), and functions/copy.fish.
completions/ and themes/ are omitted as they have no content and git
cannot track empty directories.
2026-07-19 07:57:21 -04:00
16f29bd64a refactor(fish): read the non-trivial snippets from files
Move the interactiveShellInit block and the copy function body into
modules/fish/*.fish and pull them in with builtins.readFile, so the
non-trivial fish lives in real fish files (highlighting, fish_indent)
instead of inline Nix strings. The Auto-loader only collects .nix, so
the new .fish files are ignored by it. Rendered config is unchanged.
2026-07-19 07:57:21 -04:00
9eb0fe797a feat(fish): gate the default shell and prefer abbreviations
Move the login-shell switch behind a new modules.fish.defaultShell
option (default false; neogaia opts in). Convert every non-eza alias to
an abbreviation, set preferAbbrs and pin generateCompletions, and switch
the command line to vi-style editing. Cap navigation at four dots and
drop psmem/psmem10/dir/vdir/please. Comment the installed packages.
2026-07-19 07:57:21 -04:00
8346d63e64 feat(fish): add a native Module and set it as neogaia's login shell
Translate the CachyOS fish snapshot into a home-manager Module: the
fastfetch greeting, the bat-backed manpager, the done and bang-bang
plugins (from nixpkgs, not a plugin manager), the history/backup/copy
helper functions, and the eza and navigation aliases. Pacman-specific
aliases are dropped or replaced with NixOS equivalents (update ->
nixos-rebuild switch, cleanup -> nix-collect-garbage). Enabling the
Module also makes fish the user's default login shell.
2026-07-19 07:57:21 -04:00
8f83c3ca8c refactor(neogaia): drop the zram Module and enable zram inline
modules/zram.nix only wrapped the native zramSwap.enable toggle without
adding anything. Rewrite the touched comments to describe only the current
file content, and record the in-file-comment convention in CLAUDE.md.
2026-07-19 07:57:21 -04:00
053c4de529 feat(neogaia): add the CachyOS kernel and hardware enablement
Select the CachyOS kernel per-Host via boot.kernelPackages, enable Intel
microcode and redistributable firmware (ath10k for the QCA6174 wifi), and
move zram behind a toggle Module. Declare the chaotic binary cache in the
base Nix settings (extra-substituters/keys) so the built system fetches the
kernel from nyx-cache rather than compiling it.
2026-07-19 07:57:21 -04:00
9b36cfadd6 feat: build the Skeleton and a minimal neogaia Host
Stand up the walking skeleton the rest of the laptop MVI extends and
re-verifies against: the whole neogaia Host evaluates and its system
toplevel builds (nix flake check green).

- flake.nix: hand-rolled flake (no flake-parts). Base nixos-unstable, plus
  nixpkgs-unstable and nixos-25.05 for the per-package unstable/stable
  overlays, home-manager (nixpkgs followed), and chaotic-nyx (deliberately
  not following our nixpkgs, to keep its binary cache usable). checks build
  each Host toplevel.
- lib/: trimmed helper lib — the Auto-loader (recursive .nix discovery, no
  null-placeholder hack), the host-builder, and the script-from-file helper.
  Deps inherited explicitly; no with lib.my, no nixosModules output.
- system/: shared base config — the unstable/stable overlays, the user
  option (defaults to alexion, in wheel, drives system + home-manager user
  in lockstep), flakes, git, and home-manager as a NixOS module.
- modules/example.nix: Auto-loader / Enable-convention reference Module,
  inert until enabled.
- hosts/neogaia/: minimal laptop Host — placeholder filesystems, bootloader,
  and hardware profile.
- CLAUDE.md: project agent instructions with a Gotchas section (nix on the
  CachyOS dev host, the chaotic overlay/cache behaviour, the Gitea CLI).
2026-07-19 07:57:21 -04:00