@rehpic/vcli 0.1.0-beta.6.1 → 0.1.0-beta.68.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
@@ -1,10 +1,748 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../../node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js
13
+ import fs from "fs";
14
+ function hasDockerEnv() {
15
+ try {
16
+ fs.statSync("/.dockerenv");
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+ function hasDockerCGroup() {
23
+ try {
24
+ return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ function isDocker() {
30
+ if (isDockerCached === void 0) {
31
+ isDockerCached = hasDockerEnv() || hasDockerCGroup();
32
+ }
33
+ return isDockerCached;
34
+ }
35
+ var isDockerCached;
36
+ var init_is_docker = __esm({
37
+ "../../node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js"() {
38
+ "use strict";
39
+ }
40
+ });
41
+
42
+ // ../../node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
43
+ import fs2 from "fs";
44
+ function isInsideContainer() {
45
+ if (cachedResult === void 0) {
46
+ cachedResult = hasContainerEnv() || isDocker();
47
+ }
48
+ return cachedResult;
49
+ }
50
+ var cachedResult, hasContainerEnv;
51
+ var init_is_inside_container = __esm({
52
+ "../../node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.js"() {
53
+ "use strict";
54
+ init_is_docker();
55
+ hasContainerEnv = () => {
56
+ try {
57
+ fs2.statSync("/run/.containerenv");
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ };
63
+ }
64
+ });
65
+
66
+ // ../../node_modules/.pnpm/is-wsl@3.1.1/node_modules/is-wsl/index.js
67
+ import process2 from "process";
68
+ import os from "os";
69
+ import fs3 from "fs";
70
+ var isWsl, is_wsl_default;
71
+ var init_is_wsl = __esm({
72
+ "../../node_modules/.pnpm/is-wsl@3.1.1/node_modules/is-wsl/index.js"() {
73
+ "use strict";
74
+ init_is_inside_container();
75
+ isWsl = () => {
76
+ if (process2.platform !== "linux") {
77
+ return false;
78
+ }
79
+ if (os.release().toLowerCase().includes("microsoft")) {
80
+ if (isInsideContainer()) {
81
+ return false;
82
+ }
83
+ return true;
84
+ }
85
+ try {
86
+ if (fs3.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
87
+ return !isInsideContainer();
88
+ }
89
+ } catch {
90
+ }
91
+ if (fs3.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs3.existsSync("/run/WSL")) {
92
+ return !isInsideContainer();
93
+ }
94
+ return false;
95
+ };
96
+ is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
97
+ }
98
+ });
99
+
100
+ // ../../node_modules/.pnpm/powershell-utils@0.1.0/node_modules/powershell-utils/index.js
101
+ import process3 from "process";
102
+ import { Buffer as Buffer2 } from "buffer";
103
+ import { promisify } from "util";
104
+ import childProcess from "child_process";
105
+ import fs4, { constants as fsConstants } from "fs/promises";
106
+ var execFile, powerShellPath, executePowerShell;
107
+ var init_powershell_utils = __esm({
108
+ "../../node_modules/.pnpm/powershell-utils@0.1.0/node_modules/powershell-utils/index.js"() {
109
+ "use strict";
110
+ execFile = promisify(childProcess.execFile);
111
+ powerShellPath = () => `${process3.env.SYSTEMROOT || process3.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
112
+ executePowerShell = async (command, options = {}) => {
113
+ const {
114
+ powerShellPath: psPath,
115
+ ...execFileOptions
116
+ } = options;
117
+ const encodedCommand = executePowerShell.encodeCommand(command);
118
+ return execFile(
119
+ psPath ?? powerShellPath(),
120
+ [
121
+ ...executePowerShell.argumentsPrefix,
122
+ encodedCommand
123
+ ],
124
+ {
125
+ encoding: "utf8",
126
+ ...execFileOptions
127
+ }
128
+ );
129
+ };
130
+ executePowerShell.argumentsPrefix = [
131
+ "-NoProfile",
132
+ "-NonInteractive",
133
+ "-ExecutionPolicy",
134
+ "Bypass",
135
+ "-EncodedCommand"
136
+ ];
137
+ executePowerShell.encodeCommand = (command) => Buffer2.from(command, "utf16le").toString("base64");
138
+ executePowerShell.escapeArgument = (value) => `'${String(value).replaceAll("'", "''")}'`;
139
+ }
140
+ });
141
+
142
+ // ../../node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/utilities.js
143
+ function parseMountPointFromConfig(content) {
144
+ for (const line of content.split("\n")) {
145
+ if (/^\s*#/.test(line)) {
146
+ continue;
147
+ }
148
+ const match = /^\s*root\s*=\s*(?<mountPoint>"[^"]*"|'[^']*'|[^#]*)/.exec(line);
149
+ if (!match) {
150
+ continue;
151
+ }
152
+ return match.groups.mountPoint.trim().replaceAll(/^["']|["']$/g, "");
153
+ }
154
+ }
155
+ var init_utilities = __esm({
156
+ "../../node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/utilities.js"() {
157
+ "use strict";
158
+ }
159
+ });
160
+
161
+ // ../../node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/index.js
162
+ import { promisify as promisify2 } from "util";
163
+ import childProcess2 from "child_process";
164
+ import fs5, { constants as fsConstants2 } from "fs/promises";
165
+ var execFile2, wslDrivesMountPoint, powerShellPathFromWsl, powerShellPath2, canAccessPowerShellPromise, canAccessPowerShell, wslDefaultBrowser, convertWslPathToWindows;
166
+ var init_wsl_utils = __esm({
167
+ "../../node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/index.js"() {
168
+ "use strict";
169
+ init_is_wsl();
170
+ init_powershell_utils();
171
+ init_utilities();
172
+ init_is_wsl();
173
+ execFile2 = promisify2(childProcess2.execFile);
174
+ wslDrivesMountPoint = /* @__PURE__ */ (() => {
175
+ const defaultMountPoint = "/mnt/";
176
+ let mountPoint;
177
+ return async function() {
178
+ if (mountPoint) {
179
+ return mountPoint;
180
+ }
181
+ const configFilePath = "/etc/wsl.conf";
182
+ let isConfigFileExists = false;
183
+ try {
184
+ await fs5.access(configFilePath, fsConstants2.F_OK);
185
+ isConfigFileExists = true;
186
+ } catch {
187
+ }
188
+ if (!isConfigFileExists) {
189
+ return defaultMountPoint;
190
+ }
191
+ const configContent = await fs5.readFile(configFilePath, { encoding: "utf8" });
192
+ const parsedMountPoint = parseMountPointFromConfig(configContent);
193
+ if (parsedMountPoint === void 0) {
194
+ return defaultMountPoint;
195
+ }
196
+ mountPoint = parsedMountPoint;
197
+ mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
198
+ return mountPoint;
199
+ };
200
+ })();
201
+ powerShellPathFromWsl = async () => {
202
+ const mountPoint = await wslDrivesMountPoint();
203
+ return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
204
+ };
205
+ powerShellPath2 = is_wsl_default ? powerShellPathFromWsl : powerShellPath;
206
+ canAccessPowerShell = async () => {
207
+ canAccessPowerShellPromise ??= (async () => {
208
+ try {
209
+ const psPath = await powerShellPath2();
210
+ await fs5.access(psPath, fsConstants2.X_OK);
211
+ return true;
212
+ } catch {
213
+ return false;
214
+ }
215
+ })();
216
+ return canAccessPowerShellPromise;
217
+ };
218
+ wslDefaultBrowser = async () => {
219
+ const psPath = await powerShellPath2();
220
+ const command = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
221
+ const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
222
+ return stdout.trim();
223
+ };
224
+ convertWslPathToWindows = async (path3) => {
225
+ if (/^[a-z]+:\/\//i.test(path3)) {
226
+ return path3;
227
+ }
228
+ try {
229
+ const { stdout } = await execFile2("wslpath", ["-aw", path3], { encoding: "utf8" });
230
+ return stdout.trim();
231
+ } catch {
232
+ return path3;
233
+ }
234
+ };
235
+ }
236
+ });
237
+
238
+ // ../../node_modules/.pnpm/define-lazy-prop@3.0.0/node_modules/define-lazy-prop/index.js
239
+ function defineLazyProperty(object, propertyName, valueGetter) {
240
+ const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
241
+ Object.defineProperty(object, propertyName, {
242
+ configurable: true,
243
+ enumerable: true,
244
+ get() {
245
+ const result = valueGetter();
246
+ define(result);
247
+ return result;
248
+ },
249
+ set(value) {
250
+ define(value);
251
+ }
252
+ });
253
+ return object;
254
+ }
255
+ var init_define_lazy_prop = __esm({
256
+ "../../node_modules/.pnpm/define-lazy-prop@3.0.0/node_modules/define-lazy-prop/index.js"() {
257
+ "use strict";
258
+ }
259
+ });
260
+
261
+ // ../../node_modules/.pnpm/default-browser-id@5.0.1/node_modules/default-browser-id/index.js
262
+ import { promisify as promisify3 } from "util";
263
+ import process4 from "process";
264
+ import { execFile as execFile3 } from "child_process";
265
+ async function defaultBrowserId() {
266
+ if (process4.platform !== "darwin") {
267
+ throw new Error("macOS only");
268
+ }
269
+ const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
270
+ const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
271
+ const browserId = match?.groups.id ?? "com.apple.Safari";
272
+ if (browserId === "com.apple.safari") {
273
+ return "com.apple.Safari";
274
+ }
275
+ return browserId;
276
+ }
277
+ var execFileAsync;
278
+ var init_default_browser_id = __esm({
279
+ "../../node_modules/.pnpm/default-browser-id@5.0.1/node_modules/default-browser-id/index.js"() {
280
+ "use strict";
281
+ execFileAsync = promisify3(execFile3);
282
+ }
283
+ });
284
+
285
+ // ../../node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js
286
+ import process5 from "process";
287
+ import { promisify as promisify4 } from "util";
288
+ import { execFile as execFile4, execFileSync as execFileSync2 } from "child_process";
289
+ async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
290
+ if (process5.platform !== "darwin") {
291
+ throw new Error("macOS only");
292
+ }
293
+ const outputArguments = humanReadableOutput ? [] : ["-ss"];
294
+ const execOptions = {};
295
+ if (signal) {
296
+ execOptions.signal = signal;
297
+ }
298
+ const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments], execOptions);
299
+ return stdout.trim();
300
+ }
301
+ var execFileAsync2;
302
+ var init_run_applescript = __esm({
303
+ "../../node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js"() {
304
+ "use strict";
305
+ execFileAsync2 = promisify4(execFile4);
306
+ }
307
+ });
308
+
309
+ // ../../node_modules/.pnpm/bundle-name@4.1.0/node_modules/bundle-name/index.js
310
+ async function bundleName(bundleId) {
311
+ return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
312
+ tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
313
+ }
314
+ var init_bundle_name = __esm({
315
+ "../../node_modules/.pnpm/bundle-name@4.1.0/node_modules/bundle-name/index.js"() {
316
+ "use strict";
317
+ init_run_applescript();
318
+ }
319
+ });
320
+
321
+ // ../../node_modules/.pnpm/default-browser@5.5.0/node_modules/default-browser/windows.js
322
+ import { promisify as promisify5 } from "util";
323
+ import { execFile as execFile5 } from "child_process";
324
+ async function defaultBrowser(_execFileAsync = execFileAsync3) {
325
+ const { stdout } = await _execFileAsync("reg", [
326
+ "QUERY",
327
+ " HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
328
+ "/v",
329
+ "ProgId"
330
+ ]);
331
+ const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
332
+ if (!match) {
333
+ throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
334
+ }
335
+ const { id } = match.groups;
336
+ const dotIndex = id.lastIndexOf(".");
337
+ const hyphenIndex = id.lastIndexOf("-");
338
+ const baseIdByDot = dotIndex === -1 ? void 0 : id.slice(0, dotIndex);
339
+ const baseIdByHyphen = hyphenIndex === -1 ? void 0 : id.slice(0, hyphenIndex);
340
+ return windowsBrowserProgIds[id] ?? windowsBrowserProgIds[baseIdByDot] ?? windowsBrowserProgIds[baseIdByHyphen] ?? { name: id, id };
341
+ }
342
+ var execFileAsync3, windowsBrowserProgIds, _windowsBrowserProgIdMap, UnknownBrowserError;
343
+ var init_windows = __esm({
344
+ "../../node_modules/.pnpm/default-browser@5.5.0/node_modules/default-browser/windows.js"() {
345
+ "use strict";
346
+ execFileAsync3 = promisify5(execFile5);
347
+ windowsBrowserProgIds = {
348
+ MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
349
+ // The missing `L` is correct.
350
+ MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
351
+ MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
352
+ AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
353
+ ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
354
+ ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
355
+ ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
356
+ ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
357
+ BraveHTML: { name: "Brave", id: "com.brave.Browser" },
358
+ BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
359
+ BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
360
+ BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
361
+ FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
362
+ OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
363
+ VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
364
+ "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
365
+ };
366
+ _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
367
+ UnknownBrowserError = class extends Error {
368
+ };
369
+ }
370
+ });
371
+
372
+ // ../../node_modules/.pnpm/default-browser@5.5.0/node_modules/default-browser/index.js
373
+ import { promisify as promisify6 } from "util";
374
+ import process6 from "process";
375
+ import { execFile as execFile6 } from "child_process";
376
+ async function defaultBrowser2() {
377
+ if (process6.platform === "darwin") {
378
+ const id = await defaultBrowserId();
379
+ const name = await bundleName(id);
380
+ return { name, id };
381
+ }
382
+ if (process6.platform === "linux") {
383
+ const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
384
+ const id = stdout.trim();
385
+ const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
386
+ return { name, id };
387
+ }
388
+ if (process6.platform === "win32") {
389
+ return defaultBrowser();
390
+ }
391
+ throw new Error("Only macOS, Linux, and Windows are supported");
392
+ }
393
+ var execFileAsync4, titleize;
394
+ var init_default_browser = __esm({
395
+ "../../node_modules/.pnpm/default-browser@5.5.0/node_modules/default-browser/index.js"() {
396
+ "use strict";
397
+ init_default_browser_id();
398
+ init_bundle_name();
399
+ init_windows();
400
+ init_windows();
401
+ execFileAsync4 = promisify6(execFile6);
402
+ titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
403
+ }
404
+ });
405
+
406
+ // ../../node_modules/.pnpm/is-in-ssh@1.0.0/node_modules/is-in-ssh/index.js
407
+ import process7 from "process";
408
+ var isInSsh, is_in_ssh_default;
409
+ var init_is_in_ssh = __esm({
410
+ "../../node_modules/.pnpm/is-in-ssh@1.0.0/node_modules/is-in-ssh/index.js"() {
411
+ "use strict";
412
+ isInSsh = Boolean(process7.env.SSH_CONNECTION || process7.env.SSH_CLIENT || process7.env.SSH_TTY);
413
+ is_in_ssh_default = isInSsh;
414
+ }
415
+ });
416
+
417
+ // ../../node_modules/.pnpm/open@11.0.0/node_modules/open/index.js
418
+ var open_exports = {};
419
+ __export(open_exports, {
420
+ apps: () => apps,
421
+ default: () => open_default,
422
+ openApp: () => openApp
423
+ });
424
+ import process8 from "process";
425
+ import path2 from "path";
426
+ import { fileURLToPath } from "url";
427
+ import childProcess3 from "child_process";
428
+ import fs6, { constants as fsConstants3 } from "fs/promises";
429
+ function detectArchBinary(binary) {
430
+ if (typeof binary === "string" || Array.isArray(binary)) {
431
+ return binary;
432
+ }
433
+ const { [arch]: archBinary } = binary;
434
+ if (!archBinary) {
435
+ throw new Error(`${arch} is not supported`);
436
+ }
437
+ return archBinary;
438
+ }
439
+ function detectPlatformBinary({ [platform2]: platformBinary }, { wsl } = {}) {
440
+ if (wsl && is_wsl_default) {
441
+ return detectArchBinary(wsl);
442
+ }
443
+ if (!platformBinary) {
444
+ throw new Error(`${platform2} is not supported`);
445
+ }
446
+ return detectArchBinary(platformBinary);
447
+ }
448
+ var fallbackAttemptSymbol, __dirname, localXdgOpenPath, platform2, arch, tryEachApp, baseOpen, open, openApp, apps, open_default;
449
+ var init_open = __esm({
450
+ "../../node_modules/.pnpm/open@11.0.0/node_modules/open/index.js"() {
451
+ "use strict";
452
+ init_wsl_utils();
453
+ init_powershell_utils();
454
+ init_define_lazy_prop();
455
+ init_default_browser();
456
+ init_is_inside_container();
457
+ init_is_in_ssh();
458
+ fallbackAttemptSymbol = Symbol("fallbackAttempt");
459
+ __dirname = import.meta.url ? path2.dirname(fileURLToPath(import.meta.url)) : "";
460
+ localXdgOpenPath = path2.join(__dirname, "xdg-open");
461
+ ({ platform: platform2, arch } = process8);
462
+ tryEachApp = async (apps2, opener) => {
463
+ if (apps2.length === 0) {
464
+ return;
465
+ }
466
+ const errors = [];
467
+ for (const app of apps2) {
468
+ try {
469
+ return await opener(app);
470
+ } catch (error) {
471
+ errors.push(error);
472
+ }
473
+ }
474
+ throw new AggregateError(errors, "Failed to open in all supported apps");
475
+ };
476
+ baseOpen = async (options) => {
477
+ options = {
478
+ wait: false,
479
+ background: false,
480
+ newInstance: false,
481
+ allowNonzeroExitCode: false,
482
+ ...options
483
+ };
484
+ const isFallbackAttempt = options[fallbackAttemptSymbol] === true;
485
+ delete options[fallbackAttemptSymbol];
486
+ if (Array.isArray(options.app)) {
487
+ return tryEachApp(options.app, (singleApp) => baseOpen({
488
+ ...options,
489
+ app: singleApp,
490
+ [fallbackAttemptSymbol]: true
491
+ }));
492
+ }
493
+ let { name: app, arguments: appArguments = [] } = options.app ?? {};
494
+ appArguments = [...appArguments];
495
+ if (Array.isArray(app)) {
496
+ return tryEachApp(app, (appName) => baseOpen({
497
+ ...options,
498
+ app: {
499
+ name: appName,
500
+ arguments: appArguments
501
+ },
502
+ [fallbackAttemptSymbol]: true
503
+ }));
504
+ }
505
+ if (app === "browser" || app === "browserPrivate") {
506
+ const ids = {
507
+ "com.google.chrome": "chrome",
508
+ "google-chrome.desktop": "chrome",
509
+ "com.brave.browser": "brave",
510
+ "org.mozilla.firefox": "firefox",
511
+ "firefox.desktop": "firefox",
512
+ "com.microsoft.msedge": "edge",
513
+ "com.microsoft.edge": "edge",
514
+ "com.microsoft.edgemac": "edge",
515
+ "microsoft-edge.desktop": "edge",
516
+ "com.apple.safari": "safari"
517
+ };
518
+ const flags = {
519
+ chrome: "--incognito",
520
+ brave: "--incognito",
521
+ firefox: "--private-window",
522
+ edge: "--inPrivate"
523
+ // Safari doesn't support private mode via command line
524
+ };
525
+ let browser;
526
+ if (is_wsl_default) {
527
+ const progId = await wslDefaultBrowser();
528
+ const browserInfo = _windowsBrowserProgIdMap.get(progId);
529
+ browser = browserInfo ?? {};
530
+ } else {
531
+ browser = await defaultBrowser2();
532
+ }
533
+ if (browser.id in ids) {
534
+ const browserName = ids[browser.id.toLowerCase()];
535
+ if (app === "browserPrivate") {
536
+ if (browserName === "safari") {
537
+ throw new Error("Safari doesn't support opening in private mode via command line");
538
+ }
539
+ appArguments.push(flags[browserName]);
540
+ }
541
+ return baseOpen({
542
+ ...options,
543
+ app: {
544
+ name: apps[browserName],
545
+ arguments: appArguments
546
+ }
547
+ });
548
+ }
549
+ throw new Error(`${browser.name} is not supported as a default browser`);
550
+ }
551
+ let command;
552
+ const cliArguments = [];
553
+ const childProcessOptions = {};
554
+ let shouldUseWindowsInWsl = false;
555
+ if (is_wsl_default && !isInsideContainer() && !is_in_ssh_default && !app) {
556
+ shouldUseWindowsInWsl = await canAccessPowerShell();
557
+ }
558
+ if (platform2 === "darwin") {
559
+ command = "open";
560
+ if (options.wait) {
561
+ cliArguments.push("--wait-apps");
562
+ }
563
+ if (options.background) {
564
+ cliArguments.push("--background");
565
+ }
566
+ if (options.newInstance) {
567
+ cliArguments.push("--new");
568
+ }
569
+ if (app) {
570
+ cliArguments.push("-a", app);
571
+ }
572
+ } else if (platform2 === "win32" || shouldUseWindowsInWsl) {
573
+ command = await powerShellPath2();
574
+ cliArguments.push(...executePowerShell.argumentsPrefix);
575
+ if (!is_wsl_default) {
576
+ childProcessOptions.windowsVerbatimArguments = true;
577
+ }
578
+ if (is_wsl_default && options.target) {
579
+ options.target = await convertWslPathToWindows(options.target);
580
+ }
581
+ const encodedArguments = ["$ProgressPreference = 'SilentlyContinue';", "Start"];
582
+ if (options.wait) {
583
+ encodedArguments.push("-Wait");
584
+ }
585
+ if (app) {
586
+ encodedArguments.push(executePowerShell.escapeArgument(app));
587
+ if (options.target) {
588
+ appArguments.push(options.target);
589
+ }
590
+ } else if (options.target) {
591
+ encodedArguments.push(executePowerShell.escapeArgument(options.target));
592
+ }
593
+ if (appArguments.length > 0) {
594
+ appArguments = appArguments.map((argument) => executePowerShell.escapeArgument(argument));
595
+ encodedArguments.push("-ArgumentList", appArguments.join(","));
596
+ }
597
+ options.target = executePowerShell.encodeCommand(encodedArguments.join(" "));
598
+ if (!options.wait) {
599
+ childProcessOptions.stdio = "ignore";
600
+ }
601
+ } else {
602
+ if (app) {
603
+ command = app;
604
+ } else {
605
+ const isBundled = !__dirname || __dirname === "/";
606
+ let exeLocalXdgOpen = false;
607
+ try {
608
+ await fs6.access(localXdgOpenPath, fsConstants3.X_OK);
609
+ exeLocalXdgOpen = true;
610
+ } catch {
611
+ }
612
+ const useSystemXdgOpen = process8.versions.electron ?? (platform2 === "android" || isBundled || !exeLocalXdgOpen);
613
+ command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
614
+ }
615
+ if (appArguments.length > 0) {
616
+ cliArguments.push(...appArguments);
617
+ }
618
+ if (!options.wait) {
619
+ childProcessOptions.stdio = "ignore";
620
+ childProcessOptions.detached = true;
621
+ }
622
+ }
623
+ if (platform2 === "darwin" && appArguments.length > 0) {
624
+ cliArguments.push("--args", ...appArguments);
625
+ }
626
+ if (options.target) {
627
+ cliArguments.push(options.target);
628
+ }
629
+ const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
630
+ if (options.wait) {
631
+ return new Promise((resolve, reject) => {
632
+ subprocess.once("error", reject);
633
+ subprocess.once("close", (exitCode) => {
634
+ if (!options.allowNonzeroExitCode && exitCode !== 0) {
635
+ reject(new Error(`Exited with code ${exitCode}`));
636
+ return;
637
+ }
638
+ resolve(subprocess);
639
+ });
640
+ });
641
+ }
642
+ if (isFallbackAttempt) {
643
+ return new Promise((resolve, reject) => {
644
+ subprocess.once("error", reject);
645
+ subprocess.once("spawn", () => {
646
+ subprocess.once("close", (exitCode) => {
647
+ subprocess.off("error", reject);
648
+ if (exitCode !== 0) {
649
+ reject(new Error(`Exited with code ${exitCode}`));
650
+ return;
651
+ }
652
+ subprocess.unref();
653
+ resolve(subprocess);
654
+ });
655
+ });
656
+ });
657
+ }
658
+ subprocess.unref();
659
+ return new Promise((resolve, reject) => {
660
+ subprocess.once("error", reject);
661
+ subprocess.once("spawn", () => {
662
+ subprocess.off("error", reject);
663
+ resolve(subprocess);
664
+ });
665
+ });
666
+ };
667
+ open = (target, options) => {
668
+ if (typeof target !== "string") {
669
+ throw new TypeError("Expected a `target`");
670
+ }
671
+ return baseOpen({
672
+ ...options,
673
+ target
674
+ });
675
+ };
676
+ openApp = (name, options) => {
677
+ if (typeof name !== "string" && !Array.isArray(name)) {
678
+ throw new TypeError("Expected a valid `name`");
679
+ }
680
+ const { arguments: appArguments = [] } = options ?? {};
681
+ if (appArguments !== void 0 && appArguments !== null && !Array.isArray(appArguments)) {
682
+ throw new TypeError("Expected `appArguments` as Array type");
683
+ }
684
+ return baseOpen({
685
+ ...options,
686
+ app: {
687
+ name,
688
+ arguments: appArguments
689
+ }
690
+ });
691
+ };
692
+ apps = {
693
+ browser: "browser",
694
+ browserPrivate: "browserPrivate"
695
+ };
696
+ defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
697
+ darwin: "google chrome",
698
+ win32: "chrome",
699
+ // `chromium-browser` is the older deb package name used by Ubuntu/Debian before snap.
700
+ linux: ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]
701
+ }, {
702
+ wsl: {
703
+ ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
704
+ x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
705
+ }
706
+ }));
707
+ defineLazyProperty(apps, "brave", () => detectPlatformBinary({
708
+ darwin: "brave browser",
709
+ win32: "brave",
710
+ linux: ["brave-browser", "brave"]
711
+ }, {
712
+ wsl: {
713
+ ia32: "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe",
714
+ x64: ["/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe", "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe"]
715
+ }
716
+ }));
717
+ defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
718
+ darwin: "firefox",
719
+ win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`,
720
+ linux: "firefox"
721
+ }, {
722
+ wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
723
+ }));
724
+ defineLazyProperty(apps, "edge", () => detectPlatformBinary({
725
+ darwin: "microsoft edge",
726
+ win32: "msedge",
727
+ linux: ["microsoft-edge", "microsoft-edge-dev"]
728
+ }, {
729
+ wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
730
+ }));
731
+ defineLazyProperty(apps, "safari", () => detectPlatformBinary({
732
+ darwin: "Safari"
733
+ }));
734
+ open_default = open;
735
+ }
736
+ });
2
737
 
3
- // ../../src/cli/index.ts
738
+ // src/index.ts
739
+ import { readFileSync as readFileSync3 } from "fs";
4
740
  import { readFile as readFile2 } from "fs/promises";
5
- import { extname } from "path";
741
+ import { dirname, extname, join as join3 } from "path";
742
+ import { fileURLToPath as fileURLToPath2 } from "url";
6
743
  import { config as loadEnv } from "dotenv";
7
744
  import { Command } from "commander";
745
+ import { ConvexHttpClient as ConvexHttpClient3 } from "convex/browser";
8
746
  import { makeFunctionReference } from "convex/server";
9
747
 
10
748
  // ../../convex/_generated/api.js
@@ -12,7 +750,7 @@ import { anyApi, componentsGeneric } from "convex/server";
12
750
  var api = anyApi;
13
751
  var components = componentsGeneric();
14
752
 
15
- // ../../src/cli/auth.ts
753
+ // src/auth.ts
16
754
  import { isCancel, password as passwordPrompt, text } from "@clack/prompts";
17
755
  function buildUrl(appUrl, pathname) {
18
756
  return new URL(pathname, appUrl).toString();
@@ -52,6 +790,9 @@ function applySetCookieHeaders(session, response) {
52
790
  async function authRequest(session, appUrl, pathname, init = {}) {
53
791
  const headers = new Headers(init.headers);
54
792
  const origin = new URL(appUrl).origin;
793
+ if (session.bearerToken && !headers.has("authorization")) {
794
+ headers.set("authorization", `Bearer ${session.bearerToken}`);
795
+ }
55
796
  if (Object.keys(session.cookies).length > 0) {
56
797
  headers.set("cookie", cookieHeader(session.cookies));
57
798
  }
@@ -146,7 +887,7 @@ async function fetchAuthSession(session, appUrl) {
146
887
  const data = await response.json();
147
888
  return {
148
889
  session: nextSession,
149
- user: data.user ?? null
890
+ user: data?.user ?? null
150
891
  };
151
892
  }
152
893
  async function fetchConvexToken(session, appUrl) {
@@ -170,6 +911,65 @@ async function fetchConvexToken(session, appUrl) {
170
911
  token: data.token
171
912
  };
172
913
  }
914
+ async function requestDeviceCode(appUrl, clientId) {
915
+ const response = await fetch(buildUrl(appUrl, "/api/auth/device/code"), {
916
+ method: "POST",
917
+ headers: { "content-type": "application/json" },
918
+ body: JSON.stringify({ client_id: clientId })
919
+ });
920
+ if (!response.ok) {
921
+ throw new Error(`Failed to request device code: HTTP ${response.status}`);
922
+ }
923
+ return await response.json();
924
+ }
925
+ async function pollDeviceToken(session, appUrl, deviceCode, clientId, interval, expiresIn) {
926
+ const deadline = Date.now() + expiresIn * 1e3;
927
+ let pollInterval = interval * 1e3;
928
+ while (Date.now() < deadline) {
929
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
930
+ const { response, session: nextSession } = await authRequest(
931
+ session,
932
+ appUrl,
933
+ "/api/auth/device/token",
934
+ {
935
+ method: "POST",
936
+ body: JSON.stringify({
937
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
938
+ device_code: deviceCode,
939
+ client_id: clientId
940
+ })
941
+ }
942
+ );
943
+ session = nextSession;
944
+ if (response.ok) {
945
+ const data = await response.json();
946
+ if (data.access_token) {
947
+ session.bearerToken = data.access_token;
948
+ return session;
949
+ }
950
+ }
951
+ let errorData;
952
+ try {
953
+ errorData = await response.json();
954
+ } catch {
955
+ errorData = { error: `HTTP ${response.status}` };
956
+ }
957
+ switch (errorData.error) {
958
+ case "authorization_pending":
959
+ break;
960
+ case "slow_down":
961
+ pollInterval += 5e3;
962
+ break;
963
+ case "access_denied":
964
+ throw new Error("Authorization denied by user.");
965
+ case "expired_token":
966
+ throw new Error("Device code expired. Please try again.");
967
+ default:
968
+ throw new Error(`Device auth error: ${errorData.error}`);
969
+ }
970
+ }
971
+ throw new Error("Device code expired. Please try again.");
972
+ }
173
973
  async function prompt(question) {
174
974
  const value = await text({
175
975
  message: question.replace(/:\s*$/, "")
@@ -190,7 +990,7 @@ async function promptSecret(question) {
190
990
  return String(value);
191
991
  }
192
992
 
193
- // ../../src/cli/convex.ts
993
+ // src/convex.ts
194
994
  import { ConvexHttpClient } from "convex/browser";
195
995
  async function createConvexClient(session, appUrl, convexUrl) {
196
996
  const { token } = await fetchConvexToken(session, appUrl);
@@ -208,7 +1008,7 @@ async function runAction(client, ref, ...args) {
208
1008
  return await client.action(ref, ...args);
209
1009
  }
210
1010
 
211
- // ../../src/cli/output.ts
1011
+ // src/output.ts
212
1012
  function simplify(value) {
213
1013
  if (value === null || value === void 0) {
214
1014
  return value;
@@ -232,70 +1032,2393 @@ function printOutput(data, json = false) {
232
1032
  console.log(JSON.stringify(data, null, 2));
233
1033
  return;
234
1034
  }
235
- if (Array.isArray(data)) {
236
- if (data.length === 0) {
237
- console.log("No results.");
238
- return;
1035
+ if (Array.isArray(data)) {
1036
+ if (data.length === 0) {
1037
+ console.log("No results.");
1038
+ return;
1039
+ }
1040
+ if (data.every((item) => typeof item === "object" && item !== null)) {
1041
+ console.table(
1042
+ data.map(
1043
+ (item) => Object.fromEntries(
1044
+ Object.entries(item).map(([key, value]) => [key, simplify(value)])
1045
+ )
1046
+ )
1047
+ );
1048
+ return;
1049
+ }
1050
+ }
1051
+ if (typeof data === "object" && data !== null) {
1052
+ console.dir(data, { depth: null, colors: true });
1053
+ return;
1054
+ }
1055
+ console.log(String(data));
1056
+ }
1057
+
1058
+ // src/session.ts
1059
+ import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
1060
+ import { homedir } from "os";
1061
+ import path from "path";
1062
+ function getSessionRoot() {
1063
+ return process.env.VECTOR_HOME?.trim() || path.join(homedir(), ".vector");
1064
+ }
1065
+ function getProfileConfigPath() {
1066
+ return path.join(getSessionRoot(), "cli-config.json");
1067
+ }
1068
+ function getSessionPath(profile = "default") {
1069
+ return path.join(getSessionRoot(), `cli-${profile}.json`);
1070
+ }
1071
+ async function readDefaultProfile() {
1072
+ try {
1073
+ const raw = await readFile(getProfileConfigPath(), "utf8");
1074
+ const parsed = JSON.parse(raw);
1075
+ const profile = parsed.defaultProfile?.trim();
1076
+ return profile || "default";
1077
+ } catch {
1078
+ return "default";
1079
+ }
1080
+ }
1081
+ async function writeDefaultProfile(profile) {
1082
+ const normalized = profile.trim() || "default";
1083
+ await mkdir(getSessionRoot(), { recursive: true });
1084
+ const config = {
1085
+ version: 1,
1086
+ defaultProfile: normalized
1087
+ };
1088
+ await writeFile(
1089
+ getProfileConfigPath(),
1090
+ `${JSON.stringify(config, null, 2)}
1091
+ `,
1092
+ "utf8"
1093
+ );
1094
+ }
1095
+ async function listProfiles() {
1096
+ const root = getSessionRoot();
1097
+ const defaultProfile = await readDefaultProfile();
1098
+ try {
1099
+ const entries = await readdir(root, { withFileTypes: true });
1100
+ const names = entries.filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => /^cli-.+\.json$/.test(name)).map((name) => name.replace(/^cli-/, "").replace(/\.json$/, ""));
1101
+ const uniqueNames = Array.from(/* @__PURE__ */ new Set([...names, defaultProfile])).sort(
1102
+ (left, right) => left.localeCompare(right)
1103
+ );
1104
+ return Promise.all(
1105
+ uniqueNames.map(async (name) => ({
1106
+ name,
1107
+ isDefault: name === defaultProfile,
1108
+ hasSession: await readSession(name) !== null
1109
+ }))
1110
+ );
1111
+ } catch {
1112
+ return [
1113
+ {
1114
+ name: defaultProfile,
1115
+ isDefault: true,
1116
+ hasSession: await readSession(defaultProfile) !== null
1117
+ }
1118
+ ];
1119
+ }
1120
+ }
1121
+ async function readSession(profile = "default") {
1122
+ try {
1123
+ const raw = await readFile(getSessionPath(profile), "utf8");
1124
+ const parsed = JSON.parse(raw);
1125
+ return {
1126
+ version: 1,
1127
+ cookies: {},
1128
+ ...parsed
1129
+ };
1130
+ } catch {
1131
+ return null;
1132
+ }
1133
+ }
1134
+ async function writeSession(session, profile = "default") {
1135
+ await mkdir(getSessionRoot(), { recursive: true });
1136
+ await writeFile(
1137
+ getSessionPath(profile),
1138
+ `${JSON.stringify(session, null, 2)}
1139
+ `,
1140
+ "utf8"
1141
+ );
1142
+ }
1143
+ async function clearSession(profile = "default") {
1144
+ await rm(getSessionPath(profile), { force: true });
1145
+ }
1146
+ function createEmptySession() {
1147
+ return {
1148
+ version: 1,
1149
+ cookies: {}
1150
+ };
1151
+ }
1152
+
1153
+ // src/bridge-service.ts
1154
+ import { ConvexHttpClient as ConvexHttpClient2 } from "convex/browser";
1155
+ import { execFileSync, execSync as execSync2 } from "child_process";
1156
+
1157
+ // src/terminal-peer.ts
1158
+ import { createServer } from "http";
1159
+ import { WebSocketServer, WebSocket } from "ws";
1160
+ import { ConvexClient } from "convex/browser";
1161
+ import * as pty from "node-pty";
1162
+ import { existsSync } from "fs";
1163
+ import { randomUUID } from "crypto";
1164
+ import localtunnel from "localtunnel";
1165
+ function findTmuxPath() {
1166
+ for (const p of [
1167
+ "/opt/homebrew/bin/tmux",
1168
+ "/usr/local/bin/tmux",
1169
+ "/usr/bin/tmux"
1170
+ ]) {
1171
+ if (existsSync(p)) return p;
1172
+ }
1173
+ return "tmux";
1174
+ }
1175
+ function ts() {
1176
+ return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
1177
+ }
1178
+ function findPort(start = 9100) {
1179
+ return new Promise((resolve, reject) => {
1180
+ const srv = createServer();
1181
+ srv.listen(0, "127.0.0.1", () => {
1182
+ const addr = srv.address();
1183
+ const port = typeof addr === "object" && addr ? addr.port : start;
1184
+ srv.close(() => resolve(port));
1185
+ });
1186
+ srv.on("error", reject);
1187
+ });
1188
+ }
1189
+ var TerminalPeerManager = class {
1190
+ constructor(config) {
1191
+ this.terminals = /* @__PURE__ */ new Map();
1192
+ this.failedSessions = /* @__PURE__ */ new Set();
1193
+ this.pendingStops = /* @__PURE__ */ new Map();
1194
+ this.unsubscribers = /* @__PURE__ */ new Map();
1195
+ this.config = config;
1196
+ this.client = new ConvexClient(config.convexUrl);
1197
+ }
1198
+ watchSession(workSessionId, tmuxSessionName) {
1199
+ if (this.unsubscribers.has(workSessionId)) return;
1200
+ const unsub = this.client.onUpdate(
1201
+ api.agentBridge.bridgePublic.getWorkSessionTerminalState,
1202
+ {
1203
+ deviceId: this.config.deviceId,
1204
+ deviceSecret: this.config.deviceSecret,
1205
+ workSessionId
1206
+ },
1207
+ (state) => {
1208
+ if (!state) return;
1209
+ const terminal = this.terminals.get(workSessionId);
1210
+ if (state.terminalViewerActive && !terminal && !this.failedSessions.has(workSessionId)) {
1211
+ const pendingStop = this.pendingStops.get(workSessionId);
1212
+ if (pendingStop) {
1213
+ clearTimeout(pendingStop);
1214
+ this.pendingStops.delete(workSessionId);
1215
+ }
1216
+ console.log(`[${ts()}] Viewer active for ${tmuxSessionName}`);
1217
+ void this.startTerminal(
1218
+ workSessionId,
1219
+ tmuxSessionName,
1220
+ state.terminalCols,
1221
+ state.terminalRows
1222
+ );
1223
+ } else if (!state.terminalViewerActive && terminal) {
1224
+ if (!this.pendingStops.has(workSessionId)) {
1225
+ this.pendingStops.set(
1226
+ workSessionId,
1227
+ setTimeout(() => {
1228
+ this.pendingStops.delete(workSessionId);
1229
+ console.log(`[${ts()}] Viewer inactive for ${tmuxSessionName}`);
1230
+ this.stopTerminal(workSessionId);
1231
+ this.failedSessions.delete(workSessionId);
1232
+ }, 2e3)
1233
+ );
1234
+ }
1235
+ }
1236
+ }
1237
+ );
1238
+ this.unsubscribers.set(workSessionId, unsub);
1239
+ }
1240
+ unwatchSession(workSessionId) {
1241
+ const unsub = this.unsubscribers.get(workSessionId);
1242
+ if (unsub) {
1243
+ unsub();
1244
+ this.unsubscribers.delete(workSessionId);
1245
+ }
1246
+ this.stopTerminal(workSessionId);
1247
+ }
1248
+ async startTerminal(workSessionId, tmuxSessionName, cols, rows) {
1249
+ if (this.terminals.has(workSessionId)) return;
1250
+ const tmuxBin = findTmuxPath();
1251
+ try {
1252
+ const port = await findPort();
1253
+ console.log(
1254
+ `[${ts()}] Spawning PTY: ${tmuxBin} attach-session -t ${tmuxSessionName}`
1255
+ );
1256
+ const ptyProcess = pty.spawn(
1257
+ tmuxBin,
1258
+ ["attach-session", "-t", tmuxSessionName],
1259
+ {
1260
+ name: "xterm-256color",
1261
+ cols: Math.max(cols, 10),
1262
+ rows: Math.max(rows, 4),
1263
+ cwd: process.env.HOME ?? "/",
1264
+ env: { ...process.env, TERM: "xterm-256color" }
1265
+ }
1266
+ );
1267
+ console.log(`[${ts()}] PTY started for ${tmuxSessionName}`);
1268
+ const token = randomUUID();
1269
+ const httpServer = createServer();
1270
+ const wss = new WebSocketServer({ server: httpServer });
1271
+ wss.on("connection", (ws, req) => {
1272
+ const url = new URL(req.url ?? "/", `http://localhost`);
1273
+ const clientToken = url.searchParams.get("token");
1274
+ if (clientToken !== token) {
1275
+ console.log(`[${ts()}] Rejected unauthorized WebSocket connection`);
1276
+ ws.close(4401, "Unauthorized");
1277
+ return;
1278
+ }
1279
+ console.log(
1280
+ `[${ts()}] WebSocket client connected (${tmuxSessionName})`
1281
+ );
1282
+ const dataHandler = ptyProcess.onData((data) => {
1283
+ if (ws.readyState === WebSocket.OPEN) {
1284
+ ws.send(data);
1285
+ }
1286
+ });
1287
+ ws.on("message", (msg) => {
1288
+ const str = msg.toString();
1289
+ if (str.startsWith("\0{")) {
1290
+ try {
1291
+ const parsed = JSON.parse(str.slice(1));
1292
+ if (parsed.type === "resize" && parsed.cols && parsed.rows) {
1293
+ ptyProcess.resize(
1294
+ Math.max(parsed.cols, 10),
1295
+ Math.max(parsed.rows, 4)
1296
+ );
1297
+ return;
1298
+ }
1299
+ } catch {
1300
+ }
1301
+ }
1302
+ ptyProcess.write(str);
1303
+ });
1304
+ ws.on("close", () => {
1305
+ console.log(
1306
+ `[${ts()}] WebSocket client disconnected (${tmuxSessionName})`
1307
+ );
1308
+ dataHandler.dispose();
1309
+ });
1310
+ });
1311
+ await new Promise((resolve) => {
1312
+ httpServer.listen(port, "127.0.0.1", resolve);
1313
+ });
1314
+ console.log(`[${ts()}] WS server on port ${port}`);
1315
+ console.log(`[${ts()}] Opening tunnel...`);
1316
+ const tunnel = await localtunnel({ port });
1317
+ const tunnelUrl = tunnel.url;
1318
+ console.log(`[${ts()}] Tunnel: ${tunnelUrl}`);
1319
+ const wsUrl = tunnelUrl.replace(/^https?:\/\//, "wss://");
1320
+ const terminal = {
1321
+ ptyProcess,
1322
+ httpServer,
1323
+ wss,
1324
+ tunnel,
1325
+ tunnelUrl: wsUrl,
1326
+ token,
1327
+ workSessionId,
1328
+ tmuxSessionName,
1329
+ port
1330
+ };
1331
+ this.terminals.set(workSessionId, terminal);
1332
+ await this.client.mutation(
1333
+ api.agentBridge.bridgePublic.updateWorkSessionTerminalUrl,
1334
+ {
1335
+ deviceId: this.config.deviceId,
1336
+ deviceSecret: this.config.deviceSecret,
1337
+ workSessionId,
1338
+ terminalUrl: wsUrl,
1339
+ terminalToken: token
1340
+ }
1341
+ );
1342
+ ptyProcess.onExit(() => {
1343
+ console.log(`[${ts()}] PTY exited for ${tmuxSessionName}`);
1344
+ this.stopTerminal(workSessionId);
1345
+ });
1346
+ } catch (err) {
1347
+ console.error(`[${ts()}] Failed to start terminal:`, err);
1348
+ this.failedSessions.add(workSessionId);
1349
+ }
1350
+ }
1351
+ stopTerminal(workSessionId) {
1352
+ const terminal = this.terminals.get(workSessionId);
1353
+ if (!terminal) return;
1354
+ try {
1355
+ terminal.ptyProcess.kill();
1356
+ } catch {
1357
+ }
1358
+ try {
1359
+ terminal.tunnel.close();
1360
+ } catch {
1361
+ }
1362
+ try {
1363
+ terminal.wss.close();
1364
+ } catch {
1365
+ }
1366
+ try {
1367
+ terminal.httpServer.close();
1368
+ } catch {
1369
+ }
1370
+ this.terminals.delete(workSessionId);
1371
+ console.log(`[${ts()}] Terminal stopped for ${terminal.tmuxSessionName}`);
1372
+ }
1373
+ stop() {
1374
+ for (const unsub of this.unsubscribers.values()) {
1375
+ try {
1376
+ unsub();
1377
+ } catch {
1378
+ }
1379
+ }
1380
+ this.unsubscribers.clear();
1381
+ for (const id of this.terminals.keys()) {
1382
+ this.stopTerminal(id);
1383
+ }
1384
+ void this.client.close();
1385
+ }
1386
+ };
1387
+
1388
+ // src/bridge-service.ts
1389
+ import {
1390
+ existsSync as existsSync3,
1391
+ mkdirSync,
1392
+ readFileSync as readFileSync2,
1393
+ writeFileSync,
1394
+ unlinkSync
1395
+ } from "fs";
1396
+ import { homedir as homedir3, hostname, platform } from "os";
1397
+ import { join as join2 } from "path";
1398
+ import { randomUUID as randomUUID2 } from "crypto";
1399
+
1400
+ // src/agent-adapters.ts
1401
+ import { execSync, spawn as spawn2 } from "child_process";
1402
+ import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
1403
+ import { homedir as homedir2, userInfo } from "os";
1404
+ import { basename, join } from "path";
1405
+ var LSOF_PATHS = ["/usr/sbin/lsof", "/usr/bin/lsof"];
1406
+ var VECTOR_BRIDGE_CLIENT_VERSION = "0.1.0";
1407
+ function discoverAttachableSessions() {
1408
+ return dedupeSessions([
1409
+ ...discoverTmuxSessions(),
1410
+ ...discoverCodexSessions(),
1411
+ ...discoverClaudeSessions()
1412
+ ]);
1413
+ }
1414
+ async function resumeProviderSession(provider, sessionKey, cwd, prompt2) {
1415
+ if (provider === "codex") {
1416
+ return runCodexAppServerTurn({
1417
+ cwd,
1418
+ prompt: prompt2,
1419
+ sessionKey,
1420
+ launchCommand: "codex app-server (thread/resume)"
1421
+ });
1422
+ }
1423
+ return runClaudeSdkTurn({
1424
+ cwd,
1425
+ prompt: prompt2,
1426
+ sessionKey,
1427
+ launchCommand: "@anthropic-ai/claude-agent-sdk query(resume)"
1428
+ });
1429
+ }
1430
+ async function runCodexAppServerTurn(args) {
1431
+ const child = spawn2("codex", ["app-server"], {
1432
+ cwd: args.cwd,
1433
+ env: { ...process.env },
1434
+ stdio: ["pipe", "pipe", "pipe"]
1435
+ });
1436
+ let stderr = "";
1437
+ let stdoutBuffer = "";
1438
+ let sessionKey = args.sessionKey;
1439
+ let finalAssistantText = "";
1440
+ let completed = false;
1441
+ let nextRequestId = 1;
1442
+ const pending = /* @__PURE__ */ new Map();
1443
+ let completeTurn;
1444
+ let failTurn;
1445
+ const turnCompleted = new Promise((resolve, reject) => {
1446
+ completeTurn = () => {
1447
+ completed = true;
1448
+ resolve();
1449
+ };
1450
+ failTurn = (error) => {
1451
+ completed = true;
1452
+ reject(error);
1453
+ };
1454
+ });
1455
+ child.stdout.on("data", (chunk) => {
1456
+ stdoutBuffer += chunk.toString();
1457
+ while (true) {
1458
+ const newlineIndex = stdoutBuffer.indexOf("\n");
1459
+ if (newlineIndex < 0) {
1460
+ break;
1461
+ }
1462
+ const line = stdoutBuffer.slice(0, newlineIndex).trim();
1463
+ stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
1464
+ if (!line) {
1465
+ continue;
1466
+ }
1467
+ const payload = tryParseJson(line);
1468
+ if (!payload || typeof payload !== "object") {
1469
+ continue;
1470
+ }
1471
+ const responseId = payload.id;
1472
+ if (typeof responseId === "number" && pending.has(responseId)) {
1473
+ const entry = pending.get(responseId);
1474
+ pending.delete(responseId);
1475
+ const errorRecord = asObject(payload.error);
1476
+ if (errorRecord) {
1477
+ entry.reject(
1478
+ new Error(
1479
+ `codex app-server error: ${asString(errorRecord.message) ?? "Unknown JSON-RPC error"}`
1480
+ )
1481
+ );
1482
+ continue;
1483
+ }
1484
+ entry.resolve(payload.result);
1485
+ continue;
1486
+ }
1487
+ const method = asString(payload.method);
1488
+ const params = asObject(payload.params);
1489
+ if (!method || !params) {
1490
+ continue;
1491
+ }
1492
+ if (method === "thread/started") {
1493
+ sessionKey = asString(asObject(params.thread)?.id) ?? asString(asObject(params.thread)?.threadId) ?? sessionKey;
1494
+ continue;
1495
+ }
1496
+ if (method === "item/agentMessage/delta") {
1497
+ finalAssistantText += asString(params.delta) ?? "";
1498
+ continue;
1499
+ }
1500
+ if (method === "item/completed") {
1501
+ const item = asObject(params.item);
1502
+ if (asString(item?.type) === "agentMessage") {
1503
+ finalAssistantText = asString(item?.text) ?? finalAssistantText;
1504
+ }
1505
+ continue;
1506
+ }
1507
+ if (method === "turn/completed") {
1508
+ const turn = asObject(params.turn);
1509
+ const status = asString(turn?.status);
1510
+ if (status === "failed") {
1511
+ const turnError = asObject(turn?.error);
1512
+ failTurn?.(
1513
+ new Error(
1514
+ asString(turnError?.message) ?? "Codex turn failed without an error message"
1515
+ )
1516
+ );
1517
+ } else if (status === "interrupted") {
1518
+ failTurn?.(new Error("Codex turn was interrupted"));
1519
+ } else {
1520
+ completeTurn?.();
1521
+ }
1522
+ }
1523
+ }
1524
+ });
1525
+ child.stderr.on("data", (chunk) => {
1526
+ stderr += chunk.toString();
1527
+ });
1528
+ const request = (method, params) => new Promise((resolve, reject) => {
1529
+ const id = nextRequestId++;
1530
+ pending.set(id, { resolve, reject });
1531
+ child.stdin.write(`${JSON.stringify({ method, id, params })}
1532
+ `);
1533
+ });
1534
+ const notify = (method, params) => {
1535
+ child.stdin.write(`${JSON.stringify({ method, params })}
1536
+ `);
1537
+ };
1538
+ const waitForExit = new Promise((_, reject) => {
1539
+ child.on("error", (error) => reject(error));
1540
+ child.on("close", (code) => {
1541
+ if (!completed) {
1542
+ const detail = stderr.trim() || `codex app-server exited with code ${code}`;
1543
+ reject(new Error(detail));
1544
+ }
1545
+ });
1546
+ });
1547
+ try {
1548
+ await Promise.race([
1549
+ request("initialize", {
1550
+ clientInfo: {
1551
+ name: "vector_bridge",
1552
+ title: "Vector Bridge",
1553
+ version: VECTOR_BRIDGE_CLIENT_VERSION
1554
+ }
1555
+ }),
1556
+ waitForExit
1557
+ ]);
1558
+ notify("initialized", {});
1559
+ const threadResult = await Promise.race([
1560
+ args.sessionKey ? request("thread/resume", {
1561
+ threadId: args.sessionKey,
1562
+ cwd: args.cwd,
1563
+ approvalPolicy: "never",
1564
+ personality: "pragmatic"
1565
+ }) : request("thread/start", {
1566
+ cwd: args.cwd,
1567
+ approvalPolicy: "never",
1568
+ personality: "pragmatic",
1569
+ serviceName: "vector_bridge"
1570
+ }),
1571
+ waitForExit
1572
+ ]);
1573
+ sessionKey = asString(asObject(threadResult.thread)?.id) ?? asString(asObject(threadResult.thread)?.threadId) ?? sessionKey;
1574
+ if (!sessionKey) {
1575
+ throw new Error("Codex app-server did not return a thread id");
1576
+ }
1577
+ await Promise.race([
1578
+ request("turn/start", {
1579
+ threadId: sessionKey,
1580
+ input: [{ type: "text", text: args.prompt }],
1581
+ cwd: args.cwd,
1582
+ approvalPolicy: "never",
1583
+ personality: "pragmatic"
1584
+ }),
1585
+ waitForExit
1586
+ ]);
1587
+ await Promise.race([turnCompleted, waitForExit]);
1588
+ const gitInfo = getGitInfo(args.cwd);
1589
+ return {
1590
+ provider: "codex",
1591
+ providerLabel: "Codex",
1592
+ sessionKey,
1593
+ cwd: args.cwd,
1594
+ ...gitInfo,
1595
+ title: summarizeTitle(void 0, args.cwd),
1596
+ mode: "managed",
1597
+ status: "waiting",
1598
+ supportsInboundMessages: true,
1599
+ responseText: finalAssistantText.trim() || void 0,
1600
+ launchCommand: args.launchCommand
1601
+ };
1602
+ } finally {
1603
+ for (const entry of pending.values()) {
1604
+ entry.reject(
1605
+ new Error("codex app-server closed before request resolved")
1606
+ );
1607
+ }
1608
+ pending.clear();
1609
+ child.kill();
1610
+ }
1611
+ }
1612
+ async function runClaudeSdkTurn(args) {
1613
+ const { query } = await import("@anthropic-ai/claude-agent-sdk");
1614
+ const stream = query({
1615
+ prompt: args.prompt,
1616
+ options: {
1617
+ cwd: args.cwd,
1618
+ resume: args.sessionKey,
1619
+ persistSession: true,
1620
+ permissionMode: "bypassPermissions",
1621
+ allowDangerouslySkipPermissions: true,
1622
+ env: {
1623
+ ...process.env,
1624
+ CLAUDE_AGENT_SDK_CLIENT_APP: `vector-bridge/${VECTOR_BRIDGE_CLIENT_VERSION}`
1625
+ }
1626
+ }
1627
+ });
1628
+ let sessionKey = args.sessionKey;
1629
+ let responseText = "";
1630
+ let model;
1631
+ try {
1632
+ for await (const message of stream) {
1633
+ if (!message || typeof message !== "object") {
1634
+ continue;
1635
+ }
1636
+ sessionKey = asString(message.session_id) ?? sessionKey;
1637
+ if (message.type === "assistant") {
1638
+ const assistantText = extractClaudeMessageTexts(
1639
+ message.message
1640
+ ).join("\n\n").trim();
1641
+ if (assistantText) {
1642
+ responseText = assistantText;
1643
+ }
1644
+ continue;
1645
+ }
1646
+ if (message.type !== "result") {
1647
+ continue;
1648
+ }
1649
+ if (message.subtype === "success") {
1650
+ const resultText = asString(message.result);
1651
+ if (resultText) {
1652
+ responseText = resultText;
1653
+ }
1654
+ model = firstObjectKey(
1655
+ message.modelUsage
1656
+ );
1657
+ continue;
1658
+ }
1659
+ const errors = message.errors;
1660
+ const detail = Array.isArray(errors) && errors.length > 0 ? errors.join("\n") : "Claude execution failed";
1661
+ throw new Error(detail);
1662
+ }
1663
+ } finally {
1664
+ stream.close();
1665
+ }
1666
+ if (!sessionKey) {
1667
+ throw new Error("Claude Agent SDK did not return a session id");
1668
+ }
1669
+ const gitInfo = getGitInfo(args.cwd);
1670
+ return {
1671
+ provider: "claude_code",
1672
+ providerLabel: "Claude",
1673
+ sessionKey,
1674
+ cwd: args.cwd,
1675
+ ...gitInfo,
1676
+ title: summarizeTitle(void 0, args.cwd),
1677
+ model,
1678
+ mode: "managed",
1679
+ status: "waiting",
1680
+ supportsInboundMessages: true,
1681
+ responseText: responseText.trim() || void 0,
1682
+ launchCommand: args.launchCommand
1683
+ };
1684
+ }
1685
+ function discoverCodexSessions() {
1686
+ const historyBySession = buildCodexHistoryIndex();
1687
+ return listLiveProcessIds("codex").flatMap((pid) => {
1688
+ const transcriptPath = getCodexTranscriptPath(pid);
1689
+ if (!transcriptPath) {
1690
+ return [];
1691
+ }
1692
+ const processCwd = getProcessCwd(pid);
1693
+ const parsed = parseObservedCodexSession(
1694
+ pid,
1695
+ transcriptPath,
1696
+ processCwd,
1697
+ historyBySession
1698
+ );
1699
+ return parsed ? [parsed] : [];
1700
+ }).sort(compareObservedSessions);
1701
+ }
1702
+ function discoverClaudeSessions() {
1703
+ const historyBySession = buildClaudeHistoryIndex();
1704
+ return listLiveProcessIds("claude").flatMap((pid) => {
1705
+ const sessionMeta = readClaudePidSession(pid);
1706
+ if (!sessionMeta?.sessionId) {
1707
+ return [];
1708
+ }
1709
+ const transcriptPath = findClaudeTranscriptPath(sessionMeta.sessionId);
1710
+ const parsed = parseObservedClaudeSession(
1711
+ pid,
1712
+ sessionMeta,
1713
+ transcriptPath,
1714
+ historyBySession
1715
+ );
1716
+ return parsed ? [parsed] : [];
1717
+ }).sort(compareObservedSessions);
1718
+ }
1719
+ function discoverTmuxSessions() {
1720
+ try {
1721
+ const output = execSync(
1722
+ "tmux list-panes -a -F '#{pane_id} #{pane_pid} #{session_name} #{window_name} #{pane_current_path} #{pane_current_command} #{pane_title}'",
1723
+ {
1724
+ encoding: "utf-8",
1725
+ timeout: 3e3
1726
+ }
1727
+ );
1728
+ return output.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
1729
+ const [
1730
+ paneId,
1731
+ panePid,
1732
+ sessionName,
1733
+ windowName,
1734
+ cwd,
1735
+ currentCommand,
1736
+ paneTitle
1737
+ ] = line.split(" ");
1738
+ if (!paneId || !panePid || !sessionName || !windowName || !cwd) {
1739
+ return [];
1740
+ }
1741
+ const normalizedCommand = (currentCommand ?? "").trim().toLowerCase();
1742
+ if (normalizedCommand === "codex" || normalizedCommand === "claude") {
1743
+ return [];
1744
+ }
1745
+ const gitInfo = getGitInfo(cwd);
1746
+ const title = summarizeTitle(
1747
+ buildTmuxPaneTitle({
1748
+ paneTitle,
1749
+ sessionName,
1750
+ windowName,
1751
+ cwd,
1752
+ currentCommand
1753
+ }),
1754
+ cwd
1755
+ );
1756
+ return [
1757
+ {
1758
+ provider: "vector_cli",
1759
+ providerLabel: "Tmux",
1760
+ localProcessId: panePid,
1761
+ sessionKey: `tmux:${paneId}`,
1762
+ cwd,
1763
+ ...gitInfo,
1764
+ title,
1765
+ tmuxSessionName: sessionName,
1766
+ tmuxWindowName: windowName,
1767
+ tmuxPaneId: paneId,
1768
+ mode: "observed",
1769
+ status: "observed",
1770
+ supportsInboundMessages: true
1771
+ }
1772
+ ];
1773
+ }).sort(compareObservedSessions);
1774
+ } catch {
1775
+ return [];
1776
+ }
1777
+ }
1778
+ function getCodexHistoryFile() {
1779
+ return join(getRealHomeDir(), ".codex", "history.jsonl");
1780
+ }
1781
+ function getClaudeProjectsDir() {
1782
+ return join(getRealHomeDir(), ".claude", "projects");
1783
+ }
1784
+ function getClaudeSessionStateDir() {
1785
+ return join(getRealHomeDir(), ".claude", "sessions");
1786
+ }
1787
+ function getClaudeHistoryFile() {
1788
+ return join(getRealHomeDir(), ".claude", "history.jsonl");
1789
+ }
1790
+ function getRealHomeDir() {
1791
+ try {
1792
+ const realHome = userInfo().homedir?.trim();
1793
+ if (realHome) {
1794
+ return realHome;
1795
+ }
1796
+ } catch {
1797
+ }
1798
+ return homedir2();
1799
+ }
1800
+ function resolveExecutable(fallbackCommand, absoluteCandidates) {
1801
+ for (const candidate of absoluteCandidates) {
1802
+ if (existsSync2(candidate)) {
1803
+ return candidate;
1804
+ }
1805
+ }
1806
+ try {
1807
+ const output = execSync(`command -v ${fallbackCommand}`, {
1808
+ encoding: "utf-8",
1809
+ timeout: 1e3
1810
+ }).trim();
1811
+ return output || void 0;
1812
+ } catch {
1813
+ return void 0;
1814
+ }
1815
+ }
1816
+ function listLiveProcessIds(commandName) {
1817
+ try {
1818
+ const output = execSync("ps -axo pid=,comm=", {
1819
+ encoding: "utf-8",
1820
+ timeout: 3e3
1821
+ });
1822
+ return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/, 2)).filter(([, command]) => command === commandName).map(([pid]) => pid).filter(Boolean);
1823
+ } catch {
1824
+ return [];
1825
+ }
1826
+ }
1827
+ function getProcessCwd(pid) {
1828
+ const lsofCommand = resolveExecutable("lsof", LSOF_PATHS);
1829
+ if (!lsofCommand) {
1830
+ return void 0;
1831
+ }
1832
+ try {
1833
+ const output = execSync(`${lsofCommand} -a -p ${pid} -Fn -d cwd`, {
1834
+ encoding: "utf-8",
1835
+ timeout: 3e3
1836
+ });
1837
+ return output.split("\n").map((line) => line.trim()).find((line) => line.startsWith("n"))?.slice(1);
1838
+ } catch {
1839
+ return void 0;
1840
+ }
1841
+ }
1842
+ function getCodexTranscriptPath(pid) {
1843
+ const lsofCommand = resolveExecutable("lsof", LSOF_PATHS);
1844
+ if (!lsofCommand) {
1845
+ return void 0;
1846
+ }
1847
+ try {
1848
+ const output = execSync(`${lsofCommand} -p ${pid} -Fn`, {
1849
+ encoding: "utf-8",
1850
+ timeout: 3e3
1851
+ });
1852
+ return output.split("\n").map((line) => line.trim()).find(
1853
+ (line) => line.startsWith("n") && line.includes("/.codex/sessions/") && line.endsWith(".jsonl")
1854
+ )?.slice(1);
1855
+ } catch {
1856
+ return void 0;
1857
+ }
1858
+ }
1859
+ function readClaudePidSession(pid) {
1860
+ const path3 = join(getClaudeSessionStateDir(), `${pid}.json`);
1861
+ if (!existsSync2(path3)) {
1862
+ return null;
1863
+ }
1864
+ try {
1865
+ const payload = JSON.parse(readFileSync(path3, "utf-8"));
1866
+ const sessionId = asString(payload.sessionId);
1867
+ if (!sessionId) {
1868
+ return null;
1869
+ }
1870
+ return {
1871
+ sessionId,
1872
+ cwd: asString(payload.cwd),
1873
+ startedAt: typeof payload.startedAt === "number" ? payload.startedAt : void 0
1874
+ };
1875
+ } catch {
1876
+ return null;
1877
+ }
1878
+ }
1879
+ function findClaudeTranscriptPath(sessionId) {
1880
+ return findJsonlFileByStem(getClaudeProjectsDir(), sessionId);
1881
+ }
1882
+ function findJsonlFileByStem(root, stem) {
1883
+ if (!existsSync2(root)) {
1884
+ return void 0;
1885
+ }
1886
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
1887
+ const path3 = join(root, entry.name);
1888
+ if (entry.isDirectory()) {
1889
+ const nested = findJsonlFileByStem(path3, stem);
1890
+ if (nested) {
1891
+ return nested;
1892
+ }
1893
+ continue;
1894
+ }
1895
+ if (entry.isFile() && entry.name === `${stem}.jsonl`) {
1896
+ return path3;
1897
+ }
1898
+ }
1899
+ return void 0;
1900
+ }
1901
+ function readJsonLines(path3) {
1902
+ try {
1903
+ return readFileSync(path3, "utf-8").split("\n").map((line) => line.trim()).filter(Boolean).map(tryParseJson).filter(Boolean);
1904
+ } catch {
1905
+ return [];
1906
+ }
1907
+ }
1908
+ function tryParseJson(value) {
1909
+ try {
1910
+ return JSON.parse(value);
1911
+ } catch {
1912
+ return null;
1913
+ }
1914
+ }
1915
+ function dedupeSessions(sessions) {
1916
+ const seen = /* @__PURE__ */ new Set();
1917
+ return sessions.filter((session) => {
1918
+ const key = `${session.provider}:${session.localProcessId ?? session.sessionKey}`;
1919
+ if (seen.has(key)) {
1920
+ return false;
1921
+ }
1922
+ seen.add(key);
1923
+ return true;
1924
+ });
1925
+ }
1926
+ function compareObservedSessions(a, b) {
1927
+ return Number(b.localProcessId ?? 0) - Number(a.localProcessId ?? 0);
1928
+ }
1929
+ function parseObservedCodexSession(pid, transcriptPath, processCwd, historyBySession) {
1930
+ const entries = readJsonLines(transcriptPath);
1931
+ let sessionKey;
1932
+ let cwd = processCwd;
1933
+ const userMessages = [];
1934
+ const assistantMessages = [];
1935
+ for (const rawEntry of entries) {
1936
+ const entry = asObject(rawEntry);
1937
+ if (!entry) {
1938
+ continue;
1939
+ }
1940
+ if (entry.type === "session_meta") {
1941
+ const payload = asObject(entry.payload);
1942
+ sessionKey = asString(payload?.id) ?? sessionKey;
1943
+ cwd = asString(payload?.cwd) ?? cwd;
1944
+ }
1945
+ if (entry.type === "event_msg") {
1946
+ const payload = asObject(entry.payload);
1947
+ if (payload?.type === "user_message") {
1948
+ pushIfPresent(userMessages, payload.message);
1949
+ }
1950
+ }
1951
+ if (entry.type === "response_item" && asObject(entry.payload)?.type === "message" && asObject(entry.payload)?.role === "user") {
1952
+ userMessages.push(
1953
+ ...extractTextSegments(asObject(entry.payload)?.content)
1954
+ );
1955
+ }
1956
+ if (entry.type === "event_msg") {
1957
+ const payload = asObject(entry.payload);
1958
+ if (payload?.type === "agent_message") {
1959
+ pushIfPresent(assistantMessages, payload.message);
1960
+ }
1961
+ }
1962
+ if (entry.type === "response_item" && asObject(entry.payload)?.type === "message" && asObject(entry.payload)?.role === "assistant") {
1963
+ assistantMessages.push(
1964
+ ...extractTextSegments(asObject(entry.payload)?.content)
1965
+ );
1966
+ }
1967
+ }
1968
+ if (!sessionKey) {
1969
+ return null;
1970
+ }
1971
+ const gitInfo = cwd ? getGitInfo(cwd) : {};
1972
+ const historyTitle = sessionKey ? selectSessionTitle(historyBySession?.get(sessionKey) ?? []) : void 0;
1973
+ return {
1974
+ provider: "codex",
1975
+ providerLabel: "Codex",
1976
+ localProcessId: pid,
1977
+ sessionKey,
1978
+ cwd,
1979
+ ...gitInfo,
1980
+ title: summarizeTitle(
1981
+ historyTitle ?? selectSessionTitle(userMessages) ?? selectSessionTitle(assistantMessages),
1982
+ cwd
1983
+ ),
1984
+ mode: "observed",
1985
+ status: "observed",
1986
+ supportsInboundMessages: true
1987
+ };
1988
+ }
1989
+ function parseObservedClaudeSession(pid, sessionMeta, transcriptPath, historyBySession) {
1990
+ const entries = transcriptPath ? readJsonLines(transcriptPath) : [];
1991
+ let cwd = sessionMeta.cwd;
1992
+ let branch;
1993
+ let model;
1994
+ const userMessages = [];
1995
+ const assistantMessages = [];
1996
+ for (const rawEntry of entries) {
1997
+ const entry = asObject(rawEntry);
1998
+ if (!entry) {
1999
+ continue;
2000
+ }
2001
+ cwd = asString(entry.cwd) ?? cwd;
2002
+ branch = asString(entry.gitBranch) ?? branch;
2003
+ if (entry.type === "user") {
2004
+ userMessages.push(...extractClaudeMessageTexts(entry.message));
2005
+ }
2006
+ if (entry.type === "assistant") {
2007
+ const message = asObject(entry.message);
2008
+ model = asString(message?.model) ?? model;
2009
+ assistantMessages.push(...extractClaudeMessageTexts(entry.message));
2010
+ }
2011
+ }
2012
+ const gitInfo = cwd ? getGitInfo(cwd) : {};
2013
+ const historyTitle = selectSessionTitle(
2014
+ historyBySession?.get(sessionMeta.sessionId) ?? []
2015
+ );
2016
+ return {
2017
+ provider: "claude_code",
2018
+ providerLabel: "Claude",
2019
+ localProcessId: pid,
2020
+ sessionKey: sessionMeta.sessionId,
2021
+ cwd,
2022
+ repoRoot: gitInfo.repoRoot,
2023
+ branch: branch ?? gitInfo.branch,
2024
+ title: summarizeTitle(
2025
+ historyTitle ?? selectSessionTitle(userMessages) ?? selectSessionTitle(assistantMessages),
2026
+ cwd
2027
+ ),
2028
+ model,
2029
+ mode: "observed",
2030
+ status: "observed",
2031
+ supportsInboundMessages: true
2032
+ };
2033
+ }
2034
+ function summarizeTitle(message, cwd) {
2035
+ if (message) {
2036
+ return truncate(message.replace(/\s+/g, " ").trim(), 96);
2037
+ }
2038
+ if (cwd) {
2039
+ return basename(cwd);
2040
+ }
2041
+ return "Local session";
2042
+ }
2043
+ function buildTmuxPaneTitle(args) {
2044
+ const paneTitle = cleanSessionTitleCandidate(args.paneTitle ?? "");
2045
+ if (paneTitle) {
2046
+ return paneTitle;
2047
+ }
2048
+ const command = asString(args.currentCommand);
2049
+ if (command && !["zsh", "bash", "fish", "sh", "nu"].includes(command)) {
2050
+ return `${command} in ${basename(args.cwd)}`;
2051
+ }
2052
+ return `${basename(args.cwd)} (${args.sessionName}:${args.windowName})`;
2053
+ }
2054
+ function truncate(value, maxLength) {
2055
+ return value.length > maxLength ? `${value.slice(0, maxLength - 3).trimEnd()}...` : value;
2056
+ }
2057
+ function firstObjectKey(value) {
2058
+ if (!value || typeof value !== "object") {
2059
+ return void 0;
2060
+ }
2061
+ const [firstKey] = Object.keys(value);
2062
+ return firstKey ? normalizeModelKey(firstKey) : void 0;
2063
+ }
2064
+ function normalizeModelKey(value) {
2065
+ const normalized = stripAnsi(value).replace(/\[\d+(?:;\d+)*m$/g, "").trim();
2066
+ return normalized || void 0;
2067
+ }
2068
+ function asObject(value) {
2069
+ return value && typeof value === "object" ? value : void 0;
2070
+ }
2071
+ function asString(value) {
2072
+ return typeof value === "string" && value.trim() ? value : void 0;
2073
+ }
2074
+ function pushIfPresent(target, value) {
2075
+ const text2 = asString(value);
2076
+ if (text2) {
2077
+ target.push(text2);
2078
+ }
2079
+ }
2080
+ function extractClaudeMessageTexts(message) {
2081
+ if (!message || typeof message !== "object") {
2082
+ return [];
2083
+ }
2084
+ return extractTextSegments(message.content);
2085
+ }
2086
+ function extractTextSegments(value) {
2087
+ if (typeof value === "string") {
2088
+ return [value];
2089
+ }
2090
+ if (!Array.isArray(value)) {
2091
+ return [];
2092
+ }
2093
+ return value.flatMap(extractTextSegmentFromBlock).filter(Boolean);
2094
+ }
2095
+ function extractTextSegmentFromBlock(block) {
2096
+ if (!block || typeof block !== "object") {
2097
+ return [];
2098
+ }
2099
+ const typedBlock = block;
2100
+ const blockType = asString(typedBlock.type);
2101
+ if (blockType && isIgnoredContentBlockType(blockType)) {
2102
+ return [];
2103
+ }
2104
+ const directText = asString(typedBlock.text);
2105
+ if (directText) {
2106
+ return [directText];
2107
+ }
2108
+ if (typeof typedBlock.content === "string") {
2109
+ return [typedBlock.content];
2110
+ }
2111
+ return [];
2112
+ }
2113
+ function isIgnoredContentBlockType(blockType) {
2114
+ return [
2115
+ "tool_result",
2116
+ "tool_use",
2117
+ "image",
2118
+ "thinking",
2119
+ "reasoning",
2120
+ "contextCompaction"
2121
+ ].includes(blockType);
2122
+ }
2123
+ function buildCodexHistoryIndex() {
2124
+ const historyBySession = /* @__PURE__ */ new Map();
2125
+ for (const rawEntry of readJsonLines(getCodexHistoryFile())) {
2126
+ const entry = asObject(rawEntry);
2127
+ if (!entry) {
2128
+ continue;
2129
+ }
2130
+ const sessionId = asString(entry.session_id);
2131
+ const text2 = asString(entry.text);
2132
+ if (!sessionId || !text2) {
2133
+ continue;
2134
+ }
2135
+ appendHistoryEntry(historyBySession, sessionId, text2);
2136
+ }
2137
+ return historyBySession;
2138
+ }
2139
+ function buildClaudeHistoryIndex() {
2140
+ const historyBySession = /* @__PURE__ */ new Map();
2141
+ for (const rawEntry of readJsonLines(getClaudeHistoryFile())) {
2142
+ const entry = asObject(rawEntry);
2143
+ if (!entry) {
2144
+ continue;
2145
+ }
2146
+ const sessionId = asString(entry.sessionId);
2147
+ if (!sessionId) {
2148
+ continue;
2149
+ }
2150
+ const texts = extractClaudeHistoryTexts(entry);
2151
+ for (const text2 of texts) {
2152
+ appendHistoryEntry(historyBySession, sessionId, text2);
2153
+ }
2154
+ }
2155
+ return historyBySession;
2156
+ }
2157
+ function appendHistoryEntry(historyBySession, sessionId, text2) {
2158
+ const existing = historyBySession.get(sessionId);
2159
+ if (existing) {
2160
+ existing.push(text2);
2161
+ return;
2162
+ }
2163
+ historyBySession.set(sessionId, [text2]);
2164
+ }
2165
+ function extractClaudeHistoryTexts(entry) {
2166
+ if (!entry || typeof entry !== "object") {
2167
+ return [];
2168
+ }
2169
+ const record = entry;
2170
+ const pastedTexts = extractClaudePastedTexts(record.pastedContents);
2171
+ if (pastedTexts.length > 0) {
2172
+ return pastedTexts;
2173
+ }
2174
+ const display = asString(record.display);
2175
+ return display ? [display] : [];
2176
+ }
2177
+ function extractClaudePastedTexts(value) {
2178
+ if (!value || typeof value !== "object") {
2179
+ return [];
2180
+ }
2181
+ return Object.values(value).flatMap((item) => {
2182
+ if (!item || typeof item !== "object") {
2183
+ return [];
2184
+ }
2185
+ const record = item;
2186
+ return record.type === "text" && typeof record.content === "string" ? [record.content] : [];
2187
+ }).filter(Boolean);
2188
+ }
2189
+ function selectSessionTitle(messages) {
2190
+ for (const message of messages) {
2191
+ const cleaned = cleanSessionTitleCandidate(message);
2192
+ if (cleaned) {
2193
+ return cleaned;
2194
+ }
2195
+ }
2196
+ return void 0;
2197
+ }
2198
+ function cleanSessionTitleCandidate(message) {
2199
+ const normalized = stripAnsi(message).replace(/\s+/g, " ").trim();
2200
+ if (!normalized) {
2201
+ return void 0;
2202
+ }
2203
+ if (normalized.length < 4) {
2204
+ return void 0;
2205
+ }
2206
+ if (normalized.startsWith("/") || looksLikeGeneratedTagEnvelope(normalized) || looksLikeGeneratedImageSummary(normalized) || looksLikeStandaloneImagePath(normalized) || looksLikeInstructionScaffold(normalized)) {
2207
+ return void 0;
2208
+ }
2209
+ return normalized;
2210
+ }
2211
+ function looksLikeGeneratedTagEnvelope(value) {
2212
+ return /^<[\w:-]+>[\s\S]*<\/[\w:-]+>$/.test(value);
2213
+ }
2214
+ function looksLikeGeneratedImageSummary(value) {
2215
+ return /^\[image:/i.test(value) || /displayed at/i.test(value) && /coordinates/i.test(value);
2216
+ }
2217
+ function looksLikeStandaloneImagePath(value) {
2218
+ return /^\/\S+\.(png|jpe?g|gif|webp|heic|bmp)$/i.test(value) || /^file:\S+\.(png|jpe?g|gif|webp|heic|bmp)$/i.test(value);
2219
+ }
2220
+ function looksLikeInstructionScaffold(value) {
2221
+ if (value.length < 700) {
2222
+ return false;
2223
+ }
2224
+ const headingCount = value.match(/^#{1,3}\s/gm)?.length ?? 0;
2225
+ const tagCount = value.match(/<\/?[\w:-]+>/g)?.length ?? 0;
2226
+ const bulletCount = value.match(/^\s*[-*]\s/gm)?.length ?? 0;
2227
+ return headingCount + tagCount + bulletCount >= 6;
2228
+ }
2229
+ function stripAnsi(value) {
2230
+ return value.replace(/\u001B\[[0-9;]*m/g, "");
2231
+ }
2232
+ function getGitInfo(cwd) {
2233
+ try {
2234
+ const repoRoot = execSync("git rev-parse --show-toplevel", {
2235
+ encoding: "utf-8",
2236
+ cwd,
2237
+ timeout: 3e3
2238
+ }).trim();
2239
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
2240
+ encoding: "utf-8",
2241
+ cwd,
2242
+ timeout: 3e3
2243
+ }).trim();
2244
+ return {
2245
+ repoRoot: repoRoot || void 0,
2246
+ branch: branch || void 0
2247
+ };
2248
+ } catch {
2249
+ return {};
2250
+ }
2251
+ }
2252
+
2253
+ // src/bridge-service.ts
2254
+ var CONFIG_DIR = process.env.VECTOR_HOME?.trim() || join2(homedir3(), ".vector");
2255
+ var BRIDGE_CONFIG_FILE = join2(CONFIG_DIR, "bridge.json");
2256
+ var DEVICE_KEY_FILE = join2(CONFIG_DIR, "device-key");
2257
+ var PID_FILE = join2(CONFIG_DIR, "bridge.pid");
2258
+ var LIVE_ACTIVITIES_CACHE = join2(CONFIG_DIR, "live-activities.json");
2259
+ var LAUNCHAGENT_DIR = join2(homedir3(), "Library", "LaunchAgents");
2260
+ var LAUNCHAGENT_PLIST = join2(LAUNCHAGENT_DIR, "com.vector.bridge.plist");
2261
+ var LAUNCHAGENT_LABEL = "com.vector.bridge";
2262
+ var LEGACY_MENUBAR_LAUNCHAGENT_LABEL = "com.vector.menubar";
2263
+ var LEGACY_MENUBAR_LAUNCHAGENT_PLIST = join2(
2264
+ LAUNCHAGENT_DIR,
2265
+ `${LEGACY_MENUBAR_LAUNCHAGENT_LABEL}.plist`
2266
+ );
2267
+ var HEARTBEAT_INTERVAL_MS = 3e4;
2268
+ var COMMAND_POLL_INTERVAL_MS = 5e3;
2269
+ var LIVE_ACTIVITY_SYNC_INTERVAL_MS = 5e3;
2270
+ var PROCESS_DISCOVERY_INTERVAL_MS = 6e4;
2271
+ function loadBridgeConfig() {
2272
+ if (!existsSync3(BRIDGE_CONFIG_FILE)) return null;
2273
+ try {
2274
+ return JSON.parse(readFileSync2(BRIDGE_CONFIG_FILE, "utf-8"));
2275
+ } catch {
2276
+ return null;
2277
+ }
2278
+ }
2279
+ function saveBridgeConfig(config) {
2280
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2281
+ writeFileSync(BRIDGE_CONFIG_FILE, JSON.stringify(config, null, 2));
2282
+ persistDeviceKey(config.deviceKey);
2283
+ }
2284
+ function writeLiveActivitiesCache(activities) {
2285
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2286
+ writeFileSync(LIVE_ACTIVITIES_CACHE, JSON.stringify(activities, null, 2));
2287
+ }
2288
+ var BridgeService = class {
2289
+ constructor(config) {
2290
+ this.timers = [];
2291
+ this.terminalPeer = null;
2292
+ this.config = config;
2293
+ this.client = new ConvexHttpClient2(config.convexUrl);
2294
+ }
2295
+ async heartbeat() {
2296
+ await this.client.mutation(api.agentBridge.bridgePublic.heartbeat, {
2297
+ deviceId: this.config.deviceId,
2298
+ deviceSecret: this.config.deviceSecret
2299
+ });
2300
+ }
2301
+ async pollCommands() {
2302
+ const commands = await this.client.query(
2303
+ api.agentBridge.bridgePublic.getPendingCommands,
2304
+ {
2305
+ deviceId: this.config.deviceId,
2306
+ deviceSecret: this.config.deviceSecret
2307
+ }
2308
+ );
2309
+ if (commands.length > 0) {
2310
+ console.log(`[${ts2()}] ${commands.length} pending command(s)`);
2311
+ }
2312
+ for (const cmd of commands) {
2313
+ await this.handleCommand(cmd);
2314
+ }
2315
+ }
2316
+ async handleCommand(cmd) {
2317
+ const claimed = await this.client.mutation(
2318
+ api.agentBridge.bridgePublic.claimCommand,
2319
+ {
2320
+ deviceId: this.config.deviceId,
2321
+ deviceSecret: this.config.deviceSecret,
2322
+ commandId: cmd._id
2323
+ }
2324
+ );
2325
+ if (!claimed) {
2326
+ return;
2327
+ }
2328
+ console.log(` ${cmd.kind}: ${cmd._id}`);
2329
+ try {
2330
+ switch (cmd.kind) {
2331
+ case "message":
2332
+ await this.handleMessageCommand(cmd);
2333
+ await this.completeCommand(cmd._id, "delivered");
2334
+ return;
2335
+ case "launch":
2336
+ await this.handleLaunchCommand(cmd);
2337
+ await this.completeCommand(cmd._id, "delivered");
2338
+ return;
2339
+ case "resize":
2340
+ await this.handleResizeCommand(cmd);
2341
+ await this.completeCommand(cmd._id, "delivered");
2342
+ return;
2343
+ default:
2344
+ throw new Error(`Unsupported bridge command: ${cmd.kind}`);
2345
+ }
2346
+ } catch (error) {
2347
+ const message = error instanceof Error ? error.message : "Unknown bridge error";
2348
+ console.error(` ! ${message}`);
2349
+ await this.postCommandError(cmd, message);
2350
+ await this.completeCommand(cmd._id, "failed");
2351
+ }
2352
+ }
2353
+ async reportProcesses() {
2354
+ const processes = discoverAttachableSessions();
2355
+ const activeSessionKeys = processes.map((proc) => proc.sessionKey).filter((value) => Boolean(value));
2356
+ const activeLocalProcessIds = processes.map((proc) => proc.localProcessId).filter((value) => Boolean(value));
2357
+ for (const proc of processes) {
2358
+ try {
2359
+ await this.reportProcess(proc);
2360
+ } catch {
2361
+ }
2362
+ }
2363
+ try {
2364
+ await this.client.mutation(
2365
+ api.agentBridge.bridgePublic.reconcileObservedProcesses,
2366
+ {
2367
+ deviceId: this.config.deviceId,
2368
+ deviceSecret: this.config.deviceSecret,
2369
+ activeSessionKeys,
2370
+ activeLocalProcessIds
2371
+ }
2372
+ );
2373
+ } catch {
2374
+ }
2375
+ if (processes.length > 0) {
2376
+ console.log(
2377
+ `[${ts2()}] Discovered ${processes.length} attachable session(s)`
2378
+ );
2379
+ }
2380
+ }
2381
+ async refreshLiveActivities() {
2382
+ try {
2383
+ const activities = await this.client.query(
2384
+ api.agentBridge.bridgePublic.getDeviceLiveActivities,
2385
+ {
2386
+ deviceId: this.config.deviceId,
2387
+ deviceSecret: this.config.deviceSecret
2388
+ }
2389
+ );
2390
+ writeLiveActivitiesCache(activities);
2391
+ await this.syncWorkSessionTerminals(activities);
2392
+ if (this.terminalPeer) {
2393
+ for (const activity of activities) {
2394
+ if (activity.workSessionId && activity.tmuxSessionName) {
2395
+ this.terminalPeer.watchSession(
2396
+ activity.workSessionId,
2397
+ activity.tmuxSessionName
2398
+ );
2399
+ }
2400
+ }
2401
+ }
2402
+ } catch {
2403
+ }
2404
+ }
2405
+ async syncWorkSessionTerminals(activities) {
2406
+ for (const activity of activities) {
2407
+ if (!activity.workSessionId || !activity.tmuxPaneId) {
2408
+ continue;
2409
+ }
2410
+ try {
2411
+ await this.refreshWorkSessionTerminal(activity.workSessionId, {
2412
+ tmuxPaneId: activity.tmuxPaneId,
2413
+ cwd: activity.cwd,
2414
+ repoRoot: activity.repoRoot,
2415
+ branch: activity.branch,
2416
+ agentProvider: activity.agentProvider,
2417
+ agentSessionKey: activity.agentSessionKey
2418
+ });
2419
+ await this.verifyManagedWorkSession(activity);
2420
+ } catch {
2421
+ }
2422
+ }
2423
+ }
2424
+ async verifyManagedWorkSession(activity) {
2425
+ if (!activity.workSessionId || !activity.tmuxPaneId || !activity.agentProvider || !isBridgeProvider(activity.agentProvider) || activity.agentProcessId) {
2426
+ return;
2427
+ }
2428
+ const workspacePath = activity.workspacePath ?? activity.cwd ?? activity.repoRoot;
2429
+ if (!workspacePath) {
2430
+ return;
2431
+ }
2432
+ const attachedSession = await this.attachObservedAgentSession(
2433
+ activity.agentProvider,
2434
+ workspacePath
2435
+ );
2436
+ if (!attachedSession) {
2437
+ return;
2438
+ }
2439
+ await this.refreshWorkSessionTerminal(activity.workSessionId, {
2440
+ tmuxPaneId: activity.tmuxPaneId,
2441
+ cwd: attachedSession.process.cwd ?? activity.cwd ?? workspacePath,
2442
+ repoRoot: attachedSession.process.repoRoot ?? activity.repoRoot ?? workspacePath,
2443
+ branch: attachedSession.process.branch ?? activity.branch,
2444
+ agentProvider: attachedSession.process.provider,
2445
+ agentSessionKey: attachedSession.process.sessionKey
2446
+ });
2447
+ await this.postAgentMessage(
2448
+ activity._id,
2449
+ "status",
2450
+ `Verified ${providerLabel(attachedSession.process.provider)} in ${activity.tmuxPaneId}`
2451
+ );
2452
+ await this.updateLiveActivity(activity._id, {
2453
+ status: "waiting_for_input",
2454
+ latestSummary: `Verified ${providerLabel(attachedSession.process.provider)} in ${activity.tmuxPaneId}`,
2455
+ processId: attachedSession.processId,
2456
+ title: activity.title
2457
+ });
2458
+ }
2459
+ async refreshWorkSessionTerminal(workSessionId, metadata) {
2460
+ if (!workSessionId || !metadata.tmuxPaneId) {
2461
+ return;
2462
+ }
2463
+ const terminalSnapshot = captureTmuxPane(metadata.tmuxPaneId);
2464
+ await this.client.mutation(
2465
+ api.agentBridge.bridgePublic.updateWorkSessionTerminal,
2466
+ {
2467
+ deviceId: this.config.deviceId,
2468
+ deviceSecret: this.config.deviceSecret,
2469
+ workSessionId,
2470
+ terminalSnapshot,
2471
+ tmuxSessionName: metadata.tmuxSessionName,
2472
+ tmuxWindowName: metadata.tmuxWindowName,
2473
+ tmuxPaneId: metadata.tmuxPaneId,
2474
+ cwd: metadata.cwd,
2475
+ repoRoot: metadata.repoRoot,
2476
+ branch: metadata.branch,
2477
+ agentProvider: metadata.agentProvider,
2478
+ agentSessionKey: metadata.agentSessionKey
2479
+ }
2480
+ );
2481
+ }
2482
+ async run() {
2483
+ console.log("Vector Bridge Service");
2484
+ console.log(
2485
+ ` Device: ${this.config.displayName} (${this.config.deviceId})`
2486
+ );
2487
+ console.log(` Convex: ${this.config.convexUrl}`);
2488
+ console.log(` PID: ${process.pid}`);
2489
+ console.log("");
2490
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
2491
+ writeFileSync(PID_FILE, String(process.pid));
2492
+ try {
2493
+ this.terminalPeer = new TerminalPeerManager({
2494
+ deviceId: this.config.deviceId,
2495
+ deviceSecret: this.config.deviceSecret,
2496
+ convexUrl: this.config.convexUrl
2497
+ });
2498
+ console.log(` WebRTC: ready`);
2499
+ } catch (e) {
2500
+ console.error(
2501
+ ` WebRTC: failed (${e instanceof Error ? e.message : "unknown"})`
2502
+ );
2503
+ }
2504
+ console.log("");
2505
+ await this.heartbeat();
2506
+ await this.reportProcesses();
2507
+ await this.refreshLiveActivities();
2508
+ console.log(`[${ts2()}] Service running. Ctrl+C to stop.
2509
+ `);
2510
+ this.timers.push(
2511
+ setInterval(() => {
2512
+ this.heartbeat().catch(
2513
+ (e) => console.error(`[${ts2()}] Heartbeat error:`, e.message)
2514
+ );
2515
+ }, HEARTBEAT_INTERVAL_MS)
2516
+ );
2517
+ this.timers.push(
2518
+ setInterval(() => {
2519
+ this.pollCommands().catch(
2520
+ (e) => console.error(`[${ts2()}] Command poll error:`, e.message)
2521
+ );
2522
+ }, COMMAND_POLL_INTERVAL_MS)
2523
+ );
2524
+ this.timers.push(
2525
+ setInterval(() => {
2526
+ this.refreshLiveActivities().catch(
2527
+ (e) => console.error(`[${ts2()}] Live activity sync error:`, e.message)
2528
+ );
2529
+ }, LIVE_ACTIVITY_SYNC_INTERVAL_MS)
2530
+ );
2531
+ this.timers.push(
2532
+ setInterval(() => {
2533
+ this.reportProcesses().catch(
2534
+ (e) => console.error(`[${ts2()}] Discovery error:`, e.message)
2535
+ );
2536
+ }, PROCESS_DISCOVERY_INTERVAL_MS)
2537
+ );
2538
+ const shutdown = () => {
2539
+ console.log(`
2540
+ [${ts2()}] Shutting down...`);
2541
+ for (const t of this.timers) clearInterval(t);
2542
+ this.terminalPeer?.stop();
2543
+ try {
2544
+ unlinkSync(PID_FILE);
2545
+ } catch {
2546
+ }
2547
+ try {
2548
+ writeLiveActivitiesCache([]);
2549
+ } catch {
2550
+ }
2551
+ process.exit(0);
2552
+ };
2553
+ process.on("SIGINT", shutdown);
2554
+ process.on("SIGTERM", shutdown);
2555
+ await new Promise(() => {
2556
+ });
2557
+ }
2558
+ async handleMessageCommand(cmd) {
2559
+ if (!cmd.liveActivityId) {
2560
+ throw new Error("Message command is missing liveActivityId");
2561
+ }
2562
+ const payload = cmd.payload;
2563
+ const body = payload?.body?.trim();
2564
+ if (!body) {
2565
+ throw new Error("Message command is missing a body");
2566
+ }
2567
+ const process9 = cmd.process;
2568
+ console.log(` > "${truncateForLog(body)}"`);
2569
+ if (cmd.workSession?.tmuxPaneId) {
2570
+ sendTextToTmuxPane(cmd.workSession.tmuxPaneId, body);
2571
+ const attachedSession = cmd.workSession.agentProvider && isBridgeProvider(cmd.workSession.agentProvider) ? await this.attachObservedAgentSession(
2572
+ cmd.workSession.agentProvider,
2573
+ cmd.workSession.workspacePath ?? cmd.workSession.cwd ?? process9?.cwd
2574
+ ) : null;
2575
+ await this.postAgentMessage(
2576
+ cmd.liveActivityId,
2577
+ "status",
2578
+ "Sent input to work session terminal"
2579
+ );
2580
+ await this.refreshWorkSessionTerminal(cmd.workSession._id, {
2581
+ tmuxSessionName: cmd.workSession.tmuxSessionName,
2582
+ tmuxWindowName: cmd.workSession.tmuxWindowName,
2583
+ tmuxPaneId: cmd.workSession.tmuxPaneId,
2584
+ cwd: cmd.workSession.cwd,
2585
+ repoRoot: cmd.workSession.repoRoot,
2586
+ branch: cmd.workSession.branch,
2587
+ agentProvider: attachedSession?.process.provider ?? cmd.workSession.agentProvider,
2588
+ agentSessionKey: attachedSession?.process.sessionKey ?? cmd.workSession.agentSessionKey
2589
+ });
2590
+ await this.updateLiveActivity(cmd.liveActivityId, {
2591
+ status: "waiting_for_input",
2592
+ latestSummary: `Input sent to ${cmd.workSession.tmuxPaneId}`,
2593
+ title: cmd.liveActivity?.title,
2594
+ processId: attachedSession?.processId ?? process9?._id
2595
+ });
2596
+ return;
2597
+ }
2598
+ if (!process9 || !process9.supportsInboundMessages || !process9.sessionKey || !process9.cwd || !isBridgeProvider(process9.provider)) {
2599
+ throw new Error("No resumable local session is attached to this issue");
2600
+ }
2601
+ await this.reportProcess({
2602
+ provider: process9.provider,
2603
+ providerLabel: process9.providerLabel ?? providerLabel(process9.provider),
2604
+ sessionKey: process9.sessionKey,
2605
+ cwd: process9.cwd,
2606
+ repoRoot: process9.repoRoot,
2607
+ branch: process9.branch,
2608
+ title: process9.title,
2609
+ model: process9.model,
2610
+ mode: "managed",
2611
+ status: "waiting",
2612
+ supportsInboundMessages: true
2613
+ });
2614
+ await this.updateLiveActivity(cmd.liveActivityId, {
2615
+ status: "active",
2616
+ processId: process9._id,
2617
+ title: cmd.liveActivity?.title ?? process9.title
2618
+ });
2619
+ const result = await resumeProviderSession(
2620
+ process9.provider,
2621
+ process9.sessionKey,
2622
+ process9.cwd,
2623
+ body
2624
+ );
2625
+ const processId = await this.reportProcess(result);
2626
+ if (result.responseText) {
2627
+ await this.postAgentMessage(
2628
+ cmd.liveActivityId,
2629
+ "assistant",
2630
+ result.responseText
2631
+ );
2632
+ console.log(` < "${truncateForLog(result.responseText)}"`);
2633
+ }
2634
+ await this.updateLiveActivity(cmd.liveActivityId, {
2635
+ processId,
2636
+ status: "waiting_for_input",
2637
+ latestSummary: summarizeMessage(result.responseText),
2638
+ title: cmd.liveActivity?.title ?? process9.title
2639
+ });
2640
+ }
2641
+ async handleResizeCommand(cmd) {
2642
+ const payload = cmd.payload;
2643
+ const cols = payload?.cols;
2644
+ const rows = payload?.rows;
2645
+ const paneId = cmd.workSession?.tmuxPaneId;
2646
+ if (!paneId || !cols || !rows) {
2647
+ throw new Error("Resize command missing paneId, cols, or rows");
2648
+ }
2649
+ console.log(` Resize ${paneId} \u2192 ${cols}x${rows}`);
2650
+ resizeTmuxPane(paneId, cols, rows);
2651
+ if (cmd.workSession) {
2652
+ await this.refreshWorkSessionTerminal(cmd.workSession._id, {
2653
+ tmuxSessionName: cmd.workSession.tmuxSessionName,
2654
+ tmuxWindowName: cmd.workSession.tmuxWindowName,
2655
+ tmuxPaneId: paneId,
2656
+ cwd: cmd.workSession.cwd,
2657
+ repoRoot: cmd.workSession.repoRoot,
2658
+ branch: cmd.workSession.branch,
2659
+ agentProvider: cmd.workSession.agentProvider,
2660
+ agentSessionKey: cmd.workSession.agentSessionKey
2661
+ });
2662
+ }
2663
+ }
2664
+ async handleLaunchCommand(cmd) {
2665
+ if (!cmd.liveActivityId) {
2666
+ throw new Error("Launch command is missing liveActivityId");
2667
+ }
2668
+ const payload = cmd.payload;
2669
+ const workspacePath = payload?.workspacePath?.trim();
2670
+ if (!workspacePath) {
2671
+ throw new Error("Launch command is missing workspacePath");
2672
+ }
2673
+ const requestedProvider = payload?.provider;
2674
+ const provider = requestedProvider && isBridgeProvider(requestedProvider) ? requestedProvider : void 0;
2675
+ const issueKey = payload?.issueKey ?? cmd.liveActivity?.issueKey ?? "ISSUE";
2676
+ const issueTitle = payload?.issueTitle ?? cmd.liveActivity?.issueTitle ?? "Untitled issue";
2677
+ const prompt2 = buildLaunchPrompt(issueKey, issueTitle, workspacePath);
2678
+ const launchLabel = provider ? providerLabel(provider) : "shell session";
2679
+ const workSessionTitle = `${issueKey}: ${issueTitle}`;
2680
+ const sessionsBeforeLaunch = provider ? listObservedSessionsForWorkspace(provider, workspacePath) : [];
2681
+ await this.updateLiveActivity(cmd.liveActivityId, {
2682
+ status: "active",
2683
+ latestSummary: `Launching ${launchLabel} in ${workspacePath}`,
2684
+ delegatedRunId: payload?.delegatedRunId,
2685
+ launchStatus: "launching",
2686
+ title: workSessionTitle
2687
+ });
2688
+ await this.postAgentMessage(
2689
+ cmd.liveActivityId,
2690
+ "status",
2691
+ `Launching ${launchLabel} in ${workspacePath}`
2692
+ );
2693
+ const tmuxSession = createTmuxWorkSession({
2694
+ workspacePath,
2695
+ issueKey,
2696
+ issueTitle,
2697
+ provider,
2698
+ prompt: prompt2
2699
+ });
2700
+ const attachedSession = provider ? await this.attachObservedAgentSession(
2701
+ provider,
2702
+ workspacePath,
2703
+ sessionsBeforeLaunch,
2704
+ tmuxSession.paneProcessId
2705
+ ) : null;
2706
+ await this.refreshWorkSessionTerminal(cmd.workSession?._id, {
2707
+ tmuxSessionName: tmuxSession.sessionName,
2708
+ tmuxWindowName: tmuxSession.windowName,
2709
+ tmuxPaneId: tmuxSession.paneId,
2710
+ cwd: workspacePath,
2711
+ repoRoot: workspacePath,
2712
+ branch: currentGitBranch(workspacePath),
2713
+ agentProvider: provider,
2714
+ agentSessionKey: attachedSession?.process.sessionKey
2715
+ });
2716
+ if (provider && !attachedSession) {
2717
+ await this.postAgentMessage(
2718
+ cmd.liveActivityId,
2719
+ "status",
2720
+ `Started tmux session ${tmuxSession.sessionName}:${tmuxSession.windowName}. Waiting to verify ${providerLabel(provider)} in ${tmuxSession.paneId}.`
2721
+ );
2722
+ await this.updateLiveActivity(cmd.liveActivityId, {
2723
+ status: "waiting_for_input",
2724
+ latestSummary: `Running in ${tmuxSession.sessionName}:${tmuxSession.windowName}; waiting to verify ${providerLabel(provider)}`,
2725
+ delegatedRunId: payload?.delegatedRunId,
2726
+ launchStatus: "running",
2727
+ title: `${providerLabel(provider)} on ${this.config.displayName}`
2728
+ });
2729
+ return;
2730
+ }
2731
+ await this.updateLiveActivity(cmd.liveActivityId, {
2732
+ status: "waiting_for_input",
2733
+ latestSummary: `Running in ${tmuxSession.sessionName}:${tmuxSession.windowName}`,
2734
+ delegatedRunId: payload?.delegatedRunId,
2735
+ launchStatus: "running",
2736
+ processId: attachedSession?.processId,
2737
+ title: workSessionTitle
2738
+ });
2739
+ }
2740
+ async attachObservedAgentSession(provider, workspacePath, sessionsBeforeLaunch = [], paneProcessId) {
2741
+ if (!workspacePath) {
2742
+ return null;
2743
+ }
2744
+ const existingKeys = new Set(
2745
+ sessionsBeforeLaunch.map(sessionIdentityKey).filter(Boolean)
2746
+ );
2747
+ for (let attempt = 0; attempt < 10; attempt += 1) {
2748
+ const observedSessions = listObservedSessionsForWorkspace(
2749
+ provider,
2750
+ workspacePath
2751
+ );
2752
+ const candidate = (paneProcessId ? findObservedSessionInProcessTree(observedSessions, paneProcessId) : void 0) ?? observedSessions.find(
2753
+ (session) => !existingKeys.has(sessionIdentityKey(session))
2754
+ ) ?? (attempt === 9 ? observedSessions[0] : void 0);
2755
+ if (candidate) {
2756
+ const processId = await this.reportProcess(candidate);
2757
+ return {
2758
+ process: candidate,
2759
+ processId
2760
+ };
2761
+ }
2762
+ await sleep(750);
2763
+ }
2764
+ return null;
2765
+ }
2766
+ async reportProcess(process9) {
2767
+ const {
2768
+ provider,
2769
+ providerLabel: providerLabel2,
2770
+ localProcessId,
2771
+ sessionKey,
2772
+ cwd,
2773
+ repoRoot,
2774
+ branch,
2775
+ title,
2776
+ model,
2777
+ tmuxSessionName,
2778
+ tmuxWindowName,
2779
+ tmuxPaneId,
2780
+ mode,
2781
+ status,
2782
+ supportsInboundMessages
2783
+ } = process9;
2784
+ return await this.client.mutation(
2785
+ api.agentBridge.bridgePublic.reportProcess,
2786
+ {
2787
+ deviceId: this.config.deviceId,
2788
+ deviceSecret: this.config.deviceSecret,
2789
+ provider,
2790
+ providerLabel: providerLabel2,
2791
+ localProcessId,
2792
+ sessionKey,
2793
+ cwd,
2794
+ repoRoot,
2795
+ branch,
2796
+ title,
2797
+ model,
2798
+ tmuxSessionName,
2799
+ tmuxWindowName,
2800
+ tmuxPaneId,
2801
+ mode,
2802
+ status,
2803
+ supportsInboundMessages
2804
+ }
2805
+ );
2806
+ }
2807
+ async updateLiveActivity(liveActivityId, args) {
2808
+ await this.client.mutation(
2809
+ api.agentBridge.bridgePublic.updateLiveActivityState,
2810
+ {
2811
+ deviceId: this.config.deviceId,
2812
+ deviceSecret: this.config.deviceSecret,
2813
+ liveActivityId,
2814
+ ...args
2815
+ }
2816
+ );
2817
+ }
2818
+ async postAgentMessage(liveActivityId, role, body) {
2819
+ await this.client.mutation(api.agentBridge.bridgePublic.postAgentMessage, {
2820
+ deviceId: this.config.deviceId,
2821
+ deviceSecret: this.config.deviceSecret,
2822
+ liveActivityId,
2823
+ role,
2824
+ body
2825
+ });
2826
+ }
2827
+ async completeCommand(commandId, status) {
2828
+ await this.client.mutation(api.agentBridge.bridgePublic.completeCommand, {
2829
+ deviceId: this.config.deviceId,
2830
+ deviceSecret: this.config.deviceSecret,
2831
+ commandId,
2832
+ status
2833
+ });
2834
+ }
2835
+ async postCommandError(cmd, errorMessage) {
2836
+ if (cmd.kind === "launch" && cmd.liveActivityId) {
2837
+ const payload = cmd.payload;
2838
+ await this.updateLiveActivity(cmd.liveActivityId, {
2839
+ status: "failed",
2840
+ latestSummary: errorMessage,
2841
+ delegatedRunId: payload?.delegatedRunId,
2842
+ launchStatus: "failed"
2843
+ });
2844
+ await this.postAgentMessage(cmd.liveActivityId, "status", errorMessage);
2845
+ return;
2846
+ }
2847
+ if (cmd.kind === "message" && cmd.liveActivityId) {
2848
+ await this.postAgentMessage(cmd.liveActivityId, "status", errorMessage);
2849
+ await this.updateLiveActivity(cmd.liveActivityId, {
2850
+ status: "waiting_for_input",
2851
+ latestSummary: errorMessage
2852
+ });
2853
+ }
2854
+ }
2855
+ };
2856
+ function createTmuxWorkSession(args) {
2857
+ const slug = sanitizeTmuxName(args.issueKey.toLowerCase());
2858
+ const sessionName = `vector-${slug}-${randomUUID2().slice(0, 8)}`;
2859
+ const windowName = sanitizeTmuxName(
2860
+ args.provider === "codex" ? "codex" : args.provider === "claude_code" ? "claude" : "shell"
2861
+ );
2862
+ execFileSync("tmux", [
2863
+ "new-session",
2864
+ "-d",
2865
+ "-s",
2866
+ sessionName,
2867
+ "-n",
2868
+ windowName,
2869
+ "-c",
2870
+ args.workspacePath
2871
+ ]);
2872
+ const paneId = execFileSync(
2873
+ "tmux",
2874
+ [
2875
+ "display-message",
2876
+ "-p",
2877
+ "-t",
2878
+ `${sessionName}:${windowName}.0`,
2879
+ "#{pane_id}"
2880
+ ],
2881
+ { encoding: "utf-8" }
2882
+ ).trim();
2883
+ const paneProcessId = execFileSync(
2884
+ "tmux",
2885
+ ["display-message", "-p", "-t", paneId, "#{pane_pid}"],
2886
+ { encoding: "utf-8" }
2887
+ ).trim();
2888
+ if (args.provider) {
2889
+ execFileSync("tmux", [
2890
+ "send-keys",
2891
+ "-t",
2892
+ paneId,
2893
+ buildManagedLaunchCommand(args.provider, args.prompt),
2894
+ "Enter"
2895
+ ]);
2896
+ } else {
2897
+ execFileSync("tmux", [
2898
+ "send-keys",
2899
+ "-t",
2900
+ paneId,
2901
+ `printf '%s\\n\\n' ${shellQuote(args.prompt)}`,
2902
+ "Enter"
2903
+ ]);
2904
+ }
2905
+ return {
2906
+ sessionName,
2907
+ windowName,
2908
+ paneId,
2909
+ paneProcessId
2910
+ };
2911
+ }
2912
+ function sendTextToTmuxPane(paneId, text2) {
2913
+ execFileSync("tmux", ["set-buffer", "--", text2]);
2914
+ execFileSync("tmux", ["paste-buffer", "-t", paneId]);
2915
+ execFileSync("tmux", ["send-keys", "-t", paneId, "Enter"]);
2916
+ execFileSync("tmux", ["delete-buffer"]);
2917
+ }
2918
+ function captureTmuxPane(paneId) {
2919
+ return execFileSync(
2920
+ "tmux",
2921
+ ["capture-pane", "-p", "-e", "-t", paneId, "-S", "-120"],
2922
+ { encoding: "utf-8" }
2923
+ ).trimEnd();
2924
+ }
2925
+ function resizeTmuxPane(paneId, cols, rows) {
2926
+ try {
2927
+ execFileSync("tmux", [
2928
+ "resize-pane",
2929
+ "-t",
2930
+ paneId,
2931
+ "-x",
2932
+ String(cols),
2933
+ "-y",
2934
+ String(rows)
2935
+ ]);
2936
+ } catch (e) {
2937
+ console.error(`Failed to resize pane ${paneId}:`, e);
2938
+ }
2939
+ }
2940
+ function currentGitBranch(cwd) {
2941
+ try {
2942
+ return execSync2("git rev-parse --abbrev-ref HEAD", {
2943
+ encoding: "utf-8",
2944
+ cwd,
2945
+ timeout: 3e3
2946
+ }).trim();
2947
+ } catch {
2948
+ return void 0;
2949
+ }
2950
+ }
2951
+ function buildManagedLaunchCommand(provider, prompt2) {
2952
+ if (provider === "codex") {
2953
+ return `codex --no-alt-screen -a never ${shellQuote(prompt2)}`;
2954
+ }
2955
+ return `claude --permission-mode bypassPermissions --dangerously-skip-permissions ${shellQuote(prompt2)}`;
2956
+ }
2957
+ function sanitizeTmuxName(value) {
2958
+ return value.replace(/[^a-z0-9_-]+/gi, "-").replace(/^-+|-+$/g, "") || "work";
2959
+ }
2960
+ function shellQuote(value) {
2961
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
2962
+ }
2963
+ async function setupBridgeDevice(client, convexUrl) {
2964
+ const deviceKey = getStableDeviceKey();
2965
+ const displayName = `${process.env.USER ?? "user"}'s ${platform() === "darwin" ? "Mac" : "machine"}`;
2966
+ const result = await client.mutation(
2967
+ api.agentBridge.mutations.registerBridgeDevice,
2968
+ {
2969
+ deviceKey,
2970
+ displayName,
2971
+ hostname: hostname(),
2972
+ platform: platform(),
2973
+ serviceType: "foreground",
2974
+ cliVersion: "0.1.0",
2975
+ capabilities: ["codex", "claude_code"]
2976
+ }
2977
+ );
2978
+ const config = {
2979
+ deviceId: result.deviceId,
2980
+ deviceKey,
2981
+ deviceSecret: result.deviceSecret,
2982
+ userId: result.userId,
2983
+ displayName,
2984
+ convexUrl,
2985
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
2986
+ };
2987
+ saveBridgeConfig(config);
2988
+ return config;
2989
+ }
2990
+ function getStableDeviceKey() {
2991
+ const existingConfig = loadBridgeConfig();
2992
+ const existingKey = existingConfig?.deviceKey?.trim();
2993
+ if (existingKey) {
2994
+ persistDeviceKey(existingKey);
2995
+ return existingKey;
2996
+ }
2997
+ if (existsSync3(DEVICE_KEY_FILE)) {
2998
+ const savedKey = readFileSync2(DEVICE_KEY_FILE, "utf-8").trim();
2999
+ if (savedKey) {
3000
+ return savedKey;
3001
+ }
3002
+ }
3003
+ const generatedKey = `${hostname()}-${randomUUID2().slice(0, 8)}`;
3004
+ persistDeviceKey(generatedKey);
3005
+ return generatedKey;
3006
+ }
3007
+ function persistDeviceKey(deviceKey) {
3008
+ if (!existsSync3(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
3009
+ writeFileSync(DEVICE_KEY_FILE, `${deviceKey}
3010
+ `);
3011
+ }
3012
+ function buildLaunchPrompt(issueKey, issueTitle, workspacePath) {
3013
+ return [
3014
+ `You are working on Vector issue ${issueKey}: ${issueTitle}.`,
3015
+ `The repository is already checked out at ${workspacePath}.`,
3016
+ "Inspect the codebase, identify the relevant implementation area, and start the work.",
3017
+ "In your first reply, summarize your plan and the first concrete step you are taking."
3018
+ ].join("\n\n");
3019
+ }
3020
+ function summarizeMessage(message) {
3021
+ if (!message) {
3022
+ return void 0;
3023
+ }
3024
+ return message.length > 120 ? `${message.slice(0, 117).trimEnd()}...` : message;
3025
+ }
3026
+ function truncateForLog(message) {
3027
+ return message.length > 80 ? `${message.slice(0, 77).trimEnd()}...` : message;
3028
+ }
3029
+ function listObservedSessionsForWorkspace(provider, workspacePath) {
3030
+ return discoverAttachableSessions().filter(
3031
+ (session) => session.provider === provider && matchesWorkspacePath(session, workspacePath)
3032
+ ).sort(compareLocalSessionRecency);
3033
+ }
3034
+ function findObservedSessionInProcessTree(sessions, paneProcessId) {
3035
+ const descendantIds = listDescendantProcessIds(paneProcessId);
3036
+ if (descendantIds.size === 0) {
3037
+ return void 0;
3038
+ }
3039
+ return sessions.find(
3040
+ (session) => session.localProcessId ? descendantIds.has(session.localProcessId) : false
3041
+ );
3042
+ }
3043
+ function listDescendantProcessIds(rootPid) {
3044
+ const descendants = /* @__PURE__ */ new Set([rootPid]);
3045
+ try {
3046
+ const output = execSync2("ps -axo pid=,ppid=", {
3047
+ encoding: "utf-8",
3048
+ timeout: 3e3
3049
+ });
3050
+ const parentToChildren = /* @__PURE__ */ new Map();
3051
+ for (const line of output.split("\n").map((value) => value.trim()).filter(Boolean)) {
3052
+ const [pid, ppid] = line.split(/\s+/, 2);
3053
+ if (!pid || !ppid) {
3054
+ continue;
3055
+ }
3056
+ const children = parentToChildren.get(ppid) ?? [];
3057
+ children.push(pid);
3058
+ parentToChildren.set(ppid, children);
3059
+ }
3060
+ const queue = [rootPid];
3061
+ while (queue.length > 0) {
3062
+ const currentPid = queue.shift();
3063
+ if (!currentPid) {
3064
+ continue;
3065
+ }
3066
+ for (const childPid of parentToChildren.get(currentPid) ?? []) {
3067
+ if (descendants.has(childPid)) {
3068
+ continue;
3069
+ }
3070
+ descendants.add(childPid);
3071
+ queue.push(childPid);
3072
+ }
3073
+ }
3074
+ } catch {
3075
+ return descendants;
3076
+ }
3077
+ return descendants;
3078
+ }
3079
+ function matchesWorkspacePath(session, workspacePath) {
3080
+ const normalizedWorkspace = normalizePath(workspacePath);
3081
+ const candidatePaths = [session.cwd, session.repoRoot].filter((value) => Boolean(value)).map(normalizePath);
3082
+ return candidatePaths.some((path3) => path3 === normalizedWorkspace);
3083
+ }
3084
+ function normalizePath(value) {
3085
+ return value.replace(/\/+$/, "");
3086
+ }
3087
+ function sessionIdentityKey(session) {
3088
+ return [
3089
+ session.provider,
3090
+ session.sessionKey,
3091
+ session.localProcessId,
3092
+ session.cwd
3093
+ ].filter(Boolean).join("::");
3094
+ }
3095
+ function compareLocalSessionRecency(a, b) {
3096
+ return Number(b.localProcessId ?? 0) - Number(a.localProcessId ?? 0);
3097
+ }
3098
+ function sleep(ms) {
3099
+ return new Promise((resolve) => setTimeout(resolve, ms));
3100
+ }
3101
+ function isBridgeProvider(provider) {
3102
+ return provider === "codex" || provider === "claude_code";
3103
+ }
3104
+ function providerLabel(provider) {
3105
+ if (provider === "codex") {
3106
+ return "Codex";
3107
+ }
3108
+ if (provider === "claude_code") {
3109
+ return "Claude";
3110
+ }
3111
+ return "Vector CLI";
3112
+ }
3113
+ function installLaunchAgent(vcliPath) {
3114
+ if (platform() !== "darwin") {
3115
+ console.error("LaunchAgent is macOS only. Use systemd on Linux.");
3116
+ return;
3117
+ }
3118
+ const programArguments = getLaunchAgentProgramArguments(vcliPath);
3119
+ const environmentVariables = [
3120
+ " <key>PATH</key>",
3121
+ " <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>",
3122
+ ...process.env.VECTOR_HOME?.trim() ? [
3123
+ " <key>VECTOR_HOME</key>",
3124
+ ` <string>${process.env.VECTOR_HOME.trim()}</string>`
3125
+ ] : []
3126
+ ].join("\n");
3127
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
3128
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3129
+ <plist version="1.0">
3130
+ <dict>
3131
+ <key>Label</key>
3132
+ <string>${LAUNCHAGENT_LABEL}</string>
3133
+ <key>ProgramArguments</key>
3134
+ ${programArguments}
3135
+ <key>RunAtLoad</key>
3136
+ <true/>
3137
+ <key>KeepAlive</key>
3138
+ <true/>
3139
+ <key>StandardOutPath</key>
3140
+ <string>${CONFIG_DIR}/bridge.log</string>
3141
+ <key>StandardErrorPath</key>
3142
+ <string>${CONFIG_DIR}/bridge.err.log</string>
3143
+ <key>EnvironmentVariables</key>
3144
+ <dict>
3145
+ ${environmentVariables}
3146
+ </dict>
3147
+ </dict>
3148
+ </plist>`;
3149
+ if (!existsSync3(LAUNCHAGENT_DIR)) {
3150
+ mkdirSync(LAUNCHAGENT_DIR, { recursive: true });
3151
+ }
3152
+ removeLegacyMenuBarLaunchAgent();
3153
+ writeFileSync(LAUNCHAGENT_PLIST, plist);
3154
+ console.log(`Installed LaunchAgent: ${LAUNCHAGENT_PLIST}`);
3155
+ }
3156
+ function getLaunchAgentProgramArguments(vcliPath) {
3157
+ const args = resolveCliInvocation(vcliPath);
3158
+ return [
3159
+ "<array>",
3160
+ ...args.map((arg) => ` <string>${arg}</string>`),
3161
+ " <string>service</string>",
3162
+ " <string>run</string>",
3163
+ " </array>"
3164
+ ].join("\n");
3165
+ }
3166
+ function resolveCliInvocation(vcliPath) {
3167
+ if (vcliPath.endsWith(".js")) {
3168
+ return [process.execPath, vcliPath];
3169
+ }
3170
+ if (vcliPath.endsWith(".ts")) {
3171
+ const tsxPath = join2(
3172
+ import.meta.dirname ?? process.cwd(),
3173
+ "..",
3174
+ "..",
3175
+ "..",
3176
+ "node_modules",
3177
+ ".bin",
3178
+ "tsx"
3179
+ );
3180
+ if (existsSync3(tsxPath)) {
3181
+ return [tsxPath, vcliPath];
3182
+ }
3183
+ }
3184
+ return [vcliPath];
3185
+ }
3186
+ function loadLaunchAgent() {
3187
+ if (runLaunchctl(["bootstrap", launchctlGuiDomain(), LAUNCHAGENT_PLIST])) {
3188
+ runLaunchctl([
3189
+ "kickstart",
3190
+ "-k",
3191
+ `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`
3192
+ ]);
3193
+ console.log(
3194
+ "LaunchAgent loaded. Bridge will start automatically on login."
3195
+ );
3196
+ return;
3197
+ }
3198
+ if (runLaunchctl([
3199
+ "kickstart",
3200
+ "-k",
3201
+ `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`
3202
+ ]) || runLaunchctl(["load", LAUNCHAGENT_PLIST])) {
3203
+ console.log(
3204
+ "LaunchAgent loaded. Bridge will start automatically on login."
3205
+ );
3206
+ return;
3207
+ }
3208
+ console.error("Failed to load LaunchAgent");
3209
+ }
3210
+ function unloadLaunchAgent() {
3211
+ if (runLaunchctl(["bootout", `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`]) || runLaunchctl(["bootout", launchctlGuiDomain(), LAUNCHAGENT_PLIST]) || runLaunchctl(["unload", LAUNCHAGENT_PLIST])) {
3212
+ console.log("LaunchAgent unloaded.");
3213
+ return true;
3214
+ }
3215
+ console.error("Failed to unload LaunchAgent (may not be loaded)");
3216
+ return false;
3217
+ }
3218
+ function uninstallLaunchAgent() {
3219
+ unloadLaunchAgent();
3220
+ removeLegacyMenuBarLaunchAgent();
3221
+ try {
3222
+ unlinkSync(LAUNCHAGENT_PLIST);
3223
+ console.log("LaunchAgent removed.");
3224
+ } catch {
3225
+ }
3226
+ }
3227
+ var MENUBAR_PID_FILE = join2(CONFIG_DIR, "menubar.pid");
3228
+ function removeLegacyMenuBarLaunchAgent() {
3229
+ if (platform() !== "darwin" || !existsSync3(LEGACY_MENUBAR_LAUNCHAGENT_PLIST)) {
3230
+ return;
3231
+ }
3232
+ try {
3233
+ execSync2(`launchctl unload ${LEGACY_MENUBAR_LAUNCHAGENT_PLIST}`, {
3234
+ stdio: "pipe"
3235
+ });
3236
+ } catch {
3237
+ }
3238
+ try {
3239
+ unlinkSync(LEGACY_MENUBAR_LAUNCHAGENT_PLIST);
3240
+ } catch {
3241
+ }
3242
+ }
3243
+ function launchctlGuiDomain() {
3244
+ const uid = typeof process.getuid === "function" ? process.getuid() : typeof process.geteuid === "function" ? process.geteuid() : 0;
3245
+ return `gui/${uid}`;
3246
+ }
3247
+ function runLaunchctl(args) {
3248
+ try {
3249
+ execFileSync("launchctl", args, {
3250
+ stdio: "ignore"
3251
+ });
3252
+ return true;
3253
+ } catch {
3254
+ return false;
3255
+ }
3256
+ }
3257
+ function findCliEntrypoint() {
3258
+ const candidates = [
3259
+ join2(import.meta.dirname ?? "", "index.js"),
3260
+ join2(import.meta.dirname ?? "", "index.ts"),
3261
+ join2(import.meta.dirname ?? "", "..", "dist", "index.js")
3262
+ ];
3263
+ for (const p of candidates) {
3264
+ if (existsSync3(p)) return p;
3265
+ }
3266
+ return null;
3267
+ }
3268
+ function getCurrentCliInvocation() {
3269
+ const entrypoint = findCliEntrypoint();
3270
+ if (!entrypoint) {
3271
+ return null;
3272
+ }
3273
+ return resolveCliInvocation(entrypoint);
3274
+ }
3275
+ function findMenuBarExecutable() {
3276
+ const candidates = [
3277
+ join2(
3278
+ import.meta.dirname ?? "",
3279
+ "..",
3280
+ "native",
3281
+ "VectorMenuBar.app",
3282
+ "Contents",
3283
+ "MacOS",
3284
+ "VectorMenuBar"
3285
+ ),
3286
+ join2(
3287
+ import.meta.dirname ?? "",
3288
+ "native",
3289
+ "VectorMenuBar.app",
3290
+ "Contents",
3291
+ "MacOS",
3292
+ "VectorMenuBar"
3293
+ )
3294
+ ];
3295
+ for (const p of candidates) {
3296
+ if (existsSync3(p)) {
3297
+ return p;
3298
+ }
3299
+ }
3300
+ return null;
3301
+ }
3302
+ function isKnownMenuBarProcess(pid) {
3303
+ try {
3304
+ const command = execSync2(`ps -p ${pid} -o args=`, {
3305
+ encoding: "utf-8",
3306
+ timeout: 3e3
3307
+ });
3308
+ return command.includes("menubar.js") || command.includes("menubar.ts") || command.includes("VectorMenuBar");
3309
+ } catch {
3310
+ return false;
3311
+ }
3312
+ }
3313
+ function killExistingMenuBar() {
3314
+ if (existsSync3(MENUBAR_PID_FILE)) {
3315
+ try {
3316
+ const pid = Number(readFileSync2(MENUBAR_PID_FILE, "utf-8").trim());
3317
+ if (Number.isFinite(pid) && pid > 0 && isKnownMenuBarProcess(pid)) {
3318
+ process.kill(pid, "SIGTERM");
3319
+ }
3320
+ } catch {
3321
+ }
3322
+ try {
3323
+ unlinkSync(MENUBAR_PID_FILE);
3324
+ } catch {
3325
+ }
3326
+ }
3327
+ }
3328
+ function getRunningMenuBarPid() {
3329
+ if (!existsSync3(MENUBAR_PID_FILE)) {
3330
+ return null;
3331
+ }
3332
+ try {
3333
+ const pid = Number(readFileSync2(MENUBAR_PID_FILE, "utf-8").trim());
3334
+ if (Number.isFinite(pid) && pid > 0 && isKnownMenuBarProcess(pid)) {
3335
+ process.kill(pid, 0);
3336
+ return pid;
3337
+ }
3338
+ } catch {
3339
+ }
3340
+ try {
3341
+ unlinkSync(MENUBAR_PID_FILE);
3342
+ } catch {
3343
+ }
3344
+ return null;
3345
+ }
3346
+ async function launchMenuBar() {
3347
+ if (platform() !== "darwin") return;
3348
+ removeLegacyMenuBarLaunchAgent();
3349
+ const executable = findMenuBarExecutable();
3350
+ const cliInvocation = getCurrentCliInvocation();
3351
+ if (!executable || !cliInvocation) return;
3352
+ const existingPid = getRunningMenuBarPid();
3353
+ if (existingPid) {
3354
+ return;
3355
+ }
3356
+ killExistingMenuBar();
3357
+ try {
3358
+ const { spawn: spawnChild } = await import("child_process");
3359
+ const child = spawnChild(executable, [], {
3360
+ detached: true,
3361
+ stdio: "ignore",
3362
+ env: {
3363
+ ...process.env,
3364
+ VECTOR_CLI_COMMAND: cliInvocation[0],
3365
+ VECTOR_CLI_ARGS_JSON: JSON.stringify(cliInvocation.slice(1))
3366
+ }
3367
+ });
3368
+ child.unref();
3369
+ if (child.pid) {
3370
+ writeFileSync(MENUBAR_PID_FILE, String(child.pid));
239
3371
  }
240
- if (data.every((item) => typeof item === "object" && item !== null)) {
241
- console.table(
242
- data.map(
243
- (item) => Object.fromEntries(
244
- Object.entries(item).map(([key, value]) => [key, simplify(value)])
245
- )
246
- )
247
- );
248
- return;
3372
+ } catch {
3373
+ }
3374
+ }
3375
+ function stopMenuBar() {
3376
+ killExistingMenuBar();
3377
+ }
3378
+ function getBridgeStatus() {
3379
+ const config = loadBridgeConfig();
3380
+ if (!config) return { configured: false, running: false, starting: false };
3381
+ let running = false;
3382
+ let starting = false;
3383
+ let pid;
3384
+ if (existsSync3(PID_FILE)) {
3385
+ const pidStr = readFileSync2(PID_FILE, "utf-8").trim();
3386
+ pid = Number(pidStr);
3387
+ try {
3388
+ process.kill(pid, 0);
3389
+ running = true;
3390
+ } catch {
3391
+ running = false;
249
3392
  }
250
3393
  }
251
- if (typeof data === "object" && data !== null) {
252
- console.dir(data, { depth: null, colors: true });
253
- return;
3394
+ if (!running && platform() === "darwin") {
3395
+ starting = runLaunchctl(["print", `${launchctlGuiDomain()}/${LAUNCHAGENT_LABEL}`]) || runLaunchctl(["list", LAUNCHAGENT_LABEL]);
254
3396
  }
255
- console.log(String(data));
256
- }
257
-
258
- // ../../src/cli/session.ts
259
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
260
- import { homedir } from "os";
261
- import path from "path";
262
- var SESSION_ROOT = path.join(homedir(), ".vector");
263
- function getSessionPath(profile = "default") {
264
- return path.join(SESSION_ROOT, `cli-${profile}.json`);
3397
+ return { configured: true, running, starting, pid, config };
265
3398
  }
266
- async function readSession(profile = "default") {
3399
+ function stopBridge(options) {
3400
+ if (options?.includeMenuBar) {
3401
+ killExistingMenuBar();
3402
+ }
267
3403
  try {
268
- const raw = await readFile(getSessionPath(profile), "utf8");
269
- const parsed = JSON.parse(raw);
270
- return {
271
- version: 1,
272
- cookies: {},
273
- ...parsed
274
- };
3404
+ writeLiveActivitiesCache([]);
275
3405
  } catch {
276
- return null;
3406
+ }
3407
+ if (!existsSync3(PID_FILE)) return false;
3408
+ const pid = Number(readFileSync2(PID_FILE, "utf-8").trim());
3409
+ try {
3410
+ process.kill(pid, "SIGTERM");
3411
+ return true;
3412
+ } catch {
3413
+ return false;
277
3414
  }
278
3415
  }
279
- async function writeSession(session, profile = "default") {
280
- await mkdir(SESSION_ROOT, { recursive: true });
281
- await writeFile(
282
- getSessionPath(profile),
283
- `${JSON.stringify(session, null, 2)}
284
- `,
285
- "utf8"
286
- );
287
- }
288
- async function clearSession(profile = "default") {
289
- await rm(getSessionPath(profile), { force: true });
290
- }
291
- function createEmptySession() {
292
- return {
293
- version: 1,
294
- cookies: {}
295
- };
3416
+ function ts2() {
3417
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString();
296
3418
  }
297
3419
 
298
- // ../../src/cli/index.ts
3420
+ // src/index.ts
3421
+ import { platform as osPlatform } from "os";
299
3422
  loadEnv({ path: ".env.local", override: false });
300
3423
  loadEnv({ path: ".env", override: false });
301
3424
  var cliApi = {
@@ -404,13 +3527,128 @@ function buildPaginationOptions(limit, cursor) {
404
3527
  function normalizeMatch(value) {
405
3528
  return value?.trim().toLowerCase();
406
3529
  }
3530
+ function parseDate(value) {
3531
+ const ms = Date.parse(value);
3532
+ if (!Number.isFinite(ms)) {
3533
+ throw new Error(`Invalid date: ${value}`);
3534
+ }
3535
+ return ms;
3536
+ }
3537
+ function applyListFilters(items, options) {
3538
+ let result = [...items];
3539
+ if (options.createdAfter) {
3540
+ const threshold = parseDate(options.createdAfter);
3541
+ result = result.filter(
3542
+ (item) => typeof item.createdAt === "number" && item.createdAt >= threshold
3543
+ );
3544
+ }
3545
+ if (options.createdBefore) {
3546
+ const threshold = parseDate(options.createdBefore);
3547
+ result = result.filter(
3548
+ (item) => typeof item.createdAt === "number" && item.createdAt <= threshold
3549
+ );
3550
+ }
3551
+ if (options.updatedAfter) {
3552
+ const threshold = parseDate(options.updatedAfter);
3553
+ result = result.filter(
3554
+ (item) => typeof item.lastEditedAt === "number" && item.lastEditedAt >= threshold
3555
+ );
3556
+ }
3557
+ if (options.updatedBefore) {
3558
+ const threshold = parseDate(options.updatedBefore);
3559
+ result = result.filter(
3560
+ (item) => typeof item.lastEditedAt === "number" && item.lastEditedAt <= threshold
3561
+ );
3562
+ }
3563
+ if (options.sort) {
3564
+ const field = options.sort;
3565
+ const desc = options.order?.toLowerCase() === "desc";
3566
+ result.sort((a, b) => {
3567
+ const aVal = a[field];
3568
+ const bVal = b[field];
3569
+ if (aVal == null && bVal == null) return 0;
3570
+ if (aVal == null) return 1;
3571
+ if (bVal == null) return -1;
3572
+ if (typeof aVal === "number" && typeof bVal === "number")
3573
+ return desc ? bVal - aVal : aVal - bVal;
3574
+ return desc ? String(bVal).localeCompare(String(aVal)) : String(aVal).localeCompare(String(bVal));
3575
+ });
3576
+ }
3577
+ if (options.limit) {
3578
+ const limit = Number(options.limit);
3579
+ if (Number.isFinite(limit) && limit > 0) {
3580
+ result = result.slice(0, limit);
3581
+ }
3582
+ }
3583
+ return result;
3584
+ }
3585
+ function addEntityUrls(items, appUrl, orgSlug, entityType) {
3586
+ return items.map((item) => {
3587
+ let path3;
3588
+ switch (entityType) {
3589
+ case "issues":
3590
+ path3 = `/${orgSlug}/issues/${item.key}`;
3591
+ break;
3592
+ case "projects":
3593
+ path3 = `/${orgSlug}/projects/${item.key}`;
3594
+ break;
3595
+ case "teams":
3596
+ path3 = `/${orgSlug}/teams/${item.key}`;
3597
+ break;
3598
+ case "documents":
3599
+ path3 = `/${orgSlug}/documents/${item.id}`;
3600
+ break;
3601
+ case "folders":
3602
+ path3 = `/${orgSlug}/documents/folders/${item.id}`;
3603
+ break;
3604
+ }
3605
+ return { ...item, url: `${appUrl}${path3}` };
3606
+ });
3607
+ }
3608
+ function normalizeAppUrl(raw) {
3609
+ let url = raw.trim();
3610
+ if (!/^https?:\/\//i.test(url)) {
3611
+ const isLocal = /^localhost(:\d+)?/i.test(url) || /^127\.0\.0\.1(:\d+)?/.test(url);
3612
+ url = isLocal ? `http://${url}` : `https://${url}`;
3613
+ }
3614
+ return url.replace(/\/+$/, "");
3615
+ }
3616
+ async function resolveAppUrl(raw) {
3617
+ const url = normalizeAppUrl(raw);
3618
+ try {
3619
+ const response = await fetch(url, { method: "HEAD", redirect: "follow" });
3620
+ const resolved = new URL(response.url).origin;
3621
+ return resolved;
3622
+ } catch {
3623
+ return url;
3624
+ }
3625
+ }
3626
+ async function fetchConvexUrl(appUrl) {
3627
+ try {
3628
+ const url = new URL("/api/config", appUrl).toString();
3629
+ const response = await fetch(url);
3630
+ if (!response.ok) {
3631
+ throw new Error(`HTTP ${response.status}`);
3632
+ }
3633
+ const data = await response.json();
3634
+ if (data.convexUrl) {
3635
+ return data.convexUrl;
3636
+ }
3637
+ } catch {
3638
+ }
3639
+ return "http://127.0.0.1:3210";
3640
+ }
407
3641
  async function getRuntime(command) {
408
3642
  const options = command.optsWithGlobals();
409
- const profile = options.profile ?? "default";
3643
+ const profile = options.profile ?? await readDefaultProfile();
410
3644
  const session = await readSession(profile);
411
3645
  const appUrlSource = options.appUrl ?? session?.appUrl ?? process.env.NEXT_PUBLIC_APP_URL;
412
- const appUrl = requiredString(appUrlSource, "app URL");
413
- const convexUrl = options.convexUrl ?? session?.convexUrl ?? process.env.NEXT_PUBLIC_CONVEX_URL ?? process.env.CONVEX_URL ?? "http://127.0.0.1:3210";
3646
+ const appUrl = await resolveAppUrl(requiredString(appUrlSource, "app URL"));
3647
+ let convexUrl = options.convexUrl ?? session?.convexUrl;
3648
+ if (!convexUrl) {
3649
+ const fetchedUrl = await fetchConvexUrl(appUrl);
3650
+ convexUrl = fetchedUrl !== "http://127.0.0.1:3210" ? fetchedUrl : process.env.NEXT_PUBLIC_CONVEX_URL ?? process.env.CONVEX_URL ?? fetchedUrl;
3651
+ }
414
3652
  return {
415
3653
  appUrl,
416
3654
  convexUrl,
@@ -421,7 +3659,7 @@ async function getRuntime(command) {
421
3659
  };
422
3660
  }
423
3661
  function requireSession(runtime) {
424
- if (!runtime.session || Object.keys(runtime.session.cookies).length === 0) {
3662
+ if (!runtime.session || Object.keys(runtime.session.cookies).length === 0 && !runtime.session.bearerToken) {
425
3663
  throw new Error("Not logged in. Run `vcli auth login` first.");
426
3664
  }
427
3665
  return runtime.session;
@@ -435,6 +3673,80 @@ function requireOrg(runtime, explicit) {
435
3673
  }
436
3674
  return orgSlug;
437
3675
  }
3676
+ function hostForAppUrl(appUrl) {
3677
+ if (!appUrl) {
3678
+ return void 0;
3679
+ }
3680
+ try {
3681
+ const url = new URL(appUrl);
3682
+ return url.port ? `${url.hostname}:${url.port}` : url.hostname;
3683
+ } catch {
3684
+ return void 0;
3685
+ }
3686
+ }
3687
+ function decodeSessionClaims(session) {
3688
+ const jwt = session?.cookies?.["__Secure-better-auth.convex_jwt"];
3689
+ if (!jwt) {
3690
+ return {};
3691
+ }
3692
+ const parts = jwt.split(".");
3693
+ if (parts.length < 2) {
3694
+ return {};
3695
+ }
3696
+ let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
3697
+ while (payload.length % 4 !== 0) {
3698
+ payload += "=";
3699
+ }
3700
+ try {
3701
+ const decoded = JSON.parse(
3702
+ Buffer.from(payload, "base64").toString("utf8")
3703
+ );
3704
+ return {
3705
+ email: decoded.email,
3706
+ userId: decoded.sub ?? decoded.userId
3707
+ };
3708
+ } catch {
3709
+ return {};
3710
+ }
3711
+ }
3712
+ function buildMenuSessionInfo(session) {
3713
+ const claims = decodeSessionClaims(session);
3714
+ return {
3715
+ orgSlug: session?.activeOrgSlug ?? "oss-lab",
3716
+ appUrl: session?.appUrl,
3717
+ appDomain: hostForAppUrl(session?.appUrl),
3718
+ email: claims.email,
3719
+ userId: claims.userId
3720
+ };
3721
+ }
3722
+ function parseAgentProvider(value) {
3723
+ if (value === "codex" || value === "claude_code" || value === "vector_cli") {
3724
+ return value;
3725
+ }
3726
+ throw new Error("provider must be one of: codex, claude_code, vector_cli");
3727
+ }
3728
+ function isBridgeDeviceAuthError(error) {
3729
+ if (!error || typeof error !== "object") {
3730
+ return false;
3731
+ }
3732
+ const maybeData = error.data;
3733
+ return maybeData === "INVALID_DEVICE_SECRET" || maybeData === "DEVICE_NOT_FOUND";
3734
+ }
3735
+ async function validateStoredBridgeConfig(config) {
3736
+ const client = new ConvexHttpClient3(config.convexUrl);
3737
+ try {
3738
+ await client.mutation(api.agentBridge.bridgePublic.heartbeat, {
3739
+ deviceId: config.deviceId,
3740
+ deviceSecret: config.deviceSecret
3741
+ });
3742
+ return true;
3743
+ } catch (error) {
3744
+ if (isBridgeDeviceAuthError(error)) {
3745
+ return false;
3746
+ }
3747
+ throw error;
3748
+ }
3749
+ }
438
3750
  async function getClient(command) {
439
3751
  const runtime = await getRuntime(command);
440
3752
  const session = requireSession(runtime);
@@ -445,6 +3757,39 @@ async function getClient(command) {
445
3757
  );
446
3758
  return { client, runtime, session };
447
3759
  }
3760
+ async function ensureBridgeConfig(command) {
3761
+ let config = loadBridgeConfig();
3762
+ try {
3763
+ const runtime = await getRuntime(command);
3764
+ const session = requireSession(runtime);
3765
+ const client = await createConvexClient(
3766
+ session,
3767
+ runtime.appUrl,
3768
+ runtime.convexUrl
3769
+ );
3770
+ const user = await runQuery(client, api.users.currentUser);
3771
+ if (!user) {
3772
+ throw new Error("Not logged in. Run `vcli auth login` first.");
3773
+ }
3774
+ const backendDevice = config ? await runQuery(client, api.agentBridge.queries.getDevice, {
3775
+ deviceId: config.deviceId
3776
+ }) : null;
3777
+ const needsRegistration = !config || config.userId !== user._id || config.convexUrl !== runtime.convexUrl || !backendDevice || !await validateStoredBridgeConfig(config);
3778
+ if (needsRegistration) {
3779
+ config = await setupBridgeDevice(client, runtime.convexUrl);
3780
+ }
3781
+ if (!config) {
3782
+ throw new Error("Bridge device is not configured.");
3783
+ }
3784
+ saveBridgeConfig(config);
3785
+ return config;
3786
+ } catch (error) {
3787
+ if (config) {
3788
+ return config;
3789
+ }
3790
+ throw error;
3791
+ }
3792
+ }
448
3793
  async function resolveMemberId(client, orgSlug, ref) {
449
3794
  const members = await runQuery(
450
3795
  client,
@@ -620,10 +3965,19 @@ async function parseEstimatedTimes(client, orgSlug, value) {
620
3965
  return estimatedTimes;
621
3966
  }
622
3967
  var program = new Command();
623
- program.name("vcli").description("Vector CLI").showHelpAfterError().option(
3968
+ function readPackageVersionSync() {
3969
+ try {
3970
+ const dir = import.meta.dirname ?? dirname(fileURLToPath2(import.meta.url));
3971
+ const raw = readFileSync3(join3(dir, "..", "package.json"), "utf8");
3972
+ return JSON.parse(raw).version ?? "unknown";
3973
+ } catch {
3974
+ return "unknown";
3975
+ }
3976
+ }
3977
+ program.name("vcli").description("Vector CLI").version(readPackageVersionSync(), "-v, --version").showHelpAfterError().option(
624
3978
  "--app-url <url>",
625
3979
  "Vector app URL. Required unless saved in the profile or NEXT_PUBLIC_APP_URL is set."
626
- ).option("--convex-url <url>", "Convex deployment URL").option("--org <slug>", "Organization slug override").option("--profile <name>", "CLI profile name", "default").option("--json", "Output JSON");
3980
+ ).option("--convex-url <url>", "Convex deployment URL").option("--org <slug>", "Organization slug override").option("--profile <name>", "CLI profile name").option("--json", "Output JSON");
627
3981
  var authCommand = program.command("auth").description("Authentication");
628
3982
  authCommand.command("signup").option("--email <email>", "Email address").option("--username <username>", "Username").option("--password <password>", "Password").action(async (options, command) => {
629
3983
  const runtime = await getRuntime(command);
@@ -664,19 +4018,48 @@ authCommand.command("signup").option("--email <email>", "Email address").option(
664
4018
  runtime.json
665
4019
  );
666
4020
  });
667
- authCommand.command("login [identifier]").option("--password <password>", "Password").action(async (identifier, options, command) => {
4021
+ authCommand.command("login [identifier]").option("--password <password>", "Password (uses device flow if omitted)").action(async (identifier, options, command) => {
668
4022
  const runtime = await getRuntime(command);
669
- const loginId = identifier?.trim() || await prompt("Email or username: ");
670
- const password = options.password?.trim() || await promptSecret("Password: ");
671
4023
  let session = createEmptySession();
672
4024
  session.appUrl = runtime.appUrl;
673
4025
  session.convexUrl = runtime.convexUrl;
674
- session = await loginWithPassword(
675
- session,
676
- runtime.appUrl,
677
- loginId,
678
- password
679
- );
4026
+ const usePassword = Boolean(identifier || options.password);
4027
+ if (usePassword) {
4028
+ const loginId = identifier?.trim() || await prompt("Email or username: ");
4029
+ const password = options.password?.trim() || await promptSecret("Password: ");
4030
+ session = await loginWithPassword(
4031
+ session,
4032
+ runtime.appUrl,
4033
+ loginId,
4034
+ password
4035
+ );
4036
+ } else {
4037
+ const deviceResp = await requestDeviceCode(runtime.appUrl, "vcli");
4038
+ const verifyUrl = `${runtime.appUrl}/device?user_code=${deviceResp.user_code}`;
4039
+ console.log();
4040
+ console.log(` Open this URL in your browser to log in:`);
4041
+ console.log();
4042
+ console.log(` ${verifyUrl}`);
4043
+ console.log();
4044
+ console.log(` Or go to ${runtime.appUrl}/device and enter code:`);
4045
+ console.log();
4046
+ console.log(` ${deviceResp.user_code}`);
4047
+ console.log();
4048
+ const open2 = await Promise.resolve().then(() => (init_open(), open_exports)).then((m) => m.default).catch(() => null);
4049
+ if (open2) {
4050
+ await open2(verifyUrl).catch(() => {
4051
+ });
4052
+ }
4053
+ console.log(" Waiting for authorization...");
4054
+ session = await pollDeviceToken(
4055
+ session,
4056
+ runtime.appUrl,
4057
+ deviceResp.device_code,
4058
+ "vcli",
4059
+ deviceResp.interval,
4060
+ deviceResp.expires_in
4061
+ );
4062
+ }
680
4063
  const authState = await fetchAuthSession(session, runtime.appUrl);
681
4064
  session = authState.session;
682
4065
  const client = await createConvexClient(
@@ -717,6 +4100,38 @@ authCommand.command("whoami").action(async (_options, command) => {
717
4100
  runtime.json
718
4101
  );
719
4102
  });
4103
+ authCommand.command("profiles").action(async (_options, command) => {
4104
+ const options = command.optsWithGlobals();
4105
+ const explicitProfile = options.profile?.trim();
4106
+ const defaultProfile = await readDefaultProfile();
4107
+ const profiles = await listProfiles();
4108
+ const activeProfile = explicitProfile || defaultProfile;
4109
+ printOutput(
4110
+ {
4111
+ activeProfile,
4112
+ defaultProfile,
4113
+ profiles
4114
+ },
4115
+ Boolean(options.json)
4116
+ );
4117
+ });
4118
+ authCommand.command("use-profile <name>").action(async (name, _options, command) => {
4119
+ const options = command.optsWithGlobals();
4120
+ const profile = name.trim();
4121
+ if (!profile) {
4122
+ throw new Error("Profile name is required.");
4123
+ }
4124
+ await writeDefaultProfile(profile);
4125
+ const session = await readSession(profile);
4126
+ printOutput(
4127
+ {
4128
+ ok: true,
4129
+ defaultProfile: profile,
4130
+ hasSession: session !== null
4131
+ },
4132
+ Boolean(options.json)
4133
+ );
4134
+ });
720
4135
  var orgCommand = program.command("org").description("Organizations");
721
4136
  orgCommand.command("list").action(async (_options, command) => {
722
4137
  const { client, runtime } = await getClient(command);
@@ -1047,6 +4462,75 @@ permissionCommand.command("check-many <permissions>").option("--team <teamKey>")
1047
4462
  printOutput(result, runtime.json);
1048
4463
  });
1049
4464
  var activityCommand = program.command("activity").description("Activity feed");
4465
+ activityCommand.command("list").description(
4466
+ "List org-wide activity with optional filters by entity type, event type, and time range"
4467
+ ).option(
4468
+ "--entity-type <type>",
4469
+ "Filter by entity type: issue, project, team, document"
4470
+ ).option(
4471
+ "--event-type <type>",
4472
+ "Filter by event type (e.g. issue_created, issue_priority_changed)"
4473
+ ).option(
4474
+ "--since <datetime>",
4475
+ "Start of time range (ISO date or shorthand: today, yesterday, 7d, 30d)"
4476
+ ).option(
4477
+ "--until <datetime>",
4478
+ "End of time range (ISO date or shorthand: today, now)"
4479
+ ).option("--limit <n>").option("--cursor <cursor>").action(async (options, command) => {
4480
+ const { client, runtime } = await getClient(command);
4481
+ const orgSlug = requireOrg(runtime);
4482
+ function parseTimeArg(value, bound) {
4483
+ if (!value) return void 0;
4484
+ const now = /* @__PURE__ */ new Date();
4485
+ const startOfToday = new Date(
4486
+ now.getFullYear(),
4487
+ now.getMonth(),
4488
+ now.getDate()
4489
+ );
4490
+ const endOfToday = new Date(
4491
+ now.getFullYear(),
4492
+ now.getMonth(),
4493
+ now.getDate(),
4494
+ 23,
4495
+ 59,
4496
+ 59,
4497
+ 999
4498
+ );
4499
+ switch (value) {
4500
+ case "now":
4501
+ return now.getTime();
4502
+ case "today":
4503
+ return bound === "start" ? startOfToday.getTime() : endOfToday.getTime();
4504
+ case "yesterday":
4505
+ return bound === "start" ? startOfToday.getTime() - 864e5 : startOfToday.getTime() - 1;
4506
+ default: {
4507
+ const daysMatch = value.match(/^(\d+)d$/);
4508
+ if (daysMatch) {
4509
+ return now.getTime() - Number(daysMatch[1]) * 864e5;
4510
+ }
4511
+ const parsed = new Date(value).getTime();
4512
+ if (Number.isNaN(parsed)) {
4513
+ throw new Error(`Invalid time value: ${value}`);
4514
+ }
4515
+ return parsed;
4516
+ }
4517
+ }
4518
+ }
4519
+ const result = await runQuery(
4520
+ client,
4521
+ api.activities.queries.listOrgActivity,
4522
+ {
4523
+ orgSlug,
4524
+ entityType: options.entityType ?? void 0,
4525
+ eventType: options.eventType ?? void 0,
4526
+ since: parseTimeArg(options.since, "start"),
4527
+ until: parseTimeArg(options.until, "end"),
4528
+ limit: optionalNumber(options.limit, "limit") ?? void 0,
4529
+ cursor: options.cursor ?? void 0
4530
+ }
4531
+ );
4532
+ printOutput(result, runtime.json);
4533
+ });
1050
4534
  activityCommand.command("project <projectKey>").option("--limit <n>").option("--cursor <cursor>").action(async (projectKey, options, command) => {
1051
4535
  const { client, runtime } = await getClient(command);
1052
4536
  const orgSlug = requireOrg(runtime);
@@ -1503,13 +4987,14 @@ adminCommand.command("sync-disposable-domains").action(async (_options, command)
1503
4987
  printOutput(result, runtime.json);
1504
4988
  });
1505
4989
  var teamCommand = program.command("team").description("Teams");
1506
- teamCommand.command("list [slug]").option("--limit <n>").action(async (slug, options, command) => {
4990
+ teamCommand.command("list [slug]").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, name, key)").option("--order <direction>", "Sort order: asc or desc (default: asc)").action(async (slug, options, command) => {
1507
4991
  const { client, runtime } = await getClient(command);
1508
4992
  const orgSlug = requireOrg(runtime, slug);
1509
- const result = await runAction(client, cliApi.listTeams, {
1510
- orgSlug,
1511
- limit: options.limit ? Number(options.limit) : void 0
4993
+ const raw = await runAction(client, cliApi.listTeams, {
4994
+ orgSlug
1512
4995
  });
4996
+ const filtered = applyListFilters(raw, options);
4997
+ const result = addEntityUrls(filtered, runtime.appUrl, orgSlug, "teams");
1513
4998
  printOutput(result, runtime.json);
1514
4999
  });
1515
5000
  teamCommand.command("get <teamKey>").action(async (teamKey, _options, command) => {
@@ -1603,14 +5088,15 @@ teamCommand.command("set-lead <teamKey> <member>").action(async (teamKey, member
1603
5088
  printOutput(result, runtime.json);
1604
5089
  });
1605
5090
  var projectCommand = program.command("project").description("Projects");
1606
- projectCommand.command("list [slug]").option("--team <teamKey>").option("--limit <n>").action(async (slug, options, command) => {
5091
+ projectCommand.command("list [slug]").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, name, key)").option("--order <direction>", "Sort order: asc or desc (default: asc)").action(async (slug, options, command) => {
1607
5092
  const { client, runtime } = await getClient(command);
1608
5093
  const orgSlug = requireOrg(runtime, slug);
1609
- const result = await runAction(client, cliApi.listProjects, {
5094
+ const raw = await runAction(client, cliApi.listProjects, {
1610
5095
  orgSlug,
1611
- teamKey: options.team,
1612
- limit: options.limit ? Number(options.limit) : void 0
5096
+ teamKey: options.team
1613
5097
  });
5098
+ const filtered = applyListFilters(raw, options);
5099
+ const result = addEntityUrls(filtered, runtime.appUrl, orgSlug, "projects");
1614
5100
  printOutput(result, runtime.json);
1615
5101
  });
1616
5102
  projectCommand.command("get <projectKey>").action(async (projectKey, _options, command) => {
@@ -1710,15 +5196,17 @@ projectCommand.command("set-lead <projectKey> <member>").action(async (projectKe
1710
5196
  printOutput(result, runtime.json);
1711
5197
  });
1712
5198
  var issueCommand = program.command("issue").description("Issues");
1713
- issueCommand.command("list [slug]").option("--project <projectKey>").option("--team <teamKey>").option("--limit <n>").action(async (slug, options, command) => {
5199
+ 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) => {
1714
5200
  const { client, runtime } = await getClient(command);
1715
5201
  const orgSlug = requireOrg(runtime, slug);
1716
- const result = await runAction(client, cliApi.listIssues, {
5202
+ const raw = await runAction(client, cliApi.listIssues, {
1717
5203
  orgSlug,
1718
5204
  projectKey: options.project,
1719
5205
  teamKey: options.team,
1720
- limit: options.limit ? Number(options.limit) : void 0
5206
+ assigneeName: options.assignee
1721
5207
  });
5208
+ const filtered = applyListFilters(raw, options);
5209
+ const result = addEntityUrls(filtered, runtime.appUrl, orgSlug, "issues");
1722
5210
  printOutput(result, runtime.json);
1723
5211
  });
1724
5212
  issueCommand.command("get <issueKey>").action(async (issueKey, _options, command) => {
@@ -1915,15 +5403,40 @@ issueCommand.command("comment <issueKey>").requiredOption("--body <body>").actio
1915
5403
  });
1916
5404
  printOutput(result, runtime.json);
1917
5405
  });
5406
+ issueCommand.command("link-github <issueKey> <url>").description("Link a GitHub pull request, issue, or commit URL to an issue").action(async (issueKey, url, _options, command) => {
5407
+ const { client, runtime } = await getClient(command);
5408
+ const orgSlug = requireOrg(runtime);
5409
+ await runAction(client, api.github.actions.linkArtifactByUrl, {
5410
+ orgSlug,
5411
+ issueKey,
5412
+ url
5413
+ });
5414
+ printOutput({ success: true, issueKey, url }, runtime.json);
5415
+ });
1918
5416
  var documentCommand = program.command("document").description("Documents");
1919
- documentCommand.command("list [slug]").option("--folder-id <id>").option("--limit <n>").action(async (slug, options, command) => {
5417
+ documentCommand.command("list [slug]").option("--folder-id <id>").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(
5418
+ "--updated-after <date>",
5419
+ "Filter: last edited on or after date (ISO)"
5420
+ ).option(
5421
+ "--updated-before <date>",
5422
+ "Filter: last edited on or before date (ISO)"
5423
+ ).option(
5424
+ "--sort <field>",
5425
+ "Sort by field (e.g. createdAt, title, lastEditedAt)"
5426
+ ).option("--order <direction>", "Sort order: asc or desc (default: asc)").action(async (slug, options, command) => {
1920
5427
  const { client, runtime } = await getClient(command);
1921
5428
  const orgSlug = requireOrg(runtime, slug);
1922
- const result = await runAction(client, cliApi.listDocuments, {
5429
+ const raw = await runAction(client, cliApi.listDocuments, {
1923
5430
  orgSlug,
1924
- folderId: options.folderId,
1925
- limit: options.limit ? Number(options.limit) : void 0
5431
+ folderId: options.folderId
1926
5432
  });
5433
+ const filtered = applyListFilters(raw, options);
5434
+ const result = addEntityUrls(
5435
+ filtered,
5436
+ runtime.appUrl,
5437
+ orgSlug,
5438
+ "documents"
5439
+ );
1927
5440
  printOutput(result, runtime.json);
1928
5441
  });
1929
5442
  documentCommand.command("get <documentId>").action(async (documentId, _options, command) => {
@@ -1989,10 +5502,12 @@ documentCommand.command("delete <documentId>").action(async (documentId, _option
1989
5502
  printOutput(result, runtime.json);
1990
5503
  });
1991
5504
  var folderCommand = program.command("folder").description("Document folders");
1992
- folderCommand.command("list [slug]").action(async (slug, _options, command) => {
5505
+ folderCommand.command("list [slug]").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, name)").option("--order <direction>", "Sort order: asc or desc (default: asc)").action(async (slug, options, command) => {
1993
5506
  const { client, runtime } = await getClient(command);
1994
5507
  const orgSlug = requireOrg(runtime, slug);
1995
- const result = await runAction(client, cliApi.listFolders, { orgSlug });
5508
+ const raw = await runAction(client, cliApi.listFolders, { orgSlug });
5509
+ const filtered = applyListFilters(raw, options);
5510
+ const result = addEntityUrls(filtered, runtime.appUrl, orgSlug, "folders");
1996
5511
  printOutput(result, runtime.json);
1997
5512
  });
1998
5513
  folderCommand.command("create").requiredOption("--name <name>").option("--description <description>").option("--icon <icon>").option("--color <color>").action(async (options, command) => {
@@ -2032,6 +5547,272 @@ folderCommand.command("delete <folderId>").action(async (folderId, _options, com
2032
5547
  });
2033
5548
  printOutput(result, runtime.json);
2034
5549
  });
5550
+ var VECTOR_HOME = process.env.VECTOR_HOME?.trim() || `${process.env.HOME ?? "~"}/.vector`;
5551
+ var serviceCommand = program.command("service").description("Manage the local bridge service");
5552
+ serviceCommand.command("start").description("Start the bridge service via LaunchAgent (macOS) or foreground").action(async (_options, command) => {
5553
+ const existing = getBridgeStatus();
5554
+ if (existing.running) {
5555
+ console.log(`Bridge is already running (PID ${existing.pid}).`);
5556
+ return;
5557
+ }
5558
+ const { spinner } = await import("@clack/prompts");
5559
+ const s = spinner();
5560
+ s.start("Ensuring device registration...");
5561
+ const config = await ensureBridgeConfig(command);
5562
+ s.stop(`Device ready: ${config.displayName}`);
5563
+ if (osPlatform() === "darwin") {
5564
+ s.start("Starting bridge service...");
5565
+ const vcliPath = process.argv[1] ?? "vcli";
5566
+ installLaunchAgent(vcliPath);
5567
+ loadLaunchAgent();
5568
+ s.stop("Bridge service started.");
5569
+ } else {
5570
+ console.log(
5571
+ "Starting bridge in foreground (use systemd for background)..."
5572
+ );
5573
+ const bridge = new BridgeService(config);
5574
+ await bridge.run();
5575
+ }
5576
+ });
5577
+ serviceCommand.command("run").description("Run the bridge service in the foreground (used by LaunchAgent)").action(async (_options, command) => {
5578
+ const config = await ensureBridgeConfig(command);
5579
+ if (osPlatform() === "darwin") {
5580
+ await launchMenuBar();
5581
+ }
5582
+ const bridge = new BridgeService(config);
5583
+ await bridge.run();
5584
+ });
5585
+ serviceCommand.command("stop").description("Stop the bridge service").action(() => {
5586
+ let unloaded = false;
5587
+ if (osPlatform() === "darwin") {
5588
+ unloaded = unloadLaunchAgent();
5589
+ }
5590
+ if (stopBridge() || unloaded) {
5591
+ console.log("Bridge stopped.");
5592
+ } else if (osPlatform() !== "darwin") {
5593
+ console.log("Bridge is not running.");
5594
+ } else {
5595
+ console.log("Bridge is not running.");
5596
+ }
5597
+ });
5598
+ serviceCommand.command("status").description("Show bridge service status").action((_options, command) => {
5599
+ const status = getBridgeStatus();
5600
+ if (!status.configured) {
5601
+ console.log("Bridge not configured. Run: vcli service start");
5602
+ return;
5603
+ }
5604
+ console.log("Vector Bridge");
5605
+ console.log(
5606
+ ` Device: ${status.config.displayName} (${status.config.deviceId})`
5607
+ );
5608
+ console.log(` User: ${status.config.userId}`);
5609
+ const statusLabel = status.running ? `Running (PID ${status.pid})` : status.starting ? "Starting..." : "Not running";
5610
+ console.log(` Status: ${statusLabel}`);
5611
+ console.log(` Config: ${VECTOR_HOME}/bridge.json`);
5612
+ });
5613
+ serviceCommand.command("menu-state").description("Return JSON state for the macOS tray").action(async (_options, command) => {
5614
+ const status = getBridgeStatus();
5615
+ const globalOptions = command.optsWithGlobals();
5616
+ const profile = globalOptions.profile ?? await readDefaultProfile();
5617
+ const session = await readSession(profile);
5618
+ const profiles = await listProfiles();
5619
+ const defaultProfile = await readDefaultProfile();
5620
+ let workspaces = [];
5621
+ let workSessions = [];
5622
+ let detectedSessions = [];
5623
+ try {
5624
+ const runtime = await getRuntime(command);
5625
+ if (runtime.session && status.config?.deviceId) {
5626
+ const client = await createConvexClient(
5627
+ runtime.session,
5628
+ runtime.appUrl,
5629
+ runtime.convexUrl
5630
+ );
5631
+ workspaces = await runQuery(
5632
+ client,
5633
+ api.agentBridge.queries.listDeviceWorkspaces,
5634
+ {
5635
+ deviceId: status.config.deviceId
5636
+ }
5637
+ );
5638
+ workSessions = await runQuery(
5639
+ client,
5640
+ api.agentBridge.queries.listDeviceWorkSessions,
5641
+ {
5642
+ deviceId: status.config.deviceId
5643
+ }
5644
+ );
5645
+ const devices = await runQuery(
5646
+ client,
5647
+ api.agentBridge.queries.listProcessesForAttach,
5648
+ {}
5649
+ );
5650
+ const currentDevice = devices.find(
5651
+ (entry) => entry.device._id === status.config?.deviceId
5652
+ );
5653
+ detectedSessions = currentDevice?.processes ?? [];
5654
+ }
5655
+ } catch {
5656
+ workspaces = [];
5657
+ workSessions = [];
5658
+ detectedSessions = [];
5659
+ }
5660
+ printOutput(
5661
+ {
5662
+ configured: status.configured,
5663
+ running: status.running,
5664
+ starting: status.starting,
5665
+ pid: status.pid,
5666
+ config: status.config,
5667
+ sessionInfo: buildMenuSessionInfo(session),
5668
+ activeProfile: profile,
5669
+ defaultProfile,
5670
+ profiles,
5671
+ workspaces,
5672
+ workSessions,
5673
+ detectedSessions
5674
+ },
5675
+ Boolean(globalOptions.json)
5676
+ );
5677
+ });
5678
+ serviceCommand.command("set-default-workspace").description("Set the default workspace for this device").requiredOption("--workspace-id <id>").action(async (options, command) => {
5679
+ const { client, runtime } = await getClient(command);
5680
+ const workspaceId = await runMutation(
5681
+ client,
5682
+ api.agentBridge.mutations.setDefaultWorkspace,
5683
+ {
5684
+ workspaceId: options.workspaceId
5685
+ }
5686
+ );
5687
+ printOutput({ ok: true, workspaceId }, runtime.json);
5688
+ });
5689
+ serviceCommand.command("search-issues <query>").description("Search issues for tray attach actions").option("--limit <n>").action(async (query, options, command) => {
5690
+ const { client, runtime } = await getClient(command);
5691
+ const orgSlug = requireOrg(runtime);
5692
+ const result = await runQuery(client, api.search.queries.searchEntities, {
5693
+ orgSlug,
5694
+ query,
5695
+ limit: optionalNumber(options.limit, "limit") ?? 8
5696
+ });
5697
+ printOutput(result.issues ?? [], runtime.json);
5698
+ });
5699
+ serviceCommand.command("attach-process").description("Attach a detected local process to an issue").requiredOption("--issue-id <id>").requiredOption("--device-id <id>").requiredOption("--process-id <id>").requiredOption("--provider <provider>").option("--title <title>").action(async (options, command) => {
5700
+ const { client, runtime } = await getClient(command);
5701
+ const liveActivityId = await runMutation(
5702
+ client,
5703
+ api.agentBridge.mutations.attachLiveActivity,
5704
+ {
5705
+ issueId: options.issueId,
5706
+ deviceId: options.deviceId,
5707
+ processId: options.processId,
5708
+ provider: parseAgentProvider(options.provider),
5709
+ title: options.title?.trim() || void 0
5710
+ }
5711
+ );
5712
+ printOutput({ ok: true, liveActivityId }, runtime.json);
5713
+ });
5714
+ serviceCommand.command("install").description("Install the bridge as a system service (macOS LaunchAgent)").action(async (_options, command) => {
5715
+ if (osPlatform() !== "darwin") {
5716
+ console.error("Service install is currently macOS only (LaunchAgent).");
5717
+ console.error("On Linux, use systemd --user manually for now.");
5718
+ return;
5719
+ }
5720
+ const { spinner } = await import("@clack/prompts");
5721
+ const s = spinner();
5722
+ s.start("Ensuring device registration...");
5723
+ const config = await ensureBridgeConfig(command);
5724
+ s.stop(`Device ready: ${config.displayName}`);
5725
+ s.start("Installing LaunchAgent...");
5726
+ const vcliPath = process.argv[1] ?? "vcli";
5727
+ installLaunchAgent(vcliPath);
5728
+ s.stop("LaunchAgent installed");
5729
+ s.start("Starting bridge service...");
5730
+ loadLaunchAgent();
5731
+ s.stop("Bridge service started");
5732
+ console.log("");
5733
+ console.log(
5734
+ "Bridge installed and running. Will start automatically on login."
5735
+ );
5736
+ });
5737
+ serviceCommand.command("uninstall").description("Stop the bridge and uninstall the system service").action(() => {
5738
+ stopBridge({ includeMenuBar: true });
5739
+ uninstallLaunchAgent();
5740
+ stopMenuBar();
5741
+ console.log("Bridge stopped and service uninstalled.");
5742
+ });
5743
+ serviceCommand.command("logs").description("Show bridge service logs").action(async () => {
5744
+ const fs7 = await import("fs");
5745
+ const p = await import("path");
5746
+ const logPath = p.join(VECTOR_HOME, "bridge.log");
5747
+ if (fs7.existsSync(logPath)) {
5748
+ const content = fs7.readFileSync(logPath, "utf-8");
5749
+ const lines = content.split("\n");
5750
+ console.log(lines.slice(-50).join("\n"));
5751
+ } else {
5752
+ console.log(`No log file found at ${logPath}`);
5753
+ }
5754
+ });
5755
+ serviceCommand.command("enable").description("Enable bridge to start at login (macOS LaunchAgent)").action(async () => {
5756
+ if (osPlatform() !== "darwin") {
5757
+ console.error("Login item is macOS only.");
5758
+ return;
5759
+ }
5760
+ const vcliPath = process.argv[1] ?? "vcli";
5761
+ installLaunchAgent(vcliPath);
5762
+ loadLaunchAgent();
5763
+ console.log("Bridge will start automatically on login.");
5764
+ });
5765
+ serviceCommand.command("disable").description("Disable bridge from starting at login").action(() => {
5766
+ uninstallLaunchAgent();
5767
+ stopMenuBar();
5768
+ console.log("Bridge will no longer start at login.");
5769
+ });
5770
+ var bridgeCommand = program.command("bridge").description("Start/stop the local agent bridge");
5771
+ bridgeCommand.command("start").description("Register device, install service, and start the bridge").action(async (_options, command) => {
5772
+ const existingConfig = loadBridgeConfig();
5773
+ const config = await ensureBridgeConfig(command);
5774
+ if (!existingConfig || existingConfig.deviceId !== config.deviceId || existingConfig.userId !== config.userId) {
5775
+ console.log(
5776
+ `Device registered: ${config.displayName} (${config.deviceId})`
5777
+ );
5778
+ }
5779
+ if (osPlatform() === "darwin") {
5780
+ const vcliPath = process.argv[1] ?? "vcli";
5781
+ installLaunchAgent(vcliPath);
5782
+ loadLaunchAgent();
5783
+ console.log("\nBridge installed and started as LaunchAgent.");
5784
+ console.log("It will restart automatically on login.");
5785
+ console.log("Run `vcli service status` to check.");
5786
+ } else {
5787
+ console.log("Starting bridge in foreground...");
5788
+ const bridge = new BridgeService(config);
5789
+ await bridge.run();
5790
+ }
5791
+ });
5792
+ bridgeCommand.command("stop").description("Stop the bridge service").action(() => {
5793
+ let unloaded = false;
5794
+ if (osPlatform() === "darwin") {
5795
+ uninstallLaunchAgent();
5796
+ unloaded = true;
5797
+ }
5798
+ if (stopBridge() || unloaded) {
5799
+ console.log("Bridge stopped.");
5800
+ } else {
5801
+ console.log("Bridge is not running.");
5802
+ }
5803
+ });
5804
+ bridgeCommand.command("status").description("Show bridge status").action(() => {
5805
+ const s = getBridgeStatus();
5806
+ if (!s.configured) {
5807
+ console.log("Bridge not configured. Run: vcli bridge start");
5808
+ return;
5809
+ }
5810
+ console.log("Vector Bridge");
5811
+ console.log(` Device: ${s.config.displayName} (${s.config.deviceId})`);
5812
+ console.log(
5813
+ ` Status: ${s.running ? `Running (PID ${s.pid})` : "Not running"}`
5814
+ );
5815
+ });
2035
5816
  async function main() {
2036
5817
  await program.parseAsync(process.argv);
2037
5818
  }