lunel-cli 0.1.32 → 0.1.33
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 +154 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const ignore = Ignore.default;
|
|
|
8
8
|
import * as fs from "fs/promises";
|
|
9
9
|
import * as path from "path";
|
|
10
10
|
import * as os from "os";
|
|
11
|
-
import { spawn, execSync } from "child_process";
|
|
11
|
+
import { spawn, execSync, execFileSync } from "child_process";
|
|
12
12
|
import { createServer, createConnection } from "net";
|
|
13
13
|
import { createInterface } from "readline";
|
|
14
14
|
const DEFAULT_PROXY_URL = process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev";
|
|
@@ -17,6 +17,8 @@ const CLI_ARGS = process.argv.slice(2);
|
|
|
17
17
|
import { createRequire } from "module";
|
|
18
18
|
const __require = createRequire(import.meta.url);
|
|
19
19
|
const VERSION = __require("../package.json").version;
|
|
20
|
+
const PTY_RELEASE_INFO_URL = process.env.LUNEL_PTY_INFO_URL ||
|
|
21
|
+
"https://raw.githubusercontent.com/ssbharambe-m/pty-releases/refs/heads/main/info.json";
|
|
20
22
|
// Root directory - sandbox all file operations to this
|
|
21
23
|
const ROOT_DIR = process.cwd();
|
|
22
24
|
// Terminal sessions (managed by Rust PTY binary)
|
|
@@ -595,14 +597,157 @@ async function handleGitDiscard(payload) {
|
|
|
595
597
|
// Terminal Handlers (delegates to Rust PTY binary)
|
|
596
598
|
// ============================================================================
|
|
597
599
|
let dataChannel = null;
|
|
600
|
+
let ensurePtyBinaryPromise = null;
|
|
601
|
+
function normalizeJsonWithTrailingCommas(text) {
|
|
602
|
+
return text.replace(/,\s*([}\]])/g, "$1");
|
|
603
|
+
}
|
|
604
|
+
function compareSemver(a, b) {
|
|
605
|
+
const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
606
|
+
const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
607
|
+
const max = Math.max(aParts.length, bParts.length);
|
|
608
|
+
for (let i = 0; i < max; i++) {
|
|
609
|
+
const left = aParts[i] ?? 0;
|
|
610
|
+
const right = bParts[i] ?? 0;
|
|
611
|
+
if (left !== right)
|
|
612
|
+
return left > right ? 1 : -1;
|
|
613
|
+
}
|
|
614
|
+
return 0;
|
|
615
|
+
}
|
|
616
|
+
function getLunelConfigDir() {
|
|
617
|
+
const platform = os.platform();
|
|
618
|
+
if (platform === "win32") {
|
|
619
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
620
|
+
return path.join(appData, "lunel");
|
|
621
|
+
}
|
|
622
|
+
if (platform === "darwin") {
|
|
623
|
+
return path.join(os.homedir(), "Library", "Application Support", "lunel");
|
|
624
|
+
}
|
|
625
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
626
|
+
return path.join(xdg, "lunel");
|
|
627
|
+
}
|
|
598
628
|
function getPtyBinaryPath() {
|
|
599
|
-
|
|
600
|
-
return path.join(
|
|
629
|
+
const binName = os.platform() === "win32" ? "lunel-pty.exe" : "lunel-pty";
|
|
630
|
+
return path.join(getLunelConfigDir(), "pty-releases", binName);
|
|
631
|
+
}
|
|
632
|
+
function getPtyPlatformKey() {
|
|
633
|
+
const platform = os.platform();
|
|
634
|
+
if (platform === "win32")
|
|
635
|
+
return "windows";
|
|
636
|
+
if (platform === "linux")
|
|
637
|
+
return "linux";
|
|
638
|
+
if (platform === "darwin")
|
|
639
|
+
return "macos";
|
|
640
|
+
throw new Error(`Unsupported platform for PTY: ${platform}`);
|
|
641
|
+
}
|
|
642
|
+
function getPtyArchKey() {
|
|
643
|
+
const arch = os.arch();
|
|
644
|
+
if (arch === "arm64" || arch === "arm")
|
|
645
|
+
return "arm";
|
|
646
|
+
if (arch === "x64" || arch === "ia32")
|
|
647
|
+
return "x86";
|
|
648
|
+
throw new Error(`Unsupported architecture for PTY: ${arch}`);
|
|
649
|
+
}
|
|
650
|
+
async function fetchPtyReleaseInfo() {
|
|
651
|
+
const response = await fetch(PTY_RELEASE_INFO_URL);
|
|
652
|
+
if (!response.ok) {
|
|
653
|
+
throw new Error(`Failed to fetch PTY release info (${response.status})`);
|
|
654
|
+
}
|
|
655
|
+
const raw = await response.text();
|
|
656
|
+
const parsed = JSON.parse(raw);
|
|
657
|
+
if (!parsed?.version) {
|
|
658
|
+
throw new Error("Invalid PTY release info: missing version");
|
|
659
|
+
}
|
|
660
|
+
return parsed;
|
|
661
|
+
}
|
|
662
|
+
function readInstalledPtyVersion(binPath) {
|
|
663
|
+
try {
|
|
664
|
+
const output = execFileSync(binPath, ["--version"], {
|
|
665
|
+
encoding: "utf8",
|
|
666
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
667
|
+
});
|
|
668
|
+
const version = output.trim();
|
|
669
|
+
return version || null;
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async function downloadPtyBinary(url, destination) {
|
|
676
|
+
const tempPath = `${destination}.download`;
|
|
677
|
+
console.log("[pty] Downloading PTY [downloading...]");
|
|
678
|
+
const response = await fetch(url);
|
|
679
|
+
if (!response.ok) {
|
|
680
|
+
throw new Error(`Failed to download PTY binary (${response.status})`);
|
|
681
|
+
}
|
|
682
|
+
if (!response.body) {
|
|
683
|
+
throw new Error("PTY download response had no body");
|
|
684
|
+
}
|
|
685
|
+
const reader = response.body.getReader();
|
|
686
|
+
const chunks = [];
|
|
687
|
+
let totalBytes = 0;
|
|
688
|
+
while (true) {
|
|
689
|
+
const { done, value } = await reader.read();
|
|
690
|
+
if (done)
|
|
691
|
+
break;
|
|
692
|
+
if (value) {
|
|
693
|
+
chunks.push(value);
|
|
694
|
+
totalBytes += value.byteLength;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const binary = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
|
|
698
|
+
await fs.writeFile(tempPath, binary);
|
|
699
|
+
if (os.platform() !== "win32") {
|
|
700
|
+
await fs.chmod(tempPath, 0o755);
|
|
701
|
+
}
|
|
702
|
+
await fs.rename(tempPath, destination);
|
|
703
|
+
console.log(`[pty] Downloaded PTY (${Math.max(1, Math.round(totalBytes / 1024))} KB)`);
|
|
704
|
+
}
|
|
705
|
+
async function ensurePtyBinaryReady() {
|
|
706
|
+
if (ensurePtyBinaryPromise)
|
|
707
|
+
return ensurePtyBinaryPromise;
|
|
708
|
+
ensurePtyBinaryPromise = (async () => {
|
|
709
|
+
const binPath = getPtyBinaryPath();
|
|
710
|
+
await fs.mkdir(path.dirname(binPath), { recursive: true });
|
|
711
|
+
const releaseInfo = await fetchPtyReleaseInfo();
|
|
712
|
+
const platformKey = getPtyPlatformKey();
|
|
713
|
+
const archKey = getPtyArchKey();
|
|
714
|
+
const downloadUrl = releaseInfo[platformKey]?.[archKey] || null;
|
|
715
|
+
if (!downloadUrl) {
|
|
716
|
+
throw new Error(`PTY binary is not available for ${platformKey}/${archKey} in release ${releaseInfo.version}`);
|
|
717
|
+
}
|
|
718
|
+
let hasBinary = true;
|
|
719
|
+
try {
|
|
720
|
+
await fs.access(binPath);
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
hasBinary = false;
|
|
724
|
+
}
|
|
725
|
+
const installedVersion = hasBinary ? readInstalledPtyVersion(binPath) : null;
|
|
726
|
+
const shouldDownload = !hasBinary ||
|
|
727
|
+
!installedVersion ||
|
|
728
|
+
compareSemver(installedVersion, releaseInfo.version) < 0;
|
|
729
|
+
if (!shouldDownload)
|
|
730
|
+
return binPath;
|
|
731
|
+
if (!hasBinary) {
|
|
732
|
+
console.log(`[pty] PTY missing. Installing ${releaseInfo.version}...`);
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
console.log(`[pty] PTY outdated (${installedVersion} -> ${releaseInfo.version}). Updating...`);
|
|
736
|
+
}
|
|
737
|
+
await downloadPtyBinary(downloadUrl, binPath);
|
|
738
|
+
return binPath;
|
|
739
|
+
})();
|
|
740
|
+
try {
|
|
741
|
+
return await ensurePtyBinaryPromise;
|
|
742
|
+
}
|
|
743
|
+
finally {
|
|
744
|
+
ensurePtyBinaryPromise = null;
|
|
745
|
+
}
|
|
601
746
|
}
|
|
602
|
-
function ensurePtyProcess() {
|
|
747
|
+
async function ensurePtyProcess() {
|
|
603
748
|
if (ptyProcess && ptyProcess.exitCode === null)
|
|
604
749
|
return;
|
|
605
|
-
const binPath =
|
|
750
|
+
const binPath = await ensurePtyBinaryReady();
|
|
606
751
|
ptyProcess = spawn(binPath, [], {
|
|
607
752
|
cwd: ROOT_DIR,
|
|
608
753
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -690,7 +835,7 @@ function sendToPty(cmd) {
|
|
|
690
835
|
ptyProcess.stdin.write(JSON.stringify(cmd) + "\n");
|
|
691
836
|
}
|
|
692
837
|
async function handleTerminalSpawn(payload) {
|
|
693
|
-
ensurePtyProcess();
|
|
838
|
+
await ensurePtyProcess();
|
|
694
839
|
const shell = payload.shell || process.env.SHELL || "/bin/sh";
|
|
695
840
|
const cols = payload.cols || 80;
|
|
696
841
|
const rows = payload.rows || 24;
|
|
@@ -2152,6 +2297,9 @@ async function main() {
|
|
|
2152
2297
|
console.log(`Extra ports enabled: ${EXTRA_PORTS.join(", ")}`);
|
|
2153
2298
|
}
|
|
2154
2299
|
try {
|
|
2300
|
+
console.log("Checking PTY runtime...");
|
|
2301
|
+
await ensurePtyBinaryReady();
|
|
2302
|
+
console.log("PTY runtime ready.\n");
|
|
2155
2303
|
// Generate auth credentials (like CodeNomad does)
|
|
2156
2304
|
const opencodeUsername = "lunel";
|
|
2157
2305
|
const opencodePassword = crypto.randomBytes(32).toString("base64url");
|