Files
nixos-configs/hosts/pinwheel/modules/ai/default.nix
T
Alexander Heldt cd807b550f pinwheel: Add ct for side threads in Claude Code sessions
Install ct from its flake, with its config, a tmux menu on prefix T and a user service for the web UI. Also keep Claude Code transcripts for ten years instead of 30 days, since threads are meant to be revisited.
2026-10-27 14:49:43 +01:00

122 lines
3.4 KiB
Nix

{
inputs,
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.ai.enable;
ct = inputs.ct.packages.${pkgs.stdenv.hostPlatform.system}.default;
ctConfig = builtins.toJSON {
addr = "127.0.0.1:7777";
open = "split";
defaultProfile = "personal";
profiles = {
work = {
configDir = "/home/alex/.claude-work";
tmuxSession = "w";
paths = [ "/home/alex/code/work" ];
};
personal = {
configDir = "/home/alex/.claude-personal";
tmuxSession = "p";
};
};
};
# Shared statusline: show the logged-in account email.
statusLine = {
type = "command";
command = "jq -r '.oauthAccount.emailAddress' \"$CLAUDE_CONFIG_DIR/.claude.json\"";
};
# PostToolUse hook: echoes a JSON payload that injects a comment-policy
# reminder as additional context after every Write/Edit.
commentPolicyPayload = builtins.toJSON {
suppressOutput = true;
hookSpecificOutput = {
hookEventName = "PostToolUse";
additionalContext = "Comment policy: default to ZERO code comments. Review what you just wrote and delete any comment that narrates the change, restates the code, states the obvious, is tangential, or explains design/why rationale -- put that reasoning in your chat reply instead, never in the code. Never use em dashes or en dashes anywhere in file content; use a plain ASCII hyphen or reword.";
};
};
settings = builtins.toJSON {
inherit statusLine;
theme = "dark";
editorMode = "vim";
cleanupPeriodDays = 3650;
attribution = {
commit = "";
pr = "";
};
hooks = {
PostToolUse = [
{
matcher = "Write|Edit";
hooks = [
{
type = "command";
command = "echo ${lib.escapeShellArg commentPolicyPayload}";
}
];
}
];
};
};
in
{
options = {
mod.ai = {
enable = lib.mkEnableOption "enable ai module";
};
};
config = lib.mkIf enabled {
home-manager.users.alex = {
home.packages = [
pkgs.claude-code
ct
];
programs.zsh.shellAliases = {
claude = "pclaude";
wclaude = "CLAUDE_CONFIG_DIR=$HOME/.claude-work command claude";
pclaude = "CLAUDE_CONFIG_DIR=$HOME/.claude-personal command claude";
};
home.file.".claude-work/settings.json".text = settings;
home.file.".claude-personal/settings.json".text = settings;
xdg.configFile."ct/config.json".text = ctConfig;
programs.tmux.extraConfig = lib.mkAfter ''
bind-key T display-menu -T "ct" \
"New thread (full context)" t 'display-popup -EE -w 80 -h 10 -T " new thread " "ct thread --pane #{pane_id}"' \
"New thread (pick turns)" p 'display-popup -EE -w 90% -h 90% -T " pick turns " "ct thread --pick --pane #{pane_id}"' \
"" \
"Browse threads" b 'display-popup -EE -w 90% -h 90% -T " threads " "ct --pane #{pane_id}"' \
"Open in browser" w 'run-shell -b "ct web --pane #{pane_id}"'
'';
systemd.user.services.ct-web = {
Unit = {
Description = "ct web UI";
};
Service = {
Type = "simple";
ExecStart = "${lib.getExe ct} serve";
Environment = "TMUX_TMPDIR=%t";
Restart = "on-failure";
RestartSec = 5;
};
Install = {
WantedBy = [ "default.target" ];
};
};
};
};
}