@rehpic/vcli 0.1.0-beta.68.1 → 0.1.0-beta.69.1

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
@@ -285,7 +285,7 @@ var init_default_browser_id = __esm({
285
285
  // ../../node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js
286
286
  import process5 from "process";
287
287
  import { promisify as promisify4 } from "util";
288
- import { execFile as execFile4, execFileSync as execFileSync2 } from "child_process";
288
+ import { execFile as execFile4, execFileSync as execFileSync3 } from "child_process";
289
289
  async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
290
290
  if (process5.platform !== "darwin") {
291
291
  throw new Error("macOS only");
@@ -1152,7 +1152,7 @@ function createEmptySession() {
1152
1152
 
1153
1153
  // src/bridge-service.ts
1154
1154
  import { ConvexHttpClient as ConvexHttpClient2 } from "convex/browser";
1155
- import { execFileSync, execSync as execSync2 } from "child_process";
1155
+ import { execFileSync as execFileSync2, execSync as execSync2 } from "child_process";
1156
1156
 
1157
1157
  // src/terminal-peer.ts
1158
1158
  import { createServer } from "http";
@@ -1161,6 +1161,7 @@ import { ConvexClient } from "convex/browser";
1161
1161
  import * as pty from "node-pty";
1162
1162
  import { existsSync } from "fs";
1163
1163
  import { randomUUID } from "crypto";
1164
+ import { execFileSync } from "child_process";
1164
1165
  import localtunnel from "localtunnel";
1165
1166
  function findTmuxPath() {
1166
1167
  for (const p of [
@@ -1172,20 +1173,51 @@ function findTmuxPath() {
1172
1173
  }
1173
1174
  return "tmux";
1174
1175
  }
1176
+ var TMUX = findTmuxPath();
1175
1177
  function ts() {
1176
1178
  return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
1177
1179
  }
1178
- function findPort(start = 9100) {
1180
+ function findPort() {
1179
1181
  return new Promise((resolve, reject) => {
1180
1182
  const srv = createServer();
1181
1183
  srv.listen(0, "127.0.0.1", () => {
1182
1184
  const addr = srv.address();
1183
- const port = typeof addr === "object" && addr ? addr.port : start;
1185
+ const port = typeof addr === "object" && addr ? addr.port : 9100;
1184
1186
  srv.close(() => resolve(port));
1185
1187
  });
1186
1188
  srv.on("error", reject);
1187
1189
  });
1188
1190
  }
1191
+ function createViewerSession(targetSession, paneId) {
1192
+ const viewerName = `viewer-${randomUUID().slice(0, 8)}`;
1193
+ try {
1194
+ execFileSync(TMUX, [
1195
+ "new-session",
1196
+ "-d",
1197
+ "-s",
1198
+ viewerName,
1199
+ "-t",
1200
+ targetSession
1201
+ ]);
1202
+ execFileSync(TMUX, ["set-option", "-t", viewerName, "status", "off"]);
1203
+ if (paneId) {
1204
+ try {
1205
+ execFileSync(TMUX, ["select-pane", "-t", paneId]);
1206
+ } catch {
1207
+ }
1208
+ }
1209
+ return viewerName;
1210
+ } catch (err) {
1211
+ console.error(`[${ts()}] Failed to create viewer session:`, err);
1212
+ return targetSession;
1213
+ }
1214
+ }
1215
+ function killViewerSession(sessionName) {
1216
+ try {
1217
+ execFileSync(TMUX, ["kill-session", "-t", sessionName]);
1218
+ } catch {
1219
+ }
1220
+ }
1189
1221
  var TerminalPeerManager = class {
1190
1222
  constructor(config) {
1191
1223
  this.terminals = /* @__PURE__ */ new Map();
@@ -1195,7 +1227,7 @@ var TerminalPeerManager = class {
1195
1227
  this.config = config;
1196
1228
  this.client = new ConvexClient(config.convexUrl);
1197
1229
  }
1198
- watchSession(workSessionId, tmuxSessionName) {
1230
+ watchSession(workSessionId, tmuxSessionName, tmuxPaneId) {
1199
1231
  if (this.unsubscribers.has(workSessionId)) return;
1200
1232
  const unsub = this.client.onUpdate(
1201
1233
  api.agentBridge.bridgePublic.getWorkSessionTerminalState,
@@ -1217,6 +1249,7 @@ var TerminalPeerManager = class {
1217
1249
  void this.startTerminal(
1218
1250
  workSessionId,
1219
1251
  tmuxSessionName,
1252
+ tmuxPaneId,
1220
1253
  state.terminalCols,
1221
1254
  state.terminalRows
1222
1255
  );
@@ -1245,17 +1278,21 @@ var TerminalPeerManager = class {
1245
1278
  }
1246
1279
  this.stopTerminal(workSessionId);
1247
1280
  }
1248
- async startTerminal(workSessionId, tmuxSessionName, cols, rows) {
1281
+ async startTerminal(workSessionId, tmuxSessionName, tmuxPaneId, cols, rows) {
1249
1282
  if (this.terminals.has(workSessionId)) return;
1250
- const tmuxBin = findTmuxPath();
1251
1283
  try {
1252
1284
  const port = await findPort();
1285
+ const viewerSession = createViewerSession(tmuxSessionName, tmuxPaneId);
1286
+ const isLinked = viewerSession !== tmuxSessionName;
1287
+ console.log(
1288
+ `[${ts()}] Viewer session: ${viewerSession}${isLinked ? " (linked)" : ""}`
1289
+ );
1253
1290
  console.log(
1254
- `[${ts()}] Spawning PTY: ${tmuxBin} attach-session -t ${tmuxSessionName}`
1291
+ `[${ts()}] Spawning PTY: ${TMUX} attach-session -t ${viewerSession}`
1255
1292
  );
1256
1293
  const ptyProcess = pty.spawn(
1257
- tmuxBin,
1258
- ["attach-session", "-t", tmuxSessionName],
1294
+ TMUX,
1295
+ ["attach-session", "-t", viewerSession],
1259
1296
  {
1260
1297
  name: "xterm-256color",
1261
1298
  cols: Math.max(cols, 10),
@@ -1264,7 +1301,7 @@ var TerminalPeerManager = class {
1264
1301
  env: { ...process.env, TERM: "xterm-256color" }
1265
1302
  }
1266
1303
  );
1267
- console.log(`[${ts()}] PTY started for ${tmuxSessionName}`);
1304
+ console.log(`[${ts()}] PTY started`);
1268
1305
  const token = randomUUID();
1269
1306
  const httpServer = createServer();
1270
1307
  const wss = new WebSocketServer({ server: httpServer });
@@ -1272,13 +1309,11 @@ var TerminalPeerManager = class {
1272
1309
  const url = new URL(req.url ?? "/", `http://localhost`);
1273
1310
  const clientToken = url.searchParams.get("token");
1274
1311
  if (clientToken !== token) {
1275
- console.log(`[${ts()}] Rejected unauthorized WebSocket connection`);
1312
+ console.log(`[${ts()}] Rejected unauthorized connection`);
1276
1313
  ws.close(4401, "Unauthorized");
1277
1314
  return;
1278
1315
  }
1279
- console.log(
1280
- `[${ts()}] WebSocket client connected (${tmuxSessionName})`
1281
- );
1316
+ console.log(`[${ts()}] Client connected (${tmuxSessionName})`);
1282
1317
  const dataHandler = ptyProcess.onData((data) => {
1283
1318
  if (ws.readyState === WebSocket.OPEN) {
1284
1319
  ws.send(data);
@@ -1302,18 +1337,22 @@ var TerminalPeerManager = class {
1302
1337
  ptyProcess.write(str);
1303
1338
  });
1304
1339
  ws.on("close", () => {
1305
- console.log(
1306
- `[${ts()}] WebSocket client disconnected (${tmuxSessionName})`
1307
- );
1340
+ console.log(`[${ts()}] Client disconnected (${tmuxSessionName})`);
1308
1341
  dataHandler.dispose();
1309
1342
  });
1310
1343
  });
1311
1344
  await new Promise((resolve) => {
1312
- httpServer.listen(port, "127.0.0.1", resolve);
1345
+ httpServer.listen(port, "0.0.0.0", resolve);
1313
1346
  });
1314
1347
  console.log(`[${ts()}] WS server on port ${port}`);
1315
- console.log(`[${ts()}] Opening tunnel...`);
1316
- const tunnel = await localtunnel({ port });
1348
+ const tunnelOpts = { port };
1349
+ if (this.config.tunnelHost) {
1350
+ tunnelOpts.host = this.config.tunnelHost;
1351
+ }
1352
+ console.log(
1353
+ `[${ts()}] Opening tunnel...${this.config.tunnelHost ? ` (host: ${this.config.tunnelHost})` : ""}`
1354
+ );
1355
+ const tunnel = await localtunnel(tunnelOpts);
1317
1356
  const tunnelUrl = tunnel.url;
1318
1357
  console.log(`[${ts()}] Tunnel: ${tunnelUrl}`);
1319
1358
  const wsUrl = tunnelUrl.replace(/^https?:\/\//, "wss://");
@@ -1322,7 +1361,7 @@ var TerminalPeerManager = class {
1322
1361
  httpServer,
1323
1362
  wss,
1324
1363
  tunnel,
1325
- tunnelUrl: wsUrl,
1364
+ viewerSessionName: isLinked ? viewerSession : null,
1326
1365
  token,
1327
1366
  workSessionId,
1328
1367
  tmuxSessionName,
@@ -1336,7 +1375,8 @@ var TerminalPeerManager = class {
1336
1375
  deviceSecret: this.config.deviceSecret,
1337
1376
  workSessionId,
1338
1377
  terminalUrl: wsUrl,
1339
- terminalToken: token
1378
+ terminalToken: token,
1379
+ terminalLocalPort: port
1340
1380
  }
1341
1381
  );
1342
1382
  ptyProcess.onExit(() => {
@@ -1367,6 +1407,9 @@ var TerminalPeerManager = class {
1367
1407
  terminal.httpServer.close();
1368
1408
  } catch {
1369
1409
  }
1410
+ if (terminal.viewerSessionName) {
1411
+ killViewerSession(terminal.viewerSessionName);
1412
+ }
1370
1413
  this.terminals.delete(workSessionId);
1371
1414
  console.log(`[${ts()}] Terminal stopped for ${terminal.tmuxSessionName}`);
1372
1415
  }
@@ -2394,7 +2437,8 @@ var BridgeService = class {
2394
2437
  if (activity.workSessionId && activity.tmuxSessionName) {
2395
2438
  this.terminalPeer.watchSession(
2396
2439
  activity.workSessionId,
2397
- activity.tmuxSessionName
2440
+ activity.tmuxSessionName,
2441
+ activity.tmuxPaneId
2398
2442
  );
2399
2443
  }
2400
2444
  }
@@ -2493,9 +2537,12 @@ var BridgeService = class {
2493
2537
  this.terminalPeer = new TerminalPeerManager({
2494
2538
  deviceId: this.config.deviceId,
2495
2539
  deviceSecret: this.config.deviceSecret,
2496
- convexUrl: this.config.convexUrl
2540
+ convexUrl: this.config.convexUrl,
2541
+ tunnelHost: this.config.tunnelHost
2497
2542
  });
2498
- console.log(` WebRTC: ready`);
2543
+ console.log(
2544
+ ` Terminal: ready${this.config.tunnelHost ? ` (tunnel: ${this.config.tunnelHost})` : ""}`
2545
+ );
2499
2546
  } catch (e) {
2500
2547
  console.error(
2501
2548
  ` WebRTC: failed (${e instanceof Error ? e.message : "unknown"})`
@@ -2859,7 +2906,7 @@ function createTmuxWorkSession(args) {
2859
2906
  const windowName = sanitizeTmuxName(
2860
2907
  args.provider === "codex" ? "codex" : args.provider === "claude_code" ? "claude" : "shell"
2861
2908
  );
2862
- execFileSync("tmux", [
2909
+ execFileSync2("tmux", [
2863
2910
  "new-session",
2864
2911
  "-d",
2865
2912
  "-s",
@@ -2869,7 +2916,7 @@ function createTmuxWorkSession(args) {
2869
2916
  "-c",
2870
2917
  args.workspacePath
2871
2918
  ]);
2872
- const paneId = execFileSync(
2919
+ const paneId = execFileSync2(
2873
2920
  "tmux",
2874
2921
  [
2875
2922
  "display-message",
@@ -2880,13 +2927,13 @@ function createTmuxWorkSession(args) {
2880
2927
  ],
2881
2928
  { encoding: "utf-8" }
2882
2929
  ).trim();
2883
- const paneProcessId = execFileSync(
2930
+ const paneProcessId = execFileSync2(
2884
2931
  "tmux",
2885
2932
  ["display-message", "-p", "-t", paneId, "#{pane_pid}"],
2886
2933
  { encoding: "utf-8" }
2887
2934
  ).trim();
2888
2935
  if (args.provider) {
2889
- execFileSync("tmux", [
2936
+ execFileSync2("tmux", [
2890
2937
  "send-keys",
2891
2938
  "-t",
2892
2939
  paneId,
@@ -2894,7 +2941,7 @@ function createTmuxWorkSession(args) {
2894
2941
  "Enter"
2895
2942
  ]);
2896
2943
  } else {
2897
- execFileSync("tmux", [
2944
+ execFileSync2("tmux", [
2898
2945
  "send-keys",
2899
2946
  "-t",
2900
2947
  paneId,
@@ -2910,13 +2957,13 @@ function createTmuxWorkSession(args) {
2910
2957
  };
2911
2958
  }
2912
2959
  function sendTextToTmuxPane(paneId, text2) {
2913
- execFileSync("tmux", ["set-buffer", "--", text2]);
2914
- execFileSync("tmux", ["paste-buffer", "-t", paneId]);
2915
- execFileSync("tmux", ["send-keys", "-t", paneId, "Enter"]);
2916
- execFileSync("tmux", ["delete-buffer"]);
2960
+ execFileSync2("tmux", ["set-buffer", "--", text2]);
2961
+ execFileSync2("tmux", ["paste-buffer", "-t", paneId]);
2962
+ execFileSync2("tmux", ["send-keys", "-t", paneId, "Enter"]);
2963
+ execFileSync2("tmux", ["delete-buffer"]);
2917
2964
  }
2918
2965
  function captureTmuxPane(paneId) {
2919
- return execFileSync(
2966
+ return execFileSync2(
2920
2967
  "tmux",
2921
2968
  ["capture-pane", "-p", "-e", "-t", paneId, "-S", "-120"],
2922
2969
  { encoding: "utf-8" }
@@ -2924,7 +2971,7 @@ function captureTmuxPane(paneId) {
2924
2971
  }
2925
2972
  function resizeTmuxPane(paneId, cols, rows) {
2926
2973
  try {
2927
- execFileSync("tmux", [
2974
+ execFileSync2("tmux", [
2928
2975
  "resize-pane",
2929
2976
  "-t",
2930
2977
  paneId,
@@ -3246,7 +3293,7 @@ function launchctlGuiDomain() {
3246
3293
  }
3247
3294
  function runLaunchctl(args) {
3248
3295
  try {
3249
- execFileSync("launchctl", args, {
3296
+ execFileSync2("launchctl", args, {
3250
3297
  stdio: "ignore"
3251
3298
  });
3252
3299
  return true;
@@ -3623,7 +3670,7 @@ async function resolveAppUrl(raw) {
3623
3670
  return url;
3624
3671
  }
3625
3672
  }
3626
- async function fetchConvexUrl(appUrl) {
3673
+ async function fetchAppConfig(appUrl) {
3627
3674
  try {
3628
3675
  const url = new URL("/api/config", appUrl).toString();
3629
3676
  const response = await fetch(url);
@@ -3632,11 +3679,18 @@ async function fetchConvexUrl(appUrl) {
3632
3679
  }
3633
3680
  const data = await response.json();
3634
3681
  if (data.convexUrl) {
3635
- return data.convexUrl;
3682
+ return {
3683
+ convexUrl: data.convexUrl,
3684
+ tunnelHost: data.tunnelHost || void 0
3685
+ };
3636
3686
  }
3637
3687
  } catch {
3638
3688
  }
3639
- return "http://127.0.0.1:3210";
3689
+ return { convexUrl: "http://127.0.0.1:3210" };
3690
+ }
3691
+ async function fetchConvexUrl(appUrl) {
3692
+ const config = await fetchAppConfig(appUrl);
3693
+ return config.convexUrl;
3640
3694
  }
3641
3695
  async function getRuntime(command) {
3642
3696
  const options = command.optsWithGlobals();
@@ -3781,6 +3835,10 @@ async function ensureBridgeConfig(command) {
3781
3835
  if (!config) {
3782
3836
  throw new Error("Bridge device is not configured.");
3783
3837
  }
3838
+ const appConfig = await fetchAppConfig(runtime.appUrl);
3839
+ if (appConfig.tunnelHost) {
3840
+ config.tunnelHost = appConfig.tunnelHost;
3841
+ }
3784
3842
  saveBridgeConfig(config);
3785
3843
  return config;
3786
3844
  } catch (error) {