Kieran's opinionated (and probably slightly dumb) nix config
1{
2 lib,
3 pkgs,
4 config,
5 inputs,
6 ...
7}:
8{
9 options.atelier.shell.enable = lib.mkEnableOption "Custom shell config";
10 config = lib.mkIf config.atelier.shell.enable {
11 programs.oh-my-posh = {
12 enable = true;
13 enableZshIntegration = true;
14 settings = {
15 upgrade = {
16 notice = false;
17 interval = "2w";
18 auto = false;
19 };
20 version = 2;
21 final_space = true;
22 console_title_template = "{{ .Shell }} in {{ .Folder }}";
23 blocks = [
24 {
25 type = "prompt";
26 alignment = "left";
27 newline = true;
28 segments = [
29 {
30 type = "session";
31 background = "transparent";
32 foreground = "yellow";
33 template = "{{ if .SSHSession }}{{.HostName}} {{ end }}";
34 }
35 {
36 type = "path";
37 style = "plain";
38 background = "transparent";
39 foreground = "blue";
40 template = "{{ .Path }} ";
41 properties = {
42 style = "full";
43 };
44 }
45 {
46 type = "git";
47 style = "plain";
48 foreground = "p:grey";
49 background = "transparent";
50 template = "{{if not .Detached}}{{ .HEAD }}{{else}}@{{ printf \"%.7s\" .Commit.Sha }}{{end}}{{ if .Staging.Changed }} ({{ .Staging.String }}){{ end }}{{ if .Working.Changed }}*{{ end }} <cyan>{{ if .BranchStatus }}{{ .BranchStatus }}{{ end }}</>";
51 properties = {
52 branch_icon = "";
53 branch_identical_icon = "";
54 branch_gone_icon = "";
55 branch_ahead_icon = "⇡";
56 branch_behind_icon = "⇣";
57 commit_icon = "@";
58 fetch_status = true;
59 };
60 }
61 ];
62 }
63 {
64 type = "rprompt";
65 overflow = "hidden";
66 segments = [
67 {
68 type = "executiontime";
69 style = "plain";
70 foreground = "yellow";
71 background = "transparent";
72 template = "{{ .FormattedMs }}";
73 properties = {
74 threshold = 3000;
75 };
76 }
77 {
78 type = "nix-shell";
79 style = "plain";
80 foreground = "red";
81 background = "transparent";
82 template = ''{{if ne .Type "unknown" }} {{ .Type }}{{ end }}'';
83 }
84 ];
85 }
86 {
87 type = "prompt";
88 alignment = "left";
89 newline = true;
90 segments = [
91 {
92 type = "text";
93 style = "plain";
94 foreground_templates = [
95 "{{if gt .Code 0}}red{{end}}"
96 "{{if eq .Code 0}}magenta{{end}}"
97 ];
98 background = "transparent";
99 template = "❯";
100 }
101 ];
102 }
103 ];
104 transient_prompt = {
105 foreground_templates = [
106 "{{if gt .Code 0}}red{{end}}"
107 "{{if eq .Code 0}}magenta{{end}}"
108 ];
109 background = "transparent";
110 template = "❯ ";
111 };
112 secondary_prompt = {
113 foreground = "p:gray";
114 background = "transparent";
115 template = "❯❯ ";
116 };
117 palette = {
118 grey = "#6c6c6c";
119 };
120 };
121 };
122
123 programs.zsh = {
124 enable = true;
125 enableCompletion = true;
126 syntaxHighlighting.enable = true;
127
128 shellAliases = {
129 cat = "bat";
130 ls = "eza";
131 ll = "eza -l";
132 la = "eza -la";
133 gc = "git commit";
134 gp = "git push";
135 rr = "rm -Rf";
136 ghrpc = "gh repo create -c";
137 goops = "git commit --amend --no-edit && git push --force-with-lease";
138 vi = "nvim";
139 vim = "nvim";
140 };
141 initContent = ''
142 #ssh auto reconnect
143 assh() {
144 local host=$1
145 local port=$2
146 while true; do
147 ssh -p $port -o "BatchMode yes" $host || sleep 1
148 done
149 }
150 # hackatime summary
151 summary() {
152 local user_id=$1
153 curl -X 'GET' \
154 "https://waka.hackclub.com/api/summary?user=''${user_id}&interval=month" \
155 -H 'accept: application/json' \
156 -H 'Authorization: Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608' | jq '. + {
157 "total_categories_sum": (.categories | map(.total) | add),
158 "total_categories_human_readable": (
159 (.categories | map(.total) | add) as $total_seconds |
160 "\($total_seconds / 3600 | floor)h \(($total_seconds % 3600) / 60 | floor)m \($total_seconds % 60)s"
161 ),
162 "projectsKeys": (
163 .projects | sort_by(-.total) | map(.key)
164 )
165 }'
166 }
167
168 tangled() {
169 # Configuration variables - set these to your defaults
170 local default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul"
171 local default_github_username="taciturnaxolotl"
172 local extracted_github_username=""
173
174 # Check if current directory is a git repository
175 if ! git rev-parse --is-inside-work-tree &>/dev/null; then
176 echo "Not a git repository"
177 return 1
178 fi
179
180 # Get the repository name from the current directory
181 local repo_name=$(basename "$(git rev-parse --show-toplevel)")
182
183 # Check if origin remote exists and points to ember
184 local origin_url=$(git remote get-url origin 2>/dev/null)
185 local origin_ember=false
186
187 if [[ -n "$origin_url" ]]; then
188 # Try to extract GitHub username if origin is a GitHub URL
189 if [[ "$origin_url" == *"github.com"* ]]; then
190 extracted_github_username=$(echo "$origin_url" | sed -E 's/.*github\.com[:/]([^/]+)\/.*$/\1/')
191 # Override the default username with the extracted one
192 default_github_username=$extracted_github_username
193 fi
194
195 if [[ "$origin_url" == *"ember"* ]]; then
196 origin_ember=true
197 echo "✅ Origin remote exists and points to ember"
198 else
199 echo "⚠️ Origin remote exists but doesn't point to ember"
200 fi
201 else
202 echo "⚠️ Origin remote doesn't exist"
203 fi
204
205 # Check if github remote exists
206 local github_exists=false
207 if git remote get-url github &>/dev/null; then
208 github_exists=true
209 echo "✅ GitHub remote exists"
210 else
211 echo "⚠️ GitHub remote doesn't exist"
212 fi
213
214 # Fix remotes if needed
215 if [[ "$origin_ember" = false || "$github_exists" = false ]]; then
216 echo "Setting up remotes..."
217
218 # Prompt for PLC identifier if needed
219 local plc_id=""
220 if [[ "$origin_ember" = false ]]; then
221 echo -n "Enter your PLC identifier [default: $default_plc_id]: "
222 read plc_input
223 plc_id=''${plc_input:-$default_plc_id}
224 fi
225
226 # Prompt for GitHub username with default from origin if available
227 local github_username=""
228 if [[ "$github_exists" = false ]]; then
229 echo -n "Enter your GitHub username [default: $default_github_username]: "
230 read github_input
231 github_username=''${github_input:-$default_github_username}
232 fi
233
234 # Set up origin remote if needed
235 if [[ "$origin_ember" = false && -n "$plc_id" ]]; then
236 if git remote get-url origin &>/dev/null; then
237 git remote remove origin
238 fi
239 git remote add origin "git@ember:''${plc_id}/''${repo_name}"
240 echo "✅ Set up origin remote: git@ember:''${plc_id}/''${repo_name}"
241 fi
242
243 # Set up GitHub remote if needed
244 if [[ "$github_exists" = false && -n "$github_username" ]]; then
245 git remote add github "git@github.com:''${github_username}/''${repo_name}.git"
246 echo "✅ Set up GitHub remote: git@github.com:''${github_username}/''${repo_name}.git"
247 fi
248 else
249 echo "Remotes are correctly configured"
250 fi
251 }
252
253 # Post AtProto status updates
254 now() {
255 local message=""
256 local prompt_message=true
257 local account1_name=""
258 local account2_name=""
259 local account1_jwt=""
260 local account2_jwt=""
261
262 # Load account information from agenix secrets
263 if [[ -f "/run/agenix/bluesky" ]]; then
264 source "/run/agenix/bluesky"
265 else
266 echo "Error: Bluesky credentials file not found at /run/agenix/bluesky"
267 return 1
268 fi
269
270 # Parse arguments
271 while [[ $# -gt 0 ]]; do
272 case "$1" in
273 -m|--message)
274 message="$2"
275 prompt_message=false
276 shift 2
277 ;;
278 *)
279 echo "Usage: now [-m|--message \"your message\"]"
280 return 1
281 ;;
282 esac
283 done
284
285 # Prompt for message if none provided
286 if [[ "$prompt_message" = true ]]; then
287 echo -n "$ACCOUNT1 is: "
288 read message
289
290 if [[ -z "$message" ]]; then
291 echo "No message provided. Aborting."
292 return 1
293 fi
294 fi
295
296 # Generate JWT for ACCOUNT1
297 local account1_response=$(curl -s -X POST \
298 -H "Content-Type: application/json" \
299 -d '{
300 "identifier": "'$ACCOUNT1'",
301 "password": "'$ACCOUNT1_PASSWORD'"
302 }' \
303 "https://bsky.social/xrpc/com.atproto.server.createSession")
304
305 account1_jwt=$(echo "$account1_response" | jq -r '.accessJwt')
306
307 if [[ -z "$account1_jwt" || "$account1_jwt" == "null" ]]; then
308 echo "Failed to authenticate account $ACCOUNT1"
309 echo "Response: $account1_response"
310 return 1
311 fi
312
313 # Generate JWT for ACCOUNT2
314 local account2_response=$(curl -s -X POST \
315 -H "Content-Type: application/json" \
316 -d '{
317 "identifier": "'$ACCOUNT2'",
318 "password": "'$ACCOUNT2_PASSWORD'"
319 }' \
320 "https://bsky.social/xrpc/com.atproto.server.createSession")
321
322 account2_jwt=$(echo "$account2_response" | jq -r '.accessJwt')
323
324 if [[ -z "$account2_jwt" || "$account2_jwt" == "null" ]]; then
325 echo "Failed to authenticate account $ACCOUNT2"
326 echo "Response: $account2_response"
327 return 1
328 fi
329
330 # Post to ACCOUNT1 as a.status.updates
331 local account1_post_response=$(curl -s -X POST \
332 -H "Content-Type: application/json" \
333 -H "Authorization: Bearer $account1_jwt" \
334 -d '{
335 "collection": "a.status.update",
336 "repo": "'$ACCOUNT1'",
337 "record": {
338 "$type": "a.status.update",
339 "text": "'"$message"'",
340 "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
341 }
342 }' \
343 "https://bsky.social/xrpc/com.atproto.repo.createRecord")
344
345 if [[ $(echo "$account1_post_response" | jq -r 'has("error")') == "true" ]]; then
346 echo "Error posting to $ACCOUNT1:"
347 echo "$account1_post_response" | jq
348 return 1
349 fi
350
351 # Post to ACCOUNT2 as normal post
352 local account2_post_response=$(curl -s -X POST \
353 -H "Content-Type: application/json" \
354 -H "Authorization: Bearer $account2_jwt" \
355 -d '{
356 "collection": "app.bsky.feed.post",
357 "repo": "'$ACCOUNT2'",
358 "record": {
359 "$type": "app.bsky.feed.post",
360 "text": "'"$message"'",
361 "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
362 }
363 }' \
364 "https://bsky.social/xrpc/com.atproto.repo.createRecord")
365
366 if [[ $(echo "$account2_post_response" | jq -r 'has("error")') == "true" ]]; then
367 echo "Error posting to $ACCOUNT2:"
368 echo "$account2_post_response" | jq
369 return 1
370 fi
371
372 echo "done"
373 }
374
375 zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}'
376 zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}"
377 zstyle ':completion:*' menu no
378 zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls --color $realpath'
379 zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath'
380
381 eval "$(terminal-wakatime init)"
382 '';
383 history = {
384 size = 10000;
385 path = "${config.xdg.dataHome}/zsh/history";
386 ignoreDups = true;
387 ignoreAllDups = true;
388 ignoreSpace = true;
389 expireDuplicatesFirst = true;
390 share = true;
391 extended = true;
392 append = true;
393 };
394
395 oh-my-zsh = {
396 enable = true;
397 plugins = [
398 "git"
399 "sudo"
400 "docker"
401 "git"
402 "command-not-found"
403 "colored-man-pages"
404 ];
405 };
406
407 plugins = [
408 {
409 # will source zsh-autosuggestions.plugin.zsh
410 name = "zsh-autosuggestions";
411 src = pkgs.fetchFromGitHub {
412 owner = "zsh-users";
413 repo = "zsh-autosuggestions";
414 rev = "v0.7.0";
415 sha256 = "sha256-KLUYpUu4DHRumQZ3w59m9aTW6TBKMCXl2UcKi4uMd7w=";
416 };
417 }
418 {
419 # will source zsh-sytax-highlighting
420 name = "zsh-sytax-highlighting";
421 src = pkgs.fetchFromGitHub {
422 owner = "zsh-users";
423 repo = "zsh-syntax-highlighting";
424 rev = "0.8.0";
425 sha256 = "sha256-iJdWopZwHpSyYl5/FQXEW7gl/SrKaYDEtTH9cGP7iPo=";
426 };
427 }
428 {
429 # fzf tab completion
430 name = "fzf-tab";
431 src = pkgs.fetchFromGitHub {
432 owner = "aloxaf";
433 repo = "fzf-tab";
434 rev = "v1.1.2";
435 sha256 = "sha256-Qv8zAiMtrr67CbLRrFjGaPzFZcOiMVEFLg1Z+N6VMhg=";
436 };
437 }
438 ];
439 };
440
441 programs.zoxide = {
442 enable = true;
443 enableZshIntegration = true;
444 };
445 programs.fzf = {
446 enable = true;
447 enableZshIntegration = true;
448 colors = {
449 bg = lib.mkForce "";
450 };
451 };
452 programs.atuin = {
453 enable = true;
454 settings = {
455 auto_sync = true;
456 sync_frequency = "5m";
457 sync_address = "https://api.atuin.sh";
458 search_mode = "fuzzy";
459 update_check = false;
460 style = "auto";
461 sync.records = true;
462 dotfiles.enabled = false;
463 };
464 };
465 programs.yazi = {
466 enable = true;
467 enableZshIntegration = true;
468 };
469
470 home.packages = with pkgs; [
471 inputs.terminal-wakatime.packages.${pkgs.system}.default
472 unzip
473 dog
474 dust
475 wget
476 curl
477 jq
478 fd
479 eza
480 bat
481 ripgrep-all
482 neofetch
483 ];
484
485 atelier.shell.git.enable = lib.mkDefault true;
486 };
487}