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,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
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/core/detection.sh"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/zsh/execute.zsh"
@@ -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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/zsh/keybindings.zsh"
package/lib/mcp.zsh ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/core/mcp.sh"
package/lib/modes.zsh ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/core/modes.sh"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/core/preheat.sh"
package/lib/prompt.zsh ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/zsh/prompt.zsh"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env zsh
2
+ # Backward-compat wrapper — sources from new location
3
+ source "${0:A:h}/core/spinner.sh"