mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-10 04:09:40 +00:00
refactor: extract error guidance data structures into separate module (#1335)
Extracted EXIT_CODE_GUIDANCE and SIGNAL_GUIDANCE from commands.ts into a new guidance-data.ts module. This reduces commands.ts complexity by 100+ lines, making error handling logic more maintainable and focused. Changes: - New file: cli/src/guidance-data.ts (116 lines) with error/signal guidance data - Refactored: commands.ts now 100 lines shorter, imports guidance data - Improved: Exit code 1 handling to avoid circular dependency with credentialHints The extracted module is a pure data file focused on error messages and guidance, separate from the command execution logic. Co-authored-by: spawn-bot <bot@openrouter.ai> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6be328c314
commit
2b87735e3d
2 changed files with 127 additions and 111 deletions
|
|
@ -19,6 +19,7 @@ import pkg from "../package.json" with { type: "json" };
|
|||
const VERSION = pkg.version;
|
||||
import { validateIdentifier, validateScriptContent, validatePrompt } from "./security.js";
|
||||
import { saveSpawnRecord, filterHistory, clearHistory, markRecordDeleted, getActiveServers, type SpawnRecord, type VMConnection } from "./history.js";
|
||||
import { buildDashboardHint, EXIT_CODE_GUIDANCE, SIGNAL_GUIDANCE, type ExitCodeEntry, type SignalEntry } from "./guidance-data.js";
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -915,116 +916,6 @@ export function credentialHints(cloud: string, authHint?: string, verb = "Missin
|
|||
return lines;
|
||||
}
|
||||
|
||||
function buildDashboardHint(dashboardUrl?: string): string {
|
||||
return dashboardUrl
|
||||
? ` - Check your dashboard: ${pc.cyan(dashboardUrl)}`
|
||||
: " - Check your cloud provider dashboard to stop or delete any unused servers";
|
||||
}
|
||||
|
||||
interface SignalEntry {
|
||||
header: string;
|
||||
causes: string[];
|
||||
includeDashboard: boolean;
|
||||
}
|
||||
|
||||
interface ExitCodeEntry {
|
||||
header: string;
|
||||
lines: string[];
|
||||
includeDashboard: boolean;
|
||||
specialHandling?: (cloud: string, authHint?: string, dashboardUrl?: string) => string[];
|
||||
}
|
||||
|
||||
const EXIT_CODE_GUIDANCE: Record<number, ExitCodeEntry> = {
|
||||
130: {
|
||||
header: "Script was interrupted (Ctrl+C).",
|
||||
lines: ["Note: If a server was already created, it may still be running."],
|
||||
includeDashboard: true,
|
||||
},
|
||||
137: {
|
||||
header: "Script was killed (likely by the system due to timeout or out of memory).",
|
||||
lines: [
|
||||
" - The server may not have enough RAM for this agent",
|
||||
" - Try a larger instance size or a different cloud provider",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
255: {
|
||||
header: "SSH connection failed. Common causes:",
|
||||
lines: [
|
||||
" - Server is still booting (wait a moment and retry)",
|
||||
" - Firewall blocking SSH port 22",
|
||||
" - Server was terminated before the session started",
|
||||
],
|
||||
includeDashboard: false,
|
||||
},
|
||||
127: {
|
||||
header: "A required command was not found. Check that these are installed:",
|
||||
lines: [" - bash, curl, ssh, jq"],
|
||||
includeDashboard: false,
|
||||
specialHandling: (cloud) => [` - Cloud-specific CLI tools (run ${pc.cyan(`spawn ${cloud}`)} for details)`],
|
||||
},
|
||||
126: {
|
||||
header: "A command was found but could not be executed (permission denied).",
|
||||
lines: [
|
||||
" - A downloaded binary may lack execute permissions",
|
||||
" - The script may require root/sudo access",
|
||||
` - Report it if this persists: ${pc.cyan(`https://github.com/OpenRouterTeam/spawn/issues`)}`,
|
||||
],
|
||||
includeDashboard: false,
|
||||
},
|
||||
2: {
|
||||
header: "Shell syntax or argument error. This is likely a bug in the script.",
|
||||
lines: [` Report it at: ${pc.cyan(`https://github.com/OpenRouterTeam/spawn/issues`)}`],
|
||||
includeDashboard: false,
|
||||
},
|
||||
1: {
|
||||
header: "Common causes:",
|
||||
lines: [],
|
||||
includeDashboard: true,
|
||||
specialHandling: (cloud, authHint) => [
|
||||
...credentialHints(cloud, authHint),
|
||||
" - Cloud provider API error (quota, rate limit, or region issue)",
|
||||
" - Server provisioning failed (try again or pick a different region)",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const SIGNAL_GUIDANCE: Record<string, SignalEntry> = {
|
||||
SIGKILL: {
|
||||
header: "Script was forcibly killed (SIGKILL). Common causes:",
|
||||
causes: [
|
||||
" - Out of memory (OOM killer terminated the process)",
|
||||
" - The server may not have enough RAM for this agent",
|
||||
" - Try a larger instance size or a different cloud provider",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
SIGTERM: {
|
||||
header: "Script was terminated (SIGTERM). Common causes:",
|
||||
causes: [
|
||||
" - The process was stopped by the system or a supervisor",
|
||||
" - Server shutdown or reboot in progress",
|
||||
" - Cloud provider terminated the instance (spot/preemptible instance or billing issue)",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
SIGINT: {
|
||||
header: "Script was interrupted (Ctrl+C).",
|
||||
causes: [
|
||||
"Note: If a server was already created, it may still be running.",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
SIGHUP: {
|
||||
header: "Script lost its terminal connection (SIGHUP). Common causes:",
|
||||
causes: [
|
||||
" - SSH session disconnected or timed out",
|
||||
" - Terminal window was closed during execution",
|
||||
" - Try using a more stable connection or a terminal multiplexer (tmux/screen)",
|
||||
],
|
||||
includeDashboard: false,
|
||||
},
|
||||
};
|
||||
|
||||
export function getSignalGuidance(signal: string, dashboardUrl?: string): string[] {
|
||||
const entry = SIGNAL_GUIDANCE[signal];
|
||||
|
|
@ -1062,7 +953,16 @@ export function getScriptFailureGuidance(exitCode: number | null, cloud: string,
|
|||
|
||||
// Apply special handling if defined for this exit code
|
||||
if (entry.specialHandling) {
|
||||
lines.push(...entry.specialHandling(cloud, authHint, dashboardUrl));
|
||||
// Exit code 1 special case: needs credentialHints
|
||||
if (exitCode === 1) {
|
||||
lines.push(
|
||||
...credentialHints(cloud, authHint),
|
||||
" - Cloud provider API error (quota, rate limit, or region issue)",
|
||||
" - Server provisioning failed (try again or pick a different region)"
|
||||
);
|
||||
} else {
|
||||
lines.push(...entry.specialHandling(cloud, authHint, dashboardUrl));
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.includeDashboard) {
|
||||
|
|
|
|||
116
cli/src/guidance-data.ts
Normal file
116
cli/src/guidance-data.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* Guidance data structures for error and signal reporting.
|
||||
* Extracted from commands.ts to improve maintainability and reduce cognitive complexity.
|
||||
*/
|
||||
|
||||
import pc from "picocolors";
|
||||
|
||||
export interface SignalEntry {
|
||||
header: string;
|
||||
causes: string[];
|
||||
includeDashboard: boolean;
|
||||
}
|
||||
|
||||
export interface ExitCodeEntry {
|
||||
header: string;
|
||||
lines: string[];
|
||||
includeDashboard: boolean;
|
||||
specialHandling?: (cloud: string, authHint?: string, dashboardUrl?: string) => string[];
|
||||
}
|
||||
|
||||
export function buildDashboardHint(dashboardUrl?: string): string {
|
||||
return dashboardUrl
|
||||
? ` - Check your dashboard: ${pc.cyan(dashboardUrl)}`
|
||||
: " - Check your cloud provider dashboard to stop or delete any unused servers";
|
||||
}
|
||||
|
||||
// Note: Exit code 1 uses specialHandling because it needs credentialHints from commands.ts to avoid circular deps
|
||||
export const EXIT_CODE_GUIDANCE: Record<number, ExitCodeEntry> = {
|
||||
130: {
|
||||
header: "Script was interrupted (Ctrl+C).",
|
||||
lines: ["Note: If a server was already created, it may still be running."],
|
||||
includeDashboard: true,
|
||||
},
|
||||
137: {
|
||||
header: "Script was killed (likely by the system due to timeout or out of memory).",
|
||||
lines: [
|
||||
" - The server may not have enough RAM for this agent",
|
||||
" - Try a larger instance size or a different cloud provider",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
255: {
|
||||
header: "SSH connection failed. Common causes:",
|
||||
lines: [
|
||||
" - Server is still booting (wait a moment and retry)",
|
||||
" - Firewall blocking SSH port 22",
|
||||
" - Server was terminated before the session started",
|
||||
],
|
||||
includeDashboard: false,
|
||||
},
|
||||
127: {
|
||||
header: "A required command was not found. Check that these are installed:",
|
||||
lines: [" - bash, curl, ssh, jq"],
|
||||
includeDashboard: false,
|
||||
specialHandling: (cloud) => [` - Cloud-specific CLI tools (run ${pc.cyan(`spawn ${cloud}`)} for details)`],
|
||||
},
|
||||
126: {
|
||||
header: "A command was found but could not be executed (permission denied).",
|
||||
lines: [
|
||||
" - A downloaded binary may lack execute permissions",
|
||||
" - The script may require root/sudo access",
|
||||
` - Report it if this persists: ${pc.cyan(`https://github.com/OpenRouterTeam/spawn/issues`)}`,
|
||||
],
|
||||
includeDashboard: false,
|
||||
},
|
||||
2: {
|
||||
header: "Shell syntax or argument error. This is likely a bug in the script.",
|
||||
lines: [` Report it at: ${pc.cyan(`https://github.com/OpenRouterTeam/spawn/issues`)}`],
|
||||
includeDashboard: false,
|
||||
},
|
||||
1: {
|
||||
header: "Common causes:",
|
||||
lines: [],
|
||||
includeDashboard: true,
|
||||
// specialHandling is set in getScriptFailureGuidance in commands.ts
|
||||
// to avoid circular dependency with credentialHints
|
||||
specialHandling: () => [],
|
||||
},
|
||||
};
|
||||
|
||||
export const SIGNAL_GUIDANCE: Record<string, SignalEntry> = {
|
||||
SIGKILL: {
|
||||
header: "Script was forcibly killed (SIGKILL). Common causes:",
|
||||
causes: [
|
||||
" - Out of memory (OOM killer terminated the process)",
|
||||
" - The server may not have enough RAM for this agent",
|
||||
" - Try a larger instance size or a different cloud provider",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
SIGTERM: {
|
||||
header: "Script was terminated (SIGTERM). Common causes:",
|
||||
causes: [
|
||||
" - The process was stopped by the system or a supervisor",
|
||||
" - Server shutdown or reboot in progress",
|
||||
" - Cloud provider terminated the instance (spot/preemptible instance or billing issue)",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
SIGINT: {
|
||||
header: "Script was interrupted (Ctrl+C).",
|
||||
causes: [
|
||||
"Note: If a server was already created, it may still be running.",
|
||||
],
|
||||
includeDashboard: true,
|
||||
},
|
||||
SIGHUP: {
|
||||
header: "Script lost its terminal connection (SIGHUP). Common causes:",
|
||||
causes: [
|
||||
" - SSH session disconnected or timed out",
|
||||
" - Terminal window was closed during execution",
|
||||
" - Try using a more stable connection or a terminal multiplexer (tmux/screen)",
|
||||
],
|
||||
includeDashboard: false,
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue