lacy 1.8.11 → 1.8.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/.claude/settings.local.json +26 -0
  2. package/.github/FUNDING.yml +3 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +49 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +28 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  7. package/.github/SECURITY.md +32 -0
  8. package/.github/assets/logo-horizontal-dark.png +0 -0
  9. package/.github/assets/logo-horizontal-dark.svg +17 -0
  10. package/.github/assets/logo-horizontal.png +0 -0
  11. package/.github/assets/logo-horizontal.svg +17 -0
  12. package/.github/assets/logo.png +0 -0
  13. package/.github/assets/logo.svg +12 -0
  14. package/.github/assets/social-preview.png +0 -0
  15. package/.github/assets/social-preview.svg +50 -0
  16. package/.github/dependabot.yml +21 -0
  17. package/.github/workflows/ci.yml +80 -0
  18. package/.github/workflows/dependabot-auto-merge.yml +32 -0
  19. package/CHANGELOG.md +366 -0
  20. package/CLAUDE.md +340 -0
  21. package/CONTRIBUTING.md +141 -0
  22. package/LICENSE +110 -0
  23. package/README.md +201 -31
  24. package/RELEASING.md +148 -0
  25. package/STYLE.md +202 -0
  26. package/assets/hero.jpeg +0 -0
  27. package/assets/mode-indicators.jpeg +0 -0
  28. package/assets/real-time-indicator.jpeg +0 -0
  29. package/assets/supported-tools.jpeg +0 -0
  30. package/bin/lacy +1028 -0
  31. package/docs/ADDING-BACKENDS.md +124 -0
  32. package/docs/DEVTO-ARTICLE.md +94 -0
  33. package/docs/DOCS.md +68 -0
  34. package/docs/GROWTH-STRATEGY.md +119 -0
  35. package/docs/HN-RESPONSES.md +122 -0
  36. package/docs/LAUNCH-COPY-FINAL.md +105 -0
  37. package/docs/MARKETING.md +411 -0
  38. package/docs/NATURAL_LANGUAGE_DETECTION.md +204 -0
  39. package/docs/UGC_VIDEO_SCRIPT.md +114 -0
  40. package/docs/articles/devto-how-i-made-my-terminal-understand-english.md +117 -0
  41. package/docs/demo-color-transition.gif +0 -0
  42. package/docs/demo-full.gif +0 -0
  43. package/docs/demo-indicator.gif +0 -0
  44. package/docs/launch-thread-may6.sh +158 -0
  45. package/docs/videos/README.md +189 -0
  46. package/docs/videos/generate_frames.py +510 -0
  47. package/docs/videos/generate_frames_v2.py +729 -0
  48. package/docs/videos/generate_short.py +328 -0
  49. package/docs/videos/generate_short_v2.py +526 -0
  50. package/docs/videos/lacy-shell-demo-v2.mp4 +0 -0
  51. package/docs/videos/lacy-shell-demo.mp4 +0 -0
  52. package/docs/videos/lacy-shell-short-v2.mp4 +0 -0
  53. package/docs/videos/lacy-shell-short.mp4 +0 -0
  54. package/install.sh +1009 -0
  55. package/lacy.plugin.bash +75 -0
  56. package/lacy.plugin.fish +43 -0
  57. package/lacy.plugin.zsh +65 -0
  58. package/lib/animations.zsh +3 -0
  59. package/lib/bash/completions.bash +40 -0
  60. package/lib/bash/execute.bash +233 -0
  61. package/lib/bash/init.bash +40 -0
  62. package/lib/bash/keybindings.bash +134 -0
  63. package/lib/bash/prompt.bash +85 -0
  64. package/lib/commands/info.sh +25 -0
  65. package/lib/config.zsh +3 -0
  66. package/lib/constants.zsh +3 -0
  67. package/lib/core/animations.sh +271 -0
  68. package/lib/core/commands.sh +297 -0
  69. package/lib/core/config.sh +340 -0
  70. package/lib/core/constants.sh +366 -0
  71. package/lib/core/context.sh +260 -0
  72. package/lib/core/detection.sh +417 -0
  73. package/lib/core/mcp.sh +741 -0
  74. package/lib/core/modes.sh +123 -0
  75. package/lib/core/preheat.sh +496 -0
  76. package/lib/core/spinner.sh +174 -0
  77. package/lib/core/telemetry.sh +99 -0
  78. package/lib/detection.zsh +3 -0
  79. package/lib/execute.zsh +3 -0
  80. package/lib/fish/config.fish +66 -0
  81. package/lib/fish/detection.fish +90 -0
  82. package/lib/fish/execute.fish +105 -0
  83. package/lib/fish/keybindings.fish +42 -0
  84. package/lib/fish/prompt.fish +30 -0
  85. package/lib/keybindings.zsh +3 -0
  86. package/lib/mcp.zsh +3 -0
  87. package/lib/modes.zsh +3 -0
  88. package/lib/preheat.zsh +3 -0
  89. package/lib/prompt.zsh +3 -0
  90. package/lib/spinner.zsh +3 -0
  91. package/lib/zsh/completions.zsh +60 -0
  92. package/lib/zsh/execute.zsh +294 -0
  93. package/lib/zsh/init.zsh +26 -0
  94. package/lib/zsh/keybindings.zsh +551 -0
  95. package/lib/zsh/prompt.zsh +90 -0
  96. package/package.json +42 -27
  97. package/packages/lacy/README.md +61 -0
  98. package/packages/lacy/commands/info.sh +25 -0
  99. package/{index.mjs → packages/lacy/index.mjs} +247 -20
  100. package/packages/lacy/package-lock.json +71 -0
  101. package/packages/lacy/package.json +42 -0
  102. package/script/release.ts +487 -0
  103. package/squirrel.toml +36 -0
  104. package/tests/test_bash.bash +163 -0
  105. package/tests/test_core.sh +607 -0
  106. package/tests/test_gemini.sh +119 -0
  107. package/tests/test_gemini_mcp.sh +126 -0
  108. package/tests/test_preheat_server.zsh +446 -0
  109. package/uninstall.sh +52 -0
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Prompt handling for Lacy Shell — Bash adapter
4
+ # - Mode badge in left prompt (no right prompt in Bash)
5
+ # - No real-time indicator (Bash lacks per-keystroke hooks)
6
+
7
+ # Store original prompt (captured later, after user's profile loads)
8
+ LACY_SHELL_ORIGINAL_PS1=""
9
+ LACY_SHELL_BASE_PS1=""
10
+ LACY_SHELL_PROMPT_INITIALIZED=false
11
+
12
+ # Setup prompt integration (called during init, defers to first PROMPT_COMMAND)
13
+ lacy_shell_setup_prompt() {
14
+ LACY_SHELL_PROMPT_INITIALIZED=false
15
+ }
16
+
17
+ # Actually initialize the prompt (called on first PROMPT_COMMAND)
18
+ lacy_shell_init_prompt_once() {
19
+ [[ "$LACY_SHELL_PROMPT_INITIALIZED" == true ]] && return
20
+
21
+ # Capture the user's fully-loaded prompt
22
+ LACY_SHELL_ORIGINAL_PS1="$PS1"
23
+ LACY_SHELL_BASE_PS1="$PS1"
24
+
25
+ # Mark initialized BEFORE calling update_prompt to prevent infinite recursion
26
+ # (update_prompt calls init_prompt_once, which would call update_prompt again)
27
+ LACY_SHELL_PROMPT_INITIALIZED=true
28
+
29
+ # Set initial prompt with mode badge
30
+ lacy_shell_update_prompt
31
+ }
32
+
33
+ # Get ANSI color escape for a 256-color code
34
+ _lacy_bash_color() {
35
+ printf '\[\e[38;5;%dm\]' "$1"
36
+ }
37
+
38
+ _lacy_bash_reset() {
39
+ printf '\[\e[0m\]'
40
+ }
41
+
42
+ # Update prompt with mode badge
43
+ lacy_shell_update_prompt() {
44
+ # Initialize on first call
45
+ lacy_shell_init_prompt_once
46
+
47
+ local mode_text mode_color
48
+ case "$LACY_SHELL_CURRENT_MODE" in
49
+ "shell")
50
+ mode_text="SHELL"
51
+ mode_color="$LACY_COLOR_SHELL"
52
+ ;;
53
+ "agent")
54
+ mode_text="AGENT"
55
+ mode_color="$LACY_COLOR_AGENT"
56
+ ;;
57
+ "auto")
58
+ mode_text="AUTO"
59
+ mode_color="$LACY_COLOR_AUTO"
60
+ ;;
61
+ *)
62
+ mode_text="?"
63
+ mode_color="$LACY_COLOR_NEUTRAL"
64
+ ;;
65
+ esac
66
+
67
+ # Build prompt: [MODE] indicator original_ps1
68
+ local badge
69
+ badge="$(_lacy_bash_color "$mode_color")${mode_text}$(_lacy_bash_reset)"
70
+ local indicator
71
+ indicator="$(_lacy_bash_color "$LACY_COLOR_NEUTRAL")${LACY_INDICATOR_CHAR}$(_lacy_bash_reset)"
72
+
73
+ PS1="${badge} ${indicator} ${LACY_SHELL_BASE_PS1}"
74
+ }
75
+
76
+ # Restore original prompt
77
+ lacy_shell_restore_prompt() {
78
+ if [[ -n "$LACY_SHELL_ORIGINAL_PS1" ]]; then
79
+ PS1="$LACY_SHELL_ORIGINAL_PS1"
80
+ fi
81
+ }
82
+
83
+ # Stubs for removed features (from ZSH adapter)
84
+ lacy_shell_remove_top_bar() { :; }
85
+ lacy_shell_show_top_bar_message() { :; }
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+
3
+ # Lacy Shell - Info command
4
+ # Shows basic information and guides users to setup
5
+
6
+ _lacy_info_version() {
7
+ local pkg="${HOME}/.lacy/package.json"
8
+ if [[ -f "$pkg" ]]; then
9
+ grep '"version"' "$pkg" 2>/dev/null | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//' | sed 's/".*//'
10
+ else
11
+ echo "unknown"
12
+ fi
13
+ }
14
+
15
+ printf '\033[38;5;75m%s\033[0m\n' "🔧 Lacy Shell v$(_lacy_info_version)"
16
+ echo
17
+ printf '%s\n' "Lacy Shell detects natural language and routes it to AI coding agents."
18
+ echo
19
+ printf '%s\n' "Quick tips:"
20
+ printf ' • %s\n' "Type normally for shell commands"
21
+ printf ' • %s\n' "Type natural language for AI assistance"
22
+ printf ' • %s\n' "Press Ctrl+Space to toggle modes"
23
+ echo
24
+ printf '%b\n' "Run '\033[38;5;200mlacy setup\033[0m' to configure your AI tool and settings."
25
+ printf '%b\n' "Run '\033[38;5;200mlacy mode\033[0m' to see current mode and legend."
package/lib/config.zsh ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/core/config.sh"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/core/constants.sh"
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Braille Loading Animations for Lacy Shell
4
+ # 18 Unicode Braille animations for the agent spinner
5
+ # Sourced from https://github.com/gunnargray-dev/unicode-animations
6
+ # ALL frames are exactly 4 braille characters wide (pad with ⠀ U+2800)
7
+ # to prevent horizontal shifting when switching animations
8
+ #
9
+ # Braille dot layout (2×4 grid per char):
10
+ # dot1 (0x01) dot4 (0x08)
11
+ # dot2 (0x02) dot5 (0x10)
12
+ # dot3 (0x04) dot6 (0x20)
13
+ # dot7 (0x40) dot8 (0x80)
14
+
15
+ # Available animation names
16
+ LACY_SPINNER_ANIMATIONS=(
17
+ "braille" "dots" "ascii" "braillewave" "dna" "scan" "rain" "scanline"
18
+ "pulse" "snake" "sparkle" "cascade" "columns" "orbit"
19
+ "breathe" "waverows" "checkerboard" "helix" "fillsweep" "diagswipe"
20
+ )
21
+
22
+ # Set the active spinner animation frames
23
+ # Usage: lacy_set_spinner_animation "name"
24
+ # Sets LACY_SPINNER_ANIM array
25
+ lacy_set_spinner_animation() {
26
+ local name="${1:-braille}"
27
+
28
+ # "random" picks a random animation each time
29
+ if [[ "$name" == "random" ]]; then
30
+ local count=${#LACY_SPINNER_ANIMATIONS[@]}
31
+ local arr_off=${_LACY_ARR_OFFSET:-0}
32
+ if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
33
+ name="${LACY_SPINNER_ANIMATIONS[$(( (RANDOM % count) + 1 ))]}"
34
+ else
35
+ name="${LACY_SPINNER_ANIMATIONS[$(( RANDOM % count ))]}"
36
+ fi
37
+ fi
38
+
39
+ case "$name" in
40
+ braille)
41
+ # Classic rotating dots
42
+ LACY_SPINNER_ANIM=("⠋⠀⠀⠀" "⠙⠀⠀⠀" "⠹⠀⠀⠀" "⠸⠀⠀⠀" "⠼⠀⠀⠀" "⠴⠀⠀⠀"
43
+ "⠦⠀⠀⠀" "⠧⠀⠀⠀" "⠇⠀⠀⠀" "⠏⠀⠀⠀")
44
+ ;;
45
+ dots)
46
+ # Single dot traveling all 8 braille positions
47
+ LACY_SPINNER_ANIM=("⠁⠀⠀⠀" "⠂⠀⠀⠀" "⠄⠀⠀⠀" "⡀⠀⠀⠀" "⢀⠀⠀⠀" "⠠⠀⠀⠀"
48
+ "⠐⠀⠀⠀" "⠈⠀⠀⠀")
49
+ ;;
50
+ ascii)
51
+ # ASCII-only fallback for terminals without Unicode
52
+ LACY_SPINNER_ANIM=("| " "/ " "- " "\\ ")
53
+ ;;
54
+ braillewave)
55
+ # Traveling dot wave
56
+ LACY_SPINNER_ANIM=("⠁⠂⠄⡀" "⠂⠄⡀⢀" "⠄⡀⢀⠠" "⡀⢀⠠⠐" "⢀⠠⠐⠈" "⠠⠐⠈⠁"
57
+ "⠐⠈⠁⠂" "⠈⠁⠂⠄")
58
+ ;;
59
+ dna)
60
+ # DNA helix strand
61
+ LACY_SPINNER_ANIM=("⠋⠉⠙⠚" "⠉⠙⠚⠒" "⠙⠚⠒⠂" "⠚⠒⠂⠂" "⠒⠂⠂⠒" "⠂⠂⠒⠲"
62
+ "⠂⠒⠲⠴" "⠒⠲⠴⠤" "⠲⠴⠤⠄" "⠴⠤⠄⠋" "⠤⠄⠋⠉" "⠄⠋⠉⠙")
63
+ ;;
64
+ scan)
65
+ # Lit column scanning across (8×4 grid)
66
+ LACY_SPINNER_ANIM=("⠀⠀⠀⠀" "⡇⠀⠀⠀" "⣿⠀⠀⠀" "⢸⡇⠀⠀" "⠀⣿⠀⠀" "⠀⢸⡇⠀"
67
+ "⠀⠀⣿⠀" "⠀⠀⢸⡇" "⠀⠀⠀⣿" "⠀⠀⠀⢸")
68
+ ;;
69
+ rain)
70
+ # Dots falling at staggered heights (8×4 grid)
71
+ LACY_SPINNER_ANIM=("⢁⠂⠔⠈" "⠂⠌⡠⠐" "⠄⡐⢀⠡" "⡈⠠⠀⢂" "⠐⢀⠁⠄" "⠠⠁⠊⡀"
72
+ "⢁⠂⠔⠈" "⠂⠌⡠⠐" "⠄⡐⢀⠡" "⡈⠠⠀⢂" "⠐⢀⠁⠄" "⠠⠁⠊⡀")
73
+ ;;
74
+ scanline)
75
+ # Horizontal line bouncing up and down (6×4 grid)
76
+ LACY_SPINNER_ANIM=("⠉⠉⠉⠀" "⠓⠓⠓⠀" "⠦⠦⠦⠀" "⣄⣄⣄⠀" "⠦⠦⠦⠀" "⠓⠓⠓⠀")
77
+ ;;
78
+ pulse)
79
+ # Expanding/contracting ring (6×4 grid)
80
+ LACY_SPINNER_ANIM=("⠀⠶⠀⠀" "⠰⣿⠆⠀" "⢾⣉⡷⠀" "⣏⠀⣹⠀" "⡁⠀⢈⠀")
81
+ ;;
82
+ snake)
83
+ # Snake traversing a 4×4 grid
84
+ LACY_SPINNER_ANIM=("⣁⡀⠀⠀" "⣉⠀⠀⠀" "⡉⠁⠀⠀" "⠉⠉⠀⠀" "⠈⠙⠀⠀" "⠀⠛⠀⠀"
85
+ "⠐⠚⠀⠀" "⠒⠒⠀⠀" "⠖⠂⠀⠀" "⠶⠀⠀⠀" "⠦⠄⠀⠀" "⠤⠤⠀⠀"
86
+ "⠠⢤⠀⠀" "⠀⣤⠀⠀" "⢀⣠⠀⠀" "⣀⣀⠀⠀")
87
+ ;;
88
+ sparkle)
89
+ # Pseudo-random twinkling dots (8×4 grid)
90
+ LACY_SPINNER_ANIM=("⡡⠊⢔⠡" "⠊⡰⡡⡘" "⢔⢅⠈⢢" "⡁⢂⠆⡍" "⢔⠨⢑⢐" "⠨⡑⡠⠊")
91
+ ;;
92
+ cascade)
93
+ # Diagonal wave sweeping across (8×4 grid)
94
+ LACY_SPINNER_ANIM=("⠀⠀⠀⠀" "⠀⠀⠀⠀" "⠁⠀⠀⠀" "⠋⠀⠀⠀" "⠞⠁⠀⠀" "⡴⠋⠀⠀"
95
+ "⣠⠞⠁⠀" "⢀⡴⠋⠀" "⠀⣠⠞⠁" "⠀⢀⡴⠋" "⠀⠀⣠⠞" "⠀⠀⢀⡴"
96
+ "⠀⠀⠀⣠" "⠀⠀⠀⢀")
97
+ ;;
98
+ columns)
99
+ # Columns filling left to right (6×4 grid)
100
+ LACY_SPINNER_ANIM=("⡀⠀⠀⠀" "⡄⠀⠀⠀" "⡆⠀⠀⠀" "⡇⠀⠀⠀" "⣇⠀⠀⠀" "⣧⠀⠀⠀"
101
+ "⣷⠀⠀⠀" "⣿⠀⠀⠀" "⣿⡀⠀⠀" "⣿⡄⠀⠀" "⣿⡆⠀⠀" "⣿⡇⠀⠀"
102
+ "⣿⣇⠀⠀" "⣿⣧⠀⠀" "⣿⣷⠀⠀" "⣿⣿⠀⠀" "⣿⣿⡀⠀" "⣿⣿⡄⠀"
103
+ "⣿⣿⡆⠀" "⣿⣿⡇⠀" "⣿⣿⣇⠀" "⣿⣿⣧⠀" "⣿⣿⣷⠀" "⣿⣿⣿⠀"
104
+ "⣿⣿⣿⠀" "⠀⠀⠀⠀")
105
+ ;;
106
+ orbit)
107
+ # Two dots circling a 2×4 cell
108
+ LACY_SPINNER_ANIM=("⠃⠀⠀⠀" "⠉⠀⠀⠀" "⠘⠀⠀⠀" "⠰⠀⠀⠀" "⢠⠀⠀⠀" "⣀⠀⠀⠀"
109
+ "⡄⠀⠀⠀" "⠆⠀⠀⠀")
110
+ ;;
111
+ breathe)
112
+ # Dots filling and draining diagonally
113
+ LACY_SPINNER_ANIM=("⠀⠀⠀⠀" "⠂⠀⠀⠀" "⠌⠀⠀⠀" "⡑⠀⠀⠀" "⢕⠀⠀⠀" "⢝⠀⠀⠀"
114
+ "⣫⠀⠀⠀" "⣟⠀⠀⠀" "⣿⠀⠀⠀" "⣟⠀⠀⠀" "⣫⠀⠀⠀" "⢝⠀⠀⠀"
115
+ "⢕⠀⠀⠀" "⡑⠀⠀⠀" "⠌⠀⠀⠀" "⠂⠀⠀⠀" "⠀⠀⠀⠀")
116
+ ;;
117
+ waverows)
118
+ # Sine wave traveling across (8×4 grid)
119
+ LACY_SPINNER_ANIM=("⠖⠉⠉⠑" "⡠⠖⠉⠉" "⣠⡠⠖⠉" "⣄⣠⡠⠖" "⠢⣄⣠⡠" "⠙⠢⣄⣠"
120
+ "⠉⠙⠢⣄" "⠊⠉⠙⠢" "⠜⠊⠉⠙" "⡤⠜⠊⠉" "⣀⡤⠜⠊" "⢤⣀⡤⠜"
121
+ "⠣⢤⣀⡤" "⠑⠣⢤⣀" "⠉⠑⠣⢤" "⠋⠉⠑⠣")
122
+ ;;
123
+ checkerboard)
124
+ # Alternating patterns (6×4 grid)
125
+ LACY_SPINNER_ANIM=("⢕⢕⢕⠀" "⡪⡪⡪⠀" "⢊⠔⡡⠀" "⡡⢊⠔⠀")
126
+ ;;
127
+ helix)
128
+ # Double helix — two sine strands crossing (8×4 grid)
129
+ LACY_SPINNER_ANIM=("⢌⣉⢎⣉" "⣉⡱⣉⡱" "⣉⢎⣉⢎" "⡱⣉⡱⣉" "⢎⣉⢎⣉" "⣉⡱⣉⡱"
130
+ "⣉⢎⣉⢎" "⡱⣉⡱⣉" "⢎⣉⢎⣉" "⣉⡱⣉⡱" "⣉⢎⣉⢎" "⡱⣉⡱⣉"
131
+ "⢎⣉⢎⣉" "⣉⡱⣉⡱" "⣉⢎⣉⢎" "⡱⣉⡱⣉")
132
+ ;;
133
+ fillsweep)
134
+ # Rows filling then draining (4×4 grid)
135
+ LACY_SPINNER_ANIM=("⣀⣀⠀⠀" "⣤⣤⠀⠀" "⣶⣶⠀⠀" "⣿⣿⠀⠀" "⣿⣿⠀⠀" "⣿⣿⠀⠀"
136
+ "⣶⣶⠀⠀" "⣤⣤⠀⠀" "⣀⣀⠀⠀" "⠀⠀⠀⠀" "⠀⠀⠀⠀")
137
+ ;;
138
+ diagswipe)
139
+ # Diagonal fill and clear (4×4 grid)
140
+ LACY_SPINNER_ANIM=("⠁⠀⠀⠀" "⠋⠀⠀⠀" "⠟⠁⠀⠀" "⡿⠋⠀⠀" "⣿⠟⠀⠀" "⣿⡿⠀⠀"
141
+ "⣿⣿⠀⠀" "⣿⣿⠀⠀" "⣾⣿⠀⠀" "⣴⣿⠀⠀" "⣠⣾⠀⠀" "⢀⣴⠀⠀"
142
+ "⠀⣠⠀⠀" "⠀⢀⠀⠀" "⠀⠀⠀⠀" "⠀⠀⠀⠀")
143
+ ;;
144
+ *)
145
+ # Unknown — fall back to braille
146
+ LACY_SPINNER_ANIM=("⠋⠀⠀⠀" "⠙⠀⠀⠀" "⠹⠀⠀⠀" "⠸⠀⠀⠀" "⠼⠀⠀⠀" "⠴⠀⠀⠀"
147
+ "⠦⠀⠀⠀" "⠧⠀⠀⠀" "⠇⠀⠀⠀" "⠏⠀⠀⠀")
148
+ ;;
149
+ esac
150
+ }
151
+
152
+ # Preview all animations simultaneously
153
+ # Each animation shown on its own line with its name as the shimmer label
154
+ lacy_preview_all_spinners() {
155
+ local duration="${1:-4}"
156
+ local delay="${LACY_SPINNER_FRAME_DELAY:-0.05}"
157
+ local num_anims=${#LACY_SPINNER_ANIMATIONS[@]}
158
+ local arr_off=${_LACY_ARR_OFFSET:-0}
159
+
160
+ # Shimmer colors — ZSH doesn't word-split $var, use ${=var}
161
+ local -a shimmer_colors
162
+ if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
163
+ shimmer_colors=("${LACY_COLOR_SHIMMER[@]}")
164
+ else
165
+ shimmer_colors=("${LACY_COLOR_SHIMMER[@]}")
166
+ fi
167
+ local num_colors=${#shimmer_colors[@]}
168
+
169
+ # Declare ALL loop variables here — ZSH's local/typeset prints values
170
+ # when re-declared inside a loop, causing garbage output
171
+ local _n _a _name _nf _fidx _spinner _text_len _shimmer _center
172
+ local _i _char _dist _cidx _color _dots_phase _dots
173
+ local _loop_start _loop_end
174
+ local frame_num=0
175
+ local start=$SECONDS
176
+
177
+ # Hide cursor
178
+ printf '\e[?25l'
179
+
180
+ # Print initial blank lines
181
+ for ((_n = 0; _n < num_anims; _n++)); do
182
+ printf '\n'
183
+ done
184
+
185
+ if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
186
+ _loop_start=1; _loop_end=$num_anims
187
+ else
188
+ _loop_start=0; _loop_end=$((num_anims - 1))
189
+ fi
190
+
191
+ while (( SECONDS - start < duration )); do
192
+ # Move cursor up to first line
193
+ printf '\e[%dA' "$num_anims"
194
+
195
+ for ((_a = _loop_start; _a <= _loop_end; _a++)); do
196
+ _name="${LACY_SPINNER_ANIMATIONS[$_a]}"
197
+
198
+ # Load this animation's frames
199
+ lacy_set_spinner_animation "$_name"
200
+ _nf=${#LACY_SPINNER_ANIM[@]}
201
+
202
+ if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
203
+ _fidx=$(( (frame_num % _nf) + 1 ))
204
+ else
205
+ _fidx=$(( frame_num % _nf ))
206
+ fi
207
+ _spinner="${LACY_SPINNER_ANIM[$_fidx]}"
208
+
209
+ # Build shimmer text for the animation name
210
+ _text_len=${#_name}
211
+ _shimmer=""
212
+ _center=$(( frame_num % (_text_len + 4) ))
213
+
214
+ for ((_i = 0; _i < _text_len; _i++)); do
215
+ if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
216
+ _char="${_name[$((_i + 1))]}"
217
+ else
218
+ _char="${_name:$_i:1}"
219
+ fi
220
+ _dist=$(( _center - _i ))
221
+ (( _dist < 0 )) && _dist=$(( -_dist ))
222
+
223
+ if (( _dist < num_colors )); then
224
+ _cidx=$(( _dist + arr_off ))
225
+ else
226
+ _cidx=$(( num_colors - 1 + arr_off ))
227
+ fi
228
+ _color=${shimmer_colors[$_cidx]}
229
+ _shimmer+="\e[38;5;${_color}m${_char}"
230
+ done
231
+ _shimmer+="\e[0m"
232
+
233
+ # Cycling dots
234
+ _dots_phase=$(( (frame_num / 3) % 4 ))
235
+ _dots=""
236
+ case $_dots_phase in
237
+ 1) _dots="." ;; 2) _dots=".." ;; 3) _dots="..." ;;
238
+ esac
239
+
240
+ printf "\e[2K\r \e[38;5;${LACY_COLOR_AGENT}m%s\e[0m %b\e[38;5;${LACY_COLOR_NEUTRAL}m%s\e[0m\n" \
241
+ "$_spinner" "$_shimmer" "$_dots"
242
+ done
243
+
244
+ frame_num=$((frame_num + 1))
245
+ sleep "$delay"
246
+ done
247
+
248
+ # Show cursor
249
+ printf '\e[?25h'
250
+
251
+ # Restore active spinner
252
+ lacy_set_spinner_animation "${LACY_SPINNER_STYLE:-braille}"
253
+ }
254
+
255
+ # List available animations
256
+ lacy_list_spinner_animations() {
257
+ local current="${LACY_SPINNER_STYLE:-braille}"
258
+ local name
259
+ for name in "${LACY_SPINNER_ANIMATIONS[@]}"; do
260
+ if [[ "$name" == "$current" ]]; then
261
+ lacy_print_color "$LACY_COLOR_AGENT" " ● $name (active)"
262
+ else
263
+ lacy_print_color "$LACY_COLOR_NEUTRAL" " ○ $name"
264
+ fi
265
+ done
266
+ if [[ "$current" == "random" ]]; then
267
+ lacy_print_color "$LACY_COLOR_AGENT" " ● random (active)"
268
+ else
269
+ lacy_print_color "$LACY_COLOR_NEUTRAL" " ○ random"
270
+ fi
271
+ }