Drop nounset (set -u) flag — incompatible with env var checks (#27)

The autonomous refactoring added `set -euo pipefail` but the scripts
check optional env vars with `[[ -n "$VAR" ]]` which is a fatal error
under nounset when the var isn't set (e.g. SPRITE_NAME, OPENROUTER_API_KEY).

Fix: downgrade to `set -eo pipefail` across all 42 affected files.

Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
L 2026-02-07 16:22:04 -08:00 committed by GitHub
parent 7e952d1310
commit 4087deb14e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 776 additions and 64 deletions

View file

@ -88,6 +88,14 @@ Research cloud providers with API-based provisioning. To add one:
```
spawn/
cli/
src/index.ts # CLI entry point (bun/TypeScript)
src/manifest.ts # Manifest fetch + cache logic
src/commands.ts # All subcommands (interactive, list, run, etc.)
src/version.ts # Version constant
package.json # npm package (@openrouter/spawn)
install.sh # One-liner installer (bun → npm → bash fallback)
spawn.sh # Bash fallback CLI (no bun/node required)
shared/
common.sh # Provider-agnostic shared utilities
{cloud}/

6
cli/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
node_modules/
cli.js
spawn
dist/
bun.lock
*.tgz

View file

@ -4,42 +4,114 @@
# Usage:
# curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cli/install.sh | bash
#
# Override install directory:
# This installs spawn via bun (preferred) or npm. If neither is available,
# it falls back to downloading the bundled JS file and creating a runner script.
#
# Override install directory (for fallback method):
# SPAWN_INSTALL_DIR=/usr/local/bin curl -fsSL ... | bash
set -euo pipefail
set -eo pipefail
SPAWN_REPO="OpenRouterTeam/spawn"
SPAWN_RAW_BASE="https://raw.githubusercontent.com/$SPAWN_REPO/main"
INSTALL_DIR="${SPAWN_INSTALL_DIR:-$HOME/.local/bin}"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[spawn]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[spawn]${NC} $1"; }
log_error() { echo -e "${RED}[spawn]${NC} $1"; }
# Check curl
if ! command -v curl &>/dev/null; then
log_error "curl is required but not found"
exit 1
# --- Method 1: bun install -g (preferred) ---
if command -v bun &>/dev/null; then
log_info "Installing spawn via bun..."
# Clone/download the cli directory and install from it
tmpdir=$(mktemp -d)
trap "rm -rf '$tmpdir'" EXIT
log_info "Downloading CLI package..."
mkdir -p "$tmpdir/cli/src"
curl -fsSL "$SPAWN_RAW_BASE/cli/package.json" -o "$tmpdir/cli/package.json"
curl -fsSL "$SPAWN_RAW_BASE/cli/tsconfig.json" -o "$tmpdir/cli/tsconfig.json"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/index.ts" -o "$tmpdir/cli/src/index.ts"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/manifest.ts" -o "$tmpdir/cli/src/manifest.ts"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/commands.ts" -o "$tmpdir/cli/src/commands.ts"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/version.ts" -o "$tmpdir/cli/src/version.ts"
cd "$tmpdir/cli"
bun install
bun link 2>/dev/null || bun install -g . 2>/dev/null || {
# If global install fails, build and copy binary
log_warn "Global install failed, building binary..."
bun build src/index.ts --compile --outfile spawn
INSTALL_DIR="${SPAWN_INSTALL_DIR:-$HOME/.local/bin}"
mkdir -p "$INSTALL_DIR"
mv spawn "$INSTALL_DIR/spawn"
log_info "Installed spawn binary to $INSTALL_DIR/spawn"
}
log_info "spawn installed successfully!"
echo ""
if command -v spawn &>/dev/null; then
spawn version
echo ""
log_info "Run ${BOLD}spawn${NC}${GREEN} to get started${NC}"
fi
exit 0
fi
# Create install directory
# --- Method 2: npm install -g ---
if command -v npm &>/dev/null && command -v node &>/dev/null; then
log_info "Installing spawn via npm..."
tmpdir=$(mktemp -d)
trap "rm -rf '$tmpdir'" EXIT
log_info "Downloading CLI package..."
mkdir -p "$tmpdir/cli/src"
curl -fsSL "$SPAWN_RAW_BASE/cli/package.json" -o "$tmpdir/cli/package.json"
curl -fsSL "$SPAWN_RAW_BASE/cli/tsconfig.json" -o "$tmpdir/cli/tsconfig.json"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/index.ts" -o "$tmpdir/cli/src/index.ts"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/manifest.ts" -o "$tmpdir/cli/src/manifest.ts"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/commands.ts" -o "$tmpdir/cli/src/commands.ts"
curl -fsSL "$SPAWN_RAW_BASE/cli/src/version.ts" -o "$tmpdir/cli/src/version.ts"
cd "$tmpdir/cli"
npm install
npm install -g . 2>/dev/null || {
log_warn "npm global install requires permissions. Try:"
echo ""
echo " sudo npm install -g ."
echo ""
exit 1
}
log_info "spawn installed successfully!"
echo ""
if command -v spawn &>/dev/null; then
spawn version
echo ""
log_info "Run ${BOLD}spawn${NC}${GREEN} to get started${NC}"
fi
exit 0
fi
# --- Method 3: Direct download fallback (bash wrapper) ---
log_warn "Neither bun nor npm found. Installing bash fallback..."
INSTALL_DIR="${SPAWN_INSTALL_DIR:-$HOME/.local/bin}"
mkdir -p "$INSTALL_DIR"
# Download spawn CLI
log_info "Downloading spawn CLI..."
if ! curl -fsSL "$SPAWN_RAW_BASE/cli/spawn.sh" -o "$INSTALL_DIR/spawn"; then
log_error "Failed to download spawn CLI"
exit 1
fi
chmod +x "$INSTALL_DIR/spawn"
log_info "Installed spawn to $INSTALL_DIR/spawn"
log_info "Installed spawn (bash) to $INSTALL_DIR/spawn"
# Check if install dir is in PATH
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then
@ -47,20 +119,12 @@ if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then
echo ""
echo "Add it by running one of:"
echo ""
# Detect shell and suggest appropriate config
case "${SHELL:-/bin/bash}" in
*/zsh)
echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.zshrc && source ~/.zshrc"
;;
*/fish)
echo " fish_add_path $INSTALL_DIR"
;;
*)
echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.bashrc && source ~/.bashrc"
;;
*/zsh) echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.zshrc && source ~/.zshrc" ;;
*/fish) echo " fish_add_path $INSTALL_DIR" ;;
*) echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.bashrc && source ~/.bashrc" ;;
esac
echo ""
else
log_info "Run 'spawn' to get started"
log_info "Run ${BOLD}spawn${NC}${GREEN} to get started${NC}"
fi

20
cli/package.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "@openrouter/spawn",
"version": "0.1.0",
"type": "module",
"bin": {
"spawn": "cli.js"
},
"scripts": {
"dev": "bun run src/index.ts",
"build": "bun build src/index.ts --outfile cli.js --target node --minify",
"compile": "bun build src/index.ts --compile --outfile spawn"
},
"dependencies": {
"@clack/prompts": "^0.10.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"@types/bun": "^1.2.0"
}
}

371
cli/src/commands.ts Normal file
View file

@ -0,0 +1,371 @@
import * as p from "@clack/prompts";
import pc from "picocolors";
import { spawn } from "child_process";
import {
loadManifest,
agentKeys,
cloudKeys,
matrixStatus,
countImplemented,
RAW_BASE,
REPO,
CACHE_DIR,
type Manifest,
} from "./manifest.js";
import { VERSION } from "./version.js";
// ── Helpers ────────────────────────────────────────────────────────────────────
function handleCancel(): never {
p.cancel("Cancelled.");
process.exit(0);
}
async function withSpinner<T>(msg: string, fn: () => Promise<T>): Promise<T> {
const s = p.spinner();
s.start(msg);
try {
const result = await fn();
s.stop(msg);
return result;
} catch (err) {
s.stop(pc.red("Failed"));
throw err;
}
}
// ── Interactive ────────────────────────────────────────────────────────────────
export async function cmdInteractive() {
p.intro(pc.inverse(` spawn v${VERSION} `));
const manifest = await withSpinner("Loading manifest...", loadManifest);
const agents = agentKeys(manifest);
const agentChoice = await p.select({
message: "Select an agent",
options: agents.map((key) => ({
value: key,
label: manifest.agents[key].name,
hint: manifest.agents[key].description,
})),
});
if (p.isCancel(agentChoice)) handleCancel();
// Only show clouds where this agent is implemented
const clouds = cloudKeys(manifest).filter(
(c) => matrixStatus(manifest, c, agentChoice) === "implemented"
);
if (clouds.length === 0) {
p.log.error(`No clouds available for ${manifest.agents[agentChoice].name}`);
process.exit(1);
}
const cloudChoice = await p.select({
message: "Select a cloud provider",
options: clouds.map((key) => ({
value: key,
label: manifest.clouds[key].name,
hint: manifest.clouds[key].description,
})),
});
if (p.isCancel(cloudChoice)) handleCancel();
const agentName = manifest.agents[agentChoice].name;
const cloudName = manifest.clouds[cloudChoice].name;
p.log.step(`Launching ${pc.bold(agentName)} on ${pc.bold(cloudName)}`);
p.outro("Handing off to spawn script...");
await execScript(cloudChoice, agentChoice);
}
// ── Run ────────────────────────────────────────────────────────────────────────
export async function cmdRun(agent: string, cloud: string) {
const manifest = await withSpinner("Loading manifest...", loadManifest);
if (!manifest.agents[agent]) {
p.log.error(`Unknown agent: ${pc.bold(agent)}`);
p.log.info(`Run ${pc.cyan("spawn agents")} to see available agents.`);
process.exit(1);
}
if (!manifest.clouds[cloud]) {
p.log.error(`Unknown cloud: ${pc.bold(cloud)}`);
p.log.info(`Run ${pc.cyan("spawn clouds")} to see available clouds.`);
process.exit(1);
}
const status = matrixStatus(manifest, cloud, agent);
if (status !== "implemented") {
p.log.error(
`${manifest.agents[agent].name} on ${manifest.clouds[cloud].name} is not yet implemented.`
);
process.exit(1);
}
const agentName = manifest.agents[agent].name;
const cloudName = manifest.clouds[cloud].name;
p.log.step(`Launching ${pc.bold(agentName)} on ${pc.bold(cloudName)}...`);
await execScript(cloud, agent);
}
async function execScript(cloud: string, agent: string): Promise<void> {
const url = `https://openrouter.ai/lab/spawn/${cloud}/${agent}.sh`;
// Download script then execute, preserving stdin/stdout/stderr for interactive use
const res = await fetch(url);
if (!res.ok) {
// Fallback to GitHub raw
const ghUrl = `${RAW_BASE}/${cloud}/${agent}.sh`;
const ghRes = await fetch(ghUrl);
if (!ghRes.ok) {
p.log.error(`Failed to download script from ${url}`);
process.exit(1);
}
await runBash(await ghRes.text());
return;
}
await runBash(await res.text());
}
function runBash(script: string): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn("bash", ["-c", script], {
stdio: "inherit",
env: process.env,
});
child.on("close", (code) => {
if (code === 0) resolve();
else reject(new Error(`Script exited with code ${code}`));
});
child.on("error", reject);
});
}
// ── List ───────────────────────────────────────────────────────────────────────
export async function cmdList() {
const manifest = await withSpinner("Loading manifest...", loadManifest);
const agents = agentKeys(manifest);
const clouds = cloudKeys(manifest);
// Calculate column widths
const agentColWidth = Math.max(16, ...agents.map((a) => manifest.agents[a].name.length + 2));
const cloudColWidth = Math.max(
10,
...clouds.map((c) => manifest.clouds[c].name.length + 2)
);
// Header
let header = "".padEnd(agentColWidth);
for (const c of clouds) {
header += pc.bold(manifest.clouds[c].name.padEnd(cloudColWidth));
}
console.log();
console.log(header);
// Separator
let sep = "".padEnd(agentColWidth);
for (const _ of clouds) {
sep += pc.dim("─".repeat(cloudColWidth - 2) + " ");
}
console.log(sep);
// Rows
for (const a of agents) {
let row = pc.bold(manifest.agents[a].name.padEnd(agentColWidth));
for (const c of clouds) {
const status = matrixStatus(manifest, c, a);
if (status === "implemented") {
row += pc.green(" \u2713".padEnd(cloudColWidth));
} else {
row += pc.dim(" \u2013".padEnd(cloudColWidth));
}
}
console.log(row);
}
// Summary
const impl = countImplemented(manifest);
const total = agents.length * clouds.length;
console.log();
console.log(pc.green(`${impl}/${total} combinations implemented`));
console.log();
}
// ── Agents ─────────────────────────────────────────────────────────────────────
export async function cmdAgents() {
const manifest = await withSpinner("Loading manifest...", loadManifest);
console.log();
console.log(pc.bold("Agents"));
console.log();
for (const key of agentKeys(manifest)) {
const a = manifest.agents[key];
console.log(` ${pc.green(a.name.padEnd(18))} ${pc.dim(a.description)}`);
}
console.log();
}
// ── Clouds ─────────────────────────────────────────────────────────────────────
export async function cmdClouds() {
const manifest = await withSpinner("Loading manifest...", loadManifest);
console.log();
console.log(pc.bold("Cloud Providers"));
console.log();
for (const key of cloudKeys(manifest)) {
const c = manifest.clouds[key];
console.log(` ${pc.green(c.name.padEnd(18))} ${pc.dim(c.description)}`);
}
console.log();
}
// ── Agent Info ─────────────────────────────────────────────────────────────────
export async function cmdAgentInfo(agent: string) {
const manifest = await withSpinner("Loading manifest...", loadManifest);
if (!manifest.agents[agent]) {
p.log.error(`Unknown agent: ${pc.bold(agent)}`);
p.log.info(`Run ${pc.cyan("spawn agents")} to see available agents.`);
process.exit(1);
}
const a = manifest.agents[agent];
console.log();
console.log(`${pc.bold(a.name)} ${pc.dim("\u2014")} ${a.description}`);
console.log();
console.log(pc.bold("Available clouds:"));
console.log();
let found = false;
for (const cloud of cloudKeys(manifest)) {
const status = matrixStatus(manifest, cloud, agent);
if (status === "implemented") {
const c = manifest.clouds[cloud];
console.log(` ${pc.green(c.name.padEnd(18))} ${pc.dim("spawn " + agent + " " + cloud)}`);
found = true;
}
}
if (!found) {
console.log(pc.dim(" No implemented clouds yet."));
}
console.log();
}
// ── Improve ────────────────────────────────────────────────────────────────────
export async function cmdImprove(args: string[]) {
const { existsSync: exists } = await import("fs");
let repoDir: string;
// Check if we're in a spawn checkout
if (exists("./improve.sh") && exists("./manifest.json")) {
repoDir = ".";
} else {
const { join } = await import("path");
repoDir = join(CACHE_DIR, "repo");
if (exists(join(repoDir, ".git"))) {
p.log.step("Updating spawn repo...");
const { execSync } = await import("child_process");
try {
execSync("git pull --ff-only", { cwd: repoDir, stdio: "pipe" });
} catch {
// ignore pull failures
}
} else {
p.log.step("Cloning spawn repo...");
const { execSync } = await import("child_process");
execSync(`git clone https://github.com/${REPO}.git ${repoDir}`, { stdio: "inherit" });
}
}
return new Promise<void>((resolve, reject) => {
const child = spawn("bash", ["improve.sh", ...args], {
cwd: repoDir,
stdio: "inherit",
env: process.env,
});
child.on("close", (code) => {
if (code === 0) resolve();
else reject(new Error(`improve.sh exited with code ${code}`));
});
child.on("error", reject);
});
}
// ── Update ─────────────────────────────────────────────────────────────────────
export async function cmdUpdate() {
const s = p.spinner();
s.start("Checking for updates...");
try {
const res = await fetch(`${RAW_BASE}/cli/package.json`, {
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) throw new Error("fetch failed");
const pkg = (await res.json()) as { version: string };
const remoteVersion = pkg.version;
if (remoteVersion === VERSION) {
s.stop(`Already up to date ${pc.dim(`(v${VERSION})`)}`);
return;
}
s.message(`Updating v${VERSION} \u2192 v${remoteVersion}...`);
// Run the install script to update
const installRes = await fetch(`${RAW_BASE}/cli/install.sh`);
if (!installRes.ok) throw new Error("fetch install.sh failed");
const installScript = await installRes.text();
s.stop(`Update available: v${VERSION} \u2192 v${remoteVersion}`);
p.log.info(`Run this to update:`);
console.log();
console.log(
` ${pc.cyan(`curl -fsSL ${RAW_BASE}/cli/install.sh | bash`)}`
);
console.log();
} catch {
s.stop(pc.red("Failed to check for updates"));
}
}
// ── Help ───────────────────────────────────────────────────────────────────────
export function cmdHelp() {
console.log(`
${pc.bold("spawn")} \u2014 Launch any AI coding agent on any cloud
${pc.bold("USAGE")}
spawn Interactive agent + cloud picker
spawn <agent> <cloud> Launch agent on cloud directly
spawn <agent> Show available clouds for agent
spawn list Full matrix table
spawn agents List all agents with descriptions
spawn clouds List all cloud providers
spawn improve [--loop] Run improvement system
spawn update Check for CLI updates
spawn version Show version
${pc.bold("EXAMPLES")}
spawn ${pc.dim("# Pick interactively")}
spawn claude sprite ${pc.dim("# Launch Claude Code on Sprite")}
spawn aider hetzner ${pc.dim("# Launch Aider on Hetzner Cloud")}
spawn claude ${pc.dim("# Show which clouds support Claude")}
spawn list ${pc.dim("# See the full agent x cloud matrix")}
${pc.bold("INSTALL")}
curl -fsSL ${RAW_BASE}/cli/install.sh | bash
`);
}

95
cli/src/index.ts Normal file
View file

@ -0,0 +1,95 @@
#!/usr/bin/env bun
import {
cmdInteractive,
cmdRun,
cmdList,
cmdAgents,
cmdClouds,
cmdAgentInfo,
cmdImprove,
cmdUpdate,
cmdHelp,
} from "./commands.js";
import { loadManifest } from "./manifest.js";
import { VERSION } from "./version.js";
async function main() {
const args = process.argv.slice(2);
const cmd = args[0];
try {
if (!cmd) {
// No args + interactive terminal → picker
if (process.stdin.isTTY && process.stdout.isTTY) {
await cmdInteractive();
} else {
cmdHelp();
}
return;
}
switch (cmd) {
case "help":
case "--help":
case "-h":
cmdHelp();
break;
case "version":
case "--version":
case "-v":
case "-V":
console.log(`spawn v${VERSION}`);
break;
case "list":
case "ls":
await cmdList();
break;
case "agents":
await cmdAgents();
break;
case "clouds":
await cmdClouds();
break;
case "improve":
await cmdImprove(args.slice(1));
break;
case "update":
await cmdUpdate();
break;
default: {
// spawn <agent> or spawn <agent> <cloud>
const agent = args[0];
const cloud = args[1];
// Quick check: is this a known agent?
const manifest = await loadManifest();
if (!manifest.agents[agent]) {
console.error(`Unknown command or agent: ${agent}`);
console.error(`Run 'spawn help' for usage.`);
process.exit(1);
}
if (cloud) {
await cmdRun(agent, cloud);
} else {
await cmdAgentInfo(agent);
}
break;
}
}
} catch (err) {
if (err instanceof Error) {
console.error(`Error: ${err.message}`);
}
process.exit(1);
}
}
main();

133
cli/src/manifest.ts Normal file
View file

@ -0,0 +1,133 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from "fs";
import { join } from "path";
import { homedir } from "os";
// ── Types ──────────────────────────────────────────────────────────────────────
export interface AgentDef {
name: string;
description: string;
url: string;
install: string;
launch: string;
env: Record<string, string>;
pre_launch?: string;
deps?: string[];
config_files?: Record<string, unknown>;
interactive_prompts?: Record<string, { prompt: string; default: string }>;
dotenv?: { path: string; values: Record<string, string> };
notes?: string;
}
export interface CloudDef {
name: string;
description: string;
url: string;
type: string;
auth: string;
provision_method: string;
exec_method: string;
interactive_method: string;
defaults?: Record<string, unknown>;
notes?: string;
}
export interface Manifest {
agents: Record<string, AgentDef>;
clouds: Record<string, CloudDef>;
matrix: Record<string, string>;
}
// ── Constants ──────────────────────────────────────────────────────────────────
const REPO = "OpenRouterTeam/spawn";
const RAW_BASE = `https://raw.githubusercontent.com/${REPO}/main`;
const CACHE_DIR = join(process.env.XDG_CACHE_HOME || join(homedir(), ".cache"), "spawn");
const CACHE_FILE = join(CACHE_DIR, "manifest.json");
const CACHE_TTL = 3600; // 1 hour in seconds
// ── Cache helpers ──────────────────────────────────────────────────────────────
function cacheAge(): number {
try {
const st = statSync(CACHE_FILE);
return (Date.now() - st.mtimeMs) / 1000;
} catch {
return Infinity;
}
}
function readCache(): Manifest | null {
try {
return JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
} catch {
return null;
}
}
function writeCache(data: Manifest): void {
mkdirSync(CACHE_DIR, { recursive: true });
writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
}
// ── Public API ─────────────────────────────────────────────────────────────────
let _cached: Manifest | null = null;
export async function loadManifest(forceRefresh = false): Promise<Manifest> {
if (_cached && !forceRefresh) return _cached;
// Check disk cache first
if (!forceRefresh && cacheAge() < CACHE_TTL) {
const cached = readCache();
if (cached) {
_cached = cached;
return cached;
}
}
// Fetch from GitHub
try {
const res = await fetch(`${RAW_BASE}/manifest.json`, {
signal: AbortSignal.timeout(10_000),
});
if (res.ok) {
const data = (await res.json()) as Manifest;
// Validate basic structure
if (data.agents && data.clouds && data.matrix) {
writeCache(data);
_cached = data;
return data;
}
}
} catch {
// Network error — fall through to cache
}
// Offline fallback: use stale cache
const stale = readCache();
if (stale) {
_cached = stale;
return stale;
}
throw new Error("Cannot load manifest. Check your internet connection.");
}
export function agentKeys(m: Manifest): string[] {
return Object.keys(m.agents);
}
export function cloudKeys(m: Manifest): string[] {
return Object.keys(m.clouds);
}
export function matrixStatus(m: Manifest, cloud: string, agent: string): string {
return m.matrix[`${cloud}/${agent}`] ?? "missing";
}
export function countImplemented(m: Manifest): number {
return Object.values(m.matrix).filter((v) => v === "implemented").length;
}
export { RAW_BASE, REPO, CACHE_DIR };

1
cli/src/version.ts Normal file
View file

@ -0,0 +1 @@
export const VERSION = "0.1.0";

14
cli/tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"types": ["bun"]
},
"include": ["src"]
}

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -2,7 +2,7 @@
# Common bash functions for DigitalOcean spawn scripts
# Bash safety flags
set -euo pipefail
set -eo pipefail
# ============================================================
# Provider-agnostic functions

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Common bash functions for Hetzner Cloud spawn scripts
# ============================================================

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi

View file

@ -2,7 +2,7 @@
# Common bash functions for Linode (Akamai) spawn scripts
# Bash safety flags
set -euo pipefail
set -eo pipefail
# ============================================================
# Provider-agnostic functions

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then source "$SCRIPT_DIR/lib/common.sh"
else eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/linode/lib/common.sh)"; fi

View file

@ -3,7 +3,7 @@
# Provider-agnostic utilities for logging, input, OAuth, etc.
#
# This file is meant to be sourced by cloud provider-specific common.sh files.
# It does not set bash flags (like set -euo pipefail) as those should be set
# It does not set bash flags (like set -eo pipefail) as those should be set
# by the scripts that source this file.
# ============================================================

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Common bash functions shared between spawn scripts
# Source shared provider-agnostic functions (local or remote fallback)

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -2,7 +2,7 @@
# Common bash functions for Vultr spawn scripts
# Bash safety flags
set -euo pipefail
set -eo pipefail
# ============================================================
# Provider-agnostic functions

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then

View file

@ -1,5 +1,5 @@
#!/bin/bash
set -euo pipefail
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then