@rehpic/vcli 0.1.0-beta.51.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,114 +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 HEARTBEAT_INTERVAL_MS = 3e4;
1118
- var COMMAND_POLL_INTERVAL_MS = 5e3;
1119
- var PROCESS_DISCOVERY_INTERVAL_MS = 6e4;
1120
- function loadBridgeConfig() {
1121
- 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() {
1122
1349
  try {
1123
- return JSON.parse(readFileSync(BRIDGE_CONFIG_FILE, "utf-8"));
1350
+ const realHome = userInfo().homedir?.trim();
1351
+ if (realHome) {
1352
+ return realHome;
1353
+ }
1124
1354
  } catch {
1125
- return null;
1126
1355
  }
1356
+ return homedir2();
1127
1357
  }
1128
- function saveBridgeConfig(config) {
1129
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1130
- 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;
1131
1371
  }
1132
- function discoverLocalProcesses() {
1133
- const processes = [];
1134
- const patterns = [
1135
- {
1136
- grep: "[c]laude",
1137
- provider: "claude_code",
1138
- label: "Claude",
1139
- prefix: "claude"
1140
- },
1141
- { grep: "[c]odex", provider: "codex", label: "Codex", prefix: "codex" }
1142
- ];
1143
- for (const { grep, provider, label, prefix } of patterns) {
1144
- try {
1145
- const ps = execSync(
1146
- `ps aux | grep -E '${grep}' | grep -v vector-bridge | grep -v grep`,
1147
- { encoding: "utf-8", timeout: 5e3 }
1148
- );
1149
- for (const line of ps.trim().split("\n").filter(Boolean)) {
1150
- const pid = line.split(/\s+/)[1];
1151
- if (!pid) continue;
1152
- let cwd;
1153
- try {
1154
- cwd = execSync(
1155
- `lsof -p ${pid} 2>/dev/null | grep cwd | awk '{print $NF}'`,
1156
- { encoding: "utf-8", timeout: 3e3 }
1157
- ).trim() || void 0;
1158
- } catch {
1159
- }
1160
- const gitInfo = cwd ? getGitInfo(cwd) : {};
1161
- processes.push({
1162
- provider,
1163
- providerLabel: label,
1164
- localProcessId: pid,
1165
- sessionKey: `${prefix}-${pid}`,
1166
- cwd,
1167
- ...gitInfo,
1168
- mode: "observed",
1169
- status: "observed",
1170
- supportsInboundMessages: false
1171
- });
1172
- }
1173
- } 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;
1174
1388
  }
1389
+ seen.add(key);
1390
+ return true;
1391
+ });
1392
+ }
1393
+ function extractCodexResponseText(content) {
1394
+ if (!Array.isArray(content)) {
1395
+ return void 0;
1175
1396
  }
1176
- return processes;
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;
1409
+ }
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;
1177
1446
  }
1178
1447
  function getGitInfo(cwd) {
1179
1448
  try {
1180
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
1449
+ const repoRoot = execSync("git rev-parse --show-toplevel", {
1181
1450
  encoding: "utf-8",
1182
1451
  cwd,
1183
1452
  timeout: 3e3
1184
1453
  }).trim();
1185
- const repoRoot = execSync("git rev-parse --show-toplevel", {
1454
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
1186
1455
  encoding: "utf-8",
1187
1456
  cwd,
1188
1457
  timeout: 3e3
1189
1458
  }).trim();
1190
- return { branch, repoRoot };
1459
+ return {
1460
+ repoRoot: repoRoot || void 0,
1461
+ branch: branch || void 0
1462
+ };
1191
1463
  } catch {
1192
1464
  return {};
1193
1465
  }
1194
1466
  }
1195
- function generateReply(userMessage) {
1196
- const lower = userMessage.toLowerCase().trim();
1197
- if (["hey", "hi", "hello"].includes(lower)) {
1198
- return "Hey! I'm running on your local machine via the Vector bridge. What would you like me to work on?";
1199
- }
1200
- if (lower.includes("status") || lower.includes("progress")) {
1201
- return "I'm making good progress. Currently reviewing the changes and running tests.";
1202
- }
1203
- if (lower.includes("stop") || lower.includes("cancel")) {
1204
- 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;
1205
1490
  }
1206
- 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));
1207
1499
  }
