Monorepo for Aesthetic.Computer
aesthetic.computer
1# Aesthetic Computer Development Environment Fish Config (Docker)
2
3# Set environment variables to prevent ETXTBSY errors
4set -gx NETLIFY_CLI_TELEMETRY_DISABLED 1
5set -gx NODE_DISABLE_COMPILE_CACHE 1
6
7if test -f /home/me/envs/load_envs.fish
8 source /home/me/envs/load_envs.fish
9 if functions -q load_envs
10 load_envs # Load devcontainer envs conditionally.
11 end
12end
13
14# Add Deno to PATH
15set -gx PATH /home/me/.deno/bin $PATH
16
17# Enable `fnm` support.
18
19if test -f $HOME/.fnm/fnm
20 set -gx PATH $HOME/.fnm $PATH
21 fnm env --use-on-cd --log-level=quiet | source
22 fnm use lts-jod --silent-if-unchanged 2>/dev/null
23end
24
25# Symlink a VSCode workspace as needed.
26# On macOS the relationship is reversed: /workspaces is a synthetic.conf symlink
27# to the user's home, so the repo already lives at ~/aesthetic-computer and the
28# codespaces-style symlink would loop/fail. Skip on Darwin.
29if test -d /workspaces/aesthetic-computer; and test (uname) != Darwin
30 if test ! -L ~/aesthetic-computer
31 echo "Symlinking /workspaces/aesthetic-computer to ~"
32 ln -s /workspaces/aesthetic-computer ~
33 end
34end
35
36# Persist Copilot CLI sessions across container restarts
37# Now handled by Docker volume mount (aesthetic-copilot-data)
38# The volume is mounted at /home/me/.copilot in devcontainer.json
39# No symlink needed - just ensure proper permissions
40if test -d ~/.copilot
41 # Fix permissions if needed (volume may have wrong owner after restart)
42 if not test -O ~/.copilot
43 sudo chown -R me:me ~/.copilot 2>/dev/null
44 end
45end
46
47function fish_prompt
48 # Show shell and directory for LLM context
49 set_color cyan
50 echo -n 'fish'
51 set_color normal
52 echo -n ' '
53 set_color yellow
54 echo -n (prompt_pwd)
55 set_color normal
56 echo -n ' > '
57end
58
59function dns
60 set -l domain $argv[1]
61 echo "https://iwantmyname.com/dashboard/dns/$domain"
62end
63
64function fish_greeting
65 if test "$nogreet" = true
66 return
67 end
68
69 printf "🧩 Hi @$AESTHETIC!\n\n"
70
71 # Show the new KidLisp source tree shortcut
72
73
74 # printf "Ask with 'umm' and forget with 'nvm'\n"
75 # printf "or use 'code' and 'done' with 'copy'\n"
76 # printf "to generate and get code.\n\n"
77 # printf "🆕 Try 'aider' to make edits: https://github.com/paul-gauthier/aider?tab=readme-ov-file#usage\n\n"
78 # printf "📋 Clipboard also requires `xhost +local:docker` to be set on the host."
79end
80
81function ac-tv
82 # Detect Codespaces and construct the appropriate base URL
83 if test -n "$CODESPACES"; and test -n "$CODESPACE_NAME"; and test -n "$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
84 set -l default_base "https://$CODESPACE_NAME-8888.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/api/tv"
85 else
86 set -l default_base "https://localhost:8888/api/tv"
87 end
88
89 set -l base $default_base
90 set -l preview true
91 set -l query_params
92 set -l script_args
93
94 for arg in $argv
95 switch $arg
96 case '--preview' 'preview'
97 set preview true
98 case '--no-preview' 'no-preview' '--json' 'json'
99 set preview false
100 case '--raw'
101 set preview true
102 set script_args $script_args '--raw'
103 case '--max-width=*' '--maxwidth=*' '--max-height=*' '--maxheight=*' '--pause=*' '--pauseMs=*' '--pausems=*'
104 set preview true
105 set script_args $script_args $arg
106 case '--base=*'
107 set base (string replace -r '^--base=' '' -- $arg)
108 case 'base=*'
109 set base (string replace 'base=' '' -- $arg)
110 case '--limit=*' '--types=*'
111 set query_params $query_params (string replace -r '^--' '' -- $arg)
112 set script_args $script_args $arg
113 case 'limit=*' 'types=*'
114 set query_params $query_params $arg
115 set script_args $script_args $arg
116 case '--*'
117 # Forward unknown long flags to preview script if used
118 set script_args $script_args $arg
119 case '*=*'
120 set query_params $query_params $arg
121 set script_args $script_args $arg
122 case '*'
123 set query_params $query_params $arg
124 set script_args $script_args $arg
125 end
126 end
127
128 set -l url $base
129 if test (count $query_params) -gt 0
130 set -l query (string join '&' $query_params)
131 set url "$base?"$query
132 end
133
134 if test $preview = true
135 set -l script "/workspaces/aesthetic-computer/reference/tools/recording/tv-preview.mjs"
136 if not test -f $script
137 echo "❌ Preview script missing at $script" >&2
138 return 1
139 end
140
141 if test $base != $default_base
142 set script_args $script_args "--base=$base"
143 end
144
145 printf "📺 Preview %s\n" $url
146 node --no-warnings $script $script_args
147 return $status
148 end
149
150 printf "📺 GET %s\n" $url
151
152 set -l response (curl --silent --show-error --insecure $url)
153 set -l curl_status $status
154 if test $curl_status -ne 0
155 echo "❌ Failed to reach $url" >&2
156 return 1
157 end
158
159 set -l body (string join "\n" $response)
160
161 if type -q jq
162 printf '%s\n' $body | jq
163 else if type -q python3
164 printf '%s\n' $body | python3 -m json.tool
165 else
166 printf '%s\n' $body
167 end
168end
169
170# Run public kidlisp CLI when installed.
171function __ac_kidlisp_cli --description "Internal helper for kidlisp CLI"
172 if command -q kidlisp
173 kidlisp $argv
174 return $status
175 end
176
177 return 127
178end
179
180# KidLisp source tree helper (used by ac-kidlisp compatibility commands)
181function __ac_kidlisp_source_tree --description "Internal KidLisp source tree helper"
182 if test "$argv[1]" = "--test-colors"
183 node --no-warnings /workspaces/aesthetic-computer/kidlisp/tools/source-tree.mjs --test-colors
184 return
185 end
186
187 if test "$argv[1]" = "--test-css-colors"
188 node --no-warnings /workspaces/aesthetic-computer/kidlisp/tools/source-tree.mjs --test-css-colors
189 return
190 end
191
192 if test "$argv[1]" = "--debug-colors"
193 node --no-warnings /workspaces/aesthetic-computer/kidlisp/tools/source-tree.mjs --debug-colors
194 return
195 end
196
197 set -l piece_name $argv[1]
198 set -l extra_args $argv[2..-1]
199
200 if test -z "$piece_name"
201 echo "Usage: ac-kidlisp source-tree <piece> [--source]"
202 return 1
203 end
204
205 # Remove $ prefix if present
206 if string match -q '$*' $piece_name
207 set piece_name (string sub -s 2 $piece_name)
208 end
209
210 # Prefer public kidlisp CLI when available
211 if __ac_kidlisp_cli tree $piece_name $extra_args
212 return $status
213 end
214
215 # Local fallback while kidlisp-cli implementation is still spec-first
216 node --no-warnings /workspaces/aesthetic-computer/kidlisp/tools/source-tree.mjs $piece_name $extra_args
217end
218
219# ac-st compatibility wrapper (preferred usage is now: ac-kidlisp source-tree ...)
220function ac-st --description "KidLisp source tree viewer"
221 ac-kidlisp source-tree $argv
222end
223
224# Legacy short command (kept for muscle memory)
225function st --description "Deprecated: use ac-kidlisp source-tree or ac-st"
226 echo "⚠️ st is deprecated; use `ac-kidlisp source-tree` or `kidlisp tree`."
227 ac-kidlisp source-tree $argv
228end
229
230# Alternative function for those who prefer the $ syntax
231function dollarpiece
232 ac-kidlisp source-tree $argv
233end
234
235# AC Pack - Package pieces for Teia with cover GIF generation
236# Usage: ac-pack '$ceo' or ac-pack 'ceo' [--density N] (from any directory)
237function ac-pack
238 if test (count $argv) -lt 1
239 echo "Usage: ac-pack PIECE_NAME [--density N]"
240 echo "Example: ac-pack '\$ceo' or ac-pack 'ceo'"
241 echo " ac-pack '\$bop' --density 8"
242 return 1
243 end
244
245 set -l piece_name $argv[1]
246 set -l current_dir (pwd)
247
248 # Store original directory
249 echo "📂 Packaging $piece_name from $current_dir"
250
251 # Run the ac-pack.mjs script from the teia directory
252 cd /workspaces/aesthetic-computer/teia
253
254 # Run the packaging with all arguments plus target directory
255 command node ac-pack.mjs $argv --target-dir "$current_dir"
256
257 # Check if packaging was successful
258 if test $status -eq 0
259 echo "✅ AC Pack complete! Files created in $current_dir"
260 else
261 echo "❌ AC Pack failed with exit code $status"
262 end
263
264 # Return to original directory
265 cd $current_dir
266end
267
268# AC Login - Authenticate with Aesthetic Computer (OAuth Device Code Flow)
269# Usage: ac-login [status|logout|token]
270# Stores access token in $AC_TOKEN environment variable for use in scripts
271function ac-login
272 set -l cmd $argv[1]
273
274 if test "$cmd" = "status"
275 node /workspaces/aesthetic-computer/tezos/ac-login.mjs status
276 return $status
277 else if test "$cmd" = "logout"
278 node /workspaces/aesthetic-computer/tezos/ac-login.mjs logout
279 set -e AC_TOKEN
280 set -e AC_USER_EMAIL
281 return 0
282 else if test "$cmd" = "token"
283 node /workspaces/aesthetic-computer/tezos/ac-login.mjs token
284 return $status
285 else
286 # Login and capture token
287 node /workspaces/aesthetic-computer/tezos/ac-login.mjs
288
289 if test $status -eq 0
290 # Read token from file and export to environment
291 if test -f ~/.ac-token
292 set -l token_json (cat ~/.ac-token)
293 set -gx AC_TOKEN (echo $token_json | jq -r '.access_token')
294 set -gx AC_USER_EMAIL (echo $token_json | jq -r '.user.email // .user.name // .user.sub')
295 echo ""
296 echo "🔑 Token exported to \$AC_TOKEN"
297 echo "👤 User exported to \$AC_USER_EMAIL"
298 echo ""
299 echo "Example usage:"
300 echo " curl -H \"Authorization: Bearer \$AC_TOKEN\" https://aesthetic.computer/api/keep-mint"
301 end
302 end
303
304 return $status
305 end
306end
307
308# AC Token - Get current access token (shortcut)
309# Usage: ac-token [--refresh]
310function ac-token
311 if test "$argv[1]" = "--refresh"
312 # Re-read from file
313 if test -f ~/.ac-token
314 set -l token_json (cat ~/.ac-token)
315 set -gx AC_TOKEN (echo $token_json | jq -r '.access_token')
316 set -gx AC_USER_EMAIL (echo $token_json | jq -r '.user.email // .user.name // .user.sub')
317 echo "✅ Token refreshed from ~/.ac-token"
318 else
319 echo "❌ No token file found. Run: ac-login"
320 return 1
321 end
322 else if test -n "$AC_TOKEN"
323 echo $AC_TOKEN
324 else
325 # Try to load from file
326 if test -f ~/.ac-token
327 set -l token_json (cat ~/.ac-token)
328 set -gx AC_TOKEN (echo $token_json | jq -r '.access_token')
329 echo $AC_TOKEN
330 else
331 echo "❌ Not logged in. Run: ac-login" >&2
332 return 1
333 end
334 end
335end
336
337# AC Keeps - Interactive CLI for keeping KidLisp pieces on Tezos
338# Usage: ac-keeps [list|keep|wallet|status] [args...]
339# Commands:
340# ac-keeps - Interactive mode
341# ac-keeps list - List your pieces
342# ac-keeps list --top - List by popularity
343# ac-keeps keep $code - Keep a piece (you pay 5ꜩ)
344# ac-keeps wallet - Connect Tezos wallet
345# ac-keeps status $code - Check if piece is kept
346function ac-keeps
347 node /workspaces/aesthetic-computer/tezos/ac-keeps.mjs $argv
348end
349
350# AC Keep - Create self-contained HTML bundles for Tezos KEEPS
351# Usage: ac-keep '$bop' or ac-keep 'bop' (from any directory)
352# Creates a single HTML file that runs offline with all dependencies embedded
353# Filenames: @author-$piece-timestamp.html (matching bios.mjs download format)
354function ac-keep
355 if test (count $argv) -lt 1
356 echo "Usage: ac-keep PIECE_NAME"
357 echo "Example: ac-keep '\$bop' or ac-keep 'bop'"
358 echo ""
359 echo "Creates self-contained HTML bundles in tezos/keep-bundles/"
360 echo "Output files (named like downloads from bios.mjs):"
361 echo " • \$piece-@author-timestamp.html (uncompressed)"
362 echo " • \$piece-@author-timestamp.brotli.html (Brotli, for Tezos)"
363 echo " • \$piece-@author-timestamp.lisp.html (gzip, for browser/drag-drop)"
364 return 1
365 end
366
367 set -l piece_name $argv[1]
368 # Remove $ prefix if present
369 set piece_name (string replace -r '^\$' '' -- $piece_name)
370
371 echo "📦 Creating KEEP bundle for \$$piece_name..."
372
373 # Run the bundle-keep-html.mjs script and capture output
374 set -l output (node /workspaces/aesthetic-computer/tezos/bundle-keep-html.mjs $piece_name 2>&1)
375 set -l exit_status $status
376
377 # Print the output
378 echo $output
379
380 if test $exit_status -eq 0
381 echo ""
382 echo "✅ KEEP bundle created!"
383 echo "📁 Files in: /workspaces/aesthetic-computer/tezos/keep-bundles/"
384 else
385 echo "❌ KEEP bundle failed with exit code $exit_status"
386 end
387
388 return $exit_status
389end
390
391# AC Keep Test - Build and serve a KEEP bundle for testing
392# Usage: ac-keep-test '$bop' or ac-keep-test 'bop'
393function ac-keep-test
394 if test (count $argv) -lt 1
395 echo "Usage: ac-keep-test PIECE_NAME"
396 echo "Example: ac-keep-test '\$bop'"
397 return 1
398 end
399
400 set -l piece_name $argv[1]
401 # Remove $ prefix if present
402 set piece_name (string replace -r '^\$' '' -- $piece_name)
403
404 # Build the bundle and capture output
405 echo "📦 Creating KEEP bundle for \$$piece_name..."
406 node /workspaces/aesthetic-computer/tezos/bundle-keep-html.mjs $piece_name
407
408 if test $status -ne 0
409 return 1
410 end
411
412 echo ""
413 echo "🌐 Starting test server on http://localhost:8082..."
414 echo " Browse to http://localhost:8082/ and select the .gzip.html file"
415 echo " Press Ctrl+C to stop"
416 echo ""
417
418 # Start server in the keep-bundles directory
419 cd /workspaces/aesthetic-computer/tezos/keep-bundles
420 python3 -m http.server 8082
421end
422
423# Usage: ac-unpack [zip-file] [port] - unpacks and tests TEIA packages locally
424# If no zip file specified, finds the most recent one in current directory
425function ac-unpack
426 set -l current_dir (pwd)
427 set -l zip_file $argv[1]
428 set -l port (test (count $argv) -ge 2; and echo $argv[2]; or echo "8080")
429
430 echo "📦 Unpacking TEIA package from $current_dir"
431
432 # Run the ac-unpack.mjs script from the teia directory
433 cd /workspaces/aesthetic-computer/teia
434
435 if test -n "$zip_file"
436 # Zip file specified - use full path if it's in the original directory
437 if not test -f "$zip_file"
438 set zip_file "$current_dir/$zip_file"
439 end
440 echo "📂 Using specified file: $zip_file"
441 node ac-unpack.mjs "$zip_file" $port "$current_dir"
442 else
443 # No zip file specified - let ac-unpack find the latest one
444 echo "🔍 Looking for latest zip file in $current_dir..."
445 node ac-unpack.mjs "" $port "$current_dir"
446 end
447
448 # Return to original directory
449 cd $current_dir
450
451 if test $status -eq 0
452 echo "✅ AC Unpack complete! Server running on port $port"
453 echo "🌐 Open http://localhost:$port in your browser to test"
454 else
455 echo "❌ AC Unpack failed with exit code $status"
456 end
457end
458
459# Usage: ac-ship [zip-file] [--platforms mac,windows,linux] - packages TEIA zips as Electron desktop apps
460# If no zip file specified, finds the most recent one in current directory
461function ac-ship
462 set -l current_dir (pwd)
463 set -l args $argv
464
465 echo "🚢 Shipping Electron apps from $current_dir"
466
467 # Run the ac-ship.mjs script from the teia directory
468 cd /workspaces/aesthetic-computer/teia
469
470 # Process arguments to handle relative paths
471 set -l processed_args
472 for arg in $args
473 if not string match -q -- '--*' $arg; and test -f "$current_dir/$arg"
474 # This looks like a zip file in the original directory
475 set processed_args $processed_args "$current_dir/$arg"
476 else
477 set processed_args $processed_args $arg
478 end
479 end
480
481 node ac-ship.mjs $processed_args
482
483 # Return to original directory
484 cd $current_dir
485
486 if test $status -eq 0
487 echo "✅ AC Ship complete! Electron apps built successfully"
488 echo "📱 Check the output for app locations"
489 else
490 echo "❌ AC Ship failed with exit code $status"
491 end
492end
493
494# AC Record - Record pieces as MP4 or GIF using the orchestrator
495# Usage: ac-record PIECE_NAME [--duration=<value>] [--width N] [--height N] [--gif] [--sixel]
496# Duration accepts suffixes: `s` for seconds (e.g. 3s) or `f` for frames (e.g. 120f). Default: 5s (300f).
497# Example: ac-record '$bair' --duration=3s or ac-record 'orbital' --duration=120f --width 512 --height 512 --gif
498function ac-record
499 if test (count $argv) -lt 1
500 echo "Usage: ac-record PIECE_NAME [OPTIONS]"
501 echo "Example: ac-record '$bair'"
502 echo " ac-record 'orbital' --duration=120f --width 512 --height 512"
503 echo " ac-record '$ceo' --gif --duration=3s"
504 echo ""
505 echo "Options:"
506 echo " --duration VAL Recording length. Append 's' for seconds or 'f' for frames"
507 echo " (default: 5s = 300f). Plain numbers are treated as frames."
508 echo " --width N Video width (default: 1024)"
509 echo " --height N Video height (default: 1024)"
510 echo " --gif Output as GIF instead of MP4"
511 echo " --gif-fps N Override GIF framerate (default: 50, allowed: 100,50,25,20,10,5,4,2,1)"
512 echo " --gif-25 Convenience flag for 25fps GIF output"
513 echo " --gif-compress Ultra-compress GIF for smallest file size"
514 echo " --sixel Display result in terminal as sixel image"
515 return 1
516 end
517
518 set -l piece_name $argv[1]
519 set -l current_dir (pwd)
520
521 set -l frames 300 # ≈5 seconds at 60fps
522 set -l duration_seconds "5.00"
523 set -l width 1024
524 set -l height 1024
525 set -l gif_flag ""
526 set -l sixel_flag ""
527 set -l duration_spec ""
528 set -l density ""
529 set -l gif_fps ""
530 set -l gif_compress ""
531
532 # Process optional arguments
533 set -l i 2
534 while test $i -le (count $argv)
535 set -l arg $argv[$i]
536 switch $arg
537 case '--duration=*'
538 set duration_spec (string split -m 1 '=' -- $arg)[2]
539 case '--duration'
540 set i (math $i + 1)
541 if test $i -le (count $argv)
542 set duration_spec $argv[$i]
543 else
544 echo "❌ Missing value for --duration" >&2
545 cd $current_dir
546 return 1
547 end
548 case '--frames'
549 set i (math $i + 1)
550 if test $i -le (count $argv)
551 set -l frames_arg $argv[$i]
552 set duration_spec (string join '' $frames_arg 'f')
553 printf "⚠️ --frames is deprecated; use --duration=%sf instead\n" $frames_arg >&2
554 else
555 echo "❌ Missing value for --frames" >&2
556 cd $current_dir
557 return 1
558 end
559 case '--width'
560 set i (math $i + 1)
561 if test $i -le (count $argv)
562 set width $argv[$i]
563 end
564 case '--height'
565 set i (math $i + 1)
566 if test $i -le (count $argv)
567 set height $argv[$i]
568 end
569 case '--density'
570 set i (math $i + 1)
571 if test $i -le (count $argv)
572 set density $argv[$i]
573 end
574 case '--gif'
575 set gif_flag "--gif"
576 case '--sixel'
577 set sixel_flag "sixel"
578 case '--gif-fps'
579 set i (math $i + 1)
580 if test $i -le (count $argv)
581 set gif_fps $argv[$i]
582 end
583 case '--gif-25'
584 set gif_fps 25
585 case '--gif25'
586 set gif_fps 25
587 case '--gif-compress'
588 set gif_compress "--gif-compress"
589 case '*'
590 echo "⚠️ Ignoring unknown option: $arg" >&2
591 end
592 set i (math $i + 1)
593 end
594
595 if test -n "$duration_spec"
596 set duration_spec (string trim (string lower $duration_spec))
597 set -l parse_result (python3 -c "import sys
598spec = sys.argv[1].strip().lower()
599if not spec:
600 raise SystemExit(1)
601if spec.endswith('s'):
602 seconds = float(spec[:-1] or '0')
603 frames = int(round(seconds * 60))
604elif spec.endswith('f'):
605 frames = int(round(float(spec[:-1] or '0')))
606 seconds = frames / 60.0
607else:
608 frames = int(round(float(spec)))
609 seconds = frames / 60.0
610if frames <= 0:
611 raise SystemExit(1)
612print(frames)
613print(f'{seconds:.2f}')" $duration_spec)
614
615 if test $status -ne 0
616 echo "❌ Invalid --duration value: $duration_spec" >&2
617 cd $current_dir
618 return 1
619 end
620
621 set frames $parse_result[1]
622 set duration_seconds $parse_result[2]
623 end
624
625 if test $frames -le 0
626 echo "❌ Duration must be greater than 0" >&2
627 cd $current_dir
628 return 1
629 end
630
631 echo "🎬 Recording $piece_name from $current_dir"
632 echo "📊 Settings: {$frames} frames (~{$duration_seconds}s) @ {$width}x{$height}"
633 if test -n "$gif_flag"
634 if test -n "$gif_fps"
635 printf "🎞️ Output format: GIF @ %sfps\n" $gif_fps
636 else
637 echo "🎞️ Output format: GIF @ 50fps (default)"
638 end
639 else
640 echo "🎞️ Output format: MP4"
641 end
642 if test -n "$density"
643 echo "🔍 Density: $density"
644 set -l final_width (math "$width * $density")
645 set -l final_height (math "$height * $density")
646 set final_width (string replace -r '\.0+$' '' $final_width)
647 set final_height (string replace -r '\.0+$' '' $final_height)
648 printf "📐 Output pixels: %sx%s\n" $final_width $final_height
649 end
650
651 # Run the orchestrator from the recording directory
652 cd /workspaces/aesthetic-computer/reference/tools/recording
653
654 # Pass the original duration spec to orchestrator, or default if none specified
655 set -l duration_arg
656 if test -n "$duration_spec"
657 set duration_arg $duration_spec
658 else
659 set duration_arg "5s" # Default to 5 seconds
660 end
661
662 set -l cmd "node" "orchestrator.mjs" $piece_name $duration_arg $width $height
663 if test -n "$gif_flag"
664 set cmd $cmd $gif_flag
665 end
666 if test -n "$sixel_flag"
667 set cmd $cmd $sixel_flag
668 end
669 if test -n "$density"
670 set cmd $cmd "--density" $density
671 end
672 if test -n "$gif_fps"
673 set cmd $cmd "--gif-fps" $gif_fps
674 end
675 if test -n "$gif_compress"
676 set cmd $cmd $gif_compress
677 end
678
679 # Execute the recording
680 command $cmd
681
682 if test $status -eq 0
683 echo "✅ AC Record complete! Video created in output directory"
684
685 set -l output_base_dir "/workspaces/aesthetic-computer/reference/tools/output"
686 set -l piece_clean (string replace '$' '' $piece_name)
687
688 if test -n "$gif_flag"
689 set -l latest_gif (find $output_base_dir -name "*$piece_clean*.gif" -type f -printf "%T@ %p\n" 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2-)
690 if test -n "$latest_gif"
691 cp "$latest_gif" "$current_dir/"
692 set -l filename (basename "$latest_gif")
693 echo "📁 Copied $filename to $current_dir"
694 end
695 else
696 set -l latest_mp4 (find $output_base_dir -name "*$piece_clean*.mp4" -type f -printf "%T@ %p\n" 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2-)
697 if test -n "$latest_mp4"
698 cp "$latest_mp4" "$current_dir/"
699 set -l filename (basename "$latest_mp4")
700 echo "📁 Copied $filename to $current_dir"
701 end
702 end
703 else
704 echo "❌ AC Record failed with exit code $status"
705 end
706
707 cd $current_dir
708end
709
710# 🩸 Artery - Direct connection to Aesthetic Computer workbench
711# Interactive REPL for controlling AC, seeing console logs, and executing JavaScript
712# Usage: artery [command] [args...]
713# Examples:
714# artery # Show help
715# artery jump prompt # Navigate to prompt piece
716# artery current # Show current piece
717# artery repl # Interactive REPL mode
718function artery
719 if test (count $argv) -eq 0
720 # No args - show help
721 node /workspaces/aesthetic-computer/artery/artery.mjs
722 else
723 # Pass through to artery
724 node /workspaces/aesthetic-computer/artery/artery.mjs $argv
725 end
726end
727
728# 🩸 AC REPL - Quick shortcut to artery REPL mode
729# Live JavaScript console with AC console logs
730# Usage: ac-repl
731# Commands in REPL:
732# .jump <piece> - Navigate to piece
733# .current - Show current piece
734# .panel [action] - Control AC panel
735# .exit - Close artery
736function ac-repl
737 echo "🩸 Starting Artery REPL - Live connection to Aesthetic Computer"
738 echo " 💉 Console logs will appear in real-time"
739 echo " 🎯 Type .jump <piece> to navigate"
740 echo " 🔍 Type JavaScript to execute in AC context"
741 echo ""
742 artery repl
743end
744
745# 🩸 Artery TUI - Interactive curses-style interface for AC
746# Usage: ac-artery or artery-tui
747# Features:
748# - Menu-driven interface with keyboard navigation
749# - Open AC panel, jump to pieces, run tests
750# - Live REPL with console log output
751# - WebGPU performance monitoring
752function ac-artery
753 node /workspaces/aesthetic-computer/artery/artery-tui.mjs
754end
755
756# Artery with hot-reload for development
757# Watches artery-tui.mjs and artery.mjs for changes and auto-restarts
758function ac-artery-dev
759 node /workspaces/aesthetic-computer/artery/artery-dev.mjs
760end
761
762function artery-tui
763 ac-artery
764end
765
766# Automated testing for AC pieces
767# Usage: test-notepat [duration_ms]
768# Example: test-notepat 60000 (runs for 60 seconds)
769function test-notepat
770 node /workspaces/aesthetic-computer/.vscode/tests/test-notepat.mjs $argv
771end
772
773# Test line drawing tool
774# Usage: test-line [duration_ms]
775# Example: test-line 30000 (runs for 30 seconds)
776function test-line
777 node /workspaces/aesthetic-computer/.vscode/tests/test-line.mjs $argv
778end
779
780# Test toss piece
781# Usage: test-toss [duration_ms]
782# Example: test-toss 5000 (runs for 5 seconds)
783function test-toss
784 node /workspaces/aesthetic-computer/.vscode/tests/test-toss.mjs $argv
785end
786
787function test-melody
788 node /workspaces/aesthetic-computer/.vscode/tests/test-melody.mjs $argv
789end
790
791function test-playlist
792 node /workspaces/aesthetic-computer/.vscode/tests/test-playlist.mjs $argv
793end
794
795function test-chords
796 node /workspaces/aesthetic-computer/.vscode/tests/test-chords.mjs $argv
797end
798
799function test-generative-waltz
800 node /workspaces/aesthetic-computer/.vscode/tests/test-generative-waltz.mjs $argv
801end
802
803# 🌐 KidLisp.com Probe - Control kidlisp.com in Simple Browser via CDP
804# Usage:
805# kidlisp # Show status
806# kidlisp eval <js> # Evaluate JS in kidlisp.com
807# kidlisp theme # Toggle theme
808# kidlisp clear # Clear editor
809# kidlisp set <code> # Set editor content
810# kidlisp play # Press play button
811function kidlisp
812 node /workspaces/aesthetic-computer/kidlisp-tools/kidlisp-probe.mjs $argv
813end
814
815# always start in aesthetic-computer directory if there was a greeting
816if not test "$nogreet" = true
817 cd ~/aesthetic-computer
818end
819
820# rebuild the container after exiting with a special code ;)
821
822# alias reload 'exit 70'
823
824# reload fish config
825alias reload 'source /workspaces/aesthetic-computer/.devcontainer/config.fish'
826alias refish 'source ~/.config/fish/config.fish'
827
828# Workaround for fish script execution on SELinux/btrfs bind mounts
829# The "No such file or directory" error happens when fish tries to execute
830# scripts via shebang on /workspaces (which is a bind mount from host).
831# Use `run script.fish` instead of `./script.fish` or `fish script.fish`
832function run --description "Run a fish script using source (workaround for bind mount issue)"
833 if test (count $argv) -eq 0
834 echo "Usage: run <script.fish> [args...]"
835 return 1
836 end
837 set -l script $argv[1]
838 set -l args $argv[2..-1]
839 if not test -f $script
840 echo "File not found: $script"
841 return 1
842 end
843 # Use source to run the script in current shell
844 source $script $args
845end
846
847# Port visibility helper - sets port 8888 to public in Codespaces
848alias port-public 'source /workspaces/aesthetic-computer/.devcontainer/scripts/set-port-public.fish'
849
850# SSH to Windows host
851alias win 'ssh me@host.docker.internal'
852
853# set default editor to emacs
854set -gx TERM xterm-256color
855set -gx EDITOR emacs
856set -gx PATH $PATH /home/me/.local/bin
857
858# add dotnet tools to path
859set -Ux PATH $PATH $HOME/.dotnet/tools
860# add stuff to path
861# set -gx PATH $PATH $HOME/isomorphic_copy/bin
862# use temporary clipboard file
863# set -gx ISOCP_USE_FILE 1
864# set -gx DISPLAY FAKE
865
866set -gx DENO_INSTALL /home/me/.deno
867set -gx PATH $PATH $DENO_INSTALL/bin
868
869# enable vi support
870set fish_cursor_default block
871set fish_cursor_insert line
872set fish_cursor_replace_one underscore
873set fish_vi_force_cursor true
874fish_vi_key_bindings
875
876# add homebrew to path (only if we are on linux)
877#switch (uname)
878# case Linux
879# eval (/home/linuxbrew/.linuxbrew/bin/brew shellenv)
880#end
881
882# include user binaries in the shell path
883fish_add_path ~/.local/bin
884
885# add rust binaries to the shell path
886fish_add_path ~/.cargo/bin
887
888# add nanos ops binaries to the shell path
889fish_add_path ~/.ops/bin
890
891# ============================================================================
892# AC Emacs Management Commands
893# ============================================================================
894
895# Emacs log directory
896set -gx AC_EMACS_LOG_DIR /home/me/.emacs-logs
897
898# Initialize emacs log directory
899function ac-emacs-init-logs
900 mkdir -p $AC_EMACS_LOG_DIR
901end
902
903# Kill zombie emacs daemon and restart fresh
904function ac-emacs-restart
905 echo "🔄 Restarting emacs daemon..."
906 ac-emacs-init-logs
907
908 # Kill any existing emacs processes
909 pkill -9 -f "emacs.*daemon" 2>/dev/null
910 pkill -9 emacs 2>/dev/null
911 pkill -9 emacsclient 2>/dev/null
912 sleep 1
913
914 # Start fresh daemon with logging
915 echo "🚀 Starting fresh emacs daemon..."
916 set -l timestamp (date +%Y%m%d_%H%M%S)
917 set -l log_file $AC_EMACS_LOG_DIR/daemon_$timestamp.log
918 set -l config_path /home/me/aesthetic-computer/dotfiles/dot_config/emacs.el
919
920 # Log startup info
921 echo "=== Emacs Daemon Start ===" > $log_file
922 echo "Timestamp: "(date -Iseconds) >> $log_file
923 echo "Config: $config_path" >> $log_file
924 echo "===========================" >> $log_file
925
926 # Start daemon in background with timeout (don't use tee - it hangs)
927 emacs -q --daemon -l $config_path >> $log_file 2>&1 &
928 set -l daemon_pid $last_pid
929
930 # Create/update symlink to latest log
931 ln -sf $log_file $AC_EMACS_LOG_DIR/latest.log
932
933 # Wait for daemon to be responsive (max 30 seconds)
934 echo "⏳ Waiting for daemon..."
935 set -l attempts 0
936 while test $attempts -lt 30
937 if timeout 2 emacsclient -e t >/dev/null 2>&1
938 echo "✅ Emacs daemon ready!"
939 echo "📝 Logs: $log_file"
940
941 # Start the crash monitor in background (if not already running)
942 if not pgrep -f "ac-emacs-crash-monitor" >/dev/null 2>&1
943 fish -c "ac-emacs-crash-monitor" &>/dev/null &
944 disown
945 end
946
947 echo ""
948 echo "💡 To connect UI: aesthetic-now (or ac-aesthetic)"
949 return 0
950 end
951 sleep 1
952 set attempts (math $attempts + 1)
953 end
954
955 echo "❌ Emacs daemon failed to start (timeout)"
956 echo "📝 Check logs: $log_file"
957 return 1
958end
959
960# Full restart including UI reconnection
961function ac-emacs-full-restart
962 ac-emacs-restart
963 and aesthetic-now
964end
965
966# Health check - verify emacs has correct config loaded
967function ac-emacs-health-check
968 if not pgrep -f "emacs.*daemon" >/dev/null
969 echo "❌ Emacs daemon not running"
970 return 1
971 end
972
973 if not timeout 3 emacsclient -e t >/dev/null 2>&1
974 echo "❌ Emacs daemon unresponsive"
975 return 1
976 end
977
978 # Check if our config functions are defined
979 set -l has_backend (emacsclient -e '(fboundp (quote aesthetic-backend))' 2>/dev/null)
980 if test "$has_backend" != "t"
981 echo "❌ Emacs loaded wrong config (aesthetic-backend not defined)"
982 echo " This usually means crash monitor restarted emacs incorrectly"
983 echo " Run: ac-emacs-restart"
984 return 1
985 end
986
987 # Check if backend has run
988 set -l backend_started (emacsclient -e 'ac--backend-started' 2>/dev/null)
989 echo "✅ Emacs daemon healthy"
990 echo " Config: aesthetic-backend defined"
991 echo " Backend started: $backend_started"
992 return 0
993end
994
995# Crash diary viewer - live tail of crash log with pretty formatting
996function ac-crash-diary
997 ac-emacs-init-logs
998 set -l crash_log $AC_EMACS_LOG_DIR/crashes.log
999
1000 clear
1001 echo ""
1002 echo " ╔══════════════════════════════════════════════════════════════╗"
1003 echo " ║ 💥 EMACS CRASH DIARY ║"
1004 echo " ║ Log: $crash_log"
1005 echo " ╚══════════════════════════════════════════════════════════════╝"
1006 echo ""
1007
1008 if not test -f $crash_log
1009 echo " 📭 No crashes recorded yet. That's good!"
1010 echo ""
1011 echo " Press Ctrl+C to exit, or wait for crashes to be logged..."
1012 echo ""
1013 # Create empty file and watch it
1014 touch $crash_log
1015 else
1016 # Show summary
1017 set -l crash_count (grep -c "EMACS CRASH DETECTED" $crash_log 2>/dev/null || echo 0)
1018 set -l restart_count (grep -c "Auto-restarting emacs" $crash_log 2>/dev/null || echo 0)
1019 set -l last_crash (grep "EMACS CRASH DETECTED" $crash_log 2>/dev/null | tail -1)
1020
1021 echo " 📊 Summary:"
1022 echo " Total crashes: $crash_count"
1023 echo " Auto-restarts: $restart_count"
1024 echo ""
1025 echo " ────────────────────────────────────────────────────────────"
1026 echo ""
1027 end
1028
1029 # Live tail with syntax highlighting
1030 tail -f $crash_log 2>/dev/null | while read -l line
1031 # Color code different types of lines
1032 if string match -q "*CRASH DETECTED*" "$line"
1033 set_color red --bold
1034 echo " $line"
1035 set_color normal
1036 else if string match -q "*Auto-restarting*" "$line"
1037 set_color yellow
1038 echo " $line"
1039 set_color normal
1040 else if string match -q "*✅*" "$line"
1041 set_color green
1042 echo " $line"
1043 set_color normal
1044 else if string match -q "*❌*" "$line"
1045 set_color red
1046 echo " $line"
1047 set_color normal
1048 else if string match -q "*===*" "$line"
1049 set_color cyan
1050 echo " $line"
1051 set_color normal
1052 else if string match -q "*---*" "$line"
1053 set_color brblack
1054 echo " $line"
1055 set_color normal
1056 else
1057 echo " $line"
1058 end
1059 end
1060end
1061
1062# Calculate health check timeout based on system load.
1063# Under heavy load, the daemon may be slow to respond without being dead.
1064function ac--health-check-timeout
1065 set -l load_1m (string split ' ' < /proc/loadavg)[1]
1066 set -l num_cpus (nproc 2>/dev/null; or echo 2)
1067 set -l load_ratio (math "$load_1m / $num_cpus")
1068 if test (math "$load_ratio > 2.0") -eq 1
1069 echo 30
1070 else if test (math "$load_ratio > 1.0") -eq 1
1071 echo 20
1072 else
1073 echo 10
1074 end
1075end
1076
1077# Background crash monitor - detects when emacs dies unexpectedly
1078function ac-emacs-crash-monitor
1079 ac-emacs-init-logs
1080 set -l crash_log $AC_EMACS_LOG_DIR/crashes.log
1081 set -l check_interval 5 # Check every 5 seconds
1082 set -l max_restarts 5 # Max restarts before giving up
1083 set -l restart_count 0
1084 set -l restart_window 300 # Reset restart count after 5 mins of stability
1085 set -l last_restart_time 0
1086
1087 # Record that we started monitoring
1088 echo "["(date -Iseconds)"] 🔍 Crash monitor started (auto-restart enabled)" >> $crash_log
1089
1090 while true
1091 # Get current PID
1092 set -l daemon_pid (pgrep -f "emacs.*daemon" 2>/dev/null | head -1)
1093
1094 if test -z "$daemon_pid"
1095 # Daemon is gone - log the crash
1096 echo "" >> $crash_log
1097 echo "============================================" >> $crash_log
1098 echo "🔴 EMACS CRASH DETECTED" >> $crash_log
1099 echo "============================================" >> $crash_log
1100 echo "Time: "(date -Iseconds) >> $crash_log
1101 echo "" >> $crash_log
1102
1103 # Try to capture crash details
1104 echo "--- System State ---" >> $crash_log
1105 echo "Memory:" >> $crash_log
1106 free -h 2>/dev/null >> $crash_log
1107 echo "" >> $crash_log
1108 echo "Load:" >> $crash_log
1109 uptime >> $crash_log
1110 echo "" >> $crash_log
1111
1112 # Check dmesg for OOM or signals
1113 echo "--- Kernel messages (last 30s) ---" >> $crash_log
1114 dmesg --time-format=iso 2>/dev/null | tail -30 | grep -iE "oom|kill|emacs|signal|memory|segfault" >> $crash_log
1115 echo "" >> $crash_log
1116
1117 # Check journalctl for emacs-related messages
1118 echo "--- Journal (emacs) ---" >> $crash_log
1119 journalctl --since "1 minute ago" 2>/dev/null | grep -i emacs >> $crash_log
1120 echo "" >> $crash_log
1121
1122 # Check if there's a core dump
1123 echo "--- Core dumps ---" >> $crash_log
1124 coredumpctl list 2>/dev/null | grep -i emacs | tail -3 >> $crash_log
1125 echo "" >> $crash_log
1126
1127 # Check emacs daemon log if it exists
1128 set -l latest_log (ls -t $AC_EMACS_LOG_DIR/daemon_*.log 2>/dev/null | head -1)
1129 if test -n "$latest_log"
1130 echo "--- Last daemon log (last 50 lines) ---" >> $crash_log
1131 tail -50 "$latest_log" >> $crash_log
1132 end
1133 echo "============================================" >> $crash_log
1134
1135 # Reset restart count if enough time has passed
1136 set -l now (date +%s)
1137 if test (math "$now - $last_restart_time") -gt $restart_window
1138 set restart_count 0
1139 end
1140
1141 # Check if we should auto-restart
1142 if test $restart_count -lt $max_restarts
1143 set restart_count (math "$restart_count + 1")
1144 set last_restart_time $now
1145
1146 echo "["(date -Iseconds)"] 🔄 Auto-restarting emacs (attempt $restart_count/$max_restarts)..." >> $crash_log
1147
1148 # Create warning file
1149 echo "Emacs crashed at "(date -Iseconds)", auto-restart #$restart_count" > /tmp/emacs-crash-warning
1150
1151 # Signal the aesthetic reconnect loop BEFORE killing emacsclient
1152 echo (date -Iseconds) > /tmp/emacs-crash-restart-signal
1153
1154 # Remove any stale startup lock from previous session
1155 rm -f /tmp/emacs-backend-startup.lock
1156
1157 # Clean up any zombie processes
1158 pkill -9 emacs 2>/dev/null
1159 pkill -9 emacsclient 2>/dev/null
1160 sleep 1
1161
1162 # Restart daemon with correct config (must use -q -l to load our config, not ~/.emacs)
1163 set -l config_path /home/me/aesthetic-computer/dotfiles/dot_config/emacs.el
1164 emacs -q --daemon -l $config_path 2>&1 | tee -a $crash_log
1165
1166 # Wait for it to come up
1167 sleep 2
1168
1169 set -l new_pid (pgrep -f "emacs.*daemon" 2>/dev/null | head -1)
1170 if test -n "$new_pid"
1171 echo "["(date -Iseconds)"] ✅ Emacs daemon restarted (PID: $new_pid)" >> $crash_log
1172
1173 # Auto-start aesthetic-backend after crash recovery
1174 # This runs in background so it doesn't block the monitor
1175 echo "["(date -Iseconds)"] 🚀 Starting aesthetic-backend..." >> $crash_log
1176 fish -c "sleep 3 && emacsclient -e '(aesthetic-backend)'" &>/dev/null &
1177 disown
1178 else
1179 echo "["(date -Iseconds)"] ❌ Failed to restart emacs daemon" >> $crash_log
1180 end
1181 else
1182 echo "["(date -Iseconds)"] ⛔ Max restarts ($max_restarts) reached, stopping monitor" >> $crash_log
1183 echo "Emacs crashed repeatedly - manual intervention needed" > /tmp/emacs-crash-warning
1184 break
1185 end
1186 else
1187 # Daemon is running - also check if it's responsive
1188 # BUT skip responsiveness check if aesthetic-backend is still starting up
1189 set -l startup_lock /tmp/emacs-backend-startup.lock
1190 if test -f $startup_lock
1191 # Check how old the lock file is (don't wait forever if it's stale)
1192 set -l lock_age (math (date +%s) - (stat -c %Y $startup_lock 2>/dev/null; or echo 0))
1193 if test $lock_age -lt 120 # Lock valid for 2 minutes max
1194 # Startup in progress - skip responsiveness check, just log
1195 echo "["(date -Iseconds)"] ⏳ Startup lock active ($lock_age""s) - skipping responsiveness check" >> $crash_log
1196 else
1197 # Stale lock - remove it and check responsiveness
1198 echo "["(date -Iseconds)"] ⚠️ Stale startup lock ($lock_age""s) - removing" >> $crash_log
1199 rm -f $startup_lock
1200 # Use load-aware timeout to prevent false positives under heavy load
1201 set -l health_timeout (ac--health-check-timeout)
1202 if not timeout $health_timeout emacsclient -e t >/dev/null 2>&1
1203 echo "["(date -Iseconds)"] ⚠️ Daemon unresponsive (PID: $daemon_pid, timeout: $health_timeout""s), forcing restart..." >> $crash_log
1204 echo (date -Iseconds) > /tmp/emacs-crash-restart-signal
1205 pkill -9 emacsclient 2>/dev/null
1206 sleep 0.2
1207 pkill -9 -f "emacs.*daemon" 2>/dev/null
1208 end
1209 end
1210 else
1211 # No startup lock - normal responsiveness check with load-aware timeout.
1212 # Be gentler for a fresh daemon and require consecutive failures before kill.
1213 set -l health_timeout (ac--health-check-timeout)
1214 set -l required_failures 1
1215 set -l daemon_age (ps -p $daemon_pid -o etimes= 2>/dev/null | string trim)
1216 if test -n "$daemon_age"; and test "$daemon_age" -lt 180
1217 # Fresh daemons can be busy creating tabs/PTYs and miss one probe.
1218 set health_timeout (math "max($health_timeout, 30)")
1219 set required_failures 2
1220 end
1221 if not timeout $health_timeout emacsclient -e t >/dev/null 2>&1
1222 set -g ac_unresponsive_count (math (set -q ac_unresponsive_count; and echo $ac_unresponsive_count; or echo 0) + 1)
1223 echo "["(date -Iseconds)"] ⚠️ Daemon unresponsive (PID: $daemon_pid, timeout: $health_timeout""s, sample: $ac_unresponsive_count/$required_failures)" >> $crash_log
1224 if test $ac_unresponsive_count -ge $required_failures
1225 echo "["(date -Iseconds)"] ⚠️ Daemon unresponsive threshold reached, forcing restart..." >> $crash_log
1226 # Signal the aesthetic reconnect loop before killing
1227 echo (date -Iseconds) > /tmp/emacs-crash-restart-signal
1228 # Kill stale emacsclient processes BEFORE killing daemon
1229 # so they don't linger as frozen terminals
1230 pkill -9 emacsclient 2>/dev/null
1231 sleep 0.2
1232 pkill -9 -f "emacs.*daemon" 2>/dev/null
1233 set -g ac_unresponsive_count 0
1234 # Loop will detect it's gone and restart
1235 end
1236 else
1237 if set -q ac_unresponsive_count; and test $ac_unresponsive_count -gt 0
1238 set -g ac_unresponsive_count 0
1239 end
1240 # Daemon is responsive - also check CPU usage
1241 set -l cpu_usage (ps -p $daemon_pid -o %cpu= 2>/dev/null | string trim | string split '.')[1]
1242 if test -n "$cpu_usage"; and test "$cpu_usage" -gt 85
1243 set -g ac_high_cpu_count (math (set -q ac_high_cpu_count; and echo $ac_high_cpu_count; or echo 0) + 1)
1244 if test $ac_high_cpu_count -ge 3
1245 echo "["(date -Iseconds)"] ⚠️ Sustained high CPU ($cpu_usage%) for 3 checks, forcing restart..." >> $crash_log
1246 echo (date -Iseconds) > /tmp/emacs-crash-restart-signal
1247 pkill -9 emacsclient 2>/dev/null
1248 sleep 0.2
1249 pkill -9 -f "emacs.*daemon" 2>/dev/null
1250 set -g ac_high_cpu_count 0
1251 else
1252 echo "["(date -Iseconds)"] ⚠️ High CPU ($cpu_usage%) sample $ac_high_cpu_count/3" >> $crash_log
1253 end
1254 else if set -q ac_high_cpu_count; and test $ac_high_cpu_count -gt 0
1255 set -g ac_high_cpu_count 0
1256 end
1257 end
1258 end
1259 end
1260
1261 sleep $check_interval
1262 end
1263end
1264
1265# Kill emacs entirely (daemon + any clients)
1266function ac-emacs-kill
1267 echo "💀 Killing all emacs processes..."
1268 # Also kill the crash monitor
1269 pkill -f "ac-emacs-crash-monitor" 2>/dev/null
1270 pkill -9 -f "emacs.*daemon" 2>/dev/null
1271 pkill -9 emacs 2>/dev/null
1272 pkill -9 emacsclient 2>/dev/null
1273 echo "✅ All emacs processes killed"
1274end
1275
1276# Check emacs daemon status
1277function ac-emacs-status
1278 set -l daemon_pid (pgrep -f "emacs.*daemon" 2>/dev/null)
1279 if test -n "$daemon_pid"
1280 echo "🟢 Emacs daemon running (PID: $daemon_pid)"
1281 if timeout 3 emacsclient -e t >/dev/null 2>&1
1282 echo "✅ Daemon is responsive"
1283 else
1284 echo "⚠️ Daemon process exists but NOT responsive (zombie)"
1285 end
1286 else
1287 echo "🔴 Emacs daemon not running"
1288 # Check for crash warning
1289 if test -f /tmp/emacs-crash-warning
1290 echo "⚠️ Last crash: "(cat /tmp/emacs-crash-warning)
1291 end
1292 end
1293
1294 # Check crash monitor status
1295 set -l monitor_pid (pgrep -f "ac-emacs-crash-monitor" 2>/dev/null)
1296 if test -n "$monitor_pid"
1297 echo "🔍 Crash monitor active (PID: $monitor_pid)"
1298 else
1299 echo "⚠️ Crash monitor not running"
1300 end
1301end
1302
1303# Start the crash monitor manually
1304function ac-emacs-start-monitor
1305 if pgrep -f "ac-emacs-crash-monitor" >/dev/null 2>&1
1306 echo "Crash monitor already running"
1307 return 0
1308 end
1309
1310 if not pgrep -f "emacs.*daemon" >/dev/null 2>&1
1311 echo "❌ No emacs daemon to monitor"
1312 return 1
1313 end
1314
1315 echo "🔍 Starting crash monitor..."
1316 fish -c "ac-emacs-crash-monitor" &>/dev/null &
1317 disown
1318 echo "✅ Crash monitor started"
1319end
1320
1321# View emacs crash logs
1322function ac-emacs-logs
1323 ac-emacs-init-logs
1324 if test "$argv[1]" = "crashes"
1325 if test -f $AC_EMACS_LOG_DIR/crashes.log
1326 bat $AC_EMACS_LOG_DIR/crashes.log 2>/dev/null || cat $AC_EMACS_LOG_DIR/crashes.log
1327 else
1328 echo "No crash logs found"
1329 end
1330 else if test "$argv[1]" = "latest"
1331 if test -f $AC_EMACS_LOG_DIR/latest.log
1332 bat $AC_EMACS_LOG_DIR/latest.log 2>/dev/null || cat $AC_EMACS_LOG_DIR/latest.log
1333 else
1334 echo "No daemon logs found"
1335 end
1336 else if test "$argv[1]" = "tail"
1337 if test -f $AC_EMACS_LOG_DIR/crashes.log
1338 tail -f $AC_EMACS_LOG_DIR/crashes.log
1339 else
1340 echo "No crash logs found"
1341 end
1342 else if test "$argv[1]" = "list"
1343 echo "📁 Emacs logs in $AC_EMACS_LOG_DIR:"
1344 ls -lth $AC_EMACS_LOG_DIR/ 2>/dev/null || echo "No logs directory"
1345 else
1346 echo "Usage: ac-emacs-logs [crashes|latest|tail|list]"
1347 echo ""
1348 echo " crashes - View crash history"
1349 echo " latest - View latest daemon startup log"
1350 echo " tail - Follow crash log in real-time"
1351 echo " list - List all log files"
1352 end
1353end
1354
1355# Clear the crash warning
1356function ac-emacs-ack
1357 rm -f /tmp/emacs-crash-warning
1358 echo "✅ Crash warning cleared"
1359end
1360
1361# === SIXEL IMAGE DISPLAY ===
1362
1363# Display an image (URL or local path) as sixel in the emacs terminal
1364# Usage: ac-pix <url-or-path> [size]
1365# Examples:
1366# ac-pix https://example.com/image.png
1367# ac-pix /tmp/my-image.jpg
1368# ac-pix https://example.com/image.png 80 (80 pixels wide)
1369function ac-pix
1370 if test (count $argv) -lt 1
1371 echo "Usage: ac-pix <url-or-path> [size]"
1372 echo ""
1373 echo "Display an image as sixel graphics in the emacs terminal."
1374 echo ""
1375 echo "Arguments:"
1376 echo " url-or-path URL (http/https/ipfs) or local file path"
1377 echo " size Max pixel dimension (default: 60)"
1378 echo ""
1379 echo "Examples:"
1380 echo " ac-pix https://example.com/photo.png"
1381 echo " ac-pix /tmp/thumbnail.jpg 100"
1382 echo " ac-pix ipfs://Qm... 40"
1383 return 1
1384 end
1385
1386 set -l input $argv[1]
1387 set -l size 60
1388 if test (count $argv) -ge 2
1389 set size $argv[2]
1390 end
1391
1392 # Find emacsclient PTY for direct sixel output
1393 set -l pty (pgrep -f 'emacsclient.*-nw' | head -1 | xargs -I{} readlink /proc/{}/fd/0 2>/dev/null)
1394 if test -z "$pty"
1395 echo "❌ No emacsclient terminal found"
1396 return 1
1397 end
1398
1399 set -l tmpfile /tmp/ac-pix-(date +%s%N).img
1400
1401 # Handle different input types
1402 if string match -rq '^https?://' -- $input
1403 # HTTP/HTTPS URL
1404 echo "📥 Fetching $input..."
1405 if not curl -sL --max-time 15 -o $tmpfile $input
1406 echo "❌ Failed to download image"
1407 return 1
1408 end
1409 else if string match -rq '^ipfs://' -- $input
1410 # IPFS URL - try multiple gateways
1411 set -l cid (string replace 'ipfs://' '' $input)
1412 set -l gateways \
1413 "https://cloudflare-ipfs.com/ipfs/" \
1414 "https://ipfs.io/ipfs/" \
1415 "https://gateway.pinata.cloud/ipfs/" \
1416 "https://w3s.link/ipfs/"
1417
1418 set -l downloaded 0
1419 for gateway in $gateways
1420 echo "📥 Trying $gateway..."
1421 if curl -sL --max-time 10 -o $tmpfile "$gateway$cid" 2>/dev/null
1422 if test -s $tmpfile
1423 set downloaded 1
1424 break
1425 end
1426 end
1427 end
1428
1429 if test $downloaded -eq 0
1430 echo "❌ Failed to fetch from IPFS"
1431 return 1
1432 end
1433 else
1434 # Local file path
1435 if not test -f $input
1436 echo "❌ File not found: $input"
1437 return 1
1438 end
1439 set tmpfile $input
1440 end
1441
1442 # Verify file has content
1443 if not test -s $tmpfile
1444 echo "❌ Empty or invalid image"
1445 test "$tmpfile" != "$input"; and rm -f $tmpfile
1446 return 1
1447 end
1448
1449 # Convert to sixel and write directly to emacsclient's terminal
1450 # Note: [0] is ImageMagick syntax for first frame, needs quoting to avoid fish array interpretation
1451 set -l sixel (/usr/bin/magick "$tmpfile"'[0]' -resize "$size"x"$size>" sixel:- 2>/dev/null)
1452 if test -z "$sixel"
1453 echo "❌ Failed to convert image to sixel"
1454 test "$tmpfile" != "$input"; and rm -f $tmpfile
1455 return 1
1456 end
1457
1458 # Clean up temp file if we created one
1459 test "$tmpfile" != "$input"; and rm -f $tmpfile
1460
1461 # Output newline + sixel directly to emacsclient's PTY
1462 printf '\n%s\n' $sixel > $pty
1463 echo "" # Add space after sixel in eat
1464end
1465
1466# Full aesthetic platform restart (emacs + reconnect artery)
1467function ac-restart
1468 echo "🔄 Full aesthetic platform restart..."
1469 ac-emacs-restart
1470 if test $status -eq 0
1471 echo "🩸 Reconnecting to artery..."
1472 emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))'
1473 end
1474end
1475
1476# === EMACS TAB TESTING & DIAGNOSTICS ===
1477
1478# Run emacs tab unit tests
1479function ac-test-tabs
1480 /workspaces/aesthetic-computer/scripts/test-emacs-tabs.fish $argv
1481end
1482
1483# Quick diagnose current daemon state
1484function ac-diagnose
1485 if timeout 3 emacsclient -e '(ac-diagnose-all)' >/dev/null 2>&1
1486 echo "✅ Diagnostics logged to .emacs-logs/emacs-debug.log"
1487 echo ""
1488 tail -30 /workspaces/aesthetic-computer/.emacs-logs/emacs-debug.log | grep -E "DIAGNOSE|BUFFER|TIMER|PROC"
1489 else
1490 echo "❌ Daemon not responsive - cannot run diagnostics"
1491 end
1492end
1493
1494# Start emacs CPU profiler
1495function ac-profile-start
1496 if emacsclient -e '(ac-profile-start)' >/dev/null 2>&1
1497 echo "🔬 CPU profiler started"
1498 else
1499 echo "❌ Could not start profiler - daemon not responsive?"
1500 end
1501end
1502
1503# Stop profiler and show summary
1504function ac-profile-stop
1505 if emacsclient -e '(ac-profile-stop)' >/dev/null 2>&1
1506 echo "🔬 Profiler stopped - report in .emacs-logs/emacs-profile.log"
1507 echo ""
1508 if test -f /workspaces/aesthetic-computer/.emacs-logs/emacs-profile.log
1509 tail -50 /workspaces/aesthetic-computer/.emacs-logs/emacs-profile.log
1510 end
1511 else
1512 echo "❌ Could not stop profiler - daemon not responsive?"
1513 end
1514end
1515
1516# Show emacs profiler report interactively
1517function ac-profile-report
1518 emacsclient -e '(ac-profile-report)'
1519end
1520
1521# Watch daemon CPU in real-time
1522function ac-watch-cpu
1523 echo "Watching emacs CPU usage (Ctrl+C to stop)..."
1524 while true
1525 set cpu (ps aux | grep "emacs.*daemon" | grep -v grep | awk '{print $3}')
1526 set mem (ps aux | grep "emacs.*daemon" | grep -v grep | awk '{print $4}')
1527 set responsive "?"
1528 if timeout 1 emacsclient -e t >/dev/null 2>&1
1529 set responsive "✓"
1530 else
1531 set responsive "✗"
1532 end
1533 echo (date '+%H:%M:%S')" CPU: $cpu% MEM: $mem% Responsive: $responsive"
1534 sleep 2
1535 end
1536end
1537
1538# Start/restart CDP tunnel for VS Code control
1539function ac-cdp-tunnel
1540 echo "🩸 Managing CDP tunnel..."
1541
1542 # Kill any existing tunnel
1543 set -l existing (pgrep -f "ssh.*9333.*(host.docker.internal|172.17.0.1)" 2>/dev/null)
1544 if test -n "$existing"
1545 echo " Killing existing tunnel (PID: $existing)..."
1546 kill $existing 2>/dev/null
1547 sleep 1
1548 end
1549
1550 # Determine host address - try host.docker.internal first, fallback to gateway
1551 set -l host_addr "host.docker.internal"
1552 if not getent hosts host.docker.internal >/dev/null 2>&1
1553 # Use Docker gateway IP as fallback
1554 set host_addr "172.17.0.1"
1555 end
1556
1557 # Start new tunnel
1558 echo " Starting new CDP tunnel to $host_addr..."
1559 ssh -f -N -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5 -L 9333:127.0.0.1:9333 me@$host_addr 2>/dev/null
1560
1561 sleep 1
1562
1563 # Verify
1564 if nc -zv localhost 9333 2>&1 | grep -q "Connected"
1565 echo "✅ CDP tunnel online (localhost:9333 → $host_addr:9333)"
1566 return 0
1567 else
1568 echo "❌ CDP tunnel failed - is VS Code running on host with --remote-debugging-port=9333?"
1569 return 1
1570 end
1571end
1572
1573# Check CDP tunnel status
1574function ac-cdp-status
1575 set -l tunnel_pid (pgrep -f "ssh.*9333" 2>/dev/null)
1576 if test -n "$tunnel_pid"
1577 echo "🟢 CDP tunnel process running (PID: $tunnel_pid)"
1578 if nc -zv localhost 9333 2>&1 | grep -q "Connected"
1579 echo "✅ Port 9333 is accessible"
1580 else
1581 echo "⚠️ Port 9333 not accessible (tunnel may be broken)"
1582 end
1583 else
1584 echo "🔴 CDP tunnel not running"
1585 end
1586end
1587
1588# Assume the daemon is running when entering emacs.
1589
1590# For fast config reloading - simple approach
1591function platform
1592 emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))'
1593end
1594
1595function ensure-emacs-daemon-ready
1596 set -l timeout 240
1597 set -l silent 0
1598 set -l force_restart 0
1599 argparse 't/timeout=' 's/silent' 'f/force' -- $argv
1600 or return 1
1601
1602 if set -q _flag_timeout
1603 set timeout $_flag_timeout
1604 end
1605 if set -q _flag_silent
1606 set silent 1
1607 end
1608 if set -q _flag_force
1609 set force_restart 1
1610 end
1611
1612 # Initialize logging
1613 ac-emacs-init-logs
1614 set -l timestamp (date +%Y%m%d_%H%M%S)
1615 set -l log_file $AC_EMACS_LOG_DIR/daemon_$timestamp.log
1616 set -l config_path /home/me/aesthetic-computer/dotfiles/dot_config/emacs.el
1617 set -l wait_step 2
1618 set -l next_report 10
1619
1620 if test $force_restart -eq 1
1621 if test $silent -ne 1
1622 echo "♻️ Forcing emacs daemon restart..."
1623 end
1624 pkill -f "emacs.*daemon" 2>/dev/null
1625 sleep 2
1626 else if pgrep -f "emacs.*daemon" >/dev/null
1627 if timeout 3 emacsclient -e t >/dev/null 2>&1
1628 # Verify the correct config is loaded (not a bare emacs --daemon)
1629 set -l has_config (timeout 3 emacsclient -e '(fboundp (quote aesthetic-backend))' 2>/dev/null)
1630 if test "$has_config" = "t"
1631 if test $silent -ne 1
1632 echo "✅ Emacs daemon already running with correct config"
1633 end
1634 return 0
1635 else
1636 if test $silent -ne 1
1637 echo "⚠️ Emacs daemon running but wrong config loaded, restarting..."
1638 end
1639 pkill -f "emacs.*daemon" 2>/dev/null
1640 sleep 2
1641 end
1642 else
1643 if test $silent -ne 1
1644 echo "⚠️ Emacs daemon process unresponsive, restarting..."
1645 end
1646 pkill -f "emacs.*daemon" 2>/dev/null
1647 sleep 2
1648 end
1649 end
1650
1651 if not test -f $config_path
1652 echo "❌ Emacs configuration not found at $config_path"
1653 return 1
1654 end
1655
1656 if test $silent -ne 1
1657 echo "🚀 Bootstrapping emacs daemon (timeout: $timeout s)"
1658 echo "📜 Logs: $log_file"
1659 end
1660
1661 # Log startup info
1662 echo "=== Emacs Daemon Start ===" > $log_file
1663 echo "Timestamp: "(date -Iseconds) >> $log_file
1664 echo "Config: $config_path" >> $log_file
1665 echo "===========================" >> $log_file
1666
1667 # Start daemon with output to log file
1668 command emacs -q --daemon -l $config_path >> $log_file 2>&1 &
1669 set -l daemon_pid $last_pid
1670 if test -z "$daemon_pid"
1671 set daemon_pid (jobs -l | tail -n1 | awk '{print $2}')
1672 end
1673
1674 echo "PID: $daemon_pid" >> $log_file
1675
1676 # Create symlink to latest log
1677 ln -sf $log_file $AC_EMACS_LOG_DIR/latest.log
1678
1679 set -l elapsed 0
1680 while test $elapsed -lt $timeout
1681 if timeout 3 emacsclient -e t >/dev/null 2>&1
1682 if test $silent -ne 1
1683 echo "✅ Emacs daemon is ready!"
1684 end
1685 # Start crash monitor in background (if not already running)
1686 if not pgrep -f "ac-emacs-crash-monitor" >/dev/null 2>&1
1687 fish -c "ac-emacs-crash-monitor" &>/dev/null &
1688 disown %last 2>/dev/null
1689 end
1690 echo "[DEBUG] ensure-emacs-daemon-ready returning 0"
1691 return 0
1692 end
1693
1694 if test -n "$daemon_pid"
1695 if not kill -0 $daemon_pid 2>/dev/null
1696 if test $silent -ne 1
1697 echo "❌ Emacs daemon exited unexpectedly. Check $log_file for details."
1698 if test -f $log_file
1699 tail -n 20 $log_file
1700 end
1701 end
1702 return 1
1703 end
1704 end
1705
1706 sleep $wait_step
1707 set elapsed (math "$elapsed + $wait_step")
1708
1709 if test $silent -ne 1
1710 if test $elapsed -ge $next_report
1711 echo "⏳ Waiting for emacs daemon... ($elapsed/$timeout s)"
1712 if test -f $log_file
1713 tail -n 5 $log_file
1714 end
1715 set next_report (math "$next_report + 10")
1716 end
1717 end
1718 end
1719
1720 if test $silent -ne 1
1721 echo "❌ Emacs daemon did not become ready within $timeout seconds."
1722 if test -f $log_file
1723 tail -n 20 $log_file
1724 end
1725 end
1726
1727 return 1
1728end
1729
1730# ⏲️ Wait on `entry.fish` to touch the `.waiter` file.
1731
1732function aesthetic
1733 # Parse flags: --force (full kill+restart), --no-wait (skip .waiter)
1734 set -l force 0
1735 set -l no_wait 0
1736 for arg in $argv
1737 switch $arg
1738 case "--force"
1739 set force 1
1740 case "--no-wait"
1741 set no_wait 1
1742 end
1743 end
1744
1745 # Only kill if --force is passed. Otherwise ensure-emacs-daemon-ready
1746 # will reuse a healthy daemon or restart an unhealthy one.
1747 if test $force -eq 1
1748 echo "🔄 Force restart: killing existing emacs..."
1749 ac-emacs-kill
1750 end
1751
1752 # Check if --no-wait flag is passed
1753 if test $no_wait -eq 1
1754 echo "Skipping wait for .waiter file..."
1755 else
1756 # Check if entry.fish already completed (container is ready)
1757 set -l skip_wait 0
1758 if test -f /home/me/.waiter
1759 echo "✅ Container already configured, skipping wait..."
1760 set skip_wait 1
1761 end
1762
1763 if test $skip_wait -eq 0
1764 # Docker not up or emacs not ready - wait for waiter file
1765 clear
1766 set -l config_count 0
1767 set -l last_log_hash ""
1768 set -l entry_log /tmp/entry-fish.log
1769
1770 while not test -f /home/me/.waiter
1771 # Only redraw if log content changed (reduces flicker)
1772 set -l current_log_hash ""
1773 if test -f $entry_log
1774 set current_log_hash (tail -n 12 $entry_log 2>/dev/null | md5sum 2>/dev/null | cut -d' ' -f1)
1775 end
1776
1777 if test "$current_log_hash" != "$last_log_hash" -o $config_count -eq 0 -o (math "$config_count % 10") -eq 0
1778 clear
1779
1780 # Show banner (static, no animation to reduce flicker)
1781 toilet "Configuring..." -f future | lolcat -f
1782
1783 # Show entry.fish progress if log exists
1784 if test -f $entry_log
1785 set -l log_lines (wc -l < $entry_log 2>/dev/null | string trim)
1786 if test -n "$log_lines" -a "$log_lines" -gt 0
1787 echo ""
1788 echo "─────────────────────────────────────────"
1789 tail -n 12 $entry_log 2>/dev/null
1790 echo "─────────────────────────────────────────"
1791 end
1792 else
1793 echo ""
1794 echo "📄 Waiting for entry log at $entry_log"
1795 end
1796
1797 echo "📄 Log: $entry_log (latest: /home/me/.entry-logs/latest.log)"
1798 echo "⏱️ Waited: $config_count s"
1799
1800 set last_log_hash $current_log_hash
1801 end
1802
1803 sleep 1
1804 set config_count (math $config_count + 1)
1805 end
1806 sudo rm /home/me/.waiter 2>/dev/null
1807 end
1808 end
1809
1810 echo "[DEBUG] About to call ensure-emacs-daemon-ready..."
1811 if not ensure-emacs-daemon-ready --timeout=360
1812 echo "❌ Cannot connect to emacs daemon. Please check your emacs configuration."
1813 return 1
1814 end
1815
1816 echo "[DEBUG] ensure-emacs-daemon-ready succeeded!"
1817 echo "[DEBUG] TTY: "(tty 2>&1)
1818 echo "[DEBUG] TERM: $TERM"
1819
1820 # Connect to emacs and run aesthetic-backend to create all tabs
1821 # Auto-reconnect loop: if the crash monitor restarts the daemon,
1822 # this will detect the signal file and reconnect automatically.
1823 set -l reconnect_max 5
1824 set -l reconnect_count 0
1825 while true
1826 echo "🚀 Connecting to aesthetic platform..."
1827 emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))'
1828 set -l exit_code $status
1829
1830 # Check if crash monitor triggered a restart (signal file)
1831 if test -f /tmp/emacs-crash-restart-signal
1832 rm -f /tmp/emacs-crash-restart-signal
1833 set reconnect_count (math "$reconnect_count + 1")
1834 if test $reconnect_count -ge $reconnect_max
1835 echo ""
1836 echo "⛔ Too many crash-restarts ($reconnect_max), giving up auto-reconnect."
1837 echo "💡 Run: aesthetic-now"
1838 return 1
1839 end
1840 echo ""
1841 echo "🔄 Daemon was crash-restarted, reconnecting ($reconnect_count/$reconnect_max)..."
1842 # Wait for daemon to be ready
1843 if not ensure-emacs-daemon-ready --timeout=60
1844 echo "❌ Daemon didn't come back after crash restart."
1845 return 1
1846 end
1847 continue
1848 end
1849
1850 # Normal exit (user quit emacs, or unrelated error)
1851 if test $exit_code -ne 0
1852 echo ""
1853 echo "⚠️ emacsclient exited with code $exit_code"
1854 echo "💡 If the '💻 Aesthetic' VS Code task is frozen, restart it from the Task menu."
1855 echo " Or run: ac-emacs-restart && then restart the task"
1856 end
1857 return $exit_code
1858 end
1859end
1860
1861# Convenience alias for skipping the wait
1862function aesthetic-now
1863 aesthetic --no-wait
1864end
1865
1866# Direct aesthetic function that skips waiting entirely
1867function aesthetic-direct
1868 echo "🚀 Starting aesthetic directly (no wait)..."
1869
1870 if not ensure-emacs-daemon-ready --timeout=300 --silent
1871 echo "❌ Cannot connect to emacs daemon."
1872 return 1
1873 end
1874
1875 # Connect to emacs with aesthetic-backend (with crash-restart reconnect)
1876 set -l reconnect_max 5
1877 set -l reconnect_count 0
1878 while true
1879 emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))'
1880 set -l exit_code $status
1881 if test -f /tmp/emacs-crash-restart-signal
1882 rm -f /tmp/emacs-crash-restart-signal
1883 set reconnect_count (math "$reconnect_count + 1")
1884 if test $reconnect_count -ge $reconnect_max
1885 echo "⛔ Too many crash-restarts, giving up."
1886 return 1
1887 end
1888 echo "🔄 Daemon crash-restarted, reconnecting ($reconnect_count/$reconnect_max)..."
1889 if not ensure-emacs-daemon-ready --timeout=60 --silent
1890 echo "❌ Daemon didn't come back."
1891 return 1
1892 end
1893 continue
1894 end
1895 return $exit_code
1896 end
1897end
1898
1899# TODO: Automatically kill online mode and go to offline mode if necessary.
1900
1901# Helper function to check emacs daemon status
1902function check-daemon
1903 if pgrep -f "emacs.*daemon" >/dev/null
1904 echo "✅ Emacs daemon process found"
1905 if emacsclient -e t >/dev/null 2>&1
1906 echo "✅ Emacs daemon is responsive"
1907 else
1908 echo "❌ Emacs daemon found but not responsive"
1909 end
1910 else
1911 echo "❌ No emacs daemon process found"
1912 end
1913end
1914
1915# Helper function to restart emacs daemon
1916function restart-daemon
1917 echo "🔄 Restarting emacs daemon..."
1918 if ensure-emacs-daemon-ready --force --timeout=360
1919 check-daemon
1920 else
1921 echo "❌ Failed to restart emacs daemon."
1922 check-daemon
1923 end
1924end
1925
1926function ac-site
1927 # ac-site now runs lith (monolith) instead of netlify dev
1928 ac-lith
1929end
1930
1931function ac-lith
1932 cd ~/aesthetic-computer/lith
1933 echo "🪨 Starting lith (monolith server)..."
1934 echo "🔍 Cleaning up any stuck processes..."
1935 pkill -f "lith/server.mjs" 2>/dev/null
1936 sleep 1
1937 echo "🔌 Killing port 8888..."
1938 timeout 5 npx kill-port 8888 2>/dev/null; or true
1939 # Load env vars from system/.env if present
1940 if test -f ~/aesthetic-computer/system/.env
1941 for line in (cat ~/aesthetic-computer/system/.env | grep -v '^#' | grep -v '^$' | grep '=')
1942 set -l parts (string split -m1 '=' $line)
1943 if test (count $parts) -ge 2
1944 set -gx $parts[1] $parts[2]
1945 end
1946 end
1947 end
1948 echo "🚀 Starting server on https://localhost:8888..."
1949 node --watch server.mjs
1950end
1951
1952function ac-deploy --description 'Deploy lith: push + pull + restart on droplet'
1953 cd ~/aesthetic-computer
1954 set LITH_IP "209.38.133.33"
1955
1956 echo "🪨 Deploying to lith ($LITH_IP)..."
1957
1958 # Push local changes if any
1959 set -l status_output (git status --porcelain)
1960 if test -n "$status_output"
1961 echo "⚠️ You have uncommitted changes. Commit first."
1962 return 1
1963 end
1964
1965 echo "📤 Pushing to origin..."
1966 git push origin main; or begin; echo "❌ Push failed"; return 1; end
1967
1968 echo "🔄 Pulling on droplet..."
1969 ssh -o StrictHostKeyChecking=no root@$LITH_IP "cd /opt/ac && git pull origin main && git rev-parse --short HEAD > system/public/.commit-ref && cat system/public/.commit-ref"
1970
1971 echo "🔁 Restarting lith..."
1972 ssh root@$LITH_IP "systemctl restart lith"
1973
1974 sleep 3
1975 set -l version (curl -s "https://aesthetic.computer/api/version" | python3 -c "import json,sys; print(json.load(sys.stdin)['deployed'])" 2>/dev/null)
1976 echo "✅ Deployed: $version"
1977end
1978
1979function ac-media
1980 cd ~/aesthetic-computer/system
1981 echo "📦 Starting Caddy media server on :8111..."
1982 npm run media-server-caddy
1983end
1984
1985# ac - Smart command: cd with no args, jump to piece with args
1986function ac --description 'cd to aesthetic-computer or jump to piece'
1987 if test (count $argv) -eq 0
1988 cd ~/aesthetic-computer
1989 else
1990 set piece_path $argv[1]
1991 echo "🎯 Jumping to: $piece_path"
1992 set response (curl -s -k -X POST https://localhost:8889/jump -H "Content-Type: application/json" -d "{\"piece\": \"$piece_path\"}")
1993
1994 # Show appropriate feedback based on connection status
1995 if string match -q "*vscodeConnected*true*" $response
1996 echo "✅ Sent to VSCode extension"
1997 else if string match -q "*Jump request sent*" $response
1998 echo "✅ Sent to browser clients"
1999 else
2000 echo "$response"
2001 end
2002 end
2003end
2004
2005function ac-help --description "List available ac-* commands"
2006 set -l filter $argv[1]
2007 set -l names (functions -n | string match -r '^ac(-|$)' | string match -v 'ac--*' | sort -u)
2008
2009 if test -n "$filter"
2010 set names (printf "%s\n" $names | string match -i "*$filter*")
2011 end
2012
2013 if test (count $names) -eq 0
2014 echo "No ac-* commands match '$filter'"
2015 return 1
2016 end
2017
2018 echo "AC commands ("(count $names)"):"
2019 for name in $names
2020 echo " $name"
2021 end
2022end
2023
2024function ac-offline
2025 echo "🐭 Starting offline mode..."
2026 ac
2027 cd system/public
2028 cp system/offline-index.html system/public/index.html
2029 npx http-server -p 8888 -c-1 -g -b -S -C ../../ssl-dev/localhost.pem -K ../../ssl-dev/localhost-key.pem
2030end
2031
2032function ac-url
2033 clear
2034 ac
2035 npm run -s url $argv
2036end
2037
2038# 🖼️ Extension views dev server (3D process tree viz at localhost:5555)
2039function ac-views
2040 clear
2041 cd ~/aesthetic-computer/vscode-extension
2042 echo "🖼️ Starting Extension Views Dev Server on http://localhost:5555"
2043 echo " Open dev.html in browser or toggle local mode in VS Code extension"
2044 npx --yes serve views -l 5555
2045end
2046
2047alias ac-watch 'ac; npm run watch'
2048
2049# 📱 Dev log monitoring function with dynamic file detection
2050function ac-dev-log
2051 set log_dir "/tmp/dev-logs"
2052
2053 if not test -d $log_dir
2054 echo "🔍 No dev logs directory found at $log_dir"
2055 echo "💡 Try touching/drawing on your iPhone to generate logs"
2056 return 1
2057 end
2058
2059 echo "📱 Monitoring ALL device logs in real-time..."
2060 echo "🎨 Touch/draw on any device to see live logs..."
2061 echo "🔄 Watching for new files in: $log_dir"
2062 echo "----------------------------------------"
2063
2064 # Kill any existing tail/inotify processes for clean start
2065 pkill -f "tail.*$log_dir" 2>/dev/null
2066 pkill -f "inotifywait.*$log_dir" 2>/dev/null
2067
2068 # Function to start monitoring a single log file
2069 function monitor_log_file
2070 set logfile $argv[1]
2071 set log_name (basename "$logfile" .log)
2072
2073 tail -f "$logfile" 2>/dev/null | while read line
2074 # Extract JSON part (everything after first '{')
2075 set json_start (string match -r '\{.*' "$line")
2076 if test -n "$json_start"
2077 # Try to format with jq for colors
2078 set formatted (echo "$json_start" | jq -C . 2>/dev/null)
2079 if test $status -eq 0
2080 # Extract timestamp/prefix part
2081 set prefix (string replace -r '\{.*' '' "$line")
2082 echo "📱 $log_name: $prefix$formatted"
2083 else
2084 echo "📱 $log_name: $line"
2085 end
2086 else
2087 echo "📱 $log_name: $line"
2088 end
2089 end &
2090 end
2091
2092 # Monitor existing log files
2093 for logfile in $log_dir/*.log
2094 if test -f "$logfile"
2095 echo "🔍 Found existing log: "(basename "$logfile")
2096 monitor_log_file "$logfile"
2097 end
2098 end
2099
2100 # Monitor for new log files being created (fallback if inotify not available)
2101 if command -v inotifywait >/dev/null 2>&1
2102 echo "�️ Using inotifywait for new file detection"
2103 inotifywait -m -e create -e moved_to --format '%w%f' "$log_dir" 2>/dev/null | while read new_file
2104 if string match -q "*.log" "$new_file"
2105 echo "🆕 New log file detected: "(basename "$new_file")
2106 monitor_log_file "$new_file"
2107 end
2108 end &
2109 else
2110 echo "⏰ Using polling for new file detection (inotify not available)"
2111 # Fallback: poll for new files every 2 seconds
2112 while true
2113 for logfile in $log_dir/*.log
2114 if test -f "$logfile"
2115 set log_name (basename "$logfile" .log)
2116 # Check if we're already monitoring this file
2117 if not pgrep -f "tail.*$logfile" >/dev/null
2118 echo "🆕 New log file detected: $log_name"
2119 monitor_log_file "$logfile"
2120 end
2121 end
2122 end
2123 sleep 2
2124 end &
2125 end
2126
2127 # Keep the function running with proper interrupt handling
2128 echo "✅ Monitoring started. Press Ctrl+C to stop."
2129
2130 # Use read to wait for interrupt (Ctrl+C) - this is more reliable in Fish
2131 # The read command will be interrupted by Ctrl+C and return control
2132 echo "Press any key to stop monitoring, or Ctrl+C..."
2133 read -s
2134
2135 # Cleanup on exit
2136 echo "🛑 Stopping all monitoring processes..."
2137 pkill -f "tail.*$log_dir" 2>/dev/null
2138 pkill -f "inotifywait.*$log_dir" 2>/dev/null
2139 echo "✅ Monitoring stopped"
2140end
2141
2142# 📱 List all available device logs
2143function ac-dev-logs
2144 set log_dir "/tmp/dev-logs"
2145
2146 if not test -d $log_dir
2147 echo "🔍 No dev logs directory found"
2148 return 1
2149 end
2150
2151 echo "📱 Available device logs:"
2152 ls -la $log_dir/*.log 2>/dev/null | while read line
2153 echo " $line"
2154 end
2155end
2156
2157# 📱 Clean old device logs
2158function ac-dev-log-clean
2159 set log_dir "/tmp/dev-logs"
2160
2161 if test -d $log_dir
2162 echo "🧹 Cleaning old device logs..."
2163 rm -f $log_dir/*.log
2164 echo "✨ Done!"
2165 else
2166 echo "🔍 No dev logs directory found"
2167 end
2168end
2169
2170# 📱 Improved dev log monitoring with proper Ctrl+C support (test version)
2171function ac-dev-log-new
2172 set log_dir "/tmp/dev-logs"
2173
2174 if not test -d $log_dir
2175 echo "🔍 No dev logs directory found at $log_dir"
2176 echo "💡 Try touching/drawing on your iPhone to generate logs"
2177 return 1
2178 end
2179
2180 echo "📱 Monitoring ALL device logs in real-time..."
2181 echo "🎨 Touch/draw on any device to see live logs..."
2182 echo "🔄 Watching for new files in: $log_dir"
2183 echo "✅ Press Ctrl+C to stop monitoring"
2184 echo "----------------------------------------"
2185
2186 # Kill any existing monitoring processes for clean start
2187 pkill -f "tail.*$log_dir" 2>/dev/null
2188 pkill -f "inotifywait.*$log_dir" 2>/dev/null
2189
2190 # Trap Ctrl+C to clean up properly
2191 function cleanup_logs --on-signal INT
2192 echo ""
2193 echo "🛑 Stopping log monitoring..."
2194 pkill -f "tail.*$log_dir" 2>/dev/null
2195 pkill -f "inotifywait.*$log_dir" 2>/dev/null
2196 echo "✨ Monitoring stopped!"
2197 return 0
2198 end
2199
2200 # Function to start monitoring a single log file
2201 function start_monitor
2202 set logfile $argv[1]
2203 set log_name (basename "$logfile" .log)
2204
2205 tail -f "$logfile" 2>/dev/null | while read line
2206 # Extract JSON part (everything after first '{')
2207 set json_start (string match -r '\{.*' "$line")
2208 if test -n "$json_start"
2209 # Try to format with jq for colors
2210 set formatted (echo "$json_start" | jq -C . 2>/dev/null)
2211 if test $status -eq 0
2212 # Extract timestamp/prefix part
2213 set prefix (string replace -r '\{.*' '' "$line")
2214 echo "📱 $log_name: $prefix$formatted"
2215 else
2216 echo "📱 $log_name: $line"
2217 end
2218 else
2219 echo "📱 $log_name: $line"
2220 end
2221 end &
2222 end
2223
2224 # Start monitoring existing files
2225 set -l monitored_files
2226 for logfile in $log_dir/*.log
2227 if test -f "$logfile"
2228 echo "🔍 Found existing log: "(basename "$logfile")
2229 start_monitor "$logfile"
2230 set -a monitored_files "$logfile"
2231 end
2232 end
2233
2234 # Simple loop that responds quickly to Ctrl+C
2235 if command -v inotifywait >/dev/null 2>&1
2236 echo "👁️ Using inotifywait for new file detection"
2237
2238 # Watch for new files
2239 inotifywait -m -e create -e moved_to --format '%w%f' "$log_dir" 2>/dev/null &
2240 set inotify_pid $last_pid
2241
2242 while true
2243 # Read from inotify if available
2244 if jobs -q %$inotify_pid
2245 # Process new files...
2246 # (simplified for testing)
2247 end
2248
2249 # Check for new files manually as backup
2250 for logfile in $log_dir/*.log
2251 if test -f "$logfile"; and not contains "$logfile" $monitored_files
2252 set log_name (basename "$logfile" .log)
2253 echo "🆕 New log file detected: $log_name"
2254 start_monitor "$logfile"
2255 set -a monitored_files "$logfile"
2256 end
2257 end
2258
2259 sleep 1
2260 end
2261 else
2262 echo "⏰ Using polling for file detection"
2263
2264 while true
2265 # Check for new files
2266 for logfile in $log_dir/*.log
2267 if test -f "$logfile"; and not contains "$logfile" $monitored_files
2268 set log_name (basename "$logfile" .log)
2269 echo "🆕 New log file detected: $log_name"
2270 start_monitor "$logfile"
2271 set -a monitored_files "$logfile"
2272 end
2273 end
2274
2275 sleep 2
2276 end
2277 end
2278end
2279# 📱 Simple improved dev log monitoring (test version)
2280function ac-dev-log-simple
2281 set log_dir "/tmp/dev-logs"
2282
2283 if not test -d $log_dir
2284 echo "🔍 No dev logs directory found at $log_dir"
2285 return 1
2286 end
2287
2288 echo "📱 Monitoring device logs..."
2289 echo "✅ Press Ctrl+C to stop"
2290 echo "----------------------------------------"
2291
2292 # Kill existing processes
2293 pkill -f "tail.*$log_dir" 2>/dev/null
2294
2295 # Simple approach - just monitor all existing files
2296 for logfile in $log_dir/*.log
2297 if test -f "$logfile"
2298 set log_name (basename "$logfile" .log)
2299 echo "🔍 Monitoring: $log_name"
2300 tail -f "$logfile" | while read line
2301 echo "📱 $log_name: $line"
2302 end &
2303 end
2304 end
2305
2306 # Simple loop that can be interrupted
2307 echo "🔄 Running... (Ctrl+C to stop)"
2308 while true
2309 sleep 1
2310 end
2311end
2312# alias ac-kidlisp 'ac; npm run test:kidlisp'
2313# Session server (simplified - no Emacs special handling)
2314function ac-session
2315 echo "🎮 Starting session server..."
2316 ac
2317 cd session-server
2318
2319 echo "🔍 Cleaning up any stuck processes..."
2320 pkill -f "nodemon.*session.mjs" 2>/dev/null
2321 sleep 1
2322 npx kill-port 8889 2>/dev/null
2323
2324 echo "🚀 Starting session server on port 8889..."
2325 PORT=8889 NODE_ENV=development npx nodemon -I --watch session.mjs session.mjs
2326end
2327
2328# Oven service (tape video processing)
2329function ac-oven
2330 echo "🔥 Starting oven service..."
2331 ac
2332 cd oven
2333
2334 echo "🔍 Cleaning up any stuck processes..."
2335 pkill -f "node.*server.mjs" 2>/dev/null
2336 sleep 1
2337 npx kill-port 3002 2>/dev/null
2338
2339 echo "🚀 Starting oven server on https://localhost:3002..."
2340 npm run dev
2341end
2342
2343# Silo service (data & storage dashboard)
2344function ac-silo
2345 echo "🏗️ Starting silo service..."
2346 ac
2347 cd silo
2348
2349 echo "🔍 Cleaning up any stuck processes..."
2350 pkill -f "node.*silo/server.mjs" 2>/dev/null
2351 sleep 1
2352 npx kill-port 3003 2>/dev/null
2353
2354 # SSH tunnel to DO droplet MongoDB (localhost:27018 → droplet:27017)
2355 if not pgrep -f "ssh.*27018:localhost:27017.*64.23.151.169" >/dev/null 2>&1
2356 echo "🔗 Setting up SSH tunnel to DO droplet MongoDB..."
2357 ssh -fN -L 27018:localhost:27017 -i ~/.ssh/silo-deploy-key root@64.23.151.169 2>/dev/null
2358 and echo " tunnel established (localhost:27018)"
2359 or echo " tunnel failed (check SSH key)"
2360 else
2361 echo "🔗 SSH tunnel already active (localhost:27018)"
2362 end
2363
2364 echo "🚀 Starting silo on https://localhost:3003..."
2365 npm run dev
2366end
2367
2368alias ac-stripe-print 'ac; npm run stripe-print-micro'
2369alias ac-stripe-ticket 'ac; npm run stripe-ticket-micro'
2370alias ac-extension 'ac; cd vscode-extension; npm run build; ac'
2371
2372# KidLisp command entrypoint.
2373# Primary path: public `kidlisp` CLI (when installed).
2374# Fallback path: local repo scripts (until CLI implementation fully lands).
2375function ac-kidlisp
2376 set -l cmd $argv[1]
2377 test -z "$cmd"; and set cmd "test"
2378
2379 if command -q kidlisp
2380 switch $cmd
2381 case source-tree st tree
2382 __ac_kidlisp_cli tree $argv[2..-1]
2383 case watch
2384 __ac_kidlisp_cli test --watch $argv[2..-1]
2385 case test direct
2386 __ac_kidlisp_cli test $argv[2..-1]
2387 case help --help -h
2388 __ac_kidlisp_cli --help
2389 case '*'
2390 __ac_kidlisp_cli $argv
2391 end
2392 return $status
2393 end
2394
2395 ac
2396 switch $cmd
2397 case source-tree st tree
2398 __ac_kidlisp_source_tree $argv[2..-1]
2399 case watch
2400 echo "🔍 Running kidlisp tests in watch mode..."
2401 npm run test:kidlisp -- $argv[2..-1]
2402 case test direct
2403 echo "🚀 Running kidlisp tests directly..."
2404 npm run test:kidlisp:direct -- $argv[2..-1]
2405 case help --help -h
2406 echo "kidlisp CLI not installed; using local fallback."
2407 echo "Usage:"
2408 echo " ac-kidlisp test"
2409 echo " ac-kidlisp watch"
2410 echo " ac-kidlisp source-tree <piece>"
2411 echo ""
2412 echo "Install CLI for full command parity: https://kidlisp.com/install"
2413 case '*'
2414 if test (count $argv) -gt 0
2415 # Compatibility path: treat bare value as piece reference.
2416 __ac_kidlisp_source_tree $argv
2417 else
2418 echo "Usage: ac-kidlisp [test|watch|source-tree <piece>]"
2419 end
2420 end
2421end
2422
2423# Pipe command output to session server build stream (for live build progress)
2424# Usage: some-command | ac-pipe
2425function ac-pipe
2426 while read -l line
2427 # Escape double quotes in the line for JSON
2428 set escaped_line (string replace -a '"' '\"' -- $line)
2429 /usr/bin/curl -s -X POST https://session-server.aesthetic.computer/build-stream --header "Content-Type: application/json" --data "{\"line\": \"$escaped_line\"}" > /dev/null 2>&1
2430 echo $line # Also echo to terminal
2431 end
2432end
2433
2434# ATProto PDS Admin - SSH into PDS server and run pdsadmin
2435function ac-at
2436 # ASCII art header with colors
2437 set_color cyan
2438 echo " █████╗ ████████╗██████╗ ██████╗ ██████╗ ████████╗ ██████╗"
2439 echo "██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗██╔═══██╗╚══██╔══╝██╔═══██╗"
2440 echo "███████║ ██║ ██████╔╝██████╔╝██║ ██║ ██║ ██║ ██║"
2441 echo "██╔══██║ ██║ ██╔═══╝ ██╔══██╗██║ ██║ ██║ ██║ ██║"
2442 echo "██║ ██║ ██║ ██║ ██║ ██║╚██████╔╝ ██║ ╚██████╔╝"
2443 echo "╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝"
2444 set_color blue
2445 echo " Personal Data Server - aesthetic.computer"
2446 set_color cyan
2447 echo "🕷️ at.aesthetic.computer 💾 165.227.120.137"
2448 echo "📦 at-blobs-aesthetic-computer 🦋 ATProto Federation"
2449 set_color normal
2450 echo ""
2451
2452 if test (count $argv) -eq 0
2453 # No arguments: drop into interactive shell
2454 ssh -o StrictHostKeyChecking=no -i ~/.ssh/aesthetic_pds root@165.227.120.137
2455 else
2456 # With arguments: run pdsadmin command directly
2457 ssh -o StrictHostKeyChecking=no -i ~/.ssh/aesthetic_pds root@165.227.120.137 "pdsadmin $argv"
2458 end
2459end
2460
2461# Backward compatibility alias
2462alias ac-pds='ac-at'
2463
2464# Agent memory helpers (ac-prefixed, like the rest of the shell UX)
2465function ac-memory --description "Run agent memory CLI"
2466 ac
2467 npm run -s agent-memory -- $argv
2468end
2469
2470function ac-memory-list --description "List remembered sessions"
2471 ac
2472 npm run -s agent-memory:list -- $argv
2473end
2474
2475function ac-memory-remember --description "Create a new session from remembered context"
2476 ac
2477 npm run -s agent-memory:remember -- $argv
2478end
2479
2480function ac-memory-hooks --description "Enable git hooks for memory sync"
2481 ac
2482 npm run -s agent-memory:install-hooks
2483end
2484
2485function ac-memory-sync --description "Import recent Codex sessions into local memory"
2486 ac
2487 npm run -s agent-memory:sync-codex -- $argv
2488end
2489
2490function ac-memory-flush --description "Flush queued local memory records to remote"
2491 ac
2492 npm run -s agent-memory:flush -- $argv
2493end
2494
2495# Claude Code CLI agent
2496function ac-agent
2497 claude $argv
2498end
2499
2500# Ollama LLM daemon management
2501function ac-llama
2502 set -l command $argv[1]
2503
2504 switch $command
2505 case start
2506 echo "🦙 Starting Ollama daemon..."
2507
2508 # Check if already running
2509 if pgrep -f "ollama serve" >/dev/null
2510 echo "⚠️ Ollama daemon already running"
2511 if curl -s http://localhost:11434/api/version >/dev/null 2>&1
2512 echo "✅ Daemon is responsive"
2513 return 0
2514 else
2515 echo "⚠️ Daemon process exists but not responsive, restarting..."
2516 pkill -f "ollama serve" 2>/dev/null
2517 sleep 2
2518 end
2519 end
2520
2521 # Start the daemon
2522 nohup ollama serve > /tmp/ollama.log 2>&1 &
2523 set daemon_pid $last_pid
2524 echo "🚀 Ollama daemon started (PID: $daemon_pid)"
2525
2526 # Wait for it to become responsive
2527 set -l timeout 30
2528 set -l elapsed 0
2529 while test $elapsed -lt $timeout
2530 if curl -s http://localhost:11434/api/version >/dev/null 2>&1
2531 echo "✅ Ollama daemon is ready!"
2532 return 0
2533 end
2534 sleep 1
2535 set elapsed (math $elapsed + 1)
2536 end
2537
2538 echo "❌ Ollama daemon didn't become ready within $timeout seconds"
2539 echo "📋 Check logs at /tmp/ollama.log"
2540 return 1
2541
2542 case stop
2543 echo "🛑 Stopping Ollama daemon..."
2544 pkill -f "ollama serve" 2>/dev/null
2545 if test $status -eq 0
2546 echo "✅ Ollama daemon stopped"
2547 else
2548 echo "⚠️ No Ollama daemon was running"
2549 end
2550
2551 case restart
2552 echo "🔄 Restarting Ollama daemon..."
2553 ac-llama stop
2554 sleep 2
2555 ac-llama start
2556
2557 case status
2558 if pgrep -f "ollama serve" >/dev/null
2559 echo "✅ Ollama daemon process is running"
2560 if curl -s http://localhost:11434/api/version >/dev/null 2>&1
2561 set -l ollama_version (curl -s http://localhost:11434/api/version 2>/dev/null | jq -r '.version' 2>/dev/null)
2562 echo "✅ Daemon is responsive (version: $ollama_version)"
2563
2564 # List loaded models
2565 echo "📦 Checking available models..."
2566 set -l models (curl -s http://localhost:11434/api/tags 2>/dev/null | jq -r '.models[]?.name' 2>/dev/null)
2567 if test -n "$models"
2568 echo "Models available:"
2569 for model in $models
2570 echo " • $model"
2571 end
2572 else
2573 echo " (no models loaded)"
2574 end
2575 else
2576 echo "❌ Daemon process exists but not responsive"
2577 echo "📋 Check logs at /tmp/ollama.log"
2578 end
2579 else
2580 echo "❌ Ollama daemon is not running"
2581 end
2582
2583 case logs
2584 if test -f /tmp/ollama.log
2585 echo "📋 Ollama logs (last 50 lines):"
2586 tail -n 50 /tmp/ollama.log
2587 else
2588 echo "⚠️ No log file found at /tmp/ollama.log"
2589 end
2590
2591 case '*'
2592 echo "Usage: ac-llama [start|stop|restart|status|logs]"
2593 echo ""
2594 echo "Commands:"
2595 echo " start - Start the Ollama daemon"
2596 echo " stop - Stop the Ollama daemon"
2597 echo " restart - Restart the Ollama daemon"
2598 echo " status - Check daemon status and list models"
2599 echo " logs - Show recent daemon logs"
2600 return 1
2601 end
2602end
2603
2604# alias ac-shell 'ac; ac-url; ac-tunnel; fish'
2605# alias ac-offline 'ac; cd system/public; npx http-server -p 8888 -c-1 -g -b -S -C ../../ssl-dev/localhost.pem -K ../../ssl-dev/localhost-key.pem'
2606alias ac-redis 'clear; ac; npm run redis'
2607alias ac-udp 'ssh root@157.245.134.225' # ac monolith udp server management
2608
2609# Send aesthetic.computer playlist to TV cast coordination
2610function ac-ff-playlist
2611 cd ~/aesthetic-computer
2612 ./rebroadcast.sh
2613end
2614
2615# Connect to the aesthetic platform (emacs TUI)
2616alias ac-aesthetic 'aesthetic-now'
2617
2618alias ac-servers 'clear; ac; npm run -s servers; env nogreet=true fish'
2619alias ac-chat-system 'clear; ac; npm run -s chat; cd nanos; npm run chat-system:dev; fish'
2620alias ac-chat-sotce 'clear; ac; npm run -s chat; cd nanos; npm run chat-sotce:dev; fish'
2621alias ac-chat-clock 'clear; ac; npm run -s chat; cd nanos; npm run chat-clock:dev; fish'
2622alias ac-logger 'ac; cd system; npx netlify logs:function index'
2623alias sotce-net 'ac; cd system; npx netlify logs:function sotce-net'
2624alias acw 'cd ~/aesthetic-computer/system; npm run watch'
2625
2626# GitHub Copilot CLI (LLM agent in terminal)
2627# Uses GH_TOKEN from vault for auth, runs interactively
2628# Default to Claude Opus 4.5 for best quality
2629# Use --continue to resume last session after reboot
2630alias ac-llm 'clear; ac; claude'
2631alias ac-llm-continue 'clear; ac; claude --continue'
2632alias ac-llm-resume 'clear; ac; claude --resume'
2633
2634# Mail sync (mbsync + mu index for mu4e)
2635# Syncs both mail@aesthetic.computer and me@jas.life
2636function ac-mail
2637 clear
2638 set -l mbsyncrc ~/.mbsyncrc
2639 set -l msmtprc ~/.msmtprc
2640 set -l authinfo ~/.authinfo
2641 set -l maildir_ac ~/.mail
2642 set -l maildir_jas ~/.mail-jas
2643 set -l maildir_all ~/.mail-all
2644
2645 # Check for required binaries
2646 if not command -q mbsync
2647 echo "📬 Mail tools not installed. Installing..."
2648 sudo dnf install -y isync maildir-utils msmtp 2>/dev/null
2649 if not command -q mbsync
2650 echo "❌ Failed to install mail tools (isync/maildir-utils/msmtp)"
2651 echo " Waiting... (press Ctrl+C to exit)"
2652 sleep infinity
2653 return 1
2654 end
2655 end
2656
2657 # Configs come from vault via devault.fish (~/.mbsyncrc, ~/.msmtprc)
2658 if not test -f $mbsyncrc
2659 echo "❌ Missing $mbsyncrc — run devault.fish to copy from vault"
2660 echo " Waiting... (press Ctrl+C to exit)"
2661 sleep infinity
2662 return 1
2663 end
2664
2665 if not test -f $msmtprc
2666 echo "❌ Missing $msmtprc — run devault.fish to copy from vault"
2667 echo " Waiting... (press Ctrl+C to exit)"
2668 sleep infinity
2669 return 1
2670 end
2671
2672 chmod 600 $mbsyncrc $msmtprc
2673
2674 # Create maildirs
2675 mkdir -p $maildir_ac $maildir_jas
2676
2677 # Unified maildir root via symlinks (for mu to index both)
2678 mkdir -p $maildir_all
2679 ln -sfn $maildir_ac $maildir_all/ac
2680 ln -sfn $maildir_jas $maildir_all/jas
2681
2682 # Initialize mu database if needed
2683 if not test -d "$HOME/.cache/mu/xapian"
2684 echo "📬 Initializing mu database..."
2685 mu init --maildir=$maildir_all --my-address=mail@aesthetic.computer --my-address=me@jas.life 2>/dev/null
2686 end
2687
2688 echo "📬 mail — sync loop (mail@aesthetic.computer + me@jas.life)"
2689 echo "────────────────────────────────────────────────────────────"
2690 echo ""
2691
2692 # Initial sync
2693 echo "🔄 Initial sync..."
2694 mbsync ac-mail 2>&1
2695 mbsync jas-mail 2>&1
2696 mu index --quiet 2>/dev/null
2697 echo "✅ Sync complete. "(date '+%H:%M:%S')
2698 echo ""
2699
2700 # Periodic sync loop (every 5 minutes, matching mu4e-update-interval)
2701 while true
2702 sleep 300
2703 echo "🔄 Syncing... "(date '+%H:%M:%S')
2704 mbsync ac-mail 2>&1
2705 mbsync jas-mail 2>&1
2706 mu index --quiet 2>/dev/null
2707 echo "✅ Done. "(date '+%H:%M:%S')
2708 end
2709end
2710
2711# Process viewer (htop for monitoring system resources)
2712alias ac-top 'clear; htop'
2713
2714alias cat 'bat -p' # use bat for syntax highlighting instead of the `cat` default
2715
2716# 📶 Signal messenger (gurk TUI)
2717function ac-signal
2718 clear
2719 echo "📶 Signal Messenger (gurk)"
2720 echo ""
2721
2722 if not command -q gurk
2723 echo "⏳ gurk not found, installing from source..."
2724 if not command -q cargo
2725 echo "❌ Rust/cargo not found. Cannot build gurk."
2726 echo " Waiting... (press Ctrl+C to exit)"
2727 sleep infinity
2728 return 1
2729 end
2730 cargo install --git https://github.com/boxdot/gurk-rs gurk
2731 if test $status -ne 0
2732 echo "❌ Failed to build gurk"
2733 echo " Waiting... (press Ctrl+C to exit)"
2734 sleep infinity
2735 return 1
2736 end
2737 end
2738
2739 # signal-cli must be available (used by gurk as backend)
2740 if not command -q signal-cli
2741 echo "❌ signal-cli not found. Install it first."
2742 echo " Waiting... (press Ctrl+C to exit)"
2743 sleep infinity
2744 return 1
2745 end
2746
2747 gurk
2748end
2749
2750# 🧟 Zombie process monitor and cleanup
2751function ac-zombie-monitor
2752 echo "🧟 Starting zombie process monitor..."
2753 echo "📊 Checking every 30 seconds for zombie accumulation"
2754 echo "🔪 Will clean up if zombies > 100"
2755 echo "Press Ctrl+C to stop monitoring"
2756
2757 while true
2758 set zombie_count (ps aux | grep -c defunct)
2759 set timestamp (date '+%H:%M:%S')
2760
2761 if test $zombie_count -gt 100
2762 echo "[$timestamp] ⚠️ Found $zombie_count zombies - cleaning up..."
2763 # Find parent processes with zombie children and log them
2764 ps aux | grep defunct | awk '{print $2}' | while read zpid
2765 set parent_pid (ps -o ppid= -p $zpid 2>/dev/null | string trim)
2766 if test -n "$parent_pid"
2767 set parent_cmd (ps -o cmd= -p $parent_pid 2>/dev/null | string trim)
2768 echo " 🧟 Zombie $zpid has parent $parent_pid: $parent_cmd"
2769 end
2770 end
2771 echo " 🧹 Attempting cleanup by reaping zombies..."
2772 # Signal parent processes to reap their zombie children
2773 ps aux | grep defunct | awk '{print $2}' | while read zpid
2774 set parent_pid (ps -o ppid= -p $zpid 2>/dev/null | string trim)
2775 if test -n "$parent_pid"; and test "$parent_pid" != "1"
2776 # Send SIGCHLD to parent to trigger reaping
2777 kill -CHLD $parent_pid 2>/dev/null
2778 end
2779 end
2780 sleep 2
2781 set new_zombie_count (ps aux | grep -c defunct)
2782 echo " 📊 Zombies after cleanup: $new_zombie_count (was $zombie_count)"
2783 else
2784 echo "[$timestamp] ✅ System healthy: $zombie_count zombies"
2785 end
2786
2787 sleep 30
2788 end
2789end
2790
2791# Quick zombie status check
2792function ac-zombie-status
2793 set zombie_count (ps aux | grep -c defunct)
2794 echo "🧟 Current zombie count: $zombie_count"
2795
2796 if test $zombie_count -gt 10
2797 echo "📊 Top zombie-producing parents:"
2798 ps aux | grep defunct | awk '{print $2}' | while read zpid
2799 set parent_pid (ps -o ppid= -p $zpid 2>/dev/null | string trim)
2800 if test -n "$parent_pid"
2801 ps -o pid,cmd= -p $parent_pid 2>/dev/null
2802 end
2803 end | sort | uniq -c | sort -rn | head -5
2804 end
2805end
2806
2807# set up an ngrok tunnel
2808
2809function ac-tunnel
2810 # Run in foreground for Emacs eat terminals (shows logs)
2811 # Skip auto-restart loop when in Emacs - just run once
2812 # Use AC_EMACS_MODE instead of INSIDE_EMACS to avoid conflicts with tools
2813 if test -n "$AC_EMACS_MODE"
2814 echo "🚇 Starting tunnel (foreground mode for Emacs)..."
2815 ac
2816 npm run tunnel
2817 return
2818 end
2819
2820 set tmp (mktemp)
2821 ngrok start --config ngrok.yml --all 2>$tmp
2822 set ngrok_exit $status
2823 set err (cat $tmp)
2824 rm -f $tmp
2825
2826 if test $ngrok_exit -ne 0
2827 if string match -q '*ERR_NGROK_334*' $err
2828 clear
2829 echo "🟢 tunnel already online — watching..."
2830 # Add timeout to prevent infinite loops (5 minutes max)
2831 set timeout_count 0
2832 set max_timeout 60 # 60 * 5 seconds = 5 minutes
2833 while test $timeout_count -lt $max_timeout
2834 sleep 5
2835 set timeout_count (math $timeout_count + 1)
2836 if not curl --silent --max-time 2 --output /dev/null https://local.aesthetic.computer
2837 echo "🔁 tunnel down, restarting..."
2838 # Kill any existing ngrok processes to ensure clean restart
2839 pkill -f ngrok 2>/dev/null
2840 sleep 2
2841 ac-tunnel
2842 return
2843 end
2844 end
2845 echo "⏱️ Tunnel monitoring timeout reached (5 minutes)"
2846 return
2847 else
2848 echo "❌ ngrok error:"
2849 echo $err
2850 end
2851 else
2852 echo "🚀 tunnel started successfully!"
2853 end
2854end
2855
2856function ac-function
2857 # Watch Netlify function logs
2858 # Usage: ac-function [function-name]
2859 # If no function name is provided, lists all functions
2860 if test (count $argv) -eq 0
2861 echo "📋 Watching all Netlify function logs..."
2862 npx netlify logs:function
2863 else
2864 echo "📋 Watching logs for function: $argv[1]"
2865 npx netlify logs:function $argv[1]
2866 end
2867end
2868
2869# a shell-gpt shortcut (must be all lowercase / otherwise quoted)
2870
2871function umm
2872 # Use string escape to handle special characters
2873 set -l args (string join " " $argv)
2874 # Pass the joined, escaped string to sgpt
2875 sgpt --chat umm "$args"
2876end
2877
2878function umms
2879 sgpt --show-chat umm
2880end
2881
2882function code
2883 if set -q argv[1]
2884 set -l args (string join " " $argv)
2885 sgpt --code --chat code "$args"
2886 else
2887 sgpt --code --editor --chat code
2888 end
2889end
2890
2891function codes
2892 sgpt --show-chat code
2893end
2894
2895function copy
2896 # Extract everything from the chat after the last "assistant: " line.
2897 set content (sgpt --show-chat code | tac | sed '/^assistant: /q' | tac | sed '1s/^assistant: //')
2898 printf "%s\n" $content | xclip -selection clipboard
2899end
2900
2901function done
2902 rm /tmp/chat_cache/code 2>/dev/null
2903 echo "bye :)"
2904end
2905
2906function ok
2907 rm /tmp/chat_cache/umm 2>/dev/null
2908 echo "bye :)"
2909end
2910
2911function forget
2912 rm /tmp/chat_cache/umm 2>/dev/null
2913 echo "umm, i forgot :)"
2914end
2915
2916# Increase Node.js heap size
2917
2918set -x NODE_OPTIONS "--max-old-space-size=4096"
2919
2920# Set Google Application Credentials
2921
2922set -x GOOGLE_APPLICATION_CREDENTIALS /home/me/aesthetic-computer/nanos/gcp-service.key.json
2923
2924set -x PATH /google-cloud-sdk/bin $PATH
2925
2926alias nvm forget
2927
2928# use tab to autocomplete the first suggestion
2929
2930bind \t complete-select-first
2931
2932function ac-deploy-session
2933 set script_path ~/aesthetic-computer/session-server/deploy.fish
2934 if test -f $script_path
2935 fish $script_path $argv
2936 else
2937 echo "❌ Deploy script not found at $script_path"
2938 return 1
2939 end
2940end
2941
2942function clipboard
2943 set content $argv
2944 set -l hosts host.docker.internal $HOST_IP 172.17.0.1
2945 for host in $hosts
2946 echo "🧪 trying $host..."
2947 if echo "" | nc -z -w 0.15 $host 12345 2>/dev/null
2948 echo "✅ sending to $host"
2949 printf "%s\n" $content | nc $host 12345
2950 return
2951 end
2952 end
2953 echo "❌ clipboard: No reachable host for clipboard relay"
2954end
2955
2956# Automatically reload the fish config when it changes. 25.04.28.00.12
2957
2958# if status --is-interactive
2959
2960# function __fish_watch_config --description "Watch and reload config.fish on save"
2961
2962# while inotifywait --quiet -e close_write ~/.config/fish/config.fish
2963
2964# echo "🐟 Reloading config.fish..."
2965
2966# source ~/.config/fish/config.fish
2967
2968# end
2969
2970# end
2971
2972# set pidfile ~/.cache/fish-config-watcher.pid
2973
2974# if not test -e $pidfile; or not kill (cat $pidfile) ^/dev/null
2975
2976# __fish_watch_config & disown
2977
2978# echo $last_pid > $pidfile
2979
2980# end
2981
2982# end
2983
2984# ac-shop - Shopify CLI for Aesthetic Computer
2985function ac-shop
2986 node /workspaces/aesthetic-computer/ac-shop/shopify.mjs $argv
2987end
2988
2989# ac-tezbot - Tezos daemon helpers
2990set -gx TEZBOT_SCRIPT /workspaces/aesthetic-computer/tezos/ac-tezbot.mjs
2991
2992function tezbot --description "Tezos daemon interface"
2993 if test (count $argv) -eq 0
2994 node $TEZBOT_SCRIPT status
2995 else
2996 node $TEZBOT_SCRIPT $argv
2997 end
2998end
2999
3000function tezbot-start --description "Start tezbot daemon"
3001 node $TEZBOT_SCRIPT status >/dev/null 2>&1
3002 if test $status -eq 0
3003 echo "🟢 Tezbot already running"
3004 else
3005 echo "🚀 Starting tezbot..."
3006 nohup node $TEZBOT_SCRIPT > /tmp/tezbot.log 2>&1 &
3007 sleep 0.5
3008 node $TEZBOT_SCRIPT status
3009 end
3010end
3011
3012function tezbot-stop --description "Stop tezbot daemon"
3013 node $TEZBOT_SCRIPT stop
3014end
3015
3016function tz-balance --description "Get Tezos wallet balance via tezbot"
3017 node $TEZBOT_SCRIPT cmd balance
3018end
3019
3020function tz-status --description "Get Tezos contract status via tezbot"
3021 node $TEZBOT_SCRIPT cmd status
3022end
3023
3024function tz-tokens --description "List minted tokens via tezbot"
3025 node $TEZBOT_SCRIPT cmd tokens
3026end
3027
3028# 🎮 KidLisp Playdate Build and Simulate Functions
3029
3030# Build a KidLisp program for Playdate
3031# Usage: ac-playdate-build examples/bop.lisp
3032function ac-playdate-build
3033 if test (count $argv) -lt 1
3034 echo "Usage: ac-playdate-build <source.lisp>"
3035 echo "Example: ac-playdate-build examples/bop.lisp"
3036 return 1
3037 end
3038
3039 set -l source_file $argv[1]
3040 cd /workspaces/aesthetic-computer/kidlisp-playdate
3041 source build.fish $source_file
3042end
3043
3044# Run the last built Playdate game on host simulator
3045# Usage: ac-playdate-simulate [game-name]
3046# If no game name provided, uses 'bop' as default
3047function ac-playdate-simulate
3048 set -l game_name (test (count $argv) -ge 1; and echo $argv[1]; or echo "bop")
3049 set -l pdx_path "/workspaces/aesthetic-computer/kidlisp-playdate/build/$game_name.pdx"
3050
3051 if not test -d $pdx_path
3052 echo "❌ Game not found: $pdx_path"
3053 echo "💡 Build it first with: ac-playdate-build examples/$game_name.lisp"
3054 return 1
3055 end
3056
3057 echo "🎮 Deploying $game_name to host simulator..."
3058
3059 # Kill existing simulator
3060 ssh jas@172.17.0.1 "pkill -9 -f Playdate" 2>/dev/null
3061 sleep 0.3
3062
3063 # Copy and run
3064 scp -r $pdx_path jas@172.17.0.1:~/
3065 ssh jas@172.17.0.1 "DISPLAY=:0 nohup ~/PlaydateSDK/bin/PlaydateSimulator ~/$game_name.pdx >/dev/null 2>&1 &"
3066
3067 echo "✅ $game_name running on host simulator"
3068end
3069
3070# Build and immediately simulate
3071# Usage: ac-playdate examples/bop.lisp
3072function ac-playdate
3073 if test (count $argv) -lt 1
3074 echo "Usage: ac-playdate <source.lisp>"
3075 echo "Example: ac-playdate examples/bop.lisp"
3076 return 1
3077 end
3078
3079 set -l source_file $argv[1]
3080 set -l game_name (basename $source_file .lisp)
3081
3082 ac-playdate-build $source_file
3083 and ac-playdate-simulate $game_name
3084end
3085
3086# 🖥️ Electron App Management (SSH to Mac host)
3087# Restarts the Aesthetic Computer Electron app on the host Mac
3088
3089function ac-electron-restart --description "Restart the Aesthetic Computer Electron app on host Mac"
3090 set -l host "jas@host.docker.internal"
3091 set -l ac_path "/Users/jas/Desktop/code/aesthetic-computer/ac-electron"
3092
3093 echo "🔄 Restarting Aesthetic Computer Electron app..."
3094
3095 # Kill existing Electron processes (our app only, not VS Code)
3096 ssh -o StrictHostKeyChecking=no $host "pkill -f '$ac_path/node_modules/electron'" 2>/dev/null
3097 sleep 1
3098
3099 # Start the app again
3100 ssh -o StrictHostKeyChecking=no $host "cd $ac_path && nohup npm start > /tmp/ac-electron.log 2>&1 &"
3101
3102 echo "✅ Electron app restarting... check /tmp/ac-electron.log on host for output"
3103end
3104
3105function ac-electron-stop --description "Stop the Aesthetic Computer Electron app on host Mac"
3106 set -l host "jas@host.docker.internal"
3107 set -l ac_path "/Users/jas/Desktop/code/aesthetic-computer/ac-electron"
3108
3109 echo "🛑 Stopping Aesthetic Computer Electron app..."
3110 ssh -o StrictHostKeyChecking=no $host "pkill -f '$ac_path/node_modules/electron'" 2>/dev/null
3111 echo "✅ Electron app stopped"
3112end
3113
3114function ac-electron-start --description "Start the Aesthetic Computer Electron app on host Mac"
3115 set -l host "jas@host.docker.internal"
3116 set -l ac_path "/Users/jas/Desktop/code/aesthetic-computer/ac-electron"
3117
3118 echo "🚀 Starting Aesthetic Computer Electron app..."
3119 ssh -o StrictHostKeyChecking=no $host "cd $ac_path && nohup npm start > /tmp/ac-electron.log 2>&1 &"
3120 echo "✅ Electron app started"
3121end
3122
3123function ac-electron-dev --description "Toggle dev mode for Electron app (loads files from repo)"
3124 set -l host "jas@host.docker.internal"
3125 set -l dev_flag ".ac-electron-dev"
3126
3127 # Check current state
3128 set -l is_dev (ssh -o StrictHostKeyChecking=no $host "test -f ~/$dev_flag && echo yes || echo no" 2>/dev/null)
3129
3130 if test "$argv[1]" = "status"
3131 if test "$is_dev" = "yes"
3132 echo "🔧 Dev mode is ENABLED"
3133 echo " Files load from: ~/aesthetic-computer/ac-electron/"
3134 else
3135 echo "📦 Dev mode is DISABLED"
3136 echo " Files load from: app bundle"
3137 end
3138 return 0
3139 end
3140
3141 if test "$argv[1]" = "on"
3142 ssh -o StrictHostKeyChecking=no $host "touch ~/$dev_flag"
3143 echo "🔧 Dev mode ENABLED - restart Electron app to apply"
3144 echo " Run: ac-electron-restart"
3145 return 0
3146 end
3147
3148 if test "$argv[1]" = "off"
3149 ssh -o StrictHostKeyChecking=no $host "rm -f ~/$dev_flag"
3150 echo "📦 Dev mode DISABLED - restart Electron app to apply"
3151 echo " Run: ac-electron-restart"
3152 return 0
3153 end
3154
3155 # Toggle
3156 if test "$is_dev" = "yes"
3157 ssh -o StrictHostKeyChecking=no $host "rm -f ~/$dev_flag"
3158 echo "📦 Dev mode DISABLED - restart Electron app to apply"
3159 else
3160 ssh -o StrictHostKeyChecking=no $host "touch ~/$dev_flag"
3161 echo "🔧 Dev mode ENABLED - restart Electron app to apply"
3162 end
3163 echo " Run: ac-electron-restart"
3164end
3165
3166function ac-electron-reload --description "Reload all Electron windows (dev mode: picks up file changes)"
3167 set -l host "jas@host.docker.internal"
3168
3169 echo "♻️ Reloading Electron windows..."
3170 # Use osascript to send Cmd+R to Aesthetic Computer windows
3171 ssh -o StrictHostKeyChecking=no $host 'osascript -e "tell application \"Aesthetic Computer\" to activate" -e "delay 0.2" -e "tell application \"System Events\" to keystroke \"r\" using command down"' 2>/dev/null
3172 echo "✅ Reload triggered"
3173end
3174
3175# 🎸 Ableton M4L Console Tunnel
3176# Listen for console.log/error/warn from M4L devices via UDP
3177
3178function ac-ableton-tunnel --description "Listen for Ableton M4L device console logs"
3179 set -l port 7777
3180 set -l host "jas@host.docker.internal"
3181
3182 echo "🎸 AC Ableton Console Tunnel"
3183 echo " Listening for M4L device logs on UDP port $port..."
3184 echo " (Ctrl+C to stop)"
3185 echo ""
3186 echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
3187
3188 # Run nc on the Mac via SSH to listen for UDP messages
3189 # Max sends to 127.0.0.1:7777 on the Mac
3190 ssh -o StrictHostKeyChecking=no -t $host "nc -lu $port" 2>/dev/null
3191end
3192
3193function ac-ableton-tunnel-simple --description "Simple UDP listener (run on Mac directly)"
3194 echo "🎸 AC Ableton Console (Simple Mode)"
3195 echo " Run this on your Mac (not in devcontainer):"
3196 echo ""
3197 echo " nc -lu 7777"
3198 echo ""
3199 echo " Or with formatting:"
3200 echo " nc -lu 7777 | while read line; do echo \"\$(date '+%H:%M:%S') \$line\"; done"
3201end
3202
3203# 🖥️ Machine Info / SSH Helpers
3204# Read machine configs from vault/machines.json
3205
3206function __ac_machines_file --description "Resolve machines.json with local cache fallback"
3207 set -l machines_file "/workspaces/aesthetic-computer/aesthetic-computer-vault/machines.json"
3208 set -l machines_cache "$HOME/.cache/ac/machines.json"
3209
3210 if test -f $machines_file
3211 mkdir -p (dirname $machines_cache)
3212 cp $machines_file $machines_cache 2>/dev/null
3213 echo $machines_file
3214 return 0
3215 end
3216
3217 if test -f $machines_cache
3218 echo $machines_cache
3219 return 0
3220 end
3221
3222 return 1
3223end
3224
3225function ac-host --description "Show current host SSH config from machines.json"
3226 set -l machines_file (__ac_machines_file)
3227
3228 if test $status -ne 0 -o -z "$machines_file"
3229 echo "❌ machines.json not found in vault or local cache"
3230 return 1
3231 end
3232
3233 set -l machine_key $argv[1]
3234
3235 if test -z "$machine_key"
3236 # Show all machines
3237 echo "📍 Available machines (from vault/machines.json):"
3238 echo ""
3239 cat $machines_file | jq -r '.machines | to_entries[] | "\(.value.emoji // "🖥️") \(.key): \(.value.label) [\(.value.os // "unknown")]"'
3240 echo ""
3241 echo "Usage: ac-host <machine-key> [ssh|ip|info|raw]"
3242 echo "Examples:"
3243 echo " ac-host jeffrey-macbook ssh # SSH to the machine"
3244 echo " ac-host ff1-dvveklza ip # Show just the IP"
3245 echo " ac-host mac-mini info # Show full info"
3246 return 0
3247 end
3248
3249 set -l action $argv[2]
3250 test -z "$action"; and set action info
3251 set -l machine_data (jq -c --arg key "$machine_key" '.machines[$key]' $machines_file)
3252
3253 if test "$machine_data" = "null" -o -z "$machine_data"
3254 echo "❌ Machine '$machine_key' not found"
3255 echo "Available: "(jq -r '.machines | keys | join(", ")' $machines_file)
3256 return 1
3257 end
3258
3259 set -l ip (echo $machine_data | jq -r '.ip // empty')
3260 set -l user (echo $machine_data | jq -r '.user // "jas"')
3261 set -l host (echo $machine_data | jq -r '.host // empty')
3262 set -l label (echo $machine_data | jq -r '.label // .key')
3263 set -l ssh_port (echo $machine_data | jq -r '.ssh.port // .port // empty')
3264 set -l ssh_key (echo $machine_data | jq -r '.ssh.keyFile // empty')
3265 set -l ssh_enabled (echo $machine_data | jq -r '.ssh.enabled // true')
3266
3267 # Use host if specified (e.g., host.docker.internal), else ip
3268 set -l target $host
3269 if test -z "$target"
3270 set target $ip
3271 end
3272
3273 if string match -q "~/*" $ssh_key
3274 set ssh_key "$HOME/"(string sub -s 3 -- $ssh_key)
3275 end
3276
3277 switch $action
3278 case ssh
3279 if test "$ssh_enabled" = "false"
3280 echo "❌ SSH is disabled for $machine_key in machines.json"
3281 return 1
3282 end
3283 if test -z "$target"
3284 echo "❌ No IP/host configured for $machine_key"
3285 return 1
3286 end
3287 echo "🔗 Connecting to $label ($user@$target)..."
3288 set -l ssh_args -o StrictHostKeyChecking=no
3289 if test -n "$ssh_port"
3290 set ssh_args $ssh_args -p $ssh_port
3291 end
3292 if test -n "$ssh_key"
3293 set ssh_args $ssh_args -i $ssh_key
3294 end
3295 ssh $ssh_args $user@$target
3296 case ip
3297 if test -n "$ip"
3298 echo $ip
3299 else if test -n "$host"
3300 echo $host
3301 else
3302 echo "❌ No IP configured"
3303 return 1
3304 end
3305 case info
3306 echo "📋 $label"
3307 echo $machine_data | jq '
3308 del(
3309 .password,
3310 .apiKey,
3311 .token,
3312 .accessToken,
3313 .secret,
3314 .privateKey,
3315 .ssh.password,
3316 .ssh.apiKey,
3317 .ssh.token,
3318 .ssh.privateKey
3319 )
3320 '
3321 case raw
3322 echo "📋 $label (raw)"
3323 echo $machine_data | jq .
3324 case '*'
3325 echo "Usage: ac-host <machine-key> [ssh|ip|info|raw]"
3326 return 1
3327 end
3328end
3329
3330function ac-machines --description "List all machines from vault/machines.json"
3331 ac-host
3332end
3333
3334function ac-host-nmap --description "Run nmap scan on local network via current host"
3335 set -l machines_file (__ac_machines_file)
3336 if test $status -ne 0 -o -z "$machines_file"
3337 echo "❌ machines.json not found in vault or local cache"
3338 return 1
3339 end
3340 set -l search_term $argv[1]
3341
3342 set -l hosts_to_try (jq -r '
3343 .machines
3344 | to_entries[]
3345 | select((.value.ip // "") != "" and (.value.ssh.enabled // true))
3346 | .key
3347 ' $machines_file)
3348
3349 if test (count $hosts_to_try) -eq 0
3350 echo "❌ No candidate hosts with IP + SSH enabled found in machines.json"
3351 return 1
3352 end
3353
3354 for host_key in $hosts_to_try
3355 set -l host_data (jq -c --arg key "$host_key" '.machines[$key]' $machines_file)
3356 if test "$host_data" = "null"
3357 continue
3358 end
3359
3360 set -l ip (echo $host_data | jq -r '.ip // empty')
3361 set -l user (echo $host_data | jq -r '.user // "jas"')
3362 set -l label (echo $host_data | jq -r '.label')
3363 set -l ssh_port (echo $host_data | jq -r '.ssh.port // empty')
3364 set -l ssh_key (echo $host_data | jq -r '.ssh.keyFile // empty')
3365
3366 if test -z "$ip"
3367 continue
3368 end
3369
3370 if string match -q "~/*" $ssh_key
3371 set ssh_key "$HOME/"(string sub -s 3 -- $ssh_key)
3372 end
3373
3374 set -l probe_args -o ConnectTimeout=1 -o StrictHostKeyChecking=no -o BatchMode=yes
3375 if test -n "$ssh_port"
3376 set probe_args $probe_args -p $ssh_port
3377 end
3378 if test -n "$ssh_key"
3379 set probe_args $probe_args -i $ssh_key
3380 end
3381
3382 # Quick connectivity check (1 second timeout)
3383 if ssh $probe_args $user@$ip "echo ok" 2>/dev/null | grep -q ok
3384 echo "🔍 Running nmap via $label ($ip)..."
3385
3386 set -l run_args -o StrictHostKeyChecking=no
3387 if test -n "$ssh_port"
3388 set run_args $run_args -p $ssh_port
3389 end
3390 if test -n "$ssh_key"
3391 set run_args $run_args -i $ssh_key
3392 end
3393
3394 if test -n "$search_term"
3395 # Search for specific term
3396 ssh $run_args $user@$ip "nmap -sn 192.168.1.0/24 2>/dev/null | grep -B2 -i '$search_term'"
3397 else
3398 # Full scan
3399 ssh $run_args $user@$ip "nmap -sn 192.168.1.0/24 2>/dev/null"
3400 end
3401 return $status
3402 end
3403 end
3404
3405 echo "❌ No reachable host found to run nmap"
3406 echo "Tried: $hosts_to_try"
3407 echo ""
3408 echo "Make sure one of your machines is online and has the correct IP in machines.json"
3409 echo "You can update IPs by running on the host: hostname -I | awk '{print \$1}'"
3410 return 1
3411end
3412
3413# 🖼️ FF1 Art Computer Helpers
3414
3415function __ac_ff1_find_host --description "Find a reachable host to run network commands"
3416 set -l machines_file "/workspaces/aesthetic-computer/aesthetic-computer-vault/machines.json"
3417 set -l hosts_to_try (jq -r '
3418 .machines
3419 | to_entries[]
3420 | select((.value.ip // "") != "" and (.value.ssh.enabled // true))
3421 | .key
3422 ' $machines_file)
3423
3424 for host_key in $hosts_to_try
3425 set -l host_data (jq -c --arg key "$host_key" '.machines[$key]' $machines_file 2>/dev/null)
3426 if test "$host_data" = "null" -o -z "$host_data"
3427 continue
3428 end
3429
3430 set -l ip (echo $host_data | jq -r '.ip // empty')
3431 set -l user (echo $host_data | jq -r '.user // "jas"')
3432 set -l ssh_port (echo $host_data | jq -r '.ssh.port // empty')
3433 set -l ssh_key (echo $host_data | jq -r '.ssh.keyFile // empty')
3434
3435 if test -z "$ip"
3436 continue
3437 end
3438
3439 if string match -q "~/*" $ssh_key
3440 set ssh_key "$HOME/"(string sub -s 3 -- $ssh_key)
3441 end
3442
3443 set -l probe_args -o ConnectTimeout=1 -o StrictHostKeyChecking=no -o BatchMode=yes
3444 if test -n "$ssh_port"
3445 set probe_args $probe_args -p $ssh_port
3446 end
3447 if test -n "$ssh_key"
3448 set probe_args $probe_args -i $ssh_key
3449 end
3450
3451 # Quick connectivity check (1 second timeout)
3452 if ssh $probe_args $user@$ip "echo ok" 2>/dev/null | grep -q ok
3453 echo "$user@$ip"
3454 return 0
3455 end
3456 end
3457 return 1
3458end
3459
3460function __ac_ff1_scan_network --description "Scan network for FF1 device"
3461 set -l host_target (__ac_ff1_find_host)
3462 if test -z "$host_target"
3463 echo ""
3464 return 1
3465 end
3466
3467 # Run nmap on the host and look for FF1
3468 set -l result (ssh -o StrictHostKeyChecking=no $host_target "nmap -sn 192.168.1.0/24 2>/dev/null | grep -A1 'FF1'" 2>/dev/null)
3469 if test -n "$result"
3470 # Extract IP from result like "Nmap scan report for FF1-DVVEKLZA (192.168.1.164)"
3471 echo $result | grep -oP '\d+\.\d+\.\d+\.\d+' | head -1
3472 else
3473 echo ""
3474 end
3475end
3476
3477function __ac_ff1_update_ip --description "Update FF1 IP in machines.json"
3478 set -l new_ip $argv[1]
3479 set -l machines_file "/workspaces/aesthetic-computer/aesthetic-computer-vault/machines.json"
3480
3481 # Use jq to update the IP
3482 set -l tmp_file (mktemp)
3483 cat $machines_file | jq ".machines[\"ff1-dvveklza\"].ip = \"$new_ip\"" > $tmp_file
3484 mv $tmp_file $machines_file
3485end
3486
3487function ac-ff1 --description "Control FF1 Art Computer (direct network access)"
3488 set -l machines_file "/workspaces/aesthetic-computer/aesthetic-computer-vault/machines.json"
3489 set -l ff1_data (cat $machines_file 2>/dev/null | jq -r '.machines["ff1-dvveklza"]')
3490 set -l ff1_ip (echo $ff1_data | jq -r '.ip')
3491 set -l ff1_port (echo $ff1_data | jq -r '.port // 1111')
3492
3493 set -l action $argv[1]
3494
3495 # For commands that need FF1 to be online, check connectivity first
3496 set -l needs_connection false
3497 switch $action
3498 case ping cast top colors chords playlist
3499 set needs_connection true
3500 end
3501
3502 if test "$needs_connection" = true
3503 # Quick ping check
3504 if not curl -s --connect-timeout 2 "http://$ff1_ip:$ff1_port/" >/dev/null 2>&1
3505 echo "⚠️ FF1 not responding at $ff1_ip:$ff1_port"
3506 echo "🔍 Scanning network for FF1..."
3507
3508 # Find a host to scan from
3509 set -l host_target (__ac_ff1_find_host)
3510 if test -z "$host_target"
3511 echo "❌ No reachable host to scan from. Update machine IPs in machines.json"
3512 echo " Run on your host: hostname -I | awk '{print \$1}'"
3513 return 1
3514 end
3515
3516 echo " Using $host_target for scan..."
3517 echo -n " Scanning "
3518
3519 # Run nmap with progress indication
3520 set -l scan_result (ssh -o StrictHostKeyChecking=no $host_target "nmap -sn 192.168.1.0/24 2>/dev/null" 2>/dev/null)
3521 echo "done!"
3522
3523 # Look for FF1 in results
3524 set -l new_ip (echo $scan_result | grep -oP 'FF1[^\(]*\(\K[0-9.]+')
3525
3526 if test -n "$new_ip"
3527 echo "✅ Found FF1 at $new_ip"
3528
3529 if test "$new_ip" != "$ff1_ip"
3530 echo "📝 Updating machines.json ($ff1_ip → $new_ip)"
3531 __ac_ff1_update_ip $new_ip
3532 set ff1_ip $new_ip
3533 end
3534
3535 # Verify new IP works
3536 if curl -s --connect-timeout 2 "http://$ff1_ip:$ff1_port/" >/dev/null 2>&1
3537 echo "✅ FF1 responding at new IP!"
3538 echo ""
3539 else
3540 echo "❌ FF1 found but not responding on port $ff1_port"
3541 return 1
3542 end
3543 else
3544 echo "❌ FF1 not found on network"
3545 echo ""
3546 echo "Is the FF1 powered on and connected to WiFi?"
3547 echo "Full scan results:"
3548 echo $scan_result | grep -E "^Nmap|Host is up" | head -20
3549 return 1
3550 end
3551 end
3552 end
3553
3554 switch $action
3555 case scan
3556 echo "🔍 Scanning for FF1 on network..."
3557 set -l host_target (__ac_ff1_find_host)
3558 if test -z "$host_target"
3559 echo "❌ No reachable host to scan from"
3560 return 1
3561 end
3562 echo " Using $host_target..."
3563 ssh -o StrictHostKeyChecking=no $host_target "nmap -sn 192.168.1.0/24 2>/dev/null | grep -B2 -i 'ff1'"
3564 case ping
3565 echo "🏓 Pinging FF1 at $ff1_ip:$ff1_port..."
3566 curl -s --connect-timeout 3 "http://$ff1_ip:$ff1_port/" >/dev/null 2>&1 && echo "✅ FF1 responding!" || echo "❌ FF1 not responding"
3567 case cast
3568 if test (count $argv) -lt 2
3569 echo "Usage: ac-ff1 cast <piece|url> [options]"
3570 echo ""
3571 echo "Pieces starting with \$ are KidLisp and auto-use device.kidlisp.com"
3572 echo "Regular pieces use aesthetic.computer with ?device param"
3573 echo ""
3574 echo "Examples:"
3575 echo " ac-ff1 cast \$mtz # KidLisp → device.kidlisp.com/mtz"
3576 echo " ac-ff1 cast \$mtz --perf # With FPS/performance HUD"
3577 echo " ac-ff1 cast \$mtz --nohud # No code-label, QR, or progress bar"
3578 echo " ac-ff1 cast \$mtz --socklogs # Enable remote console logging"
3579 echo " ac-ff1 cast ceo # Regular → aesthetic.computer/ceo?device"
3580 echo " ac-ff1 cast https://example.com/art.html"
3581 echo ""
3582 echo "Options:"
3583 echo " --perf Enable KidLisp performance/FPS HUD overlay"
3584 echo " --nohud Hide device.html overlays (code-label, QR, progress bar)"
3585 echo " --nolabel Hide AC piece label (for regular pieces)"
3586 echo " --socklogs Enable remote console logs (view with: ac-ff1 logs)"
3587 echo " --relay Use cloud relay (requires API key)"
3588 return 1
3589 end
3590 set -l input $argv[2]
3591 set -l use_relay false
3592 set -l use_perf false
3593 set -l use_socklogs false
3594 set -l use_nohud false
3595 set -l use_nolabel false
3596
3597 # Parse flags
3598 for arg in $argv[3..-1]
3599 switch $arg
3600 case '--relay'
3601 set use_relay true
3602 case '--perf'
3603 set use_perf true
3604 case '--socklogs'
3605 set use_socklogs true
3606 case '--nohud'
3607 set use_nohud true
3608 case '--nolabel'
3609 set use_nolabel true
3610 end
3611 end
3612
3613 # Build URL from input - auto-detect KidLisp ($) vs regular pieces
3614 set -l url
3615 # Auto-prepend https:// for domain-like inputs (e.g., top.kidlisp.com)
3616 if string match -rq '\.[a-z]{2,}(/|$)' $input; and not string match -q 'http*' $input
3617 set input "https://$input"
3618 end
3619 if string match -q 'http*' $input
3620 # Already a full URL
3621 # Don't add &device for device/top.kidlisp.com (already device-optimized)
3622 if string match -q '*device.kidlisp.com*' $input; or string match -q '*top.kidlisp.com*' $input
3623 set url $input
3624 else if string match -q '*?*' $input
3625 set url "$input&device"
3626 else
3627 set url "$input?device"
3628 end
3629 # Add perf param if requested
3630 if test "$use_perf" = true
3631 if string match -q '*?*' $url
3632 set url "$url&perf"
3633 else
3634 set url "$url?perf"
3635 end
3636 end
3637 # Add socklogs param if requested
3638 if test "$use_socklogs" = true
3639 if string match -q '*?*' $url
3640 set url "$url&socklogs"
3641 else
3642 set url "$url?socklogs"
3643 end
3644 end
3645 else if string match -rq '^\$' $input
3646 # KidLisp piece (starts with $) - use device.kidlisp.com
3647 # Keep the $ prefix - device.kidlisp.com passes it to aesthetic.computer
3648 set -l code_id $input
3649 set -l params ""
3650 if test "$use_perf" = true
3651 set params "perf=true"
3652 end
3653 if test "$use_socklogs" = true
3654 if test -n "$params"
3655 set params "$params&socklogs"
3656 else
3657 set params "socklogs"
3658 end
3659 end
3660 if test "$use_nohud" = true
3661 if test -n "$params"
3662 set params "$params&nohud"
3663 else
3664 set params "nohud"
3665 end
3666 end
3667 if test -n "$params"
3668 set url "https://device.kidlisp.com/$code_id?$params"
3669 else
3670 set url "https://device.kidlisp.com/$code_id"
3671 end
3672 if test "$use_perf" = true
3673 echo "🎨 KidLisp piece detected (perf mode)"
3674 else
3675 echo "🎨 KidLisp piece detected"
3676 end
3677 if test "$use_socklogs" = true
3678 echo "🔌 Remote logging enabled - run 'ac-ff1 logs' to view"
3679 end
3680 else
3681 # Regular aesthetic.computer piece - add &device if has query, else ?device
3682 # density=4 for a good balance of quality and performance on FF1 4K display
3683 if string match -q '*?*' $input
3684 set url "https://aesthetic.computer/$input&device&density=4"
3685 else
3686 set url "https://aesthetic.computer/$input?device&density=4"
3687 end
3688 # Add perf param if requested
3689 if test "$use_perf" = true
3690 set url "$url&perf"
3691 end
3692 # Add nolabel param if requested
3693 if test "$use_nolabel" = true
3694 set url "$url&nolabel"
3695 end
3696 end
3697
3698 if test "$use_relay" = true
3699 # Use Feral File cloud relay API (requires topicId and API key)
3700 set -l topic_id (echo $ff1_data | jq -r '.topicId')
3701 set -l api_key (echo $ff1_data | jq -r '.apiKey // ""')
3702 if test -z "$topic_id" -o "$topic_id" = "null"
3703 echo "❌ No topicId configured for FF1 in machines.json"
3704 return 1
3705 end
3706 if test -z "$api_key" -o "$api_key" = "null"
3707 echo "⚠️ Cloud relay requires API-KEY. Get it from FF1 app or use direct mode."
3708 echo " Add 'apiKey' to machines.json or use: ac-ff1 cast <url> (without --relay)"
3709 return 1
3710 end
3711 echo "☁️ Casting $url to FF1 via cloud relay..."
3712 set -l payload (printf '{"command":"displayPlaylist","request":{"playlist":{"dpVersion":"1.0.0","items":[{"source":"%s","duration":0}]},"intent":{"action":"now_display"}}}' "$url")
3713 curl -s -X POST -H 'Content-Type: application/json' -H "topicID: $topic_id" -H "API-KEY: $api_key" "https://artwork-info.feral-file.workers.dev/api/cast" -d "$payload"
3714 else
3715 # Direct network access to FF1
3716 # Check if it's a playlist URL (feed server or contains /playlists/ or /api/playlist)
3717 if string match -q '*feed.aesthetic.computer*' $url; or string match -q '*playlists*' $url; or string match -q '*/api/playlist*' $url
3718 # It's a playlist URL - send as playlistUrl so FF1 fetches and displays it
3719 echo "📺 Casting playlist to FF1: $url"
3720 curl -s --connect-timeout 5 -X POST -H 'Content-Type: application/json' "http://$ff1_ip:$ff1_port/api/cast" -d "{\"command\":\"displayPlaylist\",\"request\":{\"playlistUrl\":\"$url\",\"intent\":{\"action\":\"now_display\"}}}"
3721 else
3722 # Single URL - wrap in a minimal playlist
3723 echo "📺 Casting $url to FF1..."
3724 curl -s --connect-timeout 5 -X POST -H 'Content-Type: application/json' "http://$ff1_ip:$ff1_port/api/cast" -d "{\"command\":\"displayPlaylist\",\"request\":{\"dp1_call\":{\"dpVersion\":\"1.1.0\",\"items\":[{\"source\":\"$url\",\"duration\":0}]},\"intent\":{\"action\":\"now_display\"}}}"
3725 end
3726 end
3727 case top
3728 # Top 100 KidLisp hits - uses TV endpoint directly (no Cloudflare KV dependency)
3729 set -l limit 100
3730 set -l duration 24
3731
3732 # Parse options
3733 for arg in $argv[2..-1]
3734 if string match -q -- '--limit=*' "$arg"
3735 set limit (string replace -- '--limit=' '' "$arg")
3736 else if string match -q -- '--duration=*' "$arg"
3737 set duration (string replace -- '--duration=' '' "$arg")
3738 end
3739 end
3740
3741 set -l playlist_url "https://aesthetic.computer/api/tv?types=kidlisp&sort=hits&limit=$limit&format=dp1&duration=$duration"
3742 echo "🎵 Fetching Top $limit KidLisp Hits from TV endpoint..."
3743 echo " URL: $playlist_url"
3744 echo "📺 Casting playlist to FF1..."
3745 curl -s --connect-timeout 5 -X POST -H 'Content-Type: application/json' "http://$ff1_ip:$ff1_port/api/cast" -d "{\"command\":\"displayPlaylist\",\"request\":{\"playlistUrl\":\"$playlist_url\",\"intent\":{\"action\":\"now_display\"}}}"
3746 case colors chords
3747 # Legacy feed server playlists (may hit KV limits)
3748 set -l playlist_name $action
3749 echo "🎵 Looking up $playlist_name playlist from feed server..."
3750 echo "⚠️ Note: Feed server may hit Cloudflare KV limits. Consider using 'ac-ff1 top' instead."
3751
3752 # Map playlist names to their IDs
3753 set -l playlist_id
3754 switch $playlist_name
3755 case colors
3756 set playlist_id "4b872517-e4d8-4433-af8b-a9a4a8204cc9"
3757 case chords
3758 set playlist_id "49f0ee0e-0303-4192-9cb2-aa3c5abb64b5"
3759 end
3760
3761 set -l playlist_url "https://feed.aesthetic.computer/api/v1/playlists/$playlist_id"
3762 echo "📺 Casting playlist to FF1: $playlist_url"
3763 curl -s --connect-timeout 5 -X POST -H 'Content-Type: application/json' "http://$ff1_ip:$ff1_port/api/cast" -d "{\"command\":\"displayPlaylist\",\"request\":{\"playlistUrl\":\"$playlist_url\",\"intent\":{\"action\":\"now_display\"}}}"
3764 case tunnel
3765 echo "🚇 Starting SSH tunnel to FF1 (localhost:1111 -> $ff1_ip:$ff1_port)..."
3766 echo "Press Ctrl+C to stop the tunnel"
3767 ssh -L 1111:$ff1_ip:$ff1_port jas@host.docker.internal -N
3768 case playlist
3769 # Generate and push a DP-1 playlist of top KidLisp hits
3770 set -l limit 10
3771 set -l duration 60
3772 set -l handle ""
3773
3774 # Parse options using string match with -- separator
3775 for arg in $argv[2..-1]
3776 if string match -q -- '--limit=*' "$arg"
3777 set limit (string replace -- '--limit=' '' "$arg")
3778 else if string match -q -- '--duration=*' "$arg"
3779 set duration (string replace -- '--duration=' '' "$arg")
3780 else if string match -q -- '--handle=*' "$arg"
3781 set handle (string replace -- '--handle=' '' "$arg")
3782 else if string match -q -- 'handle=*' "$arg"
3783 # Support handle=jeffrey without --
3784 set handle (string replace -- 'handle=' '' "$arg")
3785 end
3786 end
3787
3788 # Build playlist API URL
3789 set -l playlist_url "https://aesthetic.computer/api/playlist?limit=$limit&duration=$duration&density=8"
3790 if test -n "$handle"
3791 set playlist_url "$playlist_url&handle=$handle"
3792 echo "🎵 Fetching top $limit KidLisp hits by @$handle..."
3793 else
3794 echo "🎵 Fetching top $limit KidLisp hits..."
3795 end
3796
3797 # Fetch playlist from API
3798 set -l playlist_json (curl -s "$playlist_url")
3799
3800 if test -z "$playlist_json"
3801 echo "❌ Failed to fetch playlist"
3802 return 1
3803 end
3804
3805 # Extract codes for display
3806 set -l codes (echo $playlist_json | jq -r '.items[].title | ltrimstr("$")')
3807 set codes (string split \n -- $codes)
3808
3809 echo "📺 Pushing playlist to FF1 ("(count $codes)" items, "$duration"s each)..."
3810 echo " Codes:" $codes
3811
3812 # Extract items array from playlist response
3813 set -l items_json (echo $playlist_json | jq -c '.items | map({source, duration})')
3814
3815 # Build full DP-1 payload
3816 set -l playlist_payload (printf '{"command":"displayPlaylist","request":{"dp1_call":{"dpVersion":"1.0.0","items":%s},"intent":{"action":"now_display"}}}' "$items_json")
3817
3818 # Send to FF1 directly
3819 curl -s --connect-timeout 5 -X POST -H 'Content-Type: application/json' "http://$ff1_ip:$ff1_port/api/cast" -d "$playlist_payload"
3820 case logs
3821 # Stream remote console logs from devices with ?socklogs enabled
3822 echo "👁️ Connecting to session-server for remote logs..."
3823 echo " Cast with: ac-ff1 cast \$code --socklogs"
3824 echo " Press Ctrl+C to stop"
3825 echo ""
3826
3827 # Determine session server URL - use local if available, else production
3828 # Production uses direct IP since Cloudflare SSL isn't configured for WebSocket origin
3829 set -l session_url "ws://157.245.134.225:8889/socklogs?role=viewer"
3830 set -l websocat_opts ""
3831 # Check if local session server is running
3832 if nc -z localhost 8889 2>/dev/null
3833 set session_url "wss://localhost:8889/socklogs?role=viewer"
3834 set websocat_opts "-k" # Skip cert verification for local dev
3835 echo "📡 Using local session server (localhost:8889)"
3836 else
3837 echo "📡 Using production session server (direct IP)"
3838 end
3839
3840 # Use websocat if available, otherwise fall back to node script
3841 if command -v websocat >/dev/null 2>&1
3842 websocat $websocat_opts "$session_url" 2>/dev/null | while read -l line
3843 set -l json $line
3844 set -l type (echo $json | jq -r '.type // ""' 2>/dev/null)
3845 if test "$type" = "log"
3846 set -l level (echo $json | jq -r '.level // "info"' 2>/dev/null)
3847 set -l msg (echo $json | jq -r '.message // ""' 2>/dev/null)
3848 set -l device (echo $json | jq -r '.deviceId // "unknown"' 2>/dev/null)
3849 set -l ts (echo $json | jq -r '.timestamp // 0' 2>/dev/null)
3850 set -l time_str (date -d "@"(math $ts / 1000) "+%H:%M:%S" 2>/dev/null; or echo "??:??:??")
3851
3852 # Color based on level
3853 switch $level
3854 case error
3855 set_color red
3856 case warn
3857 set_color yellow
3858 case debug
3859 set_color brblack
3860 case '*'
3861 set_color normal
3862 end
3863 printf "[%s] %s [%s] %s\n" $time_str $device (string upper $level) $msg
3864 set_color normal
3865 else if test "$type" = "status"
3866 set -l device_count (echo $json | jq -r '.devices | length' 2>/dev/null)
3867 set_color cyan
3868 echo "📱 $device_count device(s) connected"
3869 set_color normal
3870 else if test "$type" = "device-connected"
3871 set -l device (echo $json | jq -r '.deviceId // "unknown"' 2>/dev/null)
3872 set_color green
3873 echo "📱 Device connected: $device"
3874 set_color normal
3875 else if test "$type" = "device-disconnected"
3876 set -l device (echo $json | jq -r '.deviceId // "unknown"' 2>/dev/null)
3877 set_color red
3878 echo "📱 Device disconnected: $device"
3879 set_color normal
3880 end
3881 end
3882 else
3883 echo "⚠️ websocat not installed. Install with: cargo install websocat"
3884 echo " Or run: npm install -g wscat && wscat -c '$session_url'"
3885 end
3886 case info '*'
3887 set -l topic_id (echo $ff1_data | jq -r '.topicId // "not set"')
3888 echo "🖼️ FF1 Art Computer"
3889 echo " IP: $ff1_ip"
3890 echo " Port: $ff1_port"
3891 echo " Device ID: "(echo $ff1_data | jq -r '.deviceId')
3892 echo " Topic ID: $topic_id"
3893 echo ""
3894 echo "Commands:"
3895 echo " ac-ff1 - Show this help"
3896 echo " ac-ff1 scan - Find FF1 via mDNS"
3897 echo " ac-ff1 ping - Check if FF1 is responding"
3898 echo " ac-ff1 cast \$mtz - Cast KidLisp piece (auto device.kidlisp.com)"
3899 echo " ac-ff1 cast \$mtz --perf - Cast with FPS/performance HUD"
3900 echo " ac-ff1 cast \$mtz --nohud - Hide code-label, QR, and progress bar"
3901 echo " ac-ff1 cast \$mtz --socklogs - Enable remote console logging"
3902 echo " ac-ff1 cast ceo - Cast regular piece (auto ?device param)"
3903 echo " ac-ff1 cast <url> - Cast any URL"
3904 echo " ac-ff1 top - Cast Top KidLisp Hits playlist"
3905 echo " ac-ff1 colors - Cast KidLisp Colors playlist"
3906 echo " ac-ff1 chords - Cast KidLisp Chords playlist"
3907 echo " ac-ff1 playlist [options] - Push dynamic KidLisp playlist"
3908 echo " ac-ff1 tunnel - Create SSH tunnel for local dev"
3909 echo " ac-ff1 logs - Stream remote console logs (requires ?socklogs)"
3910 echo ""
3911 echo "Playlist options:"
3912 echo " --limit=N Number of items (default: 10)"
3913 echo " --duration=S Seconds per item (default: 60)"
3914 echo " --handle=H Filter by user handle"
3915 end
3916end
3917
3918# 📊 Bundle Telemetry - query performance data from inline bundles
3919function ac-telemetry --description "Query bundle telemetry from MongoDB"
3920 set -l action $argv[1]
3921 set -l limit 20
3922 set -l piece ""
3923
3924 # Parse options
3925 for arg in $argv[2..-1]
3926 if string match -q -- '--limit=*' "$arg"
3927 set limit (string replace -- '--limit=' '' "$arg")
3928 else if string match -q -- '--piece=*' "$arg"
3929 set piece (string replace -- '--piece=' '' "$arg")
3930 end
3931 end
3932
3933 set -l base_url "https://aesthetic.computer/api/bundle-telemetry-query"
3934 set -l piece_param ""
3935 if test -n "$piece"
3936 set piece_param "&piece=$piece"
3937 end
3938
3939 switch $action
3940 case recent boot
3941 echo "📊 Recent bundle boots (last $limit)..."
3942 curl -s "$base_url?type=boot&limit=$limit$piece_param" | jq -r '
3943 .results[] |
3944 "\(.timestamp | split(".")[0] | gsub("T"; " ")) | \(.piece // "?") | boot:\(.bootTime // "?")ms | density:\(.density // "?") | \(.screen // "?")"
3945 ' 2>/dev/null || echo "❌ Query failed"
3946 case fps perf
3947 echo "📈 FPS data from recent sessions..."
3948 curl -s "$base_url?type=perf&limit=$limit$piece_param" | jq -r '
3949 .results[] |
3950 "\(.timestamp | split(".")[0] | gsub("T"; " ")) | \(.piece // "?") | samples:\(.samples | length // 0) | density:\(.density // "?")"
3951 ' 2>/dev/null || echo "❌ Query failed"
3952 case errors error
3953 echo "❌ Recent bundle errors..."
3954 curl -s "$base_url?type=error&limit=$limit$piece_param" | jq -r '
3955 .results[] |
3956 "\(.timestamp | split(".")[0] | gsub("T"; " ")) | \(.piece // "?") | \(.message // "unknown")"
3957 ' 2>/dev/null || echo "❌ Query failed"
3958 case summary
3959 echo "📊 Bundle telemetry summary (last 24h)..."
3960 curl -s "$base_url?type=summary" | jq '.' 2>/dev/null || echo "❌ Query failed"
3961 case raw
3962 echo "🔍 Raw telemetry documents..."
3963 curl -s "$base_url?type=boot&limit=$limit$piece_param" | jq '.' 2>/dev/null || echo "❌ Query failed"
3964 case '*'
3965 echo "📊 Bundle Telemetry Query Tool"
3966 echo ""
3967 echo "Usage: ac-telemetry <command> [options]"
3968 echo ""
3969 echo "Commands:"
3970 echo " ac-telemetry recent - Show recent bundle boots"
3971 echo " ac-telemetry fps - Show FPS data from sessions"
3972 echo " ac-telemetry errors - Show recent errors"
3973 echo " ac-telemetry summary - Show 24h summary stats"
3974 echo " ac-telemetry raw - Show raw JSON"
3975 echo ""
3976 echo "Options:"
3977 echo " --limit=N Number of results (default: 20)"
3978 echo " --piece=NAME Filter by piece name"
3979 end
3980end
3981
3982# 🎮 Xbox helper - connect to Xbox on local network
3983function ac-xbox --description "Interact with Xbox on 1244 Innes Ave LAN"
3984 set -l vault_path "$HOME/aesthetic-computer/aesthetic-computer-vault/machines.json"
3985 if not test -f $vault_path
3986 echo "❌ machines.json not found"
3987 return 1
3988 end
3989
3990 set -l xbox_ip (jq -r '.machines.xbox.ip' $vault_path)
3991 set -l portal_port (jq -r '.machines.xbox.devicePortalPort' $vault_path)
3992
3993 if test -z "$xbox_ip" -o "$xbox_ip" = "null"
3994 echo "❌ Xbox not configured in machines.json"
3995 return 1
3996 end
3997
3998 switch $argv[1]
3999 case ping
4000 echo "🏓 Pinging Xbox at $xbox_ip..."
4001 ssh jas@host.docker.internal "ping -c 3 $xbox_ip"
4002 case portal
4003 echo "🌐 Xbox Device Portal: https://$xbox_ip:$portal_port"
4004 echo " Opening in browser..."
4005 ssh jas@host.docker.internal "open 'https://$xbox_ip:$portal_port'"
4006 case info
4007 echo "🎮 Xbox Series X"
4008 echo " IP: $xbox_ip"
4009 echo " Device Portal: https://$xbox_ip:$portal_port"
4010 echo " Network: 1244 Innes Ave Home LAN"
4011 echo ""
4012 echo "Deploy from Mac:"
4013 echo " 1. Push to trigger GitHub Actions build"
4014 echo " 2. Download .msix from Actions artifacts"
4015 echo " 3. ac-xbox deploy <path-to.msix>"
4016 echo ""
4017 echo "Or open Device Portal: ac-xbox portal"
4018 case tunnel
4019 echo "🚇 Creating SSH tunnel to Xbox Device Portal..."
4020 echo " Local: https://localhost:11443 → Xbox: $xbox_ip:$portal_port"
4021 echo " Press Ctrl+C to stop"
4022 ssh -L 11443:$xbox_ip:$portal_port jas@host.docker.internal -N
4023 case deploy
4024 if test (count $argv) -lt 2
4025 echo "Usage: ac-xbox deploy <package.msix>"
4026 echo ""
4027 echo "Get the package from GitHub Actions:"
4028 echo " 1. Push changes to xbox/ folder"
4029 echo " 2. Go to Actions → Build Xbox App → Download artifact"
4030 echo " 3. ac-xbox deploy ~/Downloads/AestheticComputer.msix"
4031 return 1
4032 end
4033 set -l pkg $argv[2]
4034 if not test -f $pkg
4035 echo "❌ Package not found: $pkg"
4036 return 1
4037 end
4038 echo "📦 Deploying $pkg to Xbox..."
4039 # Upload via Mac host curl to Device Portal
4040 ssh jas@host.docker.internal "curl -k -X POST -F 'file=@$pkg' 'https://$xbox_ip:$portal_port/api/app/packagemanager/package'"
4041 case build
4042 echo "🔨 Triggering GitHub Actions build..."
4043 echo " This will build the Xbox app in the cloud."
4044 gh workflow run xbox-build.yml
4045 echo ""
4046 echo " Watch progress: gh run watch"
4047 echo " Download when done: gh run download"
4048 case '*'
4049 echo "🎮 Xbox Helper (1244 Innes Ave LAN)"
4050 echo ""
4051 echo "Usage: ac-xbox <command>"
4052 echo ""
4053 echo "Commands:"
4054 echo " ac-xbox ping - Check if Xbox is reachable"
4055 echo " ac-xbox portal - Open Device Portal in browser"
4056 echo " ac-xbox info - Show connection info"
4057 echo " ac-xbox tunnel - Create SSH tunnel to Device Portal"
4058 echo " ac-xbox build - Trigger GitHub Actions build"
4059 echo " ac-xbox deploy <file.msix> - Deploy package to Xbox"
4060 echo ""
4061 echo "Xbox IP: $xbox_ip"
4062 end
4063end
4064
4065if set -q AC_TASK_LAUNCH
4066 set -e AC_TASK_LAUNCH # Clear it so nested shells don't re-trigger
4067 aesthetic
4068end