@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
|
|
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,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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1129
|
-
|
|
1130
|
-
|
|
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
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
1459
|
+
return {
|
|
1460
|
+
repoRoot: repoRoot || void 0,
|
|
1461
|
+
branch: branch || void 0
|
|
1462
|
+
};
|
|
1191
1463
|
} catch {
|
|
1192
1464
|
return {};
|
|
1193
1465
|
}
|
|
1194
1466
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
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");
|
|
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 =
|
|
1561
|
+
const processes = discoverAttachableSessions();
|
|
1263
1562
|
for (const proc of processes) {
|
|
1264
1563
|
try {
|
|
1265
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
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 (!
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
1402
|
-
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
1914
|
+
${environmentVariables}
|
|
1403
1915
|
</dict>
|
|
1404
1916
|
</dict>
|
|
1405
1917
|
</plist>`;
|
|
1406
|
-
|
|
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
|
-
|
|
1445
|
-
|
|
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
|
-
|
|
1450
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
);
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
-
|
|
1523
|
-
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
1539
|
-
const pidStr =
|
|
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
|
-
|
|
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 (
|
|
1564
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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(
|
|
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(
|
|
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.");
|