@rehpic/vcli 0.1.0-beta.61.1 → 0.1.0-beta.68.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 +535 -41
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/dist/index.js
CHANGED
|
@@ -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");
|
|
@@ -1100,8 +1153,241 @@ function createEmptySession() {
|
|
|
1100
1153
|
// src/bridge-service.ts
|
|
1101
1154
|
import { ConvexHttpClient as ConvexHttpClient2 } from "convex/browser";
|
|
1102
1155
|
import { execFileSync, 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 localtunnel from "localtunnel";
|
|
1165
|
+
function findTmuxPath() {
|
|
1166
|
+
for (const p of [
|
|
1167
|
+
"/opt/homebrew/bin/tmux",
|
|
1168
|
+
"/usr/local/bin/tmux",
|
|
1169
|
+
"/usr/bin/tmux"
|
|
1170
|
+
]) {
|
|
1171
|
+
if (existsSync(p)) return p;
|
|
1172
|
+
}
|
|
1173
|
+
return "tmux";
|
|
1174
|
+
}
|
|
1175
|
+
function ts() {
|
|
1176
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
1177
|
+
}
|
|
1178
|
+
function findPort(start = 9100) {
|
|
1179
|
+
return new Promise((resolve, reject) => {
|
|
1180
|
+
const srv = createServer();
|
|
1181
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
1182
|
+
const addr = srv.address();
|
|
1183
|
+
const port = typeof addr === "object" && addr ? addr.port : start;
|
|
1184
|
+
srv.close(() => resolve(port));
|
|
1185
|
+
});
|
|
1186
|
+
srv.on("error", reject);
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
var TerminalPeerManager = class {
|
|
1190
|
+
constructor(config) {
|
|
1191
|
+
this.terminals = /* @__PURE__ */ new Map();
|
|
1192
|
+
this.failedSessions = /* @__PURE__ */ new Set();
|
|
1193
|
+
this.pendingStops = /* @__PURE__ */ new Map();
|
|
1194
|
+
this.unsubscribers = /* @__PURE__ */ new Map();
|
|
1195
|
+
this.config = config;
|
|
1196
|
+
this.client = new ConvexClient(config.convexUrl);
|
|
1197
|
+
}
|
|
1198
|
+
watchSession(workSessionId, tmuxSessionName) {
|
|
1199
|
+
if (this.unsubscribers.has(workSessionId)) return;
|
|
1200
|
+
const unsub = this.client.onUpdate(
|
|
1201
|
+
api.agentBridge.bridgePublic.getWorkSessionTerminalState,
|
|
1202
|
+
{
|
|
1203
|
+
deviceId: this.config.deviceId,
|
|
1204
|
+
deviceSecret: this.config.deviceSecret,
|
|
1205
|
+
workSessionId
|
|
1206
|
+
},
|
|
1207
|
+
(state) => {
|
|
1208
|
+
if (!state) return;
|
|
1209
|
+
const terminal = this.terminals.get(workSessionId);
|
|
1210
|
+
if (state.terminalViewerActive && !terminal && !this.failedSessions.has(workSessionId)) {
|
|
1211
|
+
const pendingStop = this.pendingStops.get(workSessionId);
|
|
1212
|
+
if (pendingStop) {
|
|
1213
|
+
clearTimeout(pendingStop);
|
|
1214
|
+
this.pendingStops.delete(workSessionId);
|
|
1215
|
+
}
|
|
1216
|
+
console.log(`[${ts()}] Viewer active for ${tmuxSessionName}`);
|
|
1217
|
+
void this.startTerminal(
|
|
1218
|
+
workSessionId,
|
|
1219
|
+
tmuxSessionName,
|
|
1220
|
+
state.terminalCols,
|
|
1221
|
+
state.terminalRows
|
|
1222
|
+
);
|
|
1223
|
+
} else if (!state.terminalViewerActive && terminal) {
|
|
1224
|
+
if (!this.pendingStops.has(workSessionId)) {
|
|
1225
|
+
this.pendingStops.set(
|
|
1226
|
+
workSessionId,
|
|
1227
|
+
setTimeout(() => {
|
|
1228
|
+
this.pendingStops.delete(workSessionId);
|
|
1229
|
+
console.log(`[${ts()}] Viewer inactive for ${tmuxSessionName}`);
|
|
1230
|
+
this.stopTerminal(workSessionId);
|
|
1231
|
+
this.failedSessions.delete(workSessionId);
|
|
1232
|
+
}, 2e3)
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
);
|
|
1238
|
+
this.unsubscribers.set(workSessionId, unsub);
|
|
1239
|
+
}
|
|
1240
|
+
unwatchSession(workSessionId) {
|
|
1241
|
+
const unsub = this.unsubscribers.get(workSessionId);
|
|
1242
|
+
if (unsub) {
|
|
1243
|
+
unsub();
|
|
1244
|
+
this.unsubscribers.delete(workSessionId);
|
|
1245
|
+
}
|
|
1246
|
+
this.stopTerminal(workSessionId);
|
|
1247
|
+
}
|
|
1248
|
+
async startTerminal(workSessionId, tmuxSessionName, cols, rows) {
|
|
1249
|
+
if (this.terminals.has(workSessionId)) return;
|
|
1250
|
+
const tmuxBin = findTmuxPath();
|
|
1251
|
+
try {
|
|
1252
|
+
const port = await findPort();
|
|
1253
|
+
console.log(
|
|
1254
|
+
`[${ts()}] Spawning PTY: ${tmuxBin} attach-session -t ${tmuxSessionName}`
|
|
1255
|
+
);
|
|
1256
|
+
const ptyProcess = pty.spawn(
|
|
1257
|
+
tmuxBin,
|
|
1258
|
+
["attach-session", "-t", tmuxSessionName],
|
|
1259
|
+
{
|
|
1260
|
+
name: "xterm-256color",
|
|
1261
|
+
cols: Math.max(cols, 10),
|
|
1262
|
+
rows: Math.max(rows, 4),
|
|
1263
|
+
cwd: process.env.HOME ?? "/",
|
|
1264
|
+
env: { ...process.env, TERM: "xterm-256color" }
|
|
1265
|
+
}
|
|
1266
|
+
);
|
|
1267
|
+
console.log(`[${ts()}] PTY started for ${tmuxSessionName}`);
|
|
1268
|
+
const token = randomUUID();
|
|
1269
|
+
const httpServer = createServer();
|
|
1270
|
+
const wss = new WebSocketServer({ server: httpServer });
|
|
1271
|
+
wss.on("connection", (ws, req) => {
|
|
1272
|
+
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
1273
|
+
const clientToken = url.searchParams.get("token");
|
|
1274
|
+
if (clientToken !== token) {
|
|
1275
|
+
console.log(`[${ts()}] Rejected unauthorized WebSocket connection`);
|
|
1276
|
+
ws.close(4401, "Unauthorized");
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
console.log(
|
|
1280
|
+
`[${ts()}] WebSocket client connected (${tmuxSessionName})`
|
|
1281
|
+
);
|
|
1282
|
+
const dataHandler = ptyProcess.onData((data) => {
|
|
1283
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
1284
|
+
ws.send(data);
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
ws.on("message", (msg) => {
|
|
1288
|
+
const str = msg.toString();
|
|
1289
|
+
if (str.startsWith("\0{")) {
|
|
1290
|
+
try {
|
|
1291
|
+
const parsed = JSON.parse(str.slice(1));
|
|
1292
|
+
if (parsed.type === "resize" && parsed.cols && parsed.rows) {
|
|
1293
|
+
ptyProcess.resize(
|
|
1294
|
+
Math.max(parsed.cols, 10),
|
|
1295
|
+
Math.max(parsed.rows, 4)
|
|
1296
|
+
);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
} catch {
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
ptyProcess.write(str);
|
|
1303
|
+
});
|
|
1304
|
+
ws.on("close", () => {
|
|
1305
|
+
console.log(
|
|
1306
|
+
`[${ts()}] WebSocket client disconnected (${tmuxSessionName})`
|
|
1307
|
+
);
|
|
1308
|
+
dataHandler.dispose();
|
|
1309
|
+
});
|
|
1310
|
+
});
|
|
1311
|
+
await new Promise((resolve) => {
|
|
1312
|
+
httpServer.listen(port, "127.0.0.1", resolve);
|
|
1313
|
+
});
|
|
1314
|
+
console.log(`[${ts()}] WS server on port ${port}`);
|
|
1315
|
+
console.log(`[${ts()}] Opening tunnel...`);
|
|
1316
|
+
const tunnel = await localtunnel({ port });
|
|
1317
|
+
const tunnelUrl = tunnel.url;
|
|
1318
|
+
console.log(`[${ts()}] Tunnel: ${tunnelUrl}`);
|
|
1319
|
+
const wsUrl = tunnelUrl.replace(/^https?:\/\//, "wss://");
|
|
1320
|
+
const terminal = {
|
|
1321
|
+
ptyProcess,
|
|
1322
|
+
httpServer,
|
|
1323
|
+
wss,
|
|
1324
|
+
tunnel,
|
|
1325
|
+
tunnelUrl: wsUrl,
|
|
1326
|
+
token,
|
|
1327
|
+
workSessionId,
|
|
1328
|
+
tmuxSessionName,
|
|
1329
|
+
port
|
|
1330
|
+
};
|
|
1331
|
+
this.terminals.set(workSessionId, terminal);
|
|
1332
|
+
await this.client.mutation(
|
|
1333
|
+
api.agentBridge.bridgePublic.updateWorkSessionTerminalUrl,
|
|
1334
|
+
{
|
|
1335
|
+
deviceId: this.config.deviceId,
|
|
1336
|
+
deviceSecret: this.config.deviceSecret,
|
|
1337
|
+
workSessionId,
|
|
1338
|
+
terminalUrl: wsUrl,
|
|
1339
|
+
terminalToken: token
|
|
1340
|
+
}
|
|
1341
|
+
);
|
|
1342
|
+
ptyProcess.onExit(() => {
|
|
1343
|
+
console.log(`[${ts()}] PTY exited for ${tmuxSessionName}`);
|
|
1344
|
+
this.stopTerminal(workSessionId);
|
|
1345
|
+
});
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
console.error(`[${ts()}] Failed to start terminal:`, err);
|
|
1348
|
+
this.failedSessions.add(workSessionId);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
stopTerminal(workSessionId) {
|
|
1352
|
+
const terminal = this.terminals.get(workSessionId);
|
|
1353
|
+
if (!terminal) return;
|
|
1354
|
+
try {
|
|
1355
|
+
terminal.ptyProcess.kill();
|
|
1356
|
+
} catch {
|
|
1357
|
+
}
|
|
1358
|
+
try {
|
|
1359
|
+
terminal.tunnel.close();
|
|
1360
|
+
} catch {
|
|
1361
|
+
}
|
|
1362
|
+
try {
|
|
1363
|
+
terminal.wss.close();
|
|
1364
|
+
} catch {
|
|
1365
|
+
}
|
|
1366
|
+
try {
|
|
1367
|
+
terminal.httpServer.close();
|
|
1368
|
+
} catch {
|
|
1369
|
+
}
|
|
1370
|
+
this.terminals.delete(workSessionId);
|
|
1371
|
+
console.log(`[${ts()}] Terminal stopped for ${terminal.tmuxSessionName}`);
|
|
1372
|
+
}
|
|
1373
|
+
stop() {
|
|
1374
|
+
for (const unsub of this.unsubscribers.values()) {
|
|
1375
|
+
try {
|
|
1376
|
+
unsub();
|
|
1377
|
+
} catch {
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
this.unsubscribers.clear();
|
|
1381
|
+
for (const id of this.terminals.keys()) {
|
|
1382
|
+
this.stopTerminal(id);
|
|
1383
|
+
}
|
|
1384
|
+
void this.client.close();
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
// src/bridge-service.ts
|
|
1103
1389
|
import {
|
|
1104
|
-
existsSync as
|
|
1390
|
+
existsSync as existsSync3,
|
|
1105
1391
|
mkdirSync,
|
|
1106
1392
|
readFileSync as readFileSync2,
|
|
1107
1393
|
writeFileSync,
|
|
@@ -1109,17 +1395,18 @@ import {
|
|
|
1109
1395
|
} from "fs";
|
|
1110
1396
|
import { homedir as homedir3, hostname, platform } from "os";
|
|
1111
1397
|
import { join as join2 } from "path";
|
|
1112
|
-
import { randomUUID } from "crypto";
|
|
1398
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1113
1399
|
|
|
1114
1400
|
// src/agent-adapters.ts
|
|
1115
|
-
import { execSync, spawn } from "child_process";
|
|
1116
|
-
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
1401
|
+
import { execSync, spawn as spawn2 } from "child_process";
|
|
1402
|
+
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
1117
1403
|
import { homedir as homedir2, userInfo } from "os";
|
|
1118
1404
|
import { basename, join } from "path";
|
|
1119
1405
|
var LSOF_PATHS = ["/usr/sbin/lsof", "/usr/bin/lsof"];
|
|
1120
1406
|
var VECTOR_BRIDGE_CLIENT_VERSION = "0.1.0";
|
|
1121
1407
|
function discoverAttachableSessions() {
|
|
1122
1408
|
return dedupeSessions([
|
|
1409
|
+
...discoverTmuxSessions(),
|
|
1123
1410
|
...discoverCodexSessions(),
|
|
1124
1411
|
...discoverClaudeSessions()
|
|
1125
1412
|
]);
|
|
@@ -1141,7 +1428,7 @@ async function resumeProviderSession(provider, sessionKey, cwd, prompt2) {
|
|
|
1141
1428
|
});
|
|
1142
1429
|
}
|
|
1143
1430
|
async function runCodexAppServerTurn(args) {
|
|
1144
|
-
const child =
|
|
1431
|
+
const child = spawn2("codex", ["app-server"], {
|
|
1145
1432
|
cwd: args.cwd,
|
|
1146
1433
|
env: { ...process.env },
|
|
1147
1434
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1429,6 +1716,65 @@ function discoverClaudeSessions() {
|
|
|
1429
1716
|
return parsed ? [parsed] : [];
|
|
1430
1717
|
}).sort(compareObservedSessions);
|
|
1431
1718
|
}
|
|
1719
|
+
function discoverTmuxSessions() {
|
|
1720
|
+
try {
|
|
1721
|
+
const output = execSync(
|
|
1722
|
+
"tmux list-panes -a -F '#{pane_id} #{pane_pid} #{session_name} #{window_name} #{pane_current_path} #{pane_current_command} #{pane_title}'",
|
|
1723
|
+
{
|
|
1724
|
+
encoding: "utf-8",
|
|
1725
|
+
timeout: 3e3
|
|
1726
|
+
}
|
|
1727
|
+
);
|
|
1728
|
+
return output.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
1729
|
+
const [
|
|
1730
|
+
paneId,
|
|
1731
|
+
panePid,
|
|
1732
|
+
sessionName,
|
|
1733
|
+
windowName,
|
|
1734
|
+
cwd,
|
|
1735
|
+
currentCommand,
|
|
1736
|
+
paneTitle
|
|
1737
|
+
] = line.split(" ");
|
|
1738
|
+
if (!paneId || !panePid || !sessionName || !windowName || !cwd) {
|
|
1739
|
+
return [];
|
|
1740
|
+
}
|
|
1741
|
+
const normalizedCommand = (currentCommand ?? "").trim().toLowerCase();
|
|
1742
|
+
if (normalizedCommand === "codex" || normalizedCommand === "claude") {
|
|
1743
|
+
return [];
|
|
1744
|
+
}
|
|
1745
|
+
const gitInfo = getGitInfo(cwd);
|
|
1746
|
+
const title = summarizeTitle(
|
|
1747
|
+
buildTmuxPaneTitle({
|
|
1748
|
+
paneTitle,
|
|
1749
|
+
sessionName,
|
|
1750
|
+
windowName,
|
|
1751
|
+
cwd,
|
|
1752
|
+
currentCommand
|
|
1753
|
+
}),
|
|
1754
|
+
cwd
|
|
1755
|
+
);
|
|
1756
|
+
return [
|
|
1757
|
+
{
|
|
1758
|
+
provider: "vector_cli",
|
|
1759
|
+
providerLabel: "Tmux",
|
|
1760
|
+
localProcessId: panePid,
|
|
1761
|
+
sessionKey: `tmux:${paneId}`,
|
|
1762
|
+
cwd,
|
|
1763
|
+
...gitInfo,
|
|
1764
|
+
title,
|
|
1765
|
+
tmuxSessionName: sessionName,
|
|
1766
|
+
tmuxWindowName: windowName,
|
|
1767
|
+
tmuxPaneId: paneId,
|
|
1768
|
+
mode: "observed",
|
|
1769
|
+
status: "observed",
|
|
1770
|
+
supportsInboundMessages: true
|
|
1771
|
+
}
|
|
1772
|
+
];
|
|
1773
|
+
}).sort(compareObservedSessions);
|
|
1774
|
+
} catch {
|
|
1775
|
+
return [];
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1432
1778
|
function getCodexHistoryFile() {
|
|
1433
1779
|
return join(getRealHomeDir(), ".codex", "history.jsonl");
|
|
1434
1780
|
}
|
|
@@ -1453,7 +1799,7 @@ function getRealHomeDir() {
|
|
|
1453
1799
|
}
|
|
1454
1800
|
function resolveExecutable(fallbackCommand, absoluteCandidates) {
|
|
1455
1801
|
for (const candidate of absoluteCandidates) {
|
|
1456
|
-
if (
|
|
1802
|
+
if (existsSync2(candidate)) {
|
|
1457
1803
|
return candidate;
|
|
1458
1804
|
}
|
|
1459
1805
|
}
|
|
@@ -1512,7 +1858,7 @@ function getCodexTranscriptPath(pid) {
|
|
|
1512
1858
|
}
|
|
1513
1859
|
function readClaudePidSession(pid) {
|
|
1514
1860
|
const path3 = join(getClaudeSessionStateDir(), `${pid}.json`);
|
|
1515
|
-
if (!
|
|
1861
|
+
if (!existsSync2(path3)) {
|
|
1516
1862
|
return null;
|
|
1517
1863
|
}
|
|
1518
1864
|
try {
|
|
@@ -1534,7 +1880,7 @@ function findClaudeTranscriptPath(sessionId) {
|
|
|
1534
1880
|
return findJsonlFileByStem(getClaudeProjectsDir(), sessionId);
|
|
1535
1881
|
}
|
|
1536
1882
|
function findJsonlFileByStem(root, stem) {
|
|
1537
|
-
if (!
|
|
1883
|
+
if (!existsSync2(root)) {
|
|
1538
1884
|
return void 0;
|
|
1539
1885
|
}
|
|
1540
1886
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
@@ -1694,6 +2040,17 @@ function summarizeTitle(message, cwd) {
|
|
|
1694
2040
|
}
|
|
1695
2041
|
return "Local session";
|
|
1696
2042
|
}
|
|
2043
|
+
function buildTmuxPaneTitle(args) {
|
|
2044
|
+
const paneTitle = cleanSessionTitleCandidate(args.paneTitle ?? "");
|
|
2045
|
+
if (paneTitle) {
|
|
2046
|
+
return paneTitle;
|
|
2047
|
+
}
|
|
2048
|
+
const command = asString(args.currentCommand);
|
|
2049
|
+
if (command && !["zsh", "bash", "fish", "sh", "nu"].includes(command)) {
|
|
2050
|
+
return `${command} in ${basename(args.cwd)}`;
|
|
2051
|
+
}
|
|
2052
|
+
return `${basename(args.cwd)} (${args.sessionName}:${args.windowName})`;
|
|
2053
|
+
}
|
|
1697
2054
|
function truncate(value, maxLength) {
|
|
1698
2055
|
return value.length > maxLength ? `${value.slice(0, maxLength - 3).trimEnd()}...` : value;
|
|
1699
2056
|
}
|
|
@@ -1912,7 +2269,7 @@ var COMMAND_POLL_INTERVAL_MS = 5e3;
|
|
|
1912
2269
|
var LIVE_ACTIVITY_SYNC_INTERVAL_MS = 5e3;
|
|
1913
2270
|
var PROCESS_DISCOVERY_INTERVAL_MS = 6e4;
|
|
1914
2271
|
function loadBridgeConfig() {
|
|
1915
|
-
if (!
|
|
2272
|
+
if (!existsSync3(BRIDGE_CONFIG_FILE)) return null;
|
|
1916
2273
|
try {
|
|
1917
2274
|
return JSON.parse(readFileSync2(BRIDGE_CONFIG_FILE, "utf-8"));
|
|
1918
2275
|
} catch {
|
|
@@ -1920,17 +2277,18 @@ function loadBridgeConfig() {
|
|
|
1920
2277
|
}
|
|
1921
2278
|
}
|
|
1922
2279
|
function saveBridgeConfig(config) {
|
|
1923
|
-
if (!
|
|
2280
|
+
if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1924
2281
|
writeFileSync(BRIDGE_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
1925
2282
|
persistDeviceKey(config.deviceKey);
|
|
1926
2283
|
}
|
|
1927
2284
|
function writeLiveActivitiesCache(activities) {
|
|
1928
|
-
if (!
|
|
2285
|
+
if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1929
2286
|
writeFileSync(LIVE_ACTIVITIES_CACHE, JSON.stringify(activities, null, 2));
|
|
1930
2287
|
}
|
|
1931
2288
|
var BridgeService = class {
|
|
1932
2289
|
constructor(config) {
|
|
1933
2290
|
this.timers = [];
|
|
2291
|
+
this.terminalPeer = null;
|
|
1934
2292
|
this.config = config;
|
|
1935
2293
|
this.client = new ConvexHttpClient2(config.convexUrl);
|
|
1936
2294
|
}
|
|
@@ -1949,7 +2307,7 @@ var BridgeService = class {
|
|
|
1949
2307
|
}
|
|
1950
2308
|
);
|
|
1951
2309
|
if (commands.length > 0) {
|
|
1952
|
-
console.log(`[${
|
|
2310
|
+
console.log(`[${ts2()}] ${commands.length} pending command(s)`);
|
|
1953
2311
|
}
|
|
1954
2312
|
for (const cmd of commands) {
|
|
1955
2313
|
await this.handleCommand(cmd);
|
|
@@ -1978,6 +2336,10 @@ var BridgeService = class {
|
|
|
1978
2336
|
await this.handleLaunchCommand(cmd);
|
|
1979
2337
|
await this.completeCommand(cmd._id, "delivered");
|
|
1980
2338
|
return;
|
|
2339
|
+
case "resize":
|
|
2340
|
+
await this.handleResizeCommand(cmd);
|
|
2341
|
+
await this.completeCommand(cmd._id, "delivered");
|
|
2342
|
+
return;
|
|
1981
2343
|
default:
|
|
1982
2344
|
throw new Error(`Unsupported bridge command: ${cmd.kind}`);
|
|
1983
2345
|
}
|
|
@@ -2012,7 +2374,7 @@ var BridgeService = class {
|
|
|
2012
2374
|
}
|
|
2013
2375
|
if (processes.length > 0) {
|
|
2014
2376
|
console.log(
|
|
2015
|
-
`[${
|
|
2377
|
+
`[${ts2()}] Discovered ${processes.length} attachable session(s)`
|
|
2016
2378
|
);
|
|
2017
2379
|
}
|
|
2018
2380
|
}
|
|
@@ -2027,6 +2389,16 @@ var BridgeService = class {
|
|
|
2027
2389
|
);
|
|
2028
2390
|
writeLiveActivitiesCache(activities);
|
|
2029
2391
|
await this.syncWorkSessionTerminals(activities);
|
|
2392
|
+
if (this.terminalPeer) {
|
|
2393
|
+
for (const activity of activities) {
|
|
2394
|
+
if (activity.workSessionId && activity.tmuxSessionName) {
|
|
2395
|
+
this.terminalPeer.watchSession(
|
|
2396
|
+
activity.workSessionId,
|
|
2397
|
+
activity.tmuxSessionName
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2030
2402
|
} catch {
|
|
2031
2403
|
}
|
|
2032
2404
|
}
|
|
@@ -2115,45 +2487,59 @@ var BridgeService = class {
|
|
|
2115
2487
|
console.log(` Convex: ${this.config.convexUrl}`);
|
|
2116
2488
|
console.log(` PID: ${process.pid}`);
|
|
2117
2489
|
console.log("");
|
|
2118
|
-
if (!
|
|
2490
|
+
if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
2119
2491
|
writeFileSync(PID_FILE, String(process.pid));
|
|
2492
|
+
try {
|
|
2493
|
+
this.terminalPeer = new TerminalPeerManager({
|
|
2494
|
+
deviceId: this.config.deviceId,
|
|
2495
|
+
deviceSecret: this.config.deviceSecret,
|
|
2496
|
+
convexUrl: this.config.convexUrl
|
|
2497
|
+
});
|
|
2498
|
+
console.log(` WebRTC: ready`);
|
|
2499
|
+
} catch (e) {
|
|
2500
|
+
console.error(
|
|
2501
|
+
` WebRTC: failed (${e instanceof Error ? e.message : "unknown"})`
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
console.log("");
|
|
2120
2505
|
await this.heartbeat();
|
|
2121
2506
|
await this.reportProcesses();
|
|
2122
2507
|
await this.refreshLiveActivities();
|
|
2123
|
-
console.log(`[${
|
|
2508
|
+
console.log(`[${ts2()}] Service running. Ctrl+C to stop.
|
|
2124
2509
|
`);
|
|
2125
2510
|
this.timers.push(
|
|
2126
2511
|
setInterval(() => {
|
|
2127
2512
|
this.heartbeat().catch(
|
|
2128
|
-
(e) => console.error(`[${
|
|
2513
|
+
(e) => console.error(`[${ts2()}] Heartbeat error:`, e.message)
|
|
2129
2514
|
);
|
|
2130
2515
|
}, HEARTBEAT_INTERVAL_MS)
|
|
2131
2516
|
);
|
|
2132
2517
|
this.timers.push(
|
|
2133
2518
|
setInterval(() => {
|
|
2134
2519
|
this.pollCommands().catch(
|
|
2135
|
-
(e) => console.error(`[${
|
|
2520
|
+
(e) => console.error(`[${ts2()}] Command poll error:`, e.message)
|
|
2136
2521
|
);
|
|
2137
2522
|
}, COMMAND_POLL_INTERVAL_MS)
|
|
2138
2523
|
);
|
|
2139
2524
|
this.timers.push(
|
|
2140
2525
|
setInterval(() => {
|
|
2141
2526
|
this.refreshLiveActivities().catch(
|
|
2142
|
-
(e) => console.error(`[${
|
|
2527
|
+
(e) => console.error(`[${ts2()}] Live activity sync error:`, e.message)
|
|
2143
2528
|
);
|
|
2144
2529
|
}, LIVE_ACTIVITY_SYNC_INTERVAL_MS)
|
|
2145
2530
|
);
|
|
2146
2531
|
this.timers.push(
|
|
2147
2532
|
setInterval(() => {
|
|
2148
2533
|
this.reportProcesses().catch(
|
|
2149
|
-
(e) => console.error(`[${
|
|
2534
|
+
(e) => console.error(`[${ts2()}] Discovery error:`, e.message)
|
|
2150
2535
|
);
|
|
2151
2536
|
}, PROCESS_DISCOVERY_INTERVAL_MS)
|
|
2152
2537
|
);
|
|
2153
2538
|
const shutdown = () => {
|
|
2154
2539
|
console.log(`
|
|
2155
|
-
[${
|
|
2540
|
+
[${ts2()}] Shutting down...`);
|
|
2156
2541
|
for (const t of this.timers) clearInterval(t);
|
|
2542
|
+
this.terminalPeer?.stop();
|
|
2157
2543
|
try {
|
|
2158
2544
|
unlinkSync(PID_FILE);
|
|
2159
2545
|
} catch {
|
|
@@ -2252,6 +2638,29 @@ var BridgeService = class {
|
|
|
2252
2638
|
title: cmd.liveActivity?.title ?? process9.title
|
|
2253
2639
|
});
|
|
2254
2640
|
}
|
|
2641
|
+
async handleResizeCommand(cmd) {
|
|
2642
|
+
const payload = cmd.payload;
|
|
2643
|
+
const cols = payload?.cols;
|
|
2644
|
+
const rows = payload?.rows;
|
|
2645
|
+
const paneId = cmd.workSession?.tmuxPaneId;
|
|
2646
|
+
if (!paneId || !cols || !rows) {
|
|
2647
|
+
throw new Error("Resize command missing paneId, cols, or rows");
|
|
2648
|
+
}
|
|
2649
|
+
console.log(` Resize ${paneId} \u2192 ${cols}x${rows}`);
|
|
2650
|
+
resizeTmuxPane(paneId, cols, rows);
|
|
2651
|
+
if (cmd.workSession) {
|
|
2652
|
+
await this.refreshWorkSessionTerminal(cmd.workSession._id, {
|
|
2653
|
+
tmuxSessionName: cmd.workSession.tmuxSessionName,
|
|
2654
|
+
tmuxWindowName: cmd.workSession.tmuxWindowName,
|
|
2655
|
+
tmuxPaneId: paneId,
|
|
2656
|
+
cwd: cmd.workSession.cwd,
|
|
2657
|
+
repoRoot: cmd.workSession.repoRoot,
|
|
2658
|
+
branch: cmd.workSession.branch,
|
|
2659
|
+
agentProvider: cmd.workSession.agentProvider,
|
|
2660
|
+
agentSessionKey: cmd.workSession.agentSessionKey
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2255
2664
|
async handleLaunchCommand(cmd) {
|
|
2256
2665
|
if (!cmd.liveActivityId) {
|
|
2257
2666
|
throw new Error("Launch command is missing liveActivityId");
|
|
@@ -2365,6 +2774,9 @@ var BridgeService = class {
|
|
|
2365
2774
|
branch,
|
|
2366
2775
|
title,
|
|
2367
2776
|
model,
|
|
2777
|
+
tmuxSessionName,
|
|
2778
|
+
tmuxWindowName,
|
|
2779
|
+
tmuxPaneId,
|
|
2368
2780
|
mode,
|
|
2369
2781
|
status,
|
|
2370
2782
|
supportsInboundMessages
|
|
@@ -2383,6 +2795,9 @@ var BridgeService = class {
|
|
|
2383
2795
|
branch,
|
|
2384
2796
|
title,
|
|
2385
2797
|
model,
|
|
2798
|
+
tmuxSessionName,
|
|
2799
|
+
tmuxWindowName,
|
|
2800
|
+
tmuxPaneId,
|
|
2386
2801
|
mode,
|
|
2387
2802
|
status,
|
|
2388
2803
|
supportsInboundMessages
|
|
@@ -2440,7 +2855,7 @@ var BridgeService = class {
|
|
|
2440
2855
|
};
|
|
2441
2856
|
function createTmuxWorkSession(args) {
|
|
2442
2857
|
const slug = sanitizeTmuxName(args.issueKey.toLowerCase());
|
|
2443
|
-
const sessionName = `vector-${slug}-${
|
|
2858
|
+
const sessionName = `vector-${slug}-${randomUUID2().slice(0, 8)}`;
|
|
2444
2859
|
const windowName = sanitizeTmuxName(
|
|
2445
2860
|
args.provider === "codex" ? "codex" : args.provider === "claude_code" ? "claude" : "shell"
|
|
2446
2861
|
);
|
|
@@ -2503,9 +2918,24 @@ function sendTextToTmuxPane(paneId, text2) {
|
|
|
2503
2918
|
function captureTmuxPane(paneId) {
|
|
2504
2919
|
return execFileSync(
|
|
2505
2920
|
"tmux",
|
|
2506
|
-
["capture-pane", "-p", "-t", paneId, "-S", "-120"],
|
|
2921
|
+
["capture-pane", "-p", "-e", "-t", paneId, "-S", "-120"],
|
|
2507
2922
|
{ encoding: "utf-8" }
|
|
2508
|
-
).
|
|
2923
|
+
).trimEnd();
|
|
2924
|
+
}
|
|
2925
|
+
function resizeTmuxPane(paneId, cols, rows) {
|
|
2926
|
+
try {
|
|
2927
|
+
execFileSync("tmux", [
|
|
2928
|
+
"resize-pane",
|
|
2929
|
+
"-t",
|
|
2930
|
+
paneId,
|
|
2931
|
+
"-x",
|
|
2932
|
+
String(cols),
|
|
2933
|
+
"-y",
|
|
2934
|
+
String(rows)
|
|
2935
|
+
]);
|
|
2936
|
+
} catch (e) {
|
|
2937
|
+
console.error(`Failed to resize pane ${paneId}:`, e);
|
|
2938
|
+
}
|
|
2509
2939
|
}
|
|
2510
2940
|
function currentGitBranch(cwd) {
|
|
2511
2941
|
try {
|
|
@@ -2564,18 +2994,18 @@ function getStableDeviceKey() {
|
|
|
2564
2994
|
persistDeviceKey(existingKey);
|
|
2565
2995
|
return existingKey;
|
|
2566
2996
|
}
|
|
2567
|
-
if (
|
|
2997
|
+
if (existsSync3(DEVICE_KEY_FILE)) {
|
|
2568
2998
|
const savedKey = readFileSync2(DEVICE_KEY_FILE, "utf-8").trim();
|
|
2569
2999
|
if (savedKey) {
|
|
2570
3000
|
return savedKey;
|
|
2571
3001
|
}
|
|
2572
3002
|
}
|
|
2573
|
-
const generatedKey = `${hostname()}-${
|
|
3003
|
+
const generatedKey = `${hostname()}-${randomUUID2().slice(0, 8)}`;
|
|
2574
3004
|
persistDeviceKey(generatedKey);
|
|
2575
3005
|
return generatedKey;
|
|
2576
3006
|
}
|
|
2577
3007
|
function persistDeviceKey(deviceKey) {
|
|
2578
|
-
if (!
|
|
3008
|
+
if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
2579
3009
|
writeFileSync(DEVICE_KEY_FILE, `${deviceKey}
|
|
2580
3010
|
`);
|
|
2581
3011
|
}
|
|
@@ -2672,7 +3102,13 @@ function isBridgeProvider(provider) {
|
|
|
2672
3102
|
return provider === "codex" || provider === "claude_code";
|
|
2673
3103
|
}
|
|
2674
3104
|
function providerLabel(provider) {
|
|
2675
|
-
|
|
3105
|
+
if (provider === "codex") {
|
|
3106
|
+
return "Codex";
|
|
3107
|
+
}
|
|
3108
|
+
if (provider === "claude_code") {
|
|
3109
|
+
return "Claude";
|
|
3110
|
+
}
|
|
3111
|
+
return "Vector CLI";
|
|
2676
3112
|
}
|
|
2677
3113
|
function installLaunchAgent(vcliPath) {
|
|
2678
3114
|
if (platform() !== "darwin") {
|
|
@@ -2710,7 +3146,7 @@ ${environmentVariables}
|
|
|
2710
3146
|
</dict>
|
|
2711
3147
|
</dict>
|
|
2712
3148
|
</plist>`;
|
|
2713
|
-
if (!
|
|
3149
|
+
if (!existsSync3(LAUNCHAGENT_DIR)) {
|
|
2714
3150
|
mkdirSync(LAUNCHAGENT_DIR, { recursive: true });
|
|
2715
3151
|
}
|
|
2716
3152
|
removeLegacyMenuBarLaunchAgent();
|
|
@@ -2741,7 +3177,7 @@ function resolveCliInvocation(vcliPath) {
|
|
|
2741
3177
|
".bin",
|
|
2742
3178
|
"tsx"
|
|
2743
3179
|
);
|
|
2744
|
-
if (
|
|
3180
|
+
if (existsSync3(tsxPath)) {
|
|
2745
3181
|
return [tsxPath, vcliPath];
|
|
2746
3182
|
}
|
|
2747
3183
|
}
|
|
@@ -2790,7 +3226,7 @@ function uninstallLaunchAgent() {
|
|
|
2790
3226
|
}
|
|
2791
3227
|
var MENUBAR_PID_FILE = join2(CONFIG_DIR, "menubar.pid");
|
|
2792
3228
|
function removeLegacyMenuBarLaunchAgent() {
|
|
2793
|
-
if (platform() !== "darwin" || !
|
|
3229
|
+
if (platform() !== "darwin" || !existsSync3(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
|
|
2794
3230
|
return;
|
|
2795
3231
|
}
|
|
2796
3232
|
try {
|
|
@@ -2825,7 +3261,7 @@ function findCliEntrypoint() {
|
|
|
2825
3261
|
join2(import.meta.dirname ?? "", "..", "dist", "index.js")
|
|
2826
3262
|
];
|
|
2827
3263
|
for (const p of candidates) {
|
|
2828
|
-
if (
|
|
3264
|
+
if (existsSync3(p)) return p;
|
|
2829
3265
|
}
|
|
2830
3266
|
return null;
|
|
2831
3267
|
}
|
|
@@ -2857,7 +3293,7 @@ function findMenuBarExecutable() {
|
|
|
2857
3293
|
)
|
|
2858
3294
|
];
|
|
2859
3295
|
for (const p of candidates) {
|
|
2860
|
-
if (
|
|
3296
|
+
if (existsSync3(p)) {
|
|
2861
3297
|
return p;
|
|
2862
3298
|
}
|
|
2863
3299
|
}
|
|
@@ -2875,7 +3311,7 @@ function isKnownMenuBarProcess(pid) {
|
|
|
2875
3311
|
}
|
|
2876
3312
|
}
|
|
2877
3313
|
function killExistingMenuBar() {
|
|
2878
|
-
if (
|
|
3314
|
+
if (existsSync3(MENUBAR_PID_FILE)) {
|
|
2879
3315
|
try {
|
|
2880
3316
|
const pid = Number(readFileSync2(MENUBAR_PID_FILE, "utf-8").trim());
|
|
2881
3317
|
if (Number.isFinite(pid) && pid > 0 && isKnownMenuBarProcess(pid)) {
|
|
@@ -2890,7 +3326,7 @@ function killExistingMenuBar() {
|
|
|
2890
3326
|
}
|
|
2891
3327
|
}
|
|
2892
3328
|
function getRunningMenuBarPid() {
|
|
2893
|
-
if (!
|
|
3329
|
+
if (!existsSync3(MENUBAR_PID_FILE)) {
|
|
2894
3330
|
return null;
|
|
2895
3331
|
}
|
|
2896
3332
|
try {
|
|
@@ -2945,7 +3381,7 @@ function getBridgeStatus() {
|
|
|
2945
3381
|
let running = false;
|
|
2946
3382
|
let starting = false;
|
|
2947
3383
|
let pid;
|
|
2948
|
-
if (
|
|
3384
|
+
if (existsSync3(PID_FILE)) {
|
|
2949
3385
|
const pidStr = readFileSync2(PID_FILE, "utf-8").trim();
|
|
2950
3386
|
pid = Number(pidStr);
|
|
2951
3387
|
try {
|
|
@@ -2968,7 +3404,7 @@ function stopBridge(options) {
|
|
|
2968
3404
|
writeLiveActivitiesCache([]);
|
|
2969
3405
|
} catch {
|
|
2970
3406
|
}
|
|
2971
|
-
if (!
|
|
3407
|
+
if (!existsSync3(PID_FILE)) return false;
|
|
2972
3408
|
const pid = Number(readFileSync2(PID_FILE, "utf-8").trim());
|
|
2973
3409
|
try {
|
|
2974
3410
|
process.kill(pid, "SIGTERM");
|
|
@@ -2977,7 +3413,7 @@ function stopBridge(options) {
|
|
|
2977
3413
|
return false;
|
|
2978
3414
|
}
|
|
2979
3415
|
}
|
|
2980
|
-
function
|
|
3416
|
+
function ts2() {
|
|
2981
3417
|
return (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
2982
3418
|
}
|
|
2983
3419
|
|
|
@@ -3204,7 +3640,7 @@ async function fetchConvexUrl(appUrl) {
|
|
|
3204
3640
|
}
|
|
3205
3641
|
async function getRuntime(command) {
|
|
3206
3642
|
const options = command.optsWithGlobals();
|
|
3207
|
-
const profile = options.profile ??
|
|
3643
|
+
const profile = options.profile ?? await readDefaultProfile();
|
|
3208
3644
|
const session = await readSession(profile);
|
|
3209
3645
|
const appUrlSource = options.appUrl ?? session?.appUrl ?? process.env.NEXT_PUBLIC_APP_URL;
|
|
3210
3646
|
const appUrl = await resolveAppUrl(requiredString(appUrlSource, "app URL"));
|
|
@@ -3541,7 +3977,7 @@ function readPackageVersionSync() {
|
|
|
3541
3977
|
program.name("vcli").description("Vector CLI").version(readPackageVersionSync(), "-v, --version").showHelpAfterError().option(
|
|
3542
3978
|
"--app-url <url>",
|
|
3543
3979
|
"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"
|
|
3980
|
+
).option("--convex-url <url>", "Convex deployment URL").option("--org <slug>", "Organization slug override").option("--profile <name>", "CLI profile name").option("--json", "Output JSON");
|
|
3545
3981
|
var authCommand = program.command("auth").description("Authentication");
|
|
3546
3982
|
authCommand.command("signup").option("--email <email>", "Email address").option("--username <username>", "Username").option("--password <password>", "Password").action(async (options, command) => {
|
|
3547
3983
|
const runtime = await getRuntime(command);
|
|
@@ -3664,6 +4100,38 @@ authCommand.command("whoami").action(async (_options, command) => {
|
|
|
3664
4100
|
runtime.json
|
|
3665
4101
|
);
|
|
3666
4102
|
});
|
|
4103
|
+
authCommand.command("profiles").action(async (_options, command) => {
|
|
4104
|
+
const options = command.optsWithGlobals();
|
|
4105
|
+
const explicitProfile = options.profile?.trim();
|
|
4106
|
+
const defaultProfile = await readDefaultProfile();
|
|
4107
|
+
const profiles = await listProfiles();
|
|
4108
|
+
const activeProfile = explicitProfile || defaultProfile;
|
|
4109
|
+
printOutput(
|
|
4110
|
+
{
|
|
4111
|
+
activeProfile,
|
|
4112
|
+
defaultProfile,
|
|
4113
|
+
profiles
|
|
4114
|
+
},
|
|
4115
|
+
Boolean(options.json)
|
|
4116
|
+
);
|
|
4117
|
+
});
|
|
4118
|
+
authCommand.command("use-profile <name>").action(async (name, _options, command) => {
|
|
4119
|
+
const options = command.optsWithGlobals();
|
|
4120
|
+
const profile = name.trim();
|
|
4121
|
+
if (!profile) {
|
|
4122
|
+
throw new Error("Profile name is required.");
|
|
4123
|
+
}
|
|
4124
|
+
await writeDefaultProfile(profile);
|
|
4125
|
+
const session = await readSession(profile);
|
|
4126
|
+
printOutput(
|
|
4127
|
+
{
|
|
4128
|
+
ok: true,
|
|
4129
|
+
defaultProfile: profile,
|
|
4130
|
+
hasSession: session !== null
|
|
4131
|
+
},
|
|
4132
|
+
Boolean(options.json)
|
|
4133
|
+
);
|
|
4134
|
+
});
|
|
3667
4135
|
var orgCommand = program.command("org").description("Organizations");
|
|
3668
4136
|
orgCommand.command("list").action(async (_options, command) => {
|
|
3669
4137
|
const { client, runtime } = await getClient(command);
|
|
@@ -5145,8 +5613,11 @@ serviceCommand.command("status").description("Show bridge service status").actio
|
|
|
5145
5613
|
serviceCommand.command("menu-state").description("Return JSON state for the macOS tray").action(async (_options, command) => {
|
|
5146
5614
|
const status = getBridgeStatus();
|
|
5147
5615
|
const globalOptions = command.optsWithGlobals();
|
|
5148
|
-
const profile = globalOptions.profile ??
|
|
5616
|
+
const profile = globalOptions.profile ?? await readDefaultProfile();
|
|
5149
5617
|
const session = await readSession(profile);
|
|
5618
|
+
const profiles = await listProfiles();
|
|
5619
|
+
const defaultProfile = await readDefaultProfile();
|
|
5620
|
+
let workspaces = [];
|
|
5150
5621
|
let workSessions = [];
|
|
5151
5622
|
let detectedSessions = [];
|
|
5152
5623
|
try {
|
|
@@ -5157,6 +5628,13 @@ serviceCommand.command("menu-state").description("Return JSON state for the macO
|
|
|
5157
5628
|
runtime.appUrl,
|
|
5158
5629
|
runtime.convexUrl
|
|
5159
5630
|
);
|
|
5631
|
+
workspaces = await runQuery(
|
|
5632
|
+
client,
|
|
5633
|
+
api.agentBridge.queries.listDeviceWorkspaces,
|
|
5634
|
+
{
|
|
5635
|
+
deviceId: status.config.deviceId
|
|
5636
|
+
}
|
|
5637
|
+
);
|
|
5160
5638
|
workSessions = await runQuery(
|
|
5161
5639
|
client,
|
|
5162
5640
|
api.agentBridge.queries.listDeviceWorkSessions,
|
|
@@ -5175,6 +5653,7 @@ serviceCommand.command("menu-state").description("Return JSON state for the macO
|
|
|
5175
5653
|
detectedSessions = currentDevice?.processes ?? [];
|
|
5176
5654
|
}
|
|
5177
5655
|
} catch {
|
|
5656
|
+
workspaces = [];
|
|
5178
5657
|
workSessions = [];
|
|
5179
5658
|
detectedSessions = [];
|
|
5180
5659
|
}
|
|
@@ -5186,12 +5665,27 @@ serviceCommand.command("menu-state").description("Return JSON state for the macO
|
|
|
5186
5665
|
pid: status.pid,
|
|
5187
5666
|
config: status.config,
|
|
5188
5667
|
sessionInfo: buildMenuSessionInfo(session),
|
|
5668
|
+
activeProfile: profile,
|
|
5669
|
+
defaultProfile,
|
|
5670
|
+
profiles,
|
|
5671
|
+
workspaces,
|
|
5189
5672
|
workSessions,
|
|
5190
5673
|
detectedSessions
|
|
5191
5674
|
},
|
|
5192
5675
|
Boolean(globalOptions.json)
|
|
5193
5676
|
);
|
|
5194
5677
|
});
|
|
5678
|
+
serviceCommand.command("set-default-workspace").description("Set the default workspace for this device").requiredOption("--workspace-id <id>").action(async (options, command) => {
|
|
5679
|
+
const { client, runtime } = await getClient(command);
|
|
5680
|
+
const workspaceId = await runMutation(
|
|
5681
|
+
client,
|
|
5682
|
+
api.agentBridge.mutations.setDefaultWorkspace,
|
|
5683
|
+
{
|
|
5684
|
+
workspaceId: options.workspaceId
|
|
5685
|
+
}
|
|
5686
|
+
);
|
|
5687
|
+
printOutput({ ok: true, workspaceId }, runtime.json);
|
|
5688
|
+
});
|
|
5195
5689
|
serviceCommand.command("search-issues <query>").description("Search issues for tray attach actions").option("--limit <n>").action(async (query, options, command) => {
|
|
5196
5690
|
const { client, runtime } = await getClient(command);
|
|
5197
5691
|
const orgSlug = requireOrg(runtime);
|