HASH 517baeda9f7c
DATE 2026-06-15
SUBJECT claude: claude desktop + usage monitor
FILES 12 CHANGED
HASH 517baeda9f7c
DATE 2026-06-15
SUBJECT claude: claude desktop + usage monitor
FILES 12 CHANGED
┌─ hosts/ooksdesk/default.nix ───────────────────────────────────────────────┐
│ diff --git a/hosts/ooksdesk/default.nix b/hosts/ooksdesk/default.nix │
│ index b8d3647..54f4502 100644 │
│ --- a/hosts/ooksdesk/default.nix │
│ +++ b/hosts/ooksdesk/default.nix │
│ @@ -26,7 +26,7 @@ │
│ environment = "hyprland"; │
│ theme = "minimal"; │
│ sunshine.enable = true; │
│ - profiles = ["creative" "virtualization" "gaming" "media" "communication" "p │
│ roductivity" "infra" "work"]; │
│ + profiles = ["creative" "virtualization" "gaming" "media" "communication" "p │
│ roductivity" "infra" "work" "ai"]; │
│ default = { │
│ browser = "zen"; │
│ terminal = "foot"; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ hosts/ooksdesk/default.nix ─────────┐
│ diff --git a/hosts/ooksdesk/default.nix b/ho │
│ sts/ooksdesk/default.nix │
│ index b8d3647..54f4502 100644 │
│ --- a/hosts/ooksdesk/default.nix │
│ +++ b/hosts/ooksdesk/default.nix │
│ @@ -26,7 +26,7 @@ │
│ environment = "hyprland"; │
│ theme = "minimal"; │
│ sunshine.enable = true; │
│ - profiles = ["creative" "virtualizatio │
│ n" "gaming" "media" "communication" "product │
│ ivity" "infra" "work"]; │
│ + profiles = ["creative" "virtualizatio │
│ n" "gaming" "media" "communication" "product │
│ ivity" "infra" "work" "ai"]; │
│ default = { │
│ browser = "zen"; │
│ terminal = "foot"; │
└──────────────────────────────────────────────┘
┌─ modules/common/console/profile/standard.nix ──────────────────────────────┐
│ diff --git a/modules/common/console/profile/standard.nix b/modules/common/console/ │
│ profile/standard.nix │
│ index 76085b1..bcec65e 100644 │
│ --- a/modules/common/console/profile/standard.nix │
│ +++ b/modules/common/console/profile/standard.nix │
│ @@ -12,6 +12,7 @@ in { │
│ multiplexer = "zellij"; │
│ tools = { │
│ codex.enable = true; │
│ + claude.enable = true; │
│ bat.enable = true; │
│ btop.enable = true; │
│ direnv.enable = true; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../console/profile/standard.nix ───┐
│ diff --git a/modules/common/console/profile/ │
│ standard.nix b/modules/common/console/profil │
│ e/standard.nix │
│ index 76085b1..bcec65e 100644 │
│ --- a/modules/common/console/profile/standar │
│ d.nix │
│ +++ b/modules/common/console/profile/standar │
│ d.nix │
│ @@ -12,6 +12,7 @@ in { │
│ multiplexer = "zellij"; │
│ tools = { │
│ codex.enable = true; │
│ + claude.enable = true; │
│ bat.enable = true; │
│ btop.enable = true; │
│ direnv.enable = true; │
└──────────────────────────────────────────────┘
┌─ modules/home/console/tools/claude.nix ────────────────────────────────────┐
│ diff --git a/modules/home/console/tools/claude.nix b/modules/home/console/tools/cl │
│ aude.nix │
│ new file mode 100644 │
│ index 0000000..6a0b781 │
│ --- /dev/null │
│ +++ b/modules/home/console/tools/claude.nix │
│ @@ -0,0 +1,163 @@ │
│ +{ │
│ + osConfig, │
│ + lib, │
│ + pkgs, │
│ + ... │
│ +}: let │
│ + inherit (lib) mkIf; │
│ + inherit (builtins) attrValues; │
│ + cfg = osConfig.ooknet.console.tools.claude; │
│ + │
│ + # reusable usage core. fetches the oauth usage endpoint (same data as /usage an │
│ d │
│ + # the website usage tab), caches with stale-while-revalidate. quickshell reads │
│ this too. │
│ + ookusage = pkgs.writeShellApplication { │
│ + name = "ookusage"; │
│ + runtimeInputs = attrValues { │
│ + inherit (pkgs) jq curl coreutils util-linux; │
│ + }; │
│ + text = '' │
│ + # ookusage - claude subscription usage (% of 5h + weekly limits) from the │
│ + # oauth usage endpoint. emits the raw endpoint json, cached. token is read │
│ + # internally, never logged; cache holds only percentages. │
│ + # ookusage print cache, refresh in background if stale (swr) │
│ + # ookusage refresh force fetch, print │
│ + # ookusage __bg internal: non-blocking fetch, silent │
│ + set -euo pipefail │
│ + │
│ + endpoint="https://api.anthropic.com/api/oauth/usage" │
│ + cache_dir="''${XDG_CACHE_HOME:-$HOME/.cache}/ookusage" │
│ + cache="$cache_dir/usage.json" │
│ + lock="$cache_dir/.lock" │
│ + ttl="''${OOKUSAGE_TTL:-60}" │
│ + │
│ + get_token() { │
│ + local t="" │
│ + if [[ -n "''${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then printf '%s' "$CLAUDE_C │
│ ODE_OAUTH_TOKEN"; return; fi │
│ + # linux: plaintext credentials file │
│ + if [[ -r "$HOME/.claude/.credentials.json" ]]; then │
│ + t="$(jq -r '.claudeAiOauth.accessToken // empty' "$HOME/.claude/.creden │
│ tials.json" 2>/dev/null || true)" │
│ + fi │
│ + # linux: libsecret keyring │
│ + if [[ -z "$t" ]] && command -v secret-tool >/dev/null 2>&1; then │
│ + t="$(secret-tool lookup service "Claude Code-credentials" 2>/dev/null \ │
│ + | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null || true) │
│ " │
│ + fi │
│ + # macos: keychain │
│ + if [[ -z "$t" ]] && command -v security >/dev/null 2>&1; then │
│ + t="$(security find-generic-password -s "Claude Code-credentials" -w 2>/ │
│ dev/null \ │
│ + | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null || true) │
│ " │
│ + fi │
│ + printf '%s' "$t" │
│ + } │
│ + │
│ + fetch() { │
│ + local tok; tok="$(get_token)" │
│ + [[ -z "$tok" ]] && return 1 │
│ + curl -sS --max-time 10 "$endpoint" \ │
│ + -H "Authorization: Bearer $tok" \ │
│ + -H "anthropic-beta: oauth-2025-04-20" │
│ + } │
│ + │
│ + refresh() { # $1 = -n for non-blocking bg refresh │
│ + mkdir -p "$cache_dir" │
│ + exec 9>"$lock" │
│ + if [ "''${1:-}" = "-n" ]; then flock -n 9 || return 0; else flock 9 || re │
│ turn 0; fi │
│ + local tmp; tmp="$(mktemp "$cache.XXXXXX")" │
│ + # only replace cache if we got a sane response (has five_hour) │
│ + if fetch >"$tmp" 2>/dev/null && jq -e '.five_hour' "$tmp" >/dev/null 2>&1 │
│ ; then │
│ + mv "$tmp" "$cache" │
│ + else │
│ + rm -f "$tmp" │
│ + return 1 │
│ + fi │
│ + } │
│ + │
│ + case "''${1:-}" in │
│ + refresh) refresh || true; cat "$cache" 2>/dev/null || echo '{"error":"fet │
│ ch failed"}' ;; │
│ + __bg) refresh -n || true ;; │
│ + *) │
│ + if [[ -f "$cache" ]]; then │
│ + cat "$cache" │
│ + age=$(( $(date +%s) - $(stat -c %Y "$cache") )) │
│ + if (( age > ttl )); then │
│ + setsid -f "$0" __bg >/dev/null 2>&1 || nohup "$0" __bg >/dev/null 2 │
│ >&1 & │
│ + fi │
│ + else │
│ + refresh || true │
│ + cat "$cache" 2>/dev/null || echo '{"error":"fetch failed"}' │
│ + fi │
│ + ;; │
│ + esac │
│ + ''; │
│ + }; │
│ + │
│ + # claude status line formatter. model/dir/git + usage % │
│ + claude-statusline = pkgs.writeShellApplication { │
│ + name = "claude-statusline"; │
│ + runtimeInputs = │
│ + [ookusage] │
│ + ++ attrValues { │
│ + inherit (pkgs) jq git coreutils gawk; │
│ + }; │
│ + text = '' │
│ + # claude-statusline - model/dir/git + claude subscription usage % (5h + wee │
│ kly) │
│ + # pulls live usage from `ookusage` (oauth usage endpoint, matches the websi │
│ te) │
│ + set -euo pipefail │
│ + │
│ + input="$(cat)" │
│ + usage="$(ookusage 2>/dev/null || echo '{}')" │
│ + │
│ + model="$(jq -r '.model.display_name // .model.id // "claude"' <<<"$input")" │
│ + dir="$(jq -r '.workspace.current_dir // .cwd // empty' <<<"$input")" │
│ + [[ -z "$dir" ]] && dir="$PWD" │
│ + branch="$(git -C "$dir" branch --show-current 2>/dev/null || true)" │
│ + short="''${dir/#$HOME/\~}" │
│ + │
│ + # ansi │
│ + d=$'\e[2m'; x=$'\e[0m'; bold=$'\e[1m'; blue=$'\e[34m'; grn=$'\e[32m' │
│ + sep=" ''${d}│''${x} " │
│ + pctcol() { awk -v p="$1" 'BEGIN{p+=0; if(p>=90)printf"\033[31m";else if(p>= │
│ 70)printf"\033[33m";else printf"\033[32m"}'; } │
│ + fmt_reset() { # iso -> local HH:MM, or "Day HH:MM" if >24h away │
│ + local iso="$1"; [[ -z "$iso" || "$iso" == "null" ]] && return │
│ + local e now; e="$(date -d "$iso" +%s 2>/dev/null)" || return; now="$(date │
│ +%s)" │
│ + if (( e - now < 86400 )); then date -d "$iso" +%H:%M; else date -d "$iso" │
│ +'%a %H:%M'; fi │
│ + } │
│ + seg() { # label util reset_iso -> "label NN% ?HH:MM" colored, or nothing if │
│ util empty │
│ + local label="$1" util="$2" iso="$3" │
│ + [[ -z "$util" || "$util" == "null" ]] && return │
│ + local rs; rs="$(fmt_reset "$iso")" │
│ + printf '%s %s%.0f%%%s' "$label" "$(pctcol "$util")" "$util" "$x" │
│ + [[ -n "$rs" ]] && printf ' %s?%s%s' "$d" "$rs" "$x" │
│ + } │
│ + │
│ + # newline-delimited (mapfile) so empty fields like a null opus slot are pre │
│ served │
│ + mapfile -t U < <(jq -r ' │
│ + (.five_hour.utilization // "" | tostring), │
│ + (.five_hour.resets_at // ""), │
│ + (.seven_day.utilization // "" | tostring), │
│ + (.seven_day.resets_at // ""), │
│ + (.seven_day_opus.utilization // "" | tostring), │
│ + (.seven_day_opus.resets_at // "") │
│ + ' <<<"$usage") │
│ + fh="''${U[0]:-}"; fh_r="''${U[1]:-}"; sd="''${U[2]:-}"; sd_r="''${U[3]:-}"; │
│ op="''${U[4]:-}"; op_r="''${U[5]:-}" │
│ + │
│ + # left: context │
│ + printf '%s%s%s %s%s%s' "$bold" "$model" "$x" "$blue" "$short" "$x" │
│ + [[ -n "$branch" ]] && printf ' %s %s%s' "$grn" "$branch" "$x" │
│ + │
│ + # right: usage. weekly opus only shown when the plan reports it (non-null) │
│ + if [[ -z "$fh$sd" ]]; then │
│ + printf '%s%susage n/a%s' "$sep" "$d" "$x" │
│ + else │
│ + printf '%s%s' "$sep" "$(seg 5h "$fh" "$fh_r")" │
│ + printf '%s%s' "$sep" "$(seg wk "$sd" "$sd_r")" │
│ + [[ -n "$op" && "$op" != "null" ]] && printf '%s%s' "$sep" "$(seg opus "$o │
│ p" "$op_r")" │
│ + fi │
│ + printf '\n' │
│ + ''; │
│ + }; │
│ +in { │
│ + config = mkIf cfg.enable { │
│ + home.packages = [ookusage claude-statusline]; │
│ + }; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...home/console/tools/claude.nix ───┐
│ diff --git a/modules/home/console/tools/clau │
│ de.nix b/modules/home/console/tools/claude.n │
│ ix │
│ new file mode 100644 │
│ index 0000000..6a0b781 │
│ --- /dev/null │
│ +++ b/modules/home/console/tools/claude.nix │
│ @@ -0,0 +1,163 @@ │
│ +{ │
│ + osConfig, │
│ + lib, │
│ + pkgs, │
│ + ... │
│ +}: let │
│ + inherit (lib) mkIf; │
│ + inherit (builtins) attrValues; │
│ + cfg = osConfig.ooknet.console.tools.claud │
│ e; │
│ + │
│ + # reusable usage core. fetches the oauth │
│ usage endpoint (same data as /usage and │
│ + # the website usage tab), caches with sta │
│ le-while-revalidate. quickshell reads this t │
│ oo. │
│ + ookusage = pkgs.writeShellApplication { │
│ + name = "ookusage"; │
│ + runtimeInputs = attrValues { │
│ + inherit (pkgs) jq curl coreutils util │
│ -linux; │
│ + }; │
│ + text = '' │
│ + # ookusage - claude subscription usag │
│ e (% of 5h + weekly limits) from the │
│ + # oauth usage endpoint. emits the raw │
│ endpoint json, cached. token is read │
│ + # internally, never logged; cache hol │
│ ds only percentages. │
│ + # ookusage print cache, r │
│ efresh in background if stale (swr) │
│ + # ookusage refresh force fetch, p │
│ rint │
│ + # ookusage __bg internal: non- │
│ blocking fetch, silent │
│ + set -euo pipefail │
│ + │
│ + endpoint="https://api.anthropic.com/a │
│ pi/oauth/usage" │
│ + cache_dir="''${XDG_CACHE_HOME:-$HOME/ │
│ .cache}/ookusage" │
│ + cache="$cache_dir/usage.json" │
│ + lock="$cache_dir/.lock" │
│ + ttl="''${OOKUSAGE_TTL:-60}" │
│ + │
│ + get_token() { │
│ + local t="" │
│ + if [[ -n "''${CLAUDE_CODE_OAUTH_TOK │
│ EN:-}" ]]; then printf '%s' "$CLAUDE_CODE_OA │
│ UTH_TOKEN"; return; fi │
│ + # linux: plaintext credentials file │
│ + if [[ -r "$HOME/.claude/.credential │
│ s.json" ]]; then │
│ + t="$(jq -r '.claudeAiOauth.access │
│ Token // empty' "$HOME/.claude/.credentials. │
│ json" 2>/dev/null || true)" │
│ + fi │
│ + # linux: libsecret keyring │
│ + if [[ -z "$t" ]] && command -v secr │
│ et-tool >/dev/null 2>&1; then │
│ + t="$(secret-tool lookup service " │
│ Claude Code-credentials" 2>/dev/null \ │
│ + | jq -r '.claudeAiOauth.acce │
│ ssToken // empty' 2>/dev/null || true)" │
│ + fi │
│ + # macos: keychain │
│ + if [[ -z "$t" ]] && command -v secu │
│ rity >/dev/null 2>&1; then │
│ + t="$(security find-generic-passwo │
│ rd -s "Claude Code-credentials" -w 2>/dev/nu │
│ ll \ │
│ + | jq -r '.claudeAiOauth.acce │
│ ssToken // empty' 2>/dev/null || true)" │
│ + fi │
│ + printf '%s' "$t" │
│ + } │
│ + │
│ + fetch() { │
│ + local tok; tok="$(get_token)" │
│ + [[ -z "$tok" ]] && return 1 │
│ + curl -sS --max-time 10 "$endpoint" │
│ \ │
│ + -H "Authorization: Bearer $tok" \ │
│ + -H "anthropic-beta: oauth-2025-04 │
│ -20" │
│ + } │
│ + │
│ + refresh() { # $1 = -n for non-blockin │
│ g bg refresh │
│ + mkdir -p "$cache_dir" │
│ + exec 9>"$lock" │
│ + if [ "''${1:-}" = "-n" ]; then floc │
│ k -n 9 || return 0; else flock 9 || return 0 │
│ ; fi │
│ + local tmp; tmp="$(mktemp "$cache.XX │
│ XXXX")" │
│ + # only replace cache if we got a sa │
│ ne response (has five_hour) │
│ + if fetch >"$tmp" 2>/dev/null && jq │
│ -e '.five_hour' "$tmp" >/dev/null 2>&1; then │
│ + mv "$tmp" "$cache" │
│ + else │
│ + rm -f "$tmp" │
│ + return 1 │
│ + fi │
│ + } │
│ + │
│ + case "''${1:-}" in │
│ + refresh) refresh || true; cat "$cac │
│ he" 2>/dev/null || echo '{"error":"fetch fai │
│ led"}' ;; │
│ + __bg) refresh -n || true ;; │
│ + *) │
│ + if [[ -f "$cache" ]]; then │
│ + cat "$cache" │
│ + age=$(( $(date +%s) - $(stat -c │
│ %Y "$cache") )) │
│ + if (( age > ttl )); then │
│ + setsid -f "$0" __bg >/dev/nul │
│ l 2>&1 || nohup "$0" __bg >/dev/null 2>&1 & │
│ + fi │
│ + else │
│ + refresh || true │
│ + cat "$cache" 2>/dev/null || ech │
│ o '{"error":"fetch failed"}' │
│ + fi │
│ + ;; │
│ + esac │
│ + ''; │
│ + }; │
│ + │
│ + # claude status line formatter. model/dir │
│ /git + usage % │
│ + claude-statusline = pkgs.writeShellApplic │
│ ation { │
│ + name = "claude-statusline"; │
│ + runtimeInputs = │
│ + [ookusage] │
│ + ++ attrValues { │
│ + inherit (pkgs) jq git coreutils gaw │
│ k; │
│ + }; │
│ + text = '' │
│ + # claude-statusline - model/dir/git + │
│ claude subscription usage % (5h + weekly) │
│ + # pulls live usage from `ookusage` (o │
│ auth usage endpoint, matches the website) │
│ + set -euo pipefail │
│ + │
│ + input="$(cat)" │
│ + usage="$(ookusage 2>/dev/null || echo │
│ '{}')" │
│ + │
│ + model="$(jq -r '.model.display_name / │
│ / .model.id // "claude"' <<<"$input")" │
│ + dir="$(jq -r '.workspace.current_dir │
│ // .cwd // empty' <<<"$input")" │
│ + [[ -z "$dir" ]] && dir="$PWD" │
│ + branch="$(git -C "$dir" branch --show │
│ -current 2>/dev/null || true)" │
│ + short="''${dir/#$HOME/\~}" │
│ + │
│ + # ansi │
│ + d=$'\e[2m'; x=$'\e[0m'; bold=$'\e[1m' │
│ ; blue=$'\e[34m'; grn=$'\e[32m' │
│ + sep=" ''${d}│''${x} " │
│ + pctcol() { awk -v p="$1" 'BEGIN{p+=0; │
│ if(p>=90)printf"\033[31m";else if(p>=70)pri │
│ ntf"\033[33m";else printf"\033[32m"}'; } │
│ + fmt_reset() { # iso -> local HH:MM, o │
│ r "Day HH:MM" if >24h away │
│ + local iso="$1"; [[ -z "$iso" || "$i │
│ so" == "null" ]] && return │
│ + local e now; e="$(date -d "$iso" +% │
│ s 2>/dev/null)" || return; now="$(date +%s)" │
│ + if (( e - now < 86400 )); then date │
│ -d "$iso" +%H:%M; else date -d "$iso" +'%a │
│ %H:%M'; fi │
│ + } │
│ + seg() { # label util reset_iso -> "la │
│ bel NN% ?HH:MM" colored, or nothing if util │
│ empty │
│ + local label="$1" util="$2" iso="$3" │
│ + [[ -z "$util" || "$util" == "null" │
│ ]] && return │
│ + local rs; rs="$(fmt_reset "$iso")" │
│ + printf '%s %s%.0f%%%s' "$label" "$( │
│ pctcol "$util")" "$util" "$x" │
│ + [[ -n "$rs" ]] && printf ' %s?%s%s' │
│ "$d" "$rs" "$x" │
│ + } │
│ + │
│ + # newline-delimited (mapfile) so empt │
│ y fields like a null opus slot are preserved │
│ + mapfile -t U < <(jq -r ' │
│ + (.five_hour.utilization // "" │
│ | tostring), │
│ + (.five_hour.resets_at // "") │
│ , │
│ + (.seven_day.utilization // "" │
│ | tostring), │
│ + (.seven_day.resets_at // "") │
│ , │
│ + (.seven_day_opus.utilization // "" │
│ | tostring), │
│ + (.seven_day_opus.resets_at // "") │
│ + ' <<<"$usage") │
│ + fh="''${U[0]:-}"; fh_r="''${U[1]:-}"; │
│ sd="''${U[2]:-}"; sd_r="''${U[3]:-}"; op="' │
│ '${U[4]:-}"; op_r="''${U[5]:-}" │
│ + │
│ + # left: context │
│ + printf '%s%s%s %s%s%s' "$bold" "$mod │
│ el" "$x" "$blue" "$short" "$x" │
│ + [[ -n "$branch" ]] && printf ' %s %s │
│ %s' "$grn" "$branch" "$x" │
│ + │
│ + # right: usage. weekly opus only show │
│ n when the plan reports it (non-null) │
│ + if [[ -z "$fh$sd" ]]; then │
│ + printf '%s%susage n/a%s' "$sep" "$d │
│ " "$x" │
│ + else │
│ + printf '%s%s' "$sep" "$(seg 5h "$fh │
│ " "$fh_r")" │
│ + printf '%s%s' "$sep" "$(seg wk "$sd │
│ " "$sd_r")" │
│ + [[ -n "$op" && "$op" != "null" ]] & │
│ & printf '%s%s' "$sep" "$(seg opus "$op" "$o │
│ p_r")" │
│ + fi │
│ + printf '\n' │
│ + ''; │
│ + }; │
│ +in { │
│ + config = mkIf cfg.enable { │
│ + home.packages = [ookusage claude-status │
│ line]; │
│ + }; │
│ +} │
└──────────────────────────────────────────────┘
┌─ modules/home/console/tools/default.nix ───────────────────────────────────┐
│ diff --git a/modules/home/console/tools/default.nix b/modules/home/console/tools/d │
│ efault.nix │
│ index 6bdac39..de143e6 100644 │
│ --- a/modules/home/console/tools/default.nix │
│ +++ b/modules/home/console/tools/default.nix │
│ @@ -1,6 +1,7 @@ │
│ { │
│ imports = [ │
│ ./codex.nix │
│ + ./claude.nix │
│ ./bat.nix │
│ ./vale.nix │
│ ./networking.nix │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ome/console/tools/default.nix ───┐
│ diff --git a/modules/home/console/tools/defa │
│ ult.nix b/modules/home/console/tools/default │
│ .nix │
│ index 6bdac39..de143e6 100644 │
│ --- a/modules/home/console/tools/default.nix │
│ +++ b/modules/home/console/tools/default.nix │
│ @@ -1,6 +1,7 @@ │
│ { │
│ imports = [ │
│ ./codex.nix │
│ + ./claude.nix │
│ ./bat.nix │
│ ./vale.nix │
│ ./networking.nix │
└──────────────────────────────────────────────┘
┌─ modules/home/nixos/ai/claude-desktop.nix ─────────────────────────────────┐
│ diff --git a/modules/home/nixos/ai/claude-desktop.nix b/modules/home/nixos/ai/clau │
│ de-desktop.nix │
│ new file mode 100644 │
│ index 0000000..4ed4b78 │
│ --- /dev/null │
│ +++ b/modules/home/nixos/ai/claude-desktop.nix │
│ @@ -0,0 +1,17 @@ │
│ +{ │
│ + lib, │
│ + osConfig, │
│ + self', │
│ + ... │
│ +}: let │
│ + inherit (lib) elem mkIf; │
│ + inherit (builtins) attrValues; │
│ + inherit (osConfig.ooknet.workstation) profiles; │
│ +in { │
│ + # fhs variant is upstreams recommended default (MCP servers) │
│ + config = mkIf (elem "ai" profiles) { │
│ + home.packages = attrValues { │
│ + inherit (self'.packages) claude-desktop-fhs; │
│ + }; │
│ + }; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...e/nixos/ai/claude-desktop.nix ───┐
│ diff --git a/modules/home/nixos/ai/claude-de │
│ sktop.nix b/modules/home/nixos/ai/claude-des │
│ ktop.nix │
│ new file mode 100644 │
│ index 0000000..4ed4b78 │
│ --- /dev/null │
│ +++ b/modules/home/nixos/ai/claude-desktop.n │
│ ix │
│ @@ -0,0 +1,17 @@ │
│ +{ │
│ + lib, │
│ + osConfig, │
│ + self', │
│ + ... │
│ +}: let │
│ + inherit (lib) elem mkIf; │
│ + inherit (builtins) attrValues; │
│ + inherit (osConfig.ooknet.workstation) pro │
│ files; │
│ +in { │
│ + # fhs variant is upstreams recommended de │
│ fault (MCP servers) │
│ + config = mkIf (elem "ai" profiles) { │
│ + home.packages = attrValues { │
│ + inherit (self'.packages) claude-deskt │
│ op-fhs; │
│ + }; │
│ + }; │
│ +} │
└──────────────────────────────────────────────┘
┌─ modules/home/nixos/ai/default.nix ────────────────────────────────────────┐
│ diff --git a/modules/home/nixos/ai/default.nix b/modules/home/nixos/ai/default.nix │
│ new file mode 100644 │
│ index 0000000..87bb4db │
│ --- /dev/null │
│ +++ b/modules/home/nixos/ai/default.nix │
│ @@ -0,0 +1,5 @@ │
│ +{ │
│ + imports = [ │
│ + ./claude-desktop.nix │
│ + ]; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...les/home/nixos/ai/default.nix ───┐
│ diff --git a/modules/home/nixos/ai/default.n │
│ ix b/modules/home/nixos/ai/default.nix │
│ new file mode 100644 │
│ index 0000000..87bb4db │
│ --- /dev/null │
│ +++ b/modules/home/nixos/ai/default.nix │
│ @@ -0,0 +1,5 @@ │
│ +{ │
│ + imports = [ │
│ + ./claude-desktop.nix │
│ + ]; │
│ +} │
└──────────────────────────────────────────────┘
┌─ modules/home/nixos/default.nix ───────────────────────────────────────────┐
│ diff --git a/modules/home/nixos/default.nix b/modules/home/nixos/default.nix │
│ index ff46539..e15088b 100644 │
│ --- a/modules/home/nixos/default.nix │
│ +++ b/modules/home/nixos/default.nix │
│ @@ -8,6 +8,7 @@ │
│ ./creative │
│ ./gaming │
│ ./work │
│ + ./ai │
│ ./userDirs.nix │
│ ./mimeApps.nix │
│ ]; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ modules/home/nixos/default.nix ─────┐
│ diff --git a/modules/home/nixos/default.nix │
│ b/modules/home/nixos/default.nix │
│ index ff46539..e15088b 100644 │
│ --- a/modules/home/nixos/default.nix │
│ +++ b/modules/home/nixos/default.nix │
│ @@ -8,6 +8,7 @@ │
│ ./creative │
│ ./gaming │
│ ./work │
│ + ./ai │
│ ./userDirs.nix │
│ ./mimeApps.nix │
│ ]; │
└──────────────────────────────────────────────┘
┌─ modules/options/console/default.nix ──────────────────────────────────────┐
│ diff --git a/modules/options/console/default.nix b/modules/options/console/default │
│ .nix │
│ index 4b811c0..df05e18 100644 │
│ --- a/modules/options/console/default.nix │
│ +++ b/modules/options/console/default.nix │
│ @@ -22,6 +22,7 @@ in { │
│ }; │
│ tools = { │
│ codex.enable = mkEnableOption ""; │
│ + claude.enable = mkEnableOption ""; │
│ bat.enable = mkEnableOption ""; │
│ btop.enable = mkEnableOption ""; │
│ direnv.enable = mkEnableOption ""; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...s/options/console/default.nix ───┐
│ diff --git a/modules/options/console/default │
│ .nix b/modules/options/console/default.nix │
│ index 4b811c0..df05e18 100644 │
│ --- a/modules/options/console/default.nix │
│ +++ b/modules/options/console/default.nix │
│ @@ -22,6 +22,7 @@ in { │
│ }; │
│ tools = { │
│ codex.enable = mkEnableOption ""; │
│ + claude.enable = mkEnableOption ""; │
│ bat.enable = mkEnableOption ""; │
│ btop.enable = mkEnableOption ""; │
│ direnv.enable = mkEnableOption ""; │
└──────────────────────────────────────────────┘
┌─ outputs/pkgs/claude-desktop/default.nix ──────────────────────────────────┐
│ diff --git a/outputs/pkgs/claude-desktop/default.nix b/outputs/pkgs/claude-desktop │
│ /default.nix │
│ new file mode 100644 │
│ index 0000000..3d71035 │
│ --- /dev/null │
│ +++ b/outputs/pkgs/claude-desktop/default.nix │
│ @@ -0,0 +1,312 @@ │
│ +# adapted from github:aaddrick/claude-desktop-debian (nix/claude-desktop.nix) │
│ +# build scripts are pulled via fetchFromGitHub rather than vendoring the repo │
│ +# to bump: update repoSrc rev/hash + version + the windows exe srcs below │
│ +{ │
│ + lib, │
│ + stdenvNoCC, │
│ + fetchurl, │
│ + fetchFromGitHub, │
│ + electron, │
│ + p7zip, │
│ + icoutils, │
│ + imagemagick, │
│ + nodejs, │
│ + asar, │
│ + makeDesktopItem, │
│ + python3, │
│ + bash, │
│ + getent, │
│ + node-pty, │
│ +}: let │
│ + pname = "claude-desktop"; │
│ + version = "1.12603.1"; │
│ + │
│ + # upstream repo providing build.sh + scripts/ (the actual packaging logic) │
│ + repoSrc = fetchFromGitHub { │
│ + owner = "aaddrick"; │
│ + repo = "claude-desktop-debian"; │
│ + rev = "d2ce0466315033938ea6c2066d424239feffabf0"; # v2.0.20+claude1.12603.1 │
│ + hash = "sha256-vMMjROGD/huIbrvTV3okyMPseleQ3dsFOabJo9TtfoI="; │
│ + }; │
│ + │
│ + srcs = { │
│ + x86_64-linux = fetchurl { │
│ + url = "https://downloads.claude.ai/releases/win32/x64/1.12603.1/Claude-3df4 │
│ fd263723119bc45f0af2d784afd5055e2ba9.exe"; │
│ + hash = "sha256-Fym+uhmkDkkA8+32jk3B7BxFJ8TbM1sTOADJlkUMl6g="; │
│ + }; │
│ + aarch64-linux = fetchurl { │
│ + url = "https://downloads.claude.ai/releases/win32/arm64/1.12603.1/Claude-3d │
│ f4fd263723119bc45f0af2d784afd5055e2ba9.exe"; │
│ + hash = "sha256-jthQmwDvYBV89iR9xdrUccCQOTaEEgyfjxFoVIZEcKw="; │
│ + }; │
│ + }; │
│ + │
│ + src = srcs.${stdenvNoCC.hostPlatform.system} or (throw "Unsupported system: ${s │
│ tdenvNoCC.hostPlatform.system}"); │
│ + │
│ + # The unwrapped electron derivation - contains the real ELF binary │
│ + # and Chromium resources (.pak files, locales/, etc.). │
│ + electronUnwrapped = electron.passthru.unwrapped or electron; │
│ + electronDir = "${electronUnwrapped}/libexec/electron"; │
│ + │
│ + desktopItem = makeDesktopItem { │
│ + name = "claude-desktop"; │
│ + exec = "claude-desktop %u"; │
│ + icon = "claude-desktop"; │
│ + type = "Application"; │
│ + terminal = false; │
│ + desktopName = "Claude"; │
│ + genericName = "Claude Desktop"; │
│ + startupWMClass = "Claude"; │
│ + categories = ["Office" "Utility"]; │
│ + mimeTypes = ["x-scheme-handler/claude"]; │
│ + }; │
│ +in │
│ + stdenvNoCC.mkDerivation { │
│ + inherit pname version src; │
│ + │
│ + nativeBuildInputs = [ │
│ + p7zip │
│ + nodejs │
│ + asar │
│ + icoutils │
│ + imagemagick │
│ + bash │
│ + python3 │
│ + getent │
│ + ]; │
│ + │
│ + # The exe is not a standard archive - use manual unpack │
│ + dontUnpack = true; │
│ + │
│ + buildPhase = '' │
│ + runHook preBuild │
│ + │
│ + export HOME=$TMPDIR │
│ + │
│ + # Writable copy of upstream so we can fix its #649 add-dir patch. │
│ + # v2.0.20's patch asserts the minified --add-dir dispatch snippet │
│ + # appears exactly once and patches only the first hit, but claude │
│ + # 1.12603.1 carries it twice and the assertion fatals. The two hits │
│ + # are byte-identical (upstream counts an escaped literal), so the │
│ + # filtered replacement it computes is correct for both - rewrite the │
│ + # patch to neutralise the count guard and replace all occurrences. │
│ + cp -r --no-preserve=mode,ownership ${repoSrc} ./upstream │
│ + substituteInPlace ./upstream/scripts/patches/config.sh \ │
│ + --replace-fail 'allMatches && allMatches.length > 1' 'false' \ │
│ + --replace-fail 'code = code.replace(match[0], filtered);' 'code = code.sp │
│ lit(match[0]).join(filtered);' │
│ + │
│ + # Copy exe to a writable location for build.sh │
│ + cp $src Claude-Setup.exe │
│ + │
│ + # Run build.sh in nix mode - it handles extraction, patching, icon │
│ + # extraction, and asar repacking. --source-dir points at our patched │
│ + # copy so build.sh finds scripts/. │
│ + bash ./upstream/build.sh \ │
│ + --exe "$(pwd)/Claude-Setup.exe" \ │
│ + --source-dir "$(pwd)/upstream" \ │
│ + --node-pty-dir "${node-pty}/lib/node_modules/node-pty" \ │
│ + --build nix \ │
│ + --clean no │
│ + │
│ + runHook postBuild │
│ + ''; │
│ + │
│ + installPhase = '' │
│ + runHook preInstall │
│ + │
│ + #==================================================================== │
│ ====== │
│ + # Create a custom Electron tree with app resources co-located. │
│ + # │
│ + # On NixOS, the stock electron-unwrapped lives in a read-only store │
│ + # path. Chromium computes process.resourcesPath from /proc/self/exe, │
│ + # so it always points to electron-unwrapped's resources/ dir - which │
│ + # doesn't contain the app's locale JSONs, tray icons, etc. When │
│ + # ELECTRON_FORCE_IS_PACKAGED=true, the app reads en-US.json from │
│ + # resourcesPath at module load time (before frame-fix-wrapper.js can │
│ + # correct the path), causing an ENOENT crash. │
│ + # │
│ + # Solution: copy the Electron ELF binary into our own tree so that │
│ + # /proc/self/exe resolves here, then merge both Electron's and the │
│ + # app's resources into resources/. Everything else (shared libs, │
│ + # .pak files, locales/) is symlinked to avoid duplication. │
│ + #==================================================================== │
│ ====== │
│ + electron_tree=$out/lib/claude-desktop/electron │
│ + │
│ + mkdir -p $electron_tree/resources │
│ + │
│ + # Copy the ELF binary - MUST be a real copy (not symlink) so that │
│ + # /proc/self/exe resolves to our tree │
│ + cp ${electronDir}/electron $electron_tree/electron │
│ + chmod +x $electron_tree/electron │
│ + │
│ + # Symlink everything else from electron-unwrapped │
│ + for item in ${electronDir}/*; do │
│ + name=$(basename "$item") │
│ + [[ "$name" = "electron" ]] && continue │
│ + [[ "$name" = "resources" ]] && continue │
│ + ln -s "$item" "$electron_tree/$name" │
│ + done │
│ + │
│ + # Populate resources/ - start with Electron's own (default_app.asar) │
│ + for item in ${electronDir}/resources/*; do │
│ + ln -s "$item" "$electron_tree/resources/$(basename "$item")" │
│ + done │
│ + │
│ + # Install app.asar and unpacked resources into the merged tree │
│ + cp build/electron-app/app.asar $electron_tree/resources/ │
│ + cp -r build/electron-app/app.asar.unpacked $electron_tree/resources/ │
│ + │
│ + # Install tray icons into resources │
│ + for tray_icon in build/electron-app/nix-resources/Tray*; do │
│ + [[ -f "$tray_icon" ]] && cp "$tray_icon" $electron_tree/resources/ │
│ + done │
│ + │
│ + # Install SSH helpers into resources │
│ + if [[ -d build/electron-app/nix-resources/claude-ssh ]]; then │
│ + cp -r build/electron-app/nix-resources/claude-ssh \ │
│ + $electron_tree/resources/ │
│ + fi │
│ + │
│ + # Install cowork resources (smol-bin, plugin shim) │
│ + for cowork_res in build/electron-app/nix-resources/smol-bin.*.vhdx \ │
│ + build/electron-app/nix-resources/cowork-plugin-shim │
│ .sh; do │
│ + if [[ -f "$cowork_res" ]]; then │
│ + cp "$cowork_res" $electron_tree/resources/ │
│ + echo "Installed cowork resource: $(basename "$cowork_res")" │
│ + fi │
│ + done │
│ + │
│ + # Install ion-dist static assets (app:// protocol handler root for │
│ + # Third-Party Inference setup - see issue #488) │
│ + if [[ -d build/electron-app/nix-resources/ion-dist ]]; then │
│ + cp -r build/electron-app/nix-resources/ion-dist \ │
│ + $electron_tree/resources/ │
│ + echo "Installed cowork resource: ion-dist" │
│ + fi │
│ + │
│ + # Install locale JSON files into resources │
│ + for locale_json in build/claude-extract/lib/net45/resources/*-*.json; │
│ do │
│ + [[ -f "$locale_json" ]] \ │
│ + && cp "$locale_json" $electron_tree/resources/ │
│ + done │
│ + │
│ + # Create the electron wrapper - replicates the env setup from the │
│ + # stock electron wrapper (GIO, GTK, GDK_PIXBUF, XDG_DATA_DIRS) but │
│ + # execs our custom binary. We extract everything except the final │
│ + # exec line from the stock wrapper, then append our own exec. │
│ + head -n -1 ${electron}/bin/electron > $electron_tree/electron-wrapper │
│ + echo "exec \"$electron_tree/electron\" \"\$@\"" >> $electron_tree/ele │
│ ctron-wrapper │
│ + chmod +x $electron_tree/electron-wrapper │
│ + │
│ + # Update CHROME_DEVEL_SANDBOX to point to our tree's chrome-sandbox │
│ + substituteInPlace $electron_tree/electron-wrapper \ │
│ + --replace-quiet "${electron}/libexec/electron/chrome-sandbox" \ │
│ + "$electron_tree/chrome-sandbox" │
│ + │
│ + #==================================================================== │
│ ====== │
│ + # Standard install (icons, desktop file, launcher) │
│ + #==================================================================== │
│ ====== │
│ + │
│ + # Convenience symlink for resources dir (used by launcher, FHS, etc.) │
│ + ln -s $electron_tree/resources $out/lib/claude-desktop/resources │
│ + │
│ + # Install icons │
│ + for size in 16 24 32 48 64 256; do │
│ + icon_dir=$out/share/icons/hicolor/"$size"x"$size"/apps │
│ + mkdir -p "$icon_dir" │
│ + icon=$(find build/ -name "claude_*''${size}x''${size}x32.png" 2>/de │
│ v/null | head -1) │
│ + if [[ -n "$icon" ]]; then │
│ + install -Dm644 "$icon" "$icon_dir/claude-desktop.png" │
│ + fi │
│ + done │
│ + │
│ + # Install shared launcher library + doctor (launcher-common.sh │
│ + # sources doctor.sh at runtime, so both must live in the same dir) │
│ + install -Dm755 ${repoSrc}/scripts/launcher-common.sh \ │
│ + $out/lib/claude-desktop/launcher-common.sh │
│ + install -Dm755 ${repoSrc}/scripts/doctor.sh \ │
│ + $out/lib/claude-desktop/doctor.sh │
│ + │
│ + # Install .desktop file │
│ + mkdir -p $out/share/applications │
│ + install -Dm644 ${desktopItem}/share/applications/* $out/share/applica │
│ tions/ │
│ + │
│ + # Create launcher script │
│ + mkdir -p $out/bin │
│ + cat > $out/bin/claude-desktop <<'LAUNCHER' │
│ + #!/usr/bin/env bash │
│ + # Claude Desktop launcher for NixOS │
│ + │
│ + electron_exec="ELECTRON_PLACEHOLDER" │
│ + app_path="RESOURCES_PLACEHOLDER/app.asar" │
│ + │
│ + source "LAUNCHER_LIB_PLACEHOLDER" │
│ + │
│ + # Handle --doctor flag before anything else │
│ + if [[ "''${1:-}" == '--doctor' ]]; then │
│ + run_doctor "$electron_exec" │
│ + exit $? │
│ + fi │
│ + │
│ + # Setup logging and environment │
│ + setup_logging || exit 1 │
│ + setup_electron_env │
│ + cleanup_orphaned_cowork_daemon │
│ + cleanup_stale_desktop_helpers │
│ + cleanup_stale_lock │
│ + cleanup_stale_cowork_socket │
│ + │
│ + # Log startup info │
│ + log_message '--- Claude Desktop Launcher Start (NixOS) ---' │
│ + log_message "Timestamp: $(date)" │
│ + log_message "Arguments: $@" │
│ + log_session_env │
│ + │
│ + # Check for display │
│ + if ! check_display; then │
│ + log_message 'No display detected (TTY session)' │
│ + echo 'Error: Claude Desktop requires a graphical desktop environment.' >&2 │
│ + echo 'Please run from within an X11 or Wayland session, not from a TTY.' > │
│ &2 │
│ + exit 1 │
│ + fi │
│ + │
│ + # Detect display backend (handles CLAUDE_USE_WAYLAND) │
│ + detect_display_backend │
│ + │
│ + # Build Electron arguments │
│ + build_electron_args 'nix' │
│ + │
│ + # Intentionally NOT appended: app.asar sits in Electron's default │
│ + # resources/ dir next to the binary, so Electron auto-loads it. Passing │
│ + # the path again makes Electron treat it as a file-to-open, which the │
│ + # app forwards to its file-drop handler, producing a spurious │
│ + # "Attach app.asar?" prompt on launch and on every taskbar reopen │
│ + # (the second-instance argv path). Omitting it is the root-cause fix. │
│ + # See issue #696. │
│ + log_message "App (auto-loaded by Electron): $app_path" │
│ + │
│ + # Execute Electron and keep the launcher alive so explicit quit can │
│ + # clean up Desktop-owned helpers that outlive the Electron main process. │
│ + log_message "Executing: $electron_exec ''${electron_args[*]} $*" │
│ + run_electron_and_cleanup "$electron_exec" "''${electron_args[@]}" "$@" │
│ + exit $? │
│ + LAUNCHER │
│ + # Substitute placeholders - electron_exec points to our custom │
│ + # wrapper (which sets GTK/GIO env then execs our merged binary) │
│ + substituteInPlace $out/bin/claude-desktop \ │
│ + --replace-fail "ELECTRON_PLACEHOLDER" "$electron_tree/electron-wrap │
│ per" \ │
│ + --replace-fail "RESOURCES_PLACEHOLDER" "$electron_tree/resources" \ │
│ + --replace-fail "LAUNCHER_LIB_PLACEHOLDER" "$out/lib/claude-desktop/ │
│ launcher-common.sh" │
│ + chmod +x $out/bin/claude-desktop │
│ + │
│ + runHook postInstall │
│ + ''; │
│ + │
│ + meta = with lib; { │
│ + description = "Claude Desktop for Linux"; │
│ + homepage = "https://github.com/aaddrick/claude-desktop-debian"; │
│ + license = licenses.unfree; │
│ + platforms = ["x86_64-linux" "aarch64-linux"]; │
│ + sourceProvenance = with sourceTypes; [binaryNativeCode]; │
│ + mainProgram = "claude-desktop"; │
│ + }; │
│ + } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...gs/claude-desktop/default.nix ───┐
│ diff --git a/outputs/pkgs/claude-desktop/def │
│ ault.nix b/outputs/pkgs/claude-desktop/defau │
│ lt.nix │
│ new file mode 100644 │
│ index 0000000..3d71035 │
│ --- /dev/null │
│ +++ b/outputs/pkgs/claude-desktop/default.ni │
│ x │
│ @@ -0,0 +1,312 @@ │
│ +# adapted from github:aaddrick/claude-deskt │
│ op-debian (nix/claude-desktop.nix) │
│ +# build scripts are pulled via fetchFromGit │
│ Hub rather than vendoring the repo │
│ +# to bump: update repoSrc rev/hash + versio │
│ n + the windows exe srcs below │
│ +{ │
│ + lib, │
│ + stdenvNoCC, │
│ + fetchurl, │
│ + fetchFromGitHub, │
│ + electron, │
│ + p7zip, │
│ + icoutils, │
│ + imagemagick, │
│ + nodejs, │
│ + asar, │
│ + makeDesktopItem, │
│ + python3, │
│ + bash, │
│ + getent, │
│ + node-pty, │
│ +}: let │
│ + pname = "claude-desktop"; │
│ + version = "1.12603.1"; │
│ + │
│ + # upstream repo providing build.sh + scri │
│ pts/ (the actual packaging logic) │
│ + repoSrc = fetchFromGitHub { │
│ + owner = "aaddrick"; │
│ + repo = "claude-desktop-debian"; │
│ + rev = "d2ce0466315033938ea6c2066d424239 │
│ feffabf0"; # v2.0.20+claude1.12603.1 │
│ + hash = "sha256-vMMjROGD/huIbrvTV3okyMPs │
│ eleQ3dsFOabJo9TtfoI="; │
│ + }; │
│ + │
│ + srcs = { │
│ + x86_64-linux = fetchurl { │
│ + url = "https://downloads.claude.ai/re │
│ leases/win32/x64/1.12603.1/Claude-3df4fd2637 │
│ 23119bc45f0af2d784afd5055e2ba9.exe"; │
│ + hash = "sha256-Fym+uhmkDkkA8+32jk3B7B │
│ xFJ8TbM1sTOADJlkUMl6g="; │
│ + }; │
│ + aarch64-linux = fetchurl { │
│ + url = "https://downloads.claude.ai/re │
│ leases/win32/arm64/1.12603.1/Claude-3df4fd26 │
│ 3723119bc45f0af2d784afd5055e2ba9.exe"; │
│ + hash = "sha256-jthQmwDvYBV89iR9xdrUcc │
│ CQOTaEEgyfjxFoVIZEcKw="; │
│ + }; │
│ + }; │
│ + │
│ + src = srcs.${stdenvNoCC.hostPlatform.syst │
│ em} or (throw "Unsupported system: ${stdenvN │
│ oCC.hostPlatform.system}"); │
│ + │
│ + # The unwrapped electron derivation - con │
│ tains the real ELF binary │
│ + # and Chromium resources (.pak files, loc │
│ ales/, etc.). │
│ + electronUnwrapped = electron.passthru.unw │
│ rapped or electron; │
│ + electronDir = "${electronUnwrapped}/libex │
│ ec/electron"; │
│ + │
│ + desktopItem = makeDesktopItem { │
│ + name = "claude-desktop"; │
│ + exec = "claude-desktop %u"; │
│ + icon = "claude-desktop"; │
│ + type = "Application"; │
│ + terminal = false; │
│ + desktopName = "Claude"; │
│ + genericName = "Claude Desktop"; │
│ + startupWMClass = "Claude"; │
│ + categories = ["Office" "Utility"]; │
│ + mimeTypes = ["x-scheme-handler/claude"] │
│ ; │
│ + }; │
│ +in │
│ + stdenvNoCC.mkDerivation { │
│ + inherit pname version src; │
│ + │
│ + nativeBuildInputs = [ │
│ + p7zip │
│ + nodejs │
│ + asar │
│ + icoutils │
│ + imagemagick │
│ + bash │
│ + python3 │
│ + getent │
│ + ]; │
│ + │
│ + # The exe is not a standard archive - u │
│ se manual unpack │
│ + dontUnpack = true; │
│ + │
│ + buildPhase = '' │
│ + runHook preBuild │
│ + │
│ + export HOME=$TMPDIR │
│ + │
│ + # Writable copy of upstream so we can │
│ fix its #649 add-dir patch. │
│ + # v2.0.20's patch asserts the minifie │
│ d --add-dir dispatch snippet │
│ + # appears exactly once and patches on │
│ ly the first hit, but claude │
│ + # 1.12603.1 carries it twice and the │
│ assertion fatals. The two hits │
│ + # are byte-identical (upstream counts │
│ an escaped literal), so the │
│ + # filtered replacement it computes is │
│ correct for both - rewrite the │
│ + # patch to neutralise the count guard │
│ and replace all occurrences. │
│ + cp -r --no-preserve=mode,ownership ${ │
│ repoSrc} ./upstream │
│ + substituteInPlace ./upstream/scripts/ │
│ patches/config.sh \ │
│ + --replace-fail 'allMatches && allMa │
│ tches.length > 1' 'false' \ │
│ + --replace-fail 'code = code.replace │
│ (match[0], filtered);' 'code = code.split(ma │
│ tch[0]).join(filtered);' │
│ + │
│ + # Copy exe to a writable location for │
│ build.sh │
│ + cp $src Claude-Setup.exe │
│ + │
│ + # Run build.sh in nix mode - it handl │
│ es extraction, patching, icon │
│ + # extraction, and asar repacking. --s │
│ ource-dir points at our patched │
│ + # copy so build.sh finds scripts/. │
│ + bash ./upstream/build.sh \ │
│ + --exe "$(pwd)/Claude-Setup.exe" \ │
│ + --source-dir "$(pwd)/upstream" \ │
│ + --node-pty-dir "${node-pty}/lib/nod │
│ e_modules/node-pty" \ │
│ + --build nix \ │
│ + --clean no │
│ + │
│ + runHook postBuild │
│ + ''; │
│ + │
│ + installPhase = '' │
│ + runHook preInstall │
│ + │
│ + #============================== │
│ ============================================ │
│ + # Create a custom Electron tree │
│ with app resources co-located. │
│ + # │
│ + # On NixOS, the stock electron- │
│ unwrapped lives in a read-only store │
│ + # path. Chromium computes proc │
│ ess.resourcesPath from /proc/self/exe, │
│ + # so it always points to electr │
│ on-unwrapped's resources/ dir - which │
│ + # doesn't contain the app's loc │
│ ale JSONs, tray icons, etc. When │
│ + # ELECTRON_FORCE_IS_PACKAGED=tr │
│ ue, the app reads en-US.json from │
│ + # resourcesPath at module load │
│ time (before frame-fix-wrapper.js can │
│ + # correct the path), causing an │
│ ENOENT crash. │
│ + # │
│ + # Solution: copy the Electron E │
│ LF binary into our own tree so that │
│ + # /proc/self/exe resolves here, │
│ then merge both Electron's and the │
│ + # app's resources into resource │
│ s/. Everything else (shared libs, │
│ + # .pak files, locales/) is syml │
│ inked to avoid duplication. │
│ + #============================== │
│ ============================================ │
│ + electron_tree=$out/lib/claude-d │
│ esktop/electron │
│ + │
│ + mkdir -p $electron_tree/resourc │
│ es │
│ + │
│ + # Copy the ELF binary - MUST be │
│ a real copy (not symlink) so that │
│ + # /proc/self/exe resolves to ou │
│ r tree │
│ + cp ${electronDir}/electron $ele │
│ ctron_tree/electron │
│ + chmod +x $electron_tree/electro │
│ n │
│ + │
│ + # Symlink everything else from │
│ electron-unwrapped │
│ + for item in ${electronDir}/*; d │
│ o │
│ + name=$(basename "$item") │
│ + [[ "$name" = "electron" ]] && │
│ continue │
│ + [[ "$name" = "resources" ]] & │
│ & continue │
│ + ln -s "$item" "$electron_tree │
│ /$name" │
│ + done │
│ + │
│ + # Populate resources/ - start w │
│ ith Electron's own (default_app.asar) │
│ + for item in ${electronDir}/reso │
│ urces/*; do │
│ + ln -s "$item" "$electron_tree │
│ /resources/$(basename "$item")" │
│ + done │
│ + │
│ + # Install app.asar and unpacked │
│ resources into the merged tree │
│ + cp build/electron-app/app.asar │
│ $electron_tree/resources/ │
│ + cp -r build/electron-app/app.as │
│ ar.unpacked $electron_tree/resources/ │
│ + │
│ + # Install tray icons into resou │
│ rces │
│ + for tray_icon in build/electron │
│ -app/nix-resources/Tray*; do │
│ + [[ -f "$tray_icon" ]] && cp " │
│ $tray_icon" $electron_tree/resources/ │
│ + done │
│ + │
│ + # Install SSH helpers into reso │
│ urces │
│ + if [[ -d build/electron-app/nix │
│ -resources/claude-ssh ]]; then │
│ + cp -r build/electron-app/nix- │
│ resources/claude-ssh \ │
│ + $electron_tree/resources/ │
│ + fi │
│ + │
│ + # Install cowork resources (smo │
│ l-bin, plugin shim) │
│ + for cowork_res in build/electro │
│ n-app/nix-resources/smol-bin.*.vhdx \ │
│ + build/electro │
│ n-app/nix-resources/cowork-plugin-shim.sh; d │
│ o │
│ + if [[ -f "$cowork_res" ]]; th │
│ en │
│ + cp "$cowork_res" $electron_ │
│ tree/resources/ │
│ + echo "Installed cowork reso │
│ urce: $(basename "$cowork_res")" │
│ + fi │
│ + done │
│ + │
│ + # Install ion-dist static asset │
│ s (app:// protocol handler root for │
│ + # Third-Party Inference setup - │
│ see issue #488) │
│ + if [[ -d build/electron-app/nix │
│ -resources/ion-dist ]]; then │
│ + cp -r build/electron-app/nix- │
│ resources/ion-dist \ │
│ + $electron_tree/resources/ │
│ + echo "Installed cowork resour │
│ ce: ion-dist" │
│ + fi │
│ + │
│ + # Install locale JSON files int │
│ o resources │
│ + for locale_json in build/claude │
│ -extract/lib/net45/resources/*-*.json; do │
│ + [[ -f "$locale_json" ]] \ │
│ + && cp "$locale_json" $elect │
│ ron_tree/resources/ │
│ + done │
│ + │
│ + # Create the electron wrapper - │
│ replicates the env setup from the │
│ + # stock electron wrapper (GIO, │
│ GTK, GDK_PIXBUF, XDG_DATA_DIRS) but │
│ + # execs our custom binary. We │
│ extract everything except the final │
│ + # exec line from the stock wrap │
│ per, then append our own exec. │
│ + head -n -1 ${electron}/bin/elec │
│ tron > $electron_tree/electron-wrapper │
│ + echo "exec \"$electron_tree/ele │
│ ctron\" \"\$@\"" >> $electron_tree/electron- │
│ wrapper │
│ + chmod +x $electron_tree/electro │
│ n-wrapper │
│ + │
│ + # Update CHROME_DEVEL_SANDBOX t │
│ o point to our tree's chrome-sandbox │
│ + substituteInPlace $electron_tre │
│ e/electron-wrapper \ │
│ + --replace-quiet "${electron}/ │
│ libexec/electron/chrome-sandbox" \ │
│ + "$electron_tree/chrome-sand │
│ box" │
│ + │
│ + #============================== │
│ ============================================ │
│ + # Standard install (icons, desk │
│ top file, launcher) │
│ + #============================== │
│ ============================================ │
│ + │
│ + # Convenience symlink for resou │
│ rces dir (used by launcher, FHS, etc.) │
│ + ln -s $electron_tree/resources │
│ $out/lib/claude-desktop/resources │
│ + │
│ + # Install icons │
│ + for size in 16 24 32 48 64 256; │
│ do │
│ + icon_dir=$out/share/icons/hic │
│ olor/"$size"x"$size"/apps │
│ + mkdir -p "$icon_dir" │
│ + icon=$(find build/ -name "cla │
│ ude_*''${size}x''${size}x32.png" 2>/dev/null │
│ | head -1) │
│ + if [[ -n "$icon" ]]; then │
│ + install -Dm644 "$icon" "$ic │
│ on_dir/claude-desktop.png" │
│ + fi │
│ + done │
│ + │
│ + # Install shared launcher libra │
│ ry + doctor (launcher-common.sh │
│ + # sources doctor.sh at runtime, │
│ so both must live in the same dir) │
│ + install -Dm755 ${repoSrc}/scrip │
│ ts/launcher-common.sh \ │
│ + $out/lib/claude-desktop/launc │
│ her-common.sh │
│ + install -Dm755 ${repoSrc}/scrip │
│ ts/doctor.sh \ │
│ + $out/lib/claude-desktop/docto │
│ r.sh │
│ + │
│ + # Install .desktop file │
│ + mkdir -p $out/share/application │
│ s │
│ + install -Dm644 ${desktopItem}/s │
│ hare/applications/* $out/share/applications/ │
│ + │
│ + # Create launcher script │
│ + mkdir -p $out/bin │
│ + cat > $out/bin/claude-desktop < │
│ <'LAUNCHER' │
│ + #!/usr/bin/env bash │
│ + # Claude Desktop launcher for NixOS │
│ + │
│ + electron_exec="ELECTRON_PLACEHOLDER" │
│ + app_path="RESOURCES_PLACEHOLDER/app.a │
│ sar" │
│ + │
│ + source "LAUNCHER_LIB_PLACEHOLDER" │
│ + │
│ + # Handle --doctor flag before anythin │
│ g else │
│ + if [[ "''${1:-}" == '--doctor' ]]; th │
│ en │
│ + run_doctor "$electron_exec" │
│ + exit $? │
│ + fi │
│ + │
│ + # Setup logging and environment │
│ + setup_logging || exit 1 │
│ + setup_electron_env │
│ + cleanup_orphaned_cowork_daemon │
│ + cleanup_stale_desktop_helpers │
│ + cleanup_stale_lock │
│ + cleanup_stale_cowork_socket │
│ + │
│ + # Log startup info │
│ + log_message '--- Claude Desktop Launc │
│ her Start (NixOS) ---' │
│ + log_message "Timestamp: $(date)" │
│ + log_message "Arguments: $@" │
│ + log_session_env │
│ + │
│ + # Check for display │
│ + if ! check_display; then │
│ + log_message 'No display detected (TT │
│ Y session)' │
│ + echo 'Error: Claude Desktop requires │
│ a graphical desktop environment.' >&2 │
│ + echo 'Please run from within an X11 │
│ or Wayland session, not from a TTY.' >&2 │
│ + exit 1 │
│ + fi │
│ + │
│ + # Detect display backend (handles CLA │
│ UDE_USE_WAYLAND) │
│ + detect_display_backend │
│ + │
│ + # Build Electron arguments │
│ + build_electron_args 'nix' │
│ + │
│ + # Intentionally NOT appended: app.asa │
│ r sits in Electron's default │
│ + # resources/ dir next to the binary, │
│ so Electron auto-loads it. Passing │
│ + # the path again makes Electron treat │
│ it as a file-to-open, which the │
│ + # app forwards to its file-drop handl │
│ er, producing a spurious │
│ + # "Attach app.asar?" prompt on launch │
│ and on every taskbar reopen │
│ + # (the second-instance argv path). Om │
│ itting it is the root-cause fix. │
│ + # See issue #696. │
│ + log_message "App (auto-loaded by Elec │
│ tron): $app_path" │
│ + │
│ + # Execute Electron and keep the launc │
│ her alive so explicit quit can │
│ + # clean up Desktop-owned helpers that │
│ outlive the Electron main process. │
│ + log_message "Executing: $electron_exe │
│ c ''${electron_args[*]} $*" │
│ + run_electron_and_cleanup "$electron_e │
│ xec" "''${electron_args[@]}" "$@" │
│ + exit $? │
│ + LAUNCHER │
│ + # Substitute placeholders - ele │
│ ctron_exec points to our custom │
│ + # wrapper (which sets GTK/GIO e │
│ nv then execs our merged binary) │
│ + substituteInPlace $out/bin/clau │
│ de-desktop \ │
│ + --replace-fail "ELECTRON_PLAC │
│ EHOLDER" "$electron_tree/electron-wrapper" \ │
│ + --replace-fail "RESOURCES_PLA │
│ CEHOLDER" "$electron_tree/resources" \ │
│ + --replace-fail "LAUNCHER_LIB_ │
│ PLACEHOLDER" "$out/lib/claude-desktop/launch │
│ er-common.sh" │
│ + chmod +x $out/bin/claude-deskto │
│ p │
│ + │
│ + runHook postInstall │
│ + ''; │
│ + │
│ + meta = with lib; { │
│ + description = "Claude Desktop for Lin │
│ ux"; │
│ + homepage = "https://github.com/aaddri │
│ ck/claude-desktop-debian"; │
│ + license = licenses.unfree; │
│ + platforms = ["x86_64-linux" "aarch64- │
│ linux"]; │
│ + sourceProvenance = with sourceTypes; │
│ [binaryNativeCode]; │
│ + mainProgram = "claude-desktop"; │
│ + }; │
│ + } │
└──────────────────────────────────────────────┘
┌─ outputs/pkgs/claude-desktop/fhs.nix ──────────────────────────────────────┐
│ diff --git a/outputs/pkgs/claude-desktop/fhs.nix b/outputs/pkgs/claude-desktop/fhs │
│ .nix │
│ new file mode 100644 │
│ index 0000000..6072a0f │
│ --- /dev/null │
│ +++ b/outputs/pkgs/claude-desktop/fhs.nix │
│ @@ -0,0 +1,41 @@ │
│ +{ │
│ + buildFHSEnv, │
│ + bubblewrap, │
│ + claude-desktop, │
│ + nodejs, │
│ + docker, │
│ + docker-compose, │
│ + openssl, │
│ + glibc, │
│ + uv, │
│ +}: │
│ +buildFHSEnv { │
│ + name = "claude-desktop"; │
│ + │
│ + targetPkgs = pkgs: [ │
│ + bubblewrap │
│ + claude-desktop │
│ + docker │
│ + docker-compose │
│ + glibc │
│ + nodejs │
│ + openssl │
│ + uv │
│ + ]; │
│ + │
│ + runScript = "${claude-desktop}/bin/claude-desktop"; │
│ + │
│ + extraInstallCommands = '' │
│ + mkdir -p $out/share/applications │
│ + cp ${claude-desktop}/share/applications/* $out/share/applications/ │
│ + │
│ + mkdir -p $out/share/icons │
│ + cp -r ${claude-desktop}/share/icons/* $out/share/icons/ │
│ + ''; │
│ + │
│ + meta = │
│ + claude-desktop.meta │
│ + // { │
│ + description = "Claude Desktop for Linux (FHS environment for MCP servers)"; │
│ + }; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...s/pkgs/claude-desktop/fhs.nix ───┐
│ diff --git a/outputs/pkgs/claude-desktop/fhs │
│ .nix b/outputs/pkgs/claude-desktop/fhs.nix │
│ new file mode 100644 │
│ index 0000000..6072a0f │
│ --- /dev/null │
│ +++ b/outputs/pkgs/claude-desktop/fhs.nix │
│ @@ -0,0 +1,41 @@ │
│ +{ │
│ + buildFHSEnv, │
│ + bubblewrap, │
│ + claude-desktop, │
│ + nodejs, │
│ + docker, │
│ + docker-compose, │
│ + openssl, │
│ + glibc, │
│ + uv, │
│ +}: │
│ +buildFHSEnv { │
│ + name = "claude-desktop"; │
│ + │
│ + targetPkgs = pkgs: [ │
│ + bubblewrap │
│ + claude-desktop │
│ + docker │
│ + docker-compose │
│ + glibc │
│ + nodejs │
│ + openssl │
│ + uv │
│ + ]; │
│ + │
│ + runScript = "${claude-desktop}/bin/claude │
│ -desktop"; │
│ + │
│ + extraInstallCommands = '' │
│ + mkdir -p $out/share/applications │
│ + cp ${claude-desktop}/share/applications │
│ /* $out/share/applications/ │
│ + │
│ + mkdir -p $out/share/icons │
│ + cp -r ${claude-desktop}/share/icons/* $ │
│ out/share/icons/ │
│ + ''; │
│ + │
│ + meta = │
│ + claude-desktop.meta │
│ + // { │
│ + description = "Claude Desktop for Lin │
│ ux (FHS environment for MCP servers)"; │
│ + }; │
│ +} │
└──────────────────────────────────────────────┘
┌─ outputs/pkgs/claude-desktop/node-pty.nix ─────────────────────────────────┐
│ diff --git a/outputs/pkgs/claude-desktop/node-pty.nix b/outputs/pkgs/claude-deskto │
│ p/node-pty.nix │
│ new file mode 100644 │
│ index 0000000..4c01412 │
│ --- /dev/null │
│ +++ b/outputs/pkgs/claude-desktop/node-pty.nix │
│ @@ -0,0 +1,51 @@ │
│ +{ │
│ + lib, │
│ + buildNpmPackage, │
│ + fetchFromGitHub, │
│ + python3, │
│ + node-gyp, │
│ +}: │
│ +buildNpmPackage rec { │
│ + pname = "node-pty"; │
│ + version = "1.1.0"; │
│ + │
│ + src = fetchFromGitHub { │
│ + owner = "microsoft"; │
│ + repo = "node-pty"; │
│ + rev = "v${version}"; │
│ + hash = "sha256-R0QxTw3tNJvW4aEi+GOF0iZhGgI42HTYJih90CdF18I="; │
│ + }; │
│ + │
│ + npmDepsHash = "sha256-HRv/4NO7CHkPs7ld8lx61n2cty0EhmWVrpH/1Vqh+Nk="; │
│ + │
│ + # node-gyp needs python3 for native compilation │
│ + nativeBuildInputs = [python3 node-gyp]; │
│ + │
│ + # chokidar (dev dep) has an optional dep on fsevents (macOS-only). │
│ + # The Nix npm deps fetcher excludes it, so npm ci sees a lock/json │
│ + # mismatch. Strip the reference from the lock file to fix the sync. │
│ + postPatch = '' │
│ + sed -i '/"fsevents"/d' package-lock.json │
│ + ''; │
│ + │
│ + # Default npmBuildHook only runs "npm run build" (tsc), but we also │
│ + # need the native addon. Run both explicitly. │
│ + buildPhase = '' │
│ + runHook preBuild │
│ + npm run build │
│ + node-gyp rebuild │
│ + runHook postBuild │
│ + ''; │
│ + │
│ + # npmInstallHook doesn't copy the native addon - do it ourselves │
│ + postInstall = '' │
│ + cp -r build $out/lib/node_modules/node-pty/ │
│ + ''; │
│ + │
│ + meta = with lib; { │
│ + description = "Fork pseudoterminals in Node.JS"; │
│ + homepage = "https://github.com/microsoft/node-pty"; │
│ + license = licenses.mit; │
│ + platforms = platforms.linux; │
│ + }; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...s/claude-desktop/node-pty.nix ───┐
│ diff --git a/outputs/pkgs/claude-desktop/nod │
│ e-pty.nix b/outputs/pkgs/claude-desktop/node │
│ -pty.nix │
│ new file mode 100644 │
│ index 0000000..4c01412 │
│ --- /dev/null │
│ +++ b/outputs/pkgs/claude-desktop/node-pty.n │
│ ix │
│ @@ -0,0 +1,51 @@ │
│ +{ │
│ + lib, │
│ + buildNpmPackage, │
│ + fetchFromGitHub, │
│ + python3, │
│ + node-gyp, │
│ +}: │
│ +buildNpmPackage rec { │
│ + pname = "node-pty"; │
│ + version = "1.1.0"; │
│ + │
│ + src = fetchFromGitHub { │
│ + owner = "microsoft"; │
│ + repo = "node-pty"; │
│ + rev = "v${version}"; │
│ + hash = "sha256-R0QxTw3tNJvW4aEi+GOF0iZh │
│ GgI42HTYJih90CdF18I="; │
│ + }; │
│ + │
│ + npmDepsHash = "sha256-HRv/4NO7CHkPs7ld8lx │
│ 61n2cty0EhmWVrpH/1Vqh+Nk="; │
│ + │
│ + # node-gyp needs python3 for native compi │
│ lation │
│ + nativeBuildInputs = [python3 node-gyp]; │
│ + │
│ + # chokidar (dev dep) has an optional dep │
│ on fsevents (macOS-only). │
│ + # The Nix npm deps fetcher excludes it, s │
│ o npm ci sees a lock/json │
│ + # mismatch. Strip the reference from the │
│ lock file to fix the sync. │
│ + postPatch = '' │
│ + sed -i '/"fsevents"/d' package-lock.jso │
│ n │
│ + ''; │
│ + │
│ + # Default npmBuildHook only runs "npm run │
│ build" (tsc), but we also │
│ + # need the native addon. Run both explici │
│ tly. │
│ + buildPhase = '' │
│ + runHook preBuild │
│ + npm run build │
│ + node-gyp rebuild │
│ + runHook postBuild │
│ + ''; │
│ + │
│ + # npmInstallHook doesn't copy the native │
│ addon - do it ourselves │
│ + postInstall = '' │
│ + cp -r build $out/lib/node_modules/node- │
│ pty/ │
│ + ''; │
│ + │
│ + meta = with lib; { │
│ + description = "Fork pseudoterminals in │
│ Node.JS"; │
│ + homepage = "https://github.com/microsof │
│ t/node-pty"; │
│ + license = licenses.mit; │
│ + platforms = platforms.linux; │
│ + }; │
│ +} │
└──────────────────────────────────────────────┘
┌─ outputs/pkgs/default.nix ─────────────────────────────────────────────────┐
│ diff --git a/outputs/pkgs/default.nix b/outputs/pkgs/default.nix │
│ index 76b919e..7148a8e 100644 │
│ --- a/outputs/pkgs/default.nix │
│ +++ b/outputs/pkgs/default.nix │
│ @@ -7,6 +7,18 @@ │
│ perSystem = {pkgs, ...}: let │
│ inherit (pkgs) callPackage qt6Packages writeTextFile; │
│ │
│ + # claude-desktop is unfree, our package set isnt; scope an unfree │
│ + # nixpkgs to just it rather than flipping allowUnfree globally │
│ + pkgsClaude = import inputs.nixpkgs { │
│ + inherit (pkgs.stdenv.hostPlatform) system; │
│ + config.allowUnfreePredicate = p: lib.getName p == "claude-desktop"; │
│ + }; │
│ + claudeDesktop = rec { │
│ + node-pty = pkgsClaude.callPackage ./claude-desktop/node-pty.nix {}; │
│ + package = pkgsClaude.callPackage ./claude-desktop {inherit node-pty;}; │
│ + fhs = pkgsClaude.callPackage ./claude-desktop/fhs.nix {claude-desktop = pac │
│ kage;}; │
│ + }; │
│ + │
│ projectPlus = { │
│ fpp-config = callPackage ./project-plus/fpp-config.nix {}; │
│ fpp-launcher = callPackage ./project-plus/fpp-launcher.nix {}; │
│ @@ -41,6 +53,9 @@ │
│ │
│ claude-code = callPackage ./claude-code {}; │
│ │
│ + claude-desktop = claudeDesktop.package; │
│ + claude-desktop-fhs = claudeDesktop.fhs; │
│ + │
│ # disabled temporarily - regex-syntax compile failure on current nixpkgs │
│ # spotify-player = pkgs.spotify-player.override { │
│ # withImage = false; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ outputs/pkgs/default.nix ───────────┐
│ diff --git a/outputs/pkgs/default.nix b/outp │
│ uts/pkgs/default.nix │
│ index 76b919e..7148a8e 100644 │
│ --- a/outputs/pkgs/default.nix │
│ +++ b/outputs/pkgs/default.nix │
│ @@ -7,6 +7,18 @@ │
│ perSystem = {pkgs, ...}: let │
│ inherit (pkgs) callPackage qt6Packages │
│ writeTextFile; │
│ │
│ + # claude-desktop is unfree, our package │
│ set isnt; scope an unfree │
│ + # nixpkgs to just it rather than flippi │
│ ng allowUnfree globally │
│ + pkgsClaude = import inputs.nixpkgs { │
│ + inherit (pkgs.stdenv.hostPlatform) sy │
│ stem; │
│ + config.allowUnfreePredicate = p: lib. │
│ getName p == "claude-desktop"; │
│ + }; │
│ + claudeDesktop = rec { │
│ + node-pty = pkgsClaude.callPackage ./c │
│ laude-desktop/node-pty.nix {}; │
│ + package = pkgsClaude.callPackage ./cl │
│ aude-desktop {inherit node-pty;}; │
│ + fhs = pkgsClaude.callPackage ./claude │
│ -desktop/fhs.nix {claude-desktop = package;} │
│ ; │
│ + }; │
│ + │
│ projectPlus = { │
│ fpp-config = callPackage ./project-pl │
│ us/fpp-config.nix {}; │
│ fpp-launcher = callPackage ./project- │
│ plus/fpp-launcher.nix {}; │
│ @@ -41,6 +53,9 @@ │
│ │
│ claude-code = callPackage ./claude-co │
│ de {}; │
│ │
│ + claude-desktop = claudeDesktop.packag │
│ e; │
│ + claude-desktop-fhs = claudeDesktop.fh │
│ s; │
│ + │
│ # disabled temporarily - regex-syntax │
│ compile failure on current nixpkgs │
│ # spotify-player = pkgs.spotify-playe │
│ r.override { │
│ # withImage = false; │
└──────────────────────────────────────────────┘
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET