trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
165 lines (163 loc) • 4.32 kB
JavaScript
// src/server/sprites.ts
import { execFile, spawn } from "child_process";
import { promisify } from "util";
var execFileAsync = promisify(execFile);
async function runSpriteCmd(args) {
try {
const { stdout } = await execFileAsync("sprite", args);
return stdout.trim();
} catch (err) {
throw new Error(
`sprite ${args[0]} failed (exit ${err.code ?? "?"}): ${err.stderr ?? err.message}`
);
}
}
async function runSpriteExec(spriteName, shellCommand, extraArgs = []) {
return runSpriteCmd([
"exec",
"-s",
spriteName,
...extraArgs,
"--",
"bash",
"-c",
shellCommand
]);
}
async function runSpriteCopy(localPath, spriteName, remotePath) {
try {
await execFileAsync("sprite", [
"exec",
"-s",
spriteName,
"--file",
`${localPath}:${remotePath}`,
"echo",
"uploaded"
]);
} catch (err) {
throw new Error(
`sprite file upload failed (exit ${err.code ?? "?"}): ${err.stderr ?? err.message}`
);
}
}
var SPRITE_ENSURE_BUN_SH = `
set -e
export PATH="$HOME/.bun/bin:$PATH"
if ! command -v bun >/dev/null 2>&1; then
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
fi
command -v bun
`.trim();
function spriteStopServiceSh(service, pkillPattern) {
return `
export PATH="$HOME/.bun/bin:$PATH"
ENV="/.sprite/bin/sprite-env"
$ENV services delete ${service} 2>/dev/null || true
# Bracket trick / path pattern: avoid matching this bash -c argv.
pkill -f "${pkillPattern}" 2>/dev/null || true
sleep 2
exit 0
`.trim();
}
function spriteStartServiceSh(opts) {
const { service, bun, script, dir, port } = opts;
return `
export PATH="$HOME/.bun/bin:$PATH"
ENV="/.sprite/bin/sprite-env"
BUN="${bun}"
$ENV services create ${service} \\
--cmd "$BUN" \\
--args run,${script} \\
--dir ${dir} \\
--http-port ${port} \\
--no-stream
`.trim();
}
var SPRITE_STOP_TRELLIS_DB_SH = spriteStopServiceSh(
"trellis-db",
// Bracket so this bash -c argv does not match the pkill regex (else exit 143).
"[t]rellis-db/server.js"
);
function spriteStartTrellisDbSh(opts) {
return spriteStartServiceSh({
service: "trellis-db",
bun: opts.bun,
script: "server.js",
dir: "/home/sprite/trellis-db",
port: opts.port
});
}
function parseSpriteUrlOutput(stdout) {
const match = stdout.match(/URL:\s*(https:\/\/\S+)/i);
if (!match) {
throw new Error(`Could not parse sprite URL from output: ${stdout.trim()}`);
}
return match[1].replace(/\/$/, "");
}
async function resolveSpritePublicUrl(spriteName) {
const out = await runSpriteCmd(["url", "-s", spriteName]);
return parseSpriteUrlOutput(out);
}
async function ensureSpritePublicAccess(spriteName) {
try {
await runSpriteCmd(["url", "update", "--auth", "public", "-s", spriteName]);
} catch (err) {
const msg = String(err.message ?? err);
if (/already|unchanged|public/i.test(msg)) return;
throw err;
}
}
async function ensureSprite(spriteName) {
try {
await runSpriteCmd(["create", spriteName, "--skip-console"]);
} catch (err) {
const msg = String(err.message ?? err);
if (/already exists|duplicate|conflict/i.test(msg)) return;
throw err;
}
}
async function runSpriteInteractive(args) {
return new Promise((resolve, reject) => {
const proc = spawn("sprite", args, { stdio: "inherit" });
proc.on("close", (code) => {
if (code !== 0)
reject(new Error(`sprite ${args[0]} failed (exit ${code})`));
else resolve();
});
proc.on("error", reject);
});
}
async function assertSpriteCli() {
await runSpriteCmd(["--version"]).catch(() => {
throw new Error(
"`sprite` CLI not found. Install it from https://docs.sprites.dev and authenticate."
);
});
}
function resolveSprite(explicitName) {
if (explicitName) {
return explicitName;
}
throw new Error(
"No sprite specified. Use --sprite <name> or set an active sprite with `trellis vm use <name>`"
);
}
export {
runSpriteCmd,
runSpriteExec,
runSpriteCopy,
SPRITE_ENSURE_BUN_SH,
spriteStopServiceSh,
spriteStartServiceSh,
SPRITE_STOP_TRELLIS_DB_SH,
spriteStartTrellisDbSh,
parseSpriteUrlOutput,
resolveSpritePublicUrl,
ensureSpritePublicAccess,
ensureSprite,
runSpriteInteractive,
assertSpriteCli,
resolveSprite
};