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,366 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Lacy Shell Constants — shared across Bash 4+ and ZSH
|
|
4
|
+
# Sourced by lib/zsh/init.zsh and lib/bash/init.bash
|
|
5
|
+
|
|
6
|
+
# === Runtime State ===
|
|
7
|
+
LACY_SHELL_ENABLED=true
|
|
8
|
+
LACY_SHELL_DEFER_QUIT=false
|
|
9
|
+
|
|
10
|
+
# === Shell Type (set by entry point before sourcing) ===
|
|
11
|
+
# LACY_SHELL_TYPE — "zsh" or "bash"
|
|
12
|
+
# _LACY_ARR_OFFSET — 1 for ZSH (1-based), 0 for Bash (0-based)
|
|
13
|
+
|
|
14
|
+
# === Paths ===
|
|
15
|
+
: "${LACY_SHELL_HOME:="${HOME}/.lacy"}"
|
|
16
|
+
|
|
17
|
+
: "${LACY_SHELL_CONFIG_FILE:="${LACY_SHELL_HOME}/config.yaml"}"
|
|
18
|
+
|
|
19
|
+
: "${LACY_SHELL_MODE_FILE:="${LACY_SHELL_HOME}/current_mode"}"
|
|
20
|
+
|
|
21
|
+
: "${LACY_SHELL_CONVERSATION_FILE:="${LACY_SHELL_HOME}/conversation.log"}"
|
|
22
|
+
|
|
23
|
+
: "${LACY_SHELL_MCP_DIR:="${LACY_SHELL_HOME}/mcp"}"
|
|
24
|
+
|
|
25
|
+
# === Defaults ===
|
|
26
|
+
: "${LACY_SHELL_DEFAULT_MODE:="auto"}"
|
|
27
|
+
|
|
28
|
+
: "${LACY_SHELL_DEFAULT_INDICATOR_STYLE:="top"}"
|
|
29
|
+
|
|
30
|
+
: "${LACY_SHELL_DEFAULT_CONFIDENCE_THRESHOLD:="0.7"}"
|
|
31
|
+
|
|
32
|
+
: "${LACY_SHELL_DEFAULT_PROVIDER:="openai"}"
|
|
33
|
+
|
|
34
|
+
: "${LACY_SHELL_DEFAULT_MODEL:="gpt-4o-mini"}"
|
|
35
|
+
|
|
36
|
+
# === Timeouts (in milliseconds) ===
|
|
37
|
+
: "${LACY_SHELL_EXIT_TIMEOUT_MS:=1000}"
|
|
38
|
+
|
|
39
|
+
: "${LACY_SHELL_EXIT_TIMEOUT_SEC:="1.0"}"
|
|
40
|
+
|
|
41
|
+
: "${LACY_SHELL_MESSAGE_DURATION_SEC:="1.0"}"
|
|
42
|
+
|
|
43
|
+
: "${LACY_SHELL_MCP_TIMEOUT_SEC:=5}"
|
|
44
|
+
|
|
45
|
+
# === UI ===
|
|
46
|
+
: "${LACY_SHELL_TOP_BAR_HEIGHT:=1}"
|
|
47
|
+
|
|
48
|
+
# === Preheat ===
|
|
49
|
+
: "${LACY_PREHEAT_EAGER:="false"}"
|
|
50
|
+
: "${LACY_PREHEAT_SERVER_PORT:="4096"}"
|
|
51
|
+
|
|
52
|
+
# === Colors (256-color palette) ===
|
|
53
|
+
LACY_COLOR_SHELL=34 # Green - shell commands
|
|
54
|
+
LACY_COLOR_AGENT=200 # Magenta - agent queries
|
|
55
|
+
LACY_COLOR_AUTO=75 # Blue - auto mode
|
|
56
|
+
LACY_COLOR_NEUTRAL=238 # Dark gray - neutral/dim
|
|
57
|
+
LACY_COLOR_SHIMMER=(255 219 213 200 141) # Spinner shimmer gradient
|
|
58
|
+
|
|
59
|
+
# === Detection ===
|
|
60
|
+
# (LACY_HARD_AGENT_INDICATORS removed — replaced by LACY_AGENT_WORDS below)
|
|
61
|
+
|
|
62
|
+
# Shell reserved words — pass `command -v` but are never valid standalone commands.
|
|
63
|
+
# Used by Layer 1 of natural language detection (see docs/NATURAL_LANGUAGE_DETECTION.md).
|
|
64
|
+
LACY_SHELL_RESERVED_WORDS=("do" "done" "then" "else" "elif" "fi" "esac" "in" "select" "function" "coproc" "{" "}" "!" "[[")
|
|
65
|
+
|
|
66
|
+
# Agent words — common English words that always route to agent, even as
|
|
67
|
+
# single-word input. Some (yes, nice, cancel) exist as real commands but are
|
|
68
|
+
# almost never typed standalone intentionally.
|
|
69
|
+
# Kept in sync with lash plugin/shell-mode/command-check.ts AGENT_WORDS.
|
|
70
|
+
LACY_AGENT_WORDS=(
|
|
71
|
+
# affirmations
|
|
72
|
+
"yes" "yeah" "yep" "yup" "sure" "ok" "okay" "alright"
|
|
73
|
+
"absolutely" "definitely" "certainly" "indeed" "correct" "right" "exactly"
|
|
74
|
+
"perfect" "agreed" "affirmative" "totally" "clearly" "obviously" "lgtm"
|
|
75
|
+
"roger" "understood" "acknowledged" "gotcha"
|
|
76
|
+
# negations
|
|
77
|
+
"no" "nope" "nah" "never" "wrong" "disagree" "nay" "meh"
|
|
78
|
+
# gratitude
|
|
79
|
+
"thanks" "thank" "thx" "ty" "cheers" "appreciated" "kudos" "congrats" "bravo"
|
|
80
|
+
# reactions
|
|
81
|
+
"great" "good" "nice" "cool" "awesome" "amazing" "wonderful" "brilliant"
|
|
82
|
+
"excellent" "fantastic" "sweet" "neat" "beautiful" "gorgeous" "impressive"
|
|
83
|
+
"incredible" "outstanding" "superb" "marvelous" "magnificent" "stellar"
|
|
84
|
+
"phenomenal" "terrific" "splendid" "fine" "solid" "dope" "sick" "fire" "lit" "rad" "legit"
|
|
85
|
+
"noice" "yay" "hooray" "woah"
|
|
86
|
+
# greetings/closings
|
|
87
|
+
"hey" "hi" "hello" "howdy" "sup" "yo" "bye" "goodbye" "cya" "later"
|
|
88
|
+
# conversational
|
|
89
|
+
"please" "sorry" "pardon" "hmm" "huh" "wow" "whoa" "oops" "ugh" "yikes"
|
|
90
|
+
"damn" "dang" "shoot" "welp" "well" "anyway" "anyways" "regardless"
|
|
91
|
+
"meanwhile" "honestly" "basically" "literally" "actually" "really"
|
|
92
|
+
"seriously" "hopefully" "unfortunately" "apparently"
|
|
93
|
+
"supposedly" "probably" "maybe" "perhaps" "possibly"
|
|
94
|
+
"sheesh" "geez" "oof" "ouch" "bummer" "duh"
|
|
95
|
+
# internet/chat shorthand
|
|
96
|
+
"lol" "haha" "heh" "omg" "wtf" "idk" "fyi" "btw" "imho" "imo" "tbh" "pls" "plz"
|
|
97
|
+
# action/intent
|
|
98
|
+
"stop" "hold" "pause" "cancel" "abort" "skip" "continue" "proceed"
|
|
99
|
+
"next" "again" "redo" "undo" "retry"
|
|
100
|
+
"explain" "elaborate" "clarify" "summarize" "describe" "show" "tell"
|
|
101
|
+
"suggest" "recommend" "consider" "imagine" "suppose"
|
|
102
|
+
# question words
|
|
103
|
+
"why" "how" "what" "when" "where" "who" "which" "whom" "whose"
|
|
104
|
+
"can" "could" "would" "should" "will" "shall" "may" "might" "must"
|
|
105
|
+
"does" "did" "is" "are" "was" "were" "has" "have" "had"
|
|
106
|
+
# programming verbs used conversationally (not valid commands on common systems)
|
|
107
|
+
"refactor" "optimize" "scaffold" "debug" "deploy" "implement"
|
|
108
|
+
"migrate" "lint" "render" "integrate" "iterate"
|
|
109
|
+
"diagnose" "troubleshoot" "hotfix" "rollback" "revert"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Natural language markers — common English words unusual as shell arguments.
|
|
113
|
+
# Used by has_nl_markers (reroute candidates) and Layer 2 detection.
|
|
114
|
+
# Kept in sync with lash plugin/shell-mode/natural-language.ts.
|
|
115
|
+
LACY_NL_MARKERS=(
|
|
116
|
+
# articles/determiners
|
|
117
|
+
"a" "an" "the" "this" "that" "these" "those" "my" "our" "your" "its" "their" "his" "her"
|
|
118
|
+
# pronouns
|
|
119
|
+
"i" "we" "you" "it" "they" "me" "us" "him" "her" "them"
|
|
120
|
+
"myself" "yourself" "itself" "ourselves" "themselves"
|
|
121
|
+
# prepositions
|
|
122
|
+
"to" "of" "about" "with" "from" "for" "into" "through" "between" "after" "before"
|
|
123
|
+
"during" "without" "within" "against" "above" "below" "under" "upon" "across"
|
|
124
|
+
"toward" "towards" "beside" "besides" "beyond" "except" "inside" "outside"
|
|
125
|
+
"behind" "near" "among" "along" "around"
|
|
126
|
+
# conjunctions
|
|
127
|
+
"and" "but" "or" "so" "because" "since" "although" "though" "unless" "while"
|
|
128
|
+
"whereas" "whether" "however" "therefore" "moreover" "furthermore"
|
|
129
|
+
"nevertheless" "otherwise" "instead"
|
|
130
|
+
# verbs
|
|
131
|
+
"is" "are" "was" "were" "be" "been" "being" "have" "has" "had" "having"
|
|
132
|
+
"can" "could" "would" "should" "will" "shall" "may" "might" "must" "need" "want"
|
|
133
|
+
"know" "think" "believe" "understand" "remember" "forget" "seem" "appear"
|
|
134
|
+
"look" "feel" "sound" "mean" "try" "keep" "let" "begin" "start" "stop"
|
|
135
|
+
"continue" "happen" "work" "run" "give" "take" "bring" "send" "put" "get"
|
|
136
|
+
"got" "went" "going" "done" "doing" "made" "making"
|
|
137
|
+
# adverbs
|
|
138
|
+
"not" "already" "also" "just" "still" "even" "really" "actually" "probably" "maybe"
|
|
139
|
+
"perhaps" "always" "never" "sometimes" "often" "usually" "only" "very" "too"
|
|
140
|
+
"enough" "quite" "rather" "pretty" "almost" "nearly" "completely" "entirely"
|
|
141
|
+
"definitely" "certainly" "obviously" "clearly" "honestly" "basically" "literally"
|
|
142
|
+
"seriously" "hopefully" "unfortunately" "apparently" "absolutely" "simply" "merely"
|
|
143
|
+
"exactly" "roughly"
|
|
144
|
+
# question words
|
|
145
|
+
"how" "what" "when" "where" "why" "who" "which" "whom" "whose"
|
|
146
|
+
# other common sentence words
|
|
147
|
+
"if" "there" "here" "all" "any" "some" "every" "no" "each"
|
|
148
|
+
"does" "do" "did" "sure" "out" "up" "down" "ahead" "back" "over" "away" "off"
|
|
149
|
+
"on" "now" "then" "again" "once" "twice" "first" "last" "next"
|
|
150
|
+
"new" "old" "same" "other" "another" "both" "either" "neither"
|
|
151
|
+
"much" "many" "more" "most" "less" "least" "few" "several" "own" "such" "whole" "entire"
|
|
152
|
+
# conversational/reactions
|
|
153
|
+
"please" "thanks" "thank" "sorry" "yes" "yeah" "yep" "ok" "okay" "alright"
|
|
154
|
+
"right" "correct" "wrong" "perfect" "great" "good" "nice" "cool" "awesome"
|
|
155
|
+
"amazing" "wonderful" "excellent" "fantastic" "brilliant" "fine"
|
|
156
|
+
"terrible" "horrible" "awful" "bad" "worse" "worst" "better" "best"
|
|
157
|
+
# indefinite pronouns
|
|
158
|
+
"anyone" "someone" "everyone" "anything" "something" "everything"
|
|
159
|
+
"nobody" "nothing" "nowhere" "wherever" "whatever" "whoever" "whenever" "however"
|
|
160
|
+
# common nouns used in conversation
|
|
161
|
+
"way" "thing" "things" "stuff" "part" "place" "point" "fact"
|
|
162
|
+
"issue" "problem" "question" "answer" "idea" "reason" "example"
|
|
163
|
+
"change" "error" "bug" "fix" "feature" "code" "file" "files" "repo" "project" "app" "test" "tests"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Error patterns that suggest the shell tried to interpret natural language.
|
|
167
|
+
# Case-insensitive matching. Used by Layer 2 detection.
|
|
168
|
+
# Kept in sync with lash plugin/shell-mode/natural-language.ts.
|
|
169
|
+
LACY_SHELL_ERROR_PATTERNS=(
|
|
170
|
+
"parse error"
|
|
171
|
+
"syntax error"
|
|
172
|
+
"unexpected token"
|
|
173
|
+
"unexpected end of file"
|
|
174
|
+
"command not found"
|
|
175
|
+
"no such file or directory"
|
|
176
|
+
"invalid option"
|
|
177
|
+
"unrecognized option"
|
|
178
|
+
"illegal option"
|
|
179
|
+
"unknown option"
|
|
180
|
+
"no rule to make target"
|
|
181
|
+
"unknown primary or operator"
|
|
182
|
+
"missing argument to"
|
|
183
|
+
"invalid regular expression"
|
|
184
|
+
"is not a git command"
|
|
185
|
+
"unknown command"
|
|
186
|
+
"no such command"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
LACY_SHELL_OPERATORS=('|' '&&' '||' ';' '>')
|
|
190
|
+
|
|
191
|
+
# === Interrupt State (shared by ZSH + Bash keybindings) ===
|
|
192
|
+
LACY_SHELL_LAST_INTERRUPT_TIME=0
|
|
193
|
+
LACY_SHELL_QUITTING=false
|
|
194
|
+
LACY_SHELL_INPUT_TYPE=""
|
|
195
|
+
|
|
196
|
+
# === UI ===
|
|
197
|
+
LACY_INDICATOR_CHAR="▌"
|
|
198
|
+
LACY_SPINNER_FRAMES='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
|
199
|
+
: "${LACY_SPINNER_STYLE:="random"}"
|
|
200
|
+
LACY_SPINNER_TEXT='Thinking'
|
|
201
|
+
|
|
202
|
+
# === Tool List (canonical order for detection and display) ===
|
|
203
|
+
LACY_TOOL_LIST=(lash claude opencode gemini codex hermes copilot goose amp aider)
|
|
204
|
+
|
|
205
|
+
# === URLs ===
|
|
206
|
+
LACY_DOCS_URL="https://lacy.sh/docs"
|
|
207
|
+
|
|
208
|
+
# === User-Facing Messages ===
|
|
209
|
+
LACY_MSG_QUIT="Exiting Lacy Shell..."
|
|
210
|
+
LACY_MSG_CTRL_C_HINT="Press Ctrl-C again to quit"
|
|
211
|
+
LACY_MSG_NO_TOOL=" No AI tool found. Install one to get started:"
|
|
212
|
+
LACY_MSG_INSTALL_HINT=" npm install -g lashcode (lash — recommended)"
|
|
213
|
+
LACY_MSG_INSTALL_HINT2=" brew install claude (Claude Code CLI)"
|
|
214
|
+
LACY_MSG_INSTALL_HINT3=" brew install opencode (OpenCode CLI)"
|
|
215
|
+
LACY_MSG_INSTALL_HINT4=" npm install -g @openai/codex"
|
|
216
|
+
LACY_MSG_CONFIGURE_HINT=" Run \`lacy setup\` to configure after installing."
|
|
217
|
+
LACY_MSG_RECOVERY_TOOL=" Try: tool set <name> Switch to a different tool"
|
|
218
|
+
LACY_MSG_RECOVERY_ASK=' ask "your query" Send directly to agent'
|
|
219
|
+
LACY_MSG_RECOVERY_DOCTOR=" lacy doctor Diagnose issues"
|
|
220
|
+
LACY_MSG_CONVERSATION_CLEARED="Conversation history cleared"
|
|
221
|
+
LACY_MSG_NO_CONVERSATION="No conversation history found"
|
|
222
|
+
|
|
223
|
+
# Mode descriptions
|
|
224
|
+
LACY_MSG_MODE_SHELL="SHELL mode - all commands execute directly"
|
|
225
|
+
LACY_MSG_MODE_AGENT="AGENT mode - all input goes to AI"
|
|
226
|
+
LACY_MSG_MODE_AUTO="AUTO mode - smart detection"
|
|
227
|
+
LACY_MSG_MODE_SHELL_SHORT="SHELL mode"
|
|
228
|
+
LACY_MSG_MODE_AGENT_SHORT="AGENT mode"
|
|
229
|
+
LACY_MSG_MODE_AUTO_SHORT="AUTO mode"
|
|
230
|
+
LACY_MSG_COLOR_SHELL="Green = shell command"
|
|
231
|
+
LACY_MSG_COLOR_AGENT="Magenta = agent query"
|
|
232
|
+
|
|
233
|
+
# === Gemini ===
|
|
234
|
+
LACY_GEMINI_CONTEXT="[Context: headless mode (-p). Available tools: grep_search, cli_help, read_file. Shell execution (run_shell_command) is NOT available — answer from context instead. cwd: {cwd}]"
|
|
235
|
+
|
|
236
|
+
# === API URLs ===
|
|
237
|
+
LACY_API_URL_OPENAI="https://api.openai.com/v1/chat/completions"
|
|
238
|
+
LACY_API_URL_ANTHROPIC="https://api.anthropic.com/v1/messages"
|
|
239
|
+
|
|
240
|
+
# === timing (seconds) ===
|
|
241
|
+
LACY_HEALTH_CHECK_TIMEOUT_SYNC=0.3
|
|
242
|
+
LACY_HEALTH_CHECK_TIMEOUT_ASYNC=0.5
|
|
243
|
+
|
|
244
|
+
LACY_SPINNER_FRAME_DELAY=0.05
|
|
245
|
+
LACY_TERMINAL_FLUSH_DELAY=0.02
|
|
246
|
+
LACY_HEALTH_CHECK_ATTEMPTS=30
|
|
247
|
+
LACY_HEALTH_CHECK_INTERVAL=0.1
|
|
248
|
+
LACY_SESSION_CREATE_TIMEOUT=10
|
|
249
|
+
LACY_SESSION_MESSAGE_TIMEOUT=120
|
|
250
|
+
|
|
251
|
+
# === Thresholds ===
|
|
252
|
+
LACY_SIGNAL_EXIT_THRESHOLD=128 # Exit codes >= this are signal-based
|
|
253
|
+
|
|
254
|
+
# === API Models (fallback only) ===
|
|
255
|
+
LACY_API_MODEL_OPENAI="gpt-4o-mini"
|
|
256
|
+
LACY_API_MODEL_ANTHROPIC="claude-3-5-sonnet-20241022"
|
|
257
|
+
|
|
258
|
+
# === Dangerous Commands ===
|
|
259
|
+
LACY_DANGEROUS_PATTERNS=("rm -rf" "sudo rm" "mkfs" "dd if=" ">" "truncate")
|
|
260
|
+
|
|
261
|
+
# === Performance Optimization: Caching ===
|
|
262
|
+
LACY_CONFIG_CACHE_VALID=false
|
|
263
|
+
declare -A LACY_CACHED_CONFIG 2>/dev/null || true
|
|
264
|
+
: "${LACY_SHELL_CONFIG_CACHE_FILE:="${LACY_SHELL_HOME}/.config_cache"}"
|
|
265
|
+
|
|
266
|
+
# Async health check cache
|
|
267
|
+
LACY_PREHEAT_HEALTH_CACHE=false
|
|
268
|
+
LACY_PREHEAT_HEALTH_CHECK_PID=""
|
|
269
|
+
: "${LACY_SHELL_HEALTH_CACHE_FILE:="${LACY_SHELL_HOME}/.health_cache"}"
|
|
270
|
+
|
|
271
|
+
# === Portable Helpers ===
|
|
272
|
+
|
|
273
|
+
# Print colored text — dispatches to shell-appropriate method
|
|
274
|
+
# Usage: lacy_print_color <color_code> <text>
|
|
275
|
+
lacy_print_color() {
|
|
276
|
+
local color="$1"
|
|
277
|
+
shift
|
|
278
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
279
|
+
print -P "%F{${color}}$*%f"
|
|
280
|
+
else
|
|
281
|
+
printf '\e[38;5;%dm%s\e[0m\n' "$color" "$*"
|
|
282
|
+
fi
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Print colored text without trailing newline
|
|
286
|
+
# Usage: lacy_print_color_n <color_code> <text>
|
|
287
|
+
lacy_print_color_n() {
|
|
288
|
+
local color="$1"
|
|
289
|
+
shift
|
|
290
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
291
|
+
print -Pn "%F{${color}}$*%f"
|
|
292
|
+
else
|
|
293
|
+
printf '\e[38;5;%dm%s\e[0m' "$color" "$*"
|
|
294
|
+
fi
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Check if a value is in a list (portable array membership)
|
|
298
|
+
# Usage: _lacy_in_list "value" "item1" "item2" ...
|
|
299
|
+
_lacy_in_list() {
|
|
300
|
+
local needle="$1"
|
|
301
|
+
shift
|
|
302
|
+
local item
|
|
303
|
+
for item in "$@"; do
|
|
304
|
+
[[ "$item" == "$needle" ]] && return 0
|
|
305
|
+
done
|
|
306
|
+
return 1
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# Portable lowercase — works in Bash 4+, ZSH, and falls back to tr
|
|
310
|
+
# Usage: result=$(_lacy_lowercase "STRING")
|
|
311
|
+
_lacy_lowercase() {
|
|
312
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
313
|
+
echo "${1:l}"
|
|
314
|
+
elif (( BASH_VERSINFO[0] >= 4 )) 2>/dev/null; then
|
|
315
|
+
echo "${1,,}"
|
|
316
|
+
else
|
|
317
|
+
# Bash 3 fallback (macOS default) — only used in test/core contexts
|
|
318
|
+
echo "$1" | tr '[:upper:]' '[:lower:]'
|
|
319
|
+
fi
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# Portable pipe status — get exit code of first command in pipeline
|
|
323
|
+
# Must be called immediately after a pipeline
|
|
324
|
+
# Usage: local exit_code=$(_lacy_pipe_status)
|
|
325
|
+
_lacy_pipe_status() {
|
|
326
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
327
|
+
echo "${pipestatus[1]}"
|
|
328
|
+
else
|
|
329
|
+
echo "${PIPESTATUS[0]}"
|
|
330
|
+
fi
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Portable job control off/on
|
|
334
|
+
_lacy_jobctl_off() {
|
|
335
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
336
|
+
[[ -o monitor ]] && LACY_JOBCTL_WAS_SET=1 || LACY_JOBCTL_WAS_SET=""
|
|
337
|
+
setopt NO_MONITOR 2>/dev/null
|
|
338
|
+
else
|
|
339
|
+
LACY_JOBCTL_WAS_SET=""
|
|
340
|
+
case "$-" in *m*) LACY_JOBCTL_WAS_SET=1 ;; esac
|
|
341
|
+
set +m 2>/dev/null
|
|
342
|
+
fi
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
_lacy_jobctl_on() {
|
|
346
|
+
if [[ -n "$LACY_JOBCTL_WAS_SET" ]]; then
|
|
347
|
+
if [[ "$LACY_SHELL_TYPE" == "zsh" ]]; then
|
|
348
|
+
setopt MONITOR 2>/dev/null
|
|
349
|
+
else
|
|
350
|
+
set -m 2>/dev/null
|
|
351
|
+
fi
|
|
352
|
+
LACY_JOBCTL_WAS_SET=""
|
|
353
|
+
fi
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
# Escape a string for safe JSON embedding — handles \, ", and control chars.
|
|
357
|
+
# Usage: escaped=$(_lacy_json_escape_str "$value")
|
|
358
|
+
_lacy_json_escape_str() {
|
|
359
|
+
local s="$1"
|
|
360
|
+
s="${s//\\/\\\\}" # \ → \\
|
|
361
|
+
s="${s//\"/\\\"}" # " → \"
|
|
362
|
+
s="${s//$'\n'/\\n}" # newline → \n
|
|
363
|
+
s="${s//$'\r'/\\r}" # carriage return → \r
|
|
364
|
+
s="${s//$'\t'/\\t}" # tab → \t
|
|
365
|
+
printf '%s' "$s"
|
|
366
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Terminal context for agent queries — delta-based, token-efficient
|
|
4
|
+
# Only sends what changed since the last agent query.
|
|
5
|
+
# Shared across Bash 4+ and ZSH.
|
|
6
|
+
|
|
7
|
+
# === State: Delta Tracking ===
|
|
8
|
+
_LACY_CTX_LAST_CWD=""
|
|
9
|
+
_LACY_CTX_LAST_GIT=""
|
|
10
|
+
_LACY_CTX_CMDS_SINCE_QUERY=0
|
|
11
|
+
_LACY_CTX_LAST_EXIT_CODE=0
|
|
12
|
+
_LACY_CTX_REAL_CMD=false
|
|
13
|
+
|
|
14
|
+
# Command ring buffer — explicit array avoids agent queries leaking from fc/history
|
|
15
|
+
_LACY_CTX_CMD_BUFFER=()
|
|
16
|
+
_LACY_CTX_CMD_BUFFER_MAX=10
|
|
17
|
+
|
|
18
|
+
# === State: Terminal Output Capture ===
|
|
19
|
+
_LACY_CTX_TERMINAL_CAPTURE_CMD="" # Detected at load time; empty = unsupported
|
|
20
|
+
_LACY_CTX_OUTPUT_ENABLED=true # Toggled via config (context.output)
|
|
21
|
+
_LACY_CTX_OUTPUT_MAX_LINES=50 # Configurable cap (context.output_lines)
|
|
22
|
+
|
|
23
|
+
# ============================================================================
|
|
24
|
+
# Terminal Detection (called once at source time)
|
|
25
|
+
# ============================================================================
|
|
26
|
+
|
|
27
|
+
# Detect terminal/multiplexer API for screen capture.
|
|
28
|
+
# Sets _LACY_CTX_TERMINAL_CAPTURE_CMD to a command string (or function name),
|
|
29
|
+
# or empty if unsupported. Checked once at source time.
|
|
30
|
+
#
|
|
31
|
+
# Priority: tmux > screen > iTerm2 > Terminal.app
|
|
32
|
+
# Multiplexers are checked first because terminal emulator APIs return wrong
|
|
33
|
+
# content when running inside a multiplexer.
|
|
34
|
+
_lacy_ctx_detect_terminal() {
|
|
35
|
+
_LACY_CTX_TERMINAL_CAPTURE_CMD=""
|
|
36
|
+
|
|
37
|
+
# 1. tmux: native pane capture (takes priority over terminal APIs)
|
|
38
|
+
if [[ -n "${TMUX:-}" ]]; then
|
|
39
|
+
if command -v tmux >/dev/null 2>&1; then
|
|
40
|
+
_LACY_CTX_TERMINAL_CAPTURE_CMD="tmux capture-pane -p"
|
|
41
|
+
return
|
|
42
|
+
fi
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# 2. screen: hardcopy to temp file
|
|
46
|
+
if [[ -n "${STY:-}" ]]; then
|
|
47
|
+
if command -v screen >/dev/null 2>&1; then
|
|
48
|
+
_LACY_CTX_TERMINAL_CAPTURE_CMD="_lacy_ctx_screen_capture"
|
|
49
|
+
return
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# 3-4. macOS: AppleScript for iTerm2 and Terminal.app
|
|
54
|
+
if [[ "$(uname -s 2>/dev/null)" == "Darwin" ]]; then
|
|
55
|
+
if [[ "${TERM_PROGRAM:-}" == "iTerm.app" ]]; then
|
|
56
|
+
_LACY_CTX_TERMINAL_CAPTURE_CMD="_lacy_ctx_iterm2_capture"
|
|
57
|
+
return
|
|
58
|
+
fi
|
|
59
|
+
if [[ "${TERM_PROGRAM:-}" == "Apple_Terminal" ]]; then
|
|
60
|
+
_LACY_CTX_TERMINAL_CAPTURE_CMD="_lacy_ctx_terminal_app_capture"
|
|
61
|
+
return
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ============================================================================
|
|
67
|
+
# Capture Helpers (multi-step captures that can't be a single command)
|
|
68
|
+
# ============================================================================
|
|
69
|
+
|
|
70
|
+
# screen: hardcopy writes to a file, not stdout
|
|
71
|
+
_lacy_ctx_screen_capture() {
|
|
72
|
+
local tmpfile
|
|
73
|
+
tmpfile=$(mktemp) || return
|
|
74
|
+
screen -X hardcopy "$tmpfile" 2>/dev/null || { rm -f "$tmpfile"; return; }
|
|
75
|
+
cat "$tmpfile" 2>/dev/null
|
|
76
|
+
rm -f "$tmpfile" 2>/dev/null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# iTerm2: AppleScript to get current session contents
|
|
80
|
+
_lacy_ctx_iterm2_capture() {
|
|
81
|
+
osascript -e 'tell application "iTerm2" to tell current session of current window to get contents' 2>/dev/null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Terminal.app: AppleScript to get current tab contents
|
|
85
|
+
_lacy_ctx_terminal_app_capture() {
|
|
86
|
+
osascript -e 'tell application "Terminal" to get contents of selected tab of front window' 2>/dev/null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# ============================================================================
|
|
90
|
+
# Screen Capture (called lazily at query time)
|
|
91
|
+
# ============================================================================
|
|
92
|
+
|
|
93
|
+
# Capture visible terminal screen text, stripped of ANSI escapes.
|
|
94
|
+
# Returns captured text on stdout, or empty if unavailable/disabled.
|
|
95
|
+
_lacy_ctx_capture_screen() {
|
|
96
|
+
[[ "$_LACY_CTX_OUTPUT_ENABLED" != true ]] && return
|
|
97
|
+
[[ -z "$_LACY_CTX_TERMINAL_CAPTURE_CMD" ]] && return
|
|
98
|
+
|
|
99
|
+
local raw_output
|
|
100
|
+
raw_output=$(eval "$_LACY_CTX_TERMINAL_CAPTURE_CMD" 2>/dev/null) || return
|
|
101
|
+
|
|
102
|
+
# Strip ANSI escape sequences (SGR, cursor movement, etc.)
|
|
103
|
+
local cleaned
|
|
104
|
+
cleaned=$(printf '%s\n' "$raw_output" | sed $'s/\x1b\[[0-9;]*[a-zA-Z]//g')
|
|
105
|
+
|
|
106
|
+
# Remove trailing blank lines
|
|
107
|
+
while [[ "$cleaned" == *$'\n' ]]; do
|
|
108
|
+
cleaned="${cleaned%$'\n'}"
|
|
109
|
+
done
|
|
110
|
+
|
|
111
|
+
[[ -z "$cleaned" ]] && return
|
|
112
|
+
|
|
113
|
+
# Truncate from the top, keeping the last N lines (errors are at the bottom)
|
|
114
|
+
local max_lines="${_LACY_CTX_OUTPUT_MAX_LINES:-50}"
|
|
115
|
+
if (( max_lines > 0 )); then
|
|
116
|
+
local line_count
|
|
117
|
+
line_count=$(printf '%s\n' "$cleaned" | wc -l)
|
|
118
|
+
if (( line_count > max_lines )); then
|
|
119
|
+
cleaned=$(printf '%s\n' "$cleaned" | tail -n "$max_lines")
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
printf '%s' "$cleaned"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ============================================================================
|
|
127
|
+
# Hooks (called from accept-line and precmd)
|
|
128
|
+
# ============================================================================
|
|
129
|
+
|
|
130
|
+
# Called from accept-line when routing input to the shell.
|
|
131
|
+
# Records the command text for inclusion in the next agent query context.
|
|
132
|
+
# Usage: _lacy_ctx_mark_command "$BUFFER" (ZSH)
|
|
133
|
+
# _lacy_ctx_mark_command "$READLINE_LINE" (Bash)
|
|
134
|
+
_lacy_ctx_mark_command() {
|
|
135
|
+
local cmd="$1"
|
|
136
|
+
_LACY_CTX_REAL_CMD=true
|
|
137
|
+
|
|
138
|
+
# Append to ring buffer, trim to max size
|
|
139
|
+
_LACY_CTX_CMD_BUFFER+=("$cmd")
|
|
140
|
+
if (( ${#_LACY_CTX_CMD_BUFFER[@]} > _LACY_CTX_CMD_BUFFER_MAX )); then
|
|
141
|
+
_LACY_CTX_CMD_BUFFER=("${_LACY_CTX_CMD_BUFFER[@]: -$_LACY_CTX_CMD_BUFFER_MAX}")
|
|
142
|
+
fi
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Called from precmd hooks. Captures exit code for real shell commands only.
|
|
146
|
+
# Usage: _lacy_ctx_on_precmd $last_exit
|
|
147
|
+
_lacy_ctx_on_precmd() {
|
|
148
|
+
local exit_code="$1"
|
|
149
|
+
if [[ "$_LACY_CTX_REAL_CMD" == true ]]; then
|
|
150
|
+
_LACY_CTX_LAST_EXIT_CODE=$exit_code
|
|
151
|
+
(( _LACY_CTX_CMDS_SINCE_QUERY++ ))
|
|
152
|
+
_LACY_CTX_REAL_CMD=false
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# ============================================================================
|
|
157
|
+
# Context Builder (called at query time)
|
|
158
|
+
# ============================================================================
|
|
159
|
+
|
|
160
|
+
# Build delta-based context and prepend to query.
|
|
161
|
+
# Sets _LACY_CTX_RESULT to the enriched query (avoids subshell so state resets
|
|
162
|
+
# propagate to the parent). If nothing changed, result is the bare query.
|
|
163
|
+
# Format: [cwd: /path] [git: branch] [exit: 1] [recent: cmd1 | cmd2] query
|
|
164
|
+
# With output: ...context header...\n[terminal-output]\n...\n[/terminal-output]\nquery
|
|
165
|
+
# Usage: _lacy_build_query_context "$query"; query="$_LACY_CTX_RESULT"
|
|
166
|
+
_LACY_CTX_RESULT=""
|
|
167
|
+
|
|
168
|
+
_lacy_build_query_context() {
|
|
169
|
+
local query="$1"
|
|
170
|
+
local ctx=""
|
|
171
|
+
|
|
172
|
+
# --- CWD (only if changed) ---
|
|
173
|
+
local cwd="${PWD}"
|
|
174
|
+
if [[ "$cwd" != "$_LACY_CTX_LAST_CWD" ]]; then
|
|
175
|
+
ctx+="[cwd: ${cwd}] "
|
|
176
|
+
_LACY_CTX_LAST_CWD="$cwd"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# --- Git branch (only if changed) ---
|
|
180
|
+
local git_branch=""
|
|
181
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
182
|
+
git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
183
|
+
# Detached HEAD returns literal "HEAD" — fall back to short hash
|
|
184
|
+
if [[ "$git_branch" == "HEAD" ]]; then
|
|
185
|
+
git_branch=$(git rev-parse --short HEAD 2>/dev/null)
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
if [[ "$git_branch" != "$_LACY_CTX_LAST_GIT" ]]; then
|
|
189
|
+
if [[ -n "$git_branch" ]]; then
|
|
190
|
+
ctx+="[git: ${git_branch}] "
|
|
191
|
+
fi
|
|
192
|
+
_LACY_CTX_LAST_GIT="$git_branch"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# --- Last exit code (only if non-zero AND a command ran since last query) ---
|
|
196
|
+
if (( _LACY_CTX_CMDS_SINCE_QUERY > 0 && _LACY_CTX_LAST_EXIT_CODE != 0 )); then
|
|
197
|
+
ctx+="[exit: ${_LACY_CTX_LAST_EXIT_CODE}] "
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# --- Recent commands since last query ---
|
|
201
|
+
if (( _LACY_CTX_CMDS_SINCE_QUERY > 0 )) && [[ ${#_LACY_CTX_CMD_BUFFER[@]} -gt 0 ]]; then
|
|
202
|
+
local cmds=""
|
|
203
|
+
local cmd
|
|
204
|
+
for cmd in "${_LACY_CTX_CMD_BUFFER[@]}"; do
|
|
205
|
+
# Truncate long commands to keep context compact
|
|
206
|
+
if (( ${#cmd} > 80 )); then
|
|
207
|
+
cmd="${cmd:0:77}..."
|
|
208
|
+
fi
|
|
209
|
+
if [[ -n "$cmds" ]]; then
|
|
210
|
+
cmds+=" | $cmd"
|
|
211
|
+
else
|
|
212
|
+
cmds="$cmd"
|
|
213
|
+
fi
|
|
214
|
+
done
|
|
215
|
+
ctx+="[recent: ${cmds}] "
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# --- Terminal screen output (lazy capture, only if commands ran) ---
|
|
219
|
+
local screen_output=""
|
|
220
|
+
if (( _LACY_CTX_CMDS_SINCE_QUERY > 0 )); then
|
|
221
|
+
screen_output=$(_lacy_ctx_capture_screen)
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# --- Reset counters ---
|
|
225
|
+
_LACY_CTX_CMDS_SINCE_QUERY=0
|
|
226
|
+
_LACY_CTX_LAST_EXIT_CODE=0
|
|
227
|
+
_LACY_CTX_CMD_BUFFER=()
|
|
228
|
+
|
|
229
|
+
# --- Set result ---
|
|
230
|
+
if [[ -n "$screen_output" ]]; then
|
|
231
|
+
_LACY_CTX_RESULT="${ctx}
|
|
232
|
+
[terminal-output]
|
|
233
|
+
${screen_output}
|
|
234
|
+
[/terminal-output]
|
|
235
|
+
${query}"
|
|
236
|
+
else
|
|
237
|
+
_LACY_CTX_RESULT="${ctx}${query}"
|
|
238
|
+
fi
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# ============================================================================
|
|
242
|
+
# Reset (called on /new session)
|
|
243
|
+
# ============================================================================
|
|
244
|
+
|
|
245
|
+
# Clear all context state so the next query sends full context.
|
|
246
|
+
# Does NOT reset terminal detection or config — those are session-lifetime.
|
|
247
|
+
_lacy_ctx_reset() {
|
|
248
|
+
_LACY_CTX_LAST_CWD=""
|
|
249
|
+
_LACY_CTX_LAST_GIT=""
|
|
250
|
+
_LACY_CTX_CMDS_SINCE_QUERY=0
|
|
251
|
+
_LACY_CTX_LAST_EXIT_CODE=0
|
|
252
|
+
_LACY_CTX_REAL_CMD=false
|
|
253
|
+
_LACY_CTX_CMD_BUFFER=()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# ============================================================================
|
|
257
|
+
# Init (runs once when sourced)
|
|
258
|
+
# ============================================================================
|
|
259
|
+
|
|
260
|
+
_lacy_ctx_detect_terminal
|