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/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 grepResult = shell.grep(regex, filePath);
557
- if (grepResult.code !== 0 || !grepResult.stdout.trim()) {
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 = await fs.readFile(filePath, "utf-8");
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 result = await runGit(["checkout", "--", ...paths]);
931
- if (result.code !== 0) {
932
- throw Object.assign(new Error(result.stderr || "git checkout failed"), { code: "EGIT" });
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.113",
3
+ "version": "0.1.115",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",