Files

332 lines
14 KiB
Nix
Raw Permalink Normal View History

{ config, pkgs, lib, ... }:
let
home = config.home.homeDirectory;
dotfiles = "${home}/.dotfiles";
firstmateHome = "${home}/Documents/firstmate";
in
{
home.username = "laptran";
home.homeDirectory = "/Users/laptran";
home.stateVersion = "24.11";
# home-configuration.nix(5) is generated via options.json that embeds a
# nixpkgs store path without string context. Determinate Nix warns on every
# rebuild. Package man pages are unaffected.
manual.manpages.enable = false;
home.packages = with pkgs; [
ripgrep
fd
fzf
jq
lazygit
neovim
nerd-fonts.hack
ngrok # reverse TCP tunnel so the phone can SSH in over cellular (unfree)
beets # music tagger / library organizer + navidrome sync plugin
];
fonts.fontconfig.enable = true;
home.sessionVariables.EDITOR = "nvim";
2026-07-18 22:54:23 -04:00
programs.zsh = {
enable = true;
autosuggestion.enable = true; # ghost text from history
syntaxHighlighting.enable = true; # commands turn green when valid
initContent = ''
bindkey '^f' autosuggest-accept
'';
shellAliases = {
".." = "cd ..";
ll = "ls -plart";
add = "git add .";
push = "git push";
pull = "git pull";
m = "git switch main";
# High-agency agent launchers (same idea across tools)
2026-07-18 22:54:23 -04:00
cc = "claude --dangerously-skip-permissions";
co = "codex --full-auto";
gb = "grok --yolo";
# firstmate primary session via OpenCode 2 (isolated config; crewmates = OpenCode)
fm = "cd ${firstmateHome} && exec ${home}/.local/bin/oc2";
oc2 = "${home}/.local/bin/oc2";
# Sync ~/Music into the beets library on Unraid (requires NAS mounted).
sync-music = "~/.local/bin/sync-music";
# Start the reverse tunnel so you can SSH into this Mac from your phone.
# Run once when you go remote: `ngrok-tunnel`. Reads authtoken from
# ~/.config/ngrok (set once per machine with `ngrok config add-authtoken <TOK>`)
ngrok-tunnel = "ngrok tcp 22";
2026-07-18 22:54:23 -04:00
};
};
2026-07-18 22:54:23 -04:00
programs.starship = {
enable = true;
settings = {
add_newline = false;
format = "$directory$git_branch$git_status$cmd_duration$line_break$character";
character = {
success_symbol = "[❯](purple)";
error_symbol = "[❯](red)";
};
cmd_duration.format = "[$duration]($style) ";
};
};
# Edit-in-place: real file stays in the repo; live path is an out-of-store symlink.
# force = true: replace a pre-existing regular file once; source of truth is home/.
home.file.".config/wezterm" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/wezterm";
force = true;
};
home.file.".config/nvim" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/nvim";
force = true;
};
home.file.".config/herdr" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/herdr";
force = true;
};
# Beets music library manager - managed by nixpkgs package, config symlinked below.
home.file.".config/beets" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/beets";
force = true;
};
# Claude Code settings (also read by Grok for permissions/compat)
home.file.".claude/settings.json" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.claude/settings.json";
force = true;
};
# Shared agent policy - one file, many harnesses
home.file.".claude/CLAUDE.md" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
force = true;
};
home.file.".codex/AGENTS.md" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
force = true;
};
home.file.".config/opencode/AGENTS.md" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
force = true;
};
home.file.".config/opencode/opencode.json" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/opencode/opencode.json";
force = true;
};
home.file.".config/opencode/skills" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/opencode/skills";
force = true;
};
# OpenCode 2 isolated config - never share ~/.config/opencode with 1.x
home.file.".config/opencode2/opencode.json" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/opencode2/opencode.json";
force = true;
};
home.file.".config/opencode2/AGENTS.md" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
force = true;
};
home.file.".local/bin/oc2" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/bin/oc2";
force = true;
};
home.file.".grok/AGENTS.md" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
force = true;
};
# Grok Build native config
home.file.".grok/config.toml" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.grok/config.toml";
force = true;
};
# Pi agent - source of truth under home/.pi (sessions/auth/npm stay live under ~/.pi)
home.file.".pi/agent/settings.json" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.pi/agent/settings.json";
force = true;
};
home.file.".pi/agent/models.json" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.pi/agent/models.json";
force = true;
};
home.file.".pi/agent/themes" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.pi/agent/themes";
force = true;
};
home.file.".pi/agent/extensions/terminal-status-title.js" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.pi/agent/extensions/terminal-status-title.js";
force = true;
};
home.file.".pi/agent/extensions/calm" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.pi/agent/extensions/calm";
force = true;
};
home.file.".pi/agent/AGENTS.md" = {
source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
force = true;
};
# firstmate is a mutable agent distro (self-update, state/, projects/). Clone once;
# never put it in the Nix store. Seed OpenCode+herdr defaults only when absent.
# Do NOT npm install -g here: activation PATH often resolves Nix's npm, which
# cannot write into the store (EACCES). The firstmate companion CLIs (*-axi)
# are installed by home.activation.axiTools below via Homebrew's node.
home.activation.firstmate = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
fm="${firstmateHome}"
git="${pkgs.git}/bin/git"
if [ ! -d "$fm/.git" ]; then
mkdir -p "$(dirname "$fm")"
$git clone https://github.com/kunchenguid/firstmate.git "$fm"
fi
mkdir -p "$fm/config" "$fm/data" "$fm/state" "$fm/projects"
# Local gitignored operating choices (do not overwrite captain edits)
[ -f "$fm/config/backend" ] || printf 'herdr\n' > "$fm/config/backend"
[ -f "$fm/config/crew-harness" ] || printf 'opencode\n' > "$fm/config/crew-harness"
# Crew dispatch profile - source of truth in dotfiles (reproducible across
# machines). Symlinked into firstmate config so a rebuild restores routing.
# Crew harness and model routing are task-specific in crew-dispatch.json.
ln -sfn "${dotfiles}/home/.config/firstmate/crew-dispatch.json" "$fm/config/crew-dispatch.json"
# Gitea PR helper for local-only mode: after the first mate (opencode)
# reviews a crewmate branch, this pushes it + opens a Gitea PR via tea.
ln -sfn "${dotfiles}/home/bin/fm-gitea-pr.sh" "$HOME/.local/bin/fm-gitea-pr.sh"
'';
# Firstmate companion CLIs (*-axi). None is a Homebrew or Nix formula, so
# install them through Homebrew's npm. tasks-axi is version-gated by firstmate
# (>= 0.2.6 is required before a spawn/teardown automatic backlog transition
# will run), so pin it and reinstall on drift; the rest install when absent.
home.activation.axiTools = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
# Activation PATH lacks /opt/homebrew/bin, so npm's `#!/usr/bin/env node`
# shebang and the installed shims would fail. Prepend only - replacing PATH
# breaks the rest of the HM activation (nix-env, gettext).
export PATH="/opt/homebrew/bin:$PATH"
npm=/opt/homebrew/bin/npm
if [ ! -x "$npm" ]; then
echo "axiTools: /opt/homebrew/bin/npm missing (declare node in homebrew.brews)" >&2
exit 1
fi
# Pin tasks-axi; reinstall only when the installed version differs.
want_tasks_axi="0.2.6"
have_tasks_axi="$(tasks-axi --version 2>/dev/null | head -1 | tr -d '[:space:]')"
if [ "$have_tasks_axi" != "$want_tasks_axi" ]; then
"$npm" install -g "tasks-axi@$want_tasks_axi"
fi
# Companion CLIs without a firstmate-enforced version floor: install once.
for tool in quota-axi gh-axi lavish-axi chrome-devtools-axi; do
[ -x "/opt/homebrew/bin/$tool" ] || "$npm" install -g "$tool"
done
'';
# Authorize the SSH key so the phone can log in through the ngrok tunnel.
# (home-manager 26.05 removed programs.ssh.authorizedKeys; manage the file
# here so ~/.ssh is 0700 and authorized_keys is 0600. Public key is not a secret.)
home.activation.authorizeSSHKey = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
printf '%s\n' "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGCZEGVYMDztSryFwoZ6cfpBH3ksP3h0yxZSanlcbrZ0 unraid-omada" > "$HOME/.ssh/authorized_keys"
chmod 600 "$HOME/.ssh/authorized_keys"
'';
# Wire the sync-music script into ~/.local/bin without touching anything else
# that lives there (node, python3.11, hermes, etc.). Source of truth is the
# dotfiles repo so a rebuild restores it if it ever disappears.
home.activation.syncMusic = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
ln -sfn "${dotfiles}/home/bin/sync-music" "$HOME/.local/bin/sync-music"
'';
# OpenCode 2 is not on Homebrew (beta). Install via Homebrew's npm, never
# Nix's npm (EACCES on the store). Skip when already present so a rebuild
# does not pull a new beta under a live TUI.
home.activation.opencode2 = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
npm=/opt/homebrew/bin/npm
bin=/opt/homebrew/bin/opencode2
if [ -x "$bin" ]; then
exit 0
fi
if [ ! -x "$npm" ]; then
echo "opencode2: /opt/homebrew/bin/npm missing (declare node in homebrew.brews)" >&2
exit 1
fi
"$npm" install -g --allow-scripts=@opencode-ai/cli @opencode-ai/cli@beta
'';
# backpass (kunchenguid/backpass) - gradient descent for agent memory: reads
# the transcript stores of the harnesses this config already declares
# (claude/codex/pi/opencode/grok), proposes evidence-backed edits to AGENTS.md
# and skills, gated by `backpass apply`. Every model call goes through `acpx`
# to a harness you already authenticated, so acpx is a hard runtime dep and
# must be on PATH. Homebrew node v26 satisfies backpass (>= 22.5) and acpx
# (>= 22.13). Neither is a Homebrew formula: install via Homebrew's npm,
# never Nix's npm (EACCES on the store). Skip-if-present like opencode2 so a
# rebuild never pulls a new version mid-session; refresh manually with
# /opt/homebrew/bin/npm update -g backpass acpx
home.activation.backpass = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
# Activation PATH lacks /opt/homebrew/bin, so npm's `#!/usr/bin/env node`
# shebang fails with "env: node: No such file or directory". Prepend only -
# replacing PATH breaks the rest of the HM activation (nix-env, gettext).
export PATH="/opt/homebrew/bin:$PATH"
npm=/opt/homebrew/bin/npm
if [ ! -x "$npm" ]; then
echo "backpass: /opt/homebrew/bin/npm missing (declare node in homebrew.brews)" >&2
exit 1
fi
if [ ! -x /opt/homebrew/bin/acpx ]; then
"$npm" install -g acpx@latest
fi
if [ ! -x /opt/homebrew/bin/backpass ]; then
"$npm" install -g backpass
fi
'';
# kunchenguid/no-mistakes gate (git push no-mistakes) - declarative install + daemon + global config
# Uses OpenCode only for pipeline steps (the opencode-go subscription), by the
# captain's decision. Binary lives in ~/.no-mistakes/bin
# with symlink ~/.local/bin/no-mistakes (installer default). Do NOT npm install -g no-mistakes
# - that npm name is jonathanong's static-analysis tool (shadowing bug fixed 2026-09-21).
home.activation.noMistakes = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
bin="$HOME/.no-mistakes/bin/no-mistakes"
link="$HOME/.local/bin/no-mistakes"
# Install/refresh via upstream installer if missing or not kunchenguid build
if [ ! -x "$bin" ] || ! "$bin" --version 2>/dev/null | grep -q "kunchenguid\|v1\."; then
if [ -x "$bin" ]; then
echo "no-mistakes: replacing unexpected binary at $bin" >&2
fi
curl -fsSL https://raw.githubusercontent.com/kunchenguid/no-mistakes/main/docs/install.sh | sh
fi
# Ensure global config selects OpenCode (idempotent - only writes if missing or still `agent: auto`)
cfg="$HOME/.no-mistakes/config.yaml"
if [ -f "$cfg" ] && grep -qE '^\s*agent:\s*auto\s*$' "$cfg"; then
tmp="$(mktemp)"
awk '
/^\s*agent:\s*auto\s*$/ {
print "# Managed by dotfiles/home.nix home.activation.noMistakes";
print "agent: [opencode]";
next
}
{ print }
' "$cfg" > "$tmp" && mv "$tmp" "$cfg"
fi
if [ ! -f "$cfg" ]; then
mkdir -p "$(dirname "$cfg")"
printf 'agent: [opencode]\n' > "$cfg"
fi
# Ensure daemon running (launchd on macOS)
"$bin" daemon restart >/dev/null 2>&1 || "$bin" daemon start >/dev/null 2>&1 || true
'';
}