lacy 0.6.4
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/LICENSE.md +134 -0
- package/README.md +492 -0
- package/bin/lash +30 -0
- package/package.json +50 -0
- package/scripts/install.js +127 -0
- package/scripts/run-issue-labeler.sh +12 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, MIT Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-MIT
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Charmbracelet, Inc
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the MIT license that is effective on the second anniversary of the date we make
|
|
91
|
+
the Software available. On or after that date, you may use the Software under
|
|
92
|
+
the MIT license, in which case the following will apply:
|
|
93
|
+
|
|
94
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
95
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
96
|
+
the Software without restriction, including without limitation the rights to
|
|
97
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
98
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
99
|
+
so, subject to the following conditions:
|
|
100
|
+
|
|
101
|
+
The above copyright notice and this permission notice shall be included in all
|
|
102
|
+
copies or substantial portions of the Software.
|
|
103
|
+
|
|
104
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
105
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
106
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
107
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
108
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
109
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
110
|
+
SOFTWARE.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
MIT License
|
|
115
|
+
|
|
116
|
+
Copyright (c) 2025-03-21 - 2025-05-30 Kujtim Hoxha
|
|
117
|
+
|
|
118
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
119
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
120
|
+
in the Software without restriction, including without limitation the rights
|
|
121
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
122
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
123
|
+
furnished to do so, subject to the following conditions:
|
|
124
|
+
|
|
125
|
+
The above copyright notice and this permission notice shall be included in all
|
|
126
|
+
copies or substantial portions of the Software.
|
|
127
|
+
|
|
128
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
129
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
130
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
131
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
132
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
133
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
134
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# Lash
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://stuff.charm.sh/crush/charm-crush.png"><img width="450" alt="Charm Crush Logo" src="https://github.com/user-attachments/assets/adc1a6f4-b284-4603-836c-59038caa2e8b" /></a><br />
|
|
5
|
+
<a href="https://github.com/lacymorrow/lash/releases"><img src="https://img.shields.io/github/release/lacymorrow/lash" alt="Latest Release"></a>
|
|
6
|
+
<a href="https://github.com/lacymorrow/lash/actions"><img src="https://github.com/lacymorrow/lash/workflows/build/badge.svg" alt="Build Status"></a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
Terminal-based AI assistant for developers. A login-shell-friendly fork of Charmbracelet Crush with Shell, Agent, and Auto modes, plus built-in MCP support.
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **Multi-Model:** choose from a wide range of LLMs or add your own via OpenAI- or Anthropic-compatible APIs
|
|
14
|
+
- **Flexible:** switch LLMs mid-session while preserving context
|
|
15
|
+
- **Session-Based:** maintain multiple work sessions and contexts per project
|
|
16
|
+
- **LSP-Enhanced:** uses LSPs for additional context, just like you do
|
|
17
|
+
- **Extensible:** add capabilities via MCPs (`http`, `stdio`, and `sse`)
|
|
18
|
+
- **Works Everywhere:** first-class support in terminals on macOS, Linux, and Windows (PowerShell and WSL)
|
|
19
|
+
- **Modes:** Shell, Agent, and Auto routing (first run defaults to Auto)
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
NPM:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @lacymorrow/lash
|
|
27
|
+
lash --version
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Homebrew:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
brew tap lacymorrow/tap
|
|
34
|
+
brew install lacymorrow/tap/lash
|
|
35
|
+
lash --version
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Windows users:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Winget
|
|
42
|
+
winget install charmbracelet.crush
|
|
43
|
+
|
|
44
|
+
# Scoop
|
|
45
|
+
scoop bucket add charm https://github.com/charmbracelet/scoop-bucket.git
|
|
46
|
+
scoop install crush
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary><strong>Nix (NUR)</strong></summary>
|
|
51
|
+
|
|
52
|
+
Crush is available via [NUR](https://github.com/nix-community/NUR) in `nur.repos.charmbracelet.crush`.
|
|
53
|
+
|
|
54
|
+
You can also try out Crush via `nix-shell`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Add the NUR channel.
|
|
58
|
+
nix-channel --add https://github.com/nix-community/NUR/archive/main.tar.gz nur
|
|
59
|
+
nix-channel --update
|
|
60
|
+
|
|
61
|
+
# Get Crush in a Nix shell.
|
|
62
|
+
nix-shell -p '(import <nur> { pkgs = import <nixpkgs> {}; }).repos.charmbracelet.crush'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
</details>
|
|
66
|
+
|
|
67
|
+
<details>
|
|
68
|
+
<summary><strong>Debian/Ubuntu</strong></summary>
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Download .deb package from GitHub releases
|
|
72
|
+
wget https://github.com/lacymorrow/lash/releases/latest/download/lash_Linux_x86_64.deb
|
|
73
|
+
sudo dpkg -i lash_Linux_x86_64.deb
|
|
74
|
+
|
|
75
|
+
# Or download and extract binary directly
|
|
76
|
+
wget https://github.com/lacymorrow/lash/releases/latest/download/lash_Linux_x86_64.tar.gz
|
|
77
|
+
tar -xzf lash_Linux_x86_64.tar.gz
|
|
78
|
+
sudo mv lash/lash /usr/local/bin/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
</details>
|
|
82
|
+
|
|
83
|
+
<details>
|
|
84
|
+
<summary><strong>Fedora/RHEL</strong></summary>
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
echo '[charm]
|
|
88
|
+
name=Charm
|
|
89
|
+
baseurl=https://repo.charm.sh/yum/
|
|
90
|
+
enabled=1
|
|
91
|
+
gpgcheck=1
|
|
92
|
+
gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo
|
|
93
|
+
sudo yum install crush
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
</details>
|
|
97
|
+
|
|
98
|
+
Or, download it:
|
|
99
|
+
|
|
100
|
+
- [Packages][releases] are available in Debian and RPM formats
|
|
101
|
+
- [Binaries][releases] are available for Linux, macOS, Windows, FreeBSD, OpenBSD, and NetBSD
|
|
102
|
+
|
|
103
|
+
[releases]: https://github.com/lacymorrow/lash/releases
|
|
104
|
+
|
|
105
|
+
Or just install it with Go:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
go install github.com/lacymorrow/lash@latest
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> [!WARNING]
|
|
112
|
+
> Productivity may increase when using Lash and you may find yourself nerd
|
|
113
|
+
> sniped when first using the application. If the symptoms persist, join the
|
|
114
|
+
> [Discord][discord] and nerd snipe the rest of us.
|
|
115
|
+
|
|
116
|
+
### Getting Started
|
|
117
|
+
|
|
118
|
+
The quickest way to get started is to grab an API key for your preferred
|
|
119
|
+
provider such as Anthropic, OpenAI, Groq, or OpenRouter and run `lash`. You'll be prompted to enter your API key.
|
|
120
|
+
|
|
121
|
+
That said, you can also set environment variables for preferred providers.
|
|
122
|
+
|
|
123
|
+
| Environment Variable | Provider |
|
|
124
|
+
| -------------------------- | -------------------------------------------------- |
|
|
125
|
+
| `ANTHROPIC_API_KEY` | Anthropic |
|
|
126
|
+
| `OPENAI_API_KEY` | OpenAI |
|
|
127
|
+
| `OPENROUTER_API_KEY` | OpenRouter |
|
|
128
|
+
| `GEMINI_API_KEY` | Google Gemini |
|
|
129
|
+
| `VERTEXAI_PROJECT` | Google Cloud VertexAI (Gemini) |
|
|
130
|
+
| `VERTEXAI_LOCATION` | Google Cloud VertexAI (Gemini) |
|
|
131
|
+
| `GROQ_API_KEY` | Groq |
|
|
132
|
+
| `AWS_ACCESS_KEY_ID` | AWS Bedrock (Claude) |
|
|
133
|
+
| `AWS_SECRET_ACCESS_KEY` | AWS Bedrock (Claude) |
|
|
134
|
+
| `AWS_REGION` | AWS Bedrock (Claude) |
|
|
135
|
+
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI models |
|
|
136
|
+
| `AZURE_OPENAI_API_KEY` | Azure OpenAI models (optional when using Entra ID) |
|
|
137
|
+
| `AZURE_OPENAI_API_VERSION` | Azure OpenAI models |
|
|
138
|
+
|
|
139
|
+
### Models Catalog
|
|
140
|
+
|
|
141
|
+
Lash uses the Catwalk model catalog from the upstream project for defaults. You can override or add providers in your configuration.
|
|
142
|
+
|
|
143
|
+
### Configuration
|
|
144
|
+
|
|
145
|
+
Lash runs great with no configuration. If you do want to customize it, configuration follows the upstream file names for compatibility and is read with the following priority:
|
|
146
|
+
|
|
147
|
+
1. `.crush.json`
|
|
148
|
+
2. `crush.json`
|
|
149
|
+
3. `$HOME/.config/crush/crush.json` (Windows: `%USERPROFILE%\AppData\Local\crush\crush.json`)
|
|
150
|
+
|
|
151
|
+
Configuration itself is stored as a JSON object:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"this-setting": {"this": "that"},
|
|
156
|
+
"that-setting": ["ceci", "cela"]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Lash stores ephemeral data, such as application state, in this location:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Project-relative (default)
|
|
164
|
+
./.lash/
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### LSPs
|
|
168
|
+
|
|
169
|
+
Lash can use LSPs for additional context. LSPs can be added manually like so:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"$schema": "https://charm.land/crush.json",
|
|
174
|
+
"lsp": {
|
|
175
|
+
"go": {
|
|
176
|
+
"command": "gopls",
|
|
177
|
+
"env": {
|
|
178
|
+
"GOTOOLCHAIN": "go1.24.5"
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"typescript": {
|
|
182
|
+
"command": "typescript-language-server",
|
|
183
|
+
"args": ["--stdio"]
|
|
184
|
+
},
|
|
185
|
+
"nix": {
|
|
186
|
+
"command": "nil"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### MCPs
|
|
193
|
+
|
|
194
|
+
Lash supports Model Context Protocol (MCP) servers through three
|
|
195
|
+
transport types: `stdio` for command-line servers, `http` for HTTP endpoints,
|
|
196
|
+
and `sse` for Server-Sent Events. Environment variable expansion is supported
|
|
197
|
+
using `$(echo $VAR)` syntax.
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"$schema": "https://charm.land/crush.json",
|
|
202
|
+
"mcp": {
|
|
203
|
+
"filesystem": {
|
|
204
|
+
"type": "stdio",
|
|
205
|
+
"command": "node",
|
|
206
|
+
"args": ["/path/to/mcp-server.js"],
|
|
207
|
+
"env": {
|
|
208
|
+
"NODE_ENV": "production"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"github": {
|
|
212
|
+
"type": "http",
|
|
213
|
+
"url": "https://example.com/mcp/",
|
|
214
|
+
"headers": {
|
|
215
|
+
"Authorization": "$(echo Bearer $EXAMPLE_MCP_TOKEN)"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"streaming-service": {
|
|
219
|
+
"type": "sse",
|
|
220
|
+
"url": "https://example.com/mcp/sse",
|
|
221
|
+
"headers": {
|
|
222
|
+
"API-Key": "$(echo $API_KEY)"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Ignoring Files
|
|
230
|
+
|
|
231
|
+
Lash respects `.gitignore` by default. You can also create a `.crushignore` file to specify additional files and directories that should be ignored when providing context.
|
|
232
|
+
|
|
233
|
+
The `.crushignore` file uses the same syntax as `.gitignore` and can be placed
|
|
234
|
+
in the root of your project or in subdirectories.
|
|
235
|
+
|
|
236
|
+
### Allowing Tools
|
|
237
|
+
|
|
238
|
+
By default, Lash will ask you for permission before running tool calls. If you'd like, you can allow tools to be executed without prompting you for permissions. Use this with care.
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"$schema": "https://charm.land/crush.json",
|
|
243
|
+
"permissions": {
|
|
244
|
+
"allowed_tools": [
|
|
245
|
+
"view",
|
|
246
|
+
"ls",
|
|
247
|
+
"grep",
|
|
248
|
+
"edit",
|
|
249
|
+
"mcp_context7_get-library-doc"
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
You can also skip all permission prompts entirely by running Lash with the `--yolo` flag (or setting `lash.yolo` in config). Be careful with this feature.
|
|
256
|
+
|
|
257
|
+
### Timeouts
|
|
258
|
+
|
|
259
|
+
To prevent requests or tool calls from hanging indefinitely, you can configure global caps under `options`:
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"$schema": "https://charm.land/crush.json",
|
|
264
|
+
"options": {
|
|
265
|
+
"request_timeout_seconds": 300,
|
|
266
|
+
"tool_call_timeout_seconds": 120
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
- `request_timeout_seconds`: Maximum duration for a single agent request. When reached, the request is canceled.
|
|
272
|
+
- `tool_call_timeout_seconds`: Maximum duration for each individual tool call. Tools with their own shorter timeouts still apply; this acts as a safety cap.
|
|
273
|
+
|
|
274
|
+
Built-in tools like `bash`, `fetch`, `download`, and `sourcegraph` already enforce their own per-call timeouts; the global caps add an extra safeguard.
|
|
275
|
+
|
|
276
|
+
### Local Models
|
|
277
|
+
|
|
278
|
+
Local models can also be configured via OpenAI-compatible API. Here are two common examples:
|
|
279
|
+
|
|
280
|
+
#### Ollama
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"providers": {
|
|
285
|
+
"ollama": {
|
|
286
|
+
"name": "Ollama",
|
|
287
|
+
"base_url": "http://localhost:11434/v1/",
|
|
288
|
+
"type": "openai",
|
|
289
|
+
"models": [
|
|
290
|
+
{
|
|
291
|
+
"name": "Qwen 3 30B",
|
|
292
|
+
"id": "qwen3:30b",
|
|
293
|
+
"context_window": 256000,
|
|
294
|
+
"default_max_tokens": 20000
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### LM Studio
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"providers": {
|
|
307
|
+
"lmstudio": {
|
|
308
|
+
"name": "LM Studio",
|
|
309
|
+
"base_url": "http://localhost:1234/v1/",
|
|
310
|
+
"type": "openai",
|
|
311
|
+
"models": [
|
|
312
|
+
{
|
|
313
|
+
"name": "Qwen 3 30B",
|
|
314
|
+
"id": "qwen/qwen3-30b-a3b-2507",
|
|
315
|
+
"context_window": 256000,
|
|
316
|
+
"default_max_tokens": 20000
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Custom Providers
|
|
325
|
+
|
|
326
|
+
Lash supports custom provider configurations for both OpenAI-compatible and Anthropic-compatible APIs.
|
|
327
|
+
|
|
328
|
+
#### OpenAI-Compatible APIs
|
|
329
|
+
|
|
330
|
+
Here’s an example configuration for Deepseek, which uses an OpenAI-compatible
|
|
331
|
+
API. Don't forget to set `DEEPSEEK_API_KEY` in your environment.
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"$schema": "https://charm.land/crush.json",
|
|
336
|
+
"providers": {
|
|
337
|
+
"deepseek": {
|
|
338
|
+
"type": "openai",
|
|
339
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
340
|
+
"api_key": "$DEEPSEEK_API_KEY",
|
|
341
|
+
"models": [
|
|
342
|
+
{
|
|
343
|
+
"id": "deepseek-chat",
|
|
344
|
+
"name": "Deepseek V3",
|
|
345
|
+
"cost_per_1m_in": 0.27,
|
|
346
|
+
"cost_per_1m_out": 1.1,
|
|
347
|
+
"cost_per_1m_in_cached": 0.07,
|
|
348
|
+
"cost_per_1m_out_cached": 1.1,
|
|
349
|
+
"context_window": 64000,
|
|
350
|
+
"default_max_tokens": 5000
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
#### Anthropic-Compatible APIs
|
|
359
|
+
|
|
360
|
+
Custom Anthropic-compatible providers follow this format:
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{
|
|
364
|
+
"$schema": "https://charm.land/crush.json",
|
|
365
|
+
"providers": {
|
|
366
|
+
"custom-anthropic": {
|
|
367
|
+
"type": "anthropic",
|
|
368
|
+
"base_url": "https://api.anthropic.com/v1",
|
|
369
|
+
"api_key": "$ANTHROPIC_API_KEY",
|
|
370
|
+
"extra_headers": {
|
|
371
|
+
"anthropic-version": "2023-06-01"
|
|
372
|
+
},
|
|
373
|
+
"models": [
|
|
374
|
+
{
|
|
375
|
+
"id": "claude-sonnet-4-20250514",
|
|
376
|
+
"name": "Claude Sonnet 4",
|
|
377
|
+
"cost_per_1m_in": 3,
|
|
378
|
+
"cost_per_1m_out": 15,
|
|
379
|
+
"cost_per_1m_in_cached": 3.75,
|
|
380
|
+
"cost_per_1m_out_cached": 0.3,
|
|
381
|
+
"context_window": 200000,
|
|
382
|
+
"default_max_tokens": 50000,
|
|
383
|
+
"can_reason": true,
|
|
384
|
+
"supports_attachments": true
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Amazon Bedrock
|
|
393
|
+
|
|
394
|
+
Lash supports running Anthropic models through Bedrock, with caching disabled.
|
|
395
|
+
|
|
396
|
+
* A Bedrock provider will appear once you have AWS configured, i.e. `aws configure`
|
|
397
|
+
* Crush also expects the `AWS_REGION` or `AWS_DEFAULT_REGION` to be set
|
|
398
|
+
* To use a specific AWS profile set `AWS_PROFILE` in your environment, i.e. `AWS_PROFILE=myprofile crush`
|
|
399
|
+
|
|
400
|
+
### Vertex AI Platform
|
|
401
|
+
|
|
402
|
+
Vertex AI will appear in the list of available providers when `VERTEXAI_PROJECT` and `VERTEXAI_LOCATION` are set. You will also need to be authenticated:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
gcloud auth application-default login
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
To add specific models to the configuration, configure as such:
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"$schema": "https://charm.land/crush.json",
|
|
413
|
+
"providers": {
|
|
414
|
+
"vertexai": {
|
|
415
|
+
"models": [
|
|
416
|
+
{
|
|
417
|
+
"id": "claude-sonnet-4@20250514",
|
|
418
|
+
"name": "VertexAI Sonnet 4",
|
|
419
|
+
"cost_per_1m_in": 3,
|
|
420
|
+
"cost_per_1m_out": 15,
|
|
421
|
+
"cost_per_1m_in_cached": 3.75,
|
|
422
|
+
"cost_per_1m_out_cached": 0.3,
|
|
423
|
+
"context_window": 200000,
|
|
424
|
+
"default_max_tokens": 50000,
|
|
425
|
+
"can_reason": true,
|
|
426
|
+
"supports_attachments": true
|
|
427
|
+
}
|
|
428
|
+
]
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### A Note on Claude Max and GitHub Copilot
|
|
435
|
+
|
|
436
|
+
Lash only supports model providers through official, compliant APIs. We do not
|
|
437
|
+
support or endorse any methods that rely on personal Claude Max and GitHub Copilot
|
|
438
|
+
accounts or OAuth workarounds, which may violate Anthropic and Microsoft’s
|
|
439
|
+
Terms of Service.
|
|
440
|
+
|
|
441
|
+
We’re committed to building sustainable, trusted integrations with model
|
|
442
|
+
providers. If you’re a provider interested in working with us,
|
|
443
|
+
[reach out](mailto:vt100@charm.sh).
|
|
444
|
+
|
|
445
|
+
### Logging
|
|
446
|
+
|
|
447
|
+
Logs are stored in `./.lash/logs/lash.log` relative to your project.
|
|
448
|
+
|
|
449
|
+
The CLI also contains some helper commands to make perusing recent logs easier:
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# Print the last 1000 lines
|
|
453
|
+
lash logs
|
|
454
|
+
|
|
455
|
+
# Print the last 500 lines
|
|
456
|
+
lash logs --tail 500
|
|
457
|
+
|
|
458
|
+
# Follow logs in real time
|
|
459
|
+
lash logs --follow
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Want more logging? Run `lash` with the `--debug` flag, or enable it in the
|
|
463
|
+
config:
|
|
464
|
+
|
|
465
|
+
```json
|
|
466
|
+
{
|
|
467
|
+
"$schema": "https://charm.land/crush.json",
|
|
468
|
+
"options": {
|
|
469
|
+
"debug": true,
|
|
470
|
+
"debug_lsp": true
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Lash-specific Configuration
|
|
476
|
+
|
|
477
|
+
Lash adds an optional `lash` namespace to configuration for mode and safety controls while remaining compatible with upstream `crush.json`:
|
|
478
|
+
|
|
479
|
+
```json
|
|
480
|
+
{
|
|
481
|
+
"$schema": "https://charm.land/crush.json",
|
|
482
|
+
"lash": {
|
|
483
|
+
"mode": "Auto",
|
|
484
|
+
"yolo": false,
|
|
485
|
+
"safety": { "confirm_agent_exec": true }
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### License
|
|
491
|
+
|
|
492
|
+
FSL-1.1-MIT (MIT Future). See `LICENSE`.
|
package/bin/lash
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
const binaryName = platform === 'win32' ? 'lash.exe' : 'lash';
|
|
8
|
+
const binaryPath = path.join(__dirname, binaryName);
|
|
9
|
+
|
|
10
|
+
// Check if binary exists
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
if (!fs.existsSync(binaryPath)) {
|
|
13
|
+
console.error('lash binary not found. Please reinstall the package.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Execute the binary with all arguments
|
|
18
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
windowsHide: false
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child.on('exit', (code) => {
|
|
24
|
+
process.exit(code);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.on('error', (err) => {
|
|
28
|
+
console.error('Failed to start lash:', err.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lacy",
|
|
3
|
+
"version": "0.6.4",
|
|
4
|
+
"description": "Terminal-based AI assistant for developers. A login-shell-friendly fork of Charmbracelet Crush with Shell, Agent, and Auto modes, plus built-in MCP support.",
|
|
5
|
+
"main": "bin/lash",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lash": "bin/lash"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/install.js"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/lacymorrow/lash.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"assistant",
|
|
19
|
+
"terminal",
|
|
20
|
+
"cli",
|
|
21
|
+
"developer-tools",
|
|
22
|
+
"llm",
|
|
23
|
+
"mcp",
|
|
24
|
+
"shell"
|
|
25
|
+
],
|
|
26
|
+
"author": "Lacy Morrow <me@lacymorrow.com>",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/lacymorrow/lash/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/lacymorrow/lash#readme",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=16"
|
|
34
|
+
},
|
|
35
|
+
"os": [
|
|
36
|
+
"darwin",
|
|
37
|
+
"linux",
|
|
38
|
+
"win32"
|
|
39
|
+
],
|
|
40
|
+
"cpu": [
|
|
41
|
+
"x64",
|
|
42
|
+
"arm64"
|
|
43
|
+
],
|
|
44
|
+
"files": [
|
|
45
|
+
"bin/",
|
|
46
|
+
"scripts/",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE.md"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const packageJson = require('../package.json');
|
|
9
|
+
const version = packageJson.version;
|
|
10
|
+
|
|
11
|
+
// Determine platform and architecture
|
|
12
|
+
const platform = process.platform;
|
|
13
|
+
const arch = process.arch;
|
|
14
|
+
|
|
15
|
+
// Map Node.js platform/arch to GoReleaser naming
|
|
16
|
+
const platformMap = {
|
|
17
|
+
'darwin': 'Darwin',
|
|
18
|
+
'linux': 'Linux',
|
|
19
|
+
'win32': 'Windows'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const archMap = {
|
|
23
|
+
'x64': 'x86_64',
|
|
24
|
+
'arm64': 'arm64'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const mappedPlatform = platformMap[platform];
|
|
28
|
+
const mappedArch = archMap[arch];
|
|
29
|
+
|
|
30
|
+
if (!mappedPlatform || !mappedArch) {
|
|
31
|
+
console.error(`Unsupported platform: ${platform} ${arch}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Construct download URL
|
|
36
|
+
const fileName = `lash_${version}_${mappedPlatform}_${mappedArch}`;
|
|
37
|
+
const archiveExt = platform === 'win32' ? 'zip' : 'tar.gz';
|
|
38
|
+
const downloadUrl = `https://github.com/lacymorrow/lash/releases/download/v${version}/${fileName}.${archiveExt}`;
|
|
39
|
+
|
|
40
|
+
console.log(`Downloading lash v${version} for ${platform} ${arch}...`);
|
|
41
|
+
console.log(`URL: ${downloadUrl}`);
|
|
42
|
+
|
|
43
|
+
// Create bin directory
|
|
44
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
45
|
+
if (!fs.existsSync(binDir)) {
|
|
46
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Download and extract
|
|
50
|
+
const tempFile = path.join(binDir, `lash.${archiveExt}`);
|
|
51
|
+
|
|
52
|
+
function download(url, dest) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const file = fs.createWriteStream(dest);
|
|
55
|
+
https.get(url, (response) => {
|
|
56
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
57
|
+
// Follow redirect
|
|
58
|
+
return download(response.headers.location, dest).then(resolve).catch(reject);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (response.statusCode !== 200) {
|
|
62
|
+
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
response.pipe(file);
|
|
67
|
+
file.on('finish', () => {
|
|
68
|
+
file.close();
|
|
69
|
+
resolve();
|
|
70
|
+
});
|
|
71
|
+
}).on('error', reject);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function install() {
|
|
76
|
+
try {
|
|
77
|
+
await download(downloadUrl, tempFile);
|
|
78
|
+
|
|
79
|
+
// Extract the archive
|
|
80
|
+
const extractDir = path.join(binDir, 'temp');
|
|
81
|
+
if (!fs.existsSync(extractDir)) {
|
|
82
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (platform === 'win32') {
|
|
86
|
+
// Extract zip (requires unzip or 7z)
|
|
87
|
+
try {
|
|
88
|
+
execSync(`powershell -command "Expand-Archive -Path '${tempFile}' -DestinationPath '${extractDir}' -Force"`, { stdio: 'inherit' });
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error('Failed to extract with PowerShell, trying 7z...');
|
|
91
|
+
execSync(`7z x "${tempFile}" -o"${extractDir}"`, { stdio: 'inherit' });
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
// Extract tar.gz
|
|
95
|
+
execSync(`tar -xzf "${tempFile}" -C "${extractDir}"`, { stdio: 'inherit' });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Find and move the binary
|
|
99
|
+
const binaryName = platform === 'win32' ? 'lash.exe' : 'lash';
|
|
100
|
+
const extractedBinary = path.join(extractDir, fileName, binaryName);
|
|
101
|
+
const finalBinary = path.join(binDir, binaryName);
|
|
102
|
+
|
|
103
|
+
if (fs.existsSync(extractedBinary)) {
|
|
104
|
+
fs.copyFileSync(extractedBinary, finalBinary);
|
|
105
|
+
|
|
106
|
+
// Make executable on Unix systems
|
|
107
|
+
if (platform !== 'win32') {
|
|
108
|
+
fs.chmodSync(finalBinary, 0o755);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(`✅ lash v${version} installed successfully!`);
|
|
112
|
+
console.log(`Binary location: ${finalBinary}`);
|
|
113
|
+
} else {
|
|
114
|
+
throw new Error(`Binary not found in extracted archive: ${extractedBinary}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Cleanup
|
|
118
|
+
fs.rmSync(tempFile, { force: true });
|
|
119
|
+
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
120
|
+
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Installation failed:', error.message);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
install();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ISSUES=$(gh issue list --state=all --limit=1000 --json "number" -t '{{range .}}{{printf "%.0f\n" .number}}{{end}}')
|
|
2
|
+
PRS=$(gh pr list --state=all --limit=1000 --json "number" -t '{{range .}}{{printf "%.0f\n" .number}}{{end}}')
|
|
3
|
+
|
|
4
|
+
for issue in $ISSUES; do
|
|
5
|
+
echo "Dispatching issue-labeler.yml for $issue"
|
|
6
|
+
gh workflow run issue-labeler.yml -f issue-number="$issue"
|
|
7
|
+
done
|
|
8
|
+
|
|
9
|
+
for pr in $PRS; do
|
|
10
|
+
echo "Dispatching issue-labeler.yml for $pr"
|
|
11
|
+
gh workflow run issue-labeler.yml -f issue-number="$pr"
|
|
12
|
+
done
|