mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-19 16:39:50 +00:00
* feat: add --custom flag for interactive machine type/region selection By default, all clouds now skip size/region prompts and use sensible defaults for faster provisioning. The --custom flag enables interactive pickers on all clouds, unifying the previously inconsistent behavior where some clouds always prompted and others never did. - AWS: promptRegion/promptBundle gated on SPAWN_CUSTOM - GCP: promptMachineType/promptZone gated on SPAWN_CUSTOM - Fly: promptVmOptions gated on SPAWN_CUSTOM - Hetzner: new promptServerType/promptLocation with type/location arrays - DigitalOcean: new promptDropletSize/promptDoRegion with size/region arrays - Daytona: new promptSandboxSize with cpu/memory/disk presets - Sprite: no change (managed platform, no meaningful size options) - --custom + --headless is an error (incompatible modes) - Version bump to 0.8.0 (new feature) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix biome format violations in --custom flag code Auto-format object literals in arrays (expand to multi-line), wrap long console.error line, and expand inline array in test assertion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: lab <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
116 lines
3 KiB
TypeScript
116 lines
3 KiB
TypeScript
#!/usr/bin/env bun
|
|
// fly/main.ts — Orchestrator: deploys an agent on Fly.io
|
|
|
|
import {
|
|
ensureFlyCli,
|
|
ensureFlyToken,
|
|
promptOrg,
|
|
promptSpawnName,
|
|
createServer,
|
|
getServerName,
|
|
waitForCloudInit,
|
|
waitForSsh,
|
|
runServer,
|
|
uploadFile,
|
|
interactiveSession,
|
|
FLY_VM_TIERS,
|
|
DEFAULT_VM_TIER,
|
|
} from "./fly";
|
|
import type { ServerOptions } from "./fly";
|
|
import { resolveAgent } from "./agents";
|
|
import { saveLaunchCmd } from "../history.js";
|
|
import { runOrchestration } from "../shared/orchestrate";
|
|
import type { CloudOrchestrator } from "../shared/orchestrate";
|
|
import { selectFromList } from "../shared/ui";
|
|
|
|
async function promptVmOptions(): Promise<ServerOptions> {
|
|
if (process.env.FLY_VM_MEMORY) {
|
|
const memoryMb = Number.parseInt(process.env.FLY_VM_MEMORY, 10);
|
|
const tier = FLY_VM_TIERS.find((t) => t.memoryMb === memoryMb) || DEFAULT_VM_TIER;
|
|
return {
|
|
cpuKind: tier.cpuKind,
|
|
cpus: tier.cpus,
|
|
memoryMb: tier.memoryMb,
|
|
};
|
|
}
|
|
|
|
if (process.env.SPAWN_CUSTOM !== "1") {
|
|
return {
|
|
cpuKind: DEFAULT_VM_TIER.cpuKind,
|
|
cpus: DEFAULT_VM_TIER.cpus,
|
|
memoryMb: DEFAULT_VM_TIER.memoryMb,
|
|
};
|
|
}
|
|
|
|
if (process.env.SPAWN_NON_INTERACTIVE === "1") {
|
|
return {
|
|
cpuKind: DEFAULT_VM_TIER.cpuKind,
|
|
cpus: DEFAULT_VM_TIER.cpus,
|
|
memoryMb: DEFAULT_VM_TIER.memoryMb,
|
|
};
|
|
}
|
|
|
|
process.stderr.write("\n");
|
|
const tierItems = FLY_VM_TIERS.map((t) => `${t.id}|${t.label}`);
|
|
const tierId = await selectFromList(tierItems, "VM size", DEFAULT_VM_TIER.id);
|
|
const selectedTier = FLY_VM_TIERS.find((t) => t.id === tierId) || DEFAULT_VM_TIER;
|
|
|
|
return {
|
|
cpuKind: selectedTier.cpuKind,
|
|
cpus: selectedTier.cpus,
|
|
memoryMb: selectedTier.memoryMb,
|
|
};
|
|
}
|
|
|
|
async function main() {
|
|
const agentName = process.argv[2];
|
|
if (!agentName) {
|
|
console.error("Usage: bun run fly/main.ts <agent>");
|
|
console.error("Agents: claude, codex, openclaw, opencode, kilocode, zeroclaw");
|
|
process.exit(1);
|
|
}
|
|
|
|
const agent = resolveAgent(agentName);
|
|
|
|
let serverOpts: ServerOptions;
|
|
|
|
const cloud: CloudOrchestrator = {
|
|
cloudName: "fly",
|
|
cloudLabel: "Fly.io",
|
|
runner: {
|
|
runServer,
|
|
uploadFile,
|
|
},
|
|
async authenticate() {
|
|
await promptSpawnName();
|
|
await ensureFlyCli();
|
|
await ensureFlyToken();
|
|
await promptOrg();
|
|
},
|
|
async promptSize() {
|
|
serverOpts = await promptVmOptions();
|
|
},
|
|
getServerName,
|
|
async createServer(name: string) {
|
|
await createServer(name, serverOpts, agent.image);
|
|
},
|
|
async waitForReady() {
|
|
if (agent.image) {
|
|
// Custom image already has packages baked in — just wait for SSH
|
|
await waitForSsh();
|
|
} else {
|
|
await waitForCloudInit(agent.cloudInitTier);
|
|
}
|
|
},
|
|
interactiveSession,
|
|
saveLaunchCmd,
|
|
};
|
|
|
|
await runOrchestration(cloud, agent, agentName);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
const msg = err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
|
|
process.stderr.write(`\x1b[0;31mFatal: ${msg}\x1b[0m\n`);
|
|
process.exit(1);
|
|
});
|