airloom 0.1.6 → 0.1.7

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
@@ -267,7 +267,7 @@ var Channel = class extends EventEmitter2 {
267
267
  }
268
268
  waitForReady(timeoutMs = 3e4) {
269
269
  if (this._ready) return Promise.resolve();
270
- return new Promise((resolve3, reject) => {
270
+ return new Promise((resolve4, reject) => {
271
271
  const cleanup = () => {
272
272
  clearTimeout(timer);
273
273
  this.removeListener("ready", onReady);
@@ -276,7 +276,7 @@ var Channel = class extends EventEmitter2 {
276
276
  };
277
277
  const onReady = () => {
278
278
  cleanup();
279
- resolve3();
279
+ resolve4();
280
280
  };
281
281
  const onError = (err) => {
282
282
  cleanup();
@@ -373,14 +373,14 @@ var WebSocketAdapter = class {
373
373
  }
374
374
  async doConnect() {
375
375
  const ws = await this.createWebSocket(this.url);
376
- return new Promise((resolve3, reject) => {
376
+ return new Promise((resolve4, reject) => {
377
377
  ws.onopen = () => {
378
378
  this._connected = true;
379
379
  this.shouldReconnect = true;
380
380
  this.reconnectAttempts = 0;
381
381
  const msg = this.role === "host" ? { type: "create", sessionToken: this.sessionToken } : { type: "join", sessionToken: this.sessionToken };
382
382
  ws.send(JSON.stringify(msg));
383
- resolve3();
383
+ resolve4();
384
384
  };
385
385
  ws.onmessage = (event) => {
386
386
  const raw = event.data;
@@ -514,8 +514,8 @@ var AblyAdapter = class {
514
514
  clientOpts.token = this.opts.token;
515
515
  }
516
516
  this.ably = new Realtime(clientOpts);
517
- await new Promise((resolve3, reject) => {
518
- this.ably.connection.once("connected", () => resolve3());
517
+ await new Promise((resolve4, reject) => {
518
+ this.ably.connection.once("connected", () => resolve4());
519
519
  this.ably.connection.once("failed", (stateChange) => {
520
520
  reject(new Error(stateChange?.reason?.message ?? "Ably connection failed"));
521
521
  });
@@ -602,8 +602,8 @@ var AblyAdapter = class {
602
602
  import { sha256 as sha2562 } from "@noble/hashes/sha256";
603
603
  import { networkInterfaces } from "os";
604
604
  import { fileURLToPath } from "url";
605
- import { dirname, resolve as resolve2 } from "path";
606
- import { existsSync as existsSync3 } from "fs";
605
+ import { dirname, resolve as resolve3 } from "path";
606
+ import { existsSync as existsSync4 } from "fs";
607
607
  import QRCode from "qrcode";
608
608
 
609
609
  // src/server.ts
@@ -1148,11 +1148,11 @@ function createHostServer(opts) {
1148
1148
  if (ws.readyState === WebSocket.OPEN) ws.send(msg);
1149
1149
  }
1150
1150
  }
1151
- return new Promise((resolve3) => {
1151
+ return new Promise((resolve4) => {
1152
1152
  server.listen(opts.port, "0.0.0.0", () => {
1153
1153
  const addr = server.address();
1154
1154
  const actualPort = typeof addr === "object" && addr ? addr.port : opts.port;
1155
- resolve3({ server, broadcast, port: actualPort });
1155
+ resolve4({ server, broadcast, port: actualPort });
1156
1156
  });
1157
1157
  server.on("error", (err) => {
1158
1158
  console.error(`[host] Server error: ${err.message}`);
@@ -1359,6 +1359,174 @@ function addMsg(role,content){const el=document.createElement('div');el.classNam
1359
1359
  </body>
1360
1360
  </html>`;
1361
1361
 
1362
+ // src/terminal.ts
1363
+ import { basename, delimiter as delimiter2, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
1364
+ import { existsSync as existsSync3 } from "fs";
1365
+ import { spawn as spawn2 } from "node-pty";
1366
+ function resolveExecutable2(command, envPath = process.env.PATH ?? "") {
1367
+ if (!command) return null;
1368
+ if (isAbsolute2(command) && existsSync3(command)) return command;
1369
+ if (command.includes("/")) {
1370
+ const candidate = resolve2(process.cwd(), command);
1371
+ return existsSync3(candidate) ? candidate : null;
1372
+ }
1373
+ for (const dir of envPath.split(delimiter2)) {
1374
+ if (!dir) continue;
1375
+ const candidate = join3(dir.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"), command);
1376
+ if (existsSync3(candidate)) return candidate;
1377
+ }
1378
+ return null;
1379
+ }
1380
+ function parseCommand(command) {
1381
+ const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [command];
1382
+ return {
1383
+ file: parts[0],
1384
+ args: parts.slice(1).map((part) => part.replace(/^"|"$/g, ""))
1385
+ };
1386
+ }
1387
+ function getDefaultTerminalCommand() {
1388
+ const configured = process.env.AIRLOOM_TERMINAL_COMMAND?.trim();
1389
+ if (configured) return parseCommand(configured);
1390
+ if (process.platform === "win32") {
1391
+ const file = process.env.COMSPEC || "powershell.exe";
1392
+ return { file, args: [] };
1393
+ }
1394
+ const shell = process.env.SHELL || "/bin/bash";
1395
+ const name = basename(shell);
1396
+ if (name === "bash" || name === "zsh" || name === "sh") return { file: shell, args: ["-il"] };
1397
+ return { file: shell, args: ["-i"] };
1398
+ }
1399
+ var AdaptiveOutputBatcher = class {
1400
+ constructor(onFlush, fastInterval = 16, slowInterval = 80, interactiveWindow = 250, maxBytes = 4096) {
1401
+ this.onFlush = onFlush;
1402
+ this.fastInterval = fastInterval;
1403
+ this.slowInterval = slowInterval;
1404
+ this.interactiveWindow = interactiveWindow;
1405
+ this.maxBytes = maxBytes;
1406
+ }
1407
+ buffer = "";
1408
+ timer = null;
1409
+ lastInputAt = 0;
1410
+ noteInput() {
1411
+ this.lastInputAt = Date.now();
1412
+ if (this.timer) {
1413
+ clearTimeout(this.timer);
1414
+ this.timer = null;
1415
+ }
1416
+ if (this.buffer) this.schedule();
1417
+ }
1418
+ write(data) {
1419
+ this.buffer += data;
1420
+ if (this.buffer.length >= this.maxBytes) {
1421
+ this.flush();
1422
+ return;
1423
+ }
1424
+ this.schedule();
1425
+ }
1426
+ flush() {
1427
+ if (this.timer) {
1428
+ clearTimeout(this.timer);
1429
+ this.timer = null;
1430
+ }
1431
+ if (!this.buffer) return;
1432
+ const data = this.buffer;
1433
+ this.buffer = "";
1434
+ this.onFlush(data);
1435
+ }
1436
+ destroy() {
1437
+ this.flush();
1438
+ }
1439
+ schedule() {
1440
+ if (this.timer) return;
1441
+ const recentInput = Date.now() - this.lastInputAt <= this.interactiveWindow;
1442
+ this.timer = setTimeout(() => this.flush(), recentInput ? this.fastInterval : this.slowInterval);
1443
+ }
1444
+ };
1445
+ var TerminalSession = class {
1446
+ constructor(channel) {
1447
+ this.channel = channel;
1448
+ }
1449
+ pty = null;
1450
+ stream = null;
1451
+ batcher = null;
1452
+ cols = 120;
1453
+ rows = 36;
1454
+ handleMessage(message) {
1455
+ switch (message.type) {
1456
+ case "terminal_open":
1457
+ this.open(message);
1458
+ break;
1459
+ case "terminal_input":
1460
+ this.writeInput(message);
1461
+ break;
1462
+ case "terminal_resize":
1463
+ this.resize(message);
1464
+ break;
1465
+ case "terminal_close":
1466
+ this.close();
1467
+ break;
1468
+ case "terminal_exit":
1469
+ break;
1470
+ }
1471
+ }
1472
+ close() {
1473
+ this.batcher?.destroy();
1474
+ this.batcher = null;
1475
+ if (this.stream && !this.stream.ended) this.stream.end();
1476
+ this.stream = null;
1477
+ try {
1478
+ this.pty?.kill();
1479
+ } catch {
1480
+ }
1481
+ this.pty = null;
1482
+ }
1483
+ open(message) {
1484
+ this.cols = Math.max(20, Math.floor(message.cols || this.cols));
1485
+ this.rows = Math.max(5, Math.floor(message.rows || this.rows));
1486
+ if (this.pty) {
1487
+ this.pty.resize(this.cols, this.rows);
1488
+ return;
1489
+ }
1490
+ const command = getDefaultTerminalCommand();
1491
+ const file = resolveExecutable2(command.file) ?? command.file;
1492
+ const meta = { kind: "terminal", cols: this.cols, rows: this.rows };
1493
+ this.stream = this.channel.createStream(meta);
1494
+ this.batcher = new AdaptiveOutputBatcher((data) => this.stream?.write(data));
1495
+ const env = { ...process.env, TERM: "xterm-256color" };
1496
+ this.pty = spawn2(file, command.args, {
1497
+ name: "xterm-256color",
1498
+ cols: this.cols,
1499
+ rows: this.rows,
1500
+ cwd: process.cwd(),
1501
+ env
1502
+ });
1503
+ this.pty.onData((data) => this.batcher?.write(data));
1504
+ this.pty.onExit(({ exitCode, signal }) => {
1505
+ this.batcher?.flush();
1506
+ this.stream?.end();
1507
+ this.channel.send({ type: "terminal_exit", exitCode, signal });
1508
+ this.stream = null;
1509
+ this.batcher = null;
1510
+ this.pty = null;
1511
+ });
1512
+ }
1513
+ writeInput(message) {
1514
+ if (!this.pty) return;
1515
+ this.batcher?.noteInput();
1516
+ this.pty.write(message.data);
1517
+ }
1518
+ resize(message) {
1519
+ this.cols = Math.max(20, Math.floor(message.cols || this.cols));
1520
+ this.rows = Math.max(5, Math.floor(message.rows || this.rows));
1521
+ this.pty?.resize(this.cols, this.rows);
1522
+ }
1523
+ };
1524
+ function isTerminalMessage(data) {
1525
+ if (!data || typeof data !== "object" || !("type" in data)) return false;
1526
+ const type = data.type;
1527
+ return type === "terminal_open" || type === "terminal_input" || type === "terminal_resize" || type === "terminal_close" || type === "terminal_exit";
1528
+ }
1529
+
1362
1530
  // src/index.ts
1363
1531
  function parseArgs(argv) {
1364
1532
  const args = {};
@@ -1437,10 +1605,10 @@ function getLanIP() {
1437
1605
  return void 0;
1438
1606
  }
1439
1607
  function resolveViewerDir() {
1440
- const prod = resolve2(__dirname, "viewer");
1441
- if (existsSync3(prod)) return prod;
1442
- const dev = resolve2(__dirname, "../../viewer/dist");
1443
- if (existsSync3(dev)) return dev;
1608
+ const prod = resolve3(__dirname, "viewer");
1609
+ if (existsSync4(prod)) return prod;
1610
+ const dev = resolve3(__dirname, "../../viewer/dist");
1611
+ if (existsSync4(dev)) return dev;
1444
1612
  return void 0;
1445
1613
  }
1446
1614
  async function main() {
@@ -1601,6 +1769,7 @@ async function main() {
1601
1769
  exec(`${cmd} ${localUrl}`);
1602
1770
  }).catch(() => {
1603
1771
  });
1772
+ const terminal = new TerminalSession(channel);
1604
1773
  channel.on("ready", () => {
1605
1774
  console.log("[host] Phone connected! Channel ready.");
1606
1775
  state.connected = true;
@@ -1609,9 +1778,14 @@ async function main() {
1609
1778
  channel.on("peer_left", () => {
1610
1779
  console.log("[host] Phone disconnected.");
1611
1780
  state.connected = false;
1781
+ terminal.close();
1612
1782
  broadcast({ type: "peer_disconnected" });
1613
1783
  });
1614
1784
  channel.on("message", (data) => {
1785
+ if (isTerminalMessage(data)) {
1786
+ terminal.handleMessage(data);
1787
+ return;
1788
+ }
1615
1789
  if (typeof data === "object" && data !== null && "type" in data && "content" in data) {
1616
1790
  const msg = data;
1617
1791
  if (msg.type === "chat" && typeof msg.content === "string") {
@@ -1630,6 +1804,7 @@ async function main() {
1630
1804
  if (shuttingDown) return;
1631
1805
  shuttingDown = true;
1632
1806
  console.log("\n[host] Shutting down...");
1807
+ terminal.close();
1633
1808
  state.adapter?.destroy?.();
1634
1809
  try {
1635
1810
  channel.close();
@@ -1 +1 @@
1
- import{g as c}from"./index-DLLnAccl.js";function f(t,i){for(var o=0;o<i.length;o++){const e=i[o];if(typeof e!="string"&&!Array.isArray(e)){for(const r in e)if(r!=="default"&&!(r in t)){const s=Object.getOwnPropertyDescriptor(e,r);s&&Object.defineProperty(t,r,s.get?s:{enumerable:!0,get:()=>e[r]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var n,a;function b(){return a||(a=1,n=function(){throw new Error("ws does not work in the browser. Browser clients must use the native WebSocket object")}),n}var u=b();const w=c(u),p=f({__proto__:null,default:w},[u]);export{p as b};
1
+ import{g as c}from"./index-CCYyPnVc.js";function f(t,i){for(var o=0;o<i.length;o++){const e=i[o];if(typeof e!="string"&&!Array.isArray(e)){for(const r in e)if(r!=="default"&&!(r in t)){const s=Object.getOwnPropertyDescriptor(e,r);s&&Object.defineProperty(t,r,s.get?s:{enumerable:!0,get:()=>e[r]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var n,a;function b(){return a||(a=1,n=function(){throw new Error("ws does not work in the browser. Browser clients must use the native WebSocket object")}),n}var u=b();const w=c(u),p=f({__proto__:null,default:w},[u]);export{p as b};