diff --git a/.gitignore b/.gitignore index 2c25b15..136e23d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ flake.lock.bak .sidecar-start.sh .sidecar-base .td-root +.pi-lens +.cache diff --git a/.pi-lens/cache/jscpd.json b/.pi-lens/cache/jscpd.json deleted file mode 100644 index ee25c61..0000000 --- a/.pi-lens/cache/jscpd.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "success": true, - "clones": [], - "duplicatedLines": 0, - "totalLines": 0, - "percentage": 0 -} \ No newline at end of file diff --git a/.pi-lens/cache/jscpd.meta.json b/.pi-lens/cache/jscpd.meta.json deleted file mode 100644 index 666cf25..0000000 --- a/.pi-lens/cache/jscpd.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-15T09:30:34.459Z" -} \ No newline at end of file diff --git a/.pi-lens/cache/knip.json b/.pi-lens/cache/knip.json deleted file mode 100644 index a4147c6..0000000 --- a/.pi-lens/cache/knip.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "success": false, - "issues": [], - "unusedExports": [], - "unusedFiles": [], - "unusedDeps": [], - "unlistedDeps": [], - "summary": "Failed to parse output" -} \ No newline at end of file diff --git a/.pi-lens/cache/knip.meta.json b/.pi-lens/cache/knip.meta.json deleted file mode 100644 index d80ceea..0000000 --- a/.pi-lens/cache/knip.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-15T09:30:35.667Z" -} \ No newline at end of file diff --git a/.pi-lens/cache/session-start-guidance.json b/.pi-lens/cache/session-start-guidance.json deleted file mode 100644 index ec747fa..0000000 --- a/.pi-lens/cache/session-start-guidance.json +++ /dev/null @@ -1 +0,0 @@ -null \ No newline at end of file diff --git a/.pi-lens/cache/session-start-guidance.meta.json b/.pi-lens/cache/session-start-guidance.meta.json deleted file mode 100644 index 42b8956..0000000 --- a/.pi-lens/cache/session-start-guidance.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-15T09:28:51.987Z" -} \ No newline at end of file diff --git a/.pi-lens/cache/todo-baseline.json b/.pi-lens/cache/todo-baseline.json deleted file mode 100644 index fc69ce2..0000000 --- a/.pi-lens/cache/todo-baseline.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "items": [] -} \ No newline at end of file diff --git a/.pi-lens/cache/todo-baseline.meta.json b/.pi-lens/cache/todo-baseline.meta.json deleted file mode 100644 index f2588fa..0000000 --- a/.pi-lens/cache/todo-baseline.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-15T09:28:16.965Z" -} \ No newline at end of file diff --git a/.pi-lens/turn-state.json b/.pi-lens/turn-state.json deleted file mode 100644 index 3e1b99d..0000000 --- a/.pi-lens/turn-state.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "files": {}, - "turnCycles": 0, - "maxCycles": 3, - "lastUpdated": "2026-04-15T09:30:35.668Z" -} \ No newline at end of file diff --git a/modules/home-manager/coding/agents/claude-code.nix b/modules/home-manager/coding/agents/claude-code.nix index 7bfee57..7a5d402 100644 --- a/modules/home-manager/coding/agents/claude-code.nix +++ b/modules/home-manager/coding/agents/claude-code.nix @@ -76,7 +76,10 @@ in { mcpServers = mkOption { type = types.attrsOf types.anything; - default = if mcpCfg != null then mcpCfg.servers else {}; + default = + if mcpCfg != null + then mcpCfg.servers + else {}; defaultText = literalExpression "config.programs.mcp.servers"; description = '' MCP server configurations for Claude Code. diff --git a/modules/nixos/pi-agent-runner.nix b/modules/nixos/pi-agent-runner.nix index d8ccec4..9e98cff 100644 --- a/modules/nixos/pi-agent-runner.nix +++ b/modules/nixos/pi-agent-runner.nix @@ -60,6 +60,19 @@ in cwd="$1" shift + # Parse forwarded environment variables from wrapper (KEY=VALUE) + while [ "$#" -gt 0 ]; do + case "$1" in + TERM=*|LANG=*|LC_ALL=*|LC_CTYPE=*|COLORTERM=*|TERM_PROGRAM=*) + export "$1" + shift + ;; + *) + break + ;; + esac + done + resolve_user_policy() { local user="$1" USER_CONFIG_PATH="" @@ -384,5 +397,34 @@ in ${concatStringsSep "\n" (map (arg: ''cmd+=( ${escapeShellArg arg} )'') cfg.wrapper.extraRunArgs)} cmd+=( "$@" ) - exec "''${cmd[@]}" + # Reset terminal keyboard protocol modes that pi's TUI may have enabled. + # If pi crashes or is killed (OOM, SIGKILL, etc.), its cleanup handler + # never runs and the host terminal stays in Kitty keyboard protocol or + # xterm modifyOtherKeys mode. This causes all keystrokes to appear as + # raw escape sequences like ^[[99;5u (ctrl+c in CSI-u encoding). + # + # Try /dev/tty first (controlling terminal), fall back to stdout + # (connected through sudo to the user's Ghostty terminal). + cleanup_terminal() { + local output_dev="" + if [ -w /dev/tty ]; then + output_dev=/dev/tty + elif [ -w /dev/stdout ]; then + output_dev=/dev/stdout + fi + if [ -n "$output_dev" ]; then + # Disable Kitty keyboard protocol (pop all flags) + printf '\033[ "$output_dev" 2>/dev/null || true + # Disable xterm modifyOtherKeys + printf '\033[>4;0m' > "$output_dev" 2>/dev/null || true + # Disable bracketed paste mode + printf '\033[?2004l' > "$output_dev" 2>/dev/null || true + # Restore cursor visibility + printf '\033[?25h' > "$output_dev" 2>/dev/null || true + fi + } + trap cleanup_terminal EXIT + + # Run without exec so the EXIT trap fires after pi exits (normal or crash). + "''${cmd[@]}" '' diff --git a/modules/nixos/pi-agent-wrapper.nix b/modules/nixos/pi-agent-wrapper.nix index e276432..51eb22d 100644 --- a/modules/nixos/pi-agent-wrapper.nix +++ b/modules/nixos/pi-agent-wrapper.nix @@ -97,6 +97,6 @@ with lib; exec /run/wrappers/bin/sudo --non-interactive \ ${runner}/bin/${cfg.wrapper.runnerName} \ "$user_name" "$cwd_real" \ - "TERM=$TERM" "LANG=$LANG" "LC_ALL=''${LC_ALL:-}" "LC_CTYPE=''${LC_CTYPE:-}" "COLORTERM=''${COLORTERM:-}" \ + "TERM=$TERM" "LANG=$LANG" "LC_ALL=''${LC_ALL:-}" "LC_CTYPE=''${LC_CTYPE:-}" "COLORTERM=''${COLORTERM:-}" "TERM_PROGRAM=''${TERM_PROGRAM:-}" \ "$@" '' diff --git a/overlays/default.nix b/overlays/default.nix index b0ad210..574659c 100644 --- a/overlays/default.nix +++ b/overlays/default.nix @@ -1,7 +1,6 @@ {inputs, ...}: { # This one brings our custom packages from the 'pkgs' directory - additions = final: prev: - (import ../pkgs {pkgs = final;}); + additions = final: prev: (import ../pkgs {pkgs = final;}); # This one contains whatever you want to overlay # You can change versions, add patches, set compilation flags, anything really. diff --git a/pkgs/opencode-desktop/default.nix b/pkgs/opencode-desktop/default.nix index 06b53c7..97d98d2 100644 --- a/pkgs/opencode-desktop/default.nix +++ b/pkgs/opencode-desktop/default.nix @@ -31,7 +31,7 @@ # Upstream is missing outputHashes for git dependencies # Also fix stale npm deps hash in upstream node_modules FOD fixedNodeModules = opencode.node_modules.overrideAttrs { - outputHash = "sha256-LRhPPrOKCGUSCEWTpAxPdWKTKVNkg82WrvD25cP3jts="; + outputHash = "sha256-285KZ7rZLRoc6XqCZRHc25NE+mmpGh/BVeMpv8aPQtQ="; }; opencode-desktop = rustPlatform.buildRustPackage (finalAttrs: { diff --git a/tests/lib/agents-test.nix b/tests/lib/agents-test.nix index 6e3b3c0..9fe2bed 100644 --- a/tests/lib/agents-test.nix +++ b/tests/lib/agents-test.nix @@ -12,17 +12,14 @@ let } ); in - assert result.success == false; - {result = "pass";}; + assert result.success == false; {result = "pass";}; # Test 2: loadCanonical extracts loadAgents from input testLoadCanonical = let fakeInput = {lib.loadAgents = {test = {description = "test";};};}; result = agentsLib.loadCanonical {agentsInput = fakeInput;}; in - assert result == {test = {description = "test";};}; - {result = "pass";}; - + assert result == {test = {description = "test";};}; {result = "pass";}; in { unknown-tool-throws = testUnknownTool; load-canonical = testLoadCanonical; diff --git a/tests/lib/coding-rules-test.nix b/tests/lib/coding-rules-test.nix index 433b270..e37c804 100644 --- a/tests/lib/coding-rules-test.nix +++ b/tests/lib/coding-rules-test.nix @@ -11,11 +11,11 @@ let rulesDir = ".coding-rules"; }; in - assert rules.instructions == [ + assert rules.instructions + == [ ".coding-rules/concerns/naming.md" ".coding-rules/languages/python.md" - ]; - {result = "pass";}; + ]; {result = "pass";}; # Test 2: default rulesDir is .opencode-rules testDefaultRulesDir = let @@ -24,13 +24,10 @@ let }; hasCorrectPrefix = builtins.all (s: builtins.substring 0 15 s == ".opencode-rules") rules.instructions; in - assert hasCorrectPrefix == true; - {result = "pass";}; + assert hasCorrectPrefix == true; {result = "pass";}; # Test 3: backward-compat alias exists - testBackwardCompat = - assert codingRulesLib.mkOpencodeRules == codingRulesLib.mkCodingRules; - {result = "pass";}; + testBackwardCompat = assert codingRulesLib.mkOpencodeRules == codingRulesLib.mkCodingRules; {result = "pass";}; # Test 4: shellHook contains both the symlink command and the config generation testShellHook = let @@ -42,9 +39,7 @@ let hasConfigGen = builtins.match ".*coding-rules.json.*" hook != null; in assert hasSymlink; - assert hasConfigGen; - {result = "pass";}; - + assert hasConfigGen; {result = "pass";}; in { instructions-correct = testInstructions; default-rules-dir = testDefaultRulesDir;