@rehpic/vcli 0.1.0-beta.61.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");
@@ -1056,15 +1056,68 @@ function printOutput(data, json = false) {
1056
1056
  }
1057
1057
 
1058
1058
  // src/session.ts
1059
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
1059
+ import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
1060
1060
  import { homedir } from "os";
1061
1061
  import path from "path";
1062
1062
  function getSessionRoot() {
1063
1063
  return process.env.VECTOR_HOME?.trim() || path.join(homedir(), ".vector");
1064
1064
  }
1065
+ function getProfileConfigPath() {
1066
+ return path.join(getSessionRoot(), "cli-config.json");
1067
+ }
1065
1068
  function getSessionPath(profile = "default") {
1066
1069
  return path.join(getSessionRoot(), `cli-${profile}.json`);
1067
1070
  }
1071
+ async function readDefaultProfile() {
1072
+ try {
1073
+ const raw = await readFile(getProfileConfigPath(), "utf8");
1074
+ const parsed = JSON.parse(raw);
1075
+ const profile = parsed.defaultProfile?.trim();
1076
+ return profile || "default";
1077
+ } catch {
1078
+ return "default";
1079
+ }
1080
+ }
1081
+ async function writeDefaultProfile(profile) {
1082
+ const normalized = profile.trim() || "default";
1083
+ await mkdir(getSessionRoot(), { recursive: true });
1084
+ const config = {
1085
+ version: 1,
1086
+ defaultProfile: normalized
1087
+ };
1088
+ await writeFile(
1089
+ getProfileConfigPath(),
1090
+ `${JSON.stringify(config, null, 2)}
1091
+ `,
1092
+ "utf8"
1093
+ );
1094
+ }
1095
+ async function listProfiles() {
1096
+ const root = getSessionRoot();
1097
+ const defaultProfile = await readDefaultProfile();
1098
+ try {
1099
+ const entries = await readdir(root, { withFileTypes: true });
1100
+ const names = entries.filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => /^cli-.+\.json$/.test(name)).map((name) => name.replace(/^cli-/, "").replace(/\.json$/, ""));
1101
+ const uniqueNames = Array.from(/* @__PURE__ */ new Set([...names, defaultProfile])).sort(
1102
+ (left, right) => left.localeCompare(right)
1103
+ );
1104
+ return Promise.all(
1105
+ uniqueNames.map(async (name) => ({
1106
+ name,
1107
+ isDefault: name === defaultProfile,
1108
+ hasSession: await readSession(name) !== null
1109
+ }))
1110
+ );
1111
+ } catch {
1112
+ return [
1113
+ {
1114
+ name: defaultProfile,
1115
+ isDefault: true,
1116
+ hasSession: await readSession(defaultProfile) !== null
1117
+ }
1118
+ ];
1119
+ }
1120
+ }
1068
1121
  async function readSession(profile = "default") {
1069
1122
  try {
1070
1123
  const raw = await readFile(getSessionPath(profile), "utf8");
@@ -1099,9 +1152,285 @@ function createEmptySession() {
1099
1152
 
1100
1153
  // src/bridge-service.ts
1101
1154
  import { ConvexHttpClient as ConvexHttpClient2 } from "convex/browser";
1102
- import { execFileSync, execSync as execSync2 } from "child_process";
1155
+ import { execFileSync as execFileSync2, execSync as execSync2 } from "child_process";
1156
+
1157
+ // src/terminal-peer.ts
1158
+ import { createServer } from "http";
1159
+ import { WebSocketServer, WebSocket } from "ws";
1160
+ import { ConvexClient } from "convex/browser";
1161
+ import * as pty from "node-pty";
1162
+ import { existsSync } from "fs";
1163
+ import { randomUUID } from "crypto";
1164
+ import { execFileSync } from "child_process";
1165
+ import localtunnel from "localtunnel";
1166
+ function findTmuxPath() {
1167
+ for (const p of [
1168
+ "/opt/homebrew/bin/tmux",
1169
+ "/usr/local/bin/tmux",
1170
+ "/usr/bin/tmux"
1171
+ ]) {
1172
+ if (existsSync(p)) return p;
1173
+ }
1174
+ return "tmux";
1175
+ }
1176
+ var TMUX = findTmuxPath();
1177
+ function ts() {
1178
+ return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
1179
+ }
1180
+ function findPort() {
1181
+ return new Promise((resolve, reject) => {
1182
+ const srv = createServer();
1183
+ srv.listen(0, "127.0.0.1", () => {
1184
+ const addr = srv.address();
1185
+ const port = typeof addr === "object" && addr ? addr.port : 9100;
1186
+ srv.close(() => resolve(port));
1187
+ });
1188
+ srv.on("error", reject);
1189
+ });
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
+ }
1221
+ var TerminalPeerManager = class {
1222
+ constructor(config) {
1223
+ this.terminals = /* @__PURE__ */ new Map();
1224
+ this.failedSessions = /* @__PURE__ */ new Set();
1225
+ this.pendingStops = /* @__PURE__ */ new Map();
1226
+ this.unsubscribers = /* @__PURE__ */ new Map();
1227
+ this.config = config;
1228
+ this.client = new ConvexClient(config.convexUrl);
1229
+ }
1230
+ watchSession(workSessionId, tmuxSessionName, tmuxPaneId) {
1231
+ if (this.unsubscribers.has(workSessionId)) return;
1232
+ const unsub = this.client.onUpdate(
1233
+ api.agentBridge.bridgePublic.getWorkSessionTerminalState,
1234
+ {
1235
+ deviceId: this.config.deviceId,
1236
+ deviceSecret: this.config.deviceSecret,
1237
+ workSessionId
1238
+ },
1239
+ (state) => {
1240
+ if (!state) return;
1241
+ const terminal = this.terminals.get(workSessionId);
1242
+ if (state.terminalViewerActive && !terminal && !this.failedSessions.has(workSessionId)) {
1243
+ const pendingStop = this.pendingStops.get(workSessionId);
1244
+ if (pendingStop) {
1245
+ clearTimeout(pendingStop);
1246
+ this.pendingStops.delete(workSessionId);
1247
+ }
1248
+ console.log(`[${ts()}] Viewer active for ${tmuxSessionName}`);
1249
+ void this.startTerminal(
1250
+ workSessionId,
1251
+ tmuxSessionName,
1252
+ tmuxPaneId,
1253
+ state.terminalCols,
1254
+ state.terminalRows
1255
+ );
1256
+ } else if (!state.terminalViewerActive && terminal) {
1257
+ if (!this.pendingStops.has(workSessionId)) {
1258
+ this.pendingStops.set(
1259
+ workSessionId,
1260
+ setTimeout(() => {
1261
+ this.pendingStops.delete(workSessionId);
1262
+ console.log(`[${ts()}] Viewer inactive for ${tmuxSessionName}`);
1263
+ this.stopTerminal(workSessionId);
1264
+ this.failedSessions.delete(workSessionId);
1265
+ }, 2e3)
1266
+ );
1267
+ }
1268
+ }
1269
+ }
1270
+ );
1271
+ this.unsubscribers.set(workSessionId, unsub);
1272
+ }
1273
+ unwatchSession(workSessionId) {
1274
+ const unsub = this.unsubscribers.get(workSessionId);
1275
+ if (unsub) {
1276
+ unsub();
1277
+ this.unsubscribers.delete(workSessionId);
1278
+ }
1279
+ this.stopTerminal(workSessionId);
1280
+ }
1281
+ async startTerminal(workSessionId, tmuxSessionName, tmuxPaneId, cols, rows) {
1282
+ if (this.terminals.has(workSessionId)) return;
1283
+ try {
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
+ );
1290
+ console.log(
1291
+ `[${ts()}] Spawning PTY: ${TMUX} attach-session -t ${viewerSession}`
1292
+ );
1293
+ const ptyProcess = pty.spawn(
1294
+ TMUX,
1295
+ ["attach-session", "-t", viewerSession],
1296
+ {
1297
+ name: "xterm-256color",
1298
+ cols: Math.max(cols, 10),
1299
+ rows: Math.max(rows, 4),
1300
+ cwd: process.env.HOME ?? "/",
1301
+ env: { ...process.env, TERM: "xterm-256color" }
1302
+ }
1303
+ );
1304
+ console.log(`[${ts()}] PTY started`);
1305
+ const token = randomUUID();
1306
+ const httpServer = createServer();
1307
+ const wss = new WebSocketServer({ server: httpServer });
1308
+ wss.on("connection", (ws, req) => {
1309
+ const url = new URL(req.url ?? "/", `http://localhost`);
1310
+ const clientToken = url.searchParams.get("token");
1311
+ if (clientToken !== token) {
1312
+ console.log(`[${ts()}] Rejected unauthorized connection`);
1313
+ ws.close(4401, "Unauthorized");
1314
+ return;
1315
+ }
1316
+ console.log(`[${ts()}] Client connected (${tmuxSessionName})`);
1317
+ const dataHandler = ptyProcess.onData((data) => {
1318
+ if (ws.readyState === WebSocket.OPEN) {
1319
+ ws.send(data);
1320
+ }
1321
+ });
1322
+ ws.on("message", (msg) => {
1323
+ const str = msg.toString();
1324
+ if (str.startsWith("\0{")) {
1325
+ try {
1326
+ const parsed = JSON.parse(str.slice(1));
1327
+ if (parsed.type === "resize" && parsed.cols && parsed.rows) {
1328
+ ptyProcess.resize(
1329
+ Math.max(parsed.cols, 10),
1330
+ Math.max(parsed.rows, 4)
1331
+ );
1332
+ return;
1333
+ }
1334
+ } catch {
1335
+ }
1336
+ }
1337
+ ptyProcess.write(str);
1338
+ });
1339
+ ws.on("close", () => {
1340
+ console.log(`[${ts()}] Client disconnected (${tmuxSessionName})`);
1341
+ dataHandler.dispose();
1342
+ });
1343
+ });
1344
+ await new Promise((resolve) => {
1345
+ httpServer.listen(port, "0.0.0.0", resolve);
1346
+ });
1347
+ console.log(`[${ts()}] WS server on port ${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);
1356
+ const tunnelUrl = tunnel.url;
1357
+ console.log(`[${ts()}] Tunnel: ${tunnelUrl}`);
1358
+ const wsUrl = tunnelUrl.replace(/^https?:\/\//, "wss://");
1359
+ const terminal = {
1360
+ ptyProcess,
1361
+ httpServer,
1362
+ wss,
1363
+ tunnel,
1364
+ viewerSessionName: isLinked ? viewerSession : null,
1365
+ token,
1366
+ workSessionId,
1367
+ tmuxSessionName,
1368
+ port
1369
+ };
1370
+ this.terminals.set(workSessionId, terminal);
1371
+ await this.client.mutation(
1372
+ api.agentBridge.bridgePublic.updateWorkSessionTerminalUrl,
1373
+ {
1374
+ deviceId: this.config.deviceId,
1375
+ deviceSecret: this.config.deviceSecret,
1376
+ workSessionId,
1377
+ terminalUrl: wsUrl,
1378
+ terminalToken: token,
1379
+ terminalLocalPort: port
1380
+ }
1381
+ );
1382
+ ptyProcess.onExit(() => {
1383
+ console.log(`[${ts()}] PTY exited for ${tmuxSessionName}`);
1384
+ this.stopTerminal(workSessionId);
1385
+ });
1386
+ } catch (err) {
1387
+ console.error(`[${ts()}] Failed to start terminal:`, err);
1388
+ this.failedSessions.add(workSessionId);
1389
+ }
1390
+ }
1391
+ stopTerminal(workSessionId) {
1392
+ const terminal = this.terminals.get(workSessionId);
1393
+ if (!terminal) return;
1394
+ try {
1395
+ terminal.ptyProcess.kill();
1396
+ } catch {
1397
+ }
1398
+ try {
1399
+ terminal.tunnel.close();
1400
+ } catch {
1401
+ }
1402
+ try {
1403
+ terminal.wss.close();
1404
+ } catch {
1405
+ }
1406
+ try {
1407
+ terminal.httpServer.close();
1408
+ } catch {
1409
+ }
1410
+ if (terminal.viewerSessionName) {
1411
+ killViewerSession(terminal.viewerSessionName);
1412
+ }
1413
+ this.terminals.delete(workSessionId);
1414
+ console.log(`[${ts()}] Terminal stopped for ${terminal.tmuxSessionName}`);
1415
+ }
1416
+ stop() {
1417
+ for (const unsub of this.unsubscribers.values()) {
1418
+ try {
1419
+ unsub();
1420
+ } catch {
1421
+ }
1422
+ }
1423
+ this.unsubscribers.clear();
1424
+ for (const id of this.terminals.keys()) {
1425
+ this.stopTerminal(id);
1426
+ }
1427
+ void this.client.close();
1428
+ }
1429
+ };
1430
+
1431
+ // src/bridge-service.ts
1103
1432
  import {
1104
- existsSync as existsSync2,
1433
+ existsSync as existsSync3,
1105
1434
  mkdirSync,
1106
1435
  readFileSync as readFileSync2,
1107
1436
  writeFileSync,
@@ -1109,17 +1438,18 @@ import {
1109
1438
  } from "fs";
1110
1439
  import { homedir as homedir3, hostname, platform } from "os";
1111
1440
  import { join as join2 } from "path";
1112
- import { randomUUID } from "crypto";
1441
+ import { randomUUID as randomUUID2 } from "crypto";
1113
1442
 
1114
1443
  // src/agent-adapters.ts
1115
- import { execSync, spawn } from "child_process";
1116
- import { existsSync, readFileSync, readdirSync } from "fs";
1444
+ import { execSync, spawn as spawn2 } from "child_process";
1445
+ import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
1117
1446
  import { homedir as homedir2, userInfo } from "os";
1118
1447
  import { basename, join } from "path";
1119
1448
  var LSOF_PATHS = ["/usr/sbin/lsof", "/usr/bin/lsof"];
1120
1449
  var VECTOR_BRIDGE_CLIENT_VERSION = "0.1.0";
1121
1450
  function discoverAttachableSessions() {
1122
1451
  return dedupeSessions([
1452
+ ...discoverTmuxSessions(),
1123
1453
  ...discoverCodexSessions(),
1124
1454
  ...discoverClaudeSessions()
1125
1455
  ]);
@@ -1141,7 +1471,7 @@ async function resumeProviderSession(provider, sessionKey, cwd, prompt2) {
1141
1471
  });
1142
1472
  }
1143
1473
  async function runCodexAppServerTurn(args) {
1144
- const child = spawn("codex", ["app-server"], {
1474
+ const child = spawn2("codex", ["app-server"], {
1145
1475
  cwd: args.cwd,
1146
1476
  env: { ...process.env },
1147
1477
  stdio: ["pipe", "pipe", "pipe"]
@@ -1429,6 +1759,65 @@ function discoverClaudeSessions() {
1429
1759
  return parsed ? [parsed] : [];
1430
1760
  }).sort(compareObservedSessions);
1431
1761
  }
1762
+ function discoverTmuxSessions() {
1763
+ try {
1764
+ const output = execSync(
1765
+ "tmux list-panes -a -F '#{pane_id} #{pane_pid} #{session_name} #{window_name} #{pane_current_path} #{pane_current_command} #{pane_title}'",
1766
+ {
1767
+ encoding: "utf-8",
1768
+ timeout: 3e3
1769
+ }
1770
+ );
1771
+ return output.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
1772
+ const [
1773
+ paneId,
1774
+ panePid,
1775
+ sessionName,
1776
+ windowName,
1777
+ cwd,
1778
+ currentCommand,
1779
+ paneTitle
1780
+ ] = line.split(" ");
1781
+ if (!paneId || !panePid || !sessionName || !windowName || !cwd) {
1782
+ return [];
1783
+ }
1784
+ const normalizedCommand = (currentCommand ?? "").trim().toLowerCase();
1785
+ if (normalizedCommand === "codex" || normalizedCommand === "claude") {
1786
+ return [];
1787
+ }
1788
+ const gitInfo = getGitInfo(cwd);
1789
+ const title = summarizeTitle(
1790
+ buildTmuxPaneTitle({
1791
+ paneTitle,
1792
+ sessionName,
1793
+ windowName,
1794
+ cwd,
1795
+ currentCommand
1796
+ }),
1797
+ cwd
1798
+ );
1799
+ return [
1800
+ {
1801
+ provider: "vector_cli",
1802
+ providerLabel: "Tmux",
1803
+ localProcessId: panePid,
1804
+ sessionKey: `tmux:${paneId}`,
1805
+ cwd,
1806
+ ...gitInfo,
1807
+ title,
1808
+ tmuxSessionName: sessionName,
1809
+ tmuxWindowName: windowName,
1810
+ tmuxPaneId: paneId,
1811
+ mode: "observed",
1812
+ status: "observed",
1813
+ supportsInboundMessages: true
1814
+ }
1815
+ ];
1816
+ }).sort(compareObservedSessions);
1817
+ } catch {
1818
+ return [];
1819
+ }
1820
+ }
1432
1821
  function getCodexHistoryFile() {
1433
1822
  return join(getRealHomeDir(), ".codex", "history.jsonl");
1434
1823
  }
@@ -1453,7 +1842,7 @@ function getRealHomeDir() {
1453
1842
  }
1454
1843
  function resolveExecutable(fallbackCommand, absoluteCandidates) {
1455
1844
  for (const candidate of absoluteCandidates) {
1456
- if (existsSync(candidate)) {
1845
+ if (existsSync2(candidate)) {
1457
1846
  return candidate;
1458
1847
  }
1459
1848
  }
@@ -1512,7 +1901,7 @@ function getCodexTranscriptPath(pid) {
1512
1901
  }
1513
1902
  function readClaudePidSession(pid) {
1514
1903
  const path3 = join(getClaudeSessionStateDir(), `${pid}.json`);
1515
- if (!existsSync(path3)) {
1904
+ if (!existsSync2(path3)) {
1516
1905
  return null;
1517
1906
  }
1518
1907
  try {
@@ -1534,7 +1923,7 @@ function findClaudeTranscriptPath(sessionId) {
1534
1923
  return findJsonlFileByStem(getClaudeProjectsDir(), sessionId);
1535
1924
  }
1536
1925
  function findJsonlFileByStem(root, stem) {
1537
- if (!existsSync(root)) {
1926
+ if (!existsSync2(root)) {
1538
1927
  return void 0;
1539
1928
  }
1540
1929
  for (const entry of readdirSync(root, { withFileTypes: true })) {
@@ -1694,6 +2083,17 @@ function summarizeTitle(message, cwd) {
1694
2083
  }
1695
2084
  return "Local session";
1696
2085
  }
2086
+ function buildTmuxPaneTitle(args) {
2087
+ const paneTitle = cleanSessionTitleCandidate(args.paneTitle ?? "");
2088
+ if (paneTitle) {
2089
+ return paneTitle;
2090
+ }
2091
+ const command = asString(args.currentCommand);
2092
+ if (command && !["zsh", "bash", "fish", "sh", "nu"].includes(command)) {
2093
+ return `${command} in ${basename(args.cwd)}`;
2094
+ }
2095
+ return `${basename(args.cwd)} (${args.sessionName}:${args.windowName})`;
2096
+ }
1697
2097
  function truncate(value, maxLength) {
1698
2098
  return value.length > maxLength ? `${value.slice(0, maxLength - 3).trimEnd()}...` : value;
1699
2099
  }
@@ -1912,7 +2312,7 @@ var COMMAND_POLL_INTERVAL_MS = 5e3;
1912
2312
  var LIVE_ACTIVITY_SYNC_INTERVAL_MS = 5e3;
1913
2313
  var PROCESS_DISCOVERY_INTERVAL_MS = 6e4;
1914
2314
  function loadBridgeConfig() {
1915
- if (!existsSync2(BRIDGE_CONFIG_FILE)) return null;
2315
+ if (!existsSync3(BRIDGE_CONFIG_FILE)) return null;
1916
2316
  try {
1917
2317
  return JSON.parse(readFileSync2(BRIDGE_CONFIG_FILE, "utf-8"));
1918
2318
  } catch {
@@ -1920,17 +2320,18 @@ function loadBridgeConfig() {
1920
2320
  }
1921
2321
  }
1922
2322
  function saveBridgeConfig(config) {
1923
- if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2323
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1924
2324
  writeFileSync(BRIDGE_CONFIG_FILE, JSON.stringify(config, null, 2));
1925
2325
  persistDeviceKey(config.deviceKey);
1926
2326
  }
1927
2327
  function writeLiveActivitiesCache(activities) {
1928
- if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2328
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1929
2329
  writeFileSync(LIVE_ACTIVITIES_CACHE, JSON.stringify(activities, null, 2));
1930
2330
  }
1931
2331
  var BridgeService = class {
1932
2332
  constructor(config) {
1933
2333
  this.timers = [];
2334
+ this.terminalPeer = null;
1934
2335
  this.config = config;
1935
2336
  this.client = new ConvexHttpClient2(config.convexUrl);
1936
2337
  }
@@ -1949,7 +2350,7 @@ var BridgeService = class {
1949
2350
  }
1950
2351
  );
1951
2352
  if (commands.length > 0) {
1952
- console.log(`[${ts()}] ${commands.length} pending command(s)`);
2353
+ console.log(`[${ts2()}] ${commands.length} pending command(s)`);
1953
2354
  }
1954
2355
  for (const cmd of commands) {
1955
2356
  await this.handleCommand(cmd);
@@ -1978,6 +2379,10 @@ var BridgeService = class {
1978
2379
  await this.handleLaunchCommand(cmd);
1979
2380
  await this.completeCommand(cmd._id, "delivered");
1980
2381
  return;
2382
+ case "resize":
2383
+ await this.handleResizeCommand(cmd);
2384
+ await this.completeCommand(cmd._id, "delivered");
2385
+ return;
1981
2386
  default:
1982
2387
  throw new Error(`Unsupported bridge command: ${cmd.kind}`);
1983
2388
  }
@@ -2012,7 +2417,7 @@ var BridgeService = class {
2012
2417
  }
2013
2418
  if (processes.length > 0) {
2014
2419
  console.log(
2015
- `[${ts()}] Discovered ${processes.length} attachable session(s)`
2420
+ `[${ts2()}] Discovered ${processes.length} attachable session(s)`
2016
2421
  );
2017
2422
  }
2018
2423
  }
@@ -2027,6 +2432,17 @@ var BridgeService = class {
2027
2432
  );
2028
2433
  writeLiveActivitiesCache(activities);
2029
2434
  await this.syncWorkSessionTerminals(activities);
2435
+ if (this.terminalPeer) {
2436
+ for (const activity of activities) {
2437
+ if (activity.workSessionId && activity.tmuxSessionName) {
2438
+ this.terminalPeer.watchSession(
2439
+ activity.workSessionId,
2440
+ activity.tmuxSessionName,
2441
+ activity.tmuxPaneId
2442
+ );
2443
+ }
2444
+ }
2445
+ }
2030
2446
  } catch {
2031
2447
  }
2032
2448
  }
@@ -2115,45 +2531,62 @@ var BridgeService = class {
2115
2531
  console.log(` Convex: ${this.config.convexUrl}`);
2116
2532
  console.log(` PID: ${process.pid}`);
2117
2533
  console.log("");
2118
- if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2534
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2119
2535
  writeFileSync(PID_FILE, String(process.pid));
2536
+ try {
2537
+ this.terminalPeer = new TerminalPeerManager({
2538
+ deviceId: this.config.deviceId,
2539
+ deviceSecret: this.config.deviceSecret,
2540
+ convexUrl: this.config.convexUrl,
2541
+ tunnelHost: this.config.tunnelHost
2542
+ });
2543
+ console.log(
2544
+ ` Terminal: ready${this.config.tunnelHost ? ` (tunnel: ${this.config.tunnelHost})` : ""}`
2545
+ );
2546
+ } catch (e) {
2547
+ console.error(
2548
+ ` WebRTC: failed (${e instanceof Error ? e.message : "unknown"})`
2549
+ );
2550
+ }
2551
+ console.log("");
2120
2552
  await this.heartbeat();
2121
2553
  await this.reportProcesses();
2122
2554
  await this.refreshLiveActivities();
2123
- console.log(`[${ts()}] Service running. Ctrl+C to stop.
2555
+ console.log(`[${ts2()}] Service running. Ctrl+C to stop.
2124
2556
  `);
2125
2557
  this.timers.push(
2126
2558
  setInterval(() => {
2127
2559
  this.heartbeat().catch(
2128
- (e) => console.error(`[${ts()}] Heartbeat error:`, e.message)
2560
+ (e) => console.error(`[${ts2()}] Heartbeat error:`, e.message)
2129
2561
  );
2130
2562
  }, HEARTBEAT_INTERVAL_MS)
2131
2563
  );
2132
2564
  this.timers.push(
2133
2565
  setInterval(() => {
2134
2566
  this.pollCommands().catch(
2135
- (e) => console.error(`[${ts()}] Command poll error:`, e.message)
2567
+ (e) => console.error(`[${ts2()}] Command poll error:`, e.message)
2136
2568
  );
2137
2569
  }, COMMAND_POLL_INTERVAL_MS)
2138
2570
  );
2139
2571
  this.timers.push(
2140
2572
  setInterval(() => {
2141
2573
  this.refreshLiveActivities().catch(
2142
- (e) => console.error(`[${ts()}] Live activity sync error:`, e.message)
2574
+ (e) => console.error(`[${ts2()}] Live activity sync error:`, e.message)
2143
2575
  );
2144
2576
  }, LIVE_ACTIVITY_SYNC_INTERVAL_MS)
2145
2577
  );
2146
2578
  this.timers.push(
2147
2579
  setInterval(() => {
2148
2580
  this.reportProcesses().catch(
2149
- (e) => console.error(`[${ts()}] Discovery error:`, e.message)
2581
+ (e) => console.error(`[${ts2()}] Discovery error:`, e.message)
2150
2582
  );
2151
2583
  }, PROCESS_DISCOVERY_INTERVAL_MS)
2152
2584
  );
2153
2585
  const shutdown = () => {
2154
2586
  console.log(`
2155
- [${ts()}] Shutting down...`);
2587
+ [${ts2()}] Shutting down...`);
2156
2588
  for (const t of this.timers) clearInterval(t);
2589
+ this.terminalPeer?.stop();
2157
2590
  try {
2158
2591
  unlinkSync(PID_FILE);
2159
2592
  } catch {
@@ -2252,6 +2685,29 @@ var BridgeService = class {
2252
2685
  title: cmd.liveActivity?.title ?? process9.title
2253
2686
  });
2254
2687
  }
2688
+ async handleResizeCommand(cmd) {
2689
+ const payload = cmd.payload;
2690
+ const cols = payload?.cols;
2691
+ const rows = payload?.rows;
2692
+ const paneId = cmd.workSession?.tmuxPaneId;
2693
+ if (!paneId || !cols || !rows) {
2694
+ throw new Error("Resize command missing paneId, cols, or rows");
2695
+ }
2696
+ console.log(` Resize ${paneId} \u2192 ${cols}x${rows}`);
2697
+ resizeTmuxPane(paneId, cols, rows);
2698
+ if (cmd.workSession) {
2699
+ await this.refreshWorkSessionTerminal(cmd.workSession._id, {
2700
+ tmuxSessionName: cmd.workSession.tmuxSessionName,
2701
+ tmuxWindowName: cmd.workSession.tmuxWindowName,
2702
+ tmuxPaneId: paneId,
2703
+ cwd: cmd.workSession.cwd,
2704
+ repoRoot: cmd.workSession.repoRoot,
2705
+ branch: cmd.workSession.branch,
2706
+ agentProvider: cmd.workSession.agentProvider,
2707
+ agentSessionKey: cmd.workSession.agentSessionKey
2708
+ });
2709
+ }
2710
+ }
2255
2711
  async handleLaunchCommand(cmd) {
2256
2712
  if (!cmd.liveActivityId) {
2257
2713
  throw new Error("Launch command is missing liveActivityId");
@@ -2365,6 +2821,9 @@ var BridgeService = class {
2365
2821
  branch,
2366
2822
  title,
2367
2823
  model,
2824
+ tmuxSessionName,
2825
+ tmuxWindowName,
2826
+ tmuxPaneId,
2368
2827
  mode,
2369
2828
  status,
2370
2829
  supportsInboundMessages
@@ -2383,6 +2842,9 @@ var BridgeService = class {
2383
2842
  branch,
2384
2843
  title,
2385
2844
  model,
2845
+ tmuxSessionName,
2846
+ tmuxWindowName,
2847
+ tmuxPaneId,
2386
2848
  mode,
2387
2849
  status,
2388
2850
  supportsInboundMessages
@@ -2440,11 +2902,11 @@ var BridgeService = class {
2440
2902
  };
2441
2903
  function createTmuxWorkSession(args) {
2442
2904
  const slug = sanitizeTmuxName(args.issueKey.toLowerCase());
2443
- const sessionName = `vector-${slug}-${randomUUID().slice(0, 8)}`;
2905
+ const sessionName = `vector-${slug}-${randomUUID2().slice(0, 8)}`;
2444
2906
  const windowName = sanitizeTmuxName(
2445
2907
  args.provider === "codex" ? "codex" : args.provider === "claude_code" ? "claude" : "shell"
2446
2908
  );
2447
- execFileSync("tmux", [
2909
+ execFileSync2("tmux", [
2448
2910
  "new-session",
2449
2911
  "-d",
2450
2912
  "-s",
@@ -2454,7 +2916,7 @@ function createTmuxWorkSession(args) {
2454
2916
  "-c",
2455
2917
  args.workspacePath
2456
2918
  ]);
2457
- const paneId = execFileSync(
2919
+ const paneId = execFileSync2(
2458
2920
  "tmux",
2459
2921
  [
2460
2922
  "display-message",
@@ -2465,13 +2927,13 @@ function createTmuxWorkSession(args) {
2465
2927
  ],
2466
2928
  { encoding: "utf-8" }
2467
2929
  ).trim();
2468
- const paneProcessId = execFileSync(
2930
+ const paneProcessId = execFileSync2(
2469
2931
  "tmux",
2470
2932
  ["display-message", "-p", "-t", paneId, "#{pane_pid}"],
2471
2933
  { encoding: "utf-8" }
2472
2934
  ).trim();
2473
2935
  if (args.provider) {
2474
- execFileSync("tmux", [
2936
+ execFileSync2("tmux", [
2475
2937
  "send-keys",
2476
2938
  "-t",
2477
2939
  paneId,
@@ -2479,7 +2941,7 @@ function createTmuxWorkSession(args) {
2479
2941
  "Enter"
2480
2942
  ]);
2481
2943
  } else {
2482
- execFileSync("tmux", [
2944
+ execFileSync2("tmux", [
2483
2945
  "send-keys",
2484
2946
  "-t",
2485
2947
  paneId,
@@ -2495,17 +2957,32 @@ function createTmuxWorkSession(args) {
2495
2957
  };
2496
2958
  }
2497
2959
  function sendTextToTmuxPane(paneId, text2) {
2498
- execFileSync("tmux", ["set-buffer", "--", text2]);
2499
- execFileSync("tmux", ["paste-buffer", "-t", paneId]);
2500
- execFileSync("tmux", ["send-keys", "-t", paneId, "Enter"]);
2501
- 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"]);
2502
2964
  }
2503
2965
  function captureTmuxPane(paneId) {
2504
- return execFileSync(
2966
+ return execFileSync2(
2505
2967
  "tmux",
2506
- ["capture-pane", "-p", "-t", paneId, "-S", "-120"],
2968
+ ["capture-pane", "-p", "-e", "-t", paneId, "-S", "-120"],
2507
2969
  { encoding: "utf-8" }
2508
- ).replace(/\u001B\[[0-9;?]*[A-Za-z]/g, "").trimEnd();
2970
+ ).trimEnd();
2971
+ }
2972
+ function resizeTmuxPane(paneId, cols, rows) {
2973
+ try {
2974
+ execFileSync2("tmux", [
2975
+ "resize-pane",
2976
+ "-t",
2977
+ paneId,
2978
+ "-x",
2979
+ String(cols),
2980
+ "-y",
2981
+ String(rows)
2982
+ ]);
2983
+ } catch (e) {
2984
+ console.error(`Failed to resize pane ${paneId}:`, e);
2985
+ }
2509
2986
  }
2510
2987
  function currentGitBranch(cwd) {
2511
2988
  try {
@@ -2564,18 +3041,18 @@ function getStableDeviceKey() {
2564
3041
  persistDeviceKey(existingKey);
2565
3042
  return existingKey;
2566
3043
  }
2567
- if (existsSync2(DEVICE_KEY_FILE)) {
3044
+ if (existsSync3(DEVICE_KEY_FILE)) {
2568
3045
  const savedKey = readFileSync2(DEVICE_KEY_FILE, "utf-8").trim();
2569
3046
  if (savedKey) {
2570
3047
  return savedKey;
2571
3048
  }
2572
3049
  }
2573
- const generatedKey = `${hostname()}-${randomUUID().slice(0, 8)}`;
3050
+ const generatedKey = `${hostname()}-${randomUUID2().slice(0, 8)}`;
2574
3051
  persistDeviceKey(generatedKey);
2575
3052
  return generatedKey;
2576
3053
  }
2577
3054
  function persistDeviceKey(deviceKey) {
2578
- if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
3055
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2579
3056
  writeFileSync(DEVICE_KEY_FILE, `${deviceKey}
2580
3057
  `);
2581
3058
  }
@@ -2672,7 +3149,13 @@ function isBridgeProvider(provider) {
2672
3149
  return provider === "codex" || provider === "claude_code";
2673
3150
  }
2674
3151
  function providerLabel(provider) {
2675
- return provider === "codex" ? "Codex" : "Claude";
3152
+ if (provider === "codex") {
3153
+ return "Codex";
3154
+ }
3155
+ if (provider === "claude_code") {
3156
+ return "Claude";
3157
+ }
3158
+ return "Vector CLI";
2676
3159
  }
2677
3160
  function installLaunchAgent(vcliPath) {
2678
3161
  if (platform() !== "darwin") {
@@ -2710,7 +3193,7 @@ ${environmentVariables}
2710
3193
  </dict>
2711
3194
  </dict>
2712
3195
  </plist>`;
2713
- if (!existsSync2(LAUNCHAGENT_DIR)) {
3196
+ if (!existsSync3(LAUNCHAGENT_DIR)) {
2714
3197
  mkdirSync(LAUNCHAGENT_DIR, { recursive: true });
2715
3198
  }
2716
3199
  removeLegacyMenuBarLaunchAgent();
@@ -2741,7 +3224,7 @@ function resolveCliInvocation(vcliPath) {
2741
3224
  ".bin",
2742
3225
  "tsx"
2743
3226
  );
2744
- if (existsSync2(tsxPath)) {
3227
+ if (existsSync3(tsxPath)) {
2745
3228
  return [tsxPath, vcliPath];
2746
3229
  }
2747
3230
  }
@@ -2790,7 +3273,7 @@ function uninstallLaunchAgent() {
2790
3273
  }
2791
3274
  var MENUBAR_PID_FILE = join2(CONFIG_DIR, "menubar.pid");
2792
3275
  function removeLegacyMenuBarLaunchAgent() {
2793
- if (platform() !== "darwin" || !existsSync2(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
3276
+ if (platform() !== "darwin" || !existsSync3(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
2794
3277
  return;
2795
3278
  }
2796
3279
  try {
@@ -2810,7 +3293,7 @@ function launchctlGuiDomain() {
2810
3293
  }
2811
3294
  function runLaunchctl(args) {
2812
3295
  try {
2813
- execFileSync("launchctl", args, {
3296
+ execFileSync2("launchctl", args, {
2814
3297
  stdio: "ignore"
2815
3298
  });
2816
3299
  return true;
@@ -2825,7 +3308,7 @@ function findCliEntrypoint() {
2825
3308
  join2(import.meta.dirname ?? "", "..", "dist", "index.js")
2826
3309
  ];
2827
3310
  for (const p of candidates) {
2828
- if (existsSync2(p)) return p;
3311
+ if (existsSync3(p)) return p;
2829
3312
  }
2830
3313
  return null;
2831
3314
  }
@@ -2857,7 +3340,7 @@ function findMenuBarExecutable() {
2857
3340
  )
2858
3341
  ];
2859
3342
  for (const p of candidates) {
2860
- if (existsSync2(p)) {
3343
+ if (existsSync3(p)) {
2861
3344
  return p;
2862
3345
  }
2863
3346
  }
@@ -2875,7 +3358,7 @@ function isKnownMenuBarProcess(pid) {
2875
3358
  }
2876
3359
  }
2877
3360
  function killExistingMenuBar() {
2878
- if (existsSync2(MENUBAR_PID_FILE)) {
3361
+ if (existsSync3(MENUBAR_PID_FILE)) {
2879
3362
  try {
2880
3363
  const pid = Number(readFileSync2(MENUBAR_PID_FILE, "utf-8").trim());
2881
3364
  if (Number.isFinite(pid) && pid > 0 && isKnownMenuBarProcess(pid)) {
@@ -2890,7 +3373,7 @@ function killExistingMenuBar() {
2890
3373
  }
2891
3374
  }
2892
3375
  function getRunningMenuBarPid() {
2893
- if (!existsSync2(MENUBAR_PID_FILE)) {
3376
+ if (!existsSync3(MENUBAR_PID_FILE)) {
2894
3377
  return null;
2895
3378
  }
2896
3379
  try {
@@ -2945,7 +3428,7 @@ function getBridgeStatus() {
2945
3428
  let running = false;
2946
3429
  let starting = false;
2947
3430
  let pid;
2948
- if (existsSync2(PID_FILE)) {
3431
+ if (existsSync3(PID_FILE)) {
2949
3432
  const pidStr = readFileSync2(PID_FILE, "utf-8").trim();
2950
3433
  pid = Number(pidStr);
2951
3434
  try {
@@ -2968,7 +3451,7 @@ function stopBridge(options) {
2968
3451
  writeLiveActivitiesCache([]);
2969
3452
  } catch {
2970
3453
  }
2971
- if (!existsSync2(PID_FILE)) return false;
3454
+ if (!existsSync3(PID_FILE)) return false;
2972
3455
  const pid = Number(readFileSync2(PID_FILE, "utf-8").trim());
2973
3456
  try {
2974
3457
  process.kill(pid, "SIGTERM");
@@ -2977,7 +3460,7 @@ function stopBridge(options) {
2977
3460
  return false;
2978
3461
  }
2979
3462
  }
2980
- function ts() {
3463
+ function ts2() {
2981
3464
  return (/* @__PURE__ */ new Date()).toLocaleTimeString();
2982
3465
  }
2983
3466
 
@@ -3187,7 +3670,7 @@ async function resolveAppUrl(raw) {
3187
3670
  return url;
3188
3671
  }
3189
3672
  }
3190
- async function fetchConvexUrl(appUrl) {
3673
+ async function fetchAppConfig(appUrl) {
3191
3674
  try {
3192
3675
  const url = new URL("/api/config", appUrl).toString();
3193
3676
  const response = await fetch(url);
@@ -3196,15 +3679,22 @@ async function fetchConvexUrl(appUrl) {
3196
3679
  }
3197
3680
  const data = await response.json();
3198
3681
  if (data.convexUrl) {
3199
- return data.convexUrl;
3682
+ return {
3683
+ convexUrl: data.convexUrl,
3684
+ tunnelHost: data.tunnelHost || void 0
3685
+ };
3200
3686
  }
3201
3687
  } catch {
3202
3688
  }
3203
- 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;
3204
3694
  }
3205
3695
  async function getRuntime(command) {
3206
3696
  const options = command.optsWithGlobals();
3207
- const profile = options.profile ?? "default";
3697
+ const profile = options.profile ?? await readDefaultProfile();
3208
3698
  const session = await readSession(profile);
3209
3699
  const appUrlSource = options.appUrl ?? session?.appUrl ?? process.env.NEXT_PUBLIC_APP_URL;
3210
3700
  const appUrl = await resolveAppUrl(requiredString(appUrlSource, "app URL"));
@@ -3345,6 +3835,10 @@ async function ensureBridgeConfig(command) {
3345
3835
  if (!config) {
3346
3836
  throw new Error("Bridge device is not configured.");
3347
3837
  }
3838
+ const appConfig = await fetchAppConfig(runtime.appUrl);
3839
+ if (appConfig.tunnelHost) {
3840
+ config.tunnelHost = appConfig.tunnelHost;
3841
+ }
3348
3842
  saveBridgeConfig(config);
3349
3843
  return config;
3350
3844
  } catch (error) {
@@ -3541,7 +4035,7 @@ function readPackageVersionSync() {
3541
4035
  program.name("vcli").description("Vector CLI").version(readPackageVersionSync(), "-v, --version").showHelpAfterError().option(
3542
4036
  "--app-url <url>",
3543
4037
  "Vector app URL. Required unless saved in the profile or NEXT_PUBLIC_APP_URL is set."
3544
- ).option("--convex-url <url>", "Convex deployment URL").option("--org <slug>", "Organization slug override").option("--profile <name>", "CLI profile name", "default").option("--json", "Output JSON");
4038
+ ).option("--convex-url <url>", "Convex deployment URL").option("--org <slug>", "Organization slug override").option("--profile <name>", "CLI profile name").option("--json", "Output JSON");
3545
4039
  var authCommand = program.command("auth").description("Authentication");
3546
4040
  authCommand.command("signup").option("--email <email>", "Email address").option("--username <username>", "Username").option("--password <password>", "Password").action(async (options, command) => {
3547
4041
  const runtime = await getRuntime(command);
@@ -3664,6 +4158,38 @@ authCommand.command("whoami").action(async (_options, command) => {
3664
4158
  runtime.json
3665
4159
  );
3666
4160
  });
4161
+ authCommand.command("profiles").action(async (_options, command) => {
4162
+ const options = command.optsWithGlobals();
4163
+ const explicitProfile = options.profile?.trim();
4164
+ const defaultProfile = await readDefaultProfile();
4165
+ const profiles = await listProfiles();
4166
+ const activeProfile = explicitProfile || defaultProfile;
4167
+ printOutput(
4168
+ {
4169
+ activeProfile,
4170
+ defaultProfile,
4171
+ profiles
4172
+ },
4173
+ Boolean(options.json)
4174
+ );
4175
+ });
4176
+ authCommand.command("use-profile <name>").action(async (name, _options, command) => {
4177
+ const options = command.optsWithGlobals();
4178
+ const profile = name.trim();
4179
+ if (!profile) {
4180
+ throw new Error("Profile name is required.");
4181
+ }
4182
+ await writeDefaultProfile(profile);
4183
+ const session = await readSession(profile);
4184
+ printOutput(
4185
+ {
4186
+ ok: true,
4187
+ defaultProfile: profile,
4188
+ hasSession: session !== null
4189
+ },
4190
+ Boolean(options.json)
4191
+ );
4192
+ });
3667
4193
  var orgCommand = program.command("org").description("Organizations");
3668
4194
  orgCommand.command("list").action(async (_options, command) => {
3669
4195
  const { client, runtime } = await getClient(command);
@@ -5145,8 +5671,11 @@ serviceCommand.command("status").description("Show bridge service status").actio
5145
5671
  serviceCommand.command("menu-state").description("Return JSON state for the macOS tray").action(async (_options, command) => {
5146
5672
  const status = getBridgeStatus();
5147
5673
  const globalOptions = command.optsWithGlobals();
5148
- const profile = globalOptions.profile ?? "default";
5674
+ const profile = globalOptions.profile ?? await readDefaultProfile();
5149
5675
  const session = await readSession(profile);
5676
+ const profiles = await listProfiles();
5677
+ const defaultProfile = await readDefaultProfile();
5678
+ let workspaces = [];
5150
5679
  let workSessions = [];
5151
5680
  let detectedSessions = [];
5152
5681
  try {
@@ -5157,6 +5686,13 @@ serviceCommand.command("menu-state").description("Return JSON state for the macO
5157
5686
  runtime.appUrl,
5158
5687
  runtime.convexUrl
5159
5688
  );
5689
+ workspaces = await runQuery(
5690
+ client,
5691
+ api.agentBridge.queries.listDeviceWorkspaces,
5692
+ {
5693
+ deviceId: status.config.deviceId
5694
+ }
5695
+ );
5160
5696
  workSessions = await runQuery(
5161
5697
  client,
5162
5698
  api.agentBridge.queries.listDeviceWorkSessions,
@@ -5175,6 +5711,7 @@ serviceCommand.command("menu-state").description("Return JSON state for the macO
5175
5711
  detectedSessions = currentDevice?.processes ?? [];
5176
5712
  }
5177
5713
  } catch {
5714
+ workspaces = [];
5178
5715
  workSessions = [];
5179
5716
  detectedSessions = [];
5180
5717
  }
@@ -5186,12 +5723,27 @@ serviceCommand.command("menu-state").description("Return JSON state for the macO
5186
5723
  pid: status.pid,
5187
5724
  config: status.config,
5188
5725
  sessionInfo: buildMenuSessionInfo(session),
5726
+ activeProfile: profile,
5727
+ defaultProfile,
5728
+ profiles,
5729
+ workspaces,
5189
5730
  workSessions,
5190
5731
  detectedSessions
5191
5732
  },
5192
5733
  Boolean(globalOptions.json)
5193
5734
  );
5194
5735
  });
5736
+ serviceCommand.command("set-default-workspace").description("Set the default workspace for this device").requiredOption("--workspace-id <id>").action(async (options, command) => {
5737
+ const { client, runtime } = await getClient(command);
5738
+ const workspaceId = await runMutation(
5739
+ client,
5740
+ api.agentBridge.mutations.setDefaultWorkspace,
5741
+ {
5742
+ workspaceId: options.workspaceId
5743
+ }
5744
+ );
5745
+ printOutput({ ok: true, workspaceId }, runtime.json);
5746
+ });
5195
5747
  serviceCommand.command("search-issues <query>").description("Search issues for tray attach actions").option("--limit <n>").action(async (query, options, command) => {
5196
5748
  const { client, runtime } = await getClient(command);
5197
5749
  const orgSlug = requireOrg(runtime);