From a7f8230e81f7d7194603c6bc3e62d708c078a923 Mon Sep 17 00:00:00 2001 From: David Vargas Date: Wed, 25 Feb 2026 20:53:10 -0500 Subject: [PATCH] Changes needed for Vellum Assistant on Mac Mini --- cli/src/commands/client.ts | 9 ++- cli/src/commands/hatch.ts | 81 +++++++++++-------- cli/src/lib/gcp.ts | 20 ++--- clients/macos/build.sh | 4 +- .../vellum-assistant/App/AssistantCli.swift | 5 +- 5 files changed, 69 insertions(+), 50 deletions(-) diff --git a/cli/src/commands/client.ts b/cli/src/commands/client.ts index 9c4133f1ecf..9157372bb6a 100644 --- a/cli/src/commands/client.ts +++ b/cli/src/commands/client.ts @@ -1,10 +1,15 @@ import { readFileSync } from "fs"; import { join } from "path"; -import { ANSI, renderChatApp } from "../components/DefaultMainScreen"; import { findAssistantByName, loadLatestAssistant } from "../lib/assistant-config"; import { GATEWAY_PORT, type Species } from "../lib/constants"; +const ANSI = { + reset: "\x1b[0m", + bold: "\x1b[1m", + dim: "\x1b[2m", +}; + const FALLBACK_RUNTIME_URL = `http://127.0.0.1:${GATEWAY_PORT}`; const FALLBACK_ASSISTANT_ID = "default"; @@ -108,6 +113,8 @@ ${ANSI.bold}EXAMPLES:${ANSI.reset} export async function client(): Promise { const { runtimeUrl, assistantId, species, bearerToken, project, zone } = parseArgs(); + const { renderChatApp } = await import("../components/DefaultMainScreen"); + process.stdout.write("\x1b[2J\x1b[H"); const app = renderChatApp( diff --git a/cli/src/commands/hatch.ts b/cli/src/commands/hatch.ts index 7caaa5f571e..06d927fb940 100644 --- a/cli/src/commands/hatch.ts +++ b/cli/src/commands/hatch.ts @@ -29,13 +29,14 @@ export type { PollResult, WatchHatchingResult } from "../lib/gcp"; const INSTALL_SCRIPT_REMOTE_PATH = "/tmp/vellum-install.sh"; -async function resolveInstallScriptPath(): Promise { - const sourcePath = join(import.meta.dir, "..", "adapters", "install.sh"); - if (existsSync(sourcePath)) { - return sourcePath; - } - console.warn("⚠️ Install script not found at", sourcePath, "(expected in compiled binary)"); - return null; +// Embedded install script — bun --compile doesn't bundle non-JS assets, +// so we inline it to ensure it's available in the compiled binary. +import INSTALL_SCRIPT_CONTENT from "../adapters/install.sh" with { type: "text" }; + +function resolveInstallScriptPath(): string { + const tmpPath = join(tmpdir(), `vellum-install-${process.pid}.sh`); + writeFileSync(tmpPath, INSTALL_SCRIPT_CONTENT, { mode: 0o755 }); + return tmpPath; } const HATCH_TIMEOUT_MS: Record = { vellum: 2 * 60 * 1000, @@ -51,8 +52,8 @@ function desktopLog(msg: string): void { process.stdout.write(msg + "\n"); } -function buildTimestampRedirect(): string { - return `exec > >(while IFS= read -r line; do printf '[%s] %s\\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$line"; done > /var/log/startup-script.log) 2>&1`; +function buildTimestampRedirect(logPath: string): string { + return `exec > >(while IFS= read -r line; do printf '[%s] %s\\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$line"; done > ${logPath}) 2>&1`; } function buildUserSetup(sshUser: string): string { @@ -82,7 +83,9 @@ export async function buildStartupScript( cloud: RemoteHost, ): Promise { const platformUrl = process.env.VELLUM_ASSISTANT_PLATFORM_URL ?? "https://assistant.vellum.ai"; - const timestampRedirect = buildTimestampRedirect(); + const logPath = cloud === "custom" ? "/tmp/vellum-startup.log" : "/var/log/startup-script.log"; + const errorPath = cloud === "custom" ? "/tmp/vellum-startup-error" : "/var/log/startup-error"; + const timestampRedirect = buildTimestampRedirect(logPath); const userSetup = buildUserSetup(sshUser); const ownershipFixup = buildOwnershipFixup(); @@ -102,7 +105,7 @@ set -e ${timestampRedirect} -trap 'EXIT_CODE=\$?; if [ \$EXIT_CODE -ne 0 ]; then echo "Startup script failed with exit code \$EXIT_CODE at line \$LINENO" > /var/log/startup-error; echo "Last 20 log lines:" >> /var/log/startup-error; tail -20 /var/log/startup-script.log >> /var/log/startup-error 2>/dev/null || true; fi' EXIT +trap 'EXIT_CODE=\$?; if [ \$EXIT_CODE -ne 0 ]; then echo "Startup script failed with exit code \$EXIT_CODE at line \$LINENO" > ${errorPath}; echo "Last 20 log lines:" >> ${errorPath}; tail -20 ${logPath} >> ${errorPath} 2>/dev/null || true; fi' EXIT ${userSetup} ANTHROPIC_API_KEY=${anthropicApiKey} GATEWAY_RUNTIME_PROXY_ENABLED=true @@ -401,13 +404,31 @@ function watchHatchingDesktop( } function buildSshArgs(host: string): string[] { - return [ - host, + const args: string[] = [host]; + const keyPath = process.env.VELLUM_SSH_KEY_PATH; + if (keyPath) { + args.push("-i", keyPath); + } + args.push( "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "ConnectTimeout=10", "-o", "LogLevel=ERROR", - ]; + ); + return args; +} + +function buildScpArgs(keyPath?: string): string[] { + const args: string[] = []; + if (keyPath) { + args.push("-i", keyPath); + } + args.push( + "-o", "StrictHostKeyChecking=no", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "LogLevel=ERROR", + ); + return args; } function extractHostname(host: string): string { @@ -454,27 +475,22 @@ async function hatchCustom( const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`); writeFileSync(startupScriptPath, startupScript); + const sshKeyPath = process.env.VELLUM_SSH_KEY_PATH; + + const installScriptPath = resolveInstallScriptPath(); + try { - const installScriptPath = await resolveInstallScriptPath(); - if (installScriptPath) { - console.log("📋 Uploading install script to instance..."); - await exec("scp", [ - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "LogLevel=ERROR", - installScriptPath, - `${host}:${INSTALL_SCRIPT_REMOTE_PATH}`, - ]); - } else { - console.warn("⚠️ Skipping install script upload (not available in compiled binary)"); - } + console.log("📋 Uploading install script to instance..."); + await exec("scp", [ + ...buildScpArgs(sshKeyPath), + installScriptPath, + `${host}:${INSTALL_SCRIPT_REMOTE_PATH}`, + ]); console.log("📋 Uploading startup script to instance..."); const remoteStartupPath = `/tmp/${instanceName}-startup.sh`; await exec("scp", [ - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "LogLevel=ERROR", + ...buildScpArgs(sshKeyPath), startupScriptPath, `${host}:${remoteStartupPath}`, ]); @@ -485,9 +501,8 @@ async function hatchCustom( `chmod +x ${remoteStartupPath} ${INSTALL_SCRIPT_REMOTE_PATH} && bash ${remoteStartupPath}`, ]); } finally { - try { - unlinkSync(startupScriptPath); - } catch {} + try { unlinkSync(startupScriptPath); } catch {} + try { unlinkSync(installScriptPath); } catch {} } const runtimeUrl = `http://${hostname}:${GATEWAY_PORT}`; diff --git a/cli/src/lib/gcp.ts b/cli/src/lib/gcp.ts index 18fc60d8e6c..cf1e893dbb4 100644 --- a/cli/src/lib/gcp.ts +++ b/cli/src/lib/gcp.ts @@ -370,13 +370,12 @@ const DESIRED_FIREWALL_RULES: FirewallRuleSpec[] = [ }, ]; -async function resolveInstallScriptPath(): Promise { - const sourcePath = join(import.meta.dir, "..", "adapters", "install.sh"); - if (existsSync(sourcePath)) { - return sourcePath; - } - console.warn("\u26a0\ufe0f Install script not found at", sourcePath, "(expected in compiled binary)"); - return null; +import INSTALL_SCRIPT_CONTENT from "../adapters/install.sh" with { type: "text" }; + +function resolveInstallScriptPath(): string { + const tmpPath = join(tmpdir(), `vellum-install-${process.pid}.sh`); + writeFileSync(tmpPath, INSTALL_SCRIPT_CONTENT, { mode: 0o755 }); + return tmpPath; } async function pollInstance( @@ -459,11 +458,7 @@ async function recoverFromCurlFailure( sshUser: string, account?: string, ): Promise { - const installScriptPath = await resolveInstallScriptPath(); - if (!installScriptPath) { - console.warn("\u26a0\ufe0f Skipping install script upload (not available in compiled binary)"); - return; - } + const installScriptPath = resolveInstallScriptPath(); const scpArgs = [ "compute", @@ -488,6 +483,7 @@ async function recoverFromCurlFailure( if (account) sshArgs.push(`--account=${account}`); console.log("\ud83d\udd27 Running install script on instance..."); await exec("gcloud", sshArgs); + try { unlinkSync(installScriptPath); } catch {} } export async function hatchGcp( diff --git a/clients/macos/build.sh b/clients/macos/build.sh index 51e728a5faf..95551bcacaa 100755 --- a/clients/macos/build.sh +++ b/clients/macos/build.sh @@ -154,7 +154,7 @@ build_binaries() { # CLI build_bun_binary "$CLI_SRC_DIR" "$CLI_SRC_DIR/src/index.ts" \ - "$SCRIPT_DIR/cli-bin" "vellum-cli" --external react-devtools-core + "$SCRIPT_DIR/cli-bin" "vellum-cli" # Gateway build_bun_binary "$GATEWAY_SRC_DIR" "$GATEWAY_SRC_DIR/src/index.ts" \ @@ -312,7 +312,7 @@ if [ -d "$CLI_SRC_DIR/src" ] && command -v bun &>/dev/null; then fi if [ "$CLI_BIN_NEEDS_BUILD" = true ]; then build_bun_binary "$CLI_SRC_DIR" "$CLI_SRC_DIR/src/index.ts" \ - "$SCRIPT_DIR/cli-bin" "vellum-cli" --external react-devtools-core + "$SCRIPT_DIR/cli-bin" "vellum-cli" fi # Also rebuild if CLI binary changed or newly added diff --git a/clients/macos/vellum-assistant/App/AssistantCli.swift b/clients/macos/vellum-assistant/App/AssistantCli.swift index ecbbce7f515..e4cc5f4998c 100644 --- a/clients/macos/vellum-assistant/App/AssistantCli.swift +++ b/clients/macos/vellum-assistant/App/AssistantCli.swift @@ -352,7 +352,8 @@ final class AssistantCli { let proc = Process() proc.executableURL = binaryURL - proc.arguments = ["hatch", "--remote", config.remote] + let cliRemote = config.remote == "customHardware" ? "custom" : config.remote + proc.arguments = ["hatch", "--remote", cliRemote] let stdoutPipe = Pipe() let stderrPipe = Pipe() @@ -412,7 +413,7 @@ final class AssistantCli { if !config.awsRoleArn.isEmpty { env["VELLUM_AWS_ROLE_ARN"] = config.awsRoleArn } - } else if config.remote == "custom" { + } else if cliRemote == "custom" { if !config.sshHost.isEmpty { let hostString = config.sshUser.isEmpty ? config.sshHost