mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-26 11:00:38 +00:00
fix(zeroclaw): remove broken zeroclaw agent (repo 404) (#3107)
* fix(zeroclaw): remove broken zeroclaw agent (repo 404) The zeroclaw-labs/zeroclaw GitHub repository returns 404 — all installs fail. Remove zeroclaw entirely from the matrix: agent definition, setup code, shell scripts, e2e tests, packer config, skill files, and documentation. Fixes #3102 Agent: code-health Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(zeroclaw): remove stale zeroclaw reference from discovery.md ARM agents list Addresses security review on PR #3107 — the last remaining zeroclaw reference in .claude/rules/discovery.md is now removed. Agent: issue-fixer Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(zeroclaw): remove remaining stale zeroclaw references from CI/packer Remove zeroclaw from: - .github/workflows/agent-tarballs.yml ARM build matrix - .github/workflows/docker.yml agent matrix - packer/digitalocean.pkr.hcl comment - sh/e2e/e2e.sh comment Addresses all 5 stale references flagged in security review of PR #3107. Agent: issue-fixer Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
570caaba8d
commit
5e0144b645
44 changed files with 22 additions and 603 deletions
|
|
@ -62,7 +62,7 @@ Do NOT add agents speculatively. Only add one if there's **real community buzz**
|
|||
Agents that ship compiled binaries (Rust, Go, etc.) need separate ARM (aarch64) tarball builds. npm-based agents are arch-independent and only need x86_64 builds. When adding a new agent:
|
||||
- If it installs via `npm install -g` → x86_64 tarball only (Node handles arch)
|
||||
- If it installs a pre-compiled binary (curl download, cargo install, go install) → add an ARM entry in `.github/workflows/agent-tarballs.yml` matrix `include` section
|
||||
- Current native binary agents needing ARM: zeroclaw (Rust), opencode (Go), hermes, claude
|
||||
- Current native binary agents needing ARM: opencode (Go), hermes, claude
|
||||
|
||||
To add: same steps as before (manifest.json entry, matrix entries, implement on 1+ cloud, README).
|
||||
|
||||
|
|
|
|||
2
.github/workflows/agent-tarballs.yml
vendored
2
.github/workflows/agent-tarballs.yml
vendored
|
|
@ -49,8 +49,6 @@ jobs:
|
|||
# Native-binary agents need ARM builds too.
|
||||
# npm-based agents (codex, openclaw, kilocode) are arch-independent — x86_64 only.
|
||||
include:
|
||||
- agent: zeroclaw
|
||||
arch: arm64
|
||||
- agent: opencode
|
||||
arch: arm64
|
||||
- agent: hermes
|
||||
|
|
|
|||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
agent: [claude, codex, cursor, openclaw, opencode, kilocode, zeroclaw, hermes, junie]
|
||||
agent: [claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie]
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
|
|
|
|||
|
|
@ -324,7 +324,6 @@ If an agent fails to install or launch on a cloud:
|
|||
|---|---|---|---|---|---|---|
|
||||
| [**Claude Code**](https://claude.ai) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| [**OpenClaw**](https://github.com/openclaw/openclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| [**ZeroClaw**](https://github.com/zeroclaw-labs/zeroclaw) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| [**Codex CLI**](https://github.com/openai/codex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| [**OpenCode**](https://github.com/sst/opencode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| [**Kilo Code**](https://github.com/Kilo-Org/kilocode) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
"url": "https://openclaw.ai/apple-touch-icon.png",
|
||||
"ext": "png"
|
||||
},
|
||||
"zeroclaw": {
|
||||
"url": "https://avatars.githubusercontent.com/u/261820148?s=200&v=4",
|
||||
"ext": "png"
|
||||
},
|
||||
"codex": {
|
||||
"url": "https://avatars.githubusercontent.com/u/14957082?s=200&v=4",
|
||||
"ext": "png"
|
||||
|
|
|
|||
|
|
@ -89,52 +89,6 @@
|
|||
"gateway"
|
||||
]
|
||||
},
|
||||
"zeroclaw": {
|
||||
"name": "ZeroClaw",
|
||||
"description": "Fast, small, fully autonomous AI assistant infrastructure — deploy anywhere, swap anything",
|
||||
"url": "https://github.com/zeroclaw-labs/zeroclaw",
|
||||
"install": "curl -LsSf https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/a117be64fdaa31779204beadf2942c8aef57d0e5/scripts/bootstrap.sh | bash -s -- --install-rust --install-system-deps --prefer-prebuilt",
|
||||
"launch": "zeroclaw agent",
|
||||
"env": {
|
||||
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
|
||||
"ZEROCLAW_PROVIDER": "openrouter"
|
||||
},
|
||||
"config_files": {
|
||||
"~/.zeroclaw/config.toml": {
|
||||
"security": {
|
||||
"autonomy": "full",
|
||||
"supervised": false,
|
||||
"allow_destructive": true
|
||||
},
|
||||
"shell": {
|
||||
"policy": "allow_all"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notes": "Rust-based agent framework built by Harvard/MIT/Sundai.Club communities. Natively supports OpenRouter via OPENROUTER_API_KEY + ZEROCLAW_PROVIDER=openrouter. Requires compilation from source (~5-10 min).",
|
||||
"icon": "https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/assets/agents/zeroclaw.png",
|
||||
"featured_cloud": [
|
||||
"digitalocean",
|
||||
"sprite"
|
||||
],
|
||||
"creator": "Sundai.Club",
|
||||
"repo": "zeroclaw-labs/zeroclaw",
|
||||
"license": "Apache-2.0",
|
||||
"created": "2026-02",
|
||||
"added": "2025-12",
|
||||
"github_stars": 29095,
|
||||
"stars_updated": "2026-03-29",
|
||||
"language": "Rust",
|
||||
"runtime": "binary",
|
||||
"category": "cli",
|
||||
"tagline": "Fast, small, fully autonomous AI infrastructure — deploy anywhere, swap anything",
|
||||
"tags": [
|
||||
"coding",
|
||||
"terminal",
|
||||
"rust",
|
||||
"autonomous"
|
||||
]
|
||||
},
|
||||
"codex": {
|
||||
"name": "Codex CLI",
|
||||
"description": "OpenAI's open-source coding agent",
|
||||
|
|
@ -453,37 +407,31 @@
|
|||
"matrix": {
|
||||
"local/claude": "implemented",
|
||||
"local/openclaw": "implemented",
|
||||
"local/zeroclaw": "implemented",
|
||||
"local/codex": "implemented",
|
||||
"local/opencode": "implemented",
|
||||
"local/kilocode": "implemented",
|
||||
"hetzner/claude": "implemented",
|
||||
"hetzner/openclaw": "implemented",
|
||||
"hetzner/zeroclaw": "implemented",
|
||||
"hetzner/codex": "implemented",
|
||||
"hetzner/opencode": "implemented",
|
||||
"hetzner/kilocode": "implemented",
|
||||
"aws/claude": "implemented",
|
||||
"aws/openclaw": "implemented",
|
||||
"aws/zeroclaw": "implemented",
|
||||
"aws/codex": "implemented",
|
||||
"aws/opencode": "implemented",
|
||||
"aws/kilocode": "implemented",
|
||||
"digitalocean/claude": "implemented",
|
||||
"digitalocean/openclaw": "implemented",
|
||||
"digitalocean/zeroclaw": "implemented",
|
||||
"digitalocean/codex": "implemented",
|
||||
"digitalocean/opencode": "implemented",
|
||||
"digitalocean/kilocode": "implemented",
|
||||
"gcp/claude": "implemented",
|
||||
"gcp/openclaw": "implemented",
|
||||
"gcp/zeroclaw": "implemented",
|
||||
"gcp/codex": "implemented",
|
||||
"gcp/opencode": "implemented",
|
||||
"gcp/kilocode": "implemented",
|
||||
"sprite/claude": "implemented",
|
||||
"sprite/openclaw": "implemented",
|
||||
"sprite/zeroclaw": "implemented",
|
||||
"sprite/codex": "implemented",
|
||||
"sprite/opencode": "implemented",
|
||||
"sprite/kilocode": "implemented",
|
||||
|
|
|
|||
|
|
@ -191,12 +191,6 @@ describe("createCloudAgents", () => {
|
|||
"ANTHROPIC_BASE_URL",
|
||||
],
|
||||
],
|
||||
[
|
||||
"zeroclaw",
|
||||
[
|
||||
"ZEROCLAW_PROVIDER=openrouter",
|
||||
],
|
||||
],
|
||||
[
|
||||
"hermes",
|
||||
[
|
||||
|
|
@ -228,11 +222,6 @@ describe("createCloudAgents", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("zeroclaw agent configure calls runServer", async () => {
|
||||
await result.agents.zeroclaw.configure?.("sk-or-v1-test", undefined, new Set());
|
||||
expect(runner.runServer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("all agents have launchCmd returning non-empty string", () => {
|
||||
for (const agent of Object.values(result.agents)) {
|
||||
const cmd = agent.launchCmd();
|
||||
|
|
|
|||
|
|
@ -189,7 +189,6 @@ describe("validateLaunchCmd", () => {
|
|||
"source ~/.spawnrc 2>/dev/null; export PATH=$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH; openclaw tui",
|
||||
"source ~/.spawnrc 2>/dev/null; source ~/.zshrc 2>/dev/null; opencode",
|
||||
"source ~/.spawnrc 2>/dev/null; source ~/.zshrc 2>/dev/null; kilocode",
|
||||
"export PATH=$HOME/.cargo/bin:$PATH; source ~/.cargo/env 2>/dev/null; source ~/.spawnrc 2>/dev/null; zeroclaw agent",
|
||||
"source ~/.spawnrc 2>/dev/null; hermes",
|
||||
"claude",
|
||||
"aider",
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ describe("getSpawnSkillPath", () => {
|
|||
"openclaw",
|
||||
"~/.openclaw/skills/spawn/SKILL.md",
|
||||
],
|
||||
[
|
||||
"zeroclaw",
|
||||
"~/.zeroclaw/workspace/AGENTS.md",
|
||||
],
|
||||
[
|
||||
"opencode",
|
||||
"~/.config/opencode/AGENTS.md",
|
||||
|
|
@ -67,7 +63,6 @@ describe("isAppendMode", () => {
|
|||
"claude",
|
||||
"codex",
|
||||
"openclaw",
|
||||
"zeroclaw",
|
||||
"opencode",
|
||||
"kilocode",
|
||||
"junie",
|
||||
|
|
@ -85,7 +80,6 @@ describe("getSkillContent", () => {
|
|||
"claude",
|
||||
"codex",
|
||||
"openclaw",
|
||||
"zeroclaw",
|
||||
"opencode",
|
||||
"kilocode",
|
||||
"hermes",
|
||||
|
|
@ -114,7 +108,6 @@ describe("getSkillContent", () => {
|
|||
}
|
||||
|
||||
for (const agent of [
|
||||
"zeroclaw",
|
||||
"opencode",
|
||||
"kilocode",
|
||||
"junie",
|
||||
|
|
@ -184,7 +177,6 @@ describe("injectSpawnSkill", () => {
|
|||
"claude",
|
||||
"codex",
|
||||
"openclaw",
|
||||
"zeroclaw",
|
||||
"opencode",
|
||||
"kilocode",
|
||||
"hermes",
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ function defaultSshCommand(host: string, user: string, keyOpts: string[], cmd: s
|
|||
const KNOWN_AGENTS = [
|
||||
"claude",
|
||||
"openclaw",
|
||||
"zeroclaw",
|
||||
"codex",
|
||||
"opencode",
|
||||
"kilocode",
|
||||
|
|
@ -79,7 +78,7 @@ type KnownAgent = (typeof KNOWN_AGENTS)[number];
|
|||
function detectAgent(host: string, user: string, keyOpts: string[], runCmd: SshCommandFn): string | null {
|
||||
// First: check running processes
|
||||
const psCmd =
|
||||
"ps aux 2>/dev/null | grep -oE 'claude(-code)?|openclaw|zeroclaw|codex|opencode|kilocode|hermes|junie' | grep -v grep | head -1 || true";
|
||||
"ps aux 2>/dev/null | grep -oE 'claude(-code)?|openclaw|codex|opencode|kilocode|hermes|junie' | grep -v grep | head -1 || true";
|
||||
const psOut = runCmd(host, user, keyOpts, psCmd);
|
||||
if (psOut) {
|
||||
const match = KNOWN_AGENTS.find((b: KnownAgent) => psOut.includes(b));
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ const MARKETPLACE_IMAGES: Record<string, string> = {
|
|||
openclaw: "openrouter-spawnopenclaw",
|
||||
opencode: "openrouter-spawnopencode",
|
||||
kilocode: "openrouter-spawnkilocode",
|
||||
zeroclaw: "openrouter-spawnzeroclaw",
|
||||
hermes: "openrouter-spawnhermes",
|
||||
junie: "openrouter-spawnjunie",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -406,7 +406,7 @@ export function validateLaunchCmd(cmd: string): void {
|
|||
"Invalid launch command in history: invalid agent invocation\n\n" +
|
||||
`Command: "${cmd}"\n` +
|
||||
`Rejected segment: "${lastSegment}"\n\n` +
|
||||
"The final segment must be a simple binary name (e.g., 'claude', 'zeroclaw agent').\n\n" +
|
||||
"The final segment must be a simple binary name (e.g., 'claude', 'hermes').\n\n" +
|
||||
"Your spawn history file may be corrupted or tampered with.\n" +
|
||||
`To fix: run 'spawn list --clear' to reset history`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -592,51 +592,6 @@ export async function startGateway(runner: CloudRunner): Promise<void> {
|
|||
logInfo("OpenClaw gateway started");
|
||||
}
|
||||
|
||||
// ─── ZeroClaw Config ─────────────────────────────────────────────────────────
|
||||
|
||||
async function setupZeroclawConfig(runner: CloudRunner, _apiKey: string): Promise<void> {
|
||||
logStep("Configuring ZeroClaw for autonomous operation...");
|
||||
|
||||
// Remove any pre-existing config (e.g. from Docker image extraction) before
|
||||
// running onboard, which generates a fresh config with the correct API key.
|
||||
await runner.runServer("rm -f ~/.zeroclaw/config.toml");
|
||||
|
||||
// Run onboard first to set up provider/key
|
||||
await runner.runServer(
|
||||
`source ~/.spawnrc 2>/dev/null; export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"; zeroclaw onboard --api-key "\${OPENROUTER_API_KEY}" --provider openrouter`,
|
||||
);
|
||||
|
||||
// Patch autonomy settings in-place. `zeroclaw onboard` already generates
|
||||
// [security] and [shell] sections — so we sed the values instead of
|
||||
// appending duplicate sections.
|
||||
const patchScript = [
|
||||
"cd ~/.zeroclaw",
|
||||
// Update existing security values (or append section if missing)
|
||||
'if grep -q "^\\[security\\]" config.toml 2>/dev/null; then',
|
||||
" sed -i 's/^autonomy = .*/autonomy = \"full\"/' config.toml",
|
||||
" sed -i 's/^supervised = .*/supervised = false/' config.toml",
|
||||
" sed -i 's/^allow_destructive = .*/allow_destructive = true/' config.toml",
|
||||
"else",
|
||||
" printf '\\n[security]\\nautonomy = \"full\"\\nsupervised = false\\nallow_destructive = true\\n' >> config.toml",
|
||||
"fi",
|
||||
// Update existing shell policy (or append section if missing)
|
||||
'if grep -q "^\\[shell\\]" config.toml 2>/dev/null; then',
|
||||
" sed -i 's/^policy = .*/policy = \"allow_all\"/' config.toml",
|
||||
"else",
|
||||
" printf '\\n[shell]\\npolicy = \"allow_all\"\\n' >> config.toml",
|
||||
"fi",
|
||||
// Force native runtime (no Docker) — zeroclaw auto-detects Docker and
|
||||
// launches in a container otherwise, which hangs the interactive session.
|
||||
'if grep -q "^\\[runtime\\]" config.toml 2>/dev/null; then',
|
||||
" sed -i 's/^adapter = .*/adapter = \"native\"/' config.toml",
|
||||
"else",
|
||||
" printf '\\n[runtime]\\nadapter = \"native\"\\n' >> config.toml",
|
||||
"fi",
|
||||
].join("\n");
|
||||
await runner.runServer(patchScript);
|
||||
logInfo("ZeroClaw configured for autonomous operation");
|
||||
}
|
||||
|
||||
// ─── OpenCode Install Command ────────────────────────────────────────────────
|
||||
|
||||
function openCodeInstallCmd(): string {
|
||||
|
|
@ -894,10 +849,6 @@ export async function setupAutoUpdate(runner: CloudRunner, agentName: string, up
|
|||
|
||||
// ─── Default Agent Definitions ───────────────────────────────────────────────
|
||||
|
||||
// Last zeroclaw release that shipped Linux prebuilt binaries (v0.1.9a has none).
|
||||
// Used for direct binary install to avoid a Rust source build timeout.
|
||||
const ZEROCLAW_PREBUILT_TAG = "v0.1.7-beta.30";
|
||||
|
||||
function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
|
||||
return {
|
||||
claude: {
|
||||
|
|
@ -1011,50 +962,6 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
|
|||
"npm install -g ${_NPM_G_FLAGS:-} @kilocode/cli@latest",
|
||||
},
|
||||
|
||||
zeroclaw: {
|
||||
name: "ZeroClaw",
|
||||
cloudInitTier: "minimal",
|
||||
modelEnvVar: "ZEROCLAW_MODEL",
|
||||
preProvision: detectGithubAuth,
|
||||
install: async () => {
|
||||
// Direct binary install from pinned release (v0.1.9a "latest" has no assets,
|
||||
// causing the bootstrap --prefer-prebuilt path to 404-fail and fall back to
|
||||
// a Rust source build that exceeds the 600s install timeout).
|
||||
const directInstallCmd =
|
||||
`_ZC_ARCH="$(uname -m)"; ` +
|
||||
`if [ "$_ZC_ARCH" = "x86_64" ]; then _ZC_TARGET="x86_64-unknown-linux-gnu"; ` +
|
||||
`elif [ "$_ZC_ARCH" = "aarch64" ] || [ "$_ZC_ARCH" = "arm64" ]; then _ZC_TARGET="aarch64-unknown-linux-gnu"; ` +
|
||||
`else echo "Unsupported arch: $_ZC_ARCH" >&2; exit 1; fi; ` +
|
||||
`_ZC_URL="https://github.com/zeroclaw-labs/zeroclaw/releases/download/${ZEROCLAW_PREBUILT_TAG}/zeroclaw-\${_ZC_TARGET}.tar.gz"; ` +
|
||||
`_ZC_TMP="$(mktemp -d)"; ` +
|
||||
`curl --proto '=https' -fsSL "$_ZC_URL" -o "$_ZC_TMP/zeroclaw.tar.gz" && ` +
|
||||
`tar -xzf "$_ZC_TMP/zeroclaw.tar.gz" -C "$_ZC_TMP" && ` +
|
||||
`{ mkdir -p "$HOME/.local/bin" && install -m 755 "$_ZC_TMP/zeroclaw" "$HOME/.local/bin/zeroclaw"; } && ` +
|
||||
`rm -rf "$_ZC_TMP"`;
|
||||
await installAgent(runner, "ZeroClaw", directInstallCmd, 120);
|
||||
},
|
||||
envVars: (apiKey) => [
|
||||
`OPENROUTER_API_KEY=${apiKey}`,
|
||||
"ZEROCLAW_PROVIDER=openrouter",
|
||||
"ZEROCLAW_RUNTIME=native",
|
||||
],
|
||||
configure: (apiKey) => setupZeroclawConfig(runner, apiKey),
|
||||
launchCmd: () =>
|
||||
"export PATH=$HOME/.local/bin:$HOME/.cargo/bin:$PATH; source ~/.spawnrc 2>/dev/null; zeroclaw agent",
|
||||
updateCmd:
|
||||
'export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"; ' +
|
||||
`_ZC_ARCH="$(uname -m)"; ` +
|
||||
`if [ "$_ZC_ARCH" = "x86_64" ]; then _ZC_TARGET="x86_64-unknown-linux-gnu"; ` +
|
||||
`elif [ "$_ZC_ARCH" = "aarch64" ] || [ "$_ZC_ARCH" = "arm64" ]; then _ZC_TARGET="aarch64-unknown-linux-gnu"; ` +
|
||||
"else exit 1; fi; " +
|
||||
`_ZC_URL="https://github.com/zeroclaw-labs/zeroclaw/releases/latest/download/zeroclaw-\${_ZC_TARGET}.tar.gz"; ` +
|
||||
`_ZC_TMP="$(mktemp -d)"; ` +
|
||||
`curl --proto '=https' -fsSL "$_ZC_URL" -o "$_ZC_TMP/zeroclaw.tar.gz" && ` +
|
||||
`tar -xzf "$_ZC_TMP/zeroclaw.tar.gz" -C "$_ZC_TMP" && ` +
|
||||
`install -m 755 "$_ZC_TMP/zeroclaw" "$HOME/.local/bin/zeroclaw" && ` +
|
||||
`rm -rf "$_ZC_TMP"`,
|
||||
},
|
||||
|
||||
hermes: {
|
||||
name: "Hermes Agent",
|
||||
cloudInitTier: "minimal",
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ export async function runOrchestration(
|
|||
|
||||
// Skip cloud-init for minimal-tier agents when using tarballs or snapshots.
|
||||
// Ubuntu 24.04 base images already have curl + git, so minimal agents (claude,
|
||||
// opencode, zeroclaw, hermes) don't need the cloud-init package install step.
|
||||
// opencode, hermes) don't need the cloud-init package install step.
|
||||
// This saves ~30-60s by just waiting for SSH instead of polling for cloud-init completion.
|
||||
if (
|
||||
cloud.cloudName !== "local" &&
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ You have the \`spawn\` CLI installed. Use it to provision cloud VMs with AI agen
|
|||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
\`\`\`
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Agents:** claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: \`{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}\`
|
||||
|
|
@ -74,7 +74,7 @@ const HERMES_SNIPPET = `
|
|||
|
||||
You have the \`spawn\` CLI for creating child cloud VMs with AI agents.
|
||||
Use \`spawn <agent> <cloud> --headless --output json --prompt "task"\` to delegate work.
|
||||
Available agents: claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie.
|
||||
Available agents: claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie.
|
||||
Cloud credentials are pre-configured. Run \`spawn list --json\` to see children.
|
||||
\`--headless\` only provisions. To run a prompt on the child: \`ssh root@<ip> "bash -lc 'claude -p --dangerously-skip-permissions \\"prompt\\"'"\`. Always use \`bash -lc\` (binaries are in ~/.local/bin/).
|
||||
`;
|
||||
|
|
@ -104,11 +104,6 @@ const AGENT_SKILLS: Record<string, SkillConfig> = {
|
|||
content: SKILL_FRONTMATTER + SKILL_BODY,
|
||||
append: false,
|
||||
},
|
||||
zeroclaw: {
|
||||
remotePath: "~/.zeroclaw/workspace/AGENTS.md",
|
||||
content: SKILL_BODY,
|
||||
append: false,
|
||||
},
|
||||
opencode: {
|
||||
remotePath: "~/.config/opencode/AGENTS.md",
|
||||
content: SKILL_BODY,
|
||||
|
|
|
|||
|
|
@ -30,13 +30,6 @@
|
|||
"mkdir -p ~/.npm-global/bin && npm install -g --prefix ~/.npm-global @kilocode/cli"
|
||||
]
|
||||
},
|
||||
"zeroclaw": {
|
||||
"tier": "minimal",
|
||||
"install": [
|
||||
"if [ ! -f /swapfile ]; then fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile; fi",
|
||||
"curl -LsSf https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/a117be64fdaa31779204beadf2942c8aef57d0e5/scripts/bootstrap.sh | bash -s -- --install-rust --install-system-deps --prefer-prebuilt"
|
||||
]
|
||||
},
|
||||
"hermes": {
|
||||
"tier": "minimal",
|
||||
"install": [
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ source "digitalocean" "spawn" {
|
|||
api_token = var.digitalocean_access_token
|
||||
image = "ubuntu-24-04-x64"
|
||||
region = "sfo3"
|
||||
# 2 GB RAM needed — Claude's native installer and zeroclaw's Rust build
|
||||
# get OOM-killed on s-1vcpu-1gb. Snapshots built here work on all sizes.
|
||||
# 2 GB RAM needed — Claude's native installer gets OOM-killed on
|
||||
# s-1vcpu-1gb. Snapshots built here work on all sizes.
|
||||
size = "s-2vcpu-2gb"
|
||||
ssh_username = "root"
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ fi
|
|||
|
||||
# Validate agent name against allowed list to prevent injection
|
||||
case "${AGENT_NAME}" in
|
||||
openclaw|codex|kilocode|claude|opencode|zeroclaw|hermes|junie) ;;
|
||||
openclaw|codex|kilocode|claude|opencode|hermes|junie) ;;
|
||||
*)
|
||||
printf 'Error: Invalid agent name: %s\nAllowed: openclaw, codex, kilocode, claude, opencode, zeroclaw, hermes, junie\n' "${AGENT_NAME}" >&2
|
||||
printf 'Error: Invalid agent name: %s\nAllowed: openclaw, codex, kilocode, claude, opencode, hermes, junie\n' "${AGENT_NAME}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
@ -44,9 +44,6 @@ case "${AGENT_NAME}" in
|
|||
opencode)
|
||||
echo "/root/.opencode/" >> "${PATHS_FILE}"
|
||||
;;
|
||||
zeroclaw)
|
||||
echo "/root/.cargo/bin/zeroclaw" >> "${PATHS_FILE}"
|
||||
;;
|
||||
hermes)
|
||||
echo "/root/.local/bin/hermes" >> "${PATHS_FILE}"
|
||||
echo "/root/.local/share/" >> "${PATHS_FILE}"
|
||||
|
|
|
|||
|
|
@ -24,12 +24,6 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/aws/claude.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/aws/openclaw.sh)
|
||||
```
|
||||
|
||||
#### ZeroClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/aws/zeroclaw.sh)
|
||||
```
|
||||
|
||||
#### Codex CLI
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled aws.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/aws/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/aws/main.ts" zeroclaw "$@"
|
||||
fi
|
||||
|
||||
# Remote — download and run compiled TypeScript bundle
|
||||
AWS_JS=$(mktemp)
|
||||
trap 'rm -f "$AWS_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/aws-latest/aws.js" -o "$AWS_JS" \
|
||||
|| { printf '\033[0;31mFailed to download aws.js\033[0m\n' >&2; exit 1; }
|
||||
exec bun run "$AWS_JS" zeroclaw "$@"
|
||||
|
|
@ -16,12 +16,6 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/digitalocean/claude.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/digitalocean/openclaw.sh)
|
||||
```
|
||||
|
||||
#### ZeroClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/digitalocean/zeroclaw.sh)
|
||||
```
|
||||
|
||||
#### Codex CLI
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled digitalocean.js (local or from GitHub release)
|
||||
# Includes restart loop for SIGTERM recovery on DigitalOcean
|
||||
|
||||
_AGENT_NAME="zeroclaw"
|
||||
_MAX_RETRIES=3
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
# Run command in the foreground so bun gets full terminal access (raw mode,
|
||||
# arrow keys for interactive prompts). The old pattern backgrounded the child
|
||||
# with & + wait so a SIGTERM trap could forward the signal, but that removed
|
||||
# bun from the foreground process group and broke @clack/prompts multiselect.
|
||||
# Now SIGTERM is detected from exit code 143 (128 + 15) after the child exits.
|
||||
_run_with_restart() {
|
||||
# In headless mode (E2E / --headless), skip the restart loop entirely.
|
||||
# Restarting in headless mode creates duplicate droplets, exhausting the
|
||||
# account's droplet quota and causing all subsequent agents to fail.
|
||||
if [ "${SPAWN_HEADLESS:-}" = "1" ]; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
local attempt=0
|
||||
local backoff=2
|
||||
while [ "$attempt" -lt "$_MAX_RETRIES" ]; do
|
||||
attempt=$((attempt + 1))
|
||||
|
||||
"$@"
|
||||
local exit_code=$?
|
||||
|
||||
# Normal exit
|
||||
if [ "$exit_code" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# SIGTERM (143) or SIGKILL (137) — attempt restart
|
||||
if [ "$exit_code" -eq 143 ] || [ "$exit_code" -eq 137 ]; then
|
||||
printf '\033[0;33m[spawn/%s] Agent process terminated (exit %s). The droplet is likely still running.\033[0m\n' \
|
||||
"$_AGENT_NAME" "$exit_code" >&2
|
||||
printf '\033[0;33m[spawn/%s] Check your DigitalOcean dashboard: https://cloud.digitalocean.com/droplets\033[0m\n' \
|
||||
"$_AGENT_NAME" >&2
|
||||
if [ "$attempt" -lt "$_MAX_RETRIES" ]; then
|
||||
printf '\033[0;33m[spawn/%s] Restarting (attempt %s/%s, backoff %ss)...\033[0m\n' \
|
||||
"$_AGENT_NAME" "$((attempt + 1))" "$_MAX_RETRIES" "$backoff" >&2
|
||||
sleep "$backoff"
|
||||
backoff=$((backoff * 2))
|
||||
continue
|
||||
else
|
||||
printf '\033[0;31m[spawn/%s] Max restart attempts reached (%s). Giving up.\033[0m\n' \
|
||||
"$_AGENT_NAME" "$_MAX_RETRIES" >&2
|
||||
return "$exit_code"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Other failure — exit with the original code
|
||||
return "$exit_code"
|
||||
done
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/digitalocean/main.ts" ]]; then
|
||||
_run_with_restart bun run "$SPAWN_CLI_DIR/packages/cli/src/digitalocean/main.ts" "$_AGENT_NAME" "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Remote — download bundled digitalocean.js from GitHub release
|
||||
DO_JS=$(mktemp)
|
||||
trap 'rm -f "$DO_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/digitalocean-latest/digitalocean.js" -o "$DO_JS" \
|
||||
|| { printf '\033[0;31mFailed to download digitalocean.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
_run_with_restart bun run "$DO_JS" "$_AGENT_NAME" "$@"
|
||||
exit $?
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Base packages
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl git ca-certificates build-essential unzip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ZeroClaw — bootstrap script installs Rust + builds from source
|
||||
RUN curl --proto '=https' -LsSf \
|
||||
https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/a117be64fdaa31779204beadf2942c8aef57d0e5/scripts/bootstrap.sh \
|
||||
| bash -s -- --install-rust --install-system-deps --prefer-prebuilt
|
||||
|
||||
# Ensure cargo bin is on PATH for all shells
|
||||
RUN for rc in /root/.bashrc /root/.zshrc; do \
|
||||
grep -q '.cargo/bin' "$rc" 2>/dev/null || \
|
||||
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> "$rc"; \
|
||||
done
|
||||
|
||||
CMD ["/bin/sleep", "inf"]
|
||||
|
|
@ -230,7 +230,7 @@ run_single_agent() {
|
|||
# Per-agent timeout: run provision/verify/input_test in a subshell with a
|
||||
# wall-clock timeout. This prevents any single step from hanging indefinitely
|
||||
# and ensures a result file is always written (pass, fail, or timeout).
|
||||
# Fixes #2714: sprite-zeroclaw and digitalocean-opencode stalling with no result.
|
||||
# Fixes #2714: digitalocean-opencode stalling with no result.
|
||||
# ---------------------------------------------------------------------------
|
||||
local effective_agent_timeout
|
||||
effective_agent_timeout=$(get_agent_timeout "${agent}")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ set -eo pipefail
|
|||
# ---------------------------------------------------------------------------
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
ALL_AGENTS="claude openclaw zeroclaw codex opencode kilocode hermes junie cursor"
|
||||
ALL_AGENTS="claude openclaw codex opencode kilocode hermes junie cursor"
|
||||
PROVISION_TIMEOUT="${PROVISION_TIMEOUT:-720}"
|
||||
INSTALL_WAIT="${INSTALL_WAIT:-600}"
|
||||
INPUT_TEST_TIMEOUT="${INPUT_TEST_TIMEOUT:-120}"
|
||||
|
|
|
|||
|
|
@ -275,11 +275,6 @@ CLOUD_ENV
|
|||
printf 'export OPENAI_BASE_URL=%q\n' "https://openrouter.ai/api/v1"
|
||||
} >> "${env_tmp}"
|
||||
;;
|
||||
zeroclaw)
|
||||
{
|
||||
printf 'export ZEROCLAW_PROVIDER=%q\n' "openrouter"
|
||||
} >> "${env_tmp}"
|
||||
;;
|
||||
hermes)
|
||||
{
|
||||
printf 'export OPENAI_BASE_URL=%q\n' "https://openrouter.ai/api/v1"
|
||||
|
|
@ -393,10 +388,6 @@ _ensure_agent_binary() {
|
|||
bin_name="codex"
|
||||
install_cmd="mkdir -p ~/.npm-global && npm install -g --prefix ~/.npm-global @openai/codex"
|
||||
;;
|
||||
zeroclaw)
|
||||
bin_name="zeroclaw"
|
||||
install_cmd="curl -LsSf https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/a117be64fdaa31779204beadf2942c8aef57d0e5/scripts/bootstrap.sh | bash -s -- --install-rust --install-system-deps --prefer-prebuilt"
|
||||
;;
|
||||
opencode)
|
||||
bin_name="opencode"
|
||||
install_cmd="curl -fsSL https://opencode.ai/install | bash"
|
||||
|
|
|
|||
|
|
@ -274,40 +274,6 @@ input_test_openclaw() {
|
|||
return 1
|
||||
}
|
||||
|
||||
input_test_zeroclaw() {
|
||||
local app="$1"
|
||||
|
||||
_validate_timeout || return 1
|
||||
|
||||
log_step "Running input test for zeroclaw..."
|
||||
# Base64-encode the prompt and stage it to a remote temp file.
|
||||
# Use -m/--message for non-interactive single-message mode (not -p which is --provider).
|
||||
local encoded_prompt
|
||||
encoded_prompt=$(printf '%s' "${INPUT_TEST_PROMPT}" | base64 -w 0 2>/dev/null || printf '%s' "${INPUT_TEST_PROMPT}" | base64 | tr -d '\n')
|
||||
_validate_base64 "${encoded_prompt}" || return 1
|
||||
_stage_prompt_remotely "${app}" "${encoded_prompt}"
|
||||
_stage_timeout_remotely "${app}" "${INPUT_TEST_TIMEOUT}"
|
||||
|
||||
local output
|
||||
# The prompt and timeout are read from staged temp files — no interpolation in this command.
|
||||
output=$(cloud_exec "${app}" "\
|
||||
source ~/.spawnrc 2>/dev/null; source ~/.cargo/env 2>/dev/null; \
|
||||
_TIMEOUT=\$(cat /tmp/.e2e-timeout); \
|
||||
rm -rf /tmp/e2e-test && mkdir -p /tmp/e2e-test && cd /tmp/e2e-test && git init -q; \
|
||||
PROMPT=\$(cat /tmp/.e2e-prompt | base64 -d); \
|
||||
timeout \"\$_TIMEOUT\" zeroclaw agent -m \"\$PROMPT\"" 2>&1) || true
|
||||
|
||||
if printf '%s' "${output}" | grep -qx "${INPUT_TEST_MARKER}"; then
|
||||
log_ok "zeroclaw input test — marker found in response"
|
||||
return 0
|
||||
else
|
||||
log_err "zeroclaw input test — marker '${INPUT_TEST_MARKER}' not found in response"
|
||||
log_err "Response (last 5 lines):"
|
||||
printf '%s\n' "${output}" | tail -5 >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
input_test_opencode() {
|
||||
log_warn "opencode is TUI-only — skipping input test"
|
||||
return 0
|
||||
|
|
@ -355,7 +321,6 @@ run_input_test() {
|
|||
claude) input_test_claude "${app}" ;;
|
||||
codex) input_test_codex "${app}" ;;
|
||||
openclaw) input_test_openclaw "${app}" ;;
|
||||
zeroclaw) input_test_zeroclaw "${app}" ;;
|
||||
opencode) input_test_opencode ;;
|
||||
kilocode) input_test_kilocode ;;
|
||||
hermes) input_test_hermes ;;
|
||||
|
|
@ -557,40 +522,6 @@ _openclaw_verify_gateway_resilience() {
|
|||
fi
|
||||
}
|
||||
|
||||
verify_zeroclaw() {
|
||||
local app="$1"
|
||||
local failures=0
|
||||
|
||||
# Binary check (may be in ~/.local/bin or ~/.cargo/bin depending on install method)
|
||||
log_step "Checking zeroclaw binary..."
|
||||
if cloud_exec "${app}" "export PATH=\$HOME/.local/bin:\$HOME/.cargo/bin:\$PATH; source ~/.cargo/env 2>/dev/null; command -v zeroclaw" >/dev/null 2>&1; then
|
||||
log_ok "zeroclaw binary found"
|
||||
else
|
||||
log_err "zeroclaw binary not found"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
# Env check: ZEROCLAW_PROVIDER
|
||||
log_step "Checking zeroclaw env (ZEROCLAW_PROVIDER)..."
|
||||
if cloud_exec "${app}" "grep -q ZEROCLAW_PROVIDER ~/.spawnrc" >/dev/null 2>&1; then
|
||||
log_ok "ZEROCLAW_PROVIDER present in .spawnrc"
|
||||
else
|
||||
log_err "ZEROCLAW_PROVIDER not found in .spawnrc"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
# Env check: provider is openrouter
|
||||
log_step "Checking zeroclaw uses openrouter..."
|
||||
if cloud_exec "${app}" "grep ZEROCLAW_PROVIDER ~/.spawnrc | grep -q openrouter" >/dev/null 2>&1; then
|
||||
log_ok "ZEROCLAW_PROVIDER set to openrouter"
|
||||
else
|
||||
log_err "ZEROCLAW_PROVIDER not set to openrouter"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
return "${failures}"
|
||||
}
|
||||
|
||||
verify_codex() {
|
||||
local app="$1"
|
||||
local failures=0
|
||||
|
|
@ -810,7 +741,6 @@ verify_agent() {
|
|||
case "${agent}" in
|
||||
claude) verify_claude "${app}" || agent_failures=$? ;;
|
||||
openclaw) verify_openclaw "${app}" || agent_failures=$? ;;
|
||||
zeroclaw) verify_zeroclaw "${app}" || agent_failures=$? ;;
|
||||
codex) verify_codex "${app}" || agent_failures=$? ;;
|
||||
opencode) verify_opencode "${app}" || agent_failures=$? ;;
|
||||
kilocode) verify_kilocode "${app}" || agent_failures=$? ;;
|
||||
|
|
|
|||
|
|
@ -18,12 +18,6 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/gcp/claude.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/gcp/openclaw.sh)
|
||||
```
|
||||
|
||||
#### ZeroClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/gcp/zeroclaw.sh)
|
||||
```
|
||||
|
||||
#### Codex CLI
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled gcp.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/gcp/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/gcp/main.ts" zeroclaw "$@"
|
||||
fi
|
||||
|
||||
# Remote — download bundled gcp.js from GitHub release
|
||||
GCP_JS=$(mktemp)
|
||||
trap 'rm -f "$GCP_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/gcp-latest/gcp.js" -o "$GCP_JS" \
|
||||
|| { printf '\033[0;31mFailed to download gcp.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
exec bun run "$GCP_JS" zeroclaw "$@"
|
||||
|
|
@ -16,12 +16,6 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/hetzner/claude.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/hetzner/openclaw.sh)
|
||||
```
|
||||
|
||||
#### ZeroClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/hetzner/zeroclaw.sh)
|
||||
```
|
||||
|
||||
#### Codex CLI
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/hetzner/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/hetzner/main.ts" zeroclaw "$@"
|
||||
fi
|
||||
|
||||
HETZNER_JS=$(mktemp)
|
||||
trap 'rm -f "$HETZNER_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/hetzner-latest/hetzner.js" -o "$HETZNER_JS" \
|
||||
|| { printf '\033[0;31mFailed to download hetzner.js\033[0m\n' >&2; exit 1; }
|
||||
exec bun run "$HETZNER_JS" zeroclaw "$@"
|
||||
|
|
@ -11,7 +11,6 @@ If you have the [spawn CLI](https://github.com/OpenRouterTeam/spawn) installed:
|
|||
```bash
|
||||
spawn claude local
|
||||
spawn openclaw local
|
||||
spawn zeroclaw local
|
||||
spawn codex local
|
||||
spawn opencode local
|
||||
spawn kilocode local
|
||||
|
|
@ -25,7 +24,6 @@ Or run directly without the CLI:
|
|||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/claude.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/openclaw.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/zeroclaw.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/codex.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/opencode.sh)
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/local/kilocode.sh)
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled local.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/local/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/local/main.ts" zeroclaw "$@"
|
||||
fi
|
||||
|
||||
# Remote — download bundled local.js from GitHub release
|
||||
LOCAL_JS=$(mktemp)
|
||||
trap 'rm -f "$LOCAL_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/local-latest/local.js" -o "$LOCAL_JS" \
|
||||
|| { printf '\033[0;31mFailed to download local.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
exec bun run "$LOCAL_JS" zeroclaw "$@"
|
||||
|
|
@ -16,12 +16,6 @@ bash <(curl -fsSL https://openrouter.ai/labs/spawn/sprite/claude.sh)
|
|||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/sprite/openclaw.sh)
|
||||
```
|
||||
|
||||
#### ZeroClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/labs/spawn/sprite/zeroclaw.sh)
|
||||
```
|
||||
|
||||
#### Codex CLI
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Thin shim: ensures bun is available, runs bundled sprite.js (local or from GitHub release)
|
||||
|
||||
_ensure_bun() {
|
||||
if command -v bun &>/dev/null; then return 0; fi
|
||||
printf '\033[0;36mInstalling bun...\033[0m\n' >&2
|
||||
curl -fsSL --proto '=https' --show-error https://bun.sh/install?version=1.3.9 | bash >/dev/null || { printf '\033[0;31mFailed to install bun\033[0m\n' >&2; exit 1; }
|
||||
export PATH="$HOME/.bun/bin:$PATH"
|
||||
command -v bun &>/dev/null || { printf '\033[0;31mbun not found after install\033[0m\n' >&2; exit 1; }
|
||||
}
|
||||
|
||||
_ensure_bun
|
||||
|
||||
# SPAWN_CLI_DIR override — force local source (used by e2e tests)
|
||||
if [[ -n "${SPAWN_CLI_DIR:-}" && -f "$SPAWN_CLI_DIR/packages/cli/src/sprite/main.ts" ]]; then
|
||||
exec bun run "$SPAWN_CLI_DIR/packages/cli/src/sprite/main.ts" zeroclaw "$@"
|
||||
fi
|
||||
|
||||
# Remote — download bundled sprite.js from GitHub release
|
||||
SPRITE_JS=$(mktemp)
|
||||
trap 'rm -f "$SPRITE_JS"' EXIT
|
||||
curl -fsSL --proto '=https' "https://github.com/OpenRouterTeam/spawn/releases/download/sprite-latest/sprite.js" -o "$SPRITE_JS" \
|
||||
|| { printf '\033[0;31mFailed to download sprite.js\033[0m\n' >&2; exit 1; }
|
||||
|
||||
exec bun run "$SPRITE_JS" zeroclaw "$@"
|
||||
|
|
@ -106,8 +106,8 @@ result=$(make_app_name "openclaw")
|
|||
assert_match "make_app_name openclaw (aws)" '^e2e-aws-openclaw-[0-9]+$' "${result}"
|
||||
|
||||
ACTIVE_CLOUD="sprite"
|
||||
result=$(make_app_name "zeroclaw")
|
||||
assert_match "make_app_name zeroclaw (sprite)" '^e2e-sprite-zeroclaw-[0-9]+$' "${result}"
|
||||
result=$(make_app_name "codex")
|
||||
assert_match "make_app_name codex (sprite)" '^e2e-sprite-codex-[0-9]+$' "${result}"
|
||||
|
||||
# Reset
|
||||
ACTIVE_CLOUD=""
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents
|
|||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
```
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Agents:** claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}`
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents
|
|||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
```
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Agents:** claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}`
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
You have the `spawn` CLI for creating child cloud VMs with AI agents.
|
||||
Use `spawn <agent> <cloud> --headless --output json --prompt "task"` to delegate work.
|
||||
Available agents: claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie.
|
||||
Available agents: claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie.
|
||||
Cloud credentials are pre-configured. Run `spawn list --json` to see children.
|
||||
`--headless` only provisions. To run a prompt on the child: `ssh root@<ip> "bash -lc 'claude -p --dangerously-skip-permissions \"prompt\"'"`. Always use `bash -lc` (binaries are in ~/.local/bin/).
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents
|
|||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
```
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Agents:** claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}`
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents
|
|||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
```
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Agents:** claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}`
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents
|
|||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
```
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Agents:** claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}`
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents
|
|||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
```
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Agents:** claude, codex, cursor, openclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}`
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
# Spawn — Create Child VMs
|
||||
|
||||
You have the `spawn` CLI installed. Use it to provision cloud VMs with AI agents.
|
||||
|
||||
## Provision a Child VM
|
||||
|
||||
```bash
|
||||
spawn <agent> <cloud> --headless --output json --prompt "task description"
|
||||
```
|
||||
|
||||
**Agents:** claude, codex, cursor, openclaw, zeroclaw, opencode, kilocode, hermes, junie
|
||||
**Clouds:** hetzner, digitalocean, aws, gcp, sprite
|
||||
|
||||
Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_id":"..."}`
|
||||
|
||||
## Running Prompts on Child VMs
|
||||
|
||||
`--headless` only provisions — to run work on the child, SSH in with `bash -lc`:
|
||||
|
||||
```bash
|
||||
# Claude Code (use -p, NOT --print or --headless)
|
||||
ssh -o StrictHostKeyChecking=accept-new root@<ip> "bash -lc 'claude -p --dangerously-skip-permissions \"your prompt\"'"
|
||||
|
||||
# Codex CLI
|
||||
ssh -o StrictHostKeyChecking=accept-new root@<ip> "bash -lc 'codex --quiet \"your prompt\"'"
|
||||
```
|
||||
|
||||
### IMPORTANT — do NOT waste tokens rediscovering these:
|
||||
- **Always use `bash -lc`** — agent binaries are in `~/.local/bin/` which is only on PATH in login shells
|
||||
- **Claude uses `-p`** for non-interactive output (NOT `--print`, NOT `--headless`)
|
||||
- **Add `--dangerously-skip-permissions`** to skip approval prompts on child VMs
|
||||
- **Never try `which claude` or `find`** to locate binaries — they are always at `~/.local/bin/<agent>`
|
||||
- **Never create non-root users** to work around permission issues — just use `-p`
|
||||
|
||||
## Managing Children
|
||||
|
||||
- `spawn list --json` — see running children
|
||||
- `spawn delete --name <name> --yes` — tear down a child VM (headless)
|
||||
- `spawn tree` — see the full spawn tree
|
||||
|
||||
## Context
|
||||
|
||||
- You are running inside a spawned VM (SPAWN_DEPTH is set)
|
||||
- Cloud credentials are pre-configured — no auth prompts
|
||||
- OpenRouter billing is shared with the parent
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue