lunel-cli 0.1.36 → 0.1.38
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 +103 -2
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as os from "os";
|
|
|
11
11
|
import { spawn, execSync, execFileSync } from "child_process";
|
|
12
12
|
import { createServer, createConnection } from "net";
|
|
13
13
|
import { createInterface } from "readline";
|
|
14
|
+
import { simpleGit } from "simple-git";
|
|
14
15
|
const DEFAULT_PROXY_URL = process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev";
|
|
15
16
|
const MANAGER_URL = process.env.LUNEL_MANAGER_URL || "https://manager.lunel.dev";
|
|
16
17
|
const CLI_ARGS = process.argv.slice(2);
|
|
@@ -22,6 +23,7 @@ const PTY_RELEASE_INFO_URL = process.env.LUNEL_PTY_INFO_URL ||
|
|
|
22
23
|
const VERBOSE_AI_LOGS = process.env.LUNEL_DEBUG_AI === "1";
|
|
23
24
|
// Root directory - sandbox all file operations to this
|
|
24
25
|
const ROOT_DIR = process.cwd();
|
|
26
|
+
const gitClient = simpleGit(ROOT_DIR);
|
|
25
27
|
// Terminal sessions (managed by Rust PTY binary)
|
|
26
28
|
const terminals = new Set();
|
|
27
29
|
// PTY binary process
|
|
@@ -220,6 +222,21 @@ function assertSafePath(requestedPath) {
|
|
|
220
222
|
// ============================================================================
|
|
221
223
|
// File System Handlers
|
|
222
224
|
// ============================================================================
|
|
225
|
+
function isLikelyBinaryBuffer(buffer) {
|
|
226
|
+
if (buffer.length === 0)
|
|
227
|
+
return false;
|
|
228
|
+
if (buffer.includes(0x00))
|
|
229
|
+
return true;
|
|
230
|
+
let suspicious = 0;
|
|
231
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
232
|
+
const byte = buffer[i];
|
|
233
|
+
const isPrintableAscii = byte >= 0x20 && byte <= 0x7e;
|
|
234
|
+
const isCommonControl = byte === 0x09 || byte === 0x0a || byte === 0x0d;
|
|
235
|
+
if (!isPrintableAscii && !isCommonControl)
|
|
236
|
+
suspicious++;
|
|
237
|
+
}
|
|
238
|
+
return suspicious / buffer.length > 0.3;
|
|
239
|
+
}
|
|
223
240
|
async function handleFsLs(payload) {
|
|
224
241
|
const reqPath = payload.path || ".";
|
|
225
242
|
const safePath = assertSafePath(reqPath);
|
|
@@ -258,6 +275,25 @@ async function handleFsStat(payload) {
|
|
|
258
275
|
mtime: stat.mtimeMs,
|
|
259
276
|
mode: stat.mode,
|
|
260
277
|
};
|
|
278
|
+
if (stat.isFile()) {
|
|
279
|
+
try {
|
|
280
|
+
const fd = await fs.open(safePath, "r");
|
|
281
|
+
try {
|
|
282
|
+
const sampleSize = Math.min(stat.size, 8192);
|
|
283
|
+
const sample = Buffer.alloc(sampleSize);
|
|
284
|
+
if (sampleSize > 0) {
|
|
285
|
+
await fd.read(sample, 0, sampleSize, 0);
|
|
286
|
+
}
|
|
287
|
+
result.isBinary = isLikelyBinaryBuffer(sample);
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
await fd.close();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// Keep stat resilient even if sampling fails
|
|
295
|
+
}
|
|
296
|
+
}
|
|
261
297
|
return result;
|
|
262
298
|
}
|
|
263
299
|
async function handleFsRead(payload) {
|
|
@@ -268,8 +304,8 @@ async function handleFsRead(payload) {
|
|
|
268
304
|
// Check if binary
|
|
269
305
|
const stat = await fs.stat(safePath);
|
|
270
306
|
const content = await fs.readFile(safePath);
|
|
271
|
-
//
|
|
272
|
-
const isBinary = content.
|
|
307
|
+
// Detect if binary
|
|
308
|
+
const isBinary = isLikelyBinaryBuffer(content.subarray(0, 8192));
|
|
273
309
|
if (isBinary) {
|
|
274
310
|
return {
|
|
275
311
|
path: reqPath,
|
|
@@ -570,6 +606,68 @@ async function handleGitLog(payload) {
|
|
|
570
606
|
});
|
|
571
607
|
return { commits };
|
|
572
608
|
}
|
|
609
|
+
async function handleGitCommitDetails(payload) {
|
|
610
|
+
const hash = payload.hash;
|
|
611
|
+
if (!hash)
|
|
612
|
+
throw Object.assign(new Error("hash is required"), { code: "EINVAL" });
|
|
613
|
+
try {
|
|
614
|
+
const logResult = await gitClient.log({
|
|
615
|
+
from: hash,
|
|
616
|
+
to: hash,
|
|
617
|
+
maxCount: 1,
|
|
618
|
+
format: {
|
|
619
|
+
fullHash: "%H",
|
|
620
|
+
message: "%s",
|
|
621
|
+
author: "%an",
|
|
622
|
+
timestamp: "%at",
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
const latest = logResult.latest;
|
|
626
|
+
if (!latest) {
|
|
627
|
+
throw Object.assign(new Error("Commit not found"), { code: "EGIT" });
|
|
628
|
+
}
|
|
629
|
+
const filesRaw = await gitClient.show(["--name-status", "--format=", hash]);
|
|
630
|
+
const files = filesRaw
|
|
631
|
+
.split(/\r?\n/)
|
|
632
|
+
.map((line) => line.trim())
|
|
633
|
+
.filter(Boolean)
|
|
634
|
+
.map((line) => {
|
|
635
|
+
const parts = line.split("\t");
|
|
636
|
+
const status = parts[0] || "?";
|
|
637
|
+
// Handles regular + rename/copy name-status output.
|
|
638
|
+
const path = parts[2] || parts[1] || "";
|
|
639
|
+
return { status, path };
|
|
640
|
+
})
|
|
641
|
+
.filter((entry) => !!entry.path);
|
|
642
|
+
const diff = await gitClient.show(["--patch", "--format=", hash]);
|
|
643
|
+
const fileDiffs = {};
|
|
644
|
+
const fileChunks = diff.split(/^diff --git /m).filter(Boolean);
|
|
645
|
+
for (const chunk of fileChunks) {
|
|
646
|
+
const patch = `diff --git ${chunk}`;
|
|
647
|
+
const firstLine = chunk.split(/\r?\n/, 1)[0] || "";
|
|
648
|
+
const match = firstLine.match(/^a\/(.+?) b\/(.+)$/);
|
|
649
|
+
if (match?.[2]) {
|
|
650
|
+
fileDiffs[match[2]] = patch;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
commit: {
|
|
655
|
+
hash: latest.fullHash.substring(0, 7),
|
|
656
|
+
fullHash: latest.fullHash,
|
|
657
|
+
message: latest.message,
|
|
658
|
+
author: latest.author,
|
|
659
|
+
date: parseInt(latest.timestamp, 10) * 1000,
|
|
660
|
+
},
|
|
661
|
+
files,
|
|
662
|
+
diff,
|
|
663
|
+
fileDiffs,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
const message = err instanceof Error ? err.message : "git show failed";
|
|
668
|
+
throw Object.assign(new Error(message), { code: "EGIT" });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
573
671
|
async function handleGitDiff(payload) {
|
|
574
672
|
const filepath = payload.path;
|
|
575
673
|
const staged = payload.staged === true;
|
|
@@ -2110,6 +2208,9 @@ async function processMessage(message) {
|
|
|
2110
2208
|
case "log":
|
|
2111
2209
|
result = await handleGitLog(payload);
|
|
2112
2210
|
break;
|
|
2211
|
+
case "commitDetails":
|
|
2212
|
+
result = await handleGitCommitDetails(payload);
|
|
2213
|
+
break;
|
|
2113
2214
|
case "diff":
|
|
2114
2215
|
result = await handleGitDiff(payload);
|
|
2115
2216
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lunel-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"author": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Soham Bharambe",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"license": "Functional Source License, Version 1.1, Apache 2.0 Future License",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"bin": {
|
|
17
|
-
"lunel-cli": "
|
|
17
|
+
"lunel-cli": "dist/index.js"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@opencode-ai/sdk": "^1.1.56",
|
|
30
30
|
"ignore": "^6.0.2",
|
|
31
31
|
"qrcode-terminal": "^0.12.0",
|
|
32
|
+
"simple-git": "^3.32.3",
|
|
32
33
|
"ws": "^8.18.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|