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,174 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Spinner with shimmer text effect for Lacy Shell
|
|
4
|
+
# Displays a braille spinner + "Thinking" with a sweeping brightness pulse
|
|
5
|
+
# Shared across Bash 4+ and ZSH
|
|
6
|
+
|
|
7
|
+
LACY_SPINNER_PID=""
|
|
8
|
+
LACY_SPINNER_MONITOR_WAS_SET=""
|
|
9
|
+
LACY_SPINNER_NOTIFY_WAS_SET=""
|
|
10
|
+
|
|
11
|
+
lacy_start_spinner() {
|
|
12
|
+
# Signal to Bash interrupt handler that an agent/spinner is active.
|
|
13
|
+
# In ZSH this is unnecessary — TRAPINT checks $ZLE_STATE instead.
|
|
14
|
+
LACY_SHELL_AGENT_RUNNING=true
|
|
15
|
+
|
|
16
|
+
# Guard against double-start
|
|
17
|
+
if [[ -n "$LACY_SPINNER_PID" ]]; then
|
|
18
|
+
lacy_stop_spinner
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Save and suppress job control messages (must persist until lacy_stop_spinner)
|
|
22
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
23
|
+
if [[ -o monitor ]]; then
|
|
24
|
+
LACY_SPINNER_MONITOR_WAS_SET=1
|
|
25
|
+
setopt NO_MONITOR
|
|
26
|
+
else
|
|
27
|
+
LACY_SPINNER_MONITOR_WAS_SET=""
|
|
28
|
+
fi
|
|
29
|
+
if [[ -o notify ]]; then
|
|
30
|
+
LACY_SPINNER_NOTIFY_WAS_SET=1
|
|
31
|
+
setopt NO_NOTIFY
|
|
32
|
+
else
|
|
33
|
+
LACY_SPINNER_NOTIFY_WAS_SET=""
|
|
34
|
+
fi
|
|
35
|
+
else
|
|
36
|
+
# Bash
|
|
37
|
+
LACY_SPINNER_MONITOR_WAS_SET=""
|
|
38
|
+
case "$-" in *m*) LACY_SPINNER_MONITOR_WAS_SET=1 ;; esac
|
|
39
|
+
set +m 2>/dev/null
|
|
40
|
+
LACY_SPINNER_NOTIFY_WAS_SET=""
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Initialize animation frames (global array, inherited by forked subshell)
|
|
44
|
+
lacy_set_spinner_animation "${LACY_SPINNER_STYLE:-braille}"
|
|
45
|
+
# Guard: if animation failed to load, use hardcoded fallback
|
|
46
|
+
if [[ ${#LACY_SPINNER_ANIM[@]} -eq 0 ]]; then
|
|
47
|
+
LACY_SPINNER_ANIM=("⠋⠀⠀⠀" "⠙⠀⠀⠀" "⠹⠀⠀⠀" "⠸⠀⠀⠀" "⠼⠀⠀⠀" "⠴⠀⠀⠀" "⠦⠀⠀⠀" "⠧⠀⠀⠀" "⠇⠀⠀⠀" "⠏⠀⠀⠀")
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
trap 'printf "\e[?25h" >&2' EXIT
|
|
52
|
+
trap 'exit 0' TERM INT HUP
|
|
53
|
+
|
|
54
|
+
# Hide cursor
|
|
55
|
+
printf '\e[?25l' >&2
|
|
56
|
+
|
|
57
|
+
local num_frames=${#LACY_SPINNER_ANIM[@]}
|
|
58
|
+
|
|
59
|
+
local text="$LACY_SPINNER_TEXT"
|
|
60
|
+
local text_len=${#text}
|
|
61
|
+
local frame_num=0
|
|
62
|
+
local -a shimmer_colors=("${LACY_COLOR_SHIMMER[@]}")
|
|
63
|
+
local num_colors=${#shimmer_colors[@]}
|
|
64
|
+
local arr_off=${_LACY_ARR_OFFSET:-0}
|
|
65
|
+
local spinner_idx spinner_char shimmer center i char dist color_idx color dots_phase dots
|
|
66
|
+
|
|
67
|
+
while true; do
|
|
68
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
69
|
+
spinner_idx=$(( (frame_num % num_frames) + 1 ))
|
|
70
|
+
else
|
|
71
|
+
spinner_idx=$(( frame_num % num_frames ))
|
|
72
|
+
fi
|
|
73
|
+
spinner_char="${LACY_SPINNER_ANIM[$spinner_idx]}"
|
|
74
|
+
|
|
75
|
+
# Build shimmer text
|
|
76
|
+
shimmer=""
|
|
77
|
+
center=$(( frame_num % (text_len + 4) ))
|
|
78
|
+
|
|
79
|
+
for (( i = 0; i < text_len; i++ )); do
|
|
80
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
81
|
+
char="${text[$((i + 1))]}"
|
|
82
|
+
else
|
|
83
|
+
char="${text:$i:1}"
|
|
84
|
+
fi
|
|
85
|
+
dist=$(( center - i ))
|
|
86
|
+
if (( dist < 0 )); then
|
|
87
|
+
dist=$(( -dist ))
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
if (( dist < num_colors )); then
|
|
91
|
+
color_idx=$(( dist + arr_off ))
|
|
92
|
+
color=${shimmer_colors[$color_idx]}
|
|
93
|
+
else
|
|
94
|
+
color_idx=$(( num_colors - 1 + arr_off ))
|
|
95
|
+
color=${shimmer_colors[$color_idx]}
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
shimmer+="\e[38;5;${color}m${char}"
|
|
99
|
+
done
|
|
100
|
+
shimmer+="\e[0m"
|
|
101
|
+
|
|
102
|
+
# Cycling dots (change every 3 frames)
|
|
103
|
+
dots_phase=$(( (frame_num / 3) % 4 ))
|
|
104
|
+
dots=""
|
|
105
|
+
case $dots_phase in
|
|
106
|
+
0) dots="" ;;
|
|
107
|
+
1) dots="." ;;
|
|
108
|
+
2) dots=".." ;;
|
|
109
|
+
3) dots="..." ;;
|
|
110
|
+
esac
|
|
111
|
+
|
|
112
|
+
# Render: clear line, carriage return, draw
|
|
113
|
+
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" \
|
|
114
|
+
"$spinner_char" "$shimmer" "$dots" >&2
|
|
115
|
+
|
|
116
|
+
frame_num=$(( frame_num + 1 ))
|
|
117
|
+
|
|
118
|
+
sleep "$LACY_SPINNER_FRAME_DELAY"
|
|
119
|
+
done
|
|
120
|
+
} &
|
|
121
|
+
|
|
122
|
+
LACY_SPINNER_PID=$!
|
|
123
|
+
|
|
124
|
+
# In Bash, disown the spinner so it won't print "[N] Done ..." with
|
|
125
|
+
# the entire subshell body when the job exits.
|
|
126
|
+
if [[ "$LACY_SHELL_TYPE" != "zsh" ]]; then
|
|
127
|
+
disown "$LACY_SPINNER_PID" 2>/dev/null
|
|
128
|
+
fi
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
lacy_stop_spinner() {
|
|
132
|
+
# Unconditional terminal state restore
|
|
133
|
+
printf '\e[?25h' >&2 # Cursor visible
|
|
134
|
+
printf '\e[?7h' >&2 # Line wrapping enabled
|
|
135
|
+
|
|
136
|
+
if [[ -z "$LACY_SPINNER_PID" ]]; then
|
|
137
|
+
_lacy_spinner_restore_jobctl
|
|
138
|
+
return
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Kill if still running
|
|
142
|
+
if kill -0 "$LACY_SPINNER_PID" 2>/dev/null; then
|
|
143
|
+
kill "$LACY_SPINNER_PID" 2>/dev/null
|
|
144
|
+
# In ZSH we can wait; in Bash the process is disowned so just sleep
|
|
145
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
146
|
+
wait "$LACY_SPINNER_PID" 2>/dev/null
|
|
147
|
+
fi
|
|
148
|
+
sleep "$LACY_TERMINAL_FLUSH_DELAY"
|
|
149
|
+
printf '\e[2K\r' >&2
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
LACY_SPINNER_PID=""
|
|
153
|
+
LACY_SHELL_AGENT_RUNNING=false
|
|
154
|
+
|
|
155
|
+
_lacy_spinner_restore_jobctl
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Restore job control after spinner
|
|
159
|
+
_lacy_spinner_restore_jobctl() {
|
|
160
|
+
if [[ -n "$LACY_SPINNER_MONITOR_WAS_SET" ]]; then
|
|
161
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
162
|
+
setopt MONITOR
|
|
163
|
+
else
|
|
164
|
+
set -m 2>/dev/null
|
|
165
|
+
fi
|
|
166
|
+
LACY_SPINNER_MONITOR_WAS_SET=""
|
|
167
|
+
fi
|
|
168
|
+
if [[ -n "$LACY_SPINNER_NOTIFY_WAS_SET" ]]; then
|
|
169
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
170
|
+
setopt NOTIFY
|
|
171
|
+
fi
|
|
172
|
+
LACY_SPINNER_NOTIFY_WAS_SET=""
|
|
173
|
+
fi
|
|
174
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Lacy Shell Telemetry — lightweight, anonymous usage tracking via Umami
|
|
4
|
+
# No PII collected. Respects DO_NOT_TRACK and LACY_NO_TELEMETRY.
|
|
5
|
+
# See: https://umami.is
|
|
6
|
+
|
|
7
|
+
readonly _LACY_UMAMI_URL="${LACY_UMAMI_URL:-https://analytics.lacy.sh}"
|
|
8
|
+
readonly _LACY_UMAMI_WEBSITE_ID="${LACY_UMAMI_WEBSITE_ID:-577521d7-3db7-4a77-a45c-3c97f21b5322}"
|
|
9
|
+
readonly _LACY_TELEMETRY_FLAG="${LACY_SHELL_HOME}/.telemetry_sent"
|
|
10
|
+
|
|
11
|
+
# _lacy_json_escape_str is defined in constants.sh (shared helper).
|
|
12
|
+
# Guard: define here only if not already available (e.g., standalone sourcing).
|
|
13
|
+
if ! command -v _lacy_json_escape_str >/dev/null 2>&1; then
|
|
14
|
+
_lacy_json_escape_str() {
|
|
15
|
+
local s="$1"
|
|
16
|
+
s="${s//\\/\\\\}"; s="${s//\"/\\\"}"; s="${s//$'\n'/\\n}"; s="${s//$'\r'/\\r}"; s="${s//$'\t'/\\t}"
|
|
17
|
+
printf '%s' "$s"
|
|
18
|
+
}
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Send a tracking event to Umami (background, fail-silent)
|
|
22
|
+
_lacy_track_event() {
|
|
23
|
+
[[ "${DO_NOT_TRACK:-}" == "1" ]] && return
|
|
24
|
+
[[ "${LACY_NO_TELEMETRY:-}" == "1" ]] && return
|
|
25
|
+
|
|
26
|
+
local event_name="${1:-unknown}"
|
|
27
|
+
local method="${2:-unknown}"
|
|
28
|
+
local version=""
|
|
29
|
+
|
|
30
|
+
# Read version from package.json
|
|
31
|
+
local pkg_file="${LACY_SHELL_HOME}/package.json"
|
|
32
|
+
if [[ -f "$pkg_file" ]]; then
|
|
33
|
+
version=$(grep '"version"' "$pkg_file" 2>/dev/null | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//' | sed 's/".*//')
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
local os_name
|
|
37
|
+
os_name="$(uname -s 2>/dev/null || echo unknown)"
|
|
38
|
+
local arch
|
|
39
|
+
arch="$(uname -m 2>/dev/null || echo unknown)"
|
|
40
|
+
|
|
41
|
+
# Escape all values for safe JSON interpolation
|
|
42
|
+
event_name=$(_lacy_json_escape_str "$event_name")
|
|
43
|
+
method=$(_lacy_json_escape_str "$method")
|
|
44
|
+
version=$(_lacy_json_escape_str "${version:-unknown}")
|
|
45
|
+
os_name=$(_lacy_json_escape_str "$os_name")
|
|
46
|
+
arch=$(_lacy_json_escape_str "$arch")
|
|
47
|
+
local shell_type
|
|
48
|
+
shell_type=$(_lacy_json_escape_str "${LACY_SHELL_TYPE:-unknown}")
|
|
49
|
+
local website_id
|
|
50
|
+
website_id=$(_lacy_json_escape_str "$_LACY_UMAMI_WEBSITE_ID")
|
|
51
|
+
|
|
52
|
+
(curl -sf --connect-timeout 3 --max-time 5 -X POST "${_LACY_UMAMI_URL}/api/send" \
|
|
53
|
+
-H "Content-Type: application/json" \
|
|
54
|
+
-H "User-Agent: lacy/${version}" \
|
|
55
|
+
-d "{
|
|
56
|
+
\"type\": \"event\",
|
|
57
|
+
\"payload\": {
|
|
58
|
+
\"hostname\": \"lacy.sh\",
|
|
59
|
+
\"language\": \"\",
|
|
60
|
+
\"referrer\": \"\",
|
|
61
|
+
\"screen\": \"\",
|
|
62
|
+
\"title\": \"Install\",
|
|
63
|
+
\"url\": \"/install/${method}\",
|
|
64
|
+
\"website\": \"${website_id}\",
|
|
65
|
+
\"name\": \"${event_name}\",
|
|
66
|
+
\"data\": {
|
|
67
|
+
\"method\": \"${method}\",
|
|
68
|
+
\"os\": \"${os_name}\",
|
|
69
|
+
\"arch\": \"${arch}\",
|
|
70
|
+
\"shell\": \"${shell_type}\",
|
|
71
|
+
\"version\": \"${version}\"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}" >/dev/null 2>&1 &)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# One-time first-load tracking — detects install method and fires once
|
|
78
|
+
_lacy_track_first_load() {
|
|
79
|
+
[[ "${DO_NOT_TRACK:-}" == "1" ]] && return
|
|
80
|
+
[[ "${LACY_NO_TELEMETRY:-}" == "1" ]] && return
|
|
81
|
+
[[ -f "$_LACY_TELEMETRY_FLAG" ]] && return
|
|
82
|
+
|
|
83
|
+
# Detect install method
|
|
84
|
+
local method="unknown"
|
|
85
|
+
if [[ -L "$LACY_SHELL_HOME" ]]; then
|
|
86
|
+
local link_target
|
|
87
|
+
link_target=$(readlink "$LACY_SHELL_HOME" 2>/dev/null || true)
|
|
88
|
+
if [[ "$link_target" == *"/Cellar/"* || "$link_target" == *"/homebrew/"* ]]; then
|
|
89
|
+
method="brew"
|
|
90
|
+
fi
|
|
91
|
+
elif [[ -d "$LACY_SHELL_HOME/.git" ]]; then
|
|
92
|
+
method="git"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Create flag file before sending (avoid duplicate sends)
|
|
96
|
+
touch "$_LACY_TELEMETRY_FLAG" 2>/dev/null
|
|
97
|
+
|
|
98
|
+
_lacy_track_event "first_load" "$method"
|
|
99
|
+
}
|
package/lib/execute.zsh
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Lacy Shell — Fish configuration loader
|
|
2
|
+
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Defaults
|
|
5
|
+
# ============================================================================
|
|
6
|
+
|
|
7
|
+
set -g LACY_SHELL_MODE auto # shell | agent | auto
|
|
8
|
+
set -g LACY_ACTIVE_TOOL "" # empty = auto-detect
|
|
9
|
+
set -g LACY_CUSTOM_TOOL_CMD ""
|
|
10
|
+
set -g LACY_TOOL_LIST lash claude opencode gemini codex
|
|
11
|
+
|
|
12
|
+
# Colors (256-color indices)
|
|
13
|
+
set -g LACY_COLOR_SHELL 34 # green
|
|
14
|
+
set -g LACY_COLOR_AGENT 200 # magenta
|
|
15
|
+
set -g LACY_COLOR_AUTO 75 # blue
|
|
16
|
+
set -g LACY_COLOR_NEUTRAL 238 # dark gray
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# YAML config parser (reads ~/.lacy/config.yaml)
|
|
20
|
+
# ============================================================================
|
|
21
|
+
|
|
22
|
+
function _lacy_yaml_value --description "Read a key from ~/.lacy/config.yaml"
|
|
23
|
+
set -l file "$LACY_SHELL_HOME/config.yaml"
|
|
24
|
+
set -l key $argv[1]
|
|
25
|
+
test -f "$file" || return 1
|
|
26
|
+
grep "^[[:space:]]*$key:" "$file" 2>/dev/null | head -1 \
|
|
27
|
+
| sed 's/^[^:]*:[[:space:]]*//' | tr -d '"' | tr -d "'"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
function _lacy_load_config --description "Load config.yaml into Fish globals"
|
|
31
|
+
set -l file "$LACY_SHELL_HOME/config.yaml"
|
|
32
|
+
test -f "$file" || return
|
|
33
|
+
|
|
34
|
+
# agent_tools section
|
|
35
|
+
set -l in_tools 0
|
|
36
|
+
while read -l line
|
|
37
|
+
if string match -qr '^agent_tools:' -- "$line"
|
|
38
|
+
set in_tools 1
|
|
39
|
+
continue
|
|
40
|
+
end
|
|
41
|
+
if test $in_tools -eq 1
|
|
42
|
+
if string match -qr '^\S' -- "$line"
|
|
43
|
+
set in_tools 0
|
|
44
|
+
end
|
|
45
|
+
set -l kv (string match -r '^\s+(\w+):\s*(.*)' -- "$line")
|
|
46
|
+
if set -q kv[1]
|
|
47
|
+
set -l k (string trim -- $kv[2])
|
|
48
|
+
set -l v (string trim -- $kv[3] | tr -d '"' | tr -d "'")
|
|
49
|
+
switch $k
|
|
50
|
+
case active
|
|
51
|
+
set -gx LACY_ACTIVE_TOOL $v
|
|
52
|
+
case custom_command
|
|
53
|
+
set -gx LACY_CUSTOM_TOOL_CMD $v
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end < "$file"
|
|
58
|
+
|
|
59
|
+
# modes section
|
|
60
|
+
set -l default_mode (_lacy_yaml_value "default")
|
|
61
|
+
if test -n "$default_mode"
|
|
62
|
+
set -g LACY_SHELL_MODE $default_mode
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
_lacy_load_config
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Lacy Shell — Fish input classification
|
|
2
|
+
#
|
|
3
|
+
# Ports the core detection logic from lib/core/detection.sh to Fish syntax.
|
|
4
|
+
# Returns: "shell" | "agent" | "neutral"
|
|
5
|
+
|
|
6
|
+
# Agent words: single words that always route to the AI agent.
|
|
7
|
+
# Kept in sync with lib/core/constants.sh LACY_AGENT_WORDS.
|
|
8
|
+
set -g LACY_AGENT_WORDS \
|
|
9
|
+
yes yeah yep yup sure ok okay alright \
|
|
10
|
+
absolutely definitely certainly indeed correct right exactly \
|
|
11
|
+
perfect agreed affirmative totally clearly obviously lgtm \
|
|
12
|
+
no nope nah never wrong disagree \
|
|
13
|
+
thanks thank thx ty cheers appreciated \
|
|
14
|
+
great good nice cool awesome amazing wonderful brilliant \
|
|
15
|
+
excellent fantastic sweet neat beautiful gorgeous impressive \
|
|
16
|
+
incredible outstanding superb marvelous magnificent stellar \
|
|
17
|
+
phenomenal terrific splendid fine solid dope sick fire lit rad legit \
|
|
18
|
+
hey hi hello howdy sup yo bye goodbye cya later \
|
|
19
|
+
please sorry pardon hmm huh wow whoa oops ugh yikes \
|
|
20
|
+
damn dang shoot welp well anyway anyways regardless \
|
|
21
|
+
meanwhile honestly basically literally actually really \
|
|
22
|
+
seriously obviously hopefully unfortunately apparently \
|
|
23
|
+
supposedly probably maybe perhaps possibly \
|
|
24
|
+
stop hold pause cancel abort skip continue proceed \
|
|
25
|
+
next again redo undo retry \
|
|
26
|
+
explain elaborate clarify summarize describe show tell \
|
|
27
|
+
why how what when where who which whom whose \
|
|
28
|
+
can could would should will shall may might must \
|
|
29
|
+
does did is are was were has have had \
|
|
30
|
+
refactor optimize scaffold
|
|
31
|
+
|
|
32
|
+
# Shell reserved words that pass `command -v` but are never standalone commands.
|
|
33
|
+
set -g LACY_SHELL_RESERVED_WORDS \
|
|
34
|
+
do done then else elif fi esac in select function
|
|
35
|
+
|
|
36
|
+
# Classify a line of input.
|
|
37
|
+
# Usage: set result (_lacy_classify_input "some input")
|
|
38
|
+
# Returns: shell | agent | neutral
|
|
39
|
+
function _lacy_classify_input --description "Classify input as shell/agent/neutral"
|
|
40
|
+
set -l input $argv[1]
|
|
41
|
+
|
|
42
|
+
# Empty input — return mode color signal
|
|
43
|
+
if test -z "$input"
|
|
44
|
+
switch $LACY_SHELL_MODE
|
|
45
|
+
case shell; echo shell; return
|
|
46
|
+
case agent; echo agent; return
|
|
47
|
+
case '*'; echo neutral; return
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Locked modes
|
|
52
|
+
switch $LACY_SHELL_MODE
|
|
53
|
+
case shell; echo shell; return
|
|
54
|
+
case agent; echo agent; return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Emergency bypass: leading !
|
|
58
|
+
if string match -qr '^\!' -- "$input"
|
|
59
|
+
echo shell; return
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Split on first word
|
|
63
|
+
set -l first_word (string split -m1 ' ' -- (string trim -- "$input") | head -1)
|
|
64
|
+
set -l word_count (string split ' ' -- (string trim -- "$input") | count)
|
|
65
|
+
|
|
66
|
+
set -l lower_first (string lower -- "$first_word")
|
|
67
|
+
|
|
68
|
+
# Agent word check (single words that always route to agent)
|
|
69
|
+
if contains -- $lower_first $LACY_AGENT_WORDS
|
|
70
|
+
echo agent; return
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Shell reserved words that look like NL
|
|
74
|
+
if contains -- $lower_first $LACY_SHELL_RESERVED_WORDS
|
|
75
|
+
echo agent; return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Valid command → shell
|
|
79
|
+
if command -q $first_word 2>/dev/null
|
|
80
|
+
echo shell; return
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Single unknown word → shell (let it fail naturally — typo)
|
|
84
|
+
if test $word_count -eq 1
|
|
85
|
+
echo shell; return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Multi-word, first word not a command → agent (natural language)
|
|
89
|
+
echo agent
|
|
90
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Lacy Shell — Fish execution routing
|
|
2
|
+
#
|
|
3
|
+
# Intercepts Enter to route input to either the shell or the AI agent.
|
|
4
|
+
|
|
5
|
+
# Resolve the active AI tool command string.
|
|
6
|
+
function _lacy_tool_cmd --description "Return the command template for a tool"
|
|
7
|
+
switch $argv[1]
|
|
8
|
+
case lash; echo "lash run -c"
|
|
9
|
+
case claude; echo "claude -p"
|
|
10
|
+
case opencode; echo "opencode run -c"
|
|
11
|
+
case gemini; echo "gemini -p"
|
|
12
|
+
case codex; echo "codex exec resume --last"
|
|
13
|
+
case '*'; echo ""
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Auto-detect the first installed tool from LACY_TOOL_LIST.
|
|
18
|
+
function _lacy_detect_tool --description "Return first installed AI tool"
|
|
19
|
+
if test -n "$LACY_ACTIVE_TOOL"; and test "$LACY_ACTIVE_TOOL" != auto
|
|
20
|
+
echo $LACY_ACTIVE_TOOL; return
|
|
21
|
+
end
|
|
22
|
+
for t in $LACY_TOOL_LIST
|
|
23
|
+
if command -q $t 2>/dev/null
|
|
24
|
+
echo $t; return
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
echo ""
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Send a query to the AI agent.
|
|
31
|
+
function _lacy_query_agent --description "Route a query to the AI agent"
|
|
32
|
+
set -l query $argv[1]
|
|
33
|
+
set -l tool (_lacy_detect_tool)
|
|
34
|
+
|
|
35
|
+
if test -z "$tool"
|
|
36
|
+
printf '\n\e[38;5;196m No AI tool detected.\e[0m Lacy needs an AI CLI to handle queries.\n\n'
|
|
37
|
+
printf '\e[1m Supported tools:\e[0m\n\n'
|
|
38
|
+
printf ' \e[38;5;34m%-12s\e[0m %s\n' "lash" "npm install -g lashcode (recommended)"
|
|
39
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "claude" "brew install claude"
|
|
40
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "opencode" "brew install opencode"
|
|
41
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "gemini" "brew install gemini"
|
|
42
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "codex" "npm install -g @openai/codex"
|
|
43
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "hermes" "curl -fsSL .../install.sh | bash"
|
|
44
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "copilot" "gh extension install github/gh-copilot"
|
|
45
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "goose" "brew install goose"
|
|
46
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "amp" "npm install -g @sourcegraph/amp"
|
|
47
|
+
printf ' \e[38;5;238m%-12s\e[0m %s\n' "aider" "pipx install aider-chat"
|
|
48
|
+
printf '\n \e[38;5;75mThen run:\e[0m lacy setup\n'
|
|
49
|
+
printf ' \e[38;5;75mDocs:\e[0m https://lacy.sh/docs\n\n'
|
|
50
|
+
return 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
set -l cmd_str (_lacy_tool_cmd $tool)
|
|
54
|
+
if test -z "$cmd_str"
|
|
55
|
+
echo "Error: unknown tool: $tool" >&2; return 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Log the query
|
|
59
|
+
_lacy_log_query $tool $query
|
|
60
|
+
|
|
61
|
+
printf '\n'
|
|
62
|
+
eval $cmd_str (string escape -- $query)
|
|
63
|
+
printf '\n'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Append a query to the query log.
|
|
67
|
+
function _lacy_log_query --description "Append query to ~/.lacy/logs/queries.log"
|
|
68
|
+
set -l tool $argv[1]
|
|
69
|
+
set -l query $argv[2]
|
|
70
|
+
set -l log_dir "$LACY_SHELL_HOME/logs"
|
|
71
|
+
set -l log_file "$log_dir/queries.log"
|
|
72
|
+
|
|
73
|
+
mkdir -p "$log_dir" 2>/dev/null
|
|
74
|
+
set -l ts (date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null; or echo unknown)
|
|
75
|
+
set -l escaped_query (string replace -a \n '\\n' -- "$query")
|
|
76
|
+
printf '%s\t%s\t%s\n' "$ts" "$tool" "$escaped_query" >> "$log_file" 2>/dev/null
|
|
77
|
+
|
|
78
|
+
# Rotate if over 1 MB
|
|
79
|
+
if test -f "$log_file"
|
|
80
|
+
set -l size (wc -c < "$log_file" 2>/dev/null; or echo 0)
|
|
81
|
+
if test $size -gt 1048576
|
|
82
|
+
set -l tmp (mktemp)
|
|
83
|
+
tail -n 1000 "$log_file" > $tmp; and mv $tmp "$log_file" 2>/dev/null
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Custom Enter binding — classify input and route accordingly.
|
|
89
|
+
function _lacy_accept_line --description "Classify and execute current commandline"
|
|
90
|
+
set -l buffer (commandline)
|
|
91
|
+
|
|
92
|
+
set -l route (_lacy_classify_input "$buffer")
|
|
93
|
+
|
|
94
|
+
switch $route
|
|
95
|
+
case agent
|
|
96
|
+
commandline -f repaint
|
|
97
|
+
set -l query $buffer
|
|
98
|
+
commandline ""
|
|
99
|
+
commandline -f execute
|
|
100
|
+
_lacy_query_agent $query
|
|
101
|
+
case '*'
|
|
102
|
+
# shell (or neutral) — normal execution
|
|
103
|
+
commandline -f execute
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Lacy Shell — Fish keybindings
|
|
2
|
+
#
|
|
3
|
+
# Registers key bindings after all other modules are loaded.
|
|
4
|
+
# Uses fish_user_key_bindings so it coexists with other plugins.
|
|
5
|
+
|
|
6
|
+
function _lacy_setup_bindings --description "Register Lacy keybindings"
|
|
7
|
+
# Override Enter to route through Lacy's classifier
|
|
8
|
+
bind \n _lacy_accept_line
|
|
9
|
+
bind \r _lacy_accept_line
|
|
10
|
+
|
|
11
|
+
# Ctrl+Space to cycle modes: auto → shell → agent → auto
|
|
12
|
+
bind \c@ _lacy_toggle_mode # \c@ = Ctrl+Space in many terminals
|
|
13
|
+
bind \e\[32~ _lacy_toggle_mode # fallback sequence for some terminals
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
function _lacy_toggle_mode --description "Cycle through shell/agent/auto modes"
|
|
17
|
+
switch $LACY_SHELL_MODE
|
|
18
|
+
case auto
|
|
19
|
+
set -g LACY_SHELL_MODE shell
|
|
20
|
+
printf '\n\e[38;5;34m ▌ SHELL mode\e[0m\n'
|
|
21
|
+
case shell
|
|
22
|
+
set -g LACY_SHELL_MODE agent
|
|
23
|
+
printf '\n\e[38;5;200m ▌ AGENT mode\e[0m\n'
|
|
24
|
+
case agent
|
|
25
|
+
set -g LACY_SHELL_MODE auto
|
|
26
|
+
printf '\n\e[38;5;75m ▌ AUTO mode\e[0m\n'
|
|
27
|
+
end
|
|
28
|
+
commandline -f repaint
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Register after user bindings so we don't clobber them unless necessary.
|
|
32
|
+
if functions -q fish_user_key_bindings
|
|
33
|
+
set -l _old_bindings (functions fish_user_key_bindings)
|
|
34
|
+
function fish_user_key_bindings
|
|
35
|
+
eval $_old_bindings
|
|
36
|
+
_lacy_setup_bindings
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
function fish_user_key_bindings
|
|
40
|
+
_lacy_setup_bindings
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Lacy Shell — Fish prompt indicator
|
|
2
|
+
#
|
|
3
|
+
# Appends a colored mode badge to the right prompt.
|
|
4
|
+
# Note: real-time per-keystroke indicator is not available in Fish
|
|
5
|
+
# without a custom event loop — the badge updates on each new prompt.
|
|
6
|
+
|
|
7
|
+
function _lacy_mode_badge --description "Print Lacy mode badge"
|
|
8
|
+
switch $LACY_SHELL_MODE
|
|
9
|
+
case shell
|
|
10
|
+
printf '\e[38;5;34mSHELL\e[0m'
|
|
11
|
+
case agent
|
|
12
|
+
printf '\e[38;5;200mAGENT\e[0m'
|
|
13
|
+
case auto
|
|
14
|
+
printf '\e[38;5;75mAUTO\e[0m'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# If fish_right_prompt already exists (e.g. Tide, Starship, oh-my-fish),
|
|
19
|
+
# copy it and wrap it so both the theme output and Lacy badge render.
|
|
20
|
+
if functions -q fish_right_prompt
|
|
21
|
+
functions -c fish_right_prompt _lacy_original_right_prompt
|
|
22
|
+
function fish_right_prompt --description "fish_right_prompt with Lacy mode badge"
|
|
23
|
+
_lacy_original_right_prompt
|
|
24
|
+
_lacy_mode_badge
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
function fish_right_prompt --description "Show Lacy mode badge in right prompt"
|
|
28
|
+
_lacy_mode_badge
|
|
29
|
+
end
|
|
30
|
+
end
|
package/lib/mcp.zsh
ADDED
package/lib/modes.zsh
ADDED
package/lib/preheat.zsh
ADDED
package/lib/prompt.zsh
ADDED
package/lib/spinner.zsh
ADDED