@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 +800 -188
- package/dist/index.js.map +1 -1
- package/native/VectorMenuBar.app/Contents/Info.plist +26 -0
- package/native/VectorMenuBar.app/Contents/MacOS/VectorMenuBar +0 -0
- package/package.json +4 -5
- package/scripts/build-menubar-app.js +89 -0
- package/dist/menubar.js +0 -242
- package/dist/menubar.js.map +0 -1
- package/scripts/postinstall.js +0 -106
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
|
|
739
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
740
740
|
import { readFile as readFile2 } from "fs/promises";
|
|
741
|
-
import { dirname, extname, join as
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
var
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
);
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
-
|
|
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
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
1459
|
+
return {
|
|
1460
|
+
repoRoot: repoRoot || void 0,
|
|
1461
|
+
branch: branch || void 0
|
|
1462
|
+
};
|
|
1196
1463
|
} catch {
|
|
1197
1464
|
return {};
|
|
1198
1465
|
}
|
|
1199
1466
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
console.
|
|
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 =
|
|
1561
|
+
const processes = discoverAttachableSessions();
|
|
1268
1562
|
for (const proc of processes) {
|
|
1269
1563
|
try {
|
|
1270
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
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 (!
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
1420
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1996
|
+
var MENUBAR_PID_FILE = join2(CONFIG_DIR, "menubar.pid");
|
|
1446
1997
|
function removeLegacyMenuBarLaunchAgent() {
|
|
1447
|
-
if (platform() !== "darwin" || !
|
|
1998
|
+
if (platform() !== "darwin" || !existsSync2(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
|
|
1448
1999
|
return;
|
|
1449
2000
|
}
|
|
1450
2001
|
try {
|
|
1451
|
-
|
|
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
|
|
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
|
-
|
|
1464
|
-
|
|
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 (
|
|
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 =
|
|
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 (
|
|
2083
|
+
if (existsSync2(MENUBAR_PID_FILE)) {
|
|
1484
2084
|
try {
|
|
1485
|
-
const pid = Number(
|
|
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
|
|
1501
|
-
|
|
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(
|
|
2106
|
+
const child = spawnChild(executable, [], {
|
|
1506
2107
|
detached: true,
|
|
1507
2108
|
stdio: "ignore",
|
|
1508
|
-
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 (
|
|
1524
|
-
const pidStr =
|
|
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
|
-
|
|
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
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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(
|
|
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(
|
|
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.");
|