@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 +609 -57
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
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
|
|
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
|
|
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 =
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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(`[${
|
|
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
|
-
`[${
|
|
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 (!
|
|
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(`[${
|
|
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(`[${
|
|
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(`[${
|
|
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(`[${
|
|
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(`[${
|
|
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
|
-
[${
|
|
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}-${
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
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
|
|
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
|
-
).
|
|
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 (
|
|
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()}-${
|
|
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 (!
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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" || !
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
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 ??
|
|
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"
|
|
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 ??
|
|
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);
|