Bump to v0.1.3. Since v0.1.2: - Install script with verbose output and error handling - Uninstall script - Alpine rootfs in setup.ts (was Ubuntu) - DNS fix for all chroot operations - Stale tap cleanup before every createTap - Dynamic binary paths (no hardcoded /usr/local/bin) - Node.js upgrade handling - Shellcheck clean - !status command and web search tool - Battle-tested on Ubuntu GPU server deployment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
import { Command } from "commander";
|
|
import { VMInstance } from "./vm.js";
|
|
import { installSignalHandlers } from "./cleanup.js";
|
|
import { runSetup } from "./setup.js";
|
|
import { createSnapshot } from "./snapshot.js";
|
|
import { runOverseer } from "./overseer.js";
|
|
import {
|
|
startAgent,
|
|
stopAgent,
|
|
listAgents,
|
|
} from "./agent-manager.js";
|
|
|
|
export function createCli() {
|
|
const program = new Command();
|
|
|
|
program
|
|
.name("fireclaw")
|
|
.description("Run commands in ephemeral Firecracker microVMs")
|
|
.version("0.1.3");
|
|
|
|
program
|
|
.command("run")
|
|
.description("Run a command inside a fresh microVM")
|
|
.argument("<command>", "Command to execute inside the microVM")
|
|
.option("-t, --timeout <seconds>", "Timeout in seconds", "60")
|
|
.option("-v, --verbose", "Show detailed progress", false)
|
|
.option("--mem <mib>", "Memory in MiB", "256")
|
|
.option("--vcpu <count>", "Number of vCPUs", "1")
|
|
.option("--no-snapshot", "Force cold boot, skip snapshot restore")
|
|
.action(async (command: string, opts) => {
|
|
installSignalHandlers();
|
|
|
|
const result = await VMInstance.run(command, {
|
|
timeout: parseInt(opts.timeout) * 1000,
|
|
verbose: opts.verbose,
|
|
mem: parseInt(opts.mem),
|
|
vcpu: parseInt(opts.vcpu),
|
|
noSnapshot: opts.snapshot === false,
|
|
});
|
|
|
|
if (!opts.verbose) {
|
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
}
|
|
|
|
process.exit(result.exitCode);
|
|
});
|
|
|
|
program
|
|
.command("setup")
|
|
.description("Download kernel, rootfs, and configure networking")
|
|
.action(async () => {
|
|
await runSetup();
|
|
});
|
|
|
|
const snapshot = program
|
|
.command("snapshot")
|
|
.description("Manage VM snapshots");
|
|
|
|
snapshot
|
|
.command("create")
|
|
.description("Boot a VM and create a snapshot for fast restores")
|
|
.action(async () => {
|
|
installSignalHandlers();
|
|
await createSnapshot();
|
|
});
|
|
|
|
// Overseer
|
|
program
|
|
.command("overseer")
|
|
.description("Start the overseer daemon (IRC bot for agent management)")
|
|
.option("--server <host>", "IRC server", "localhost")
|
|
.option("--port <port>", "IRC port", "6667")
|
|
.option("--nick <nick>", "Bot nickname", "overseer")
|
|
.option("--channel <chan>", "Control channel", "#control")
|
|
.action(async (opts) => {
|
|
await runOverseer({
|
|
server: opts.server,
|
|
port: parseInt(opts.port),
|
|
nick: opts.nick,
|
|
channel: opts.channel,
|
|
});
|
|
});
|
|
|
|
// Agent management
|
|
const agent = program
|
|
.command("agent")
|
|
.description("Manage long-running agent VMs");
|
|
|
|
agent
|
|
.command("start")
|
|
.description("Start an agent VM from a template")
|
|
.argument("<template>", "Template name")
|
|
.option("--name <name>", "Override agent name")
|
|
.option("--model <model>", "Override LLM model")
|
|
.action(async (template: string, opts) => {
|
|
installSignalHandlers();
|
|
const info = await startAgent(template, {
|
|
name: opts.name,
|
|
model: opts.model,
|
|
});
|
|
console.log(
|
|
`Agent "${info.name}" started: ${info.nick} [${info.model}] (${info.ip})`
|
|
);
|
|
process.exit(0);
|
|
});
|
|
|
|
agent
|
|
.command("stop")
|
|
.description("Stop a running agent VM")
|
|
.argument("<name>", "Agent name")
|
|
.action(async (name: string) => {
|
|
await stopAgent(name);
|
|
console.log(`Agent "${name}" stopped.`);
|
|
});
|
|
|
|
agent
|
|
.command("list")
|
|
.description("List running agent VMs")
|
|
.action(() => {
|
|
const agents = listAgents();
|
|
if (agents.length === 0) {
|
|
console.log("No agents running.");
|
|
return;
|
|
}
|
|
for (const a of agents) {
|
|
console.log(
|
|
`${a.name} (${a.template}) — ${a.nick} [${a.model}] ip=${a.ip} since ${a.startedAt}`
|
|
);
|
|
}
|
|
});
|
|
|
|
return program;
|
|
}
|