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,417 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Auto-detection logic for determining shell vs agent mode
|
|
4
|
+
# Shared across Bash 4+ and ZSH
|
|
5
|
+
|
|
6
|
+
# Cache for command -v lookups (avoids repeated PATH walks while typing)
|
|
7
|
+
LACY_CMD_CACHE_WORD=""
|
|
8
|
+
LACY_CMD_CACHE_RESULT=""
|
|
9
|
+
|
|
10
|
+
# Check if a word is a valid command, with single-entry cache
|
|
11
|
+
lacy_shell_is_valid_command() {
|
|
12
|
+
local word="$1"
|
|
13
|
+
if [[ "$word" == "$LACY_CMD_CACHE_WORD" ]]; then
|
|
14
|
+
return $LACY_CMD_CACHE_RESULT
|
|
15
|
+
fi
|
|
16
|
+
LACY_CMD_CACHE_WORD="$word"
|
|
17
|
+
if command -v "$word" &>/dev/null; then
|
|
18
|
+
LACY_CMD_CACHE_RESULT=0
|
|
19
|
+
else
|
|
20
|
+
LACY_CMD_CACHE_RESULT=1
|
|
21
|
+
fi
|
|
22
|
+
return $LACY_CMD_CACHE_RESULT
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Check if input starting with a valid command has natural language markers.
|
|
26
|
+
# Returns 0 (true) if at least one bare word after the first word is a strong
|
|
27
|
+
# NL marker. Used to flag reroute candidates — the reroute only fires when
|
|
28
|
+
# the command also fails, so this can be fairly aggressive.
|
|
29
|
+
lacy_shell_has_nl_markers() {
|
|
30
|
+
local input="$1"
|
|
31
|
+
|
|
32
|
+
# Bail if single word (no spaces)
|
|
33
|
+
[[ "$input" != *" "* ]] && return 1
|
|
34
|
+
|
|
35
|
+
# Bail if input contains shell operators — clearly shell syntax
|
|
36
|
+
local op
|
|
37
|
+
for op in "${LACY_SHELL_OPERATORS[@]}"; do
|
|
38
|
+
[[ "$input" == *"$op"* ]] && return 1
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
# Extract tokens after the first word
|
|
42
|
+
local rest="${input#* }"
|
|
43
|
+
local -a tokens
|
|
44
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
45
|
+
tokens=( ${=rest} )
|
|
46
|
+
else
|
|
47
|
+
# Bash: IFS word splitting
|
|
48
|
+
read -ra tokens <<< "$rest"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Filter to bare words only (skip flags, paths, numbers, variables)
|
|
52
|
+
local -a bare_words=()
|
|
53
|
+
local token lower_token
|
|
54
|
+
for token in "${tokens[@]}"; do
|
|
55
|
+
# Skip flags (-x, --flag)
|
|
56
|
+
[[ "$token" == -* ]] && continue
|
|
57
|
+
# Skip paths (/foo, ./bar, ~/dir)
|
|
58
|
+
[[ "$token" == /* || "$token" == ./* || "$token" == ~/* ]] && continue
|
|
59
|
+
# Skip pure numbers
|
|
60
|
+
[[ "$token" =~ ^[0-9]+$ ]] && continue
|
|
61
|
+
# Skip variables ($VAR, ${VAR})
|
|
62
|
+
[[ "$token" == \$* ]] && continue
|
|
63
|
+
lower_token=$(_lacy_lowercase "$token")
|
|
64
|
+
bare_words+=( "$lower_token" )
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
# Need at least 1 bare word after the first word
|
|
68
|
+
(( ${#bare_words[@]} < 1 )) && return 1
|
|
69
|
+
|
|
70
|
+
# Check for strong NL markers
|
|
71
|
+
local word marker
|
|
72
|
+
for word in "${bare_words[@]}"; do
|
|
73
|
+
for marker in "${LACY_NL_MARKERS[@]}"; do
|
|
74
|
+
[[ "$word" == "$marker" ]] && return 0
|
|
75
|
+
done
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Canonical detection function. Prints "neutral", "shell", or "agent".
|
|
82
|
+
# All detection flows (indicator, execution) must go through this function.
|
|
83
|
+
lacy_shell_classify_input() {
|
|
84
|
+
local input="$1"
|
|
85
|
+
|
|
86
|
+
# Trim leading whitespace (POSIX-compatible, no extendedglob)
|
|
87
|
+
input="${input#"${input%%[^[:space:]]*}"}"
|
|
88
|
+
# Trim trailing whitespace
|
|
89
|
+
input="${input%"${input##*[^[:space:]]}"}"
|
|
90
|
+
|
|
91
|
+
# Empty input - show mode color in shell/agent, neutral in auto
|
|
92
|
+
if [[ -z "$input" ]]; then
|
|
93
|
+
case "$LACY_SHELL_CURRENT_MODE" in
|
|
94
|
+
"shell") echo "shell" ;;
|
|
95
|
+
"agent") echo "agent" ;;
|
|
96
|
+
*) echo "neutral" ;;
|
|
97
|
+
esac
|
|
98
|
+
return
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Emergency bypass prefix (!) = shell
|
|
102
|
+
if [[ "$input" == !* ]]; then
|
|
103
|
+
echo "shell"
|
|
104
|
+
return
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Agent bypass prefix (@) = agent
|
|
108
|
+
if [[ "$input" == @* ]]; then
|
|
109
|
+
echo "agent"
|
|
110
|
+
return
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# In shell mode, everything goes to shell
|
|
114
|
+
if [[ "$LACY_SHELL_CURRENT_MODE" == "shell" ]]; then
|
|
115
|
+
echo "shell"
|
|
116
|
+
return
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# In agent mode, everything goes to agent
|
|
120
|
+
if [[ "$LACY_SHELL_CURRENT_MODE" == "agent" ]]; then
|
|
121
|
+
echo "agent"
|
|
122
|
+
return
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Auto mode: check special cases and commands
|
|
126
|
+
# Extract first token respecting:
|
|
127
|
+
# - backslash-escaped spaces: /path/to/Google\ Chrome
|
|
128
|
+
# - double-quoted paths: "/Applications/Google Chrome.app/..."
|
|
129
|
+
# - single-quoted paths: '/Applications/Google Chrome.app/...'
|
|
130
|
+
local first_word first_word_cmd
|
|
131
|
+
if [[ "$input" == \"* ]]; then
|
|
132
|
+
# Double-quoted first token: extract up to closing quote
|
|
133
|
+
local _after="${input#\"}"
|
|
134
|
+
first_word="\"${_after%%\"*}\""
|
|
135
|
+
# Strip quotes for command -v lookup
|
|
136
|
+
first_word_cmd="${_after%%\"*}"
|
|
137
|
+
elif [[ "$input" == \'* ]]; then
|
|
138
|
+
# Single-quoted first token: extract up to closing quote
|
|
139
|
+
local _after="${input#\'}"
|
|
140
|
+
first_word="'${_after%%\'*}'"
|
|
141
|
+
first_word_cmd="${_after%%\'*}"
|
|
142
|
+
else
|
|
143
|
+
# Backslash-escaped spaces: use a variable for the placeholder so that
|
|
144
|
+
# $'\x01' is processed by ANSI-C quoting at assignment time. In ZSH,
|
|
145
|
+
# $'\x01' inside ${var//pattern/replacement} is NOT expanded — it is
|
|
146
|
+
# treated as the literal 6-char string $'\x01', breaking the round-trip.
|
|
147
|
+
local _lacy_bsp=$'\x01'
|
|
148
|
+
local _esc_input="${input//\\ /$_lacy_bsp}"
|
|
149
|
+
first_word="${_esc_input%% *}"
|
|
150
|
+
first_word="${first_word//$_lacy_bsp/\\ }"
|
|
151
|
+
# Un-escaped version for command -v lookups (backslash-space → space)
|
|
152
|
+
first_word_cmd="${first_word//\\ / }"
|
|
153
|
+
fi
|
|
154
|
+
local first_word_lower
|
|
155
|
+
first_word_lower=$(_lacy_lowercase "$first_word_cmd")
|
|
156
|
+
|
|
157
|
+
# Strip trailing punctuation for word-list lookups (e.g., "why?" → "why")
|
|
158
|
+
local first_word_stripped="$first_word_lower"
|
|
159
|
+
while [[ -n "$first_word_stripped" && "$first_word_stripped" == *[?.,\;:!] ]]; do
|
|
160
|
+
first_word_stripped="${first_word_stripped%?}"
|
|
161
|
+
done
|
|
162
|
+
|
|
163
|
+
# Layer 1a: Shell reserved words pass `command -v` but are never valid
|
|
164
|
+
# standalone commands. Route to agent. (see docs/NATURAL_LANGUAGE_DETECTION.md)
|
|
165
|
+
if _lacy_in_list "$first_word_stripped" "${LACY_SHELL_RESERVED_WORDS[@]}"; then
|
|
166
|
+
echo "agent"
|
|
167
|
+
return
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Layer 1b: Common English words almost always route to agent.
|
|
171
|
+
# Exception: if the word is also a valid shell command AND the arguments
|
|
172
|
+
# look like shell syntax, defer to shell. Heuristic is conservative:
|
|
173
|
+
# only shell when operators are present OR there is at most one bare word
|
|
174
|
+
# argument (after flags/paths/numbers) that is not an NL marker.
|
|
175
|
+
# Examples: `which python` → shell, `yes | cmd` → shell
|
|
176
|
+
# `which version to use` → agent, `yes lets go` → agent
|
|
177
|
+
if _lacy_in_list "$first_word_stripped" "${LACY_AGENT_WORDS[@]}"; then
|
|
178
|
+
if lacy_shell_is_valid_command "$first_word_cmd"; then
|
|
179
|
+
# Shell operators anywhere → shell
|
|
180
|
+
local _op
|
|
181
|
+
for _op in "${LACY_SHELL_OPERATORS[@]}"; do
|
|
182
|
+
[[ "$input" == *"$_op"* ]] && { echo "shell"; return; }
|
|
183
|
+
done
|
|
184
|
+
# Count bare words (non-flag, non-path, non-number, non-variable)
|
|
185
|
+
if [[ "$input" == *" "* ]]; then
|
|
186
|
+
local _rest="${input#* }"
|
|
187
|
+
local -a _tokens
|
|
188
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
189
|
+
_tokens=( ${=_rest} )
|
|
190
|
+
else
|
|
191
|
+
read -ra _tokens <<< "$_rest"
|
|
192
|
+
fi
|
|
193
|
+
local -a _bare=()
|
|
194
|
+
local _tok _ltok
|
|
195
|
+
for _tok in "${_tokens[@]}"; do
|
|
196
|
+
[[ "$_tok" == -* ]] && continue
|
|
197
|
+
[[ "$_tok" == /* || "$_tok" == ./* || "$_tok" == ~/* ]] && continue
|
|
198
|
+
[[ "$_tok" =~ ^[0-9]+$ ]] && continue
|
|
199
|
+
[[ "$_tok" == \$* ]] && continue
|
|
200
|
+
_ltok=$(_lacy_lowercase "$_tok")
|
|
201
|
+
_bare+=( "$_ltok" )
|
|
202
|
+
done
|
|
203
|
+
# 0 bare words (flags only) → shell
|
|
204
|
+
if (( ${#_bare[@]} == 0 )); then
|
|
205
|
+
echo "shell"
|
|
206
|
+
return
|
|
207
|
+
fi
|
|
208
|
+
# Exactly 1 bare word that is not an NL marker → shell
|
|
209
|
+
if (( ${#_bare[@]} == 1 )); then
|
|
210
|
+
if ! _lacy_in_list "${_bare[${_LACY_ARR_OFFSET}]}" "${LACY_NL_MARKERS[@]}"; then
|
|
211
|
+
echo "shell"
|
|
212
|
+
return
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
# 2+ bare words, or the single bare word is an NL marker → agent
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
echo "agent"
|
|
219
|
+
return
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
# Inline env var assignment: VAR=value command args
|
|
223
|
+
# Skip past any VAR=value prefixes to find the actual command
|
|
224
|
+
if [[ "$first_word" == *=* ]]; then
|
|
225
|
+
local -a _words
|
|
226
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
227
|
+
_words=( ${=input} )
|
|
228
|
+
else
|
|
229
|
+
read -ra _words <<< "$input"
|
|
230
|
+
fi
|
|
231
|
+
local _w
|
|
232
|
+
for _w in "${_words[@]}"; do
|
|
233
|
+
if [[ "$_w" == *=* ]]; then
|
|
234
|
+
continue
|
|
235
|
+
fi
|
|
236
|
+
# Found the actual command after env var(s)
|
|
237
|
+
if lacy_shell_is_valid_command "$_w"; then
|
|
238
|
+
echo "shell"
|
|
239
|
+
return
|
|
240
|
+
fi
|
|
241
|
+
break
|
|
242
|
+
done
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Check if it's a valid command (cached)
|
|
246
|
+
if lacy_shell_is_valid_command "$first_word_cmd"; then
|
|
247
|
+
echo "shell"
|
|
248
|
+
return
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
# Single word that's not a command = probably a typo -> shell
|
|
252
|
+
# Multiple words with non-command first word = natural language -> agent
|
|
253
|
+
# Check if there's anything after the first token
|
|
254
|
+
local _rest_after_first="${input#"$first_word"}"
|
|
255
|
+
_rest_after_first="${_rest_after_first#"${_rest_after_first%%[^[:space:]]*}"}"
|
|
256
|
+
if [[ -z "$_rest_after_first" ]]; then
|
|
257
|
+
echo "shell"
|
|
258
|
+
else
|
|
259
|
+
echo "agent"
|
|
260
|
+
fi
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# Backward-compatible wrapper: returns 0 (agent) or 1 (shell/neutral)
|
|
264
|
+
lacy_shell_should_use_agent() {
|
|
265
|
+
local result
|
|
266
|
+
result=$(lacy_shell_classify_input "$1")
|
|
267
|
+
if [[ "$result" == "agent" ]]; then
|
|
268
|
+
return 0
|
|
269
|
+
else
|
|
270
|
+
return 1
|
|
271
|
+
fi
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# Initialize detection cache (call at startup)
|
|
275
|
+
lacy_shell_init_detection_cache() {
|
|
276
|
+
LACY_CMD_CACHE_WORD=""
|
|
277
|
+
LACY_CMD_CACHE_RESULT=""
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Layer 2: Post-execution natural language detection.
|
|
281
|
+
# Analyzes a failed shell command's output to determine if the user
|
|
282
|
+
# typed natural language. Returns 0 (true) if NL detected, 1 otherwise.
|
|
283
|
+
# See docs/NATURAL_LANGUAGE_DETECTION.md for the full algorithm.
|
|
284
|
+
#
|
|
285
|
+
# Usage: lacy_shell_detect_natural_language "input" "output" exit_code
|
|
286
|
+
lacy_shell_detect_natural_language() {
|
|
287
|
+
local input="$1"
|
|
288
|
+
local output="$2"
|
|
289
|
+
local exit_code="$3"
|
|
290
|
+
|
|
291
|
+
# Only check failed commands
|
|
292
|
+
(( exit_code == 0 )) && return 1
|
|
293
|
+
[[ -z "$exit_code" ]] && return 1
|
|
294
|
+
|
|
295
|
+
# Count words
|
|
296
|
+
local -a words
|
|
297
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
298
|
+
words=( ${=input} )
|
|
299
|
+
else
|
|
300
|
+
read -ra words <<< "$input"
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
# Single-word inputs are probably real commands
|
|
304
|
+
(( ${#words[@]} < 2 )) && return 1
|
|
305
|
+
|
|
306
|
+
# Criterion A: output must match at least one error pattern (case-insensitive)
|
|
307
|
+
local output_lower
|
|
308
|
+
output_lower=$(_lacy_lowercase "$output")
|
|
309
|
+
local pattern pattern_lower matched=false
|
|
310
|
+
for pattern in "${LACY_SHELL_ERROR_PATTERNS[@]}"; do
|
|
311
|
+
pattern_lower=$(_lacy_lowercase "$pattern")
|
|
312
|
+
if [[ "$output_lower" == *"$pattern_lower"* ]]; then
|
|
313
|
+
matched=true
|
|
314
|
+
break
|
|
315
|
+
fi
|
|
316
|
+
done
|
|
317
|
+
[[ "$matched" == false ]] && return 1
|
|
318
|
+
|
|
319
|
+
# Criterion B: check for natural language signal
|
|
320
|
+
local second_word
|
|
321
|
+
second_word=$(_lacy_lowercase "${words[$_LACY_ARR_OFFSET + 1]}")
|
|
322
|
+
|
|
323
|
+
# B1: second word is a natural language marker
|
|
324
|
+
if [[ -n "$second_word" ]] && _lacy_in_list "$second_word" "${LACY_NL_MARKERS[@]}"; then
|
|
325
|
+
return 0
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# B2: 4+ words and a parse/syntax error
|
|
329
|
+
if (( ${#words[@]} >= 4 )); then
|
|
330
|
+
if [[ "$output_lower" == *"parse error"* || "$output_lower" == *"syntax error"* || "$output_lower" == *"unexpected token"* ]]; then
|
|
331
|
+
return 0
|
|
332
|
+
fi
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
return 1
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
# Test the detection logic (for debugging)
|
|
339
|
+
lacy_shell_test_detection() {
|
|
340
|
+
local test_cases=(
|
|
341
|
+
"ls -la"
|
|
342
|
+
"what files are in this directory?"
|
|
343
|
+
"git status"
|
|
344
|
+
"cd /home/user"
|
|
345
|
+
"npm install"
|
|
346
|
+
"rm file.txt"
|
|
347
|
+
"pwd"
|
|
348
|
+
"./run.sh"
|
|
349
|
+
"what is the meaning of life?"
|
|
350
|
+
"hello there"
|
|
351
|
+
"nonexistent_command foo"
|
|
352
|
+
" ls -la"
|
|
353
|
+
" what files"
|
|
354
|
+
" !rm /tmp/test"
|
|
355
|
+
"yes lets go"
|
|
356
|
+
"no I dont want that"
|
|
357
|
+
"yes"
|
|
358
|
+
"RUST_LOG=debug cargo run"
|
|
359
|
+
"FOO=bar BAZ=qux node index.js"
|
|
360
|
+
"CC=gcc make -j4"
|
|
361
|
+
# Agent words that are also valid commands — should use heuristics
|
|
362
|
+
"which python"
|
|
363
|
+
"which -a git"
|
|
364
|
+
"which version should I install"
|
|
365
|
+
"which"
|
|
366
|
+
"yes | apt-get install -y"
|
|
367
|
+
"nice -n 10 make"
|
|
368
|
+
"nice work"
|
|
369
|
+
"who"
|
|
370
|
+
"who root"
|
|
371
|
+
"who am I"
|
|
372
|
+
# Backslash-escaped spaces in paths
|
|
373
|
+
"/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222"
|
|
374
|
+
"./my\\ script.sh --flag"
|
|
375
|
+
# Quoted paths with spaces
|
|
376
|
+
'"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222'
|
|
377
|
+
"'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' --flag"
|
|
378
|
+
'"/usr/local/bin/my tool"'
|
|
379
|
+
# @ agent bypass
|
|
380
|
+
"@ make sure the tests pass"
|
|
381
|
+
"@fix the bug in auth"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
echo "Testing auto-detection logic:"
|
|
385
|
+
echo "============================="
|
|
386
|
+
|
|
387
|
+
local test_case result
|
|
388
|
+
for test_case in "${test_cases[@]}"; do
|
|
389
|
+
result=$(lacy_shell_classify_input "$test_case")
|
|
390
|
+
printf "%-40s -> %s\n" "$test_case" "$result"
|
|
391
|
+
done
|
|
392
|
+
|
|
393
|
+
echo ""
|
|
394
|
+
echo "Testing NL marker detection:"
|
|
395
|
+
echo "============================="
|
|
396
|
+
|
|
397
|
+
local nl_tests=(
|
|
398
|
+
"kill the process on localhost:3000"
|
|
399
|
+
"kill -9 my baby"
|
|
400
|
+
"kill -9 my baby girl"
|
|
401
|
+
"kill -9"
|
|
402
|
+
"echo the quick brown fox"
|
|
403
|
+
"echo hello | grep the"
|
|
404
|
+
"find my large files"
|
|
405
|
+
"make the tests pass"
|
|
406
|
+
"git push origin main"
|
|
407
|
+
"docker run -it ubuntu"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
for test_case in "${nl_tests[@]}"; do
|
|
411
|
+
if lacy_shell_has_nl_markers "$test_case"; then
|
|
412
|
+
printf "%-40s -> nl_markers: YES\n" "$test_case"
|
|
413
|
+
else
|
|
414
|
+
printf "%-40s -> nl_markers: NO\n" "$test_case"
|
|
415
|
+
fi
|
|
416
|
+
done
|
|
417
|
+
}
|