mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 03:49:31 +00:00
feat: headless delete via spawn delete --name <name> --yes (#3015)
Agents running on spawned VMs couldn't delete child spawns because `spawn delete` requires an interactive terminal for the picker UI. Added --name and --yes flags: when both are provided in non-interactive mode, the server matching the name is deleted without prompts. This enables agents to manage their own child VMs programmatically. Updated all skill files to teach agents the headless delete syntax. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: A <258483684+la14-1@users.noreply.github.com>
This commit is contained in:
parent
73bb52e2f5
commit
0f48e4dae5
11 changed files with 49 additions and 15 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@openrouter/spawn",
|
"name": "@openrouter/spawn",
|
||||||
"version": "0.26.11",
|
"version": "0.26.12",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"spawn": "cli.js"
|
"spawn": "cli.js"
|
||||||
|
|
|
||||||
|
|
@ -352,7 +352,12 @@ export async function cascadeDelete(record: SpawnRecord, manifest: Manifest | nu
|
||||||
return confirmAndDelete(record, manifest);
|
return confirmAndDelete(record, manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cmdDelete(agentFilter?: string, cloudFilter?: string): Promise<void> {
|
export async function cmdDelete(
|
||||||
|
agentFilter?: string,
|
||||||
|
cloudFilter?: string,
|
||||||
|
nameFilter?: string,
|
||||||
|
forceYes?: boolean,
|
||||||
|
): Promise<void> {
|
||||||
const resolved = await resolveListFilters(agentFilter, cloudFilter);
|
const resolved = await resolveListFilters(agentFilter, cloudFilter);
|
||||||
agentFilter = resolved.agentFilter;
|
agentFilter = resolved.agentFilter;
|
||||||
cloudFilter = resolved.cloudFilter;
|
cloudFilter = resolved.cloudFilter;
|
||||||
|
|
@ -368,6 +373,15 @@ export async function cmdDelete(agentFilter?: string, cloudFilter?: string): Pro
|
||||||
const lower = cloudFilter.toLowerCase();
|
const lower = cloudFilter.toLowerCase();
|
||||||
filtered = filtered.filter((r) => r.cloud.toLowerCase() === lower);
|
filtered = filtered.filter((r) => r.cloud.toLowerCase() === lower);
|
||||||
}
|
}
|
||||||
|
if (nameFilter) {
|
||||||
|
const lower = nameFilter.toLowerCase();
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(r) =>
|
||||||
|
(r.name ?? "").toLowerCase() === lower ||
|
||||||
|
(r.connection?.server_name ?? "").toLowerCase() === lower ||
|
||||||
|
r.id === nameFilter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
p.log.info("No active servers to delete.");
|
p.log.info("No active servers to delete.");
|
||||||
|
|
@ -387,10 +401,22 @@ export async function cmdDelete(agentFilter?: string, cloudFilter?: string): Pro
|
||||||
const manifestResult = await asyncTryCatchIf(isNetworkError, loadManifest);
|
const manifestResult = await asyncTryCatchIf(isNetworkError, loadManifest);
|
||||||
const manifest: Manifest | null = manifestResult.ok ? manifestResult.data : null;
|
const manifest: Manifest | null = manifestResult.ok ? manifestResult.data : null;
|
||||||
|
|
||||||
|
// Non-interactive headless delete: --name + --yes skips the picker
|
||||||
if (!isInteractiveTTY()) {
|
if (!isInteractiveTTY()) {
|
||||||
p.log.error("spawn delete requires an interactive terminal.");
|
if (!forceYes) {
|
||||||
p.log.info(`Use ${pc.cyan("spawn list")} to see your servers.`);
|
p.log.error("spawn delete requires --yes in non-interactive mode.");
|
||||||
process.exit(1);
|
p.log.info(`Usage: ${pc.cyan("spawn delete --name <name> --yes")}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
for (const record of filtered) {
|
||||||
|
const label = record.connection?.server_name || record.name || record.id;
|
||||||
|
await ensureDeleteCredentials(record);
|
||||||
|
const ok = await execDeleteServer(record);
|
||||||
|
if (ok) {
|
||||||
|
p.log.success(`Server "${label}" deleted`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await activeServerPicker(filtered, manifest);
|
await activeServerPicker(filtered, manifest);
|
||||||
|
|
|
||||||
|
|
@ -613,8 +613,16 @@ async function dispatchDeleteCommand(filteredArgs: string[]): Promise<void> {
|
||||||
cmdHelp();
|
cmdHelp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { agentFilter, cloudFilter } = parseListFilters(filteredArgs.slice(1));
|
const args = filteredArgs.slice(1);
|
||||||
await cmdDelete(agentFilter, cloudFilter);
|
const forceYes = args.includes("--yes") || args.includes("-y");
|
||||||
|
let nameFilter: string | undefined;
|
||||||
|
const nameIdx = args.indexOf("--name");
|
||||||
|
if (nameIdx !== -1 && args[nameIdx + 1]) {
|
||||||
|
nameFilter = args[nameIdx + 1];
|
||||||
|
}
|
||||||
|
const cleanArgs = args.filter((a) => a !== "--yes" && a !== "-y" && a !== "--name" && a !== nameFilter);
|
||||||
|
const { agentFilter, cloudFilter } = parseListFilters(cleanArgs);
|
||||||
|
await cmdDelete(agentFilter, cloudFilter, nameFilter, forceYes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handle status/ps commands with --prune and --json flags */
|
/** Handle status/ps commands with --prune and --json flags */
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ Returns JSON: \`{"status":"success","ip_address":"...","ssh_user":"root","server
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- \`spawn list --json\` — see running children
|
- \`spawn list --json\` — see running children
|
||||||
- \`spawn delete\` — tear down a child VM
|
- \`spawn delete --name <name> --yes\` — tear down a child VM (headless)
|
||||||
- \`spawn tree\` — see the full spawn tree
|
- \`spawn tree\` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- `spawn list --json` — see running children
|
- `spawn list --json` — see running children
|
||||||
- `spawn delete` — tear down a child VM
|
- `spawn delete --name <name> --yes` — tear down a child VM
|
||||||
- `spawn tree` — see the full spawn tree
|
- `spawn tree` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- `spawn list --json` — see running children
|
- `spawn list --json` — see running children
|
||||||
- `spawn delete` — tear down a child VM
|
- `spawn delete --name <name> --yes` — tear down a child VM
|
||||||
- `spawn tree` — see the full spawn tree
|
- `spawn tree` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- `spawn list --json` — see running children
|
- `spawn list --json` — see running children
|
||||||
- `spawn delete` — tear down a child VM
|
- `spawn delete --name <name> --yes` — tear down a child VM
|
||||||
- `spawn tree` — see the full spawn tree
|
- `spawn tree` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- `spawn list --json` — see running children
|
- `spawn list --json` — see running children
|
||||||
- `spawn delete` — tear down a child VM
|
- `spawn delete --name <name> --yes` — tear down a child VM
|
||||||
- `spawn tree` — see the full spawn tree
|
- `spawn tree` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- `spawn list --json` — see running children
|
- `spawn list --json` — see running children
|
||||||
- `spawn delete` — tear down a child VM
|
- `spawn delete --name <name> --yes` — tear down a child VM
|
||||||
- `spawn tree` — see the full spawn tree
|
- `spawn tree` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- `spawn list --json` — see running children
|
- `spawn list --json` — see running children
|
||||||
- `spawn delete` — tear down a child VM
|
- `spawn delete --name <name> --yes` — tear down a child VM
|
||||||
- `spawn tree` — see the full spawn tree
|
- `spawn tree` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Returns JSON: `{"status":"success","ip_address":"...","ssh_user":"root","server_
|
||||||
## Managing Children
|
## Managing Children
|
||||||
|
|
||||||
- `spawn list --json` — see running children
|
- `spawn list --json` — see running children
|
||||||
- `spawn delete` — tear down a child VM
|
- `spawn delete --name <name> --yes` — tear down a child VM
|
||||||
- `spawn tree` — see the full spawn tree
|
- `spawn tree` — see the full spawn tree
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue