vexp-cli 2.0.11 → 2.0.13
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/agent-config.js +154 -75
- package/dist/autostart.js +375 -0
- package/dist/binary.js +63 -0
- package/dist/cli.js +258 -105
- package/dist/mcp-supervisor.js +235 -0
- package/dist/serve.js +160 -0
- package/dist/trace.js +80 -0
- package/mcp/mcp-server.cjs +38 -38
- package/package.json +6 -6
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
5
|
+
const LABEL = "io.vexp.serve";
|
|
6
|
+
const BEGIN_MARKER = "# vexp-autostart-begin";
|
|
7
|
+
const END_MARKER = "# vexp-autostart-end";
|
|
8
|
+
const INSTALL_MARKER_FILE = "autostart-installed";
|
|
9
|
+
function homedir() {
|
|
10
|
+
return os.homedir();
|
|
11
|
+
}
|
|
12
|
+
function installMarkerPath() {
|
|
13
|
+
return path.join(homedir(), ".vexp", INSTALL_MARKER_FILE);
|
|
14
|
+
}
|
|
15
|
+
/** Resolve the launcher the OS should execute. Prefers the `vexp` bin in
|
|
16
|
+
* PATH; falls back to node+dist/cli.cjs if invoked from a dev checkout. */
|
|
17
|
+
function resolveLauncher() {
|
|
18
|
+
if (process.env.VEXP_AUTOSTART_LAUNCHER) {
|
|
19
|
+
const parts = process.env.VEXP_AUTOSTART_LAUNCHER.split(" ");
|
|
20
|
+
return { bin: parts[0], args: [...parts.slice(1), "serve"] };
|
|
21
|
+
}
|
|
22
|
+
// `vexp` launches the commander CLI with argv-dispatch; argv[2] = "serve".
|
|
23
|
+
const hint = process.execPath && process.argv[1]
|
|
24
|
+
? { bin: process.execPath, args: [process.argv[1], "serve"] }
|
|
25
|
+
: { bin: "vexp", args: ["serve"] };
|
|
26
|
+
// On Windows, prefer the global shim when available so the user's PATH
|
|
27
|
+
// can be used without baking in an absolute node path.
|
|
28
|
+
if (process.platform === "win32")
|
|
29
|
+
return { bin: "vexp.cmd", args: ["serve"] };
|
|
30
|
+
// Everywhere else default to node + resolved script path for reliability.
|
|
31
|
+
return hint;
|
|
32
|
+
}
|
|
33
|
+
// ───── macOS LaunchAgent ────────────────────────────────────────────────
|
|
34
|
+
function macPlistPath() {
|
|
35
|
+
return path.join(homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
36
|
+
}
|
|
37
|
+
function macPlist(launcher, logPath) {
|
|
38
|
+
const args = [launcher.bin, ...launcher.args]
|
|
39
|
+
.map((a) => ` <string>${a.replace(/&/g, "&").replace(/</g, "<")}</string>`)
|
|
40
|
+
.join("\n");
|
|
41
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
42
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
43
|
+
<plist version="1.0">
|
|
44
|
+
<dict>
|
|
45
|
+
<key>Label</key><string>${LABEL}</string>
|
|
46
|
+
<key>ProgramArguments</key>
|
|
47
|
+
<array>
|
|
48
|
+
${args}
|
|
49
|
+
</array>
|
|
50
|
+
<key>RunAtLoad</key><true/>
|
|
51
|
+
<key>KeepAlive</key><true/>
|
|
52
|
+
<key>StandardOutPath</key><string>${logPath}</string>
|
|
53
|
+
<key>StandardErrorPath</key><string>${logPath}</string>
|
|
54
|
+
<key>EnvironmentVariables</key>
|
|
55
|
+
<dict>
|
|
56
|
+
<key>PATH</key><string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
|
|
57
|
+
</dict>
|
|
58
|
+
</dict>
|
|
59
|
+
</plist>
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
function installLaunchd() {
|
|
63
|
+
const launcher = resolveLauncher();
|
|
64
|
+
const logPath = path.join(homedir(), ".vexp", "autostart.log");
|
|
65
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
66
|
+
const plistPath = macPlistPath();
|
|
67
|
+
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
|
68
|
+
fs.writeFileSync(plistPath, macPlist(launcher, logPath));
|
|
69
|
+
// Reload if already loaded.
|
|
70
|
+
try {
|
|
71
|
+
execFileSync("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LABEL}`], { stdio: "ignore" });
|
|
72
|
+
}
|
|
73
|
+
catch { /* not loaded */ }
|
|
74
|
+
try {
|
|
75
|
+
execFileSync("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plistPath], { stdio: "ignore" });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Fallback for older macOS
|
|
79
|
+
try {
|
|
80
|
+
execFileSync("launchctl", ["load", "-w", plistPath], { stdio: "ignore" });
|
|
81
|
+
}
|
|
82
|
+
catch { /* best effort */ }
|
|
83
|
+
}
|
|
84
|
+
return { installed: true, mechanism: "launchd", path: plistPath };
|
|
85
|
+
}
|
|
86
|
+
function uninstallLaunchd() {
|
|
87
|
+
const plistPath = macPlistPath();
|
|
88
|
+
try {
|
|
89
|
+
execFileSync("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LABEL}`], { stdio: "ignore" });
|
|
90
|
+
}
|
|
91
|
+
catch { /* not loaded */ }
|
|
92
|
+
try {
|
|
93
|
+
execFileSync("launchctl", ["unload", plistPath], { stdio: "ignore" });
|
|
94
|
+
}
|
|
95
|
+
catch { /* ok */ }
|
|
96
|
+
if (fs.existsSync(plistPath))
|
|
97
|
+
fs.unlinkSync(plistPath);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
// ───── Linux: systemd --user > XDG autostart > shell profile ─────────────
|
|
101
|
+
function systemdUnitPath() {
|
|
102
|
+
return path.join(homedir(), ".config", "systemd", "user", "vexp.service");
|
|
103
|
+
}
|
|
104
|
+
function hasSystemdUser() {
|
|
105
|
+
const r = spawnSync("systemctl", ["--user", "is-system-running"], { encoding: "utf-8" });
|
|
106
|
+
// `is-system-running` returns various states; exit 0, 1, or 3 all mean the
|
|
107
|
+
// user bus is reachable. "offline" / "Failed to connect" → fallback.
|
|
108
|
+
if (r.error)
|
|
109
|
+
return false;
|
|
110
|
+
if ((r.stdout || "").includes("Failed to connect"))
|
|
111
|
+
return false;
|
|
112
|
+
if ((r.stderr || "").includes("Failed to connect"))
|
|
113
|
+
return false;
|
|
114
|
+
return r.status !== null && (r.status === 0 || r.status === 1 || r.status === 3);
|
|
115
|
+
}
|
|
116
|
+
function installSystemdUser() {
|
|
117
|
+
const launcher = resolveLauncher();
|
|
118
|
+
const unit = systemdUnitPath();
|
|
119
|
+
fs.mkdirSync(path.dirname(unit), { recursive: true });
|
|
120
|
+
const cmd = [launcher.bin, ...launcher.args].map((a) => /\s/.test(a) ? `"${a}"` : a).join(" ");
|
|
121
|
+
fs.writeFileSync(unit, `[Unit]
|
|
122
|
+
Description=vexp login supervisor
|
|
123
|
+
After=network.target
|
|
124
|
+
|
|
125
|
+
[Service]
|
|
126
|
+
Type=simple
|
|
127
|
+
ExecStart=${cmd}
|
|
128
|
+
Restart=on-failure
|
|
129
|
+
RestartSec=5
|
|
130
|
+
StandardOutput=append:${path.join(homedir(), ".vexp", "autostart.log")}
|
|
131
|
+
StandardError=append:${path.join(homedir(), ".vexp", "autostart.log")}
|
|
132
|
+
|
|
133
|
+
[Install]
|
|
134
|
+
WantedBy=default.target
|
|
135
|
+
`);
|
|
136
|
+
try {
|
|
137
|
+
execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
138
|
+
}
|
|
139
|
+
catch { /* best effort */ }
|
|
140
|
+
try {
|
|
141
|
+
execFileSync("systemctl", ["--user", "enable", "--now", "vexp.service"], { stdio: "ignore" });
|
|
142
|
+
}
|
|
143
|
+
catch { /* best effort */ }
|
|
144
|
+
return { installed: true, mechanism: "systemd-user", path: unit };
|
|
145
|
+
}
|
|
146
|
+
function uninstallSystemdUser() {
|
|
147
|
+
try {
|
|
148
|
+
execFileSync("systemctl", ["--user", "disable", "--now", "vexp.service"], { stdio: "ignore" });
|
|
149
|
+
}
|
|
150
|
+
catch { /* ok */ }
|
|
151
|
+
const unit = systemdUnitPath();
|
|
152
|
+
if (fs.existsSync(unit))
|
|
153
|
+
fs.unlinkSync(unit);
|
|
154
|
+
try {
|
|
155
|
+
execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
156
|
+
}
|
|
157
|
+
catch { /* ok */ }
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function xdgAutostartPath() {
|
|
161
|
+
return path.join(homedir(), ".config", "autostart", "vexp.desktop");
|
|
162
|
+
}
|
|
163
|
+
function hasXdgDesktop() {
|
|
164
|
+
// If $XDG_CURRENT_DESKTOP or $DESKTOP_SESSION is set, a graphical session
|
|
165
|
+
// manager will honor ~/.config/autostart.
|
|
166
|
+
return !!(process.env.XDG_CURRENT_DESKTOP || process.env.DESKTOP_SESSION);
|
|
167
|
+
}
|
|
168
|
+
function installXdgAutostart() {
|
|
169
|
+
const launcher = resolveLauncher();
|
|
170
|
+
const file = xdgAutostartPath();
|
|
171
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
172
|
+
const cmd = [launcher.bin, ...launcher.args].map((a) => /\s/.test(a) ? `"${a}"` : a).join(" ");
|
|
173
|
+
fs.writeFileSync(file, `[Desktop Entry]
|
|
174
|
+
Type=Application
|
|
175
|
+
Name=vexp
|
|
176
|
+
Comment=vexp login supervisor
|
|
177
|
+
Exec=${cmd}
|
|
178
|
+
X-GNOME-Autostart-enabled=true
|
|
179
|
+
Terminal=false
|
|
180
|
+
Hidden=false
|
|
181
|
+
`);
|
|
182
|
+
return { installed: true, mechanism: "xdg-autostart", path: file };
|
|
183
|
+
}
|
|
184
|
+
function uninstallXdgAutostart() {
|
|
185
|
+
const file = xdgAutostartPath();
|
|
186
|
+
if (fs.existsSync(file))
|
|
187
|
+
fs.unlinkSync(file);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
function shellProfilePaths() {
|
|
191
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => path.join(homedir(), f));
|
|
192
|
+
}
|
|
193
|
+
function installShellProfile() {
|
|
194
|
+
const launcher = resolveLauncher();
|
|
195
|
+
const cmd = [launcher.bin, ...launcher.args].map((a) => /\s/.test(a) ? `"${a}"` : a).join(" ");
|
|
196
|
+
const block = `${BEGIN_MARKER}
|
|
197
|
+
pgrep -f "vexp serve" >/dev/null 2>&1 || (nohup ${cmd} >> "${path.join(homedir(), ".vexp", "autostart.log")}" 2>&1 &)
|
|
198
|
+
${END_MARKER}
|
|
199
|
+
`;
|
|
200
|
+
let wrote = "";
|
|
201
|
+
for (const p of shellProfilePaths()) {
|
|
202
|
+
let content = "";
|
|
203
|
+
try {
|
|
204
|
+
content = fs.readFileSync(p, "utf-8");
|
|
205
|
+
}
|
|
206
|
+
catch { /* new file */ }
|
|
207
|
+
if (content.includes(BEGIN_MARKER)) {
|
|
208
|
+
// Replace existing block
|
|
209
|
+
content = content.replace(new RegExp(`${BEGIN_MARKER}[\\s\\S]*?${END_MARKER}\\n?`), block);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
content = content + (content.endsWith("\n") || content === "" ? "" : "\n") + block;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
fs.writeFileSync(p, content);
|
|
216
|
+
wrote = p;
|
|
217
|
+
}
|
|
218
|
+
catch { /* permission denied, try next */ }
|
|
219
|
+
}
|
|
220
|
+
return { installed: !!wrote, mechanism: "shell-profile", path: wrote || undefined };
|
|
221
|
+
}
|
|
222
|
+
function uninstallShellProfile() {
|
|
223
|
+
let changed = false;
|
|
224
|
+
for (const p of shellProfilePaths()) {
|
|
225
|
+
let content = "";
|
|
226
|
+
try {
|
|
227
|
+
content = fs.readFileSync(p, "utf-8");
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!content.includes(BEGIN_MARKER))
|
|
233
|
+
continue;
|
|
234
|
+
const cleaned = content.replace(new RegExp(`${BEGIN_MARKER}[\\s\\S]*?${END_MARKER}\\n?`), "");
|
|
235
|
+
try {
|
|
236
|
+
fs.writeFileSync(p, cleaned);
|
|
237
|
+
changed = true;
|
|
238
|
+
}
|
|
239
|
+
catch { /* ok */ }
|
|
240
|
+
}
|
|
241
|
+
return changed;
|
|
242
|
+
}
|
|
243
|
+
// ───── Windows: Startup folder VBScript ──────────────────────────────────
|
|
244
|
+
function windowsStartupPath() {
|
|
245
|
+
const appData = process.env.APPDATA || path.join(homedir(), "AppData", "Roaming");
|
|
246
|
+
return path.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "vexp-serve.vbs");
|
|
247
|
+
}
|
|
248
|
+
function installWindowsStartup() {
|
|
249
|
+
const launcher = resolveLauncher();
|
|
250
|
+
const p = windowsStartupPath();
|
|
251
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
252
|
+
const logPath = path.join(homedir(), ".vexp", "autostart.log");
|
|
253
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
254
|
+
const cmdParts = [launcher.bin, ...launcher.args]
|
|
255
|
+
.map((a) => `""${a.replace(/"/g, '\\"')}""`)
|
|
256
|
+
.join(" ");
|
|
257
|
+
const script = `' vexp autostart — launches \`vexp serve\` hidden at login.\n` +
|
|
258
|
+
`Set sh = CreateObject("WScript.Shell")\n` +
|
|
259
|
+
`sh.Run "cmd /c " & ${JSON.stringify(cmdParts)} & " >> ""${logPath.replace(/\\/g, "\\\\")}"" 2>&1", 0, False\n`;
|
|
260
|
+
fs.writeFileSync(p, script);
|
|
261
|
+
return { installed: true, mechanism: "windows-startup", path: p };
|
|
262
|
+
}
|
|
263
|
+
function uninstallWindowsStartup() {
|
|
264
|
+
const p = windowsStartupPath();
|
|
265
|
+
if (fs.existsSync(p))
|
|
266
|
+
fs.unlinkSync(p);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
// ───── Public API ────────────────────────────────────────────────────────
|
|
270
|
+
/**
|
|
271
|
+
* Install per-user autostart so `vexp serve` runs at login without admin.
|
|
272
|
+
* Chooses the best available mechanism per OS; returns what was written.
|
|
273
|
+
*/
|
|
274
|
+
export function installAutostart() {
|
|
275
|
+
if (process.platform === "darwin")
|
|
276
|
+
return installLaunchd();
|
|
277
|
+
if (process.platform === "win32")
|
|
278
|
+
return installWindowsStartup();
|
|
279
|
+
if (process.platform === "linux") {
|
|
280
|
+
if (hasSystemdUser())
|
|
281
|
+
return installSystemdUser();
|
|
282
|
+
if (hasXdgDesktop())
|
|
283
|
+
return installXdgAutostart();
|
|
284
|
+
return installShellProfile();
|
|
285
|
+
}
|
|
286
|
+
return { installed: false, mechanism: "none" };
|
|
287
|
+
}
|
|
288
|
+
/** Remove every autostart variant, including legacy ones from prior versions. */
|
|
289
|
+
export function uninstallAutostart() {
|
|
290
|
+
if (process.platform === "darwin") {
|
|
291
|
+
uninstallLaunchd();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (process.platform === "win32") {
|
|
295
|
+
uninstallWindowsStartup();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (process.platform === "linux") {
|
|
299
|
+
uninstallSystemdUser();
|
|
300
|
+
uninstallXdgAutostart();
|
|
301
|
+
uninstallShellProfile();
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
fs.unlinkSync(installMarkerPath());
|
|
305
|
+
}
|
|
306
|
+
catch { /* absent */ }
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Idempotent install guard used by `vexp` preAction / REPL / plugin activate.
|
|
310
|
+
*
|
|
311
|
+
* First invocation after an upgrade installs autostart + writes a marker.
|
|
312
|
+
* Subsequent invocations (including `vexp setup`, any subcommand, multiple
|
|
313
|
+
* VS Code windows) short-circuit on the marker → zero extra work, zero
|
|
314
|
+
* process duplication.
|
|
315
|
+
*
|
|
316
|
+
* Respects `VEXP_NO_AUTOSTART_INSTALL=1` as opt-out.
|
|
317
|
+
*/
|
|
318
|
+
export function installAutostartIfNeeded() {
|
|
319
|
+
if (process.env.VEXP_NO_AUTOSTART_INSTALL === "1")
|
|
320
|
+
return;
|
|
321
|
+
const marker = installMarkerPath();
|
|
322
|
+
if (fs.existsSync(marker))
|
|
323
|
+
return;
|
|
324
|
+
// Previous install by a different path (e.g. user ran older `vexp setup`
|
|
325
|
+
// that called installAutostart directly). Record the marker so we don't
|
|
326
|
+
// probe the filesystem again on every invocation.
|
|
327
|
+
if (autostartStatus().installed) {
|
|
328
|
+
try {
|
|
329
|
+
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
330
|
+
fs.writeFileSync(marker, new Date().toISOString());
|
|
331
|
+
}
|
|
332
|
+
catch { /* non-fatal */ }
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const r = installAutostart();
|
|
337
|
+
if (r.installed) {
|
|
338
|
+
try {
|
|
339
|
+
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
340
|
+
fs.writeFileSync(marker, new Date().toISOString());
|
|
341
|
+
}
|
|
342
|
+
catch { /* non-fatal */ }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch { /* non-fatal — opt-in best effort */ }
|
|
346
|
+
}
|
|
347
|
+
export function autostartStatus() {
|
|
348
|
+
if (process.platform === "darwin") {
|
|
349
|
+
const p = macPlistPath();
|
|
350
|
+
if (fs.existsSync(p))
|
|
351
|
+
return { installed: true, mechanism: "launchd", path: p };
|
|
352
|
+
return { installed: false, mechanism: "none" };
|
|
353
|
+
}
|
|
354
|
+
if (process.platform === "win32") {
|
|
355
|
+
const p = windowsStartupPath();
|
|
356
|
+
if (fs.existsSync(p))
|
|
357
|
+
return { installed: true, mechanism: "windows-startup", path: p };
|
|
358
|
+
return { installed: false, mechanism: "none" };
|
|
359
|
+
}
|
|
360
|
+
if (process.platform === "linux") {
|
|
361
|
+
if (fs.existsSync(systemdUnitPath()))
|
|
362
|
+
return { installed: true, mechanism: "systemd-user", path: systemdUnitPath() };
|
|
363
|
+
if (fs.existsSync(xdgAutostartPath()))
|
|
364
|
+
return { installed: true, mechanism: "xdg-autostart", path: xdgAutostartPath() };
|
|
365
|
+
for (const p of shellProfilePaths()) {
|
|
366
|
+
try {
|
|
367
|
+
if (fs.readFileSync(p, "utf-8").includes(BEGIN_MARKER)) {
|
|
368
|
+
return { installed: true, mechanism: "shell-profile", path: p };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch { /* missing */ }
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return { installed: false, mechanism: "none" };
|
|
375
|
+
}
|
package/dist/binary.js
CHANGED
|
@@ -88,6 +88,69 @@ function ensureDevSonames(releaseDir) {
|
|
|
88
88
|
// Non-fatal: user will get a clearer error from the binary itself.
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
// Copy ggml backend plugins (cpu/metal/blas) and default.metallib from
|
|
92
|
+
// the build output into releaseDir, so `ggml_backend_load_all()` can
|
|
93
|
+
// dlopen them. CI packaging does this for production bundles; in dev
|
|
94
|
+
// the files live in `target/release/build/.../out/bin/` and need a
|
|
95
|
+
// flat-copy here. Metal additionally needs default.metallib as a
|
|
96
|
+
// runtime resource.
|
|
97
|
+
copyDevBackendPlugins(releaseDir, ext);
|
|
98
|
+
}
|
|
99
|
+
/** Flat-copy backend .dylib/.so and Metal shader resources next to the
|
|
100
|
+
* binary so the runtime dynamic loader finds them. No-op if the files
|
|
101
|
+
* are already present. */
|
|
102
|
+
function copyDevBackendPlugins(releaseDir, ext) {
|
|
103
|
+
const buildDir = path.join(releaseDir, "build");
|
|
104
|
+
if (!fs.existsSync(buildDir))
|
|
105
|
+
return;
|
|
106
|
+
const backendNames = ["libggml-cpu", "libggml-blas"];
|
|
107
|
+
if (process.platform === "darwin")
|
|
108
|
+
backendNames.push("libggml-metal");
|
|
109
|
+
const scanRoots = [];
|
|
110
|
+
try {
|
|
111
|
+
for (const entry of fs.readdirSync(buildDir)) {
|
|
112
|
+
if (!entry.startsWith("llama-cpp-sys-"))
|
|
113
|
+
continue;
|
|
114
|
+
for (const sub of ["bin", "lib", "lib64"]) {
|
|
115
|
+
const d = path.join(buildDir, entry, "out", sub);
|
|
116
|
+
if (fs.existsSync(d))
|
|
117
|
+
scanRoots.push(d);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const tryCopy = (src, dest) => {
|
|
125
|
+
if (fs.existsSync(dest))
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
fs.copyFileSync(src, dest);
|
|
129
|
+
}
|
|
130
|
+
catch { /* non-fatal */ }
|
|
131
|
+
};
|
|
132
|
+
for (const root of scanRoots) {
|
|
133
|
+
let entries = [];
|
|
134
|
+
try {
|
|
135
|
+
entries = fs.readdirSync(root);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
for (const f of entries) {
|
|
141
|
+
// Backend plugin: libggml-cpu.dylib / libggml-metal.dylib / etc.
|
|
142
|
+
for (const name of backendNames) {
|
|
143
|
+
if (f === `${name}${ext}`) {
|
|
144
|
+
tryCopy(path.join(root, f), path.join(releaseDir, f));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Metal shader library — exact filename, both platforms' build
|
|
148
|
+
// layouts may place it in bin/ or lib/.
|
|
149
|
+
if (process.platform === "darwin" && f === "default.metallib") {
|
|
150
|
+
tryCopy(path.join(root, f), path.join(releaseDir, "default.metallib"));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
91
154
|
}
|
|
92
155
|
function isSymlink(p) {
|
|
93
156
|
try {
|