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.
- package/.claude/settings.local.json +26 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +49 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +28 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- package/.github/SECURITY.md +32 -0
- package/.github/assets/logo-horizontal-dark.png +0 -0
- package/.github/assets/logo-horizontal-dark.svg +17 -0
- package/.github/assets/logo-horizontal.png +0 -0
- package/.github/assets/logo-horizontal.svg +17 -0
- package/.github/assets/logo.png +0 -0
- package/.github/assets/logo.svg +12 -0
- package/.github/assets/social-preview.png +0 -0
- package/.github/assets/social-preview.svg +50 -0
- package/.github/dependabot.yml +21 -0
- package/.github/workflows/ci.yml +80 -0
- package/.github/workflows/dependabot-auto-merge.yml +32 -0
- package/CHANGELOG.md +366 -0
- package/CLAUDE.md +340 -0
- package/CONTRIBUTING.md +141 -0
- package/LICENSE +110 -0
- package/README.md +201 -31
- package/RELEASING.md +148 -0
- package/STYLE.md +202 -0
- package/assets/hero.jpeg +0 -0
- package/assets/mode-indicators.jpeg +0 -0
- package/assets/real-time-indicator.jpeg +0 -0
- package/assets/supported-tools.jpeg +0 -0
- package/bin/lacy +1028 -0
- package/docs/ADDING-BACKENDS.md +124 -0
- package/docs/DEVTO-ARTICLE.md +94 -0
- package/docs/DOCS.md +68 -0
- package/docs/GROWTH-STRATEGY.md +119 -0
- package/docs/HN-RESPONSES.md +122 -0
- package/docs/LAUNCH-COPY-FINAL.md +105 -0
- package/docs/MARKETING.md +411 -0
- package/docs/NATURAL_LANGUAGE_DETECTION.md +204 -0
- package/docs/UGC_VIDEO_SCRIPT.md +114 -0
- package/docs/articles/devto-how-i-made-my-terminal-understand-english.md +117 -0
- package/docs/demo-color-transition.gif +0 -0
- package/docs/demo-full.gif +0 -0
- package/docs/demo-indicator.gif +0 -0
- package/docs/launch-thread-may6.sh +158 -0
- package/docs/videos/README.md +189 -0
- package/docs/videos/generate_frames.py +510 -0
- package/docs/videos/generate_frames_v2.py +729 -0
- package/docs/videos/generate_short.py +328 -0
- package/docs/videos/generate_short_v2.py +526 -0
- package/docs/videos/lacy-shell-demo-v2.mp4 +0 -0
- package/docs/videos/lacy-shell-demo.mp4 +0 -0
- package/docs/videos/lacy-shell-short-v2.mp4 +0 -0
- package/docs/videos/lacy-shell-short.mp4 +0 -0
- package/install.sh +1009 -0
- package/lacy.plugin.bash +75 -0
- package/lacy.plugin.fish +43 -0
- package/lacy.plugin.zsh +65 -0
- package/lib/animations.zsh +3 -0
- package/lib/bash/completions.bash +40 -0
- package/lib/bash/execute.bash +233 -0
- package/lib/bash/init.bash +40 -0
- package/lib/bash/keybindings.bash +134 -0
- package/lib/bash/prompt.bash +85 -0
- package/lib/commands/info.sh +25 -0
- package/lib/config.zsh +3 -0
- package/lib/constants.zsh +3 -0
- package/lib/core/animations.sh +271 -0
- package/lib/core/commands.sh +297 -0
- package/lib/core/config.sh +340 -0
- package/lib/core/constants.sh +366 -0
- package/lib/core/context.sh +260 -0
- package/lib/core/detection.sh +417 -0
- package/lib/core/mcp.sh +741 -0
- package/lib/core/modes.sh +123 -0
- package/lib/core/preheat.sh +496 -0
- package/lib/core/spinner.sh +174 -0
- package/lib/core/telemetry.sh +99 -0
- package/lib/detection.zsh +3 -0
- package/lib/execute.zsh +3 -0
- package/lib/fish/config.fish +66 -0
- package/lib/fish/detection.fish +90 -0
- package/lib/fish/execute.fish +105 -0
- package/lib/fish/keybindings.fish +42 -0
- package/lib/fish/prompt.fish +30 -0
- package/lib/keybindings.zsh +3 -0
- package/lib/mcp.zsh +3 -0
- package/lib/modes.zsh +3 -0
- package/lib/preheat.zsh +3 -0
- package/lib/prompt.zsh +3 -0
- package/lib/spinner.zsh +3 -0
- package/lib/zsh/completions.zsh +60 -0
- package/lib/zsh/execute.zsh +294 -0
- package/lib/zsh/init.zsh +26 -0
- package/lib/zsh/keybindings.zsh +551 -0
- package/lib/zsh/prompt.zsh +90 -0
- package/package.json +42 -27
- package/packages/lacy/README.md +61 -0
- package/packages/lacy/commands/info.sh +25 -0
- package/{index.mjs → packages/lacy/index.mjs} +247 -20
- package/packages/lacy/package-lock.json +71 -0
- package/packages/lacy/package.json +42 -0
- package/script/release.ts +487 -0
- package/squirrel.toml +36 -0
- package/tests/test_bash.bash +163 -0
- package/tests/test_core.sh +607 -0
- package/tests/test_gemini.sh +119 -0
- package/tests/test_gemini_mcp.sh +126 -0
- package/tests/test_preheat_server.zsh +446 -0
- 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
|
+
}
|