The upstream gastown Go dependencies changed, causing a hash mismatch in the fixed-output derivation. Updated vendorHash in both: - packages/gastown/default.nix (flake packages output) - home/roles/development/default.nix (home-manager inline build)
304 lines
12 KiB
Nix
304 lines
12 KiB
Nix
{ config, lib, pkgs, globalInputs, system, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.home.roles.development;
|
|
|
|
# Build beads from flake input
|
|
beadsRev = builtins.substring 0 8 (globalInputs.beads.rev or "unknown");
|
|
|
|
beadsPackage = pkgs.unstable.buildGoModule {
|
|
pname = "beads";
|
|
version = "0.52.0-${beadsRev}";
|
|
src = globalInputs.beads;
|
|
subPackages = [ "cmd/bd" ];
|
|
doCheck = false;
|
|
vendorHash = "sha256-OL6QGf4xSMpEbmU+41pFdO0Rrs3H162T3pdiW9UfWR0=";
|
|
nativeBuildInputs = [ pkgs.git pkgs.pkg-config ];
|
|
buildInputs = [ pkgs.icu ];
|
|
meta = with lib; {
|
|
description = "beads (bd) - An issue tracker designed for AI-supervised coding workflows";
|
|
homepage = "https://github.com/steveyegge/beads";
|
|
license = licenses.mit;
|
|
mainProgram = "bd";
|
|
};
|
|
};
|
|
|
|
# Gastown - multi-agent workspace manager
|
|
# Source is tracked via flake input for renovate updates
|
|
gastownRev = builtins.substring 0 8 (globalInputs.gastown.rev or "unknown");
|
|
gastownPackage = pkgs.buildGoModule {
|
|
pname = "gastown";
|
|
version = "unstable-${gastownRev}";
|
|
src = globalInputs.gastown;
|
|
vendorHash = "sha256-/+ODyndArUF0nJY9r8G5JKhzQckBHFb48A7EBZmoIr0=";
|
|
subPackages = [ "cmd/gt" ];
|
|
doCheck = false;
|
|
|
|
# Must match ldflags from gastown Makefile - BuiltProperly=1 is required
|
|
# or gt will error with "This binary was built with 'go build' directly"
|
|
ldflags = [
|
|
"-X github.com/steveyegge/gastown/internal/cmd.Version=${gastownRev}"
|
|
"-X github.com/steveyegge/gastown/internal/cmd.Commit=${gastownRev}"
|
|
"-X github.com/steveyegge/gastown/internal/cmd.BuildTime=nix-build"
|
|
"-X github.com/steveyegge/gastown/internal/cmd.BuiltProperly=1"
|
|
];
|
|
|
|
# Bug fixes not yet merged upstream
|
|
# Each patch is stored in a separate file for clarity and maintainability
|
|
patches = [
|
|
# Fix validateRecipient bug: normalize addresses before comparison
|
|
./gastown-fix-validate-recipient.patch
|
|
# Fix agentBeadToAddress to use title field for hq- prefixed beads
|
|
./gastown-fix-agent-bead-address-title.patch
|
|
# town-root-detection fix merged upstream (detectRole removed)
|
|
# Statusline optimization: skip expensive beads queries for detached sessions
|
|
# Reduces Dolt CPU from ~70% to ~20% by caching and early-exit
|
|
./gastown-statusline-optimization.patch
|
|
];
|
|
|
|
meta = with lib; {
|
|
description = "Gas Town - multi-agent workspace manager by Steve Yegge";
|
|
homepage = "https://github.com/steveyegge/gastown";
|
|
license = licenses.mit;
|
|
mainProgram = "gt";
|
|
};
|
|
};
|
|
|
|
# Perles - TUI for beads issue tracking (no upstream flake.nix yet)
|
|
# Source is tracked via flake input for renovate updates
|
|
perlesRev = builtins.substring 0 8 (globalInputs.perles.rev or "unknown");
|
|
perlesPackage = pkgs.unstable.buildGoModule {
|
|
pname = "perles";
|
|
version = "unstable-${perlesRev}";
|
|
src = globalInputs.perles;
|
|
vendorHash = "sha256-A5LE9Cor/DRcJtVpiScSoqDYhJIKyaq0cbK+OGmr4XU=";
|
|
doCheck = false;
|
|
|
|
ldflags = [
|
|
"-X main.version=${perlesRev}"
|
|
];
|
|
|
|
meta = with lib; {
|
|
description = "Perles - Terminal UI for beads issue tracking";
|
|
homepage = "https://github.com/zjrosen/perles";
|
|
license = licenses.mit;
|
|
mainProgram = "perles";
|
|
};
|
|
};
|
|
|
|
# Fetch the claude-plugins repository (for humanlayer commands/agents)
|
|
# Update the rev to get newer versions of the commands
|
|
claudePluginsRepo = builtins.fetchGit {
|
|
url = "https://github.com/jeffh/claude-plugins.git";
|
|
# To update: change this to the latest commit hash
|
|
# You can find the latest commit at: https://github.com/jeffh/claude-plugins/commits/main
|
|
rev = "5e3e4d937162185b6d78c62022cbfd1c8ad42c4c";
|
|
ref = "main";
|
|
};
|
|
|
|
# Claude Code statusline: shows model, cwd, git branch, and context usage %
|
|
claudeCodeStatusLineConfig = pkgs.writeText "claude-statusline.json" (builtins.toJSON {
|
|
type = "command";
|
|
command = ''input=$(cat); model=$(echo "$input" | jq -r '.model.display_name'); cwd=$(echo "$input" | jq -r '.workspace.current_dir'); if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then branch=$(git -C "$cwd" --no-optional-locks rev-parse --abbrev-ref HEAD 2>/dev/null || echo ""); if [ -n "$branch" ]; then git_info=" on $branch"; else git_info=""; fi; else git_info=""; fi; usage=$(echo "$input" | jq '.context_window.current_usage'); if [ "$usage" != "null" ]; then current=$(echo "$usage" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens'); size=$(echo "$input" | jq '.context_window.context_window_size'); pct=$((current * 100 / size)); context_info=" | ''${pct}% context"; else context_info=""; fi; printf "%s in %s%s%s" "$model" "$cwd" "$git_info" "$context_info"'';
|
|
});
|
|
|
|
in
|
|
{
|
|
options.home.roles.development = {
|
|
enable = mkEnableOption "Enable development tools and utilities";
|
|
|
|
allowArbitraryClaudeCodeModelSelection = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to preserve model specifications in Claude Code humanlayer commands and agents.
|
|
|
|
When false (default), the model: line is stripped from frontmatter, allowing Claude Code
|
|
to use its default model selection.
|
|
|
|
When true, the model: specifications from the source files are preserved, allowing
|
|
commands to specify opus/sonnet/haiku explicitly.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
home.packages = [
|
|
beadsPackage
|
|
gastownPackage
|
|
perlesPackage
|
|
pkgs.unstable.claude-code
|
|
pkgs.unstable.claude-code-router
|
|
pkgs.unstable.codex
|
|
pkgs.dolt
|
|
pkgs.sqlite
|
|
|
|
# Custom packages
|
|
pkgs.custom.tea-rbw
|
|
pkgs.custom.pi-coding-agent
|
|
];
|
|
|
|
# Install Claude Code humanlayer command and agent plugins
|
|
home.activation.claudeCodeCommands = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
|
# Clean up old plugin-installed commands and agents to avoid duplicates
|
|
rm -f ~/.claude/commands/humanlayer:* 2>/dev/null || true
|
|
rm -f ~/.claude/agents/humanlayer:* 2>/dev/null || true
|
|
|
|
# Create directories if they don't exist
|
|
mkdir -p ~/.claude/commands
|
|
mkdir -p ~/.claude/agents
|
|
|
|
# Copy all humanlayer command files and remove model specifications
|
|
for file in ${claudePluginsRepo}/humanlayer/commands/*.md; do
|
|
if [ -f "$file" ]; then
|
|
filename=$(basename "$file" .md)
|
|
dest="$HOME/.claude/commands/humanlayer:''${filename}.md"
|
|
rm -f "$dest" 2>/dev/null || true
|
|
|
|
# Copy file and conditionally remove the "model:" line from frontmatter
|
|
${if cfg.allowArbitraryClaudeCodeModelSelection
|
|
then "cp \"$file\" \"$dest\""
|
|
else "${pkgs.gnused}/bin/sed '/^model:/d' \"$file\" > \"$dest\""
|
|
}
|
|
chmod u+w "$dest" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Copy all humanlayer agent files and remove model specifications
|
|
for file in ${claudePluginsRepo}/humanlayer/agents/*.md; do
|
|
if [ -f "$file" ]; then
|
|
filename=$(basename "$file" .md)
|
|
dest="$HOME/.claude/agents/humanlayer:''${filename}.md"
|
|
rm -f "$dest" 2>/dev/null || true
|
|
|
|
# Copy file and conditionally remove the "model:" line from frontmatter
|
|
${if cfg.allowArbitraryClaudeCodeModelSelection
|
|
then "cp \"$file\" \"$dest\""
|
|
else "${pkgs.gnused}/bin/sed '/^model:/d' \"$file\" > \"$dest\""
|
|
}
|
|
chmod u+w "$dest" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Copy local commands from this repo (with retry for race conditions with running Claude)
|
|
for file in ${./commands}/*.md; do
|
|
if [ -f "$file" ]; then
|
|
filename=$(basename "$file" .md)
|
|
dest="$HOME/.claude/commands/''${filename}.md"
|
|
# Remove existing file first, then copy with retry on failure
|
|
rm -f "$dest" 2>/dev/null || true
|
|
if ! cp "$file" "$dest" 2>/dev/null; then
|
|
sleep 0.5
|
|
cp "$file" "$dest" || echo "Warning: Failed to copy $filename.md to commands"
|
|
fi
|
|
chmod u+w "$dest" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Copy local skills (reference materials) to skills subdirectory
|
|
mkdir -p ~/.claude/commands/skills
|
|
for file in ${./skills}/*.md; do
|
|
if [ -f "$file" ]; then
|
|
filename=$(basename "$file" .md)
|
|
dest="$HOME/.claude/commands/skills/''${filename}.md"
|
|
rm -f "$dest" 2>/dev/null || true
|
|
if ! cp "$file" "$dest" 2>/dev/null; then
|
|
sleep 0.5
|
|
cp "$file" "$dest" || echo "Warning: Failed to copy $filename.md to skills"
|
|
fi
|
|
chmod u+w "$dest" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Copy micro-skills (compact reusable knowledge referenced by formulas)
|
|
for file in ${./skills/micro}/*.md; do
|
|
if [ -f "$file" ]; then
|
|
dest="$HOME/.claude/commands/skills/$(basename "$file")"
|
|
rm -f "$dest" 2>/dev/null || true
|
|
cp "$file" "$dest"
|
|
chmod u+w "$dest" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Install beads formulas to user-level formula directory
|
|
mkdir -p ~/.beads/formulas
|
|
for file in ${./formulas}/*.formula.toml; do
|
|
if [ -f "$file" ]; then
|
|
dest="$HOME/.beads/formulas/$(basename "$file")"
|
|
rm -f "$dest" 2>/dev/null || true
|
|
cp "$file" "$dest"
|
|
chmod u+w "$dest" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
$DRY_RUN_CMD echo "Claude Code plugins installed: humanlayer commands/agents + local commands + local skills + formulas"
|
|
'';
|
|
|
|
# Set up beads Claude Code integration (hooks for SessionStart/PreCompact)
|
|
# This uses the CLI + hooks approach which is recommended over MCP for Claude Code
|
|
home.activation.claudeCodeBeadsSetup = lib.hm.dag.entryAfter ["writeBoundary" "claudeCodeCommands"] ''
|
|
# Run bd setup claude to install hooks into ~/.claude/settings.json
|
|
# This is idempotent - safe to run multiple times
|
|
${beadsPackage}/bin/bd setup claude 2>/dev/null || true
|
|
|
|
$DRY_RUN_CMD echo "Claude Code beads integration configured (hooks installed)"
|
|
'';
|
|
|
|
# Configure Claude Code statusline (merge into existing settings.json)
|
|
home.activation.claudeCodeStatusLine = lib.hm.dag.entryAfter ["writeBoundary" "claudeCodeBeadsSetup"] ''
|
|
SETTINGS="$HOME/.claude/settings.json"
|
|
mkdir -p "$HOME/.claude"
|
|
if [ -f "$SETTINGS" ]; then
|
|
${pkgs.jq}/bin/jq --slurpfile sl ${claudeCodeStatusLineConfig} '.statusLine = $sl[0]' "$SETTINGS" > "''${SETTINGS}.tmp" && mv "''${SETTINGS}.tmp" "$SETTINGS"
|
|
else
|
|
${pkgs.jq}/bin/jq -n --slurpfile sl ${claudeCodeStatusLineConfig} '{statusLine: $sl[0]}' > "$SETTINGS"
|
|
fi
|
|
$DRY_RUN_CMD echo "Claude Code statusline configured"
|
|
'';
|
|
|
|
# Beads timer gate checker (Linux only - uses systemd)
|
|
# Runs every 5 minutes to auto-resolve expired timer gates across all beads projects
|
|
# This enables self-scheduling molecules (watchers, patrols, etc.)
|
|
systemd.user.services.beads-gate-check = lib.mkIf pkgs.stdenv.isLinux {
|
|
Unit = {
|
|
Description = "Check and resolve expired beads timer gates";
|
|
};
|
|
Service = {
|
|
Type = "oneshot";
|
|
# Check gates in all workspaces that have running daemons
|
|
ExecStart = pkgs.writeShellScript "beads-gate-check-all" ''
|
|
# Get list of workspaces from daemon registry
|
|
workspaces=$(${beadsPackage}/bin/bd daemon list --json 2>/dev/null | ${pkgs.jq}/bin/jq -r '.[].workspace // empty' 2>/dev/null)
|
|
|
|
if [ -z "$workspaces" ]; then
|
|
exit 0 # No beads workspaces, nothing to do
|
|
fi
|
|
|
|
for ws in $workspaces; do
|
|
if [ -d "$ws" ]; then
|
|
cd "$ws" && ${beadsPackage}/bin/bd gate check --type=timer --quiet 2>/dev/null || true
|
|
fi
|
|
done
|
|
'';
|
|
};
|
|
};
|
|
|
|
systemd.user.timers.beads-gate-check = lib.mkIf pkgs.stdenv.isLinux {
|
|
Unit = {
|
|
Description = "Periodic beads timer gate check";
|
|
};
|
|
Timer = {
|
|
OnBootSec = "5min";
|
|
OnUnitActiveSec = "5min";
|
|
};
|
|
Install = {
|
|
WantedBy = [ "timers.target" ];
|
|
};
|
|
};
|
|
|
|
# Note: modules must be imported at top-level home config
|
|
};
|
|
}
|