@vellumai/cli 0.4.4 → 0.4.6
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/package.json +1 -1
- package/src/adapters/install.sh +27 -5
- package/src/commands/hatch.ts +17 -2
- package/src/components/DefaultMainScreen.tsx +2 -2
- package/src/lib/local.ts +65 -21
package/package.json
CHANGED
package/src/adapters/install.sh
CHANGED
|
@@ -24,11 +24,33 @@ ensure_git() {
|
|
|
24
24
|
|
|
25
25
|
info "Installing git..."
|
|
26
26
|
if [ "$(uname -s)" = "Darwin" ]; then
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
# On macOS, the standard way to get git is via Xcode Command Line Tools.
|
|
28
|
+
# Try installing CLT first before falling back to Homebrew.
|
|
29
|
+
if ! xcode-select -p >/dev/null 2>&1; then
|
|
30
|
+
info "Installing Xcode Command Line Tools (includes git)..."
|
|
31
|
+
xcode-select --install 2>/dev/null || true
|
|
32
|
+
info "Please follow the on-screen dialog to install. Waiting..."
|
|
33
|
+
local waited=0
|
|
34
|
+
while ! xcode-select -p >/dev/null 2>&1; do
|
|
35
|
+
sleep 5
|
|
36
|
+
waited=$((waited + 5))
|
|
37
|
+
if [ "$waited" -ge 600 ]; then
|
|
38
|
+
error "Timed out waiting for Xcode Command Line Tools. Please install manually and re-run."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
hash -r 2>/dev/null || true
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# If git still doesn't work after CLT, try Homebrew as a fallback.
|
|
46
|
+
if ! git --version >/dev/null 2>&1; then
|
|
47
|
+
hash -r 2>/dev/null || true
|
|
48
|
+
if command -v brew >/dev/null 2>&1; then
|
|
49
|
+
brew install git
|
|
50
|
+
else
|
|
51
|
+
error "git is still not available. Please install manually: xcode-select --install"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
32
54
|
fi
|
|
33
55
|
elif command -v apt-get >/dev/null 2>&1; then
|
|
34
56
|
sudo apt-get update -qq && sudo apt-get install -y -qq git
|
package/src/commands/hatch.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "crypto";
|
|
2
|
-
import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, symlinkSync, unlinkSync } from "fs";
|
|
2
|
+
import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
|
|
3
3
|
import { homedir, hostname, userInfo } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
|
|
6
|
+
import QRCode from "qrcode";
|
|
6
7
|
import qrcode from "qrcode-terminal";
|
|
7
8
|
|
|
8
9
|
// Direct import — bun embeds this at compile time so it works in compiled binaries.
|
|
@@ -508,7 +509,7 @@ async function displayPairingQRCode(runtimeUrl: string, bearerToken: string | un
|
|
|
508
509
|
const pairingRequestId = randomUUID();
|
|
509
510
|
const pairingSecret = randomBytes(32).toString("hex");
|
|
510
511
|
|
|
511
|
-
const registerRes = await fetch(`${runtimeUrl}/
|
|
512
|
+
const registerRes = await fetch(`${runtimeUrl}/pairing/register`, {
|
|
512
513
|
method: "POST",
|
|
513
514
|
headers: {
|
|
514
515
|
"Content-Type": "application/json",
|
|
@@ -539,6 +540,20 @@ async function displayPairingQRCode(runtimeUrl: string, bearerToken: string | un
|
|
|
539
540
|
});
|
|
540
541
|
});
|
|
541
542
|
|
|
543
|
+
// Save QR code as PNG to a well-known location so it can be retrieved
|
|
544
|
+
// (e.g. via SCP) for pairing through the Desktop app.
|
|
545
|
+
const qrDir = join(homedir(), ".vellum", "pairing-qr");
|
|
546
|
+
mkdirSync(qrDir, { recursive: true });
|
|
547
|
+
const qrPngPath = join(qrDir, "initial.png");
|
|
548
|
+
try {
|
|
549
|
+
const pngBuffer = await QRCode.toBuffer(payload, { type: "png", width: 512 });
|
|
550
|
+
writeFileSync(qrPngPath, pngBuffer);
|
|
551
|
+
console.log(`QR code PNG saved to ${qrPngPath}\n`);
|
|
552
|
+
} catch (pngErr) {
|
|
553
|
+
const pngReason = pngErr instanceof Error ? pngErr.message : String(pngErr);
|
|
554
|
+
console.warn(`\u26A0 Could not save QR code PNG: ${pngReason}\n`);
|
|
555
|
+
}
|
|
556
|
+
|
|
542
557
|
console.log("Scan this QR code with the Vellum iOS app to pair:\n");
|
|
543
558
|
console.log(qrString);
|
|
544
559
|
console.log("This pairing request expires in 5 minutes.");
|
|
@@ -1347,8 +1347,8 @@ function ChatApp({
|
|
|
1347
1347
|
const pairingSecret = randomBytes(32).toString("hex");
|
|
1348
1348
|
const gatewayUrl = runtimeUrl;
|
|
1349
1349
|
|
|
1350
|
-
// Call /
|
|
1351
|
-
const registerUrl = `${runtimeUrl}/
|
|
1350
|
+
// Call /pairing/register on the gateway (dedicated pairing proxy route)
|
|
1351
|
+
const registerUrl = `${runtimeUrl}/pairing/register`;
|
|
1352
1352
|
const registerRes = await fetch(registerUrl, {
|
|
1353
1353
|
method: "POST",
|
|
1354
1354
|
headers: {
|
package/src/lib/local.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync, spawn } from "child_process";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
import { createConnection } from "net";
|
|
5
5
|
import { homedir } from "os";
|
|
@@ -11,6 +11,33 @@ import { stopProcessByPidFile } from "./process.js";
|
|
|
11
11
|
|
|
12
12
|
const _require = createRequire(import.meta.url);
|
|
13
13
|
|
|
14
|
+
/** Returns the XDG-compatible log directory for Vellum hatch logs. */
|
|
15
|
+
function getHatchLogDir(): string {
|
|
16
|
+
const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
17
|
+
return join(configHome, "vellum", "logs");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Open (or create) a log file in append mode, returning the file descriptor.
|
|
21
|
+
* Creates the parent directory if it doesn't exist. Returns "ignore" if the
|
|
22
|
+
* directory or file cannot be created (permissions, read-only filesystem, etc.)
|
|
23
|
+
* so that spawn falls back to discarding output instead of aborting startup. */
|
|
24
|
+
function openHatchLogFile(name: string): number | "ignore" {
|
|
25
|
+
try {
|
|
26
|
+
const dir = getHatchLogDir();
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
return openSync(join(dir, name), "a");
|
|
29
|
+
} catch {
|
|
30
|
+
return "ignore";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Close a file descriptor returned by openHatchLogFile (no-op for "ignore"). */
|
|
35
|
+
function closeHatchLogFile(fd: number | "ignore"): void {
|
|
36
|
+
if (typeof fd === "number") {
|
|
37
|
+
try { closeSync(fd); } catch { /* best-effort */ }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
function isAssistantSourceDir(dir: string): boolean {
|
|
15
42
|
const pkgPath = join(dir, "package.json");
|
|
16
43
|
if (!existsSync(pkgPath) || !existsSync(join(dir, "src", "index.ts"))) return false;
|
|
@@ -376,18 +403,25 @@ export async function startLocalDaemon(): Promise<void> {
|
|
|
376
403
|
}
|
|
377
404
|
}
|
|
378
405
|
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
406
|
+
const daemonLogFd = openHatchLogFile("daemon.log");
|
|
407
|
+
let daemonPid: number | undefined;
|
|
408
|
+
try {
|
|
409
|
+
const child = spawn(daemonBinary, [], {
|
|
410
|
+
cwd: dirname(daemonBinary),
|
|
411
|
+
detached: true,
|
|
412
|
+
stdio: ["ignore", daemonLogFd, daemonLogFd],
|
|
413
|
+
env: daemonEnv,
|
|
414
|
+
});
|
|
415
|
+
child.unref();
|
|
416
|
+
daemonPid = child.pid;
|
|
417
|
+
} finally {
|
|
418
|
+
closeHatchLogFile(daemonLogFd);
|
|
419
|
+
}
|
|
386
420
|
|
|
387
421
|
// Write PID file immediately so the health monitor can find the process
|
|
388
422
|
// and concurrent hatch() calls see it as alive.
|
|
389
|
-
if (
|
|
390
|
-
writeFileSync(pidFile, String(
|
|
423
|
+
if (daemonPid) {
|
|
424
|
+
writeFileSync(pidFile, String(daemonPid), "utf-8");
|
|
391
425
|
}
|
|
392
426
|
}
|
|
393
427
|
|
|
@@ -534,20 +568,30 @@ export async function startGateway(assistantId?: string): Promise<string> {
|
|
|
534
568
|
);
|
|
535
569
|
}
|
|
536
570
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
571
|
+
const gatewayLogFd = openHatchLogFile("gateway.log");
|
|
572
|
+
try {
|
|
573
|
+
gateway = spawn(gatewayBinary, [], {
|
|
574
|
+
detached: true,
|
|
575
|
+
stdio: ["ignore", gatewayLogFd, gatewayLogFd],
|
|
576
|
+
env: gatewayEnv,
|
|
577
|
+
});
|
|
578
|
+
} finally {
|
|
579
|
+
closeHatchLogFile(gatewayLogFd);
|
|
580
|
+
}
|
|
542
581
|
} else {
|
|
543
582
|
// Source tree / bunx: resolve the gateway source directory and run via bun.
|
|
544
583
|
const gatewayDir = resolveGatewayDir();
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
584
|
+
const gwLogFd = openHatchLogFile("gateway.log");
|
|
585
|
+
try {
|
|
586
|
+
gateway = spawn("bun", ["run", "src/index.ts"], {
|
|
587
|
+
cwd: gatewayDir,
|
|
588
|
+
detached: true,
|
|
589
|
+
stdio: ["ignore", gwLogFd, gwLogFd],
|
|
590
|
+
env: gatewayEnv,
|
|
591
|
+
});
|
|
592
|
+
} finally {
|
|
593
|
+
closeHatchLogFile(gwLogFd);
|
|
594
|
+
}
|
|
551
595
|
}
|
|
552
596
|
|
|
553
597
|
gateway.unref();
|