1208
1500
  var BridgeService = class {
1209
1501
  constructor(config) {
@@ -1233,45 +1525,50 @@ var BridgeService = class {
1233
1525
  }
1234
1526
  }
1235
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
+ }
1236
1539
  console.log(` ${cmd.kind}: ${cmd._id}`);
1237
- if (cmd.kind === "message" && cmd.liveActivityId) {
1238
- const payload = cmd.payload;
1239
- const body = payload?.body ?? "";
1240
- console.log(` > "${body}"`);
1241
- const reply = generateReply(body);
1242
- await this.client.mutation(
1243
- api.agentBridge.bridgePublic.postAgentMessage,
1244
- {
1245
- deviceId: this.config.deviceId,
1246
- deviceSecret: this.config.deviceSecret,
1247
- liveActivityId: cmd.liveActivityId,
1248
- role: "assistant",
1249
- body: reply
1250
- }
1251
- );
1252
- 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");
1253
1558
  }
1254
- await this.client.mutation(api.agentBridge.bridgePublic.completeCommand, {
1255
- deviceId: this.config.deviceId,
1256
- deviceSecret: this.config.deviceSecret,
1257
- commandId: cmd._id,
1258
- status: "delivered"
1259
- });
1260
1559
  }
1261
1560
  async reportProcesses() {
1262
- const processes = discoverLocalProcesses();
1561
+ const processes = discoverAttachableSessions();
1263
1562
  for (const proc of processes) {
1264
1563
  try {
1265
- await this.client.mutation(api.agentBridge.bridgePublic.reportProcess, {
1266
- deviceId: this.config.deviceId,
1267
- deviceSecret: this.config.deviceSecret,
1268
- ...proc
1269
- });
1564
+ await this.reportProcess(proc);
1270
1565
  } catch {
1271
1566
  }
1272
1567
  }
1273
1568
  if (processes.length > 0) {
1274
- console.log(`[${ts()}] Discovered ${processes.length} local process(es)`);
1569
+ console.log(
1570
+ `[${ts()}] Discovered ${processes.length} attachable session(s)`
1571
+ );
1275
1572
  }
1276
1573
  }
1277
1574
  async refreshLiveActivities() {
@@ -1283,7 +1580,7 @@ var BridgeService = class {
1283
1580
  deviceSecret: this.config.deviceSecret
1284
1581
  }
1285
1582
  );
1286
- writeFileSync(LIVE_ACTIVITIES_CACHE, JSON.stringify(activities, null, 2));
1583
+ writeLiveActivitiesCache(activities);
1287
1584
  } catch {
1288
1585
  }
1289
1586
  }
