Files
fireclaw/src/cli.ts
ansible a2cef20a89 v0.1.3 — deployment hardening
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>
2026-04-07 16:18:04 +00:00

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;
}