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
package/package.json
CHANGED
|
@@ -1,42 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lacy",
|
|
3
|
-
"version": "1.8.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"bin": {
|
|
7
|
-
"lacy": "index.mjs"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"index.mjs"
|
|
11
|
-
],
|
|
3
|
+
"version": "1.8.13",
|
|
4
|
+
"description": "Talk to your terminal — AI agent routing for your shell",
|
|
5
|
+
"main": "lacy.plugin.zsh",
|
|
12
6
|
"scripts": {
|
|
13
|
-
"
|
|
14
|
-
"release": "
|
|
15
|
-
"release:beta": "
|
|
7
|
+
"install": "./install.sh",
|
|
8
|
+
"release": "bun run script/release.ts",
|
|
9
|
+
"release:beta": "bun run script/release.ts --beta"
|
|
16
10
|
},
|
|
17
11
|
"keywords": [
|
|
18
|
-
"lacy",
|
|
19
12
|
"shell",
|
|
20
13
|
"terminal",
|
|
21
|
-
"
|
|
14
|
+
"zsh",
|
|
15
|
+
"bash",
|
|
16
|
+
"shell-plugin",
|
|
22
17
|
"cli",
|
|
23
|
-
"
|
|
18
|
+
"ai",
|
|
24
19
|
"ai-agent",
|
|
25
|
-
"
|
|
20
|
+
"ai-shell",
|
|
21
|
+
"mcp",
|
|
22
|
+
"natural-language",
|
|
23
|
+
"claude",
|
|
24
|
+
"gemini",
|
|
25
|
+
"opencode",
|
|
26
|
+
"codex",
|
|
27
|
+
"developer-tools"
|
|
26
28
|
],
|
|
27
|
-
"author": "Lacy
|
|
28
|
-
"license": "MIT",
|
|
29
|
+
"author": "Lacy Shell Team",
|
|
30
|
+
"license": "FSL-1.1-MIT",
|
|
31
|
+
"dependencies": {},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@clack/prompts": "^1.0.0",
|
|
34
|
+
"picocolors": "^1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
29
39
|
"repository": {
|
|
30
40
|
"type": "git",
|
|
31
|
-
"url": "
|
|
32
|
-
"directory": "packages/lacy"
|
|
41
|
+
"url": "https://github.com/lacymorrow/lacy.git"
|
|
33
42
|
},
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"@clack/prompts": "^0.7.0",
|
|
37
|
-
"picocolors": "^1.0.0"
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/lacymorrow/lacy/issues"
|
|
38
45
|
},
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
"homepage": "https://lacy.sh",
|
|
47
|
+
"funding": [
|
|
48
|
+
{
|
|
49
|
+
"type": "github",
|
|
50
|
+
"url": "https://github.com/sponsors/lacymorrow"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"type": "individual",
|
|
54
|
+
"url": "https://www.buymeacoffee.com/lm"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
42
57
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# lacy
|
|
2
|
+
|
|
3
|
+
Interactive installer for [Lacy Shell](https://github.com/lacymorrow/lacy) — talk directly to your shell.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx lacy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Features:
|
|
12
|
+
- Arrow-key tool selection
|
|
13
|
+
- Auto-detects installed AI CLI tools
|
|
14
|
+
- Offers to install lash if selected
|
|
15
|
+
- Automatic shell restart
|
|
16
|
+
|
|
17
|
+
## Uninstall
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx lacy --uninstall
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Options
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Usage:
|
|
27
|
+
npx lacy Install Lacy Shell
|
|
28
|
+
npx lacy --uninstall Uninstall Lacy Shell
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
-h, --help Show help message
|
|
32
|
+
-u, --uninstall Uninstall Lacy Shell
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## What is Lacy Shell?
|
|
36
|
+
|
|
37
|
+
Lacy routes natural language to AI and commands to your shell — automatically.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
❯ ls -la → runs in shell
|
|
41
|
+
❯ what files are here → AI answers
|
|
42
|
+
❯ git status → runs in shell
|
|
43
|
+
❯ fix the build error → AI answers
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Works with: **lash**, **claude**, **opencode**, **gemini**, **codex**
|
|
47
|
+
|
|
48
|
+
## Alternative Install Methods
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# curl
|
|
52
|
+
curl -fsSL https://lacy.sh/install | bash
|
|
53
|
+
|
|
54
|
+
# Homebrew
|
|
55
|
+
brew tap lacymorrow/tap
|
|
56
|
+
brew install lacy
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Lacy Shell - Info command
|
|
4
|
+
# Shows basic information and guides users to setup
|
|
5
|
+
|
|
6
|
+
_lacy_info_version() {
|
|
7
|
+
local pkg="${HOME}/.lacy/package.json"
|
|
8
|
+
if [[ -f "$pkg" ]]; then
|
|
9
|
+
grep '"version"' "$pkg" 2>/dev/null | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//' | sed 's/".*//'
|
|
10
|
+
else
|
|
11
|
+
echo "unknown"
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
printf '\033[38;5;75m%s\033[0m\n' "🔧 Lacy Shell v$(_lacy_info_version)"
|
|
16
|
+
echo
|
|
17
|
+
printf '%s\n' "Lacy Shell detects natural language and routes it to AI coding agents."
|
|
18
|
+
echo
|
|
19
|
+
printf '%s\n' "Quick tips:"
|
|
20
|
+
printf ' • %s\n' "Type normally for shell commands"
|
|
21
|
+
printf ' • %s\n' "Type natural language for AI assistance"
|
|
22
|
+
printf ' • %s\n' "Press Ctrl+Space to toggle modes"
|
|
23
|
+
echo
|
|
24
|
+
printf '%b\n' "Run '\033[38;5;200mlacy setup\033[0m' to configure your AI tool and settings."
|
|
25
|
+
printf '%b\n' "Run '\033[38;5;200mlacy mode\033[0m' to see current mode and legend."
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
appendFileSync,
|
|
12
12
|
rmSync,
|
|
13
13
|
} from "fs";
|
|
14
|
-
import { homedir } from "os";
|
|
14
|
+
import { homedir, tmpdir } from "os";
|
|
15
15
|
import { join, dirname } from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
17
|
|
|
@@ -19,6 +19,7 @@ const INSTALL_DIR = join(homedir(), ".lacy");
|
|
|
19
19
|
const INSTALL_DIR_OLD = join(homedir(), ".lacy-shell");
|
|
20
20
|
const CONFIG_FILE = join(INSTALL_DIR, "config.yaml");
|
|
21
21
|
const REPO_URL = "https://github.com/lacymorrow/lacy.git";
|
|
22
|
+
const TARBALL_URL = "https://github.com/lacymorrow/lacy/archive/refs/heads";
|
|
22
23
|
|
|
23
24
|
// Version — read from installed package.json (single source of truth),
|
|
24
25
|
// fall back to this npm package's own package.json
|
|
@@ -80,7 +81,11 @@ function detectShell() {
|
|
|
80
81
|
const shell = process.env.SHELL || "";
|
|
81
82
|
const base = shell.split("/").pop();
|
|
82
83
|
if (base === "bash") return "bash";
|
|
83
|
-
return "zsh";
|
|
84
|
+
if (base === "zsh") return "zsh";
|
|
85
|
+
// Unknown $SHELL — check what's available
|
|
86
|
+
if (commandExists("zsh")) return "zsh";
|
|
87
|
+
if (commandExists("bash")) return "bash";
|
|
88
|
+
return "bash";
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
function getShellConfig(shell) {
|
|
@@ -164,6 +169,11 @@ const TOOLS = [
|
|
|
164
169
|
{ value: "opencode", label: "opencode", hint: "OpenCode CLI" },
|
|
165
170
|
{ value: "gemini", label: "gemini", hint: "Google Gemini CLI" },
|
|
166
171
|
{ value: "codex", label: "codex", hint: "OpenAI Codex CLI" },
|
|
172
|
+
{ value: "hermes", label: "hermes (beta)", hint: "Hermes Agent — Nous Research" },
|
|
173
|
+
{ value: "copilot", label: "copilot (beta)", hint: "GitHub Copilot CLI" },
|
|
174
|
+
{ value: "goose", label: "goose (beta)", hint: "Block's Goose AI agent" },
|
|
175
|
+
{ value: "amp", label: "amp", hint: "Sourcegraph Amp CLI" },
|
|
176
|
+
{ value: "aider", label: "aider", hint: "Aider AI pair programming — pipx install aider-chat" },
|
|
167
177
|
{ value: "custom", label: "Custom", hint: "enter your own command" },
|
|
168
178
|
{ value: "auto", label: "Auto-detect", hint: "use first available" },
|
|
169
179
|
{ value: "none", label: "None", hint: "I'll install one later" },
|
|
@@ -185,6 +195,14 @@ function commandExists(cmd) {
|
|
|
185
195
|
}
|
|
186
196
|
}
|
|
187
197
|
|
|
198
|
+
function installViaTarball(branch) {
|
|
199
|
+
const tmpFile = join(tmpdir(), `lacy-${Date.now()}.tar.gz`);
|
|
200
|
+
execSync(`curl -fsSL "${TARBALL_URL}/${branch}.tar.gz" -o "${tmpFile}"`, { stdio: "pipe" });
|
|
201
|
+
mkdirSync(INSTALL_DIR, { recursive: true });
|
|
202
|
+
execSync(`tar xzf "${tmpFile}" --strip-components=1 -C "${INSTALL_DIR}"`, { stdio: "pipe" });
|
|
203
|
+
try { rmSync(tmpFile); } catch {}
|
|
204
|
+
}
|
|
205
|
+
|
|
188
206
|
function isInstalled() {
|
|
189
207
|
return existsSync(INSTALL_DIR) || existsSync(INSTALL_DIR_OLD);
|
|
190
208
|
}
|
|
@@ -238,6 +256,13 @@ async function restartShell(
|
|
|
238
256
|
// Restore terminal state before handing off to the new shell
|
|
239
257
|
restoreTerminalState();
|
|
240
258
|
|
|
259
|
+
// Remove signal handlers so Ctrl+C in the child shell doesn't kill Node.
|
|
260
|
+
// The child shell manages its own signals; Node just waits for it to exit.
|
|
261
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
262
|
+
process.removeAllListeners(sig);
|
|
263
|
+
}
|
|
264
|
+
process.on("SIGINT", () => {});
|
|
265
|
+
|
|
241
266
|
// Spawn a new login shell that inherits our stdio, then exit Node.
|
|
242
267
|
// We use spawn (not execSync) to avoid creating a nested shell —
|
|
243
268
|
// execSync("exec ...") only replaces the *child* process, not Node,
|
|
@@ -378,11 +403,20 @@ async function install() {
|
|
|
378
403
|
if (shell === "bash") {
|
|
379
404
|
if (commandExists("bash")) {
|
|
380
405
|
try {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
406
|
+
// Use $SHELL if it's bash — on macOS, plain `bash` resolves to /bin/bash (3.2)
|
|
407
|
+
// even when the user has a newer bash (e.g. 5.x from Homebrew) as their login shell
|
|
408
|
+
const shellEnv = process.env.SHELL || "";
|
|
409
|
+
const userBash = shellEnv.endsWith("/bash") ? shellEnv : "bash";
|
|
410
|
+
let bashVer;
|
|
411
|
+
try {
|
|
412
|
+
bashVer = execSync(`"${userBash}" -c "echo \\${BASH_VERSINFO[0]}"`, {
|
|
413
|
+
stdio: "pipe",
|
|
414
|
+
}).toString().trim();
|
|
415
|
+
} catch {
|
|
416
|
+
bashVer = execSync('bash -c "echo ${BASH_VERSINFO[0]}"', {
|
|
417
|
+
stdio: "pipe",
|
|
418
|
+
}).toString().trim();
|
|
419
|
+
}
|
|
386
420
|
if (parseInt(bashVer) < 4) {
|
|
387
421
|
missing.push(
|
|
388
422
|
`bash 4+ (found bash ${bashVer}, upgrade with: brew install bash)`,
|
|
@@ -398,8 +432,6 @@ async function install() {
|
|
|
398
432
|
if (!commandExists("zsh")) missing.push("zsh");
|
|
399
433
|
}
|
|
400
434
|
|
|
401
|
-
if (!commandExists("git")) missing.push("git");
|
|
402
|
-
|
|
403
435
|
if (missing.length > 0) {
|
|
404
436
|
prerequisites.stop("Prerequisites check failed");
|
|
405
437
|
p.log.error(`Missing required tools: ${missing.join(", ")}`);
|
|
@@ -411,7 +443,7 @@ async function install() {
|
|
|
411
443
|
|
|
412
444
|
// Detect installed tools
|
|
413
445
|
let detected = [];
|
|
414
|
-
for (const tool of
|
|
446
|
+
for (const tool of TOOLS.filter((t) => !["custom", "auto", "none"].includes(t.value)).map((t) => t.value)) {
|
|
415
447
|
if (commandExists(tool)) {
|
|
416
448
|
detected.push(tool);
|
|
417
449
|
}
|
|
@@ -552,28 +584,217 @@ async function install() {
|
|
|
552
584
|
}
|
|
553
585
|
}
|
|
554
586
|
|
|
587
|
+
// Offer to install hermes if selected but not installed
|
|
588
|
+
if (selectedTool === "hermes" && !commandExists("hermes")) {
|
|
589
|
+
const installHermes = await p.confirm({
|
|
590
|
+
message: "hermes is not installed. Would you like to install it now?",
|
|
591
|
+
initialValue: true,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
if (p.isCancel(installHermes)) {
|
|
595
|
+
p.cancel("Installation cancelled");
|
|
596
|
+
process.exit(0);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (installHermes) {
|
|
600
|
+
p.log.info("Running hermes installer...");
|
|
601
|
+
|
|
602
|
+
try {
|
|
603
|
+
if (commandExists("curl")) {
|
|
604
|
+
execSync(
|
|
605
|
+
"curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash",
|
|
606
|
+
{ stdio: "inherit" },
|
|
607
|
+
);
|
|
608
|
+
p.log.success("hermes installed");
|
|
609
|
+
} else {
|
|
610
|
+
p.log.warn(
|
|
611
|
+
"curl not found. Install manually: curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash",
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
} catch (e) {
|
|
615
|
+
p.log.warn(
|
|
616
|
+
"Installation failed. You can install manually: curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash",
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Offer to install copilot if selected but not installed
|
|
623
|
+
if (selectedTool === "copilot" && !commandExists("copilot")) {
|
|
624
|
+
const installCopilot = await p.confirm({
|
|
625
|
+
message: "copilot is not installed. Would you like to install it now?",
|
|
626
|
+
initialValue: true,
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
if (p.isCancel(installCopilot)) {
|
|
630
|
+
p.cancel("Installation cancelled");
|
|
631
|
+
process.exit(0);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (installCopilot) {
|
|
635
|
+
p.log.info("Installing copilot via gh extension...");
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
if (commandExists("gh")) {
|
|
639
|
+
execSync("gh extension install github/gh-copilot", { stdio: "inherit" });
|
|
640
|
+
p.log.success("copilot installed");
|
|
641
|
+
} else {
|
|
642
|
+
p.log.warn(
|
|
643
|
+
"gh CLI not found. Install manually: gh extension install github/gh-copilot",
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
} catch (e) {
|
|
647
|
+
p.log.warn(
|
|
648
|
+
`Installation failed: ${e.message}. You can install manually: gh extension install github/gh-copilot`,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Offer to install goose if selected but not installed
|
|
655
|
+
if (selectedTool === "goose" && !commandExists("goose")) {
|
|
656
|
+
const installGoose = await p.confirm({
|
|
657
|
+
message: "goose is not installed. Would you like to install it now?",
|
|
658
|
+
initialValue: true,
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
if (p.isCancel(installGoose)) {
|
|
662
|
+
p.cancel("Installation cancelled");
|
|
663
|
+
process.exit(0);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (installGoose) {
|
|
667
|
+
const gooseSpinner = p.spinner();
|
|
668
|
+
gooseSpinner.start("Installing goose");
|
|
669
|
+
|
|
670
|
+
try {
|
|
671
|
+
if (commandExists("brew")) {
|
|
672
|
+
execSync("brew install goose", { stdio: "pipe" });
|
|
673
|
+
gooseSpinner.stop("goose installed");
|
|
674
|
+
} else {
|
|
675
|
+
gooseSpinner.stop("Could not install goose");
|
|
676
|
+
p.log.warn(
|
|
677
|
+
"Please install homebrew, then run: brew install goose",
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
} catch (e) {
|
|
681
|
+
gooseSpinner.stop(`goose installation failed: ${e.message}`);
|
|
682
|
+
p.log.warn(
|
|
683
|
+
"You can install it manually later: brew install goose",
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Offer to install amp if selected but not installed
|
|
690
|
+
if (selectedTool === "amp" && !commandExists("amp")) {
|
|
691
|
+
const installAmp = await p.confirm({
|
|
692
|
+
message: "amp is not installed. Would you like to install it now?",
|
|
693
|
+
initialValue: true,
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
if (p.isCancel(installAmp)) {
|
|
697
|
+
p.cancel("Installation cancelled");
|
|
698
|
+
process.exit(0);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (installAmp) {
|
|
702
|
+
const ampSpinner = p.spinner();
|
|
703
|
+
ampSpinner.start("Installing amp");
|
|
704
|
+
|
|
705
|
+
try {
|
|
706
|
+
if (commandExists("npm")) {
|
|
707
|
+
execSync("npm install -g @sourcegraph/amp", { stdio: "pipe" });
|
|
708
|
+
ampSpinner.stop("amp installed");
|
|
709
|
+
} else {
|
|
710
|
+
ampSpinner.stop("Could not install amp");
|
|
711
|
+
p.log.warn(
|
|
712
|
+
"Please install npm, then run: npm install -g @sourcegraph/amp",
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
} catch (e) {
|
|
716
|
+
ampSpinner.stop("amp installation failed");
|
|
717
|
+
p.log.warn(
|
|
718
|
+
"You can install it manually later: npm install -g @sourcegraph/amp",
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Offer to install aider if selected but not installed
|
|
725
|
+
if (selectedTool === "aider" && !commandExists("aider")) {
|
|
726
|
+
const installAider = await p.confirm({
|
|
727
|
+
message: "aider is not installed. Would you like to install it now?",
|
|
728
|
+
initialValue: true,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
if (p.isCancel(installAider)) {
|
|
732
|
+
p.cancel("Installation cancelled");
|
|
733
|
+
process.exit(0);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (installAider) {
|
|
737
|
+
const aiderSpinner = p.spinner();
|
|
738
|
+
aiderSpinner.start("Installing aider");
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
if (commandExists("pipx")) {
|
|
742
|
+
execSync("pipx install aider-chat", { stdio: "pipe" });
|
|
743
|
+
aiderSpinner.stop("aider installed");
|
|
744
|
+
} else if (commandExists("pip3")) {
|
|
745
|
+
execSync("pip3 install --user aider-chat", { stdio: "pipe" });
|
|
746
|
+
aiderSpinner.stop("aider installed");
|
|
747
|
+
} else {
|
|
748
|
+
aiderSpinner.stop("Could not install aider");
|
|
749
|
+
p.log.warn(
|
|
750
|
+
"Please install pipx, then run: pipx install aider-chat",
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
} catch (e) {
|
|
754
|
+
aiderSpinner.stop("aider installation failed");
|
|
755
|
+
p.log.warn(
|
|
756
|
+
"You can install it manually later: pipx install aider-chat",
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
555
762
|
// Clone/update repository
|
|
556
763
|
const installSpinner = p.spinner();
|
|
557
764
|
installSpinner.start("Installing Lacy");
|
|
558
765
|
|
|
766
|
+
const hasGit = commandExists("git");
|
|
767
|
+
|
|
559
768
|
try {
|
|
560
769
|
if (existsSync(INSTALL_DIR)) {
|
|
561
770
|
// Update existing
|
|
562
771
|
try {
|
|
563
|
-
|
|
772
|
+
if (hasGit && existsSync(join(INSTALL_DIR, ".git"))) {
|
|
773
|
+
execSync("git pull origin main", { cwd: INSTALL_DIR, stdio: "pipe" });
|
|
774
|
+
} else {
|
|
775
|
+
installViaTarball("main");
|
|
776
|
+
}
|
|
564
777
|
} catch {
|
|
565
|
-
// Ignore
|
|
778
|
+
// Ignore update errors, use existing
|
|
566
779
|
}
|
|
567
780
|
installSpinner.stop("Lacy updated");
|
|
568
781
|
} else {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
782
|
+
if (hasGit) {
|
|
783
|
+
try {
|
|
784
|
+
execSync(`git clone --depth 1 ${REPO_URL} "${INSTALL_DIR}"`, {
|
|
785
|
+
stdio: "pipe",
|
|
786
|
+
});
|
|
787
|
+
} catch {
|
|
788
|
+
installViaTarball("main");
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
installViaTarball("main");
|
|
792
|
+
}
|
|
572
793
|
installSpinner.stop("Lacy installed");
|
|
573
794
|
}
|
|
574
795
|
} catch (e) {
|
|
575
796
|
installSpinner.stop("Installation failed");
|
|
576
|
-
p.log.error(`Could not
|
|
797
|
+
p.log.error(`Could not download repository: ${e.message}`);
|
|
577
798
|
p.outro(pc.red("Installation failed"));
|
|
578
799
|
process.exit(1);
|
|
579
800
|
}
|
|
@@ -776,7 +997,7 @@ ${pc.dim("https://github.com/lacymorrow/lacy")}
|
|
|
776
997
|
const active = readConfigValue("active");
|
|
777
998
|
const mode = readConfigValue("default");
|
|
778
999
|
const detected = [];
|
|
779
|
-
for (const tool of
|
|
1000
|
+
for (const tool of TOOLS.filter((t) => !["custom", "auto", "none"].includes(t.value)).map((t) => t.value)) {
|
|
780
1001
|
if (commandExists(tool)) detected.push(tool);
|
|
781
1002
|
}
|
|
782
1003
|
|
|
@@ -937,7 +1158,7 @@ ${pc.dim("https://github.com/lacymorrow/lacy")}
|
|
|
937
1158
|
` Mode: ${pc.cyan(modeDisplay)}`,
|
|
938
1159
|
``,
|
|
939
1160
|
` ${pc.bold("AI CLI tools:")}`,
|
|
940
|
-
...
|
|
1161
|
+
...TOOLS.filter((t) => !["custom", "auto", "none"].includes(t.value)).map(({ value: t }) =>
|
|
941
1162
|
commandExists(t)
|
|
942
1163
|
? ` ${pc.green("✓")} ${t}`
|
|
943
1164
|
: ` ${pc.dim("○")} ${pc.dim(t)}`,
|
|
@@ -960,7 +1181,11 @@ ${pc.dim("https://github.com/lacymorrow/lacy")}
|
|
|
960
1181
|
? INSTALL_DIR
|
|
961
1182
|
: INSTALL_DIR_OLD;
|
|
962
1183
|
try {
|
|
963
|
-
|
|
1184
|
+
if (commandExists("git") && existsSync(join(updateDir, ".git"))) {
|
|
1185
|
+
execSync("git pull origin main", { cwd: updateDir, stdio: "pipe" });
|
|
1186
|
+
} else {
|
|
1187
|
+
installViaTarball("main");
|
|
1188
|
+
}
|
|
964
1189
|
const updatedVersion = getVersion();
|
|
965
1190
|
updateSpinner.stop(`Lacy updated to v${updatedVersion}`);
|
|
966
1191
|
trackEvent("update", "npx");
|
|
@@ -1011,7 +1236,9 @@ ${pc.dim("https://github.com/lacymorrow/lacy")}
|
|
|
1011
1236
|
await install();
|
|
1012
1237
|
}
|
|
1013
1238
|
|
|
1014
|
-
main().
|
|
1239
|
+
main().then(() => {
|
|
1240
|
+
process.exit(0);
|
|
1241
|
+
}).catch((e) => {
|
|
1015
1242
|
restoreTerminalState();
|
|
1016
1243
|
p.log.error(e.message);
|
|
1017
1244
|
process.exit(1);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lacy",
|
|
3
|
+
"version": "1.7.2",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "lacy",
|
|
9
|
+
"version": "1.7.2",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@clack/prompts": "^0.7.0",
|
|
13
|
+
"picocolors": "^1.0.0"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"lacy": "index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"node_modules/@clack/core": {
|
|
23
|
+
"version": "0.3.5",
|
|
24
|
+
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.5.tgz",
|
|
25
|
+
"integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"picocolors": "^1.0.0",
|
|
29
|
+
"sisteransi": "^1.0.5"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"node_modules/@clack/prompts": {
|
|
33
|
+
"version": "0.7.0",
|
|
34
|
+
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz",
|
|
35
|
+
"integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==",
|
|
36
|
+
"bundleDependencies": [
|
|
37
|
+
"is-unicode-supported"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@clack/core": "^0.3.3",
|
|
42
|
+
"is-unicode-supported": "*",
|
|
43
|
+
"picocolors": "^1.0.0",
|
|
44
|
+
"sisteransi": "^1.0.5"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
|
48
|
+
"version": "1.3.0",
|
|
49
|
+
"inBundle": true,
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=12"
|
|
53
|
+
},
|
|
54
|
+
"funding": {
|
|
55
|
+
"url": "https://github.com/sponsors/sindresorhus"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"node_modules/picocolors": {
|
|
59
|
+
"version": "1.2.0",
|
|
60
|
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
|
61
|
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
|
62
|
+
"license": "ISC"
|
|
63
|
+
},
|
|
64
|
+
"node_modules/sisteransi": {
|
|
65
|
+
"version": "1.0.5",
|
|
66
|
+
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
|
67
|
+
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
|
68
|
+
"license": "MIT"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lacy",
|
|
3
|
+
"version": "1.8.11",
|
|
4
|
+
"description": "Install lacy — talk to your terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lacy": "index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.mjs"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "node index.mjs",
|
|
14
|
+
"release": "npm publish",
|
|
15
|
+
"release:beta": "npm version prerelease --preid=beta && npm publish --tag beta"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"lacy",
|
|
19
|
+
"shell",
|
|
20
|
+
"terminal",
|
|
21
|
+
"ai",
|
|
22
|
+
"cli",
|
|
23
|
+
"developer-tools",
|
|
24
|
+
"ai-agent",
|
|
25
|
+
"natural-language"
|
|
26
|
+
],
|
|
27
|
+
"author": "Lacy Morrow",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/lacymorrow/lacy.git",
|
|
32
|
+
"directory": "packages/lacy"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://lacy.sh",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@clack/prompts": "^0.7.0",
|
|
37
|
+
"picocolors": "^1.0.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
}
|
|
42
|
+
}
|