@@ -1295,7 +1592,7 @@ var BridgeService = class {
1295
1592
  console.log(` Convex: ${this.config.convexUrl}`);
1296
1593
  console.log(` PID: ${process.pid}`);
1297
1594
  console.log("");
1298
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1595
+ if (!existsSync2(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
1299
1596
  writeFileSync(PID_FILE, String(process.pid));
1300
1597
  await this.heartbeat();
1301
1598
  await this.reportProcesses();
@@ -1333,6 +1630,10 @@ var BridgeService = class {
1333
1630
  unlinkSync(PID_FILE);
1334
1631
  } catch {
1335
1632
  }
1633
+ try {
1634
+ writeLiveActivitiesCache([]);
1635
+ } catch {
1636
+ }
1336
1637
  process.exit(0);
1337
1638
  };
1338
1639
  process.on("SIGINT", shutdown);
@@ -1340,21 +1641,205 @@ var BridgeService = class {
1340
1641
  await new Promise(() => {
1341
1642
  });
1342
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
+ }
1343
1831
  };
1344
- async function setupBridgeDevice(convexUrl, userId) {
1345
- const client = new ConvexHttpClient2(convexUrl);
1832
+ async function setupBridgeDevice(client, convexUrl, userId) {
1346
1833
  const deviceKey = `${hostname()}-${randomUUID().slice(0, 8)}`;
1347
- const deviceSecret = randomUUID();
1348
1834
  const displayName = `${process.env.USER ?? "user"}'s ${platform() === "darwin" ? "Mac" : "machine"}`;
1349
1835
  const result = await client.mutation(
1350
- api.agentBridge.bridgePublic.setupDevice,
1836
+ api.agentBridge.mutations.registerBridgeDevice,
1351
1837
  {
1352
- userId,
1353
1838
  deviceKey,
1354
- deviceSecret,
1355
1839
  displayName,
1356
1840
  hostname: hostname(),
1357
1841
  platform: platform(),
1842
+ serviceType: "foreground",
1358
1843
  cliVersion: "0.1.0",
1359
1844
  capabilities: ["codex", "claude_code"]
1360
1845
  }
@@ -1362,7 +1847,7 @@ async function setupBridgeDevice(convexUrl, userId) {
1362
1847
  const config = {
1363
1848
  deviceId: result.deviceId,
1364
1849
  deviceKey,
1365
- deviceSecret,
1850
+ deviceSecret: result.deviceSecret,
1366
1851
  userId,
1367
1852
  displayName,
1368
1853
  convexUrl,
@@ -1371,11 +1856,43 @@ async function setupBridgeDevice(convexUrl, userId) {
1371
1856
  saveBridgeConfig(config);
1372
1857
  return config;
1373
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
+ }
1374
1882
  function installLaunchAgent(vcliPath) {
1375
1883
  if (platform() !== "darwin") {
1376
1884
  console.error("LaunchAgent is macOS only. Use systemd on Linux.");
1377
1885
  return;
1378
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");
1379
1896
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
1380
1897
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1381
1898
  <plist version="1.0">
@@ -1383,11 +1900,7 @@ function installLaunchAgent(vcliPath) {
1383
1900
  <key>Label</key>
1384
1901
  <string>${LAUNCHAGENT_LABEL}</string>
1385
1902
  <key>ProgramArguments</key>
1386
- <array>
1387
- <string>${vcliPath}</string>
1388
- <string>service</string>
1389
- <string>run</string>
1390
- </array>
1903
+ ${programArguments}
1391
1904
  <key>RunAtLoad</key>
1392
1905
  <true/>
1393
1906
  <key>KeepAlive</key>
@@ -1398,145 +1911,225 @@ function installLaunchAgent(vcliPath) {
1398
1911
  <string>${CONFIG_DIR}/bridge.err.log</string>
1399
1912
  <key>EnvironmentVariables</key>
1400
1913
  <dict>
1401
- <key>PATH</key>
1402
- <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
1914
+ ${environmentVariables}
1403
1915
  </dict>
1404
1916
  </dict>
1405
1917
  </plist>`;
1406
- const menuBarCandidates = [
1407
- join(CONFIG_DIR, "VectorMenuBar"),
1408
- "/usr/local/bin/VectorMenuBar",
1409
- join(homedir2(), ".local", "bin", "VectorMenuBar")
1410
- ];
1411
- const menuBarBinary = menuBarCandidates.find((p) => existsSync(p));
1412
- if (menuBarBinary) {
1413
- const menuBarPlist = `<?xml version="1.0" encoding="UTF-8"?>
1414
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1415
- <plist version="1.0">
1416
- <dict>
1417
- <key>Label</key>
1418
- <string>com.vector.menubar</string>
1419
- <key>ProgramArguments</key>
1420
- <array>
1421
- <string>${menuBarBinary}</string>
1422
- </array>
1423
- <key>RunAtLoad</key>
1424
- <true/>
1425
- <key>KeepAlive</key>
1426
- <false/>
1427
- </dict>
1428
- </plist>`;
1429
- const menuBarPlistPath = join(LAUNCHAGENT_DIR, "com.vector.menubar.plist");
1430
- writeFileSync(menuBarPlistPath, menuBarPlist);
1431
- try {
1432
- execSync(`launchctl load ${menuBarPlistPath}`, { stdio: "pipe" });
1433
- console.log("Menu bar helper installed.");
1434
- } catch {
1435
- }
1436
- }
1437
- if (!existsSync(LAUNCHAGENT_DIR)) {
1918
+ if (!existsSync2(LAUNCHAGENT_DIR)) {
1438
1919
  mkdirSync(LAUNCHAGENT_DIR, { recursive: true });
1439
1920
  }
1921
+ removeLegacyMenuBarLaunchAgent();
1440
1922
  writeFileSync(LAUNCHAGENT_PLIST, plist);
1441
1923
  console.log(`Installed LaunchAgent: ${LAUNCHAGENT_PLIST}`);
1442
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
+ }
1443
1955
  function loadLaunchAgent() {
1444
- try {
1445
- 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
+ ]);
1446
1962
  console.log(
1447
1963
  "LaunchAgent loaded. Bridge will start automatically on login."
1448
1964
  );
1449
- } catch {
1450
- console.error("Failed to load LaunchAgent");
1965
+ return;
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;
1451
1976
  }
1977
+ console.error("Failed to load LaunchAgent");
1452
1978
  }
1453
1979
  function unloadLaunchAgent() {
1454
- try {
1455
- execSync(`launchctl unload ${LAUNCHAGENT_PLIST}`, { stdio: "inherit" });
1980
+ if (runLaunchctl(["bootout", `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`]) || runLaunchctl(["bootout", launchctlGuiDomain(), LAUNCHAGENT_PLIST]) || runLaunchctl(["unload", LAUNCHAGENT_PLIST])) {
1456
1981
  console.log("LaunchAgent unloaded.");
1457
- } catch {
1458
- console.error("Failed to unload LaunchAgent (may not be loaded)");
1982
+ return true;
1459
1983
  }
1984
+ console.error("Failed to unload LaunchAgent (may not be loaded)");
1985
+ return false;
1460
1986
  }
1461
1987
  function uninstallLaunchAgent() {
1462
1988
  unloadLaunchAgent();
1989
+ removeLegacyMenuBarLaunchAgent();
1463
1990
  try {
1464
1991
  unlinkSync(LAUNCHAGENT_PLIST);
1465
1992
  console.log("LaunchAgent removed.");
1466
1993
  } catch {
1467
1994
  }
1468
1995
  }
1469
- var MENUBAR_BINARY = join(CONFIG_DIR, "VectorMenuBar");
1470
- var MENUBAR_SWIFT_URL = "https://raw.githubusercontent.com/xrehpicx/vector/main/cli/macos/VectorMenuBar.swift";
1471
- async function ensureMenuBarBinary() {
1472
- if (platform() !== "darwin") return null;
1473
- if (existsSync(MENUBAR_BINARY)) return MENUBAR_BINARY;
1996
+ var MENUBAR_PID_FILE = join2(CONFIG_DIR, "menubar.pid");
1997
+ function removeLegacyMenuBarLaunchAgent() {
1998
+ if (platform() !== "darwin" || !existsSync2(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
1999
+ return;
2000
+ }
2001
+ try {
2002
+ execSync2(`launchctl unload ${LEGACY_MENUBAR_LAUNCHAGENT_PLIST}`, {
2003
+ stdio: "pipe"
2004
+ });
2005
+ } catch {
2006
+ }
1474
2007
  try {
1475
- execSync("which swiftc", { stdio: "pipe" });
2008
+ unlinkSync(LEGACY_MENUBAR_LAUNCHAGENT_PLIST);
1476
2009
  } catch {
2010
+ }
2011
+ }
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() {
2027
+ const candidates = [
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) {
1477
2040
  return null;
1478
2041
  }
1479
- const localSource = join(
1480
- process.cwd(),
1481
- "cli",
1482
- "macos",
1483
- "VectorMenuBar.swift"
1484
- );
1485
- let swiftSource;
1486
- if (existsSync(localSource)) {
1487
- swiftSource = localSource;
1488
- } else {
1489
- const downloadPath = join(CONFIG_DIR, "VectorMenuBar.swift");
1490
- try {
1491
- execSync(`curl -fsSL "${MENUBAR_SWIFT_URL}" -o "${downloadPath}"`, {
1492
- stdio: "pipe",
1493
- timeout: 15e3
1494
- });
1495
- swiftSource = downloadPath;
1496
- } catch {
1497
- return null;
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
+ )
2063
+ ];
2064
+ for (const p of candidates) {
2065
+ if (existsSync2(p)) {
2066
+ return p;
1498
2067
  }
1499
2068
  }
2069
+ return null;
2070
+ }
2071
+ function isKnownMenuBarProcess(pid) {
1500
2072
  try {
1501
- execSync(
1502
- `swiftc -o "${MENUBAR_BINARY}" "${swiftSource}" -framework AppKit`,
1503
- { stdio: "pipe", timeout: 3e4 }
1504
- );
1505
- const assetsSource = join(swiftSource, "..", "assets");
1506
- const assetsDest = join(CONFIG_DIR, "assets");
1507
- if (existsSync(assetsSource)) {
1508
- mkdirSync(assetsDest, { recursive: true });
1509
- for (const f of ["vector-menubar.png", "vector-menubar@2x.png"]) {
1510
- const src = join(assetsSource, f);
1511
- if (existsSync(src))
1512
- writeFileSync(join(assetsDest, f), readFileSync(src));
2073
+ const command = execSync2(`ps -p ${pid} -o args=`, {
2074
+ encoding: "utf-8",
2075
+ timeout: 3e3
2076
+ });
2077
+ return command.includes("menubar.js") || command.includes("menubar.ts") || command.includes("VectorMenuBar");
2078
+ } catch {
2079
+ return false;
2080
+ }
2081
+ }
2082
+ function killExistingMenuBar() {
2083
+ if (existsSync2(MENUBAR_PID_FILE)) {
2084
+ try {
2085
+ const pid = Number(readFileSync2(MENUBAR_PID_FILE, "utf-8").trim());
2086
+ if (Number.isFinite(pid) && pid > 0 && isKnownMenuBarProcess(pid)) {
2087
+ process.kill(pid, "SIGTERM");
1513
2088
  }
2089
+ } catch {
2090
+ }
2091
+ try {
2092
+ unlinkSync(MENUBAR_PID_FILE);
2093
+ } catch {
1514
2094
  }
1515
- return MENUBAR_BINARY;
1516
- } catch {
1517
- return null;
1518
2095
  }
1519
2096
  }
1520
2097
  async function launchMenuBar() {
1521
2098
  if (platform() !== "darwin") return;
1522
- const binary = await ensureMenuBarBinary();
1523
- if (!binary) return;
2099
+ removeLegacyMenuBarLaunchAgent();
2100
+ const executable = findMenuBarExecutable();
2101
+ const cliInvocation = getCurrentCliInvocation();
2102
+ if (!executable || !cliInvocation) return;
2103
+ killExistingMenuBar();
1524
2104
  try {
1525
2105
  const { spawn: spawnChild } = await import("child_process");
1526
- const child = spawnChild(binary, [], { detached: true, stdio: "ignore" });
2106
+ const child = spawnChild(executable, [], {
2107
+ detached: true,
2108
+ stdio: "ignore",
2109
+ env: {
2110
+ ...process.env,
2111
+ VECTOR_CLI_COMMAND: cliInvocation[0],
2112
+ VECTOR_CLI_ARGS_JSON: JSON.stringify(cliInvocation.slice(1))
2113
+ }
2114
+ });
1527
2115
  child.unref();
1528
- console.log("Menu bar started.");
2116
+ if (child.pid) {
2117
+ writeFileSync(MENUBAR_PID_FILE, String(child.pid));
2118
+ }
1529
2119
  } catch {
1530
2120
  }
1531
2121
  }
2122
+ function stopMenuBar() {
2123
+ killExistingMenuBar();
2124
+ }
1532
2125
  function getBridgeStatus() {
1533
2126
  const config = loadBridgeConfig();
1534
2127
  if (!config) return { configured: false, running: false, starting: false };
1535
2128
  let running = false;
1536
2129
  let starting = false;
1537
2130
  let pid;
1538
- if (existsSync(PID_FILE)) {
1539
- const pidStr = readFileSync(PID_FILE, "utf-8").trim();
2131
+ if (existsSync2(PID_FILE)) {
2132
+ const pidStr = readFileSync2(PID_FILE, "utf-8").trim();
1540
2133
  pid = Number(pidStr);
1541
2134
  try {
1542
2135
  process.kill(pid, 0);
@@ -1546,22 +2139,20 @@ function getBridgeStatus() {
1546
2139
  }
1547
2140
  }
1548
2141
  if (!running && platform() === "darwin") {
1549
- try {
1550
- const result = execSync(
1551
- `launchctl list ${LAUNCHAGENT_LABEL} 2>/dev/null`,
1552
- { encoding: "utf-8", timeout: 3e3 }
1553
- );
1554
- if (result.includes(LAUNCHAGENT_LABEL)) {
1555
- starting = true;
1556
- }
1557
- } catch {
1558
- }
2142
+ starting = runLaunchctl(["print", `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`]) || runLaunchctl(["list", LAUNCHAGENT_LABEL]);
1559
2143
  }
1560
2144
  return { configured: true, running, starting, pid, config };
1561
2145
  }
1562
- function stopBridge() {
1563
- if (!existsSync(PID_FILE)) return false;
1564
- 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());
1565
2156
  try {
1566
2157
  process.kill(pid, "SIGTERM");
1567
2158
  return true;
@@ -2017,7 +2608,7 @@ var program = new Command();
2017
2608
  function readPackageVersionSync() {
2018
2609
  try {
2019
2610
  const dir = import.meta.dirname ?? dirname(fileURLToPath2(import.meta.url));
2020
- const raw = readFileSync2(join2(dir, "..", "package.json"), "utf8");
2611
+ const raw = readFileSync3(join3(dir, "..", "package.json"), "utf8");
2021
2612
  return JSON.parse(raw).version ?? "unknown";
2022
2613
  } catch {
2023
2614
  return "unknown";
@@ -3564,6 +4155,7 @@ folderCommand.command("delete <folderId>").action(async (folderId, _options, com
3564
4155
  });
3565
4156
  printOutput(result, runtime.json);
3566
4157
  });
4158
+ var VECTOR_HOME = process.env.VECTOR_HOME?.trim() || `${process.env.HOME ?? "~"}/.vector`;
3567
4159
  var serviceCommand = program.command("service").description("Manage the local bridge service");
3568
4160
  serviceCommand.command("start").description("Start the bridge service via LaunchAgent (macOS) or foreground").action(async (_options, command) => {
3569
4161
  const existing = getBridgeStatus();
@@ -3588,7 +4180,7 @@ serviceCommand.command("start").description("Start the bridge service via Launch
3588
4180
  s.stop("Failed");
3589
4181
  throw new Error("Not logged in. Run `vcli auth login` first.");
3590
4182
  }
3591
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4183
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
3592
4184
  s.stop(`Device registered: ${config.displayName}`);
3593
4185
  }
3594
4186
  if (osPlatform() === "darwin") {
@@ -3596,7 +4188,6 @@ serviceCommand.command("start").description("Start the bridge service via Launch
3596
4188
  const vcliPath = process.argv[1] ?? "vcli";
3597
4189
  installLaunchAgent(vcliPath);
3598
4190
  loadLaunchAgent();
3599
- await launchMenuBar();
3600
4191
  s.stop("Bridge service started.");
3601
4192
  } else {
3602
4193
  console.log(
@@ -3618,19 +4209,25 @@ serviceCommand.command("run").description("Run the bridge service in the foregro
3618
4209
  );
3619
4210
  const user = await runQuery(client, api.users.currentUser);
3620
4211
  if (!user) throw new Error("Not logged in. Run `vcli auth login` first.");
3621
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4212
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
4213
+ }
4214
+ if (osPlatform() === "darwin") {
4215
+ await launchMenuBar();
3622
4216
  }
3623
4217
  const bridge = new BridgeService(config);
3624
4218
  await bridge.run();
3625
4219
  });
3626
- 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;
3627
4222
  if (osPlatform() === "darwin") {
3628
- unloadLaunchAgent();
4223
+ unloaded = unloadLaunchAgent();
3629
4224
  }
3630
- if (stopBridge()) {
4225
+ if (stopBridge() || unloaded) {
3631
4226
  console.log("Bridge stopped.");
3632
4227
  } else if (osPlatform() !== "darwin") {
3633
4228
  console.log("Bridge is not running.");
4229
+ } else {
4230
+ console.log("Bridge is not running.");
3634
4231
  }
3635
4232
  });
3636
4233
  serviceCommand.command("status").description("Show bridge service status").action((_options, command) => {
@@ -3646,7 +4243,7 @@ serviceCommand.command("status").description("Show bridge service status").actio
3646
4243
  console.log(` User: ${status.config.userId}`);
3647
4244
  const statusLabel = status.running ? `Running (PID ${status.pid})` : status.starting ? "Starting..." : "Not running";
3648
4245
  console.log(` Status: ${statusLabel}`);
3649
- console.log(` Config: ~/.vector/bridge.json`);
4246
+ console.log(` Config: ${VECTOR_HOME}/bridge.json`);
3650
4247
  });
3651
4248
  serviceCommand.command("install").description("Install the bridge as a system service (macOS LaunchAgent)").action(async (_options, command) => {
3652
4249
  if (osPlatform() !== "darwin") {
@@ -3671,7 +4268,7 @@ serviceCommand.command("install").description("Install the bridge as a system se
3671
4268
  s.stop("Failed");
3672
4269
  throw new Error("Not logged in. Run `vcli auth login` first.");
3673
4270
  }
3674
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4271
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
3675
4272
  s.stop(`Device registered: ${config.displayName}`);
3676
4273
  }
3677
4274
  s.start("Installing LaunchAgent...");
@@ -3681,30 +4278,27 @@ serviceCommand.command("install").description("Install the bridge as a system se
3681
4278
  s.start("Starting bridge service...");
3682
4279
  loadLaunchAgent();
3683
4280
  s.stop("Bridge service started");
3684
- s.start("Launching menu bar...");
3685
- await launchMenuBar();
3686
- s.stop("Menu bar ready");
3687
4281
  console.log("");
3688
4282
  console.log(
3689
4283
  "Bridge installed and running. Will start automatically on login."
3690
4284
  );
3691
4285
  });
3692
4286
  serviceCommand.command("uninstall").description("Stop the bridge and uninstall the system service").action(() => {
3693
- stopBridge();
4287
+ stopBridge({ includeMenuBar: true });
3694
4288
  uninstallLaunchAgent();
4289
+ stopMenuBar();
3695
4290
  console.log("Bridge stopped and service uninstalled.");
3696
4291
  });
3697
4292
  serviceCommand.command("logs").description("Show bridge service logs").action(async () => {
3698
4293
  const fs7 = await import("fs");
3699
- const os2 = await import("os");
3700
4294
  const p = await import("path");
3701
- const logPath = p.join(os2.homedir(), ".vector", "bridge.log");
4295
+ const logPath = p.join(VECTOR_HOME, "bridge.log");
3702
4296
  if (fs7.existsSync(logPath)) {
3703
4297
  const content = fs7.readFileSync(logPath, "utf-8");
3704
4298
  const lines = content.split("\n");
3705
4299
  console.log(lines.slice(-50).join("\n"));
3706
4300
  } else {
3707
- console.log("No log file found at ~/.vector/bridge.log");
4301
+ console.log(`No log file found at ${logPath}`);
3708
4302
  }
3709
4303
  });
3710
4304
  serviceCommand.command("enable").description("Enable bridge to start at login (macOS LaunchAgent)").action(async () => {
@@ -3719,6 +4313,7 @@ serviceCommand.command("enable").description("Enable bridge to start at login (m
3719
4313
  });
3720
4314
  serviceCommand.command("disable").description("Disable bridge from starting at login").action(() => {
3721
4315
  uninstallLaunchAgent();
4316
+ stopMenuBar();
3722
4317
  console.log("Bridge will no longer start at login.");
3723
4318
  });
3724
4319
  var bridgeCommand = program.command("bridge").description("Start/stop the local agent bridge");
@@ -3734,7 +4329,7 @@ bridgeCommand.command("start").description("Register device, install service, an
3734
4329
  );
3735
4330
  const user = await runQuery(client, api.users.currentUser);
3736
4331
  if (!user) throw new Error("Not logged in. Run `vcli auth login` first.");
3737
- config = await setupBridgeDevice(runtime.convexUrl, user._id);
4332
+ config = await setupBridgeDevice(client, runtime.convexUrl, user._id);
3738
4333
  console.log(
3739
4334
  `Device registered: ${config.displayName} (${config.deviceId})`
3740
4335
  );
@@ -3753,10 +4348,12 @@ bridgeCommand.command("start").description("Register device, install service, an
3753
4348
  }
3754
4349
  });
3755
4350
  bridgeCommand.command("stop").description("Stop the bridge service").action(() => {
4351
+ let unloaded = false;
3756
4352
  if (osPlatform() === "darwin") {
3757
4353
  uninstallLaunchAgent();
4354
+ unloaded = true;
3758
4355
  }
3759
- if (stopBridge()) {
4356
+ if (stopBridge() || unloaded) {
3760
4357
  console.log("Bridge stopped.");
3761
4358
  } else {
3762
4359
  console.log("Bridge is not running.");