When a user types `spawn htzner claude` (cloud name typo as first arg),
checkEntity now detects that "htzner" is close to cloud "hetzner" and
suggests the user may have swapped agent and cloud arguments. Previously,
this only worked for exact cloud names; typos would produce a generic
"Unknown agent" error with no helpful suggestion.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Cover untested code paths in cmdCloudInfo and cmdAgentInfo:
- printCloudQuickStart with multi-auth env vars (e.g., UPCLOUD_USERNAME + UPCLOUD_PASSWORD)
- printCloudQuickStart URL hint only shown on first auth var (not repeated)
- printCloudQuickStart with OAuth auth (no parseable env vars)
- printCloudQuickStart with "none" auth (no extra export lines)
- printCloudQuickStart when no implemented agents (no example command)
- printAgentList "Not yet available" shown when missingAgents <= 5
- printAgentList "Not yet available" hidden when missingAgents > 5
- printAgentList boundary at exactly 5 missing agents
- cmdAgentInfo Quick start with multi-auth cloud as first available
- cmdAgentInfo Quick start with "none" auth cloud
- cmdAgentInfo no Quick start when no clouds implemented
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>
Users had no way to discover SPAWN_HOME, SPAWN_DEBUG, or SPAWN_UNICODE
env vars without reading source code. The help text now documents all
SPAWN_* environment variables in a dedicated section between
TROUBLESHOOTING and MORE INFO.
Also bumps CLI version to 0.2.50.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When `spawn list -a/-c` filter returns no results, suggest corrections
using the same fuzzy matching used elsewhere in the CLI. Also show total
history count so users know other entries exist.
When filter matches results, show "Showing X of Y spawns" with a
"Clear filter" hint instead of the default 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>
Cover untested prompt-related display paths in commands.ts and index.ts:
- cmdList prompt preview truncation at 40 chars in record rows
- cmdList rerun hint truncation at 30 chars for long prompts
- cmdList records with/without prompts showing correct output
- suggestCloudsForPrompt cloud suggestions with overflow (>5 limit)
- suggestCloudsForPrompt error messages and agent resolution paths
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Add FETCH_TIMEOUT (10s) to script download fetches in
downloadScriptWithFallback, preventing indefinite hangs when the
server is unresponsive
- Show actionable error when `spawn list -a` or `spawn list -c` is
used without a value, instead of silently showing unfiltered results
- Bump CLI version to 0.2.48
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>
commands-logic.ts (140 lines) duplicated functions already in commands.ts
and was never imported by the application. Its test file (275 lines) tested
these unused duplicates. The errorMessage function in commands.ts was defined
but never called. Total: 420 lines of dead code removed.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
checkEntity is a critical function in the run pipeline (commands.ts:182-206)
that validates user-provided agent/cloud names. It had zero test coverage
despite complex branching logic for wrong-type detection, fuzzy matching,
and error messaging.
Tests cover: valid entities, wrong-type detection (cloud-as-agent and
agent-as-cloud), non-existent entities, fuzzy match typos, empty/boundary
inputs, minimal manifests, kind parameter consistency, bulk validation
of all manifest entries, and overlapping key patterns.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
- Show prompt preview in `spawn list` history for prompted runs
- Include prompt in rerun hint when last spawn used --prompt
- Show auth requirements in `spawn clouds` listing
- Change swap detection from warn to info (auto-correcting, not a warning)
- Update `spawn clouds` help text: "for setup instructions" instead of "for details"
Bump CLI version to 0.2.46.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
`spawn list` now shows records in reverse chronological order (newest
first), matching the convention of git log, shell history, and docker ps.
Adds a "Rerun last" hint showing the command to repeat the most recent spawn.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Rename `spawn list` -> `spawn matrix` (alias: m) for the availability matrix
- New `spawn list` / `spawn ls` shows previously provisioned agents from ~/.spawn/history.json
- Support filtering: `spawn list -a <agent>`, `spawn list -c <cloud>`
- Auto-record each spawn with agent, cloud, timestamp, and prompt
- History path respects SPAWN_HOME env var for testability
- Bump CLI version to 0.2.44
Fixes#483
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover untested error paths in index.ts when agent/cloud arguments are missing:
- suggestCloudsForPrompt: --prompt with agent but no cloud shows cloud suggestions
- handleNoCommand: --dry-run and --prompt without any args
- handleDefaultCommand: --dry-run with agent but no cloud
- --prompt-file with agent but no cloud
- Combined flag edge cases
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Previously, `spawn badagent badcloud` would only show the agent error,
requiring users to fix it and re-run before discovering the cloud was
also wrong. Now both errors are shown together so users can fix
everything in one round trip.
Refactors validateEntity into checkEntity (returns bool) + validateEntity
(exits). cmdRun uses checkEntity for batch 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>
The showDryRunPreview function (added in PR #479) had only 1 basic
subprocess test. These tests cover all branches: agent info display
(name, description, install, launch), cloud info (name, description,
defaults), script URL format, env var display with OPENROUTER_API_KEY
redaction, prompt truncation at 100 chars, section ordering, and
verification that no script download occurs in dry-run mode.
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>
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>
`spawn --dry-run` silently entered interactive mode, ignoring the flag.
Now it shows an actionable error like `--prompt` does. Also adds
`--dry-run` to the README commands table.
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>
Allows users to see what would be provisioned (agent, cloud, server specs,
script URL, env vars) without actually spinning up a server.
Fixes#474
Agent: issue-fixer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover _load_json_config_fields, _save_json_config, extract_ssh_key_ids,
_generate_csrf_state, and interactive_pick -- all had zero test coverage
despite being used by every cloud provider script.
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 -f as short form for --prompt-file (parity with -p for --prompt)
- Show runnable command in "Did you mean" suggestions
- Show agent install command in agent info page
- Add agent count to "spawn agents" header (consistent with "spawn clouds")
- Update help text and examples to document -f flag
- Bump CLI version to 0.2.40
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>
Fix download error tests that checked wrong mock target (consoleMocks.error
vs mockLogError) and expected outdated "Troubleshooting" text instead of
"How to fix". Remove 11 stale describe.skip blocks from commands.test.ts
that have been superseded by dedicated test files using mock.module().
Add cmd-help-content.test.ts with 31 tests verifying help output includes
all subcommands, flags, sections, and key content.
Before: 4579 pass, 5 fail, 11 skip
After: 4615 pass, 0 fail, 0 skip
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>
Merged four nearly-identical functions (validateAgent, validateCloud,
validateAndGetAgent, validateAndGetCloud) into two generic ones
(validateEntity, validateAndGetEntity), eliminating ~20 lines of
duplicated validation, fuzzy matching, and swap-detection logic.
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>
Cover the real code paths through cmdRun for script downloading, fallback
to GitHub raw URLs, error reporting on 404/500/network errors, script
content validation, and exit code guidance. Tests exercise the actual
exported functions (getScriptFailureGuidance, getStatusDescription,
getErrorMessage) rather than reimplemented copies.
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>
- 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>