mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-11 05:00:07 +00:00
feat: remove OVH cloud and make featured_cloud an array (#1474)
- Remove OVH as a cloud provider: delete ovh/ directory (lib + 11 agent scripts), remove from manifest.json clouds and all ovh/* matrix entries, update README matrix table, remove OVH destroy case in CLI commands, and clean up all test harness references (mock.sh, mock-curl-script.sh, record.sh, e2e.sh, cloud-lib-api-surface.test.ts, test-infra-sync.test.ts) - Make featured_cloud an array (string[]) so agents can recommend multiple clouds; update manifest.ts type, all 10 manifest.json values, and the prioritizeCloudsByCredentials() comparison in commands.ts - Sandbox OAuth in subprocess tests: add OPENROUTER_API_KEY=sk-or-test-fake to the default env in cli-entry-edge-cases.test.ts and cmdrun-resolution.test.ts so get_or_prompt_api_key() never triggers the real OAuth browser flow during test runs - Fix upload-file-security.test.ts SSH cloud count (5→4) after OVH removal - Bump CLI version 0.5.6 → 0.5.7 Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5612cda40b
commit
32522882c1
26 changed files with 42 additions and 920 deletions
|
|
@ -37,6 +37,9 @@ function runCli(
|
|||
HOME: process.env.HOME,
|
||||
SHELL: process.env.SHELL,
|
||||
TERM: process.env.TERM || "xterm",
|
||||
// Prevent OAuth browser from opening during tests — if OPENROUTER_API_KEY
|
||||
// is set, get_or_prompt_api_key() skips the entire OAuth flow.
|
||||
OPENROUTER_API_KEY: "sk-or-test-fake",
|
||||
...env,
|
||||
SPAWN_NO_UPDATE_CHECK: "1",
|
||||
NODE_ENV: "",
|
||||
|
|
|
|||
|
|
@ -74,12 +74,12 @@ function isSandboxOrContainer(cloud: string): boolean {
|
|||
}
|
||||
|
||||
/** Check if a function name matches a pattern, allowing cloud-prefixed variants.
|
||||
* e.g. hasFunctionOrVariant(fns, "run_server", "ovh") matches "run_server" or "run_ovh" */
|
||||
* e.g. hasFunctionOrVariant(fns, "run_server", "sprite") matches "run_server" or "run_sprite" */
|
||||
function hasFunctionOrVariant(functions: string[], baseName: string, cloud: string): boolean {
|
||||
if (functions.includes(baseName)) return true;
|
||||
// Check for cloud-prefixed variant (e.g. run_ovh, upload_file_sprite)
|
||||
// Check for cloud-prefixed variant (e.g. run_sprite, upload_file_sprite)
|
||||
const prefix = baseName.replace(/_server$/, "").replace(/_file$/, "");
|
||||
const variant1 = `${prefix}_${cloud}`; // run_ovh, upload_file_ovh
|
||||
const variant1 = `${prefix}_${cloud}`; // run_sprite, upload_file_sprite
|
||||
const variant2 = `${baseName}_${cloud}`; // upload_file_sprite
|
||||
return functions.includes(variant1) || functions.includes(variant2);
|
||||
}
|
||||
|
|
@ -161,11 +161,11 @@ describe("Cloud lib/common.sh API surface contracts", () => {
|
|||
|
||||
for (const fn of SSH_REQUIRED_FUNCTIONS) {
|
||||
it(`${cloud}/lib/common.sh defines ${fn}() or cloud-prefixed variant`, () => {
|
||||
// Some clouds (OVH, Sprite) use cloud-prefixed function names
|
||||
// e.g. run_ovh instead of run_server, create_ovh_instance instead of create_server
|
||||
// Some clouds use cloud-prefixed function names
|
||||
// e.g. run_sprite instead of run_server, create_sprite_instance instead of create_server
|
||||
const hasStandard = functions.includes(fn);
|
||||
const hasVariant = hasFunctionOrVariant(functions, fn, cloud);
|
||||
// Also check for <action>_<cloud>_<noun> patterns (create_ovh_instance)
|
||||
// Also check for <action>_<cloud>_<noun> patterns (create_sprite_instance)
|
||||
const hasExtendedVariant = functions.some((f) => {
|
||||
const prefix = fn.split("_")[0]; // "create", "run", "upload", etc.
|
||||
return f.startsWith(`${prefix}_${cloud}`);
|
||||
|
|
@ -526,25 +526,6 @@ describe("Cloud lib/common.sh API surface contracts", () => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── OVH special case (uses function prefixing) ─────────────────────
|
||||
|
||||
describe("OVH cloud special API pattern", () => {
|
||||
const content = readCloudLib("ovh");
|
||||
if (!content) return;
|
||||
const functions = extractFunctionNames(content);
|
||||
|
||||
it("OVH lib defines signature-based auth functions", () => {
|
||||
// OVH uses a custom auth pattern with signatures
|
||||
const hasSigAuth = functions.some(
|
||||
(fn) =>
|
||||
fn.includes("sign") ||
|
||||
fn.includes("ovh_api") ||
|
||||
fn.includes("_signature")
|
||||
);
|
||||
expect(hasSigAuth).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Sprite special case (CLI-based, no standard SSH) ───────────────
|
||||
|
||||
describe("Sprite cloud special CLI pattern", () => {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ function runCli(
|
|||
HOME: process.env.HOME,
|
||||
SHELL: process.env.SHELL,
|
||||
TERM: process.env.TERM || "xterm",
|
||||
// Prevent OAuth browser from opening during tests — if OPENROUTER_API_KEY
|
||||
// is set, get_or_prompt_api_key() skips the entire OAuth flow.
|
||||
OPENROUTER_API_KEY: "sk-or-test-fake",
|
||||
...env,
|
||||
SPAWN_NO_UPDATE_CHECK: "1",
|
||||
NODE_ENV: "",
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ function getCloudsInStripApiBase(): string[] {
|
|||
"console.kamatera.com": "kamatera",
|
||||
"api.latitude.sh": "latitude",
|
||||
"infrahub-api.nexgencloud.com": "hyperstack",
|
||||
"eu.api.ovh.com": "ovh",
|
||||
"cloudapi.atlantic.net": "atlanticnet",
|
||||
"invapi.hostkey.com": "hostkey",
|
||||
"cloudsigma.com": "cloudsigma",
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ describe("upload_file() Security Patterns", () => {
|
|||
.filter(([, info]) => info.type === "ssh");
|
||||
|
||||
it("should have multiple SSH-based clouds", () => {
|
||||
expect(sshClouds.length).toBeGreaterThanOrEqual(5);
|
||||
expect(sshClouds.length).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
|
||||
for (const [cloud, info] of sshClouds) {
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ export function hasCloudCli(cloud: string): boolean {
|
|||
export function prioritizeCloudsByCredentials(
|
||||
clouds: string[],
|
||||
manifest: Manifest,
|
||||
featuredCloud?: string
|
||||
featuredCloud?: string[]
|
||||
): { sortedClouds: string[]; hintOverrides: Record<string, string>; credCount: number; cliCount: number } {
|
||||
const withCreds: string[] = [];
|
||||
const featured: string[] = [];
|
||||
|
|
@ -362,7 +362,7 @@ export function prioritizeCloudsByCredentials(
|
|||
for (const c of clouds) {
|
||||
if (hasCloudCredentials(manifest.clouds[c].auth)) {
|
||||
withCreds.push(c);
|
||||
} else if (featuredCloud && c === featuredCloud) {
|
||||
} else if (featuredCloud && featuredCloud.includes(c)) {
|
||||
featured.push(c);
|
||||
} else if (hasCloudCli(c)) {
|
||||
withCli.push(c);
|
||||
|
|
@ -1817,8 +1817,6 @@ function buildDeleteScript(cloud: string, connection: VMConnection): string {
|
|||
case "aws":
|
||||
return `${sourceLib}\nensure_aws_cli\ndestroy_server "${id}"`;
|
||||
|
||||
case "ovh":
|
||||
return `${sourceLib}\nensure_ovh_authenticated\ndestroy_ovh_instance "${id}"`;
|
||||
case "daytona":
|
||||
return `${sourceLib}\nensure_daytona_cli\nensure_daytona_token\ndestroy_server "${id}"`;
|
||||
case "sprite":
|
||||
|
|
@ -2568,7 +2566,7 @@ function getHelpExamplesSection(): string {
|
|||
spawn claude sprite --prompt "Fix all linter errors"
|
||||
${pc.dim("# Execute Claude with prompt and exit")}
|
||||
spawn codex sprite -p "Add tests" ${pc.dim("# Short form of --prompt")}
|
||||
spawn openclaw ovh -f instructions.txt
|
||||
spawn openclaw fly -f instructions.txt
|
||||
${pc.dim("# Read prompt from file (short for --prompt-file)")}
|
||||
spawn gptme gcp --dry-run ${pc.dim("# Preview without provisioning")}
|
||||
spawn claude hetzner --headless ${pc.dim("# Provision, print connection info, exit")}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export interface AgentDef {
|
|||
interactive_prompts?: Record<string, { prompt: string; default: string }>;
|
||||
dotenv?: { path: string; values: Record<string, string> };
|
||||
notes?: string;
|
||||
featured_cloud?: string;
|
||||
featured_cloud?: string[];
|
||||
}
|
||||
|
||||
export interface CloudDef {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue