@rehpic/vcli 0.1.0-beta.51.1 → 0.1.0-beta.52.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 +66 -81
- package/dist/index.js.map +1 -1
- package/dist/menubar.js +242 -0
- package/dist/menubar.js.map +1 -0
- package/package.json +4 -2
- package/scripts/postinstall.js +106 -0
package/dist/menubar.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// src/menubar.ts
|
|
2
|
+
import SysTrayModule from "systray2";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
var SysTray = typeof SysTrayModule.default === "function" ? SysTrayModule.default : SysTrayModule;
|
|
8
|
+
var CONFIG_DIR = join(homedir(), ".vector");
|
|
9
|
+
var BRIDGE_CONFIG_FILE = join(CONFIG_DIR, "bridge.json");
|
|
10
|
+
var PID_FILE = join(CONFIG_DIR, "bridge.pid");
|
|
11
|
+
var LIVE_ACTIVITIES_FILE = join(CONFIG_DIR, "live-activities.json");
|
|
12
|
+
var EMBEDDED_ICON = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABmJLR0QA/wD/AP+gvaeTAAABfUlEQVQ4ja2UT0uUURTGf2eWQYaLWgTjjCFKHyOS6BPopk1pKrgWaSGFy8pNQdsgRgT7BG5qlwa5ldqILkWngqCN2a/FnOLy8k4j1IF38Z7nOc89/+6F/2RR51SHgWngFtAGfgL7wFugExHfBiqrc2pXPVPfqS/ze6/+SOzuIJGn9qyjNmvwUXUjOY/7icwkYfkcWT9I7kwVGFK/qOuDRIqYDfVYvVQ6F9VTtVX45tRNdUt9ox6prwq8nX2cBWikfxL4EBGHxaFXgcvJaQJXgPHfYEQcALv0JvtHaAT4WKYeEY8i4gawDYwBn4BupcI94Fop1ACs6cMisAKsZtCFCiWAs1LoAJioiEwBz4EXEfEQ+F4jdB04LIMWsnHNwjevPlMb+X9bnS/w0VzQhVJoODe2Uy2vn+X4u+rFKnA/l2zpHCLLyb3Xj7CWhHW1XYO3iivyZNBps+rnXNCd4tLuZB9P6jLp94wMAXeAm0CL3nT3gS3gdUR8/Xvx/2C/ABkkUg9p4mgDAAAAAElFTkSuQmCC";
|
|
13
|
+
function loadIconBase64() {
|
|
14
|
+
const iconPath = join(CONFIG_DIR, "assets", "vector-menubar.png");
|
|
15
|
+
if (existsSync(iconPath)) {
|
|
16
|
+
return readFileSync(iconPath).toString("base64");
|
|
17
|
+
}
|
|
18
|
+
return EMBEDDED_ICON;
|
|
19
|
+
}
|
|
20
|
+
function loadConfig() {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(readFileSync(BRIDGE_CONFIG_FILE, "utf-8"));
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function loadActivities() {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(readFileSync(LIVE_ACTIVITIES_FILE, "utf-8"));
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isBridgeRunning() {
|
|
35
|
+
try {
|
|
36
|
+
const pid = Number(readFileSync(PID_FILE, "utf-8").trim());
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
return { running: true, pid };
|
|
39
|
+
} catch {
|
|
40
|
+
return { running: false };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function getSessionInfo() {
|
|
44
|
+
try {
|
|
45
|
+
const session = JSON.parse(
|
|
46
|
+
readFileSync(join(CONFIG_DIR, "cli-default.json"), "utf-8")
|
|
47
|
+
);
|
|
48
|
+
let email;
|
|
49
|
+
const jwt = session.cookies?.["__Secure-better-auth.convex_jwt"];
|
|
50
|
+
if (jwt) {
|
|
51
|
+
try {
|
|
52
|
+
const payload = JSON.parse(
|
|
53
|
+
Buffer.from(jwt.split(".")[1], "base64").toString()
|
|
54
|
+
);
|
|
55
|
+
email = payload.email;
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
orgSlug: session.activeOrgSlug ?? "oss-lab",
|
|
61
|
+
appUrl: session.appUrl,
|
|
62
|
+
email
|
|
63
|
+
};
|
|
64
|
+
} catch {
|
|
65
|
+
return { orgSlug: "oss-lab" };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function getOrgSlug() {
|
|
69
|
+
try {
|
|
70
|
+
const session = JSON.parse(
|
|
71
|
+
readFileSync(join(CONFIG_DIR, "cli-default.json"), "utf-8")
|
|
72
|
+
);
|
|
73
|
+
return session.activeOrgSlug ?? "oss-lab";
|
|
74
|
+
} catch {
|
|
75
|
+
return "oss-lab";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function providerLabel(provider) {
|
|
79
|
+
if (provider === "claude_code") return "Claude";
|
|
80
|
+
if (provider === "codex") return "Codex";
|
|
81
|
+
return provider;
|
|
82
|
+
}
|
|
83
|
+
function buildMenu() {
|
|
84
|
+
const config = loadConfig();
|
|
85
|
+
const { running, pid } = isBridgeRunning();
|
|
86
|
+
const activities = loadActivities();
|
|
87
|
+
const items = [];
|
|
88
|
+
const actions = /* @__PURE__ */ new Map();
|
|
89
|
+
let idx = 0;
|
|
90
|
+
if (running && config) {
|
|
91
|
+
items.push({
|
|
92
|
+
title: `Vector Bridge \u2014 Running (PID ${pid})`,
|
|
93
|
+
enabled: false
|
|
94
|
+
});
|
|
95
|
+
idx++;
|
|
96
|
+
items.push({ title: ` ${config.displayName}`, enabled: false });
|
|
97
|
+
idx++;
|
|
98
|
+
} else if (config) {
|
|
99
|
+
items.push({ title: "Vector Bridge \u2014 Offline", enabled: false });
|
|
100
|
+
idx++;
|
|
101
|
+
} else {
|
|
102
|
+
items.push({ title: "Vector Bridge \u2014 Not Configured", enabled: false });
|
|
103
|
+
idx++;
|
|
104
|
+
items.push({
|
|
105
|
+
title: " Run: vcli service start",
|
|
106
|
+
enabled: false
|
|
107
|
+
});
|
|
108
|
+
idx++;
|
|
109
|
+
}
|
|
110
|
+
items.push({ title: "<SEPARATOR>", enabled: false });
|
|
111
|
+
idx++;
|
|
112
|
+
if (activities.length > 0) {
|
|
113
|
+
items.push({ title: "Active Sessions", enabled: false });
|
|
114
|
+
idx++;
|
|
115
|
+
const orgSlug = getOrgSlug();
|
|
116
|
+
for (const a of activities) {
|
|
117
|
+
const label = `${a.issueKey} \u2014 ${a.title ?? a.issueTitle} (${providerLabel(a.provider)})`;
|
|
118
|
+
items.push({ title: label, tooltip: a.latestSummary });
|
|
119
|
+
const issueKey = a.issueKey;
|
|
120
|
+
actions.set(idx, () => {
|
|
121
|
+
const url = `http://localhost:3000/${orgSlug}/issues/${issueKey}`;
|
|
122
|
+
try {
|
|
123
|
+
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
idx++;
|
|
128
|
+
}
|
|
129
|
+
items.push({ title: "<SEPARATOR>", enabled: false });
|
|
130
|
+
idx++;
|
|
131
|
+
}
|
|
132
|
+
if (running) {
|
|
133
|
+
items.push({ title: "Stop Bridge" });
|
|
134
|
+
actions.set(idx, () => {
|
|
135
|
+
try {
|
|
136
|
+
if (pid) process.kill(pid, "SIGTERM");
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
idx++;
|
|
141
|
+
items.push({ title: "Restart Bridge" });
|
|
142
|
+
actions.set(idx, () => {
|
|
143
|
+
try {
|
|
144
|
+
if (pid) process.kill(pid, "SIGTERM");
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
execSync("vcli service start", { stdio: "ignore" });
|
|
147
|
+
}, 2e3);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
idx++;
|
|
152
|
+
} else if (config) {
|
|
153
|
+
items.push({ title: "Start Bridge" });
|
|
154
|
+
actions.set(idx, () => {
|
|
155
|
+
try {
|
|
156
|
+
execSync("vcli service start", { stdio: "ignore" });
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
idx++;
|
|
161
|
+
}
|
|
162
|
+
items.push({ title: "<SEPARATOR>", enabled: false });
|
|
163
|
+
idx++;
|
|
164
|
+
if (config) {
|
|
165
|
+
const { orgSlug, email } = getSessionInfo();
|
|
166
|
+
const accountLabel = email ? `${email} | ${orgSlug}` : `${config.displayName} | ${orgSlug}`;
|
|
167
|
+
items.push({ title: accountLabel, enabled: false });
|
|
168
|
+
idx++;
|
|
169
|
+
items.push({ title: "<SEPARATOR>", enabled: false });
|
|
170
|
+
idx++;
|
|
171
|
+
}
|
|
172
|
+
items.push({ title: "Open Vector" });
|
|
173
|
+
actions.set(idx, () => {
|
|
174
|
+
const { appUrl } = getSessionInfo();
|
|
175
|
+
const url = appUrl ?? "http://localhost:3000";
|
|
176
|
+
try {
|
|
177
|
+
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
idx++;
|
|
182
|
+
items.push({ title: "Quit Vector" });
|
|
183
|
+
actions.set(idx, () => {
|
|
184
|
+
try {
|
|
185
|
+
const { pid: bridgePid } = isBridgeRunning();
|
|
186
|
+
if (bridgePid) process.kill(bridgePid, "SIGTERM");
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
process.exit(0);
|
|
190
|
+
});
|
|
191
|
+
idx++;
|
|
192
|
+
return { items, actions };
|
|
193
|
+
}
|
|
194
|
+
async function startMenuBar() {
|
|
195
|
+
try {
|
|
196
|
+
const { fileURLToPath } = await import("url");
|
|
197
|
+
const { dirname, join: pjoin } = await import("path");
|
|
198
|
+
const { chmodSync } = await import("fs");
|
|
199
|
+
const systrayIndex = fileURLToPath(import.meta.resolve("systray2"));
|
|
200
|
+
const binName = process.platform === "darwin" ? "tray_darwin_release" : process.platform === "win32" ? "tray_windows_release.exe" : "tray_linux_release";
|
|
201
|
+
const trayBin = pjoin(dirname(systrayIndex), "traybin", binName);
|
|
202
|
+
chmodSync(trayBin, 493);
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
const icon = loadIconBase64();
|
|
206
|
+
const { items, actions } = buildMenu();
|
|
207
|
+
const systray = new SysTray({
|
|
208
|
+
menu: {
|
|
209
|
+
icon,
|
|
210
|
+
title: "",
|
|
211
|
+
tooltip: "Vector Bridge",
|
|
212
|
+
items: items.map((item) => ({
|
|
213
|
+
title: item.title,
|
|
214
|
+
tooltip: item.tooltip ?? "",
|
|
215
|
+
checked: item.checked ?? false,
|
|
216
|
+
enabled: item.enabled ?? true,
|
|
217
|
+
hidden: false
|
|
218
|
+
}))
|
|
219
|
+
},
|
|
220
|
+
debug: false,
|
|
221
|
+
copyDir: false
|
|
222
|
+
});
|
|
223
|
+
void systray.onClick((action) => {
|
|
224
|
+
const handler = actions.get(action.seq_id);
|
|
225
|
+
if (handler) handler();
|
|
226
|
+
});
|
|
227
|
+
setInterval(() => {
|
|
228
|
+
const { items: newItems, actions: newActions } = buildMenu();
|
|
229
|
+
void newItems;
|
|
230
|
+
void newActions;
|
|
231
|
+
}, 15e3);
|
|
232
|
+
}
|
|
233
|
+
if (process.argv[1]?.endsWith("menubar.ts") || process.argv[1]?.endsWith("menubar.js")) {
|
|
234
|
+
startMenuBar().catch((e) => {
|
|
235
|
+
console.error("Menu bar error:", e);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
export {
|
|
240
|
+
startMenuBar
|
|
241
|
+
};
|
|
242
|
+
//# sourceMappingURL=menubar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/menubar.ts"],"sourcesContent":["/**\n * Vector menu bar tray icon — pure TypeScript using systray2.\n * No native compilation required.\n */\n\nimport SysTrayModule from 'systray2';\n\n// Handle CJS default export in ESM context\nconst SysTray =\n typeof (SysTrayModule as unknown as { default: typeof SysTrayModule })\n .default === 'function'\n ? (SysTrayModule as unknown as { default: typeof SysTrayModule }).default\n : SysTrayModule;\nimport { existsSync, readFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { execSync } from 'child_process';\n\nconst CONFIG_DIR = join(homedir(), '.vector');\nconst BRIDGE_CONFIG_FILE = join(CONFIG_DIR, 'bridge.json');\nconst PID_FILE = join(CONFIG_DIR, 'bridge.pid');\nconst LIVE_ACTIVITIES_FILE = join(CONFIG_DIR, 'live-activities.json');\n\n// ── Icon ────────────────────────────────────────────────────────────────────\n\n// Vector mark icon (white on transparent, 18x18 PNG)\nconst EMBEDDED_ICON =\n 'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABmJLR0QA/wD/AP+gvaeTAAABfUlEQVQ4ja2UT0uUURTGf2eWQYaLWgTjjCFKHyOS6BPopk1pKrgWaSGFy8pNQdsgRgT7BG5qlwa5ldqILkWngqCN2a/FnOLy8k4j1IF38Z7nOc89/+6F/2RR51SHgWngFtAGfgL7wFugExHfBiqrc2pXPVPfqS/ze6/+SOzuIJGn9qyjNmvwUXUjOY/7icwkYfkcWT9I7kwVGFK/qOuDRIqYDfVYvVQ6F9VTtVX45tRNdUt9ox6prwq8nX2cBWikfxL4EBGHxaFXgcvJaQJXgPHfYEQcALv0JvtHaAT4WKYeEY8i4gawDYwBn4BupcI94Fop1ACs6cMisAKsZtCFCiWAs1LoAJioiEwBz4EXEfEQ+F4jdB04LIMWsnHNwjevPlMb+X9bnS/w0VzQhVJoODe2Uy2vn+X4u+rFKnA/l2zpHCLLyb3Xj7CWhHW1XYO3iivyZNBps+rnXNCd4tLuZB9P6jLp94wMAXeAm0CL3nT3gS3gdUR8/Xvx/2C/ABkkUg9p4mgDAAAAAElFTkSuQmCC';\n\nfunction loadIconBase64(): string {\n // Try to load from assets (user may have a custom icon)\n const iconPath = join(CONFIG_DIR, 'assets', 'vector-menubar.png');\n if (existsSync(iconPath)) {\n return readFileSync(iconPath).toString('base64');\n }\n return EMBEDDED_ICON;\n}\n\n// ── State ───────────────────────────────────────────────────────────────────\n\ninterface BridgeConfig {\n deviceId: string;\n displayName: string;\n userId: string;\n convexUrl: string;\n}\n\ninterface LiveActivity {\n _id: string;\n issueKey: string;\n issueTitle: string;\n provider: string;\n title?: string;\n status: string;\n latestSummary?: string;\n}\n\nfunction loadConfig(): BridgeConfig | null {\n try {\n return JSON.parse(readFileSync(BRIDGE_CONFIG_FILE, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nfunction loadActivities(): LiveActivity[] {\n try {\n return JSON.parse(readFileSync(LIVE_ACTIVITIES_FILE, 'utf-8'));\n } catch {\n return [];\n }\n}\n\nfunction isBridgeRunning(): { running: boolean; pid?: number } {\n try {\n const pid = Number(readFileSync(PID_FILE, 'utf-8').trim());\n process.kill(pid, 0);\n return { running: true, pid };\n } catch {\n return { running: false };\n }\n}\n\nfunction getSessionInfo(): {\n orgSlug: string;\n appUrl?: string;\n email?: string;\n} {\n try {\n const session = JSON.parse(\n readFileSync(join(CONFIG_DIR, 'cli-default.json'), 'utf-8'),\n );\n // Try to extract email from the JWT\n let email: string | undefined;\n const jwt = session.cookies?.['__Secure-better-auth.convex_jwt'];\n if (jwt) {\n try {\n const payload = JSON.parse(\n Buffer.from(jwt.split('.')[1], 'base64').toString(),\n );\n email = payload.email;\n } catch {\n /* ignore */\n }\n }\n return {\n orgSlug: session.activeOrgSlug ?? 'oss-lab',\n appUrl: session.appUrl,\n email,\n };\n } catch {\n return { orgSlug: 'oss-lab' };\n }\n}\n\nfunction getOrgSlug(): string {\n try {\n const session = JSON.parse(\n readFileSync(join(CONFIG_DIR, 'cli-default.json'), 'utf-8'),\n );\n return session.activeOrgSlug ?? 'oss-lab';\n } catch {\n return 'oss-lab';\n }\n}\n\nfunction providerLabel(provider: string): string {\n if (provider === 'claude_code') return 'Claude';\n if (provider === 'codex') return 'Codex';\n return provider;\n}\n\n// ── Menu Builder ────────────────────────────────────────────────────────────\n\ntype MenuItem = {\n title: string;\n tooltip?: string;\n enabled?: boolean;\n checked?: boolean;\n};\n\nfunction buildMenu(): {\n items: MenuItem[];\n actions: Map<number, () => void>;\n} {\n const config = loadConfig();\n const { running, pid } = isBridgeRunning();\n const activities = loadActivities();\n const items: MenuItem[] = [];\n const actions = new Map<number, () => void>();\n let idx = 0;\n\n // Header\n if (running && config) {\n items.push({\n title: `Vector Bridge — Running (PID ${pid})`,\n enabled: false,\n });\n idx++;\n items.push({ title: ` ${config.displayName}`, enabled: false });\n idx++;\n } else if (config) {\n items.push({ title: 'Vector Bridge — Offline', enabled: false });\n idx++;\n } else {\n items.push({ title: 'Vector Bridge — Not Configured', enabled: false });\n idx++;\n items.push({\n title: ' Run: vcli service start',\n enabled: false,\n });\n idx++;\n }\n\n // Separator\n items.push({ title: '<SEPARATOR>', enabled: false });\n idx++;\n\n // Live activities\n if (activities.length > 0) {\n items.push({ title: 'Active Sessions', enabled: false });\n idx++;\n\n const orgSlug = getOrgSlug();\n for (const a of activities) {\n const label = `${a.issueKey} — ${a.title ?? a.issueTitle} (${providerLabel(a.provider)})`;\n items.push({ title: label, tooltip: a.latestSummary });\n const issueKey = a.issueKey;\n actions.set(idx, () => {\n const url = `http://localhost:3000/${orgSlug}/issues/${issueKey}`;\n try {\n execSync(`open \"${url}\"`, { stdio: 'ignore' });\n } catch {\n /* ignore */\n }\n });\n idx++;\n }\n\n items.push({ title: '<SEPARATOR>', enabled: false });\n idx++;\n }\n\n // Controls\n if (running) {\n items.push({ title: 'Stop Bridge' });\n actions.set(idx, () => {\n try {\n if (pid) process.kill(pid, 'SIGTERM');\n } catch {\n /* ignore */\n }\n });\n idx++;\n\n items.push({ title: 'Restart Bridge' });\n actions.set(idx, () => {\n try {\n if (pid) process.kill(pid, 'SIGTERM');\n setTimeout(() => {\n execSync('vcli service start', { stdio: 'ignore' });\n }, 2000);\n } catch {\n /* ignore */\n }\n });\n idx++;\n } else if (config) {\n items.push({ title: 'Start Bridge' });\n actions.set(idx, () => {\n try {\n execSync('vcli service start', { stdio: 'ignore' });\n } catch {\n /* ignore */\n }\n });\n idx++;\n }\n\n items.push({ title: '<SEPARATOR>', enabled: false });\n idx++;\n\n // Account info\n if (config) {\n const { orgSlug, email } = getSessionInfo();\n const accountLabel = email\n ? `${email} | ${orgSlug}`\n : `${config.displayName} | ${orgSlug}`;\n items.push({ title: accountLabel, enabled: false });\n idx++;\n items.push({ title: '<SEPARATOR>', enabled: false });\n idx++;\n }\n\n items.push({ title: 'Open Vector' });\n actions.set(idx, () => {\n const { appUrl } = getSessionInfo();\n const url = appUrl ?? 'http://localhost:3000';\n try {\n execSync(`open \"${url}\"`, { stdio: 'ignore' });\n } catch {\n /* ignore */\n }\n });\n idx++;\n\n items.push({ title: 'Quit Vector' });\n actions.set(idx, () => {\n // Stop the bridge before quitting\n try {\n const { pid: bridgePid } = isBridgeRunning();\n if (bridgePid) process.kill(bridgePid, 'SIGTERM');\n } catch {\n /* ignore */\n }\n process.exit(0);\n });\n idx++;\n\n return { items, actions };\n}\n\n// ── Main ────────────────────────────────────────────────────────────────────\n\nexport async function startMenuBar(): Promise<void> {\n // Ensure the systray binary is executable (npm doesn't preserve +x)\n try {\n const { fileURLToPath } = await import('url');\n const { dirname, join: pjoin } = await import('path');\n const { chmodSync } = await import('fs');\n const systrayIndex = fileURLToPath(import.meta.resolve('systray2'));\n const binName =\n process.platform === 'darwin'\n ? 'tray_darwin_release'\n : process.platform === 'win32'\n ? 'tray_windows_release.exe'\n : 'tray_linux_release';\n const trayBin = pjoin(dirname(systrayIndex), 'traybin', binName);\n chmodSync(trayBin, 0o755);\n } catch {\n // Best effort\n }\n\n const icon = loadIconBase64();\n const { items, actions } = buildMenu();\n\n const systray = new SysTray({\n menu: {\n icon,\n title: '',\n tooltip: 'Vector Bridge',\n items: items.map(item => ({\n title: item.title,\n tooltip: item.tooltip ?? '',\n checked: item.checked ?? false,\n enabled: item.enabled ?? true,\n hidden: false,\n })),\n },\n debug: false,\n copyDir: false,\n });\n\n void systray.onClick(action => {\n const handler = actions.get(action.seq_id);\n if (handler) handler();\n });\n\n // Refresh the menu every 15 seconds\n setInterval(() => {\n const { items: newItems, actions: newActions } = buildMenu();\n // systray2 doesn't support full menu rebuild, but we can update items\n // For now, the initial menu is static. User can quit + restart for refresh.\n // TODO: Use systray.sendAction to update individual items\n void newItems;\n void newActions;\n }, 15_000);\n}\n\n// Run standalone if called directly\nif (\n process.argv[1]?.endsWith('menubar.ts') ||\n process.argv[1]?.endsWith('menubar.js')\n) {\n startMenuBar().catch(e => {\n console.error('Menu bar error:', e);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rehpic/vcli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.52.1",
|
|
4
4
|
"description": "Command line interface for Vector workspaces.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -28,10 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
|
+
"scripts",
|
|
31
32
|
"README.md"
|
|
32
33
|
],
|
|
33
34
|
"scripts": {
|
|
34
|
-
"build": "tsup --config tsup.config.ts"
|
|
35
|
+
"build": "tsup --config tsup.config.ts",
|
|
36
|
+
"postinstall": "node scripts/postinstall.js"
|
|
35
37
|
},
|
|
36
38
|
"dependencies": {
|
|
37
39
|
"@clack/prompts": "^1.1.0",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall script for @rehpic/vcli
|
|
5
|
+
*
|
|
6
|
+
* The systray2 package ships pre-built tray binaries compiled with an old Go version.
|
|
7
|
+
* On macOS 15+ ARM64, the binary crashes. This script detects that case and recompiles
|
|
8
|
+
* the binary from source using Go (if available).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
|
+
import { tmpdir, platform, arch } from 'os';
|
|
15
|
+
import { createRequire } from 'module';
|
|
16
|
+
|
|
17
|
+
if (platform() !== 'darwin' || arch() !== 'arm64') process.exit(0);
|
|
18
|
+
|
|
19
|
+
// Find the systray2 tray binary
|
|
20
|
+
let trayBinDir;
|
|
21
|
+
try {
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const systrayPath = require.resolve('systray2');
|
|
24
|
+
trayBinDir = join(dirname(systrayPath), 'traybin');
|
|
25
|
+
} catch {
|
|
26
|
+
// systray2 not installed yet (shouldn't happen in postinstall)
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const trayBin = join(trayBinDir, 'tray_darwin_release');
|
|
31
|
+
if (!existsSync(trayBin)) process.exit(0);
|
|
32
|
+
|
|
33
|
+
// Check if the binary is already ARM64
|
|
34
|
+
try {
|
|
35
|
+
const fileInfo = execSync(`file "${trayBin}"`, { encoding: 'utf-8' });
|
|
36
|
+
if (fileInfo.includes('arm64')) {
|
|
37
|
+
// Already ARM64, no need to recompile
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Can't check, skip
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Need to recompile — check if Go is available
|
|
46
|
+
try {
|
|
47
|
+
execSync('which go', { stdio: 'pipe' });
|
|
48
|
+
} catch {
|
|
49
|
+
console.log(
|
|
50
|
+
'vcli: systray2 binary is x86_64 — install Go to enable menu bar on ARM64 Mac',
|
|
51
|
+
);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('vcli: Recompiling tray binary for ARM64...');
|
|
56
|
+
|
|
57
|
+
const tmpDir = join(tmpdir(), 'vcli-systray-build-' + Date.now());
|
|
58
|
+
try {
|
|
59
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
60
|
+
|
|
61
|
+
// Write a minimal Go module and main file that wraps the systray-portable source
|
|
62
|
+
writeFileSync(
|
|
63
|
+
join(tmpDir, 'go.mod'),
|
|
64
|
+
`module vcli-tray-build
|
|
65
|
+
|
|
66
|
+
go 1.21
|
|
67
|
+
|
|
68
|
+
require github.com/getlantern/systray v1.2.2
|
|
69
|
+
`,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Download systray-portable source
|
|
73
|
+
execSync(
|
|
74
|
+
`cd "${tmpDir}" && git clone --depth 1 https://github.com/felixhao28/systray-portable.git src 2>/dev/null`,
|
|
75
|
+
{ stdio: 'pipe', timeout: 30000 },
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Update go.mod in the cloned source
|
|
79
|
+
writeFileSync(
|
|
80
|
+
join(tmpDir, 'src', 'go.mod'),
|
|
81
|
+
`module github.com/felixhao28/systray-portable
|
|
82
|
+
|
|
83
|
+
go 1.21
|
|
84
|
+
|
|
85
|
+
require github.com/getlantern/systray v1.2.2
|
|
86
|
+
`,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Build
|
|
90
|
+
execSync(
|
|
91
|
+
`cd "${tmpDir}/src" && CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o "${trayBin}" tray.go`,
|
|
92
|
+
{ stdio: 'pipe', timeout: 120000 },
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
execSync(`chmod +x "${trayBin}"`, { stdio: 'pipe' });
|
|
96
|
+
console.log('vcli: Tray binary recompiled successfully for ARM64.');
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.log('vcli: Could not recompile tray binary:', e.message);
|
|
99
|
+
} finally {
|
|
100
|
+
// Clean up
|
|
101
|
+
try {
|
|
102
|
+
execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' });
|
|
103
|
+
} catch {
|
|
104
|
+
/* ignore */
|
|
105
|
+
}
|
|
106
|
+
}
|