lunel-cli 0.1.12 → 0.1.13

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.
Files changed (2) hide show
  1. package/dist/index.js +27 -106
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -4,12 +4,17 @@ import qrcode from "qrcode-terminal";
4
4
  import Ignore from "ignore";
5
5
  const ignore = Ignore.default;
6
6
  import * as fs from "fs/promises";
7
+ import * as fsSync from "fs";
7
8
  import * as path from "path";
8
9
  import * as os from "os";
9
10
  import { spawn, execSync } from "child_process";
11
+ import * as pty from "node-pty";
10
12
  import { createServer } from "net";
13
+ import { fileURLToPath } from "url";
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
11
16
  const PROXY_URL = process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev";
12
- const VERSION = "0.1.11";
17
+ const VERSION = JSON.parse(fsSync.readFileSync(path.join(__dirname, "../package.json"), "utf-8")).version;
13
18
  // Root directory - sandbox all file operations to this
14
19
  const ROOT_DIR = process.cwd();
15
20
  // Simple ANSI parser for terminal state
@@ -695,58 +700,20 @@ function handleTerminalSpawn(payload) {
695
700
  throw Object.assign(new Error('No valid shell found'), { code: 'ENOSHELL' });
696
701
  }
697
702
  const terminalId = `term-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
698
- // Clean environment
699
- const cleanEnv = {};
700
- for (const [key, value] of Object.entries(process.env)) {
701
- if (value !== undefined) {
702
- cleanEnv[key] = value;
703
- }
704
- }
705
- cleanEnv['TERM'] = 'xterm-256color';
706
- cleanEnv['COLUMNS'] = cols.toString();
707
- cleanEnv['LINES'] = rows.toString();
703
+ console.log(`[Terminal] Spawning PTY shell: ${shell} (${cols}x${rows}) in ${ROOT_DIR}`);
708
704
  // Create ANSI parser for terminal state
709
705
  const parser = new AnsiParser(cols, rows);
710
- // Spawn shell directly with interactive flag
711
- let proc;
712
- const isWindows = os.platform() === 'win32';
713
- console.log(`[Terminal] Spawning shell: ${shell} in ${ROOT_DIR}`);
714
- // Force a simple prompt and disable fancy terminal features
715
- cleanEnv['PS1'] = '$ ';
716
- cleanEnv['PROMPT'] = '$ ';
717
- cleanEnv['TERM'] = 'dumb';
718
- if (isWindows) {
719
- proc = spawn(shell, [], {
720
- cwd: ROOT_DIR,
721
- env: cleanEnv,
722
- stdio: ['pipe', 'pipe', 'pipe'],
723
- shell: true,
724
- });
725
- }
726
- else {
727
- // Spawn shell with flags to disable rc files and work non-interactively
728
- const shellName = path.basename(shell);
729
- let args = [];
730
- if (shellName === 'zsh') {
731
- // -f: no rc files, -s: read from stdin
732
- args = ['-f', '-s'];
733
- }
734
- else if (shellName === 'bash') {
735
- // --norc --noprofile: no rc files, -s: read from stdin
736
- args = ['--norc', '--noprofile', '-s'];
737
- }
738
- else {
739
- args = ['-s'];
740
- }
741
- proc = spawn(shell, args, {
742
- cwd: ROOT_DIR,
743
- env: cleanEnv,
744
- stdio: ['pipe', 'pipe', 'pipe'],
745
- });
746
- }
747
- console.log(`[Terminal] Spawned with PID: ${proc.pid}`);
706
+ // Spawn PTY with node-pty
707
+ const ptyProcess = pty.spawn(shell, [], {
708
+ name: 'xterm-256color',
709
+ cols,
710
+ rows,
711
+ cwd: ROOT_DIR,
712
+ env: process.env,
713
+ });
714
+ console.log(`[Terminal] PTY spawned with PID: ${ptyProcess.pid}`);
748
715
  terminals.set(terminalId, {
749
- proc,
716
+ ptyProcess,
750
717
  shell,
751
718
  cols,
752
719
  rows,
@@ -756,9 +723,8 @@ function handleTerminalSpawn(payload) {
756
723
  parser,
757
724
  });
758
725
  // Process output and send grid state
759
- const processOutput = (data) => {
760
- const str = data.toString();
761
- parser.write(str);
726
+ ptyProcess.onData((data) => {
727
+ parser.write(data);
762
728
  const state = parser.getState();
763
729
  if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
764
730
  const msg = {
@@ -777,14 +743,9 @@ function handleTerminalSpawn(payload) {
777
743
  };
778
744
  dataChannel.send(JSON.stringify(msg));
779
745
  }
780
- };
781
- proc.stdout?.on('data', processOutput);
782
- proc.stderr?.on('data', processOutput);
783
- proc.on('error', (err) => {
784
- console.error(`[Terminal] Process error:`, err);
785
746
  });
786
- proc.on('close', (code) => {
787
- console.log(`[Terminal] Process closed with code: ${code}`);
747
+ ptyProcess.onExit(({ exitCode }) => {
748
+ console.log(`[Terminal] PTY exited with code: ${exitCode}`);
788
749
  terminals.delete(terminalId);
789
750
  if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
790
751
  const msg = {
@@ -792,32 +753,11 @@ function handleTerminalSpawn(payload) {
792
753
  id: `evt-${Date.now()}`,
793
754
  ns: "terminal",
794
755
  action: "exit",
795
- payload: { terminalId, code: code || 0 },
756
+ payload: { terminalId, code: exitCode },
796
757
  };
797
758
  dataChannel.send(JSON.stringify(msg));
798
759
  }
799
760
  });
800
- // Send initial state
801
- setTimeout(() => {
802
- const state = parser.getState();
803
- if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
804
- const msg = {
805
- v: 1,
806
- id: `evt-${Date.now()}`,
807
- ns: "terminal",
808
- action: "state",
809
- payload: {
810
- terminalId,
811
- buffer: state.buffer,
812
- cursorX: state.cursorX,
813
- cursorY: state.cursorY,
814
- cols: state.cols,
815
- rows: state.rows,
816
- },
817
- };
818
- dataChannel.send(JSON.stringify(msg));
819
- }
820
- }, 100);
821
761
  return { terminalId, shell, cols, rows };
822
762
  }
823
763
  function handleTerminalWrite(payload) {
@@ -830,7 +770,7 @@ function handleTerminalWrite(payload) {
830
770
  const session = terminals.get(terminalId);
831
771
  if (!session)
832
772
  throw Object.assign(new Error("Terminal not found"), { code: "ENOTERM" });
833
- session.proc.stdin?.write(data);
773
+ session.ptyProcess.write(data);
834
774
  return {};
835
775
  }
836
776
  function handleTerminalResize(payload) {
@@ -844,31 +784,12 @@ function handleTerminalResize(payload) {
844
784
  const session = terminals.get(terminalId);
845
785
  if (!session)
846
786
  throw Object.assign(new Error("Terminal not found"), { code: "ENOTERM" });
787
+ // Resize PTY (sends SIGWINCH)
788
+ session.ptyProcess.resize(cols, rows);
847
789
  // Resize parser buffer
848
790
  session.parser.resize(cols, rows);
849
791
  session.cols = cols;
850
792
  session.rows = rows;
851
- // Send SIGWINCH to process (resize signal) - won't work without real PTY
852
- // But we update our internal state
853
- // Send updated state
854
- const state = session.parser.getState();
855
- if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
856
- const msg = {
857
- v: 1,
858
- id: `evt-${Date.now()}`,
859
- ns: "terminal",
860
- action: "state",
861
- payload: {
862
- terminalId,
863
- buffer: state.buffer,
864
- cursorX: state.cursorX,
865
- cursorY: state.cursorY,
866
- cols: state.cols,
867
- rows: state.rows,
868
- },
869
- };
870
- dataChannel.send(JSON.stringify(msg));
871
- }
872
793
  return {};
873
794
  }
874
795
  function handleTerminalKill(payload) {
@@ -878,7 +799,7 @@ function handleTerminalKill(payload) {
878
799
  const session = terminals.get(terminalId);
879
800
  if (!session)
880
801
  throw Object.assign(new Error("Terminal not found"), { code: "ENOTERM" });
881
- session.proc.kill('SIGKILL');
802
+ session.ptyProcess.kill();
882
803
  terminals.delete(terminalId);
883
804
  return {};
884
805
  }
@@ -1675,7 +1596,7 @@ function connectWebSocket(code) {
1675
1596
  console.log("\nShutting down...");
1676
1597
  // Kill all terminals
1677
1598
  for (const [id, session] of terminals) {
1678
- session.proc.kill();
1599
+ session.ptyProcess.kill();
1679
1600
  }
1680
1601
  terminals.clear();
1681
1602
  // Kill all managed processes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",
@@ -25,10 +25,11 @@
25
25
  "prepublishOnly": "npm run build"
26
26
  },
27
27
  "dependencies": {
28
+ "@xterm/headless": "^5.5.0",
28
29
  "ignore": "^6.0.2",
30
+ "node-pty": "^1.1.0",
29
31
  "qrcode-terminal": "^0.12.0",
30
- "ws": "^8.18.0",
31
- "@xterm/headless": "^5.5.0"
32
+ "ws": "^8.18.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/node": "^20.0.0",