- Clarify download error messages: distinguish HTTP errors from network errors
with specific status codes in the message
- Add actionable next steps to OAuth timeout: re-run command or set key manually
- Standardize error help labels to "How to fix:" across CLI and shell scripts
(was inconsistently "What to do:", "Troubleshooting:", or missing)
- Add API method/endpoint context to retry failure messages so users know
which API call failed
- Make verify_agent_installed error cases mutually exclusive: first for
PATH/installation issues, second for runtime/dependency issues
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Static analysis tests that verify every cloud's upload_file() function
uses safe patterns to prevent command injection. Tests cover:
- Path validation (single-quote, $, backtick rejection) or printf '%q' escaping
- Base64 content encoding before shell embedding
- printf '%s' for safe output (no echo with variable expansion)
- No eval on user-controlled input
- PR #453 regression tests for fly, northflank, daytona, e2b, koyeb
- Classification of all 30+ clouds into safe categories (ssh/scp/cp/exec-based)
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Cancel handling: use p.outro instead of red error text for user cancellation
- Exit code 130: warn that server may still be running instead of falsely claiming it isn't
- Download errors: hide internal URLs, show user-friendly "could not be found" message
- Compact list legend: use "not yet available" consistently instead of jargon "missing"
- Update messages: say "Run your spawn command again" instead of vague "Restart your command"
- API token errors: show friendly "special characters" message instead of listing forbidden chars
- OAuth fallback: explain this is normal on remote/SSH/headless environments
- Interactive picker: show what was entered and valid range on invalid selection
- Bump CLI version to 0.2.39
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- `spawn version` now shows runtime (bun/node), version, platform,
and architecture for easier bug reporting and diagnostics
- Compact list legend changed from confusing "N/N" to descriptive
"green = all clouds yellow = some missing"
- Bump CLI to v0.2.38
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The getScriptFailureGuidance function was updated (PRs #449, #450) to
add dedicated handlers for exit codes 130, 137, 255, and 2, but the
existing tests still expected these to fall through to the default case.
Fixed 4 broken assertions in script-failure-guidance.test.ts and 2 in
exec-script-errors.test.ts. Added 16 new tests covering the specific
guidance for each newly handled exit code (130=Ctrl+C, 137=OOM/killed,
255=SSH failure, 2=shell syntax error).
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Add signal exit code handling (130/Ctrl+C, 137/killed, 255/SSH failure, 2/syntax error)
- Replace vague "Cloud API retry logic exhausted" with attempt count and retry advice
- Add network troubleshooting hint to API network error after retries
- Clarify OAuth fallback prompt: explain why OAuth failed and what happens next
- Consolidate auth cancellation message with three clear recovery options
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover all exit code paths in getScriptFailureGuidance() which had zero
direct test coverage despite being recently modified (PRs #450, #449):
- Exit code 127: command not found guidance with tool listing
- Exit code 126: permission denied message
- Exit code 1: credential/API/provisioning failure hints
- Default case: generic troubleshooting for unknown exit codes
- null exit code: falls through to default
- Edge cases: 0, negative, large codes, signal codes (130, 137)
- Structure: return type validation, non-empty arrays, distinct output
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
The old message "This agent + cloud combination doesn't exist yet" was
misleading because by the time reportDownloadFailure runs, the agent/cloud
combination has already been validated in the manifest. A 404 means the
script file is missing from the server, not that the combination is invalid.
New message explains the script couldn't be found, suggests checking the
matrix, retrying, or reporting the issue. Also adds a hint about temporary
server issues for 500-level errors.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract error handling from execScript() into dedicated helpers
(reportDownloadError, reportScriptFailure, getScriptFailureGuidance),
reducing the function from 52 to 15 lines and making error guidance
directly testable.
Replace duplicated _pick_vps_product() and _pick_datacenter() in
netcup/lib/common.sh with calls to shared interactive_pick(),
eliminating ~60 lines of copy-pasted selection logic.
Net reduction: 42 lines (-98/+56).
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmdInteractive is the primary user entry point (bare `spawn` command) with
zero test coverage. Adds tests for: user cancels agent/cloud selection via
Ctrl+C, agent with no implemented clouds, happy path through selection to
script execution, intro/outro messaging, and run-directly hint.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- validatePrompt max-length error now suggests --prompt-file alternative
- validateScriptContent shebang error explains likely download issue
- Compact list view now shows color legend (green=all, yellow=partial)
- Exit code 1 gets specific guidance (credentials, API, provisioning)
- cmdUpdate network error shows current version for context
- Bump CLI version to 0.2.37
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validates that every field in manifest.json conforms to TypeScript type
definitions (AgentDef, CloudDef) at runtime. Catches data quality issues
that truthiness-only checks miss:
- Required fields are correct types (string, not number/boolean/array)
- Agent env values are all strings with valid env var name keys
- Agent env references OPENROUTER_API_KEY (mandatory per CLAUDE.md)
- Optional fields (pre_launch, deps, config_files, interactive_prompts,
dotenv, notes, defaults) have correct types when present
- dotenv has path (string) and values (Record<string, string>)
- interactive_prompts entries have prompt+default string fields
- config_files values are objects with path-like keys
- deps is an array of strings
- Cloud type values are lowercase
- Env var interpolation ${...} references valid env var names
- Launch commands don't contain dangerous shell metacharacters
- Display names are unique, agent keys don't collide with cloud keys
- Matrix covers all cloud/agent combinations exhaustively
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover the untested code paths in validateImplementation (commands.ts
lines 233-256) that show different error messages based on how many
alternative clouds are available for an agent:
- 0 clouds: "no implemented cloud providers" + suggest "spawn list"
- 1-3 clouds: show all available clouds as examples
- >3 clouds: show first 3 examples + "Run spawn X to see all N options"
- Boundary: exactly 3 vs 4 clouds threshold
- Error message formatting (display names, singular/plural)
24 new tests, all passing (3865 total).
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add log_step() function (cyan) for status/progress messages
- Convert misused log_warn calls to log_step in shared/common.sh
(14 instances: SSH key gen, agent verification, waiting, configuring)
- Convert representative cloud scripts: hetzner, digitalocean, sprite
- Fix misleading validatePrompt error that suggested --prompt-file as a
workaround when it has the same validation
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover the critical untested error paths in commands.ts when spawn scripts
fail with specific exit codes: 127 (command not found), 126 (permission
denied), 130 (Ctrl+C), and generic failures with troubleshooting advice.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests expected help output (USAGE/EXAMPLES) in non-TTY mode, but the CLI
actually shows a "No interactive terminal detected" hint with exit code 1.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Show URL hint only on the first auth var in multi-auth cloud quick-start
(eliminates duplicate URLs for clouds like Contabo with 4 auth vars)
- Make script failure error messages exit-code-aware: show targeted guidance
for command-not-found (127) and permission-denied (126) exit codes
- Consolidate generic failure hints into "Common causes" section
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Cover critical bash functions that had zero test coverage:
- validate_oauth_port: port validation boundary cases and injection
- generate_env_config: shell export generation and value escaping
- calculate_retry_backoff: exponential backoff with jitter range
- _update_retry_interval: indirect variable update via printf -v
- _parse_api_response: HTTP response code extraction
- _api_handle_transient_http_error: retry exhaustion messaging
- get_cloud_init_userdata: cloud-init YAML content validation
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show cloud provider URL alongside credential env vars in quick-start
sections (both `spawn <agent>` and `spawn <cloud>` info views)
- Restructure script failure errors: separate credential issues from
other causes, inline the `spawn <cloud>` hint next to cloud credentials
- Replace "Check cloud-specific READMEs" with actionable `spawn <cloud>`
in help troubleshooting section
- Show concise 4-line guidance instead of full help dump when spawn is
run without a TTY (e.g. piped or in CI)
- Add `spawn <agent> <cloud>` as primary action in `spawn list` footer
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add OPENROUTER_API_KEY as first item in script failure error (most common cause)
- Replace external GitHub README link with actionable `spawn <cloud>` command in error output
- Fix help text: auth section now correctly says `spawn <cloud>` instead of `spawn <agent> <cloud>`
- Add inline URL hint next to OPENROUTER_API_KEY in quick-start sections
- Add cloud name label next to cloud auth vars in agent info quick-start
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Warn when extra positional arguments are silently ignored (e.g. "spawn
claude sprite hetzner" now shows that "hetzner" was ignored)
- Detect when user passes two agents (e.g. "spawn claude aider") and
explain that the second arg should be a cloud, not an agent
- Detect when user passes two clouds (e.g. "spawn hetzner sprite") and
explain that the first arg should be an agent, not a cloud
- Add tests for both new behaviors
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Tests handlePromptFileError formatting for ENOENT, EACCES, EISDIR, and
generic error codes. Also tests --prompt-file success path with real
files, edge cases (spaces in paths, dots, deeply nested paths, /dev/null),
mutual exclusion with --prompt, and missing cloud argument errors.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The findClosestKeyByNameOrKey function (added in PR #414) had zero direct
test coverage. This function is used in validateAgent, validateCloud, and
showInfoOrError for typo suggestions. Tests cover key-based matching,
display name matching, priority between key vs name matches, multiple
key competition, edge cases, and integration with manifest-like data.
Also adds boundary tests for levenshtein, findClosestMatch threshold,
and resolveAgentKey/resolveCloudKey display name edge cases.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "spawn update" hint to version output so users know how to update
- Simplify non-interactive TTY message (less alarming, more actionable)
- Fix _api_handle_transient_http_error passing wrong first arg to
_api_should_retry_on_error (was "http_429" instead of attempt number)
- Sync README matrix count (444 -> 445)
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fuzzy matching now checks both keys (e.g. "claude") and display names
(e.g. "Claude Code") when suggesting corrections for typos. Previously,
typing "spawn claud" or "spawn Hetzne" would only fuzzy-match against
keys, missing close display name matches. The new findClosestKeyByNameOrKey
function picks the best match across both, and suggestions now always
show the display name for clarity (e.g. "Did you mean claude (Claude Code)?").
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests three previously untested logic paths in commands.ts and manifest.ts:
- detectAndFixSwappedArgs: 8 tests covering swap detection, no-swap cases,
and swap with missing implementations
- resolveAndLog: 6 tests for case-insensitive key and display name resolution
- isValidManifest: 7 tests for manifest structure validation via loadManifest
- Prompt handling with swapped args: 2 tests for prompt+swap interaction
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Add direct unit tests for parseAuthEnvVars, getImplementedAgents,
getMissingClouds, getErrorMessage, getStatusDescription,
calculateColumnWidth, getTerminalWidth, and getImplementedClouds.
These functions were either completely untested or only tested via
inline replicas rather than the actual exports. parseAuthEnvVars is
particularly important as it parses auth config strings used in
user-facing quick-start instructions.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Tests expected old error wording after PR #387 changed the messages.
Agent: team-lead
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract resolveAndLog() from cmdRun to handle argument resolution
- Extract detectAndFixSwappedArgs() from cmdRun for swap detection
- Extract printInfoHeader() shared by cmdAgentInfo and cmdCloudInfo
- Extract groupByType() used by cmdAgentInfo, cmdCloudInfo, cmdClouds
- Extract printGroupedList() for displaying grouped items with hints
All tests pass (3260/3260). No functional changes.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Fix auto-update unicode symbols (checkmark/cross) that bypassed unicode
detection, causing garbled output in SSH sessions and dumb terminals
- Use log_info (green) instead of log_warn (yellow) for OAuth progress
messages, so normal authentication flow doesn't look like a warning
- Add install path to `spawn version` output for easier debugging when
multiple versions are installed
- Improve --prompt-file errors to distinguish file-not-found, permission
denied, and is-a-directory cases with actionable guidance
- Bump CLI version to 0.2.30
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Validates CLI helper functions (getImplementedClouds, parseAuthEnvVars,
resolveAgentKey, resolveCloudKey, calculateColumnWidth, fuzzy matching)
against the real manifest.json with all 21 clouds and 14 agents. Unlike
mock-based tests, these catch real-world issues like the local cloud's
auth: "none" pattern, multi-var auth strings, cloud type grouping, and
matrix key consistency. Includes dedicated tests for the new local cloud
provider (PR #383).
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Fix cmdCloudInfo quick-start showing "none" as a command for local cloud
- Always show OPENROUTER_API_KEY in cloud quick-start (needed for all agents)
- Update local scripts to explicitly say "Appending environment variables to ~/.zshrc"
- Update local README with spawn CLI usage and "What It Does" section
- Add 3 tests for quick-start auth display (none auth, env var auth)
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The showInfoOrError function loaded the manifest without a spinner,
causing no visual feedback on cold cache. Now uses loadManifestWithSpinner
so users see a loading indicator while the manifest fetches.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Show actionable auth setup commands in `spawn <cloud>` and `spawn <agent>`
output so users can get started immediately without reading README docs.
For clouds with env var auth (e.g. Hetzner), shows exact export commands:
Quick start:
export HCLOUD_TOKEN=your-hcloud-token-here
spawn claude hetzner
For agents, shows OpenRouter API key and example launch command:
Quick start:
export OPENROUTER_API_KEY=sk-or-v1-...
spawn claude sprite
Also adds parseAuthEnvVars() utility with 6 tests and 8 quick-start
integration tests.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Export and test 7 previously-unexported utility functions from commands.ts:
getTerminalWidth, getMissingClouds, getImplementedAgents, getImplementedClouds,
getErrorMessage, calculateColumnWidth, getStatusDescription.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover previously untested code paths:
- cmdCloudInfo "Not yet available" missing agents text (<=5 threshold)
- cmdCloudInfo setup URL and auth field display
- cmdCloudInfo agent count (N of M) format
- cmdAgentInfo URL and notes display
- cmdAgentInfo cloud count and type grouping
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Manifest load failure now shows specific troubleshooting steps including
cache directory path for manual cleanup
- Non-TTY mode explains why interactive picker is unavailable and suggests
the direct launch syntax instead of silently falling through to help
- `spawn update` network failure now includes recovery steps and manual
update command
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Agent info (spawn <agent>) now shows "X of Y" cloud count and groups
clouds by type (api, cli, sandbox) for easier scanning
- Cloud info (spawn <cloud>) now shows "X of Y" agent count, auth method,
and lists missing agents when there are 5 or fewer
- Cloud listing (spawn clouds) groups providers by type with X/Y ratio
counts instead of singular/plural text
- Remove unused TYPE_COLUMN_WIDTH constant
- Bump CLI version to 0.2.26
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validates ALL implemented scripts against CLAUDE.md shell script rules,
covering shebang, set -eo pipefail, lib/common.sh sourcing, macOS bash
3.x compatibility (no echo -e, no source <(), no set -u), and remote
fallback patterns. Unlike manifest-integrity.test.ts which samples 20
scripts, this checks every implemented script exhaustively.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Cover the previously untested branch in validateAgent (lines 150-158) and
validateCloud (lines 186-194) where findClosestMatch against display names
is used when key-based suggestion fails. Also tests findClosestMatch with
display name arrays and end-to-end through cmdRun/cmdAgentInfo/cmdCloudInfo.
22 new tests, all pass.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show agent/cloud URLs in info pages (spawn <agent>, spawn <cloud>)
- Add setup instructions link to cloud info pages
- Suggest available clouds when --prompt is used without a cloud arg
- Fix help text alignment for spawn list
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Spinner completion messages now show "done" state instead of repeating
the in-progress message (e.g., "Loading manifest" instead of "Loading manifest...")
- Script failures show actionable troubleshooting (missing credentials,
rate limits, dependencies) instead of generic "Script exited with code N"
- Ctrl+C (exit code 130) exits silently instead of showing an error
- Fuzzy matching for unknown agents/clouds now also searches display names,
so "Hetzner" suggests "hetzner" even when the key doesn't fuzzy-match
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename "Missing" column to "Not available on" to avoid confusion
- Change "all clouds" to "-- all clouds supported" for full coverage agents
- Only show +/- grid legend in grid view (not compact view)
- Fix help text alignment for "spawn list" command
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>