Commit graph

77 commits

Author SHA1 Message Date
A
ac7fa14c61
fix: cache AWS credentials to avoid re-prompting on retry (#1841) (#1852)
After a successful interactive credential entry, credentials are now
saved to ~/.config/spawn/aws.json (chmod 600). On the next run, cached
credentials are loaded and validated before prompting again.

Supports --reauth flag / SPAWN_REAUTH=1 to force fresh credential entry.

Fixes #1841

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-24 00:53:34 -05:00
A
31c6bec2f6
fix: route spawn <cloud> to cloud info in interactive TTY mode (#1834)
* fix: route `spawn <cloud>` to cloud info in interactive TTY mode

When a user runs `spawn digitalocean` in interactive mode, the CLI was
treating "digitalocean" as an agent name, producing "Unknown agent: digitalocean".
This broke the error message in credential checks which tells users to
"Run spawn <cloud> for setup instructions."

Fix: Before routing a single argument to `cmdAgentInteractive`, check if
it resolves to a cloud name and route to `cmdCloudInfo` instead. This
matches documented behavior (`spawn <cloud>` = show available agents).

Fixes #1830

Agent: issue-fixer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: expand stdout/stderr mock objects to pass Biome format check

The node:child_process mock had stdout and stderr property values
on single lines, which Biome's formatter requires to be expanded
to multi-line object style.

Agent: issue-fixer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-24 00:08:43 -05:00
A
463c7a2efb
feat: add --custom flag for machine type/region selection (#1810)
* feat: add --custom flag for interactive machine type/region selection

By default, all clouds now skip size/region prompts and use sensible
defaults for faster provisioning. The --custom flag enables interactive
pickers on all clouds, unifying the previously inconsistent behavior
where some clouds always prompted and others never did.

- AWS: promptRegion/promptBundle gated on SPAWN_CUSTOM
- GCP: promptMachineType/promptZone gated on SPAWN_CUSTOM
- Fly: promptVmOptions gated on SPAWN_CUSTOM
- Hetzner: new promptServerType/promptLocation with type/location arrays
- DigitalOcean: new promptDropletSize/promptDoRegion with size/region arrays
- Daytona: new promptSandboxSize with cpu/memory/disk presets
- Sprite: no change (managed platform, no meaningful size options)
- --custom + --headless is an error (incompatible modes)
- Version bump to 0.8.0 (new feature)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: fix biome format violations in --custom flag code

Auto-format object literals in arrays (expand to multi-line), wrap
long console.error line, and expand inline array in test assertion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-23 12:29:51 -05:00
A
ec210e37af
fix: Result monad for retry logic — prevent duplicate server creation (#1771)
* fix: Result monad for retry logic — prevent duplicate server creation

SSH exit 255 after an interactive session caused runWithRetries to retry
the entire bash script, creating duplicate servers. The old withRetry
also blindly retried all errors including timeouts where the remote
command may have already completed.

Introduces a Result<T> monad (Ok/Err) so callers explicitly signal
whether a failure is retryable (return Err) or fatal (throw). Adds
wrapSshCall() that classifies SSH errors: transient connection failures
are retryable, timeouts are not. Removes retry loop from the top-level
script runner entirely since it spans server creation + interactive
session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: mandate draft-PR-first workflow for all changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add biome lint to CI and pre-commit hook, fix lint violations

- Add Biome lint job to .github/workflows/lint.yml
- Add TypeScript lint check to .githooks/pre-commit
- Fix useBlockStatements violations in ui.ts and tests
- Add biome lint to CLAUDE.md "After Each Change" checklist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: rename Result.value to Result.data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: clean up stale pre-commit hook

- Remove dead check for deleted functions (write_oauth_response_file,
  create_oauth_response_html) — they no longer exist in the codebase
- Fix early exit skipping Biome lint when no .sh files are staged
- Replace echo -e with printf (the hook was using the pattern it bans)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve biome lint errors blocking CI

- Fix useImportType: import { type Result } → import type { Result }
- Fix noUnusedImports: remove unused KNOWN_FLAGS import
- Fix noUnusedTemplateLiteral: template literal → string literal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 20:39:42 -05:00
A
8112276121
feat: add delete sub-menu (destroy/remove) and spawn kill alias (#1765)
Pressing `d` in the server picker now shows a sub-menu:
- Destroy server: hard delete (destroys cloud VM + marks deleted)
- Remove from history: soft delete (removes entry, no cloud API call)
- Cancel: go back to picker

Also adds `kill` as an alias for `spawn delete`.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 15:23:49 -08:00
A
545ddafe4a
fix: extract flags module to fix KNOWN_FLAGS drift in tests (#1757)
KNOWN_FLAGS in unknown-flags.test.ts was copy-pasted from index.ts and
was missing the --name flag, causing silent test gaps. Extract
KNOWN_FLAGS, findUnknownFlag, and expandEqualsFlags into a new flags.ts
module so tests import the real source of truth.

- Create cli/src/flags.ts with KNOWN_FLAGS, findUnknownFlag, expandEqualsFlags
- Update index.ts to import from flags.ts (checkUnknownFlags now uses findUnknownFlag)
- Update unknown-flags.test.ts to import from flags.ts instead of copy-pasting
- Add tests for --name flag, KNOWN_FLAGS completeness, and expandEqualsFlags
- Bump CLI version to 0.6.15

Fixes #1744

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 18:10:07 -05:00
A
fdd6a9b6c3
chore: harden biome lint rules and auto-fix codebase (#1759)
* chore: harden biome lint rules and auto-fix codebase

Add strict biome rules for better TypeScript code quality:
- useBlockStatements: enforce braces on all control flow
- useConst: prefer const over let
- useNodejsImportProtocol: require node: prefix for builtins
- noUnusedImports/Variables: error (warn in tests)
- noExplicitAny: warn in source, off in tests
- noDoubleEquals, noAssignInExpressions, noFallthroughSwitchClause
- useNumberNamespace (Number.isNaN over isNaN)
- noImplicitAnyLet, noInferrableTypes, noUselessElse

Auto-fixed 55 files. Tests relaxed for any/unused patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: enable biome formatter with expand: always for brace newlines

Enable biome formatter with:
- expand: "always" — braces on their own lines
- indentStyle: space, indentWidth: 2
- lineWidth: 120
- arrowParentheses: always
- trailingCommas: all
- semicolons: always

82 files reformatted. All 1819 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 14:37:47 -08:00
A
4cec25c6b7
fix: pass spawn name through cmdRun and headless flows (#1674)
cmdRun (spawn <agent> <cloud>) was not collecting or passing the spawn
name, so SPAWN_NAME was never set in the script environment and the
history record lacked a name. cmdRunHeadless had the same gap.

- Add promptSpawnName() call to cmdRun and pass result to execScript
- Wire spawnName through HeadlessOptions to runBashHeadless
- Add --name CLI flag to set SPAWN_NAME from the command line
- Skip interactive name prompt when SPAWN_NAME is already set
- Bump CLI to 0.5.33

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 21:52:52 -08:00
A
bc83ab0559
fix: deduplicate isInteractiveTTY and remove dead OVH env wrapper (#1457)
- Export isInteractiveTTY from commands.ts and import in index.ts,
  removing the duplicate definition that was missing !! boolean coercion
- Remove unused inject_env_vars_ovh function from ovh/lib/common.sh
  (all OVH scripts use spawn_agent which calls _spawn_inject_env_vars)
- Bump CLI version to 0.5.6

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-19 01:54:47 -05:00
A
3a0ce830e5
fix: resolve unknown --default flag in CLI picker (#1449)
Add --default to KNOWN_FLAGS so it is recognized even if the `spawn pick`
early-return path is bypassed (e.g. due to Bun kqueue/TTY errors on certain
platforms). Also wrap cmdPick in a try/catch so TTY errors produce a clean
error message instead of an unhandled rejection.

Sync test copies of KNOWN_FLAGS that had drifted: unknown-flags.test.ts was
missing --debug, --headless, --output, --clear, -a, -c, --agent, --cloud;
index-dispatch-routing.test.ts had the same gaps. Fix an incorrect test that
expected --output to be flagged as unknown (it has been a known flag since
--headless/--output were added).

Fixes #1447

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 15:24:37 -05:00
A
e4bf4d86a4
feat: add spawn pick command and interactive GCP project/zone/machine-type pickers (#1443)
- New cli/src/picker.ts: modular picker module with pickToTTY() that renders
  an arrow-key UI directly to /dev/tty, works even when stdout is captured by
  bash $() subshell substitution and stdin is piped with options.

- New spawn pick subcommand: reads options from stdin as tab-separated lines
  (value\tLabel\tHint), shows clack-style picker via /dev/tty, writes selected
  value to stdout.  Falls back to a numbered list when no TTY is available.

  Usage from bash:
    zone=$(printf 'us-central1-a\tIowa\nus-east1-b\tVirginia\n' \
             | spawn pick --prompt "Select zone" --default "us-central1-a")

- gcp/lib/common.sh: interactive project, zone, and machine-type pickers for
  all GCP agent scripts.  Each picker respects env var overrides (GCP_PROJECT,
  GCP_ZONE, GCP_MACHINE_TYPE) and skips the prompt when already set.  Uses
  spawn pick for a nice arrow-key UI when available; falls back to
  _display_and_select (fzf or numbered list) from shared/common.sh.

  - _gcp_machine_type_options(): curated list of 8 popular instance types
  - _gcp_zone_options(): 12 curated zones across US / EU / APAC / AU
  - _gcp_project_options(): live list via gcloud projects list
  - _gcp_pick_{machine_type,zone,project}(): picker wrappers
  - _gcp_resolve_project(): now prompts interactively instead of erroring when
    no project is configured
  - create_server(): now calls pickers before provisioning instead of silently
    using defaults

- cli version bump 0.5.2 to 0.5.3

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:30:52 -05:00
A
c097a9d234
feat: add headless SDK mode for programmatic provisioning (#1420)
* feat: add headless SDK mode for programmatic provisioning (#1181)

Add --headless and --output json flags to enable non-interactive
provisioning with structured JSON output on stdout.

- --headless: disables prompts, OAuth browser flows, and SSH sessions
- --output json: outputs structured SpawnResult JSON on stdout
- Exit code contract: 0=success, 1=execution, 2=download, 3=validation
- Upfront credential validation (fail-fast before provisioning)
- Script stdout piped to stderr to keep JSON output clean
- SPAWN_HEADLESS=1 env var set for bash scripts

Closes #1181

-- refactor/ux-engineer

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: restore critical test mocks for fly SSH readiness checks

The PR inadvertently removed essential mock logic:
- fly ssh mock no longer responded to 'echo ok' commands
- timeout/gtimeout mocks were removed (needed for SSH polling)
- python3 mock was removed (needed for JSON parsing)
- /tmp/spawn_* cleanup was removed from test teardown

This caused 29 fly/* test failures with 'SSH connectivity failed'.

Restores the exact mock implementations from main branch.

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 15:32:14 -05:00
A
2fe8956729
fix: improve error handling by capturing error objects in catch blocks (#1360)
Replace empty catch blocks with explicit error parameters for better debugging
and potential future error logging. Changes include:

- Add error parameter to all catch blocks (currently 7 instances)
- Enable conditional debug logging for non-fatal history write failures
- Maintain backward compatibility - no behavior changes
- Improve code maintainability and debugging capability

This addresses code health issue where errors were silently swallowed without
any reference, making debugging difficult.

Agent: code-health

Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:27:35 -05:00
A
8c845869b3
ux: improve error message formatting and clarity (#1324)
- Show agent display names instead of keys in cloud suggestion errors
- Add visual spacing in "not yet implemented" error output for better scannability
- Improve readability of error messages with strategic blank lines

Agent: ux-engineer

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:24:38 -05:00
Ahmed Abushagur
758b575658
feat: add server lifecycle management (reconnect + delete) (#1363)
Wire up connection tracking across all 10 clouds so users can reconnect
to and delete previously spawned servers via `spawn list` and `spawn delete`.

Phase 1 - Connection tracking:
- Extend save_vm_connection() with cloud and metadata params
- Add save_vm_connection to create_server() in all cloud libs
- Extend VMConnection with cloud, deleted, deleted_at, metadata fields

Phase 2 - Delete via interactive picker:
- Add "Delete this server" option to spawn list picker
- Build delete scripts that reuse each cloud's destroy_server()
- Confirmation UX with spinner feedback
- Soft-delete marking in history (deleted records show [deleted])

Phase 3 - Standalone delete command:
- spawn delete (aliases: rm, destroy) with interactive picker
- Filter support: spawn delete -a <agent> -c <cloud>

Also improves reconnect hints for Fly (fly ssh console) and
Daytona (daytona ssh) connections.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:06:49 -08:00
A
4e1796230e
cli: add interactive cloud selection for spawn <agent> (#1192)
Fixes #1180

When running `spawn <agent>` (e.g., `spawn claude`), now shows an interactive
cloud picker instead of requiring the full command or showing agent info.

- Add cmdAgentInteractive() function for agent-first cloud selection
- Route `spawn <agent>` to interactive picker when in TTY mode
- Fall back to agent info display in non-interactive contexts
- Update help text to reflect new interactive behavior
- Version bump 0.2.83 → 0.2.84

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 17:36:36 -08:00
A
01ed74ba95
fix: Hetzner Claude Code installation + add --debug mode (#1198)
Fixed Hetzner installation issue where curl to claude.ai/install.sh
was returning 403 errors. Added fallback to use bun (already installed
by cloud-init) to install Claude Code.

Also added --debug flag to enable verbose bash output (set -x) for
easier troubleshooting.

Changes:
- hetzner/claude.sh: Use bun fallback installation method
- CLI: Added --debug flag support (v0.2.86)
- shared/common.sh: Enable set -x when SPAWN_DEBUG=1

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 16:37:04 -08:00
A
70c7f9f8c5
ux: add spawn last command to instantly rerun most recent spawn (#1171)
Adds a new `spawn last` command (with `rerun` alias) that instantly
reruns the most recent spawn from history without requiring the
interactive picker. This improves the workflow for users who frequently
want to restart their last session.

Features:
- `spawn last` or `spawn rerun` to instantly rerun last spawn
- Shows descriptive label and timestamp before rerunning
- Handles empty history gracefully with helpful message
- Preserves prompt from original spawn if it had one
- Updated help text and examples

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 23:27:59 -05:00
A
92ac7b8f67
ux: clarify non-interactive terminal error message (#1137)
Improved the error message when spawn is run without arguments in a
non-interactive environment (piped/redirected stdin/stdout).

Before: 'No interactive terminal detected.'
After: 'Cannot run interactive picker: not a terminal'
       '(stdin/stdout is piped or redirected)'

This makes it clearer why the interactive picker cannot run and what
the actual issue is (not just 'detected' but explicitly explaining
the stdin/stdout state).

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 18:11:47 -05:00
A
44918b64c4
refactor: extract helpers to reduce function complexity (#1055)
- Extract `readPromptFile` from `resolvePrompt` in index.ts (60 -> 40 lines),
  isolating prompt-file validation and reading into a standalone helper
- Extract `formatCredStatusLine` from `buildCredentialStatusLines` in
  commands.ts, replacing repetitive set/not-set formatting with a reusable
  helper
- Extract `_aliyun_validate_create_params` and `_aliyun_run_instances` from
  `create_server` in alibabacloud/lib/common.sh (69 -> 34 lines), separating
  validation, API call, and orchestration concerns

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-14 00:41:39 -05:00
A
b6a07e3c60
fix: prevent sensitive file exfiltration via --prompt-file flag (#1024)
Add path validation to --prompt-file to block reading sensitive files
(SSH keys, cloud credentials, .env files, etc.) whose contents would be
sent to remote agents. Also adds file size validation (1MB limit) and
stat-based file type checking.

Fixes #991

Agent: security-auditor

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-13 18:30:05 -05:00
A
aafe3d1ce4
fix: eliminate duplicate Loading manifest spinner in agent/cloud info (#1021)
When running `spawn claude` or `spawn hetzner`, the "Loading manifest..."
spinner appeared twice: once in showInfoOrError() and again in
cmdAgentInfo/cmdCloudInfo via validateAndGetEntity(). Pass the
pre-loaded manifest to avoid the redundant load and spinner flash.

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>
2026-02-13 18:07:08 -05:00
A
ac56e5454a
fix: improve CLI UX with better error messages and help text (#1003)
- Show list-specific flags (-a, -c, --clear) in unknown flag error
- Add specific error for empty prompt files instead of generic validation
- Document SPAWN_UNICODE=1 env var in help text and troubleshooting
- Show filter/clear hints in interactive list picker

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
2026-02-13 13:04:48 -08:00
A
16b9132c7c
fix: add confirmation to history clear and improve UX details (#983)
- Add interactive confirmation prompt before clearing spawn history
  (spawn list --clear) to prevent accidental data loss
- Show total prompt length in dry-run preview when prompt exceeds 100
  characters, so users can verify the correct prompt was loaded
- Add "Rerun previous" suggestion to non-interactive terminal fallback
- Show "(shown first)" hint when clouds with credentials are detected
  in interactive picker, so users understand the sort order
- Add repository URL to spawn version output for discoverability

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
2026-02-13 11:31:01 -08:00
A
37ae22ddb5
refactor: reduce complexity in Linode create_server and CLI dispatchCommand (#972)
Extract _linode_extract_error helper to deduplicate 3 inline Python
error-extraction blocks, and _linode_handle_create_error to reduce
create_server from 47 to 31 lines.

Extract dispatchListCommand, dispatchSubcommand, dispatchVerbAlias, and
dispatchSlashNotation from the 63-line dispatchCommand function, reducing
it to 15 lines with clear single-responsibility helpers.

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-13 10:58:40 -08:00
A
2e7b083f8f
fix: show cloud URL for missing credentials in dry-run and add spawn list --clear (#944)
Two UX improvements:

1. Dry-run credential status now shows the cloud provider's URL next to
   missing cloud-specific auth vars (e.g., HCLOUD_TOKEN), helping users
   find where to create their credentials. Previously only
   OPENROUTER_API_KEY showed a URL hint.

2. Added `spawn list --clear` command to let users clear their spawn
   history. Previously there was no way to reset the 100-entry history
   file without manually deleting ~/.spawn/history.json.

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>
2026-02-13 07:16:14 -08:00
A
f806085bb6
fix: improve UX of error messages and status output (#938)
- Remove redundant "Warning:" prefix from API key format message (log_warn
  already conveys warning status)
- Fix incorrect `export VAR=token spawn ...` syntax in auth failure hint
  (export makes it persistent, inline env var syntax is correct)
- Replace attempt/retry jargon with elapsed time in SSH wait and instance
  polling messages (users care about time, not internal retry counts)
- Show instance IP in friendlier "ready (IP: x.x.x.x)" format
- Move HTTP status codes from error title to body in download failures
  (cleaner error headline, details still available)
- Simplify dry-run credential warning (remove confusing double-negative
  "without --dry-run")
- Remove redundant "Warning:" prefix from extra arguments message

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
2026-02-13 06:45:04 -08:00
A
75d7be29a4
fix: improve UX for stale manifest cache, list rerun hints, and version info (#805)
- Show warning when manifest is loaded from stale cache (offline fallback)
  so users know the data may be outdated
- Fix list footer rerun command: reuse buildRetryCommand instead of
  truncating prompts with "..." which produced broken copy-paste commands
- Show manifest cache age in "spawn version" output for troubleshooting
- Bump CLI version to 0.2.67

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>
2026-02-13 06:09:56 -08:00
A
7a441813fd
fix: detect slash notation and suggest correct syntax (#859)
When users type `spawn claude/hetzner` or `spawn hetzner/claude`,
the CLI now splits on the slash and forwards to the correct handler
with a helpful tip, instead of showing a confusing "invalid characters"
error from identifier 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>
2026-02-13 02:15:59 -08:00
A
e73d6b9793
fix: support --flag=value syntax in CLI argument parsing (#826)
Previously, `spawn --prompt="Fix bugs" claude sprite` or
`spawn list --agent=claude` would fail with "Unknown flag" because
the CLI only recognized `--flag value` (space-separated) syntax.
Now `--flag=value` is expanded to `--flag value` early in the
arg parsing pipeline, supporting the common GNU-style convention.

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
2026-02-12 23:55:46 -08:00
A
0fe83fe311
fix: improve CLI error messages for retry commands and unknown names (#777)
- buildRetryCommand: suggest --prompt-file for long prompts instead of
  truncating into a non-functional command (threshold raised to 80 chars)
- showUnknownCommandError: change "Unknown command" to "Unknown agent or cloud"
  since users are passing agent/cloud names, not commands
- Bump CLI version to 0.2.66

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-12 17:19:46 -08:00
A
52b91f4c43
fix: redirect 'spawn agents <name>' and 'spawn clouds <name>' to info pages (#709)
When users type 'spawn agents claude' or 'spawn clouds hetzner', they
intuitively expect to see info about that agent/cloud. Previously, the
extra argument was silently ignored with a warning, and the full list was
shown instead. Now these commands redirect to the info page for the
given name, with a tip suggesting the shorter 'spawn <name>' form.

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>
2026-02-12 15:02:23 -08:00
A
407f79e7b5
fix: improve spawn list UX with positional filters and long flags (#549)
- Support `spawn list claude` as shorthand for `spawn list -a claude`
- Add --agent and --cloud as long-flag aliases for -a and -c
- Fix flaky cmdlist-integration tests by priming manifest cache in
  beforeEach and isolating XDG_CACHE_HOME to prevent cross-test leakage
- Export _resetCacheForTesting from manifest.ts for deterministic tests
- Update help text with new filter syntax and examples

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>
2026-02-11 15:46:39 -08:00
A
52904a9163
fix: add 'spawn history' alias for 'spawn list' and document aliases in help (#540)
The word "list" is ambiguous in a CLI that also has "spawn agents" and
"spawn clouds" -- users naturally expect "spawn list" to list resources,
not show history. Adding "spawn history" as a first-class alias makes
the history command more discoverable. Also documents both aliases (ls,
history) in the help text.

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>
2026-02-11 15:13:32 -08:00
A
a9fae77c1f
refactor: simplify API retry logic and dispatchCommand (#533)
Remove 2 unnecessary indirection layers (_handle_api_transient_error and
_api_handle_transient_http_error) from the cloud API retry infrastructure.
The old _handle_api_transient_error had a bug where "network" was passed
as the attempt parameter to _api_should_retry_on_error, which expects a
numeric value. The retry logic is now inlined directly in
_cloud_api_retry_loop, calling _api_should_retry_on_error with the
correct arguments.

Also extract duplicated help-flag checking in dispatchCommand into a
hasTrailingHelpFlag helper, reducing nesting and removing repeated code.

Net: -72 lines, 2 fewer functions, 1 bug fix.

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>
2026-02-11 14:29:37 -08:00
A
2ee7c0cef5
fix: handle common verb aliases (run, launch, start, deploy, exec) in CLI (#516)
Users coming from Docker, kubectl, or other CLIs naturally try
"spawn run claude sprite" or "spawn launch aider hetzner". Previously
these would show a confusing "Unknown command: run" error. Now the CLI
transparently strips these verb prefixes and forwards to the correct
agent/cloud handler. Bare verbs like "spawn run" show a helpful message
explaining the correct syntax.

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>
2026-02-11 13:26:25 -08:00
A
c7a3532825
refactor: extract helpers from cmdList and showInfoOrError to reduce complexity (#506)
cmdList (88 -> 28 lines): Extract suggestFilterCorrection, showEmptyListMessage,
and showListFooter as focused helpers.

showInfoOrError (44 -> 15 lines): Extract showUnknownCommandError for the
fuzzy-match suggestion display logic.

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-11 12:15:11 -08:00
A
34985e01f6
fix: add timeout to script downloads and validate list filter flags (#499)
- 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>
2026-02-11 11:23:57 -08:00
A
a932bc3734
feat: store spawn history locally and repurpose spawn list (#486)
- 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>
2026-02-11 10:01:48 -08:00
A
5851a9ce7b
fix: show error when --dry-run used without agent and cloud args (#480)
`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>
2026-02-11 09:09:57 -08:00
A
ca510881f1
feat: add --dry-run flag to preview provisioning details (#478)
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>
2026-02-11 08:56:59 -08:00
A
2ed89d5b52
refactor: reduce complexity in ensure_multi_credentials and handleDefaultCommand (#476)
Extract 4 helper functions from ensure_multi_credentials() (94 lines, CC=14
-> 48 lines, CC=4): _multi_creds_all_env_set, _multi_creds_load_config,
_multi_creds_prompt, _multi_creds_validate.

Flatten handleDefaultCommand() (39 lines, CC=7 -> 15 lines, CC=3) by
extracting suggestCloudsForPrompt() and using early returns to eliminate
nested if/else blocks.

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>
2026-02-11 08:45:47 -08:00
A
cbee5a8f15
fix: add -f shorthand for --prompt-file and improve CLI UX (#475)
- 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>
2026-02-11 08:44:43 -08:00
A
6e789f8cbf
fix: improve error message consistency and clarity across CLI (#470)
- Style all error messages with colored output (pc.red for errors, pc.cyan for commands)
- Fix inconsistent OpenRouter key URL (openrouter.ai/keys -> openrouter.ai/settings/keys)
- Improve exit code 130 guidance to suggest cloud dashboard instead of unhelpful spawn command
- Add actionable recovery hints to token/credential validation failures
- Remove redundant "Invalid input" message from validated_read (validator already shows error)
- Fix nested color codes in cmdUpdate failure spinner
- Clean up version display when binary path is unavailable

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>
2026-02-11 08:17:14 -08:00
A
1316f9f609
fix: show runtime/platform in spawn version, clarify compact list legend (#456)
- `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>
2026-02-11 06:55:24 -08:00
A
5c4f830fea
fix: improve credential guidance in error messages and quick-start hints (#427)
- 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>
2026-02-11 03:42:55 -08:00
A
0181b73506
fix: warn on extra args and detect mismatched agent/cloud types (#422)
- 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>
2026-02-11 02:58:34 -08:00
A
d890740155
refactor: reduce complexity by extracting helpers from main(), cmdCloudInfo(), and resolvePrompt() (#418)
- Extract main() (73 lines) into handleNoCommand(), showVersion(), dispatchCommand() + main() (20 lines)
- Extract cmdCloudInfo() (53 lines) into printCloudQuickStart(), printAgentList() + cmdCloudInfo() (26 lines)
- Extract resolvePrompt() error handling into handlePromptFileError() (37 lines from 50)
- Move IMMEDIATE_COMMANDS and SUBCOMMANDS to module-level constants

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>
2026-02-11 02:37:32 -08:00
A
fa11fed516
fix: improve UX with version hints, clearer non-TTY message, and retry bug fix (#417)
- 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>
2026-02-11 02:33:38 -08:00
A
83440dd6f3
fix: improve fuzzy matching to check display names for better typo suggestions (#414)
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>
2026-02-11 02:06:52 -08:00