Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 4068 lines 156 kB view raw
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