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,297 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Shared command implementations — portable across Bash 4+ and ZSH
4
+ # Uses lacy_print_color / lacy_print_color_n from constants.sh for output.
5
+ # Sourced by lib/zsh/init.zsh and lib/bash/init.bash after all core modules.
6
+
7
+ # === Helpers ===
8
+
9
+ # Print colored indicator character + message on one line
10
+ # Usage: _lacy_print_indicator_msg <color> <message>
11
+ _lacy_print_indicator_msg() {
12
+ local color="$1" msg="$2"
13
+ lacy_print_color_n "$color" " ${LACY_INDICATOR_CHAR}"
14
+ echo " $msg"
15
+ }
16
+
17
+ # Check if any tool from LACY_TOOL_LIST is installed
18
+ _lacy_is_any_tool_installed() {
19
+ local _t
20
+ for _t in "${LACY_TOOL_LIST[@]}"; do
21
+ command -v "$_t" >/dev/null 2>&1 && return 0
22
+ done
23
+ return 1
24
+ }
25
+
26
+ # === Agent Execution ===
27
+
28
+ # Execute command via AI agent — shows error hints on failure
29
+ lacy_shell_execute_agent() {
30
+ local query="$1"
31
+
32
+ if ! lacy_shell_query_agent "$query"; then
33
+ if [[ -z "$LACY_ACTIVE_TOOL" ]] && ! _lacy_is_any_tool_installed; then
34
+ echo ""
35
+ printf '\e[38;5;196m No AI tool detected.\e[0m Lacy needs an AI CLI to handle queries.\n'
36
+ echo ""
37
+ printf '\e[1m Supported tools:\e[0m\n'
38
+ echo ""
39
+ printf ' \e[38;5;34m%-12s\e[0m %s\n' "lash" "npm install -g lashcode (recommended)"
40
+ printf ' \e[38;5;238m%-12s\e[0m %s\n' "claude" "brew install claude"
41
+ printf ' \e[38;5;238m%-12s\e[0m %s\n' "opencode" "brew install opencode"
42
+ printf ' \e[38;5;238m%-12s\e[0m %s\n' "gemini" "brew install gemini"
43
+ printf ' \e[38;5;238m%-12s\e[0m %s\n' "codex" "npm install -g @openai/codex"
44
+ echo ""
45
+ printf ' \e[38;5;75mThen run:\e[0m lacy setup\n'
46
+ printf ' \e[38;5;75mDocs:\e[0m %s\n' "$LACY_DOCS_URL"
47
+ echo ""
48
+ else
49
+ local _tool="${LACY_ACTIVE_TOOL}"
50
+ if [[ -z "$_tool" ]]; then
51
+ local _t
52
+ for _t in "${LACY_TOOL_LIST[@]}"; do
53
+ if command -v "$_t" >/dev/null 2>&1; then
54
+ _tool="$_t"
55
+ break
56
+ fi
57
+ done
58
+ fi
59
+ echo ""
60
+ lacy_print_color 238 "$LACY_MSG_RECOVERY_TOOL"
61
+ lacy_print_color 238 "$LACY_MSG_RECOVERY_ASK"
62
+ lacy_print_color 238 "$LACY_MSG_RECOVERY_DOCTOR"
63
+ echo ""
64
+ fi
65
+ fi
66
+ }
67
+
68
+ # === Mode Command ===
69
+
70
+ lacy_shell_mode() {
71
+ case "$1" in
72
+ "shell"|"s")
73
+ lacy_shell_set_mode "shell"
74
+ [[ "$LACY_SHELL_TYPE" == "zsh" ]] && lacy_shell_update_rprompt 2>/dev/null
75
+ echo ""
76
+ _lacy_print_indicator_msg "$LACY_COLOR_SHELL" "$LACY_MSG_MODE_SHELL"
77
+ echo ""
78
+ ;;
79
+ "agent"|"a")
80
+ lacy_shell_set_mode "agent"
81
+ [[ "$LACY_SHELL_TYPE" == "zsh" ]] && lacy_shell_update_rprompt 2>/dev/null
82
+ echo ""
83
+ _lacy_print_indicator_msg "$LACY_COLOR_AGENT" "$LACY_MSG_MODE_AGENT"
84
+ echo ""
85
+ ;;
86
+ "auto"|"u")
87
+ lacy_shell_set_mode "auto"
88
+ [[ "$LACY_SHELL_TYPE" == "zsh" ]] && lacy_shell_update_rprompt 2>/dev/null
89
+ echo ""
90
+ _lacy_print_indicator_msg "$LACY_COLOR_AUTO" "$LACY_MSG_MODE_AUTO"
91
+ echo ""
92
+ ;;
93
+ "toggle"|"t")
94
+ lacy_shell_toggle_mode
95
+ [[ "$LACY_SHELL_TYPE" == "zsh" ]] && lacy_shell_update_rprompt 2>/dev/null
96
+ local new_mode="$LACY_SHELL_CURRENT_MODE"
97
+ echo ""
98
+ case "$new_mode" in
99
+ "shell") _lacy_print_indicator_msg "$LACY_COLOR_SHELL" "$LACY_MSG_MODE_SHELL_SHORT" ;;
100
+ "agent") _lacy_print_indicator_msg "$LACY_COLOR_AGENT" "$LACY_MSG_MODE_AGENT_SHORT" ;;
101
+ "auto") _lacy_print_indicator_msg "$LACY_COLOR_AUTO" "$LACY_MSG_MODE_AUTO_SHORT" ;;
102
+ esac
103
+ echo ""
104
+ ;;
105
+ "status")
106
+ lacy_shell_mode_status
107
+ ;;
108
+ *)
109
+ echo ""
110
+ echo "Usage: mode [shell|agent|auto|toggle|status]"
111
+ echo ""
112
+ echo -n "Current: "
113
+ case "$LACY_SHELL_CURRENT_MODE" in
114
+ "shell") lacy_print_color "$LACY_COLOR_SHELL" "SHELL" ;;
115
+ "agent") lacy_print_color "$LACY_COLOR_AGENT" "AGENT" ;;
116
+ "auto") lacy_print_color "$LACY_COLOR_AUTO" "AUTO" ;;
117
+ esac
118
+ echo ""
119
+ echo "Colors:"
120
+ _lacy_print_indicator_msg "$LACY_COLOR_SHELL" "$LACY_MSG_COLOR_SHELL"
121
+ _lacy_print_indicator_msg "$LACY_COLOR_AGENT" "$LACY_MSG_COLOR_AGENT"
122
+ echo ""
123
+ ;;
124
+ esac
125
+ }
126
+
127
+ # === Tool Command ===
128
+
129
+ lacy_shell_tool() {
130
+ case "$1" in
131
+ "")
132
+ echo ""
133
+ if [[ "$LACY_ACTIVE_TOOL" == "custom" ]]; then
134
+ echo "Active tool: custom (${LACY_CUSTOM_TOOL_CMD:-not configured})"
135
+ elif [[ -z "$LACY_ACTIVE_TOOL" ]]; then
136
+ local _detected=""
137
+ local _t
138
+ for _t in "${LACY_TOOL_LIST[@]}"; do
139
+ if command -v "$_t" >/dev/null 2>&1; then
140
+ _detected="$_t"
141
+ break
142
+ fi
143
+ done
144
+ if [[ -n "$_detected" ]]; then
145
+ echo "Active tool: auto-detect (using $_detected)"
146
+ else
147
+ echo "Active tool: auto-detect (no tools found)"
148
+ fi
149
+ else
150
+ echo "Active tool: ${LACY_ACTIVE_TOOL}"
151
+ fi
152
+ echo ""
153
+ echo "Available tools:"
154
+ local t
155
+ for t in "${LACY_TOOL_LIST[@]}"; do
156
+ if command -v "$t" >/dev/null 2>&1; then
157
+ lacy_print_color_n 34 " ✓"
158
+ echo " $t"
159
+ else
160
+ lacy_print_color_n 238 " ○"
161
+ echo " $t (not installed)"
162
+ fi
163
+ done
164
+ if [[ -n "$LACY_CUSTOM_TOOL_CMD" ]]; then
165
+ lacy_print_color_n 34 " ✓"
166
+ echo " custom ($LACY_CUSTOM_TOOL_CMD)"
167
+ else
168
+ lacy_print_color_n 238 " ○"
169
+ echo " custom (not configured)"
170
+ fi
171
+ echo ""
172
+ echo "Usage: tool set <name>"
173
+ echo " tool set custom \"command -flags\""
174
+ echo ""
175
+ ;;
176
+ set)
177
+ if [[ -z "$2" ]]; then
178
+ echo "Usage: tool set <name>"
179
+ echo "Options: lash, claude, opencode, gemini, codex, hermes, copilot, goose, amp, aider, custom, auto"
180
+ echo " tool set custom \"command -flags\""
181
+ return 1
182
+ fi
183
+ if [[ "$2" == "auto" ]]; then
184
+ lacy_preheat_cleanup
185
+ LACY_ACTIVE_TOOL=""
186
+ export LACY_ACTIVE_TOOL
187
+ echo "Tool set to: auto-detect"
188
+ elif [[ "$2" == "custom" ]]; then
189
+ if [[ -z "$3" ]]; then
190
+ echo "Usage: tool set custom \"command -flags\""
191
+ echo "Example: tool set custom \"claude --dangerously-skip-permissions -p\""
192
+ return 1
193
+ fi
194
+ lacy_preheat_cleanup
195
+ LACY_ACTIVE_TOOL="custom"
196
+ LACY_CUSTOM_TOOL_CMD="$3"
197
+ export LACY_ACTIVE_TOOL LACY_CUSTOM_TOOL_CMD
198
+ echo "Tool set to: custom ($LACY_CUSTOM_TOOL_CMD)"
199
+ else
200
+ lacy_preheat_cleanup
201
+ LACY_ACTIVE_TOOL="$2"
202
+ export LACY_ACTIVE_TOOL
203
+ echo "Tool set to: $2"
204
+ fi
205
+ ;;
206
+ *)
207
+ echo "Usage: tool [set <name>]"
208
+ echo "Options: lash, claude, opencode, gemini, codex, hermes, copilot, goose, amp, aider, custom, auto"
209
+ echo " tool set custom \"command -flags\""
210
+ ;;
211
+ esac
212
+ }
213
+
214
+ # === Spinner Command ===
215
+
216
+ lacy_shell_spinner() {
217
+ case "$1" in
218
+ "")
219
+ echo ""
220
+ echo "Active spinner: ${LACY_SPINNER_STYLE:-braille}"
221
+ echo ""
222
+ echo "Available animations:"
223
+ lacy_list_spinner_animations
224
+ echo ""
225
+ echo "Usage: spinner set <name>"
226
+ echo " spinner preview [name|all]"
227
+ echo ""
228
+ ;;
229
+ set)
230
+ if [[ -z "$2" ]]; then
231
+ echo "Usage: spinner set <name>"
232
+ echo "Available: ${LACY_SPINNER_ANIMATIONS[*]} random"
233
+ return 1
234
+ fi
235
+ if [[ "$2" == "random" ]] || _lacy_in_list "$2" "${LACY_SPINNER_ANIMATIONS[@]}"; then
236
+ LACY_SPINNER_STYLE="$2"
237
+ export LACY_SPINNER_STYLE
238
+ echo "Spinner set to: $2"
239
+ else
240
+ echo "Unknown animation: $2"
241
+ echo "Available: ${LACY_SPINNER_ANIMATIONS[*]} random"
242
+ return 1
243
+ fi
244
+ ;;
245
+ preview)
246
+ if [[ "$2" == "all" ]]; then
247
+ echo "Previewing all animations (Ctrl+C to stop)"
248
+ echo ""
249
+ lacy_preview_all_spinners 5
250
+ else
251
+ local style="${2:-${LACY_SPINNER_STYLE:-braille}}"
252
+ if [[ "$style" != "random" ]] && ! _lacy_in_list "$style" "${LACY_SPINNER_ANIMATIONS[@]}"; then
253
+ echo "Unknown animation: $style"
254
+ return 1
255
+ fi
256
+ local _saved="$LACY_SPINNER_STYLE"
257
+ LACY_SPINNER_STYLE="$style"
258
+ echo "Previewing: $style (Ctrl+C to stop)"
259
+ lacy_start_spinner
260
+ sleep 3
261
+ lacy_stop_spinner
262
+ LACY_SPINNER_STYLE="$_saved"
263
+ fi
264
+ ;;
265
+ *)
266
+ echo "Usage: spinner [set <name> | preview [name|all]]"
267
+ ;;
268
+ esac
269
+ }
270
+
271
+ # === Conversation Management ===
272
+
273
+ lacy_shell_clear_conversation() {
274
+ rm -f "$LACY_SHELL_CONVERSATION_FILE"
275
+ echo "$LACY_MSG_CONVERSATION_CLEARED"
276
+ }
277
+
278
+ lacy_shell_show_conversation() {
279
+ if [[ -f "$LACY_SHELL_CONVERSATION_FILE" ]]; then
280
+ cat "$LACY_SHELL_CONVERSATION_FILE"
281
+ else
282
+ echo "$LACY_MSG_NO_CONVERSATION"
283
+ fi
284
+ }
285
+
286
+ # === Session Command Override ===
287
+
288
+ # Override lacy command to handle session subcommands without subprocess
289
+ lacy() {
290
+ local cmd="${1:-}"
291
+ cmd="${cmd#/}" # strip optional leading slash (/new → new)
292
+ case "$cmd" in
293
+ new|reset|clear) lacy_session_new ;;
294
+ resume) lacy_session_resume ;;
295
+ *) command lacy "$@" ;;
296
+ esac
297
+ }
@@ -0,0 +1,340 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Configuration management for Lacy Shell
4
+ # Shared across Bash 4+ and ZSH
5
+
6
+ # Default configuration
7
+ declare -A LACY_SHELL_CONFIG 2>/dev/null || true
8
+ # LACY_SHELL_CONFIG_FILE is defined in constants.sh
9
+
10
+ # ============================================================================
11
+ # Config Parsing Helpers (reduces code duplication)
12
+ # ============================================================================
13
+
14
+ # Simple YAML parser for shell (handles basic key: value)
15
+ lacy_shell_parse_yaml_value() {
16
+ local file="$1"
17
+ local key="$2"
18
+
19
+ grep "^[[:space:]]*${key}:" "$file" 2>/dev/null | head -1 | sed 's/^[^:]*:[[:space:]]*//' | tr -d '"' | tr -d "'"
20
+ }
21
+
22
+ # Clean a config value (remove quotes, comments, whitespace)
23
+ lacy_shell_clean_config_value() {
24
+ local value="$1"
25
+ echo "$value" | sed 's/#.*//' | tr -d '"' | tr -d "'" | xargs
26
+ }
27
+
28
+ # Parse a key-value line from config and export if valid
29
+ # Usage: lacy_shell_export_config_value <key> <value> <key_map>
30
+ # key_map format: "config_key1:ENV_VAR1,config_key2:ENV_VAR2"
31
+ lacy_shell_export_config_value() {
32
+ local key="$1"
33
+ local value="$2"
34
+ local key_map="$3"
35
+
36
+ # Clean the value
37
+ value=$(lacy_shell_clean_config_value "$value")
38
+
39
+ # Skip empty or null values
40
+ if [[ -z "$value" ]] || [[ "$value" == "null" ]]; then
41
+ return 1
42
+ fi
43
+
44
+ # Split key_map by comma and iterate
45
+ local IFS_save="$IFS"
46
+ IFS=','
47
+ local -a mappings
48
+ if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
49
+ mappings=( ${(s:,:)key_map} )
50
+ else
51
+ read -ra mappings <<< "$key_map"
52
+ fi
53
+ IFS="$IFS_save"
54
+
55
+ local mapping config_key env_var
56
+ for mapping in "${mappings[@]}"; do
57
+ config_key="${mapping%%:*}"
58
+ env_var="${mapping#*:}"
59
+ if [[ "$key" == "$config_key" ]]; then
60
+ export "$env_var"="$value"
61
+ return 0
62
+ fi
63
+ done
64
+ return 1
65
+ }
66
+
67
+ # Load configuration from file
68
+ lacy_shell_load_config() {
69
+ # Set defaults from constants
70
+ LACY_SHELL_CURRENT_MODE="$LACY_SHELL_DEFAULT_MODE"
71
+
72
+ # Ensure config directory exists
73
+ mkdir -p "$LACY_SHELL_HOME"
74
+
75
+ # Create default config if it doesn't exist
76
+ if [[ ! -f "$LACY_SHELL_CONFIG_FILE" ]]; then
77
+ lacy_shell_create_default_config
78
+ LACY_CONFIG_CACHE_VALID=false
79
+ fi
80
+
81
+ # Check if we can use cached config (cache must be newer than config)
82
+ if [[ "$LACY_CONFIG_CACHE_VALID" == true ]] && \
83
+ [[ -f "$LACY_SHELL_CONFIG_CACHE_FILE" ]] && \
84
+ [[ ! "$LACY_SHELL_CONFIG_FILE" -nt "$LACY_SHELL_CONFIG_CACHE_FILE" ]]; then
85
+ # Use cached config - much faster
86
+ source "$LACY_SHELL_CONFIG_CACHE_FILE"
87
+ return
88
+ fi
89
+
90
+ # Parse configuration using optimized single-pass parsing
91
+ if [[ -f "$LACY_SHELL_CONFIG_FILE" ]]; then
92
+ # Define key mappings for each section
93
+ local api_keys_map="openai:LACY_SHELL_API_OPENAI,anthropic:LACY_SHELL_API_ANTHROPIC"
94
+ local model_map="provider:LACY_SHELL_PROVIDER,name:LACY_SHELL_MODEL_NAME"
95
+ local agent_map="command:LACY_SHELL_AGENT_COMMAND,context_mode:LACY_SHELL_AGENT_CONTEXT_MODE,needs_api_keys:LACY_SHELL_AGENT_NEEDS_API_KEYS"
96
+ local agent_tools_map="active:LACY_ACTIVE_TOOL,custom_command:LACY_CUSTOM_TOOL_CMD"
97
+ local preheat_map="eager:LACY_PREHEAT_EAGER,server_port:LACY_PREHEAT_SERVER_PORT"
98
+ local context_map="output:_LACY_CTX_OUTPUT_ENABLED,output_lines:_LACY_CTX_OUTPUT_MAX_LINES"
99
+ local spinner_map="style:LACY_SPINNER_STYLE"
100
+
101
+ # Track current section
102
+ local current_section=""
103
+
104
+ local line key value
105
+ while IFS= read -r line; do
106
+ # Detect section headers
107
+ if [[ "$line" =~ ^api_keys: ]]; then
108
+ current_section="api_keys"
109
+ continue
110
+ elif [[ "$line" =~ ^model: ]]; then
111
+ current_section="model"
112
+ continue
113
+ elif [[ "$line" =~ ^agent_tools: ]]; then
114
+ current_section="agent_tools"
115
+ continue
116
+ elif [[ "$line" =~ ^preheat: ]]; then
117
+ current_section="preheat"
118
+ continue
119
+ elif [[ "$line" =~ ^context: ]]; then
120
+ current_section="context"
121
+ continue
122
+ elif [[ "$line" =~ ^spinner: ]]; then
123
+ current_section="spinner"
124
+ continue
125
+ elif [[ "$line" =~ ^agent: ]]; then
126
+ current_section="agent"
127
+ continue
128
+ elif [[ "$line" =~ ^[^[:space:]] ]] && [[ ! "$line" =~ ^# ]]; then
129
+ # New section started (not indented, not a comment)
130
+ current_section=""
131
+ fi
132
+
133
+ # Parse key-value pairs within sections
134
+ if [[ -n "$current_section" ]] && [[ "$line" =~ ^[[:space:]]+([^:]+):[[:space:]]*(.+) ]]; then
135
+ if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
136
+ key="${match[1]}"
137
+ value="${match[2]}"
138
+ else
139
+ key="${BASH_REMATCH[1]}"
140
+ value="${BASH_REMATCH[2]}"
141
+ fi
142
+
143
+ # Use appropriate key map based on section
144
+ case "$current_section" in
145
+ "api_keys")
146
+ lacy_shell_export_config_value "$key" "$value" "$api_keys_map"
147
+ ;;
148
+ "model")
149
+ lacy_shell_export_config_value "$key" "$value" "$model_map"
150
+ ;;
151
+ "agent")
152
+ lacy_shell_export_config_value "$key" "$value" "$agent_map"
153
+ ;;
154
+ "agent_tools")
155
+ lacy_shell_export_config_value "$key" "$value" "$agent_tools_map"
156
+ ;;
157
+ "preheat")
158
+ lacy_shell_export_config_value "$key" "$value" "$preheat_map"
159
+ ;;
160
+ "context")
161
+ lacy_shell_export_config_value "$key" "$value" "$context_map"
162
+ ;;
163
+ "spinner")
164
+ lacy_shell_export_config_value "$key" "$value" "$spinner_map"
165
+ ;;
166
+ esac
167
+ fi
168
+ done < "$LACY_SHELL_CONFIG_FILE"
169
+
170
+ # Check for MCP configuration (simplified)
171
+ local mcp_line servers_line
172
+ mcp_line=$(grep "^mcp:" "$LACY_SHELL_CONFIG_FILE" 2>/dev/null || true)
173
+ servers_line=$(grep "^[[:space:]]*servers:" "$LACY_SHELL_CONFIG_FILE" 2>/dev/null || true)
174
+ if [[ -n "$mcp_line" ]] && [[ -n "$servers_line" ]]; then
175
+ LACY_SHELL_MCP_SERVERS="configured"
176
+ LACY_SHELL_MCP_SERVERS_JSON='[{"name":"filesystem","command":"npx","args":["@modelcontextprotocol/server-filesystem"]}]'
177
+ else
178
+ LACY_SHELL_MCP_SERVERS=""
179
+ LACY_SHELL_MCP_SERVERS_JSON=""
180
+ fi
181
+
182
+ # Cache the parsed configuration for fast future loads
183
+ # Use printf %q to safely escape values (prevents injection when sourced)
184
+ {
185
+ echo "# Generated config cache - do not edit"
186
+ printf 'LACY_SHELL_CURRENT_MODE=%q\n' "$LACY_SHELL_CURRENT_MODE"
187
+ printf 'LACY_SHELL_API_OPENAI=%q\n' "$LACY_SHELL_API_OPENAI"
188
+ printf 'LACY_SHELL_API_ANTHROPIC=%q\n' "$LACY_SHELL_API_ANTHROPIC"
189
+ printf 'LACY_SHELL_PROVIDER=%q\n' "$LACY_SHELL_PROVIDER"
190
+ printf 'LACY_SHELL_MODEL_NAME=%q\n' "$LACY_SHELL_MODEL_NAME"
191
+ printf 'LACY_ACTIVE_TOOL=%q\n' "$LACY_ACTIVE_TOOL"
192
+ printf 'LACY_CUSTOM_TOOL_CMD=%q\n' "$LACY_CUSTOM_TOOL_CMD"
193
+ printf 'LACY_SHELL_MCP_SERVERS=%q\n' "$LACY_SHELL_MCP_SERVERS"
194
+ printf 'LACY_SHELL_MCP_SERVERS_JSON=%q\n' "$LACY_SHELL_MCP_SERVERS_JSON"
195
+ printf '_LACY_CTX_OUTPUT_ENABLED=%q\n' "${_LACY_CTX_OUTPUT_ENABLED:-true}"
196
+ printf '_LACY_CTX_OUTPUT_MAX_LINES=%q\n' "${_LACY_CTX_OUTPUT_MAX_LINES:-50}"
197
+ printf 'LACY_SPINNER_STYLE=%q\n' "${LACY_SPINNER_STYLE:-random}"
198
+ } > "$LACY_SHELL_CONFIG_CACHE_FILE"
199
+
200
+ LACY_CONFIG_CACHE_VALID=true
201
+
202
+ # Export variables for global access
203
+ export LACY_SHELL_MCP_SERVERS
204
+ export LACY_SHELL_MCP_SERVERS_JSON
205
+ fi
206
+
207
+ # Also check environment variables as fallback
208
+ if [[ -z "$LACY_SHELL_API_OPENAI" ]] && [[ -n "$OPENAI_API_KEY" ]]; then
209
+ export LACY_SHELL_API_OPENAI="$OPENAI_API_KEY"
210
+ fi
211
+ if [[ -z "$LACY_SHELL_API_ANTHROPIC" ]] && [[ -n "$ANTHROPIC_API_KEY" ]]; then
212
+ export LACY_SHELL_API_ANTHROPIC="$ANTHROPIC_API_KEY"
213
+ fi
214
+
215
+ # Provider/model overrides via env
216
+ if [[ -z "$LACY_SHELL_PROVIDER" ]] && [[ -n "$LACY_SHELL_DEFAULT_PROVIDER" ]]; then
217
+ export LACY_SHELL_PROVIDER="$LACY_SHELL_DEFAULT_PROVIDER"
218
+ fi
219
+ if [[ -z "$LACY_SHELL_MODEL_NAME" ]] && [[ -n "$LACY_SHELL_DEFAULT_MODEL" ]]; then
220
+ export LACY_SHELL_MODEL_NAME="$LACY_SHELL_DEFAULT_MODEL"
221
+ fi
222
+
223
+ # Agent CLI defaults (if not configured, use defaults from constants)
224
+ : "${LACY_SHELL_AGENT_COMMAND:="$LACY_SHELL_DEFAULT_AGENT_COMMAND"}"
225
+ : "${LACY_SHELL_AGENT_CONTEXT_MODE:="$LACY_SHELL_DEFAULT_AGENT_CONTEXT_MODE"}"
226
+ : "${LACY_SHELL_AGENT_NEEDS_API_KEYS:="$LACY_SHELL_DEFAULT_AGENT_NEEDS_API_KEYS"}"
227
+ export LACY_SHELL_AGENT_COMMAND LACY_SHELL_AGENT_CONTEXT_MODE LACY_SHELL_AGENT_NEEDS_API_KEYS
228
+
229
+ # Active AI tool (empty = auto-detect)
230
+ export LACY_ACTIVE_TOOL
231
+ export LACY_CUSTOM_TOOL_CMD
232
+
233
+ # Initialize current mode from default
234
+ LACY_SHELL_CURRENT_MODE="$LACY_SHELL_DEFAULT_MODE"
235
+
236
+ # Export configuration
237
+ export LACY_SHELL_CURRENT_MODE
238
+ }
239
+
240
+ # Create default configuration file
241
+ lacy_shell_create_default_config() {
242
+ cat > "$LACY_SHELL_CONFIG_FILE" << 'EOF'
243
+ # Lacy Shell Configuration
244
+ # Edit this file to customize your settings
245
+
246
+ # API Keys for AI providers
247
+ api_keys:
248
+ openai: # Add your OpenAI API key here
249
+ anthropic: # Add your Anthropic API key here
250
+
251
+ # Operating modes
252
+ modes:
253
+ default: auto # Options: shell, agent, auto
254
+
255
+ # Smart auto-detection settings
256
+ auto_detection:
257
+ enabled: true
258
+ confidence_threshold: 0.7
259
+
260
+ # MCP (Model Context Protocol) configuration
261
+ mcp:
262
+ enabled: false
263
+ servers:
264
+ - name: filesystem
265
+ command: npx
266
+ args: ["@modelcontextprotocol/server-filesystem", "/"]
267
+ - name: web
268
+ command: npx
269
+ args: ["@modelcontextprotocol/server-web"]
270
+
271
+ # Appearance
272
+ appearance:
273
+ show_mode_indicator: true
274
+ mode_colors:
275
+ shell: green
276
+ agent: blue
277
+ auto: yellow
278
+
279
+ # Model selection (used when agent CLI is not installed)
280
+
281
+
282
+ # AI CLI tool selection
283
+ # Lacy auto-detects installed tools, or you can set one explicitly
284
+ agent_tools:
285
+ # Options: lash, claude, opencode, gemini, codex, hermes, copilot, amp, custom, or empty for auto-detect
286
+ active:
287
+ # Custom command (used when active: custom)
288
+ # custom_command: "your-command -flags"
289
+
290
+ # Preheat: keep agents warm between queries (lash, opencode)
291
+ # preheat:
292
+ # eager: false # Start background server on plugin load
293
+ # server_port: 4096 # Port for background server
294
+
295
+ # Terminal context: output capture (tmux, screen, iTerm2, Terminal.app)
296
+ # context:
297
+ # output: true # Capture terminal screen at query time
298
+ # output_lines: 50 # Max lines to include (truncates from top)
299
+
300
+ # Spinner animation style
301
+ # Options: braille (default), dots, ascii (no Unicode), braillewave, dna, scan,
302
+ # rain, scanline, pulse, snake, sparkle, cascade, columns, orbit, breathe,
303
+ # waverows, checkerboard, helix, fillsweep, diagswipe,
304
+ # random (picks a different one each query)
305
+ # spinner:
306
+ # style: random
307
+
308
+ # Agent CLI configuration (legacy)
309
+ # Configure which CLI tool to use for AI queries
310
+ agent:
311
+ # Command to run. Variables: {query}, {context_file}
312
+ command: "lash run --prompt {query}"
313
+ # How to pass context: stdin or file
314
+ context_mode: stdin
315
+ # Set to true if the CLI needs API keys from lacy
316
+ needs_api_keys: false
317
+ EOF
318
+
319
+ echo "Created default configuration at: $LACY_SHELL_CONFIG_FILE"
320
+ }
321
+
322
+ # Check if API keys are available
323
+ lacy_shell_check_api_keys() {
324
+ if [[ -n "$LACY_SHELL_API_OPENAI" ]] || [[ -n "$LACY_SHELL_API_ANTHROPIC" ]]; then
325
+ return 0
326
+ else
327
+ return 1
328
+ fi
329
+ }
330
+
331
+ # Get active API provider
332
+ lacy_shell_get_api_provider() {
333
+ if [[ -n "$LACY_SHELL_API_OPENAI" ]]; then
334
+ echo "openai"
335
+ elif [[ -n "$LACY_SHELL_API_ANTHROPIC" ]]; then
336
+ echo "anthropic"
337
+ else
338
+ echo "none"
339
+ fi
340
+ }