@rehpic/vcli 0.1.0-beta.52.1 → 0.1.0-beta.53.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 } from "child_process";
288
+ import { execFile as execFile4, execFileSync as execFileSync2 } 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");
@@ -736,9 +736,9 @@ var init_open = __esm({
736
736
  });
737
737
 
738
738
  // src/index.ts
739
- import { readFileSync as readFileSync2 } from "fs";
739
+ import { readFileSync as readFileSync3 } from "fs";
740
740
  import { readFile as readFile2 } from "fs/promises";
741
- import { dirname, extname, join as join2 } from "path";
741
+ import { dirname, extname, join as join3 } from "path";
742
742
  import { fileURLToPath as fileURLToPath2 } from "url";
743
743
  import { config as loadEnv } from "dotenv";
744
744
  import { Command } from "commander";
@@ -1058,9 +1058,11 @@ function printOutput(data, json = false) {
1058
1058
  import { mkdir, readFile, rm, writeFile } from "fs/promises";
1059
1059
  import { homedir } from "os";
1060
1060
  import path from "path";
1061
- var SESSION_ROOT = path.join(homedir(), ".vector");
1061
+ function getSessionRoot() {
1062
+ return process.env.VECTOR_HOME?.trim() || path.join(homedir(), ".vector");
1063
+ }
1062
1064
  function getSessionPath(profile = "default") {
1063
- return path.join(SESSION_ROOT, `cli-${profile}.json`);
1065
+ return path.join(getSessionRoot(), `cli-${profile}.json`);
1064
1066
  }
1065
1067
  async function readSession(profile = "default") {
1066
1068
  try {
@@ -1076,7 +1078,7 @@ async function readSession(profile = "default") {
1076
1078
  }
1077
1079
  }
1078
1080
  async function writeSession(session, profile = "default") {
1079
- await mkdir(SESSION_ROOT, { recursive: true });
1081
+ await mkdir(getSessionRoot(), { recursive: true });
1080
1082
  await writeFile(
1081
1083
  getSessionPath(profile),
1082
1084
  `${JSON.stringify(session, null, 2)}
@@ -1096,119 +1098,404 @@ function createEmptySession() {
1096
1098
 
1097
1099
  // src/bridge-service.ts
1098
1100
  import { ConvexHttpClient as ConvexHttpClient2 } from "convex/browser";
1099
- import { execSync } from "child_process";
1101
+ import { execFileSync, execSync as execSync2 } from "child_process";
1100
1102
  import {
1101
- existsSync,
1103
+ existsSync as existsSync2,
1102
1104
  mkdirSync,
1103
- readFileSync,
1105
+ readFileSync as readFileSync2,
1104
1106
  writeFileSync,
1105
1107
  unlinkSync
1106
1108
  } from "fs";
1107
- import { homedir as homedir2, hostname, platform } from "os";
1108
- import { join } from "path";
1109
+ import { homedir as homedir3, hostname, platform } from "os";
1110
+ import { join as join2 } from "path";
1109
1111
  import { randomUUID } from "crypto";
1110
- var CONFIG_DIR = join(homedir2(), ".vector");
1111
- var BRIDGE_CONFIG_FILE = join(CONFIG_DIR, "bridge.json");
1112
- var PID_FILE = join(CONFIG_DIR, "bridge.pid");
1113
- var LIVE_ACTIVITIES_CACHE = join(CONFIG_DIR, "live-activities.json");
1114
- var LAUNCHAGENT_DIR = join(homedir2(), "Library", "LaunchAgents");
1115
- var LAUNCHAGENT_PLIST = join(LAUNCHAGENT_DIR, "com.vector.bridge.plist");
1116
- var LAUNCHAGENT_LABEL = "com.vector.bridge";
1117
- var LEGACY_MENUBAR_LAUNCHAGENT_LABEL = "com.vector.menubar";
1118
- var LEGACY_MENUBAR_LAUNCHAGENT_PLIST = join(
1119
- LAUNCHAGENT_DIR,
1120
- `${LEGACY_MENUBAR_LAUNCHAGENT_LABEL}.plist`
1121
- );
1122
- var HEARTBEAT_INTERVAL_MS = 3e4;
1123
- var COMMAND_POLL_INTERVAL_MS = 5e3;
1124
- var PROCESS_DISCOVERY_INTERVAL_MS = 6e4;
1125
- function loadBridgeConfig() {
1126
- if (!existsSync(BRIDGE_CONFIG_FILE)) return null;
1112
+
1113
+ // src/agent-adapters.ts
1114
+ import { execSync, spawn } from "child_process";
1115
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
1116
+ import { homedir as homedir2, userInfo } from "os";
1117
+ import { basename, join } from "path";
1118
+ var MAX_DISCOVERED_FILES_PER_PROVIDER = 30;
1119
+ function discoverAttachableSessions() {
1120
+ return dedupeSessions([
1121
+ ...discoverCodexSessions(),
1122
+ ...discoverClaudeSessions()
1123
+ ]);
1124
+ }
1125
+ async function launchProviderSession(provider, cwd, prompt2) {
1126
+ if (provider === "codex") {
1127
+ const stdout2 = await runCommand("codex", ["exec", "--json", prompt2], cwd);
1128
+ return parseCodexRunResult(stdout2, cwd, "codex exec --json");
1129
+ }
1130
+ const stdout = await runCommand(
1131
+ "claude",
1132
+ ["-p", "--output-format", "json", prompt2],
1133
+ cwd
1134
+ );
1135
+ return parseClaudeRunResult(stdout, cwd, "claude -p --output-format json");
1136
+ }
1137
+ async function resumeProviderSession(provider, sessionKey, cwd, prompt2) {
1138
+ if (provider === "codex") {
1139
+ const stdout2 = await runCommand(
1140
+ "codex",
1141
+ ["exec", "resume", "--json", sessionKey, prompt2],
1142
+ cwd
1143
+ );
1144
+ return parseCodexRunResult(stdout2, cwd, "codex exec resume --json");
1145
+ }
1146
+ const stdout = await runCommand(
1147
+ "claude",
1148
+ ["-p", "--resume", sessionKey, "--output-format", "json", prompt2],
1149
+ cwd
1150
+ );
1151
+ return parseClaudeRunResult(
1152
+ stdout,
1153
+ cwd,
1154
+ "claude -p --resume --output-format json"
1155
+ );
1156
+ }
1157
+ async function runCommand(command, args, cwd) {
1158
+ const child = spawn(command, args, {
1159
+ cwd,
1160
+ env: { ...process.env },
1161
+ stdio: ["ignore", "pipe", "pipe"]
1162
+ });
1163
+ let stdout = "";
1164
+ let stderr = "";
1165
+ child.stdout.on("data", (chunk) => {
1166
+ stdout += chunk.toString();
1167
+ });
1168
+ child.stderr.on("data", (chunk) => {
1169
+ stderr += chunk.toString();
1170
+ });
1171
+ return await new Promise((resolve, reject) => {
1172
+ child.on("error", reject);
1173
+ child.on("close", (code) => {
1174
+ if (code === 0) {
1175
+ resolve(stdout);
1176
+ return;
1177
+ }
1178
+ const detail = stderr.trim() || stdout.trim() || `exit code ${code}`;
1179
+ reject(new Error(`${command} failed: ${detail}`));
1180
+ });
1181
+ });
1182
+ }
1183
+ function discoverCodexSessions() {
1184
+ return listRecentJsonlFiles(getCodexSessionsDir()).flatMap((file) => {
1185
+ const entries = readJsonLines(file);
1186
+ let sessionKey;
1187
+ let cwd;
1188
+ let title;
1189
+ let lastUserMessage;
1190
+ let lastAssistantMessage;
1191
+ for (const entry of entries) {
1192
+ if (entry.type === "session_meta") {
1193
+ sessionKey = asString(entry.payload?.id) ?? sessionKey;
1194
+ cwd = asString(entry.payload?.cwd) ?? cwd;
1195
+ }
1196
+ if (entry.type === "event_msg" && entry.payload?.type === "user_message") {
1197
+ lastUserMessage = asString(entry.payload?.message) ?? lastUserMessage;
1198
+ }
1199
+ if (entry.type === "response_item" && entry.payload?.type === "message" && entry.payload?.role === "user") {
1200
+ lastUserMessage = extractCodexResponseText(entry.payload?.content) ?? lastUserMessage;
1201
+ }
1202
+ if (entry.type === "event_msg" && entry.payload?.type === "agent_message") {
1203
+ lastAssistantMessage = asString(entry.payload?.message) ?? lastAssistantMessage;
1204
+ }
1205
+ if (entry.type === "response_item" && entry.payload?.type === "message" && entry.payload?.role === "assistant") {
1206
+ lastAssistantMessage = extractCodexResponseText(entry.payload?.content) ?? lastAssistantMessage;
1207
+ }
1208
+ }
1209
+ if (!sessionKey) {
1210
+ return [];
1211
+ }
1212
+ title = summarizeTitle(lastUserMessage ?? lastAssistantMessage, cwd);
1213
+ const gitInfo = cwd ? getGitInfo(cwd) : {};
1214
+ return [
1215
+ {
1216
+ provider: "codex",
1217
+ providerLabel: "Codex",
1218
+ sessionKey,
1219
+ cwd,
1220
+ ...gitInfo,
1221
+ title,
1222
+ mode: "observed",
1223
+ status: "observed",
1224
+ supportsInboundMessages: true
1225
+ }
1226
+ ];
1227
+ });
1228
+ }
1229
+ function discoverClaudeSessions() {
1230
+ return listRecentJsonlFiles(getClaudeSessionsDir()).flatMap((file) => {
1231
+ const entries = readJsonLines(file);
1232
+ let sessionKey;
1233
+ let cwd;
1234
+ let branch;
1235
+ let model;
1236
+ let lastUserMessage;
1237
+ let lastAssistantMessage;
1238
+ for (const entry of entries) {
1239
+ sessionKey = asString(entry.sessionId) ?? sessionKey;
1240
+ cwd = asString(entry.cwd) ?? cwd;
1241
+ branch = asString(entry.gitBranch) ?? branch;
1242
+ if (entry.type === "user") {
1243
+ lastUserMessage = extractClaudeUserText(entry.message) ?? lastUserMessage;
1244
+ }
1245
+ if (entry.type === "assistant") {
1246
+ model = asString(entry.message?.model) ?? model;
1247
+ lastAssistantMessage = extractClaudeAssistantText(entry.message) ?? lastAssistantMessage;
1248
+ }
1249
+ }
1250
+ if (!sessionKey) {
1251
+ return [];
1252
+ }
1253
+ const gitInfo = cwd ? getGitInfo(cwd) : {};
1254
+ return [
1255
+ {
1256
+ provider: "claude_code",
1257
+ providerLabel: "Claude",
1258
+ sessionKey,
1259
+ cwd,
1260
+ repoRoot: gitInfo.repoRoot,
1261
+ branch: branch ?? gitInfo.branch,
1262
+ title: summarizeTitle(lastUserMessage ?? lastAssistantMessage, cwd),
1263
+ model,
1264
+ mode: "observed",
1265
+ status: "observed",
1266
+ supportsInboundMessages: true
1267
+ }
1268
+ ];
1269
+ });
1270
+ }
1271
+ function parseCodexRunResult(stdout, cwd, launchCommand) {
1272
+ let sessionKey;
1273
+ const responseParts = [];
1274
+ for (const line of stdout.split("\n").map((part) => part.trim()).filter(Boolean)) {
1275
+ const payload = tryParseJson(line);
1276
+ if (!payload) continue;
1277
+ if (payload.type === "thread.started") {
1278
+ sessionKey = asString(payload.thread_id) ?? sessionKey;
1279
+ }
1280
+ if (payload.type === "item.completed" && payload.item?.type === "agent_message" && typeof payload.item.text === "string") {
1281
+ responseParts.push(payload.item.text.trim());
1282
+ }
1283
+ if (payload.type === "response_item" && payload.payload?.type === "message" && payload.payload?.role === "assistant") {
1284
+ const responseText2 = extractCodexResponseText(payload.payload?.content);
1285
+ if (responseText2) {
1286
+ responseParts.push(responseText2);
1287
+ }
1288
+ }
1289
+ }
1290
+ if (!sessionKey) {
1291
+ throw new Error("Codex did not return a thread id");
1292
+ }
1293
+ const gitInfo = getGitInfo(cwd);
1294
+ const responseText = responseParts.filter(Boolean).join("\n\n") || void 0;
1295
+ return {
1296
+ provider: "codex",
1297
+ providerLabel: "Codex",
1298
+ sessionKey,
1299
+ cwd,
1300
+ ...gitInfo,
1301
+ title: summarizeTitle(void 0, cwd),
1302
+ mode: "managed",
1303
+ status: "waiting",
1304
+ supportsInboundMessages: true,
1305
+ responseText,
1306
+ launchCommand
1307
+ };
1308
+ }
1309
+ function parseClaudeRunResult(stdout, cwd, launchCommand) {
1310
+ const payload = stdout.split("\n").map((line) => tryParseJson(line)).filter(Boolean).pop();
1311
+ if (!payload) {
1312
+ throw new Error("Claude did not return JSON output");
1313
+ }
1314
+ const sessionKey = asString(payload.session_id);
1315
+ if (!sessionKey) {
1316
+ throw new Error("Claude did not return a session id");
1317
+ }
1318
+ const gitInfo = getGitInfo(cwd);
1319
+ const model = firstObjectKey(payload.modelUsage);
1320
+ return {
1321
+ provider: "claude_code",
1322
+ providerLabel: "Claude",
1323
+ sessionKey,
1324
+ cwd,
1325
+ ...gitInfo,
1326
+ title: summarizeTitle(void 0, cwd),
1327
+ model,
1328
+ mode: "managed",
1329
+ status: "waiting",
1330
+ supportsInboundMessages: true,
1331
+ responseText: asString(payload.result) ?? void 0,
1332
+ launchCommand
1333
+ };
1334
+ }
1335
+ function listRecentJsonlFiles(root) {
1336
+ if (!existsSync(root)) {
1337
+ return [];
1338
+ }
1339
+ const files = collectJsonlFiles(root);
1340
+ return files.map((path3) => ({ path: path3, mtimeMs: statSync(path3).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_DISCOVERED_FILES_PER_PROVIDER).map((file) => file.path);
1341
+ }
1342
+ function getCodexSessionsDir() {
1343
+ return join(getRealHomeDir(), ".codex", "sessions");
1344
+ }
1345
+ function getClaudeSessionsDir() {
1346
+ return join(getRealHomeDir(), ".claude", "projects");
1347
+ }
1348
+ function getRealHomeDir() {
1127
1349
  try {
1128
- return JSON.parse(readFileSync(BRIDGE_CONFIG_FILE, "utf-8"));
1350
+ const realHome = userInfo().homedir?.trim();
1351
+ if (realHome) {
1352
+ return realHome;
1353
+ }
1129
1354
  } catch {
1130
- return null;
1131
1355
  }
1356
+ return homedir2();
1132
1357
  }
1133
- function saveBridgeConfig(config) {
1134
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1135
- writeFileSync(BRIDGE_CONFIG_FILE, JSON.stringify(config, null, 2));
1358
+ function collectJsonlFiles(root) {
1359
+ const files = [];
1360
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
1361
+ const path3 = join(root, entry.name);
1362
+ if (entry.isDirectory()) {
1363
+ files.push(...collectJsonlFiles(path3));
1364
+ continue;
1365
+ }
1366
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
1367
+ files.push(path3);
1368
+ }
1369
+ }
1370
+ return files;
1136
1371
  }
1137
- function discoverLocalProcesses() {
1138
- const processes = [];
1139
- const patterns = [
1140
- {
1141
- grep: "[c]laude",
1142
- provider: "claude_code",
1143
- label: "Claude",
1144
- prefix: "claude"
1145
- },
1146
- { grep: "[c]odex", provider: "codex", label: "Codex", prefix: "codex" }
1147
- ];
1148
- for (const { grep, provider, label, prefix } of patterns) {
1149
- try {
1150
- const ps = execSync(
1151
- `ps aux | grep -E '${grep}' | grep -v vector-bridge | grep -v grep`,
1152
- { encoding: "utf-8", timeout: 5e3 }
1153
- );
1154
- for (const line of ps.trim().split("\n").filter(Boolean)) {
1155
- const pid = line.split(/\s+/)[1];
1156
- if (!pid) continue;
1157
- let cwd;
1158
- try {
1159
- cwd = execSync(
1160
- `lsof -p ${pid} 2>/dev/null | grep cwd | awk '{print $NF}'`,
1161
- { encoding: "utf-8", timeout: 3e3 }
1162
- ).trim() || void 0;
1163
- } catch {
1164
- }
1165
- const gitInfo = cwd ? getGitInfo(cwd) : {};
1166
- processes.push({
1167
- provider,
1168
- providerLabel: label,
1169
- localProcessId: pid,
1170
- sessionKey: `${prefix}-${pid}`,
1171
- cwd,
1172
- ...gitInfo,
1173
- mode: "observed",
1174
- status: "observed",
1175
- supportsInboundMessages: false
1176
- });
1177
- }
1178
- } catch {
1372
+ function readJsonLines(path3) {
1373
+ return readFileSync(path3, "utf-8").split("\n").map((line) => line.trim()).filter(Boolean).map(tryParseJson).filter(Boolean);
1374
+ }
1375
+ function tryParseJson(value) {
1376
+ try {
1377
+ return JSON.parse(value);
1378
+ } catch {
1379
+ return null;
1380
+ }
1381
+ }
1382
+ function dedupeSessions(sessions) {
1383
+ const seen = /* @__PURE__ */ new Set();
1384
+ return sessions.filter((session) => {
1385
+ const key = `${session.provider}:${session.sessionKey}`;
1386
+ if (seen.has(key)) {
1387
+ return false;
1179
1388
  }
1389
+ seen.add(key);
1390
+ return true;
1391
+ });
1392
+ }
1393
+ function extractCodexResponseText(content) {
1394
+ if (!Array.isArray(content)) {
1395
+ return void 0;
1396
+ }
1397
+ const texts = content.map(
1398
+ (item) => item && typeof item === "object" && "text" in item ? asString(item.text) : void 0
1399
+ ).filter(Boolean);
1400
+ return texts.length > 0 ? texts.join("\n\n") : void 0;
1401
+ }
1402
+ function extractClaudeUserText(message) {
1403
+ if (!message || typeof message !== "object") {
1404
+ return void 0;
1405
+ }
1406
+ const content = message.content;
1407
+ if (typeof content === "string") {
1408
+ return content;
1180
1409
  }
1181
- return processes;
1410
+ return void 0;
1411
+ }
1412
+ function extractClaudeAssistantText(message) {
1413
+ if (!message || typeof message !== "object") {
1414
+ return void 0;
1415
+ }
1416
+ const content = message.content;
1417
+ if (!Array.isArray(content)) {
1418
+ return void 0;
1419
+ }
1420
+ const texts = content.map(
1421
+ (item) => item && typeof item === "object" && "text" in item ? asString(item.text) : void 0
1422
+ ).filter(Boolean);
1423
+ return texts.length > 0 ? texts.join("\n\n") : void 0;
1424
+ }
1425
+ function summarizeTitle(message, cwd) {
1426
+ if (message) {
1427
+ return truncate(message.replace(/\s+/g, " ").trim(), 96);
1428
+ }
1429
+ if (cwd) {
1430
+ return basename(cwd);
1431
+ }
1432
+ return "Local session";
1433
+ }
1434
+ function truncate(value, maxLength) {
1435
+ return value.length > maxLength ? `${value.slice(0, maxLength - 3).trimEnd()}...` : value;
1436
+ }
1437
+ function firstObjectKey(value) {
1438
+ if (!value || typeof value !== "object") {
1439
+ return void 0;
1440
+ }
1441
+ const [firstKey] = Object.keys(value);
1442
+ return firstKey;
1443
+ }
1444
+ function asString(value) {
1445
+ return typeof value === "string" && value.trim() ? value : void 0;
1182
1446
  }
1183
1447
  function getGitInfo(cwd) {
1184
1448
  try {
1185
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
1449
+ const repoRoot = execSync("git rev-parse --show-toplevel", {
1186
1450
  encoding: "utf-8",
1187
1451
  cwd,
1188
1452
  timeout: 3e3
1189
1453
  }).trim();
1190
- const repoRoot = execSync("git rev-parse --show-toplevel", {
1454
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
1191
1455
  encoding: "utf-8",
1192
1456
  cwd,
1193
1457
  timeout: 3e3
1194
1458
  }).trim();
1195
- return { branch, repoRoot };
1459
+ return {
1460
+ repoRoot: repoRoot || void 0,
1461
+ branch: branch || void 0
1462
+ };
1196
1463
  } catch {
1197
1464
  return {};
1198
1465
  }
1199
1466
  }
1200
- function generateReply(userMessage) {
1201
- const lower = userMessage.toLowerCase().trim();
1202
- if (["hey", "hi", "hello"].includes(lower)) {
1203
- return "Hey! I'm running on your local machine via the Vector bridge. What would you like me to work on?";
1204
- }
1205
- if (lower.includes("status") || lower.includes("progress")) {
1206
- return "I'm making good progress. Currently reviewing the changes and running tests.";
1207
- }
1208
- if (lower.includes("stop") || lower.includes("cancel")) {
1209
- return "Understood \u2014 wrapping up the current step.";
1467
+
1468
+ // src/bridge-service.ts
1469
+ var CONFIG_DIR = process.env.VECTOR_HOME?.trim() || join2(homedir3(), ".vector");
1470
+ var BRIDGE_CONFIG_FILE = join2(CONFIG_DIR, "bridge.json");
1471
+ var PID_FILE = join2(CONFIG_DIR, "bridge.pid");
1472
+ var LIVE_ACTIVITIES_CACHE = join2(CONFIG_DIR, "live-activities.json");
1473
+ var LAUNCHAGENT_DIR = join2(homedir3(), "Library", "LaunchAgents");
1474
+ var LAUNCHAGENT_PLIST = join2(LAUNCHAGENT_DIR, "com.vector.bridge.plist");
1475
+ var LAUNCHAGENT_LABEL = "com.vector.bridge";
1476
+ var LEGACY_MENUBAR_LAUNCHAGENT_LABEL = "com.vector.menubar";
1477
+ var LEGACY_MENUBAR_LAUNCHAGENT_PLIST = join2(
1478
+ LAUNCHAGENT_DIR,
1479
+ `${LEGACY_MENUBAR_LAUNCHAGENT_LABEL}.plist`
1480
+ );
1481
+ var HEARTBEAT_INTERVAL_MS = 3e4;
1482
+ var COMMAND_POLL_INTERVAL_MS = 5e3;
1483
+ var PROCESS_DISCOVERY_INTERVAL_MS = 6e4;
1484
+ function loadBridgeConfig() {
1485
+ if (!existsSync2(BRIDGE_CONFIG_FILE)) return null;
1486
+ try {
1487
+ return JSON.parse(readFileSync2(BRIDGE_CONFIG_FILE, "utf-8"));
1488
+ } catch {
1489
+ return null;
1210
1490
  }
1211
- return `Got it \u2014 "${userMessage}". I'll incorporate that into my current work.`;
1491
+ }
1492
+ function saveBridgeConfig(config) {
1493
+ if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1494
+ writeFileSync(BRIDGE_CONFIG_FILE, JSON.stringify(config, null, 2));
1495
+ }
1496
+ function writeLiveActivitiesCache(activities) {
1497
+ if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1498
+ writeFileSync(LIVE_ACTIVITIES_CACHE, JSON.stringify(activities, null, 2));
1212
1499
  }
1213
1500
  var BridgeService = class {
1214
1501
  constructor(config) {
@@ -1238,45 +1525,50 @@ var BridgeService = class {
1238
1525
  }
1239
1526
  }
1240
1527
  async handleCommand(cmd) {
1528
+ const claimed = await this.client.mutation(
1529
+ api.agentBridge.bridgePublic.claimCommand,
1530
+ {
1531
+ deviceId: this.config.deviceId,
1532
+ deviceSecret: this.config.deviceSecret,
1533
+ commandId: cmd._id
1534
+ }
1535
+ );
1536
+ if (!claimed) {
1537
+ return;
1538
+ }
1241
1539
  console.log(` ${cmd.kind}: ${cmd._id}`);
1242
- if (cmd.kind === "message" && cmd.liveActivityId) {
1243
- const payload = cmd.payload;
1244
- const body = payload?.body ?? "";
1245
- console.log(` > "${body}"`);
1246
- const reply = generateReply(body);
1247
- await this.client.mutation(
1248
- api.agentBridge.bridgePublic.postAgentMessage,
1249
- {
1250
- deviceId: this.config.deviceId,
1251
- deviceSecret: this.config.deviceSecret,
1252
- liveActivityId: cmd.liveActivityId,
1253
- role: "assistant",
1254
- body: reply
1255
- }
1256
- );
1257
- console.log(` < "${reply.slice(0, 60)}..."`);
1540
+ try {
1541
+ switch (cmd.kind) {
1542
+ case "message":
1543
+ await this.handleMessageCommand(cmd);
1544
+ await this.completeCommand(cmd._id, "delivered");
1545
+ return;
1546
+ case "launch":
1547
+ await this.handleLaunchCommand(cmd);
1548
+ await this.completeCommand(cmd._id, "delivered");
1549
+ return;
1550
+ default:
1551
+ throw new Error(`Unsupported bridge command: ${cmd.kind}`);
1552
+ }
1553
+ } catch (error) {
1554
+ const message = error instanceof Error ? error.message : "Unknown bridge error";
1555
+ console.error(` ! ${message}`);
1556
+ await this.postCommandError(cmd, message);
1557
+ await this.completeCommand(cmd._id, "failed");
1258
1558
  }
1259
- await this.client.mutation(api.agentBridge.bridgePublic.completeCommand, {
1260
- deviceId: this.config.deviceId,
1261
- deviceSecret: this.config.deviceSecret,
1262
- commandId: cmd._id,
1263
- status: "delivered"
1264
- });
1265
1559
  }
1266
1560
  async reportProcesses() {
1267
- const processes = discoverLocalProcesses();
1561
+ const processes = discoverAttachableSessions();
1268
1562
  for (const proc of processes) {
1269
1563
  try {
1270
- await this.client.mutation(api.agentBridge.bridgePublic.reportProcess, {
1271
- deviceId: this.config.deviceId,
1272
- deviceSecret: this.config.deviceSecret,
1273
- ...proc
1274
- });
1564
+ await this.reportProcess(proc);
1275
1565
  } catch {
1276
1566
  }
1277
1567
  }
1278
1568
  if (processes.length > 0) {
1279
- console.log(`[${ts()}] Discovered ${processes.length} local process(es)`);
1569
+ console.log(
1570
+ `[${ts()}] Discovered ${processes.length} attachable session(s)`
1571
+ );
1280
1572
  }
1281
1573
  }
1282
1574
  async refreshLiveActivities() {
@@ -1288,7 +1580,7 @@ var BridgeService = class {
1288
1580
  deviceSecret: this.config.deviceSecret
1289
1581
  }
1290
1582
  );
1291
- writeFileSync(LIVE_ACTIVITIES_CACHE, JSON.stringify(activities, null, 2));
1583
+ writeLiveActivitiesCache(activities);
1292
1584
  } catch {
1293
1585
  }
1294
1586
  }
@@ -1300,7 +1592,7 @@ var BridgeService = class {
1300
1592
  console.log(` Convex: ${this.config.convexUrl}`);
1301
1593
  console.log(` PID: ${process.pid}`);
1302
1594
  console.log("");
1303
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1595
+ if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1304
1596
  writeFileSync(PID_FILE, String(process.pid));
1305
1597
  await this.heartbeat();
1306
1598
  await this.reportProcesses();
@@ -1338,6 +1630,10 @@ var BridgeService = class {
1338
1630
  unlinkSync(PID_FILE);
1339
1631
  } catch {
1340
1632
  }
1633
+ try {
1634
+ writeLiveActivitiesCache([]);
1635
+ } catch {
1636
+ }
1341
1637
  process.exit(0);
1342
1638
  };
1343
1639
  process.on("SIGINT", shutdown);
@@ -1345,21 +1641,205 @@ var BridgeService = class {
1345
1641
  await new Promise(() => {
1346
1642
  });
1347
1643
  }
1644
+ async handleMessageCommand(cmd) {
1645
+ if (!cmd.liveActivityId) {
1646
+ throw new Error("Message command is missing liveActivityId");
1647
+ }
1648
+ const payload = cmd.payload;
1649
+ const body = payload?.body?.trim();
1650
+ if (!body) {
1651
+ throw new Error("Message command is missing a body");
1652
+ }
1653
+ const process9 = cmd.process;
1654
+ if (!process9 || !process9.supportsInboundMessages || !process9.sessionKey || !process9.cwd || !isBridgeProvider(process9.provider)) {
1655
+ throw new Error("No resumable local session is attached to this issue");
1656
+ }
1657
+ console.log(` > "${truncateForLog(body)}"`);
1658
+ await this.reportProcess({
1659
+ provider: process9.provider,
1660
+ providerLabel: process9.providerLabel ?? providerLabel(process9.provider),
1661
+ sessionKey: process9.sessionKey,
1662
+ cwd: process9.cwd,
1663
+ repoRoot: process9.repoRoot,
1664
+ branch: process9.branch,
1665
+ title: process9.title,
1666
+ model: process9.model,
1667
+ mode: "managed",
1668
+ status: "waiting",
1669
+ supportsInboundMessages: true
1670
+ });
1671
+ await this.updateLiveActivity(cmd.liveActivityId, {
1672
+ status: "active",
1673
+ processId: process9._id,
1674
+ title: cmd.liveActivity?.title ?? process9.title
1675
+ });
1676
+ const result = await resumeProviderSession(
1677
+ process9.provider,
1678
+ process9.sessionKey,
1679
+ process9.cwd,
1680
+ body
1681
+ );
1682
+ const processId = await this.reportProcess(result);
1683
+ if (result.responseText) {
1684
+ await this.postAgentMessage(
1685
+ cmd.liveActivityId,
1686
+ "assistant",
1687
+ result.responseText
1688
+ );
1689
+ console.log(` < "${truncateForLog(result.responseText)}"`);
1690
+ }
1691
+ await this.updateLiveActivity(cmd.liveActivityId, {
1692
+ processId,
1693
+ status: "waiting_for_input",
1694
+ latestSummary: summarizeMessage(result.responseText),
1695
+ title: cmd.liveActivity?.title ?? process9.title
1696
+ });
1697
+ }
1698
+ async handleLaunchCommand(cmd) {
1699
+ if (!cmd.liveActivityId) {
1700
+ throw new Error("Launch command is missing liveActivityId");
1701
+ }
1702
+ const payload = cmd.payload;
1703
+ const workspacePath = payload?.workspacePath?.trim();
1704
+ if (!workspacePath) {
1705
+ throw new Error("Launch command is missing workspacePath");
1706
+ }
1707
+ if (!payload?.provider || !isBridgeProvider(payload.provider)) {
1708
+ throw new Error("Launch command is missing a supported provider");
1709
+ }
1710
+ const provider = payload.provider;
1711
+ const issueKey = payload.issueKey ?? cmd.liveActivity?.issueKey ?? "ISSUE";
1712
+ const issueTitle = payload.issueTitle ?? cmd.liveActivity?.issueTitle ?? "Untitled issue";
1713
+ const prompt2 = buildLaunchPrompt(issueKey, issueTitle, workspacePath);
1714
+ await this.updateLiveActivity(cmd.liveActivityId, {
1715
+ status: "active",
1716
+ latestSummary: `Launching ${providerLabel(provider)} in ${workspacePath}`,
1717
+ delegatedRunId: payload.delegatedRunId,
1718
+ launchStatus: "launching",
1719
+ title: `${providerLabel(provider)} on ${this.config.displayName}`
1720
+ });
1721
+ await this.postAgentMessage(
1722
+ cmd.liveActivityId,
1723
+ "status",
1724
+ `Launching ${providerLabel(provider)} in ${workspacePath}`
1725
+ );
1726
+ const result = await launchProviderSession(provider, workspacePath, prompt2);
1727
+ const processId = await this.reportProcess({
1728
+ ...result,
1729
+ title: `${issueKey}: ${issueTitle}`
1730
+ });
1731
+ await this.updateLiveActivity(cmd.liveActivityId, {
1732
+ processId,
1733
+ status: "waiting_for_input",
1734
+ latestSummary: summarizeMessage(result.responseText),
1735
+ delegatedRunId: payload.delegatedRunId,
1736
+ launchStatus: "running",
1737
+ title: `${providerLabel(provider)} on ${this.config.displayName}`
1738
+ });
1739
+ if (result.responseText) {
1740
+ await this.postAgentMessage(
1741
+ cmd.liveActivityId,
1742
+ "assistant",
1743
+ result.responseText
1744
+ );
1745
+ console.log(` < "${truncateForLog(result.responseText)}"`);
1746
+ }
1747
+ }
1748
+ async reportProcess(process9) {
1749
+ const {
1750
+ provider,
1751
+ providerLabel: providerLabel2,
1752
+ localProcessId,
1753
+ sessionKey,
1754
+ cwd,
1755
+ repoRoot,
1756
+ branch,
1757
+ title,
1758
+ model,
1759
+ mode,
1760
+ status,
1761
+ supportsInboundMessages
1762
+ } = process9;
1763
+ return await this.client.mutation(
1764
+ api.agentBridge.bridgePublic.reportProcess,
1765
+ {
1766
+ deviceId: this.config.deviceId,
1767
+ deviceSecret: this.config.deviceSecret,
1768
+ provider,
1769
+ providerLabel: providerLabel2,
1770
+ localProcessId,
1771
+ sessionKey,
1772
+ cwd,
1773
+ repoRoot,
1774
+ branch,
1775
+ title,
1776
+ model,
1777
+ mode,
1778
+ status,
1779
+ supportsInboundMessages
1780
+ }
1781
+ );
1782
+ }
1783
+ async updateLiveActivity(liveActivityId, args) {
1784
+ await this.client.mutation(
1785
+ api.agentBridge.bridgePublic.updateLiveActivityState,
1786
+ {
1787
+ deviceId: this.config.deviceId,
1788
+ deviceSecret: this.config.deviceSecret,
1789
+ liveActivityId,
1790
+ ...args
1791
+ }
1792
+ );
1793
+ }
1794
+ async postAgentMessage(liveActivityId, role, body) {
1795
+ await this.client.mutation(api.agentBridge.bridgePublic.postAgentMessage, {
1796
+ deviceId: this.config.deviceId,
1797
+ deviceSecret: this.config.deviceSecret,
1798
+ liveActivityId,
1799
+ role,
1800
+ body
1801
+ });
1802
+ }
1803
+ async completeCommand(commandId, status) {
1804
+ await this.client.mutation(api.agentBridge.bridgePublic.completeCommand, {
1805
+ deviceId: this.config.deviceId,
1806
+ deviceSecret: this.config.deviceSecret,
1807
+ commandId,
1808
+ status
1809
+ });
1810
+ }
1811
+ async postCommandError(cmd, errorMessage) {
1812
+ if (cmd.kind === "launch" && cmd.liveActivityId) {
1813
+ const payload = cmd.payload;
1814
+ await this.updateLiveActivity(cmd.liveActivityId, {
1815
+ status: "failed",
1816
+ latestSummary: errorMessage,
1817
+ delegatedRunId: payload?.delegatedRunId,
1818
+ launchStatus: "failed"
1819
+ });
1820
+ await this.postAgentMessage(cmd.liveActivityId, "status", errorMessage);
1821
+ return;
1822
+ }
1823
+ if (cmd.kind === "message" && cmd.liveActivityId) {
1824
+ await this.postAgentMessage(cmd.liveActivityId, "status", errorMessage);
1825
+ await this.updateLiveActivity(cmd.liveActivityId, {
1826
+ status: "waiting_for_input",
1827
+ latestSummary: errorMessage
1828
+ });
1829
+ }
1830
+ }
1348
1831
  };
1349
- async function setupBridgeDevice(convexUrl, userId) {
1350
- const client = new ConvexHttpClient2(convexUrl);
1832
+ async function setupBridgeDevice(client, convexUrl, userId) {
1351
1833
  const deviceKey = `${hostname()}-${randomUUID().slice(0, 8)}`;
1352
- const deviceSecret = randomUUID();
1353
1834
  const displayName = `${process.env.USER ?? "user"}'s ${platform() === "darwin" ? "Mac" : "machine"}`;
1354
1835
  const result = await client.mutation(
1355
- api.agentBridge.bridgePublic.setupDevice,
1836
+ api.agentBridge.mutations.registerBridgeDevice,
1356
1837
  {
1357
- userId,
1358
1838
  deviceKey,
1359
- deviceSecret,
1360
1839
  displayName,
1361
1840
  hostname: hostname(),
1362
1841
  platform: platform(),
1842
+ serviceType: "foreground",
1363
1843
  cliVersion: "0.1.0",
1364
1844
  capabilities: ["codex", "claude_code"]
1365
1845
  }
@@ -1367,7 +1847,7 @@ async function setupBridgeDevice(convexUrl, userId) {
1367
1847
  const config = {
1368
1848
  deviceId: result.deviceId,
1369
1849
  deviceKey,
1370
- deviceSecret,
1850
+ deviceSecret: result.deviceSecret,
1371
1851
  userId,
1372
1852
  displayName,
1373
1853
  convexUrl,
@@ -1376,11 +1856,43 @@ async function setupBridgeDevice(convexUrl, userId) {
1376
1856
  saveBridgeConfig(config);
1377
1857
  return config;
1378
1858
  }
1859
+ function buildLaunchPrompt(issueKey, issueTitle, workspacePath) {
1860
+ return [
1861
+ `You are working on Vector issue ${issueKey}: ${issueTitle}.`,
1862
+ `The repository is already checked out at ${workspacePath}.`,
1863
+ "Inspect the codebase, identify the relevant implementation area, and start the work.",
1864
+ "In your first reply, summarize your plan and the first concrete step you are taking."
1865
+ ].join("\n\n");
1866
+ }
1867
+ function summarizeMessage(message) {
1868
+ if (!message) {
1869
+ return void 0;
1870
+ }
1871
+ return message.length > 120 ? `${message.slice(0, 117).trimEnd()}...` : message;
1872
+ }
1873
+ function truncateForLog(message) {
1874
+ return message.length > 80 ? `${message.slice(0, 77).trimEnd()}...` : message;
1875
+ }
1876
+ function isBridgeProvider(provider) {
1877
+ return provider === "codex" || provider === "claude_code";
1878
+ }
1879
+ function providerLabel(provider) {
1880
+ return provider === "codex" ? "Codex" : "Claude";
1881
+ }
1379
1882
  function installLaunchAgent(vcliPath) {
1380
1883
  if (platform() !== "darwin") {
1381
1884
  console.error("LaunchAgent is macOS only. Use systemd on Linux.");
1382
1885
  return;
1383
1886
  }
1887
+ const programArguments = getLaunchAgentProgramArguments(vcliPath);
1888
+ const environmentVariables = [
1889
+ " <key>PATH</key>",
1890
+ " <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>",
1891
+ ...process.env.VECTOR_HOME?.trim() ? [
1892
+ " <key>VECTOR_HOME</key>",
1893
+ ` <string>${process.env.VECTOR_HOME.trim()}</string>`
1894
+ ] : []
1895
+ ].join("\n");
1384
1896
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
1385
1897
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1386
1898
  <plist version="1.0">
@@ -1388,11 +1900,7 @@ function installLaunchAgent(vcliPath) {
1388
1900
  <key>Label</key>
1389
1901
  <string>${LAUNCHAGENT_LABEL}</string>
1390
1902
  <key>ProgramArguments</key>
1391
- <array>
1392
- <string>${vcliPath}</string>
1393
- <string>service</string>
1394
- <string>run</string>
1395
- </array>
1903
+ ${programArguments}
1396
1904
  <key>RunAtLoad</key>
1397
1905
  <true/>
1398
1906
  <key>KeepAlive</key>
@@ -1403,35 +1911,78 @@ function installLaunchAgent(vcliPath) {
1403
1911
  <string>${CONFIG_DIR}/bridge.err.log</string>
1404
1912
  <key>EnvironmentVariables</key>
1405
1913
  <dict>
1406
- <key>PATH</key>
1407
- <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
1914
+ ${environmentVariables}
1408
1915
  </dict>
1409
1916
  </dict>
1410
1917
  </plist>`;
1411
- if (!existsSync(LAUNCHAGENT_DIR)) {
1918
+ if (!existsSync2(LAUNCHAGENT_DIR)) {
1412
1919
  mkdirSync(LAUNCHAGENT_DIR, { recursive: true });
1413
1920
  }
1414
1921
  removeLegacyMenuBarLaunchAgent();
1415
1922
  writeFileSync(LAUNCHAGENT_PLIST, plist);
1416
1923
  console.log(`Installed LaunchAgent: ${LAUNCHAGENT_PLIST}`);
1417
1924
  }
1925
+ function getLaunchAgentProgramArguments(vcliPath) {
1926
+ const args = resolveCliInvocation(vcliPath);
1927
+ return [
1928
+ "<array>",
1929
+ ...args.map((arg) => ` <string>${arg}</string>`),
1930
+ " <string>service</string>",
1931
+ " <string>run</string>",
1932
+ " </array>"
1933
+ ].join("\n");
1934
+ }
1935
+ function resolveCliInvocation(vcliPath) {
1936
+ if (vcliPath.endsWith(".js")) {
1937
+ return [process.execPath, vcliPath];
1938
+ }
1939
+ if (vcliPath.endsWith(".ts")) {
1940
+ const tsxPath = join2(
1941
+ import.meta.dirname ?? process.cwd(),
1942
+ "..",
1943
+ "..",
1944
+ "..",
1945
+ "node_modules",
1946
+ ".bin",
1947
+ "tsx"
1948
+ );
1949
+ if (existsSync2(tsxPath)) {
1950
+ return [tsxPath, vcliPath];
1951
+ }
1952
+ }
1953
+ return [vcliPath];
1954
+ }
1418
1955
  function loadLaunchAgent() {
1419
- try {
1420
- execSync(`launchctl load ${LAUNCHAGENT_PLIST}`, { stdio: "inherit" });
1956
+ if (runLaunchctl(["bootstrap", launchctlGuiDomain(), LAUNCHAGENT_PLIST])) {
1957
+ runLaunchctl([
1958
+ "kickstart",
1959
+ "-k",
1960
+ `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`
1961
+ ]);
1421
1962
  console.log(
1422
1963
  "LaunchAgent loaded. Bridge will start automatically on login."
1423
1964
  );
1424
- } catch {
1425
- console.error("Failed to load LaunchAgent");
1965
+ return;
1426
1966
  }
1967
+ if (runLaunchctl([
1968
+ "kickstart",
1969
+ "-k",
1970
+ `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`
1971
+ ]) || runLaunchctl(["load", LAUNCHAGENT_PLIST])) {
1972
+ console.log(
1973
+ "LaunchAgent loaded. Bridge will start automatically on login."
1974
+ );
1975
+ return;
1976
+ }
1977
+ console.error("Failed to load LaunchAgent");
1427
1978
  }
1428
1979
  function unloadLaunchAgent() {
1429
- try {
1430
- execSync(`launchctl unload ${LAUNCHAGENT_PLIST}`, { stdio: "inherit" });
1980
+ if (runLaunchctl(["bootout", `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`]) || runLaunchctl(["bootout", launchctlGuiDomain(), LAUNCHAGENT_PLIST]) || runLaunchctl(["unload", LAUNCHAGENT_PLIST])) {
1431
1981
  console.log("LaunchAgent unloaded.");
1432
- } catch {
1433
- console.error("Failed to unload LaunchAgent (may not be loaded)");
1982
+ return true;
1434
1983
  }
1984
+ console.error("Failed to unload LaunchAgent (may not be loaded)");
1985
+ return false;
1435
1986
  }
1436
1987
  function uninstallLaunchAgent() {
1437
1988
  unloadLaunchAgent();
@@ -1442,13 +1993,13 @@ function uninstallLaunchAgent() {
1442
1993
  } catch {
1443
1994
  }
1444
1995
  }
1445
- var MENUBAR_PID_FILE = join(CONFIG_DIR, "menubar.pid");
1996
+ var MENUBAR_PID_FILE = join2(CONFIG_DIR, "menubar.pid");
1446
1997
  function removeLegacyMenuBarLaunchAgent() {
1447
- if (platform() !== "darwin" || !existsSync(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
1998
+ if (platform() !== "darwin" || !existsSync2(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
1448
1999
  return;
1449
2000
  }
1450
2001
  try {
1451
- execSync(`launchctl unload ${LEGACY_MENUBAR_LAUNCHAGENT_PLIST}`, {
2002
+ execSync2(`launchctl unload ${LEGACY_MENUBAR_LAUNCHAGENT_PLIST}`, {
1452
2003
  stdio: "pipe"
1453
2004
  });
1454
2005
  } catch {
@@ -1458,19 +2009,68 @@ function removeLegacyMenuBarLaunchAgent() {
1458
2009
  } catch {
1459
2010
  }
1460
2011
  }
1461
- function findMenuBarScript() {
2012
+ function launchctlGuiDomain() {
2013
+ const uid = typeof process.getuid === "function" ? process.getuid() : typeof process.geteuid === "function" ? process.geteuid() : 0;
2014
+ return `gui/${uid}`;
2015
+ }
2016
+ function runLaunchctl(args) {
2017
+ try {
2018
+ execFileSync("launchctl", args, {
2019
+ stdio: "ignore"
2020
+ });
2021
+ return true;
2022
+ } catch {
2023
+ return false;
2024
+ }
2025
+ }
2026
+ function findCliEntrypoint() {
1462
2027
  const candidates = [
1463
- join(import.meta.dirname ?? "", "menubar.js"),
1464
- join(import.meta.dirname ?? "", "..", "dist", "menubar.js")
2028
+ join2(import.meta.dirname ?? "", "index.js"),
2029
+ join2(import.meta.dirname ?? "", "index.ts"),
2030
+ join2(import.meta.dirname ?? "", "..", "dist", "index.js")
2031
+ ];
2032
+ for (const p of candidates) {
2033
+ if (existsSync2(p)) return p;
2034
+ }
2035
+ return null;
2036
+ }
2037
+ function getCurrentCliInvocation() {
2038
+ const entrypoint = findCliEntrypoint();
2039
+ if (!entrypoint) {
2040
+ return null;
2041
+ }
2042
+ return resolveCliInvocation(entrypoint);
2043
+ }
2044
+ function findMenuBarExecutable() {
2045
+ const candidates = [
2046
+ join2(
2047
+ import.meta.dirname ?? "",
2048
+ "..",
2049
+ "native",
2050
+ "VectorMenuBar.app",
2051
+ "Contents",
2052
+ "MacOS",
2053
+ "VectorMenuBar"
2054
+ ),
2055
+ join2(
2056
+ import.meta.dirname ?? "",
2057
+ "native",
2058
+ "VectorMenuBar.app",
2059
+ "Contents",
2060
+ "MacOS",
2061
+ "VectorMenuBar"
2062
+ )
1465
2063
  ];
1466
2064
  for (const p of candidates) {
1467
- if (existsSync(p)) return p;
2065
+ if (existsSync2(p)) {
2066
+ return p;
2067
+ }
1468
2068
  }
1469
2069
  return null;
1470
2070
  }
1471
2071
  function isKnownMenuBarProcess(pid) {
1472
2072
  try {
1473
- const command = execSync(`ps -p ${pid} -o args=`, {
2073
+ const command = execSync2(`ps -p ${pid} -o args=`, {
1474
2074
  encoding: "utf-8",
1475
2075
  timeout: 3e3
1476
2076
  });
@@ -1480,9 +2080,9 @@ function isKnownMenuBarProcess(pid) {
1480
2080
  }
1481
2081
  }
1482
2082
  function killExistingMenuBar() {
1483
- if (existsSync(MENUBAR_PID_FILE)) {
2083
+ if (existsSync2(MENUBAR_PID_FILE)) {
1484
2084
  try {
1485
- const pid = Number(readFileSync(MENUBAR_PID_FILE, "utf-8").trim());
2085
+ const pid = Number(readFileSync2(MENUBAR_PID_FILE, "utf-8").trim());
1486
2086
  if (Number.isFinite(pid) && pid > 0 && isKnownMenuBarProcess(pid)) {
1487
2087
  process.kill(pid, "SIGTERM");
1488
2088
  }
@@ -1497,15 +2097,20 @@ function killExistingMenuBar() {
1497
2097
  async function launchMenuBar() {
1498
2098
  if (platform() !== "darwin") return;
1499
2099
  removeLegacyMenuBarLaunchAgent();
1500
- const script = findMenuBarScript();
1501
- if (!script) return;
2100
+ const executable = findMenuBarExecutable();
2101
+ const cliInvocation = getCurrentCliInvocation();
2102
+ if (!executable || !cliInvocation) return;
1502
2103
  killExistingMenuBar();
1503
2104
  try {
1504
2105
  const { spawn: spawnChild } = await import("child_process");
1505
- const child = spawnChild(process.execPath, [script], {
2106
+ const child = spawnChild(executable, [], {
1506
2107
  detached: true,
1507
2108
  stdio: "ignore",
1508
- env: { ...process.env }
2109
+ env: {
2110
+ ...process.env,
2111
+ VECTOR_CLI_COMMAND: cliInvocation[0],
2112
+ VECTOR_CLI_ARGS_JSON: JSON.stringify(cliInvocation.slice(1))
2113
+ }
1509
2114
  });
1510
2115
  child.unref();
1511
2116
  if (child.pid) {
@@ -1514,14 +2119,17 @@ async function launchMenuBar() {
1514
2119
  } catch {
1515
2120
  }
1516
2121
  }
2122
+ function stopMenuBar() {
2123
+ killExistingMenuBar();
2124
+ }
1517
2125
  function getBridgeStatus() {
1518
2126
  const config = loadBridgeConfig();
1519
2127
  if (!config) return { configured: false, running: false, starting: false };
1520
2128
  let running = false;
1521
2129
  let starting = false;
1522
2130
  let pid;
1523
- if (existsSync(PID_FILE)) {
1524
- const pidStr = readFileSync(PID_FILE, "utf-8").trim();
2131
+ if (existsSync2(PID_FILE)) {
2132
+ const pidStr = readFileSync2(PID_FILE, "utf-8").trim();
1525
2133
  pid = Number(pidStr);
1526
2134
  try {
1527
2135
  process.kill(pid, 0);
@@ -1531,23 +2139,20 @@ function getBridgeStatus() {
1531
2139
  }
1532
2140
  }
1533
2141
  if (!running && platform() === "darwin") {
1534
- try {
1535
- const result = execSync(
1536
- `launchctl list ${LAUNCHAGENT_LABEL} 2>/dev/null`,
1537
- { encoding: "utf-8", timeout: 3e3 }
1538
- );
1539
- if (result.includes(LAUNCHAGENT_LABEL)) {
1540
- starting = true;
1541
- }
1542
- } catch {
1543
- }
2142
+ starting = runLaunchctl(["print", `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`]) || runLaunchctl(["list", LAUNCHAGENT_LABEL]);
1544
2143
  }
1545
2144
  return { configured: true, running, starting, pid, config };
1546
2145
  }
1547
- function stopBridge() {
1548
- killExistingMenuBar();
1549
- if (!existsSync(PID_FILE)) return false;
1550
- const pid = Number(readFileSync(PID_FILE, "utf-8").trim());
2146
+ function stopBridge(options) {
2147
+ if (options?.includeMenuBar) {
2148
+ killExistingMenuBar();
2149
+ }
2150
+ try {
2151
+ writeLiveActivitiesCache([]);
2152
+ } catch {
2153
+ }
2154
+ if (!existsSync2(PID_FILE)) return false;
2155
+ const pid = Number(readFileSync2(PID_FILE, "utf-8").trim());
1551
2156
  try {
1552
2157
  process.kill(pid, "SIGTERM");
1553
2158
  return true;
@@ -2003,7 +2608,7 @@ var program = new Command();
2003
2608
  function readPackageVersionSync() {
2004
2609
  try {
2005
2610
  const dir = import.meta.dirname ?? dirname(fileURLToPath2(import.meta.url));
2006
- const raw = readFileSync2(join2(dir, "..", "package.json"), "utf8");
2611
+ const raw = readFileSync3(join3(dir, "..", "package.json"), "utf8");
2007
2612
  return JSON.parse(raw).version ?? "unknown";
2008
2613
  } catch {
2009
2614
  return "unknown";
@@ -3550,6 +4155,7 @@ folderCommand.command("delete <folderId>").action(async (folderId, _options, com
3550
4155
  });
3551
4156
  printOutput(result, runtime.json);
3552
4157
  });
4158
+ var VECTOR_HOME = process.env.VECTOR_HOME?.trim() || `${process.env.HOME ?? "~"}/.vector`;
3553
4159
  var serviceCommand = program.command("service").description("Manage the local bridge service");
3554
4160
  serviceCommand.command("start").description("Start the bridge service via LaunchAgent (macOS) or foreground").action(async (_options, command) => {
3555
4161
  const existing = getBridgeStatus();
@@ -3574,7 +4180,7 @@ serviceCommand.command("start").description("Start the bridge service via Launch
3574
4180
  s.stop("Failed");
3575
4181
  throw new Error("Not logged in. Run `vcli auth login` first.");
3576
4182
  }
3577
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4183
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
3578
4184
  s.stop(`Device registered: ${config.displayName}`);
3579
4185
  }
3580
4186
  if (osPlatform() === "darwin") {
@@ -3603,7 +4209,7 @@ serviceCommand.command("run").description("Run the bridge service in the foregro
3603
4209
  );
3604
4210
  const user = await runQuery(client, api.users.currentUser);
3605
4211
  if (!user) throw new Error("Not logged in. Run `vcli auth login` first.");
3606
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4212
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
3607
4213
  }
3608
4214
  if (osPlatform() === "darwin") {
3609
4215
  await launchMenuBar();
@@ -3611,14 +4217,17 @@ serviceCommand.command("run").description("Run the bridge service in the foregro
3611
4217
  const bridge = new BridgeService(config);
3612
4218
  await bridge.run();
3613
4219
  });
3614
- serviceCommand.command("stop").description("Stop the bridge service and menu bar").action(() => {
4220
+ serviceCommand.command("stop").description("Stop the bridge service").action(() => {
4221
+ let unloaded = false;
3615
4222
  if (osPlatform() === "darwin") {
3616
- unloadLaunchAgent();
4223
+ unloaded = unloadLaunchAgent();
3617
4224
  }
3618
- if (stopBridge()) {
4225
+ if (stopBridge() || unloaded) {
3619
4226
  console.log("Bridge stopped.");
3620
4227
  } else if (osPlatform() !== "darwin") {
3621
4228
  console.log("Bridge is not running.");
4229
+ } else {
4230
+ console.log("Bridge is not running.");
3622
4231
  }
3623
4232
  });
3624
4233
  serviceCommand.command("status").description("Show bridge service status").action((_options, command) => {
@@ -3634,7 +4243,7 @@ serviceCommand.command("status").description("Show bridge service status").actio
3634
4243
  console.log(` User: ${status.config.userId}`);
3635
4244
  const statusLabel = status.running ? `Running (PID ${status.pid})` : status.starting ? "Starting..." : "Not running";
3636
4245
  console.log(` Status: ${statusLabel}`);
3637
- console.log(` Config: ~/.vector/bridge.json`);
4246
+ console.log(` Config: ${VECTOR_HOME}/bridge.json`);
3638
4247
  });
3639
4248
  serviceCommand.command("install").description("Install the bridge as a system service (macOS LaunchAgent)").action(async (_options, command) => {
3640
4249
  if (osPlatform() !== "darwin") {
@@ -3659,7 +4268,7 @@ serviceCommand.command("install").description("Install the bridge as a system se
3659
4268
  s.stop("Failed");
3660
4269
  throw new Error("Not logged in. Run `vcli auth login` first.");
3661
4270
  }
3662
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4271
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
3663
4272
  s.stop(`Device registered: ${config.displayName}`);
3664
4273
  }
3665
4274
  s.start("Installing LaunchAgent...");
@@ -3675,21 +4284,21 @@ serviceCommand.command("install").description("Install the bridge as a system se
3675
4284
  );
3676
4285
  });
3677
4286
  serviceCommand.command("uninstall").description("Stop the bridge and uninstall the system service").action(() => {
3678
- stopBridge();
4287
+ stopBridge({ includeMenuBar: true });
3679
4288
  uninstallLaunchAgent();
4289
+ stopMenuBar();
3680
4290
  console.log("Bridge stopped and service uninstalled.");
3681
4291
  });
3682
4292
  serviceCommand.command("logs").description("Show bridge service logs").action(async () => {
3683
4293
  const fs7 = await import("fs");
3684
- const os2 = await import("os");
3685
4294
  const p = await import("path");
3686
- const logPath = p.join(os2.homedir(), ".vector", "bridge.log");
4295
+ const logPath = p.join(VECTOR_HOME, "bridge.log");
3687
4296
  if (fs7.existsSync(logPath)) {
3688
4297
  const content = fs7.readFileSync(logPath, "utf-8");
3689
4298
  const lines = content.split("\n");
3690
4299
  console.log(lines.slice(-50).join("\n"));
3691
4300
  } else {
3692
- console.log("No log file found at ~/.vector/bridge.log");
4301
+ console.log(`No log file found at ${logPath}`);
3693
4302
  }
3694
4303
  });
3695
4304
  serviceCommand.command("enable").description("Enable bridge to start at login (macOS LaunchAgent)").action(async () => {
@@ -3704,6 +4313,7 @@ serviceCommand.command("enable").description("Enable bridge to start at login (m
3704
4313
  });
3705
4314
  serviceCommand.command("disable").description("Disable bridge from starting at login").action(() => {
3706
4315
  uninstallLaunchAgent();
4316
+ stopMenuBar();
3707
4317
  console.log("Bridge will no longer start at login.");
3708
4318
  });
3709
4319
  var bridgeCommand = program.command("bridge").description("Start/stop the local agent bridge");
@@ -3719,7 +4329,7 @@ bridgeCommand.command("start").description("Register device, install service, an
3719
4329
  );
3720
4330
  const user = await runQuery(client, api.users.currentUser);
3721
4331
  if (!user) throw new Error("Not logged in. Run `vcli auth login` first.");
3722
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4332
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
3723
4333
  console.log(
3724
4334
  `Device registered: ${config.displayName} (${config.deviceId})`
3725
4335
  );
@@ -3738,10 +4348,12 @@ bridgeCommand.command("start").description("Register device, install service, an
3738
4348
  }
3739
4349
  });
3740
4350
  bridgeCommand.command("stop").description("Stop the bridge service").action(() => {
4351
+ let unloaded = false;
3741
4352
  if (osPlatform() === "darwin") {
3742
4353
  uninstallLaunchAgent();
4354
+ unloaded = true;
3743
4355
  }
3744
- if (stopBridge()) {
4356
+ if (stopBridge() || unloaded) {
3745
4357
  console.log("Bridge stopped.");
3746
4358
  } else {
3747
4359
  console.log("Bridge is not running.");