Fix Claude Code Home Manager config

Generate Claude settings from Nix so managed commands use the correct home directory, add declarative keybindings for Shift+Enter newline support, and replace the broken inline docs hook with a portable script.

Also stop exporting CLAUDE_CONFIG_DIR so Claude no longer splits managed config between ~/.claude and ~/.config/claude, and fix the status line script shebang for NixOS.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Harivansh Rathi 2026-03-31 07:15:36 +00:00
parent b93511b85c
commit 29f9020c2f
6 changed files with 99 additions and 33 deletions

View file

@ -0,0 +1,12 @@
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"$docs": "https://code.claude.com/docs/en/keybindings",
"bindings": [
{
"context": "Chat",
"bindings": {
"shift+enter": "chat:newline"
}
}
]
}

View file

@ -1,27 +0,0 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
},
"permissions": {
"defaultMode": "bypassPermissions"
},
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "if [[ $(jq -r .tool_input.file_path 2>/dev/null) == */Users/rathi/Documents/GitHub/claude-code-docs/* ]]; then LAST_PULL=\"/Users/rathi/Documents/GitHub/claude-code-docs/.last_pull\" && NOW=$(date +%s) && GITHUB_TS=$(jq -r .last_updated \"/Users/rathi/Documents/GitHub/claude-code-docs/docs/docs_manifest.json\" 2>/dev/null | cut -d. -f1) && GITHUB_UNIX=$(date -j -u -f \"%Y-%m-%dT%H:%M:%S\" \"$GITHUB_TS\" \"+%s\" 2>/dev/null || echo 0) && if [[ -f \"$LAST_PULL\" ]]; then LAST=$(cat \"$LAST_PULL\"); if [[ $GITHUB_UNIX -gt $LAST ]]; then echo \"🔄 Updating docs to latest version...\" >&2 && (cd /Users/rathi/Documents/GitHub/claude-code-docs && git pull --quiet) && echo $NOW > \"$LAST_PULL\"; fi; else echo \"🔄 Syncing docs for the first time...\" >&2 && (cd /Users/rathi/Documents/GitHub/claude-code-docs && git pull --quiet) && echo $NOW > \"$LAST_PULL\"; fi; fi"
}
]
}
]
},
"statusLine": {
"type": "command",
"command": "/Users/rathi/.claude/statusline.sh"
},
"voiceEnabled": true
}

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Full-featured status line with context window usage
# Usage: Copy to ~/.claude/statusline.sh and make executable
#

View file

@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
docs_dir="${HOME}/Documents/GitHub/claude-code-docs"
manifest="${docs_dir}/docs/docs_manifest.json"
last_pull="${docs_dir}/.last_pull"
file_path="$(jq -r '.tool_input.file_path // empty' 2>/dev/null || true)"
case "${file_path}" in
"${docs_dir}"/*) ;;
*) exit 0 ;;
esac
if [[ ! -d "${docs_dir}/.git" || ! -f "${manifest}" ]]; then
exit 0
fi
github_unix="$(
python3 - "${manifest}" <<'PY'
import datetime
import json
import sys
manifest_path = sys.argv[1]
try:
with open(manifest_path, encoding="utf-8") as handle:
last_updated = json.load(handle).get("last_updated", "")
last_updated = last_updated.split(".", 1)[0]
parsed = datetime.datetime.strptime(last_updated, "%Y-%m-%dT%H:%M:%S")
print(int(parsed.replace(tzinfo=datetime.timezone.utc).timestamp()))
except Exception:
print(0)
PY
)"
last_synced=0
if [[ -f "${last_pull}" ]]; then
last_synced="$(cat "${last_pull}" 2>/dev/null || echo 0)"
fi
if [[ "${github_unix}" -le "${last_synced}" ]]; then
exit 0
fi
printf 'Syncing Claude docs to latest version...\n' >&2
git -C "${docs_dir}" pull --quiet
date +%s > "${last_pull}"

View file

@ -1,24 +1,60 @@
{
config,
inputs,
pkgs,
...
}:
let
claudePackage = inputs.claudeCode.packages.${pkgs.stdenv.hostPlatform.system}.default;
jsonFormat = pkgs.formats.json { };
claudeSettings = jsonFormat.generate "claude-settings.json" {
"$schema" = "https://json.schemastore.org/claude-code-settings.json";
env = {
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
};
permissions = {
defaultMode = "bypassPermissions";
};
hooks = {
PreToolUse = [
{
matcher = "Read";
hooks = [
{
type = "command";
command = "${config.home.homeDirectory}/.claude/sync-docs.sh";
}
];
}
];
};
statusLine = {
type = "command";
command = "${config.home.homeDirectory}/.claude/statusline.sh";
};
voiceEnabled = true;
};
in
{
home.file.".local/bin/claude".source = "${claudePackage}/bin/claude";
# Claude Code still resolves user settings from ~/.claude rather than XDG.
# Claude Code stores shared config, commands, plugins, and skills in ~/.claude.
# Global UI settings changed through /config still live in ~/.claude.json and are
# intentionally left user-managed because Claude mutates that file directly.
home.file.".claude/CLAUDE.md".source = ../config/claude/CLAUDE.md;
home.file.".claude/commands" = {
source = ../config/claude/commands;
recursive = true;
};
home.file.".claude/settings.json".source = ../config/claude/settings.json;
home.file.".claude/settings.json".source = claudeSettings;
home.file.".claude/settings.local.json".source = ../config/claude/settings.local.json;
home.file.".claude/keybindings.json".source = ../config/claude/keybindings.json;
home.file.".claude/statusline.sh" = {
source = ../config/claude/statusline.sh;
executable = true;
};
home.file.".claude/sync-docs.sh" = {
source = ../config/claude/sync-docs.sh;
executable = true;
};
}

View file

@ -40,9 +40,6 @@ in
AWS_SHARED_CREDENTIALS_FILE = "${config.xdg.configHome}/aws/credentials";
AWS_CONFIG_FILE = "${config.xdg.configHome}/aws/config";
})
(lib.mkIf (f.claude or false) {
CLAUDE_CONFIG_DIR = "${config.xdg.configHome}/claude";
})
{
PSQL_HISTORY = "${config.xdg.stateHome}/psql_history";
SQLITE_HISTORY = "${config.xdg.stateHome}/sqlite_history";