Kieran's opinionated (and probably slightly dumb) nix config
1#!/usr/bin/env bash
2
3set -euo pipefail
4
5# Configuration
6COPILOT_TOKEN=$(bash ~/.config/crush/copilot.sh)
7ANTHROPIC_TOKEN=$(bunx anthropic-api-key)
8
9# Colors for output
10RED='\033[0;31m'
11GREEN='\033[0;32m'
12YELLOW='\033[1;33m'
13BLUE='\033[0;34m'
14NC='\033[0m' # No Color
15
16echo -e "${GREEN}Fetching latest models from APIs...${NC}"
17
18# Fetch Copilot models
19echo " → Fetching Copilot models..."
20COPILOT_MODELS=$(curl -s https://api.githubcopilot.com/models \
21 -H "Authorization: Bearer $COPILOT_TOKEN" \
22 -H "Editor-Version: CRUSH/1.0" \
23 -H "Editor-Plugin-Version: CRUSH/1.0" \
24 -H "Copilot-Integration-Id: vscode-chat")
25
26# Fetch Anthropic models
27echo " → Fetching Anthropic models..."
28ANTHROPIC_MODELS=$(curl -s https://api.anthropic.com/v1/models \
29 -H "Authorization: Bearer $ANTHROPIC_TOKEN" \
30 -H "anthropic-version: 2023-06-01" \
31 -H "anthropic-beta: oauth-2025-04-20")
32
33# Extract model-picker enabled Copilot models
34echo -e "\n${GREEN}Copilot models (model_picker_enabled):${NC}"
35echo "$COPILOT_MODELS" | jq -r '.data[] | select(.model_picker_enabled == true) | " ✓ \(.id) - \(.name)"'
36
37# Extract all Anthropic models
38echo -e "\n${GREEN}Anthropic models:${NC}"
39echo "$ANTHROPIC_MODELS" | jq -r '.data[] | " ✓ \(.id) - \(.display_name)"'
40
41# Generate Copilot models Nix config
42generate_copilot_models() {
43 echo "$COPILOT_MODELS" | jq -r '.data[] | select(.model_picker_enabled == true) |
44 (if .capabilities.supports.tool_calls then true else false end) as $can_reason |
45 (if .capabilities.supports.vision then true else false end) as $supports_attachments |
46 " {\n" +
47 " id = \"\(.id)\";\n" +
48 " name = \"Copilot: \(.name)\";\n" +
49 " cost_per_1m_in = 0;\n" +
50 " cost_per_1m_out = 0;\n" +
51 " cost_per_1m_in_cached = 0;\n" +
52 " cost_per_1m_out_cached = 0;\n" +
53 " context_window = \(.capabilities.limits.max_context_window_tokens);\n" +
54 " default_max_tokens = \(.capabilities.limits.max_output_tokens);\n" +
55 " can_reason = \($can_reason);\n" +
56 " has_reasoning_efforts = false;\n" +
57 " supports_attachments = \($supports_attachments);\n" +
58 " }"'
59}
60
61# Generate Anthropic models Nix config with pricing
62generate_anthropic_models() {
63 echo "$ANTHROPIC_MODELS" | jq -r '.data[] |
64 # Determine pricing based on model family
65 (if (.id | contains("opus-4")) then
66 {in: 15.0, out: 75.0, in_cached: 1.5, out_cached: 75.0}
67 elif (.id | contains("sonnet-4")) then
68 {in: 3.0, out: 15.0, in_cached: 0.225, out_cached: 15.0}
69 elif (.id | contains("3-7-sonnet")) then
70 {in: 2.5, out: 12.0, in_cached: 0.187, out_cached: 12.0}
71 elif (.id | contains("3-5-sonnet")) then
72 {in: 3.0, out: 15.0, in_cached: 0.225, out_cached: 15.0}
73 elif (.id | contains("3-5-haiku")) then
74 {in: 0.8, out: 4.0, in_cached: 0.06, out_cached: 4.0}
75 elif (.id | contains("3-haiku")) then
76 {in: 0.25, out: 1.25, in_cached: 0.03, out_cached: 1.25}
77 elif (.id | contains("3-opus")) then
78 {in: 15.0, out: 75.0, in_cached: 1.5, out_cached: 75.0}
79 else
80 {in: 3.0, out: 15.0, in_cached: 0.225, out_cached: 15.0}
81 end) as $pricing |
82
83 # Determine context window and max tokens
84 (if (.id | test("claude-sonnet-4-5"))
85 then {context: 200000, max_tokens: 64000}
86 elif (.id | test("claude-sonnet-4-"))
87 then {context: 200000, max_tokens: 64000}
88 elif (.id | test("claude-3-7-sonnet"))
89 then {context: 200000, max_tokens: 64000}
90 elif (.id | test("claude-opus-4-1"))
91 then {context: 200000, max_tokens: 32000}
92 elif (.id | test("claude-opus-4-"))
93 then {context: 200000, max_tokens: 32000}
94 elif (.id | test("claude-3-5-haiku"))
95 then {context: 200000, max_tokens: 8192}
96 elif (.id | test("claude-3-haiku"))
97 then {context: 200000, max_tokens: 4096}
98 else
99 {context: 200000, max_tokens: 8192}
100 end) as $limits |
101
102 # Determine has_reasoning_efforts
103 (if (.id | contains("opus-4")) then true else false end) as $has_reasoning |
104
105 " {\n" +
106 " id = \"\(.id)\";\n" +
107 " name = \"\(.display_name)\";\n" +
108 " cost_per_1m_in = \($pricing.in);\n" +
109 " cost_per_1m_out = \($pricing.out);\n" +
110 " cost_per_1m_in_cached = \($pricing.in_cached);\n" +
111 " cost_per_1m_out_cached = \($pricing.out_cached);\n" +
112 " context_window = \($limits.context);\n" +
113 " default_max_tokens = \($limits.max_tokens);\n" +
114 " can_reason = true;\n" +
115 " has_reasoning_efforts = \($has_reasoning);\n" +
116 " supports_attachments = true;\n" +
117 " }"'
118}
119
120# Generate formatted model arrays
121COPILOT_MODELS_NIX=$(generate_copilot_models)
122ANTHROPIC_MODELS_NIX=$(generate_anthropic_models)
123
124# Output results
125echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
126echo -e "${YELLOW}COPILOT MODELS${NC}"
127echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
128echo -e "\nPaste this into your copilot.models array:\n"
129echo " models = ["
130echo "$COPILOT_MODELS_NIX"
131echo " ];"
132
133echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
134echo -e "${YELLOW}ANTHROPIC MODELS${NC}"
135echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
136echo -e "\nPaste this into your claude-pro.models array:\n"
137echo " models = ["
138echo "$ANTHROPIC_MODELS_NIX"
139echo " ];"
140
141echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
142echo -e "\n${GREEN}Summary:${NC}"
143echo " • Copilot models: $(echo "$COPILOT_MODELS" | jq '[.data[] | select(.model_picker_enabled == true)] | length')"
144echo " • Anthropic models: $(echo "$ANTHROPIC_MODELS" | jq '.data | length')"