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 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