lunel-cli 0.1.100 → 0.1.102
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 +107 -133
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { WebSocket } from "ws";
|
|
3
3
|
import qrcode from "qrcode-terminal";
|
|
4
|
+
import shell from "shelljs";
|
|
4
5
|
import { createAiManager } from "./ai/index.js";
|
|
5
6
|
import { V2SessionTransport } from "./transport/v2.js";
|
|
6
7
|
import Ignore from "ignore";
|
|
@@ -10,7 +11,7 @@ import * as fssync from "fs";
|
|
|
10
11
|
import * as path from "path";
|
|
11
12
|
import * as os from "os";
|
|
12
13
|
import { randomBytes } from "crypto";
|
|
13
|
-
import { spawn, spawnSync, execSync
|
|
14
|
+
import { spawn, spawnSync, execSync } from "child_process";
|
|
14
15
|
import { createServer, createConnection } from "net";
|
|
15
16
|
import { createInterface } from "readline";
|
|
16
17
|
const DEFAULT_PROXY_URL = normalizeGatewayUrl(process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev");
|
|
@@ -28,9 +29,22 @@ if (DEBUG_MODE) {
|
|
|
28
29
|
import { createRequire } from "module";
|
|
29
30
|
const __require = createRequire(import.meta.url);
|
|
30
31
|
const VERSION = __require("../package.json").version;
|
|
31
|
-
const PTY_RELEASE_INFO_URL = process.env.LUNEL_PTY_INFO_URL ||
|
|
32
|
-
"https://raw.githubusercontent.com/ssbharambe-m/pty-releases/refs/heads/main/info.json";
|
|
33
32
|
const VERBOSE_AI_LOGS = process.env.LUNEL_DEBUG_AI === "1";
|
|
33
|
+
const PTY_RELEASE_BASE_URL = "https://github.com/lunel-dev/lunel/releases/download/v0";
|
|
34
|
+
const PTY_RELEASES = {
|
|
35
|
+
"linux:x64": {
|
|
36
|
+
fileName: "lunel-pty-linux-x8664-0",
|
|
37
|
+
url: `${PTY_RELEASE_BASE_URL}/lunel-pty-linux-x8664-0`,
|
|
38
|
+
},
|
|
39
|
+
"darwin:arm64": {
|
|
40
|
+
fileName: "lunel-pty-macos-arm64-0",
|
|
41
|
+
url: `${PTY_RELEASE_BASE_URL}/lunel-pty-macos-arm64-0`,
|
|
42
|
+
},
|
|
43
|
+
"win32:x64": {
|
|
44
|
+
fileName: "lunel-pty-windows-x8664-0.exe",
|
|
45
|
+
url: `${PTY_RELEASE_BASE_URL}/lunel-pty-windows-x8664-0.exe`,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
34
48
|
// Root directory - sandbox all file operations to this
|
|
35
49
|
const ROOT_DIR = (() => {
|
|
36
50
|
try {
|
|
@@ -566,64 +580,82 @@ async function handleFsGrep(payload) {
|
|
|
566
580
|
throw Object.assign(new Error("pattern is required"), { code: "EINVAL" });
|
|
567
581
|
const safePath = assertSafePath(reqPath);
|
|
568
582
|
const matches = [];
|
|
569
|
-
|
|
583
|
+
let regex;
|
|
584
|
+
try {
|
|
585
|
+
regex = new RegExp(pattern, caseSensitive ? "g" : "gi");
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
throw Object.assign(new Error("pattern must be a valid regular expression"), { code: "EINVAL" });
|
|
589
|
+
}
|
|
570
590
|
const rootIgnore = await loadGitignore(ROOT_DIR);
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
591
|
+
const previousSilent = shell.config.silent;
|
|
592
|
+
shell.config.silent = true;
|
|
593
|
+
try {
|
|
594
|
+
async function searchFile(filePath, relativePath) {
|
|
595
|
+
if (matches.length >= maxResults)
|
|
596
|
+
return;
|
|
597
|
+
try {
|
|
598
|
+
const grepResult = shell.grep(regex, filePath);
|
|
599
|
+
if (grepResult.code !== 0 || !grepResult.stdout.trim()) {
|
|
600
|
+
regex.lastIndex = 0;
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
604
|
+
const lines = content.split("\n");
|
|
605
|
+
for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
|
|
606
|
+
if (regex.test(lines[i])) {
|
|
607
|
+
matches.push({
|
|
608
|
+
file: relativePath,
|
|
609
|
+
line: i + 1,
|
|
610
|
+
content: lines[i].substring(0, 500),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
regex.lastIndex = 0;
|
|
584
614
|
}
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
585
617
|
regex.lastIndex = 0;
|
|
586
618
|
}
|
|
587
619
|
}
|
|
588
|
-
|
|
589
|
-
// Skip unreadable files
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
async function searchDir(dirPath, relativePath, ig) {
|
|
593
|
-
if (matches.length >= maxResults)
|
|
594
|
-
return;
|
|
595
|
-
const localIgnore = ignore().add(ig);
|
|
596
|
-
try {
|
|
597
|
-
const localGitignorePath = path.join(dirPath, ".gitignore");
|
|
598
|
-
const content = await fs.readFile(localGitignorePath, "utf-8");
|
|
599
|
-
localIgnore.add(content);
|
|
600
|
-
}
|
|
601
|
-
catch {
|
|
602
|
-
// No local .gitignore
|
|
603
|
-
}
|
|
604
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
605
|
-
for (const entry of entries) {
|
|
620
|
+
async function searchDir(dirPath, relativePath, ig) {
|
|
606
621
|
if (matches.length >= maxResults)
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (entry.isDirectory()) {
|
|
614
|
-
await searchDir(fullPath, relPath, localIgnore);
|
|
622
|
+
return;
|
|
623
|
+
const localIgnore = ignore().add(ig);
|
|
624
|
+
try {
|
|
625
|
+
const localGitignorePath = path.join(dirPath, ".gitignore");
|
|
626
|
+
const content = await fs.readFile(localGitignorePath, "utf-8");
|
|
627
|
+
localIgnore.add(content);
|
|
615
628
|
}
|
|
616
|
-
|
|
617
|
-
|
|
629
|
+
catch {
|
|
630
|
+
// No local .gitignore
|
|
631
|
+
}
|
|
632
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
633
|
+
for (const entry of entries) {
|
|
634
|
+
if (matches.length >= maxResults)
|
|
635
|
+
break;
|
|
636
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
637
|
+
const checkPath = entry.isDirectory() ? `${relPath}/` : relPath;
|
|
638
|
+
if (localIgnore.ignores(checkPath))
|
|
639
|
+
continue;
|
|
640
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
641
|
+
if (entry.isDirectory()) {
|
|
642
|
+
await searchDir(fullPath, relPath, localIgnore);
|
|
643
|
+
}
|
|
644
|
+
else if (entry.isFile()) {
|
|
645
|
+
await searchFile(fullPath, relPath);
|
|
646
|
+
}
|
|
618
647
|
}
|
|
619
648
|
}
|
|
649
|
+
const stat = await fs.stat(safePath);
|
|
650
|
+
if (stat.isDirectory()) {
|
|
651
|
+
await searchDir(safePath, reqPath === "." ? "" : reqPath, rootIgnore);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
await searchFile(safePath, reqPath);
|
|
655
|
+
}
|
|
620
656
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
await searchDir(safePath, reqPath === "." ? "" : reqPath, rootIgnore);
|
|
624
|
-
}
|
|
625
|
-
else {
|
|
626
|
-
await searchFile(safePath, reqPath);
|
|
657
|
+
finally {
|
|
658
|
+
shell.config.silent = previousSilent;
|
|
627
659
|
}
|
|
628
660
|
return { matches };
|
|
629
661
|
}
|
|
@@ -1200,18 +1232,6 @@ let ensurePtyBinaryPromise = null;
|
|
|
1200
1232
|
function normalizeJsonWithTrailingCommas(text) {
|
|
1201
1233
|
return text.replace(/,\s*([}\]])/g, "$1");
|
|
1202
1234
|
}
|
|
1203
|
-
function compareSemver(a, b) {
|
|
1204
|
-
const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
1205
|
-
const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
1206
|
-
const max = Math.max(aParts.length, bParts.length);
|
|
1207
|
-
for (let i = 0; i < max; i++) {
|
|
1208
|
-
const left = aParts[i] ?? 0;
|
|
1209
|
-
const right = bParts[i] ?? 0;
|
|
1210
|
-
if (left !== right)
|
|
1211
|
-
return left > right ? 1 : -1;
|
|
1212
|
-
}
|
|
1213
|
-
return 0;
|
|
1214
|
-
}
|
|
1215
1235
|
function getLunelConfigDir() {
|
|
1216
1236
|
const platform = os.platform();
|
|
1217
1237
|
if (platform === "win32") {
|
|
@@ -1224,52 +1244,14 @@ function getLunelConfigDir() {
|
|
|
1224
1244
|
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
1225
1245
|
return path.join(xdg, "lunel");
|
|
1226
1246
|
}
|
|
1227
|
-
function
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
function getPtyPlatformKey() {
|
|
1232
|
-
const platform = os.platform();
|
|
1233
|
-
if (platform === "win32")
|
|
1234
|
-
return "windows";
|
|
1235
|
-
if (platform === "linux")
|
|
1236
|
-
return "linux";
|
|
1237
|
-
if (platform === "darwin")
|
|
1238
|
-
return "macos";
|
|
1239
|
-
throw new Error(`Unsupported platform for PTY: ${platform}`);
|
|
1240
|
-
}
|
|
1241
|
-
function getPtyArchKey() {
|
|
1242
|
-
const arch = os.arch();
|
|
1243
|
-
if (arch === "arm64" || arch === "arm")
|
|
1244
|
-
return "arm";
|
|
1245
|
-
if (arch === "x64" || arch === "ia32")
|
|
1246
|
-
return "x86";
|
|
1247
|
-
throw new Error(`Unsupported architecture for PTY: ${arch}`);
|
|
1248
|
-
}
|
|
1249
|
-
async function fetchPtyReleaseInfo() {
|
|
1250
|
-
const response = await fetch(PTY_RELEASE_INFO_URL);
|
|
1251
|
-
if (!response.ok) {
|
|
1252
|
-
throw new Error(`Failed to fetch PTY release info (${response.status})`);
|
|
1253
|
-
}
|
|
1254
|
-
const raw = await response.text();
|
|
1255
|
-
const parsed = JSON.parse(raw);
|
|
1256
|
-
if (!parsed?.version) {
|
|
1257
|
-
throw new Error("Invalid PTY release info: missing version");
|
|
1258
|
-
}
|
|
1259
|
-
return parsed;
|
|
1260
|
-
}
|
|
1261
|
-
function readInstalledPtyVersion(binPath) {
|
|
1262
|
-
try {
|
|
1263
|
-
const output = execFileSync(binPath, ["--version"], {
|
|
1264
|
-
encoding: "utf8",
|
|
1265
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
1266
|
-
});
|
|
1267
|
-
const version = output.trim();
|
|
1268
|
-
return version || null;
|
|
1269
|
-
}
|
|
1270
|
-
catch {
|
|
1247
|
+
function getPtyReleaseTarget() {
|
|
1248
|
+
const release = PTY_RELEASES[`${os.platform()}:${os.arch()}`];
|
|
1249
|
+
if (!release)
|
|
1271
1250
|
return null;
|
|
1272
|
-
|
|
1251
|
+
return release;
|
|
1252
|
+
}
|
|
1253
|
+
function getPtyBinaryPath(fileName) {
|
|
1254
|
+
return path.join(getLunelConfigDir(), "pty-releases", fileName);
|
|
1273
1255
|
}
|
|
1274
1256
|
async function downloadPtyBinary(url, destination) {
|
|
1275
1257
|
const tempPath = `${destination}.download`;
|
|
@@ -1305,36 +1287,20 @@ async function ensurePtyBinaryReady() {
|
|
|
1305
1287
|
if (ensurePtyBinaryPromise)
|
|
1306
1288
|
return ensurePtyBinaryPromise;
|
|
1307
1289
|
ensurePtyBinaryPromise = (async () => {
|
|
1308
|
-
const
|
|
1290
|
+
const release = getPtyReleaseTarget();
|
|
1291
|
+
if (!release)
|
|
1292
|
+
return null;
|
|
1293
|
+
const binPath = getPtyBinaryPath(release.fileName);
|
|
1309
1294
|
await fs.mkdir(path.dirname(binPath), { recursive: true });
|
|
1310
|
-
const releaseInfo = await fetchPtyReleaseInfo();
|
|
1311
|
-
const platformKey = getPtyPlatformKey();
|
|
1312
|
-
const archKey = getPtyArchKey();
|
|
1313
|
-
const downloadUrl = releaseInfo[platformKey]?.[archKey] || null;
|
|
1314
|
-
if (!downloadUrl) {
|
|
1315
|
-
throw new Error(`PTY binary is not available for ${platformKey}/${archKey} in release ${releaseInfo.version}`);
|
|
1316
|
-
}
|
|
1317
|
-
let hasBinary = true;
|
|
1318
1295
|
try {
|
|
1319
1296
|
await fs.access(binPath);
|
|
1297
|
+
return binPath;
|
|
1320
1298
|
}
|
|
1321
1299
|
catch {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
const installedVersion = hasBinary ? readInstalledPtyVersion(binPath) : null;
|
|
1325
|
-
const shouldDownload = !hasBinary ||
|
|
1326
|
-
!installedVersion ||
|
|
1327
|
-
compareSemver(installedVersion, releaseInfo.version) < 0;
|
|
1328
|
-
if (!shouldDownload)
|
|
1300
|
+
console.log(`[pty] PTY missing. Installing ${release.fileName}...`);
|
|
1301
|
+
await downloadPtyBinary(release.url, binPath);
|
|
1329
1302
|
return binPath;
|
|
1330
|
-
if (!hasBinary) {
|
|
1331
|
-
console.log(`[pty] PTY missing. Installing ${releaseInfo.version}...`);
|
|
1332
|
-
}
|
|
1333
|
-
else {
|
|
1334
|
-
console.log(`[pty] PTY outdated (${installedVersion} -> ${releaseInfo.version}). Updating...`);
|
|
1335
1303
|
}
|
|
1336
|
-
await downloadPtyBinary(downloadUrl, binPath);
|
|
1337
|
-
return binPath;
|
|
1338
1304
|
})();
|
|
1339
1305
|
try {
|
|
1340
1306
|
return await ensurePtyBinaryPromise;
|
|
@@ -1347,6 +1313,9 @@ async function ensurePtyProcess() {
|
|
|
1347
1313
|
if (ptyProcess && ptyProcess.exitCode === null)
|
|
1348
1314
|
return;
|
|
1349
1315
|
const binPath = await ensurePtyBinaryReady();
|
|
1316
|
+
if (!binPath) {
|
|
1317
|
+
throw new Error(`PTY is not supported on ${os.platform()}/${os.arch()}`);
|
|
1318
|
+
}
|
|
1350
1319
|
ptyProcess = spawn(binPath, [], {
|
|
1351
1320
|
cwd: ROOT_DIR,
|
|
1352
1321
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -3103,8 +3072,13 @@ async function main() {
|
|
|
3103
3072
|
const cliConfig = await getCliConfig();
|
|
3104
3073
|
const savedSession = getSavedSessionForRoot(cliConfig, ROOT_DIR);
|
|
3105
3074
|
debugLog("Checking PTY runtime...");
|
|
3106
|
-
await ensurePtyBinaryReady();
|
|
3107
|
-
|
|
3075
|
+
const ptyBinaryPath = await ensurePtyBinaryReady();
|
|
3076
|
+
if (ptyBinaryPath) {
|
|
3077
|
+
debugLog("PTY runtime ready.\n");
|
|
3078
|
+
}
|
|
3079
|
+
else {
|
|
3080
|
+
debugLog(`PTY runtime unsupported on ${os.platform()}/${os.arch()}. Skipping prefetch.\n`);
|
|
3081
|
+
}
|
|
3108
3082
|
// Start both AI backends (OpenCode + Codex). Unavailable ones are skipped.
|
|
3109
3083
|
aiManager = await createAiManager();
|
|
3110
3084
|
// Wire provider events → mobile app data channel, tagged with backend name.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lunel-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.102",
|
|
4
4
|
"author": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Soham Bharambe",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"email": "rinkit@lunel.dev"
|
|
12
12
|
}
|
|
13
13
|
],
|
|
14
|
-
"license": "
|
|
14
|
+
"license": "MIT",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"bin": {
|
|
17
17
|
"lunel-cli": "dist/index.js"
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"ignore": "^6.0.2",
|
|
31
31
|
"libsodium-wrappers": "^0.7.15",
|
|
32
32
|
"qrcode-terminal": "^0.12.0",
|
|
33
|
+
"shelljs": "^0.10.0",
|
|
33
34
|
"ws": "^8.18.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/minimatch": "^5.1.2",
|
|
37
38
|
"@types/node": "^20.0.0",
|
|
38
39
|
"@types/qrcode-terminal": "^0.12.2",
|
|
40
|
+
"@types/shelljs": "^0.10.0",
|
|
39
41
|
"@types/ws": "^8.5.13",
|
|
40
42
|
"typescript": "^5.0.0"
|
|
41
43
|
},
|