Initial commit — fireclaw multi-agent system
Firecracker microVM-based multi-agent system with IRC orchestration and local LLMs. Features: - Ephemeral command runner with VM snapshots (~1.1s) - Multi-agent orchestration via overseer IRC bot - 5 agent templates (worker, coder, researcher, quick, creative) - Tool access (shell + podman containers inside VMs) - Persistent workspace + memory system (MEMORY.md pattern) - Agent hot-reload (model/persona swap via SSH + SIGHUP) - Non-root agents, graceful shutdown, crash recovery - Agent-to-agent communication via IRC - DM support, /invite support - Systemd service, 20 regression tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
134
src/cli.ts
Normal file
134
src/cli.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
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.0");
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user