lunel-cli 0.1.113 → 0.1.115
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/dist/ai/codex.d.ts +19 -4
- package/dist/ai/codex.js +470 -95
- package/dist/ai/index.d.ts +5 -2
- package/dist/ai/index.js +3 -2
- package/dist/ai/interface.d.ts +9 -1
- package/dist/ai/opencode.d.ts +7 -3
- package/dist/ai/opencode.js +319 -35
- package/dist/index.js +126 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,6 +31,10 @@ const __require = createRequire(import.meta.url);
|
|
|
31
31
|
const VERSION = __require("../package.json").version;
|
|
32
32
|
const VERBOSE_AI_LOGS = process.env.LUNEL_DEBUG_AI === "1";
|
|
33
33
|
const PTY_RELEASE_BASE_URL = "https://github.com/lunel-dev/lunel/releases/download/v0";
|
|
34
|
+
const AI_RUNTIME_INSTALL_CANDIDATES = {
|
|
35
|
+
opencode: ["opencode-ai", "@opencode-ai/cli", "opencode"],
|
|
36
|
+
codex: ["@openai/codex", "codex"],
|
|
37
|
+
};
|
|
34
38
|
const PTY_RELEASES = {
|
|
35
39
|
"linux:x64": {
|
|
36
40
|
fileName: "lunel-pty-linux-x8664-0",
|
|
@@ -529,6 +533,34 @@ async function loadGitignore(dirPath) {
|
|
|
529
533
|
}
|
|
530
534
|
return ig;
|
|
531
535
|
}
|
|
536
|
+
const KNOWN_BINARY_EXTENSIONS = new Set([
|
|
537
|
+
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".avif", ".ico", ".icns", ".heic", ".heif", ".tiff", ".tif",
|
|
538
|
+
".psd", ".ai", ".eps",
|
|
539
|
+
".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac",
|
|
540
|
+
".mp4", ".mov", ".avi", ".mkv", ".webm", ".wmv", ".m4v",
|
|
541
|
+
".pdf", ".zip", ".gz", ".tgz", ".bz2", ".xz", ".7z", ".rar", ".tar",
|
|
542
|
+
".exe", ".dll", ".so", ".dylib", ".bin", ".class", ".o", ".obj", ".a", ".lib",
|
|
543
|
+
".ttf", ".otf", ".woff", ".woff2", ".eot",
|
|
544
|
+
]);
|
|
545
|
+
function isLikelyBinaryContent(content) {
|
|
546
|
+
if (content.length === 0)
|
|
547
|
+
return false;
|
|
548
|
+
const sample = content.subarray(0, Math.min(content.length, 8192));
|
|
549
|
+
let suspicious = 0;
|
|
550
|
+
for (const byte of sample) {
|
|
551
|
+
if (byte === 0)
|
|
552
|
+
return true; // Null bytes strongly indicate binary data.
|
|
553
|
+
if (byte < 7 || (byte > 13 && byte < 32))
|
|
554
|
+
suspicious += 1;
|
|
555
|
+
}
|
|
556
|
+
return suspicious / sample.length > 0.3;
|
|
557
|
+
}
|
|
558
|
+
function shouldSkipAsBinary(filePath, content) {
|
|
559
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
560
|
+
if (KNOWN_BINARY_EXTENSIONS.has(ext))
|
|
561
|
+
return true;
|
|
562
|
+
return isLikelyBinaryContent(content);
|
|
563
|
+
}
|
|
532
564
|
async function handleFsGrep(payload) {
|
|
533
565
|
const reqPath = payload.path || ".";
|
|
534
566
|
const pattern = payload.pattern;
|
|
@@ -553,12 +585,12 @@ async function handleFsGrep(payload) {
|
|
|
553
585
|
if (matches.length >= maxResults)
|
|
554
586
|
return;
|
|
555
587
|
try {
|
|
556
|
-
const
|
|
557
|
-
if (
|
|
588
|
+
const rawContent = await fs.readFile(filePath);
|
|
589
|
+
if (shouldSkipAsBinary(relativePath, rawContent)) {
|
|
558
590
|
regex.lastIndex = 0;
|
|
559
591
|
return;
|
|
560
592
|
}
|
|
561
|
-
const content =
|
|
593
|
+
const content = rawContent.toString("utf-8");
|
|
562
594
|
const lines = content.split("\n");
|
|
563
595
|
for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
|
|
564
596
|
if (regex.test(lines[i])) {
|
|
@@ -927,9 +959,20 @@ async function handleGitDiscard(payload) {
|
|
|
927
959
|
await runGit(["clean", "-fd"]);
|
|
928
960
|
}
|
|
929
961
|
else if (paths && paths.length > 0) {
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
962
|
+
for (const filePath of paths) {
|
|
963
|
+
const tracked = await runGit(["ls-files", "--error-unmatch", "--", filePath]);
|
|
964
|
+
if (tracked.code === 0) {
|
|
965
|
+
const result = await runGit(["checkout", "--", filePath]);
|
|
966
|
+
if (result.code !== 0) {
|
|
967
|
+
throw Object.assign(new Error(result.stderr || `git checkout failed for ${filePath}`), { code: "EGIT" });
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
const cleanResult = await runGit(["clean", "-fd", "--", filePath]);
|
|
972
|
+
if (cleanResult.code !== 0) {
|
|
973
|
+
throw Object.assign(new Error(cleanResult.stderr || `git clean failed for ${filePath}`), { code: "EGIT" });
|
|
974
|
+
}
|
|
975
|
+
}
|
|
933
976
|
}
|
|
934
977
|
}
|
|
935
978
|
return {};
|
|
@@ -2617,7 +2660,7 @@ async function processMessage(message) {
|
|
|
2617
2660
|
result = { backends: aiManager.availableBackends() };
|
|
2618
2661
|
break;
|
|
2619
2662
|
case "prompt":
|
|
2620
|
-
result = await aiManager.prompt(backend, payload.sessionId, payload.text, payload.model, payload.agent, payload.files);
|
|
2663
|
+
result = await aiManager.prompt(backend, payload.sessionId, payload.text, payload.model, payload.agent, payload.files, payload.codexOptions);
|
|
2621
2664
|
break;
|
|
2622
2665
|
case "createSession":
|
|
2623
2666
|
result = await aiManager.createSession(backend, payload.title);
|
|
@@ -2631,6 +2674,9 @@ async function processMessage(message) {
|
|
|
2631
2674
|
case "deleteSession":
|
|
2632
2675
|
result = await aiManager.deleteSession(backend, payload.id);
|
|
2633
2676
|
break;
|
|
2677
|
+
case "renameSession":
|
|
2678
|
+
result = await aiManager.renameSession(backend, payload.id, payload.title);
|
|
2679
|
+
break;
|
|
2634
2680
|
case "getMessages":
|
|
2635
2681
|
result = await aiManager.getMessages(backend, payload.id);
|
|
2636
2682
|
break;
|
|
@@ -2945,6 +2991,78 @@ function displaySavedSessionNotice() {
|
|
|
2945
2991
|
console.log(`${red}${border}${reset}`);
|
|
2946
2992
|
console.log("");
|
|
2947
2993
|
}
|
|
2994
|
+
function isCommandAvailable(command) {
|
|
2995
|
+
const probe = spawnSync(command, ["--version"], {
|
|
2996
|
+
stdio: "ignore",
|
|
2997
|
+
shell: process.platform === "win32",
|
|
2998
|
+
});
|
|
2999
|
+
const err = probe.error;
|
|
3000
|
+
if (err && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
3001
|
+
return false;
|
|
3002
|
+
}
|
|
3003
|
+
return !err;
|
|
3004
|
+
}
|
|
3005
|
+
function askYesNo(question, defaultValue = false) {
|
|
3006
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
3007
|
+
return Promise.resolve(false);
|
|
3008
|
+
return new Promise((resolve) => {
|
|
3009
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3010
|
+
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
3011
|
+
rl.question(`${question}${suffix}`, (answer) => {
|
|
3012
|
+
rl.close();
|
|
3013
|
+
const normalized = answer.trim().toLowerCase();
|
|
3014
|
+
if (!normalized) {
|
|
3015
|
+
resolve(defaultValue);
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
resolve(normalized === "y" || normalized === "yes");
|
|
3019
|
+
});
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
function installLatestNpmPackage(pkg) {
|
|
3023
|
+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3024
|
+
const result = spawnSync(npmCommand, ["install", "-g", `${pkg}@latest`], {
|
|
3025
|
+
stdio: "inherit",
|
|
3026
|
+
shell: process.platform === "win32",
|
|
3027
|
+
env: process.env,
|
|
3028
|
+
});
|
|
3029
|
+
return !result.error && result.status === 0;
|
|
3030
|
+
}
|
|
3031
|
+
async function ensureAiCliRuntimes() {
|
|
3032
|
+
const missingBackends = Object.keys(AI_RUNTIME_INSTALL_CANDIDATES)
|
|
3033
|
+
.filter((backend) => !isCommandAvailable(backend));
|
|
3034
|
+
if (missingBackends.length === 0)
|
|
3035
|
+
return;
|
|
3036
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3037
|
+
console.warn(`[ai] Missing runtimes: ${missingBackends.join(", ")}. Run in an interactive shell to install them.`);
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
const installPrompt = `Missing AI runtimes (${missingBackends.join(", ")}). Install latest versions now?`;
|
|
3041
|
+
const approved = await askYesNo(installPrompt, false);
|
|
3042
|
+
if (!approved) {
|
|
3043
|
+
console.warn("[ai] Skipping AI runtime installation.");
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
for (const backend of missingBackends) {
|
|
3047
|
+
if (isCommandAvailable(backend))
|
|
3048
|
+
continue;
|
|
3049
|
+
const candidates = AI_RUNTIME_INSTALL_CANDIDATES[backend];
|
|
3050
|
+
let installed = false;
|
|
3051
|
+
for (const pkg of candidates) {
|
|
3052
|
+
console.log(`[ai] Installing ${backend} via npm package ${pkg}@latest...`);
|
|
3053
|
+
if (!installLatestNpmPackage(pkg))
|
|
3054
|
+
continue;
|
|
3055
|
+
if (isCommandAvailable(backend)) {
|
|
3056
|
+
installed = true;
|
|
3057
|
+
console.log(`[ai] ${backend} installed successfully.`);
|
|
3058
|
+
break;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
if (!installed) {
|
|
3062
|
+
console.warn(`[ai] Failed to install ${backend}. You can install it manually and restart the CLI.`);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2948
3066
|
function gracefulShutdown() {
|
|
2949
3067
|
shuttingDown = true;
|
|
2950
3068
|
console.log("\nShutting down...");
|
|
@@ -3125,6 +3243,7 @@ async function main() {
|
|
|
3125
3243
|
else {
|
|
3126
3244
|
debugLog(`PTY runtime unsupported on ${os.platform()}/${os.arch()}. Skipping prefetch.\n`);
|
|
3127
3245
|
}
|
|
3246
|
+
await ensureAiCliRuntimes();
|
|
3128
3247
|
// Start AI backends in the background so missing or slow AI runtimes never
|
|
3129
3248
|
// block QR/session startup for the rest of the CLI.
|
|
3130
3249
|
startAiManagerInBackground();
|