@rehpic/vcli 0.1.0-beta.34.1 → 0.1.0-beta.35.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 +621 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
4
10
|
var __esm = (fn, res) => function __init() {
|
|
5
11
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
12
|
};
|
|
@@ -436,16 +442,16 @@ function detectArchBinary(binary) {
|
|
|
436
442
|
}
|
|
437
443
|
return archBinary;
|
|
438
444
|
}
|
|
439
|
-
function detectPlatformBinary({ [
|
|
445
|
+
function detectPlatformBinary({ [platform2]: platformBinary }, { wsl } = {}) {
|
|
440
446
|
if (wsl && is_wsl_default) {
|
|
441
447
|
return detectArchBinary(wsl);
|
|
442
448
|
}
|
|
443
449
|
if (!platformBinary) {
|
|
444
|
-
throw new Error(`${
|
|
450
|
+
throw new Error(`${platform2} is not supported`);
|
|
445
451
|
}
|
|
446
452
|
return detectArchBinary(platformBinary);
|
|
447
453
|
}
|
|
448
|
-
var fallbackAttemptSymbol,
|
|
454
|
+
var fallbackAttemptSymbol, __dirname2, localXdgOpenPath, platform2, arch, tryEachApp, baseOpen, open, openApp, apps, open_default;
|
|
449
455
|
var init_open = __esm({
|
|
450
456
|
"../../node_modules/.pnpm/open@11.0.0/node_modules/open/index.js"() {
|
|
451
457
|
"use strict";
|
|
@@ -456,9 +462,9 @@ var init_open = __esm({
|
|
|
456
462
|
init_is_inside_container();
|
|
457
463
|
init_is_in_ssh();
|
|
458
464
|
fallbackAttemptSymbol = Symbol("fallbackAttempt");
|
|
459
|
-
|
|
460
|
-
localXdgOpenPath = path2.join(
|
|
461
|
-
({ platform, arch } = process8);
|
|
465
|
+
__dirname2 = import.meta.url ? path2.dirname(fileURLToPath(import.meta.url)) : "";
|
|
466
|
+
localXdgOpenPath = path2.join(__dirname2, "xdg-open");
|
|
467
|
+
({ platform: platform2, arch } = process8);
|
|
462
468
|
tryEachApp = async (apps2, opener) => {
|
|
463
469
|
if (apps2.length === 0) {
|
|
464
470
|
return;
|
|
@@ -555,7 +561,7 @@ var init_open = __esm({
|
|
|
555
561
|
if (is_wsl_default && !isInsideContainer() && !is_in_ssh_default && !app) {
|
|
556
562
|
shouldUseWindowsInWsl = await canAccessPowerShell();
|
|
557
563
|
}
|
|
558
|
-
if (
|
|
564
|
+
if (platform2 === "darwin") {
|
|
559
565
|
command = "open";
|
|
560
566
|
if (options.wait) {
|
|
561
567
|
cliArguments.push("--wait-apps");
|
|
@@ -569,7 +575,7 @@ var init_open = __esm({
|
|
|
569
575
|
if (app) {
|
|
570
576
|
cliArguments.push("-a", app);
|
|
571
577
|
}
|
|
572
|
-
} else if (
|
|
578
|
+
} else if (platform2 === "win32" || shouldUseWindowsInWsl) {
|
|
573
579
|
command = await powerShellPath2();
|
|
574
580
|
cliArguments.push(...executePowerShell.argumentsPrefix);
|
|
575
581
|
if (!is_wsl_default) {
|
|
@@ -602,14 +608,14 @@ var init_open = __esm({
|
|
|
602
608
|
if (app) {
|
|
603
609
|
command = app;
|
|
604
610
|
} else {
|
|
605
|
-
const isBundled = !
|
|
611
|
+
const isBundled = !__dirname2 || __dirname2 === "/";
|
|
606
612
|
let exeLocalXdgOpen = false;
|
|
607
613
|
try {
|
|
608
614
|
await fs6.access(localXdgOpenPath, fsConstants3.X_OK);
|
|
609
615
|
exeLocalXdgOpen = true;
|
|
610
616
|
} catch {
|
|
611
617
|
}
|
|
612
|
-
const useSystemXdgOpen = process8.versions.electron ?? (
|
|
618
|
+
const useSystemXdgOpen = process8.versions.electron ?? (platform2 === "android" || isBundled || !exeLocalXdgOpen);
|
|
613
619
|
command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
|
|
614
620
|
}
|
|
615
621
|
if (appArguments.length > 0) {
|
|
@@ -620,7 +626,7 @@ var init_open = __esm({
|
|
|
620
626
|
childProcessOptions.detached = true;
|
|
621
627
|
}
|
|
622
628
|
}
|
|
623
|
-
if (
|
|
629
|
+
if (platform2 === "darwin" && appArguments.length > 0) {
|
|
624
630
|
cliArguments.push("--args", ...appArguments);
|
|
625
631
|
}
|
|
626
632
|
if (options.target) {
|
|
@@ -736,9 +742,9 @@ var init_open = __esm({
|
|
|
736
742
|
});
|
|
737
743
|
|
|
738
744
|
// ../../src/cli/index.ts
|
|
739
|
-
import { readFileSync } from "fs";
|
|
745
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
740
746
|
import { readFile as readFile2 } from "fs/promises";
|
|
741
|
-
import { dirname, extname, join } from "path";
|
|
747
|
+
import { dirname, extname, join as join2 } from "path";
|
|
742
748
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
743
749
|
import { config as loadEnv } from "dotenv";
|
|
744
750
|
import { Command } from "commander";
|
|
@@ -1094,7 +1100,413 @@ function createEmptySession() {
|
|
|
1094
1100
|
};
|
|
1095
1101
|
}
|
|
1096
1102
|
|
|
1103
|
+
// ../../src/cli/bridge-service.ts
|
|
1104
|
+
import { ConvexHttpClient as ConvexHttpClient2 } from "convex/browser";
|
|
1105
|
+
import { execSync } from "child_process";
|
|
1106
|
+
import {
|
|
1107
|
+
existsSync,
|
|
1108
|
+
mkdirSync,
|
|
1109
|
+
readFileSync,
|
|
1110
|
+
writeFileSync,
|
|
1111
|
+
unlinkSync
|
|
1112
|
+
} from "fs";
|
|
1113
|
+
import { homedir as homedir2, hostname, platform } from "os";
|
|
1114
|
+
import { join } from "path";
|
|
1115
|
+
import { randomUUID } from "crypto";
|
|
1116
|
+
var CONFIG_DIR = join(homedir2(), ".vector");
|
|
1117
|
+
var BRIDGE_CONFIG_FILE = join(CONFIG_DIR, "bridge.json");
|
|
1118
|
+
var PID_FILE = join(CONFIG_DIR, "bridge.pid");
|
|
1119
|
+
var LIVE_ACTIVITIES_CACHE = join(CONFIG_DIR, "live-activities.json");
|
|
1120
|
+
var LAUNCHAGENT_DIR = join(homedir2(), "Library", "LaunchAgents");
|
|
1121
|
+
var LAUNCHAGENT_PLIST = join(LAUNCHAGENT_DIR, "com.vector.bridge.plist");
|
|
1122
|
+
var LAUNCHAGENT_LABEL = "com.vector.bridge";
|
|
1123
|
+
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
1124
|
+
var COMMAND_POLL_INTERVAL_MS = 5e3;
|
|
1125
|
+
var PROCESS_DISCOVERY_INTERVAL_MS = 6e4;
|
|
1126
|
+
function loadBridgeConfig() {
|
|
1127
|
+
if (!existsSync(BRIDGE_CONFIG_FILE)) return null;
|
|
1128
|
+
try {
|
|
1129
|
+
return JSON.parse(readFileSync(BRIDGE_CONFIG_FILE, "utf-8"));
|
|
1130
|
+
} catch {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function saveBridgeConfig(config) {
|
|
1135
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1136
|
+
writeFileSync(BRIDGE_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
1137
|
+
}
|
|
1138
|
+
function discoverLocalProcesses() {
|
|
1139
|
+
const processes = [];
|
|
1140
|
+
const patterns = [
|
|
1141
|
+
{
|
|
1142
|
+
grep: "[c]laude",
|
|
1143
|
+
provider: "claude_code",
|
|
1144
|
+
label: "Claude",
|
|
1145
|
+
prefix: "claude"
|
|
1146
|
+
},
|
|
1147
|
+
{ grep: "[c]odex", provider: "codex", label: "Codex", prefix: "codex" }
|
|
1148
|
+
];
|
|
1149
|
+
for (const { grep, provider, label, prefix } of patterns) {
|
|
1150
|
+
try {
|
|
1151
|
+
const ps = execSync(
|
|
1152
|
+
`ps aux | grep -E '${grep}' | grep -v vector-bridge | grep -v grep`,
|
|
1153
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
1154
|
+
);
|
|
1155
|
+
for (const line of ps.trim().split("\n").filter(Boolean)) {
|
|
1156
|
+
const pid = line.split(/\s+/)[1];
|
|
1157
|
+
if (!pid) continue;
|
|
1158
|
+
let cwd;
|
|
1159
|
+
try {
|
|
1160
|
+
cwd = execSync(
|
|
1161
|
+
`lsof -p ${pid} 2>/dev/null | grep cwd | awk '{print $NF}'`,
|
|
1162
|
+
{ encoding: "utf-8", timeout: 3e3 }
|
|
1163
|
+
).trim() || void 0;
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
const gitInfo = cwd ? getGitInfo(cwd) : {};
|
|
1167
|
+
processes.push({
|
|
1168
|
+
provider,
|
|
1169
|
+
providerLabel: label,
|
|
1170
|
+
localProcessId: pid,
|
|
1171
|
+
sessionKey: `${prefix}-${pid}`,
|
|
1172
|
+
cwd,
|
|
1173
|
+
...gitInfo,
|
|
1174
|
+
mode: "observed",
|
|
1175
|
+
status: "observed",
|
|
1176
|
+
supportsInboundMessages: false
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return processes;
|
|
1183
|
+
}
|
|
1184
|
+
function getGitInfo(cwd) {
|
|
1185
|
+
try {
|
|
1186
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
1187
|
+
encoding: "utf-8",
|
|
1188
|
+
cwd,
|
|
1189
|
+
timeout: 3e3
|
|
1190
|
+
}).trim();
|
|
1191
|
+
const repoRoot = execSync("git rev-parse --show-toplevel", {
|
|
1192
|
+
encoding: "utf-8",
|
|
1193
|
+
cwd,
|
|
1194
|
+
timeout: 3e3
|
|
1195
|
+
}).trim();
|
|
1196
|
+
return { branch, repoRoot };
|
|
1197
|
+
} catch {
|
|
1198
|
+
return {};
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function generateReply(userMessage) {
|
|
1202
|
+
const lower = userMessage.toLowerCase().trim();
|
|
1203
|
+
if (["hey", "hi", "hello"].includes(lower)) {
|
|
1204
|
+
return "Hey! I'm running on your local machine via the Vector bridge. What would you like me to work on?";
|
|
1205
|
+
}
|
|
1206
|
+
if (lower.includes("status") || lower.includes("progress")) {
|
|
1207
|
+
return "I'm making good progress. Currently reviewing the changes and running tests.";
|
|
1208
|
+
}
|
|
1209
|
+
if (lower.includes("stop") || lower.includes("cancel")) {
|
|
1210
|
+
return "Understood \u2014 wrapping up the current step.";
|
|
1211
|
+
}
|
|
1212
|
+
return `Got it \u2014 "${userMessage}". I'll incorporate that into my current work.`;
|
|
1213
|
+
}
|
|
1214
|
+
var BridgeService = class {
|
|
1215
|
+
constructor(config) {
|
|
1216
|
+
this.timers = [];
|
|
1217
|
+
this.config = config;
|
|
1218
|
+
this.client = new ConvexHttpClient2(config.convexUrl);
|
|
1219
|
+
}
|
|
1220
|
+
async heartbeat() {
|
|
1221
|
+
await this.client.mutation(api.agentBridge.bridgePublic.heartbeat, {
|
|
1222
|
+
deviceId: this.config.deviceId,
|
|
1223
|
+
deviceSecret: this.config.deviceSecret
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
async pollCommands() {
|
|
1227
|
+
const commands = await this.client.query(
|
|
1228
|
+
api.agentBridge.bridgePublic.getPendingCommands,
|
|
1229
|
+
{
|
|
1230
|
+
deviceId: this.config.deviceId,
|
|
1231
|
+
deviceSecret: this.config.deviceSecret
|
|
1232
|
+
}
|
|
1233
|
+
);
|
|
1234
|
+
if (commands.length > 0) {
|
|
1235
|
+
console.log(`[${ts()}] ${commands.length} pending command(s)`);
|
|
1236
|
+
}
|
|
1237
|
+
for (const cmd of commands) {
|
|
1238
|
+
await this.handleCommand(cmd);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
async handleCommand(cmd) {
|
|
1242
|
+
console.log(` ${cmd.kind}: ${cmd._id}`);
|
|
1243
|
+
if (cmd.kind === "message" && cmd.liveActivityId) {
|
|
1244
|
+
const payload = cmd.payload;
|
|
1245
|
+
const body = payload?.body ?? "";
|
|
1246
|
+
console.log(` > "${body}"`);
|
|
1247
|
+
const reply = generateReply(body);
|
|
1248
|
+
await this.client.mutation(
|
|
1249
|
+
api.agentBridge.bridgePublic.postAgentMessage,
|
|
1250
|
+
{
|
|
1251
|
+
deviceId: this.config.deviceId,
|
|
1252
|
+
deviceSecret: this.config.deviceSecret,
|
|
1253
|
+
liveActivityId: cmd.liveActivityId,
|
|
1254
|
+
role: "assistant",
|
|
1255
|
+
body: reply
|
|
1256
|
+
}
|
|
1257
|
+
);
|
|
1258
|
+
console.log(` < "${reply.slice(0, 60)}..."`);
|
|
1259
|
+
}
|
|
1260
|
+
await this.client.mutation(api.agentBridge.bridgePublic.completeCommand, {
|
|
1261
|
+
deviceId: this.config.deviceId,
|
|
1262
|
+
deviceSecret: this.config.deviceSecret,
|
|
1263
|
+
commandId: cmd._id,
|
|
1264
|
+
status: "delivered"
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
async reportProcesses() {
|
|
1268
|
+
const processes = discoverLocalProcesses();
|
|
1269
|
+
for (const proc of processes) {
|
|
1270
|
+
try {
|
|
1271
|
+
await this.client.mutation(api.agentBridge.bridgePublic.reportProcess, {
|
|
1272
|
+
deviceId: this.config.deviceId,
|
|
1273
|
+
deviceSecret: this.config.deviceSecret,
|
|
1274
|
+
...proc
|
|
1275
|
+
});
|
|
1276
|
+
} catch {
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
if (processes.length > 0) {
|
|
1280
|
+
console.log(`[${ts()}] Discovered ${processes.length} local process(es)`);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
async refreshLiveActivities() {
|
|
1284
|
+
try {
|
|
1285
|
+
const activities = await this.client.query(
|
|
1286
|
+
api.agentBridge.bridgePublic.getDeviceLiveActivities,
|
|
1287
|
+
{
|
|
1288
|
+
deviceId: this.config.deviceId,
|
|
1289
|
+
deviceSecret: this.config.deviceSecret
|
|
1290
|
+
}
|
|
1291
|
+
);
|
|
1292
|
+
writeFileSync(LIVE_ACTIVITIES_CACHE, JSON.stringify(activities, null, 2));
|
|
1293
|
+
} catch {
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
async run() {
|
|
1297
|
+
console.log("Vector Bridge Service");
|
|
1298
|
+
console.log(
|
|
1299
|
+
` Device: ${this.config.displayName} (${this.config.deviceId})`
|
|
1300
|
+
);
|
|
1301
|
+
console.log(` Convex: ${this.config.convexUrl}`);
|
|
1302
|
+
console.log(` PID: ${process.pid}`);
|
|
1303
|
+
console.log("");
|
|
1304
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1305
|
+
writeFileSync(PID_FILE, String(process.pid));
|
|
1306
|
+
await this.heartbeat();
|
|
1307
|
+
await this.reportProcesses();
|
|
1308
|
+
await this.refreshLiveActivities();
|
|
1309
|
+
console.log(`[${ts()}] Service running. Ctrl+C to stop.
|
|
1310
|
+
`);
|
|
1311
|
+
this.timers.push(
|
|
1312
|
+
setInterval(() => {
|
|
1313
|
+
this.heartbeat().catch(
|
|
1314
|
+
(e) => console.error(`[${ts()}] Heartbeat error:`, e.message)
|
|
1315
|
+
);
|
|
1316
|
+
this.refreshLiveActivities().catch(() => {
|
|
1317
|
+
});
|
|
1318
|
+
}, HEARTBEAT_INTERVAL_MS)
|
|
1319
|
+
);
|
|
1320
|
+
this.timers.push(
|
|
1321
|
+
setInterval(() => {
|
|
1322
|
+
this.pollCommands().catch(
|
|
1323
|
+
(e) => console.error(`[${ts()}] Command poll error:`, e.message)
|
|
1324
|
+
);
|
|
1325
|
+
}, COMMAND_POLL_INTERVAL_MS)
|
|
1326
|
+
);
|
|
1327
|
+
this.timers.push(
|
|
1328
|
+
setInterval(() => {
|
|
1329
|
+
this.reportProcesses().catch(
|
|
1330
|
+
(e) => console.error(`[${ts()}] Discovery error:`, e.message)
|
|
1331
|
+
);
|
|
1332
|
+
}, PROCESS_DISCOVERY_INTERVAL_MS)
|
|
1333
|
+
);
|
|
1334
|
+
const shutdown = () => {
|
|
1335
|
+
console.log(`
|
|
1336
|
+
[${ts()}] Shutting down...`);
|
|
1337
|
+
for (const t of this.timers) clearInterval(t);
|
|
1338
|
+
try {
|
|
1339
|
+
unlinkSync(PID_FILE);
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
process.exit(0);
|
|
1343
|
+
};
|
|
1344
|
+
process.on("SIGINT", shutdown);
|
|
1345
|
+
process.on("SIGTERM", shutdown);
|
|
1346
|
+
await new Promise(() => {
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
async function setupBridgeDevice(convexUrl, userId) {
|
|
1351
|
+
const client = new ConvexHttpClient2(convexUrl);
|
|
1352
|
+
const deviceKey = `${hostname()}-${randomUUID().slice(0, 8)}`;
|
|
1353
|
+
const deviceSecret = randomUUID();
|
|
1354
|
+
const displayName = `${process.env.USER ?? "user"}'s ${platform() === "darwin" ? "Mac" : "machine"}`;
|
|
1355
|
+
const result = await client.mutation(
|
|
1356
|
+
api.agentBridge.bridgePublic.setupDevice,
|
|
1357
|
+
{
|
|
1358
|
+
userId,
|
|
1359
|
+
deviceKey,
|
|
1360
|
+
deviceSecret,
|
|
1361
|
+
displayName,
|
|
1362
|
+
hostname: hostname(),
|
|
1363
|
+
platform: platform(),
|
|
1364
|
+
cliVersion: "0.1.0",
|
|
1365
|
+
capabilities: ["codex", "claude_code"]
|
|
1366
|
+
}
|
|
1367
|
+
);
|
|
1368
|
+
const config = {
|
|
1369
|
+
deviceId: result.deviceId,
|
|
1370
|
+
deviceKey,
|
|
1371
|
+
deviceSecret,
|
|
1372
|
+
userId,
|
|
1373
|
+
displayName,
|
|
1374
|
+
convexUrl,
|
|
1375
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1376
|
+
};
|
|
1377
|
+
saveBridgeConfig(config);
|
|
1378
|
+
return config;
|
|
1379
|
+
}
|
|
1380
|
+
function installLaunchAgent(vcliPath) {
|
|
1381
|
+
if (platform() !== "darwin") {
|
|
1382
|
+
console.error("LaunchAgent is macOS only. Use systemd on Linux.");
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1386
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1387
|
+
<plist version="1.0">
|
|
1388
|
+
<dict>
|
|
1389
|
+
<key>Label</key>
|
|
1390
|
+
<string>${LAUNCHAGENT_LABEL}</string>
|
|
1391
|
+
<key>ProgramArguments</key>
|
|
1392
|
+
<array>
|
|
1393
|
+
<string>${vcliPath}</string>
|
|
1394
|
+
<string>service</string>
|
|
1395
|
+
<string>start</string>
|
|
1396
|
+
</array>
|
|
1397
|
+
<key>RunAtLoad</key>
|
|
1398
|
+
<true/>
|
|
1399
|
+
<key>KeepAlive</key>
|
|
1400
|
+
<true/>
|
|
1401
|
+
<key>StandardOutPath</key>
|
|
1402
|
+
<string>${CONFIG_DIR}/bridge.log</string>
|
|
1403
|
+
<key>StandardErrorPath</key>
|
|
1404
|
+
<string>${CONFIG_DIR}/bridge.err.log</string>
|
|
1405
|
+
<key>EnvironmentVariables</key>
|
|
1406
|
+
<dict>
|
|
1407
|
+
<key>PATH</key>
|
|
1408
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
1409
|
+
</dict>
|
|
1410
|
+
</dict>
|
|
1411
|
+
</plist>`;
|
|
1412
|
+
const menuBarBinary = join(
|
|
1413
|
+
__dirname,
|
|
1414
|
+
"..",
|
|
1415
|
+
"..",
|
|
1416
|
+
"cli",
|
|
1417
|
+
"macos",
|
|
1418
|
+
"VectorMenuBar"
|
|
1419
|
+
);
|
|
1420
|
+
if (existsSync(menuBarBinary)) {
|
|
1421
|
+
const menuBarPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1422
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1423
|
+
<plist version="1.0">
|
|
1424
|
+
<dict>
|
|
1425
|
+
<key>Label</key>
|
|
1426
|
+
<string>com.vector.menubar</string>
|
|
1427
|
+
<key>ProgramArguments</key>
|
|
1428
|
+
<array>
|
|
1429
|
+
<string>${menuBarBinary}</string>
|
|
1430
|
+
</array>
|
|
1431
|
+
<key>RunAtLoad</key>
|
|
1432
|
+
<true/>
|
|
1433
|
+
<key>KeepAlive</key>
|
|
1434
|
+
<false/>
|
|
1435
|
+
</dict>
|
|
1436
|
+
</plist>`;
|
|
1437
|
+
const menuBarPlistPath = join(LAUNCHAGENT_DIR, "com.vector.menubar.plist");
|
|
1438
|
+
writeFileSync(menuBarPlistPath, menuBarPlist);
|
|
1439
|
+
try {
|
|
1440
|
+
execSync(`launchctl load ${menuBarPlistPath}`, { stdio: "pipe" });
|
|
1441
|
+
console.log("Menu bar helper installed.");
|
|
1442
|
+
} catch {
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (!existsSync(LAUNCHAGENT_DIR)) {
|
|
1446
|
+
mkdirSync(LAUNCHAGENT_DIR, { recursive: true });
|
|
1447
|
+
}
|
|
1448
|
+
writeFileSync(LAUNCHAGENT_PLIST, plist);
|
|
1449
|
+
console.log(`Installed LaunchAgent: ${LAUNCHAGENT_PLIST}`);
|
|
1450
|
+
}
|
|
1451
|
+
function loadLaunchAgent() {
|
|
1452
|
+
try {
|
|
1453
|
+
execSync(`launchctl load ${LAUNCHAGENT_PLIST}`, { stdio: "inherit" });
|
|
1454
|
+
console.log(
|
|
1455
|
+
"LaunchAgent loaded. Bridge will start automatically on login."
|
|
1456
|
+
);
|
|
1457
|
+
} catch {
|
|
1458
|
+
console.error("Failed to load LaunchAgent");
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function unloadLaunchAgent() {
|
|
1462
|
+
try {
|
|
1463
|
+
execSync(`launchctl unload ${LAUNCHAGENT_PLIST}`, { stdio: "inherit" });
|
|
1464
|
+
console.log("LaunchAgent unloaded.");
|
|
1465
|
+
} catch {
|
|
1466
|
+
console.error("Failed to unload LaunchAgent (may not be loaded)");
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
function uninstallLaunchAgent() {
|
|
1470
|
+
unloadLaunchAgent();
|
|
1471
|
+
try {
|
|
1472
|
+
unlinkSync(LAUNCHAGENT_PLIST);
|
|
1473
|
+
console.log("LaunchAgent removed.");
|
|
1474
|
+
} catch {
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function getBridgeStatus() {
|
|
1478
|
+
const config = loadBridgeConfig();
|
|
1479
|
+
if (!config) return { configured: false, running: false };
|
|
1480
|
+
let running = false;
|
|
1481
|
+
let pid;
|
|
1482
|
+
if (existsSync(PID_FILE)) {
|
|
1483
|
+
const pidStr = readFileSync(PID_FILE, "utf-8").trim();
|
|
1484
|
+
pid = Number(pidStr);
|
|
1485
|
+
try {
|
|
1486
|
+
process.kill(pid, 0);
|
|
1487
|
+
running = true;
|
|
1488
|
+
} catch {
|
|
1489
|
+
running = false;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return { configured: true, running, pid, config };
|
|
1493
|
+
}
|
|
1494
|
+
function stopBridge() {
|
|
1495
|
+
if (!existsSync(PID_FILE)) return false;
|
|
1496
|
+
const pid = Number(readFileSync(PID_FILE, "utf-8").trim());
|
|
1497
|
+
try {
|
|
1498
|
+
process.kill(pid, "SIGTERM");
|
|
1499
|
+
return true;
|
|
1500
|
+
} catch {
|
|
1501
|
+
return false;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function ts() {
|
|
1505
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1097
1508
|
// ../../src/cli/index.ts
|
|
1509
|
+
import { platform as osPlatform } from "os";
|
|
1098
1510
|
loadEnv({ path: ".env.local", override: false });
|
|
1099
1511
|
loadEnv({ path: ".env", override: false });
|
|
1100
1512
|
var cliApi = {
|
|
@@ -1537,7 +1949,7 @@ var program = new Command();
|
|
|
1537
1949
|
function readPackageVersionSync() {
|
|
1538
1950
|
try {
|
|
1539
1951
|
const dir = import.meta.dirname ?? dirname(fileURLToPath2(import.meta.url));
|
|
1540
|
-
const raw =
|
|
1952
|
+
const raw = readFileSync2(join2(dir, "..", "package.json"), "utf8");
|
|
1541
1953
|
return JSON.parse(raw).version ?? "unknown";
|
|
1542
1954
|
} catch {
|
|
1543
1955
|
return "unknown";
|
|
@@ -1999,6 +2411,75 @@ permissionCommand.command("check-many <permissions>").option("--team <teamKey>")
|
|
|
1999
2411
|
printOutput(result, runtime.json);
|
|
2000
2412
|
});
|
|
2001
2413
|
var activityCommand = program.command("activity").description("Activity feed");
|
|
2414
|
+
activityCommand.command("list").description(
|
|
2415
|
+
"List org-wide activity with optional filters by entity type, event type, and time range"
|
|
2416
|
+
).option(
|
|
2417
|
+
"--entity-type <type>",
|
|
2418
|
+
"Filter by entity type: issue, project, team, document"
|
|
2419
|
+
).option(
|
|
2420
|
+
"--event-type <type>",
|
|
2421
|
+
"Filter by event type (e.g. issue_created, issue_priority_changed)"
|
|
2422
|
+
).option(
|
|
2423
|
+
"--since <datetime>",
|
|
2424
|
+
"Start of time range (ISO date or shorthand: today, yesterday, 7d, 30d)"
|
|
2425
|
+
).option(
|
|
2426
|
+
"--until <datetime>",
|
|
2427
|
+
"End of time range (ISO date or shorthand: today, now)"
|
|
2428
|
+
).option("--limit <n>").option("--cursor <cursor>").action(async (options, command) => {
|
|
2429
|
+
const { client, runtime } = await getClient(command);
|
|
2430
|
+
const orgSlug = requireOrg(runtime);
|
|
2431
|
+
function parseTimeArg(value, bound) {
|
|
2432
|
+
if (!value) return void 0;
|
|
2433
|
+
const now = /* @__PURE__ */ new Date();
|
|
2434
|
+
const startOfToday = new Date(
|
|
2435
|
+
now.getFullYear(),
|
|
2436
|
+
now.getMonth(),
|
|
2437
|
+
now.getDate()
|
|
2438
|
+
);
|
|
2439
|
+
const endOfToday = new Date(
|
|
2440
|
+
now.getFullYear(),
|
|
2441
|
+
now.getMonth(),
|
|
2442
|
+
now.getDate(),
|
|
2443
|
+
23,
|
|
2444
|
+
59,
|
|
2445
|
+
59,
|
|
2446
|
+
999
|
|
2447
|
+
);
|
|
2448
|
+
switch (value) {
|
|
2449
|
+
case "now":
|
|
2450
|
+
return now.getTime();
|
|
2451
|
+
case "today":
|
|
2452
|
+
return bound === "start" ? startOfToday.getTime() : endOfToday.getTime();
|
|
2453
|
+
case "yesterday":
|
|
2454
|
+
return bound === "start" ? startOfToday.getTime() - 864e5 : startOfToday.getTime() - 1;
|
|
2455
|
+
default: {
|
|
2456
|
+
const daysMatch = value.match(/^(\d+)d$/);
|
|
2457
|
+
if (daysMatch) {
|
|
2458
|
+
return now.getTime() - Number(daysMatch[1]) * 864e5;
|
|
2459
|
+
}
|
|
2460
|
+
const parsed = new Date(value).getTime();
|
|
2461
|
+
if (Number.isNaN(parsed)) {
|
|
2462
|
+
throw new Error(`Invalid time value: ${value}`);
|
|
2463
|
+
}
|
|
2464
|
+
return parsed;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
const result = await runQuery(
|
|
2469
|
+
client,
|
|
2470
|
+
api.activities.queries.listOrgActivity,
|
|
2471
|
+
{
|
|
2472
|
+
orgSlug,
|
|
2473
|
+
entityType: options.entityType ?? void 0,
|
|
2474
|
+
eventType: options.eventType ?? void 0,
|
|
2475
|
+
since: parseTimeArg(options.since, "start"),
|
|
2476
|
+
until: parseTimeArg(options.until, "end"),
|
|
2477
|
+
limit: optionalNumber(options.limit, "limit") ?? void 0,
|
|
2478
|
+
cursor: options.cursor ?? void 0
|
|
2479
|
+
}
|
|
2480
|
+
);
|
|
2481
|
+
printOutput(result, runtime.json);
|
|
2482
|
+
});
|
|
2002
2483
|
activityCommand.command("project <projectKey>").option("--limit <n>").option("--cursor <cursor>").action(async (projectKey, options, command) => {
|
|
2003
2484
|
const { client, runtime } = await getClient(command);
|
|
2004
2485
|
const orgSlug = requireOrg(runtime);
|
|
@@ -2664,13 +3145,14 @@ projectCommand.command("set-lead <projectKey> <member>").action(async (projectKe
|
|
|
2664
3145
|
printOutput(result, runtime.json);
|
|
2665
3146
|
});
|
|
2666
3147
|
var issueCommand = program.command("issue").description("Issues");
|
|
2667
|
-
issueCommand.command("list [slug]").option("--project <projectKey>").option("--team <teamKey>").option("--limit <n>").option("--created-after <date>", "Filter: created on or after date (ISO)").option("--created-before <date>", "Filter: created on or before date (ISO)").option("--sort <field>", "Sort by field (e.g. createdAt, title, key)").option("--order <direction>", "Sort order: asc or desc (default: asc)").action(async (slug, options, command) => {
|
|
3148
|
+
issueCommand.command("list [slug]").option("--project <projectKey>").option("--team <teamKey>").option("--assignee <name>", "Filter by assignee name or email").option("--limit <n>").option("--created-after <date>", "Filter: created on or after date (ISO)").option("--created-before <date>", "Filter: created on or before date (ISO)").option("--sort <field>", "Sort by field (e.g. createdAt, title, key)").option("--order <direction>", "Sort order: asc or desc (default: asc)").action(async (slug, options, command) => {
|
|
2668
3149
|
const { client, runtime } = await getClient(command);
|
|
2669
3150
|
const orgSlug = requireOrg(runtime, slug);
|
|
2670
3151
|
const raw = await runAction(client, cliApi.listIssues, {
|
|
2671
3152
|
orgSlug,
|
|
2672
3153
|
projectKey: options.project,
|
|
2673
|
-
teamKey: options.team
|
|
3154
|
+
teamKey: options.team,
|
|
3155
|
+
assigneeName: options.assignee
|
|
2674
3156
|
});
|
|
2675
3157
|
const filtered = applyListFilters(raw, options);
|
|
2676
3158
|
const result = addEntityUrls(filtered, runtime.appUrl, orgSlug, "issues");
|
|
@@ -3014,6 +3496,129 @@ folderCommand.command("delete <folderId>").action(async (folderId, _options, com
|
|
|
3014
3496
|
});
|
|
3015
3497
|
printOutput(result, runtime.json);
|
|
3016
3498
|
});
|
|
3499
|
+
var serviceCommand = program.command("service").description("Manage the local bridge service");
|
|
3500
|
+
serviceCommand.command("start").description("Run the bridge service in the foreground").action(async (_options, command) => {
|
|
3501
|
+
let config = loadBridgeConfig();
|
|
3502
|
+
if (!config) {
|
|
3503
|
+
const runtime = await getRuntime(command);
|
|
3504
|
+
const session = requireSession(runtime);
|
|
3505
|
+
const client = await createConvexClient(
|
|
3506
|
+
session,
|
|
3507
|
+
runtime.appUrl,
|
|
3508
|
+
runtime.convexUrl
|
|
3509
|
+
);
|
|
3510
|
+
const user = await runQuery(client, api.users.currentUser);
|
|
3511
|
+
if (!user) throw new Error("Not logged in. Run `vcli auth login` first.");
|
|
3512
|
+
config = await setupBridgeDevice(runtime.convexUrl, user._id);
|
|
3513
|
+
console.log(`Device registered: ${config.deviceId}`);
|
|
3514
|
+
}
|
|
3515
|
+
const bridge = new BridgeService(config);
|
|
3516
|
+
await bridge.run();
|
|
3517
|
+
});
|
|
3518
|
+
serviceCommand.command("stop").description("Stop the running bridge service").action(() => {
|
|
3519
|
+
if (stopBridge()) {
|
|
3520
|
+
console.log("Bridge stopped.");
|
|
3521
|
+
} else {
|
|
3522
|
+
console.log("Bridge is not running.");
|
|
3523
|
+
}
|
|
3524
|
+
});
|
|
3525
|
+
serviceCommand.command("status").description("Show bridge service status").action((_options, command) => {
|
|
3526
|
+
const status = getBridgeStatus();
|
|
3527
|
+
if (!status.configured) {
|
|
3528
|
+
console.log("Bridge not configured. Run: vcli service start");
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
console.log("Vector Bridge");
|
|
3532
|
+
console.log(
|
|
3533
|
+
` Device: ${status.config.displayName} (${status.config.deviceId})`
|
|
3534
|
+
);
|
|
3535
|
+
console.log(` User: ${status.config.userId}`);
|
|
3536
|
+
console.log(
|
|
3537
|
+
` Status: ${status.running ? `Running (PID ${status.pid})` : "Not running"}`
|
|
3538
|
+
);
|
|
3539
|
+
console.log(` Config: ~/.vector/bridge.json`);
|
|
3540
|
+
});
|
|
3541
|
+
serviceCommand.command("install").description("Install the bridge as a system service (macOS LaunchAgent)").action(async (_options, command) => {
|
|
3542
|
+
if (osPlatform() !== "darwin") {
|
|
3543
|
+
console.error("Service install is currently macOS only (LaunchAgent).");
|
|
3544
|
+
console.error("On Linux, use systemd --user manually for now.");
|
|
3545
|
+
return;
|
|
3546
|
+
}
|
|
3547
|
+
const vcliPath = process.argv[1] ?? "vcli";
|
|
3548
|
+
installLaunchAgent(vcliPath);
|
|
3549
|
+
loadLaunchAgent();
|
|
3550
|
+
});
|
|
3551
|
+
serviceCommand.command("uninstall").description("Uninstall the bridge system service").action(() => {
|
|
3552
|
+
uninstallLaunchAgent();
|
|
3553
|
+
});
|
|
3554
|
+
serviceCommand.command("logs").description("Show bridge service logs").action(() => {
|
|
3555
|
+
const { existsSync: exists, readFileSync: read } = __require("fs");
|
|
3556
|
+
const logPath = __require("path").join(
|
|
3557
|
+
__require("os").homedir(),
|
|
3558
|
+
".vector",
|
|
3559
|
+
"bridge.log"
|
|
3560
|
+
);
|
|
3561
|
+
if (exists(logPath)) {
|
|
3562
|
+
const content = read(logPath, "utf-8");
|
|
3563
|
+
const lines = content.split("\n");
|
|
3564
|
+
console.log(lines.slice(-50).join("\n"));
|
|
3565
|
+
} else {
|
|
3566
|
+
console.log("No log file found at ~/.vector/bridge.log");
|
|
3567
|
+
}
|
|
3568
|
+
});
|
|
3569
|
+
var bridgeCommand = program.command("bridge").description("Start/stop the local agent bridge");
|
|
3570
|
+
bridgeCommand.command("start").description("Register device, install service, and start the bridge").action(async (_options, command) => {
|
|
3571
|
+
let config = loadBridgeConfig();
|
|
3572
|
+
if (!config) {
|
|
3573
|
+
const runtime = await getRuntime(command);
|
|
3574
|
+
const session = requireSession(runtime);
|
|
3575
|
+
const client = await createConvexClient(
|
|
3576
|
+
session,
|
|
3577
|
+
runtime.appUrl,
|
|
3578
|
+
runtime.convexUrl
|
|
3579
|
+
);
|
|
3580
|
+
const user = await runQuery(client, api.users.currentUser);
|
|
3581
|
+
if (!user) throw new Error("Not logged in. Run `vcli auth login` first.");
|
|
3582
|
+
config = await setupBridgeDevice(runtime.convexUrl, user._id);
|
|
3583
|
+
console.log(
|
|
3584
|
+
`Device registered: ${config.displayName} (${config.deviceId})`
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
if (osPlatform() === "darwin") {
|
|
3588
|
+
const vcliPath = process.argv[1] ?? "vcli";
|
|
3589
|
+
installLaunchAgent(vcliPath);
|
|
3590
|
+
loadLaunchAgent();
|
|
3591
|
+
console.log("\nBridge installed and started as LaunchAgent.");
|
|
3592
|
+
console.log("It will restart automatically on login.");
|
|
3593
|
+
console.log("Run `vcli service status` to check.");
|
|
3594
|
+
} else {
|
|
3595
|
+
console.log("Starting bridge in foreground...");
|
|
3596
|
+
const bridge = new BridgeService(config);
|
|
3597
|
+
await bridge.run();
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
bridgeCommand.command("stop").description("Stop the bridge service").action(() => {
|
|
3601
|
+
if (osPlatform() === "darwin") {
|
|
3602
|
+
uninstallLaunchAgent();
|
|
3603
|
+
}
|
|
3604
|
+
if (stopBridge()) {
|
|
3605
|
+
console.log("Bridge stopped.");
|
|
3606
|
+
} else {
|
|
3607
|
+
console.log("Bridge is not running.");
|
|
3608
|
+
}
|
|
3609
|
+
});
|
|
3610
|
+
bridgeCommand.command("status").description("Show bridge status").action(() => {
|
|
3611
|
+
const s = getBridgeStatus();
|
|
3612
|
+
if (!s.configured) {
|
|
3613
|
+
console.log("Bridge not configured. Run: vcli bridge start");
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3616
|
+
console.log("Vector Bridge");
|
|
3617
|
+
console.log(` Device: ${s.config.displayName} (${s.config.deviceId})`);
|
|
3618
|
+
console.log(
|
|
3619
|
+
` Status: ${s.running ? `Running (PID ${s.pid})` : "Not running"}`
|
|
3620
|
+
);
|
|
3621
|
+
});
|
|
3017
3622
|
async function main() {
|
|
3018
3623
|
await program.parseAsync(process.argv);
|
|
3019
3624
|
}
|