Commit graph

644 commits

Author SHA1 Message Date
A
4b76b3422c
refactor: reduce complexity in execScript and netcup pick functions (#449)
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>
2026-02-11 05:56:48 -08:00
A
02ecd4123b
test: add 15 tests for cmdInteractive cancel and selection paths (#448)
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>
2026-02-11 05:55:27 -08:00
A
e9e5f1bfea
fix: improve error messages with actionable guidance (#447)
- 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>
2026-02-11 05:20:31 -08:00
A
305a782fea
test: add 496 manifest type contract validation tests (#446)
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>
2026-02-11 05:20:13 -08:00
A
f79de27bc7
refactor: replace custom config loaders with shared helpers in 3 cloud libs (#445)
Migrate binarylane, northflank, and kamatera to use the shared
ensure_api_token_with_provider, _load_json_config_fields, and
_save_json_config helpers, removing ~120 lines of duplicated
token loading/saving/validation logic.

- binarylane: replace 50-line ensure_binarylane_token with
  ensure_api_token_with_provider + test_binarylane_token
- northflank: remove _load_northflank_config, _save_northflank_token,
  _northflank_login; consolidate into ensure_api_token_with_provider
  with test_northflank_token doing login + validation
- kamatera: replace inline python3 config loader with
  _load_json_config_fields, replace manual JSON save with
  _save_json_config

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 05:20:08 -08:00
A
c1e009a66e
refactor: replace custom config loaders with shared helpers in 3 cloud libs (#444)
Replace inline Python config loading (`_load_*_config`) and custom JSON
save functions (`_save_*_config`) in OVH, UpCloud, and Contabo with the
shared `_load_json_config_fields` and `_save_json_config` helpers.

Removes 85 lines of duplicated credential persistence code while
preserving identical behavior (env -> config file -> prompt -> save).

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 04:56:01 -08:00
B
e25bf29008 docs: Sync README matrix with manifest.json
Agent: team-lead
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 12:51:17 +00:00
A
88fa9e48e6
fix: prevent shell/Python injection in env var and credential handling (#443)
- binarylane/continue.sh: Replace unsafe inline echo with inject_env_vars_ssh
  to prevent command injection if OPENROUTER_API_KEY contains single quotes
- test/record.sh: Pass credential values via sys.argv instead of interpolating
  into Python string literals to prevent Python injection

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-11 04:50:34 -08:00
A
2bc95fdf15
test: add tests for validateImplementation branching (0, 1-3, >3 clouds) (#442)
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>
2026-02-11 04:48:52 -08:00
A
d25cdd0da6
fix: use log_step for progress messages in netcup scripts (#441)
All netcup agent scripts were using log_warn (yellow) for routine
progress messages like "Installing...", "Setting up...", "Starting...".
These should use log_step (cyan) which was added specifically for
progress/status messages, reserving log_warn for actual warnings.

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 04:45:44 -08:00
A
10a40ca574
fix: add log_step for progress messages, fix misleading prompt error (#440)
- 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>
2026-02-11 04:28:17 -08:00
A
14fc3ebbe6
feat: Add codex agent for Netcup (#437)
Agent: gap-filler-netcup-2

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 04:27:12 -08:00
A
e9e39f2032
feat: add netcup/gemini.sh implementation (#436)
Implements Gemini CLI on Netcup cloud infrastructure using the standard
pattern: provision VPS, install Gemini via npm, inject OpenRouter API
credentials, and launch interactive session.

Agent: gap-filler-netcup-5

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 04:27:08 -08:00
A
7ca3edc7ee
feat: Add Continue agent for Netcup (#439)
Agent: gap-filler-netcup-4

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 04:26:48 -08:00
A
e7a459a456
feat: Add nanoclaw agent for Netcup (#438)
Agent: gap-filler-netcup-1

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 04:26:43 -08:00
A
8ba9a14139
feat: Add Open Interpreter agent for Netcup (#435)
Agent: gap-filler-netcup-3

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 04:26:23 -08:00
A
88a5e9e844
refactor: extract shared config load/save helpers to reduce credential management complexity (#434)
Add _load_json_config_fields and _save_json_config to shared/common.sh,
replacing duplicated multi-python3-call patterns in IONOS (2 calls -> 1),
Netcup (3 calls -> 1 + inline python save -> helper), and RamNode
(3 calls -> 1 + inline python save -> helper).

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 04:25:22 -08:00
A
5722e13851
test: add 21 tests for execScript bash execution error handling (#433)
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>
2026-02-11 04:24:09 -08:00
A
3e34133f54
test: fix non-TTY test expectations to match actual CLI behavior (#432)
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>
2026-02-11 04:09:53 -08:00
A
a1509ac8b3
fix: improve CLI error messages and cloud auth display (#431)
- 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>
2026-02-11 04:07:45 -08:00
A
4452f69144
refactor: extract helpers from create_server in binarylane, ramnode, upcloud (#430)
Extract response-handling and body-building helpers from the longest
create_server functions to improve readability and reduce per-function
complexity:

- binarylane: 69 -> 31 lines (extract _binarylane_build_server_body,
  _binarylane_handle_create_response)
- ramnode: 57 -> 40 lines (extract _ramnode_handle_create_response)
- upcloud: 56 -> 39 lines (extract _upcloud_handle_create_response)

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 04:06:32 -08:00
A
fdc5d5e58b
refactor: extract shared SSH helpers to eliminate ~410 lines of duplication (#429)
Add ssh_run_server, ssh_upload_file, ssh_interactive_session, and
ssh_verify_connectivity to shared/common.sh. These four functions
were copy-pasted identically across 21 cloud provider lib files,
differing only in SSH username (root vs ubuntu).

Providers now set SSH_USER and delegate to the shared helpers via
one-line wrappers, reducing each provider's lib by ~20 lines.

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 03:45:18 -08:00
A
81f1630424
test: Add 65 tests for untested shared/common.sh functions (#428)
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>
2026-02-11 03:45:13 -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
bb4f0c29df
fix: improve credential guidance in error messages and quick-start hints (#426)
- 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>
2026-02-11 03:21:15 -08:00
A
ba5cdd8c3c
refactor: extract helpers from create_server() in hetzner and hostinger (#425)
Extract JSON body builders and error handlers from the two largest
remaining create_server() functions (hetzner 84->43, hostinger 76->44
lines), following the pattern established in PR #423.

Extracted helpers:
- hetzner: _hetzner_build_create_body(), _hetzner_check_create_error()
- hostinger: _hostinger_build_create_body(), _hostinger_handle_create_error()

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 03:19:54 -08:00
A
2f03f746ec
test: Add 54 tests for warnExtraArgs and dispatchCommand routing (#424)
Cover the critical untested paths from PR #422:
- warnExtraArgs: singular/plural warning, boundary cases
- dispatchCommand: IMMEDIATE_COMMANDS, SUBCOMMANDS, default handler routing
- SUBCOMMANDS --help flag redirect behavior
- showVersion output format
- End-to-end dispatch scenarios

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>
2026-02-11 03:19:02 -08:00
A
de19996360
refactor: extract helpers from create_server() in 4 cloud providers (#423)
Extract wait-for-IP polling loops and JSON body builders from the
largest create_server() functions (ramnode 105->59, netcup 95->50,
cherry 80->57, binarylane 92->70 lines), following the pattern
already established in ionos/lib/common.sh.

Extracted helpers:
- ramnode: _ramnode_build_server_body(), _ramnode_wait_for_ip()
- netcup: _netcup_build_create_body(), _netcup_wait_for_ip()
- cherry: _cherry_wait_for_ip()
- binarylane: _binarylane_wait_for_active()

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:59:31 -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
df733d786b
test: Add 34 tests for --prompt-file error handling paths (#421)
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>
2026-02-11 02:58:14 -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
246b72a22b
fix: Prevent Python/JSON injection in RamNode and Netcup providers (#420)
Use sys.argv and sys.stdin instead of shell variable interpolation
in Python strings to prevent code injection via credentials, SSH keys,
server names, and other user-controlled inputs.

RamNode fixes:
- _get_ramnode_token: credentials via sys.argv instead of string interpolation
- Config file read: use sys.argv[1] for file path (matches other providers)
- Config file save: use sys.argv for all values
- ramnode_check_ssh_key: key_name via sys.argv
- ramnode_register_ssh_key: public key via stdin, name via sys.argv
- create_server: all parameters via sys.argv

Netcup fixes:
- netcup_get_session: use python3+json.dumps instead of unquoted heredoc
- netcup_api: use python3+json.dumps for action parameter
- Config file read: use sys.argv[1] for file path
- Config file save: use python3+sys.argv instead of unquoted heredoc
- create_server: all parameters via sys.argv

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-11 02:36:03 -08:00
A
42655b6e24
test: Add 57 tests for findClosestKeyByNameOrKey fuzzy matching (#419)
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>
2026-02-11 02:36:00 -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
343042cd90
feat: Add openclaw support for Netcup (#416)
- Uses netcup lib primitives for server creation
- Installs bun and openclaw via bun global install
- OpenRouter injection via env vars
- Tested with bash -n

Agent: gap-filler-1

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 02:28:30 -08:00
A
52ed7dcfbc
refactor: extract generic_wait_for_instance to reduce duplication across 7 clouds (#415)
Seven cloud providers had nearly identical instance status polling loops
(20-36 lines each). Extract the shared pattern into generic_wait_for_instance()
in shared/common.sh and replace the duplicated loops with one-liner calls.

Clouds refactored: Civo, Contabo, DigitalOcean, GenesisCloud, Linode, UpCloud, Vultr
Net reduction: ~99 lines (-185/+86)

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 02:28:18 -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
A
dd2730ee5d
fix: Quote values in generate_env_config to prevent shell injection (#413)
The generate_env_config function wrote `export KEY=VALUE` without quoting
the value. When these config files are sourced by the user's shell, any
shell metacharacters in values ($, `, \, spaces) would be interpreted,
potentially leading to arbitrary command execution.

Values are now single-quoted, which prevents all shell interpretation.
Single quotes within values are properly escaped using the standard
'\'' technique.

Agent: security-auditor

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 02:06:49 -08:00
A
2e7a362fc5
test: Add 23 tests for swapped args detection, resolution logging, and manifest validation (#412)
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>
2026-02-11 02:06:20 -08:00
B
7f5db36c01 docs: Sync README matrix with manifest.json
Agent: team-lead
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 09:50:57 +00:00
A
ccd7ff013a
refactor: reduce complexity by extracting shared interactive_pick() and using ensure_api_token_with_provider() (#411)
- Extract interactive_pick() to shared/common.sh: generic numbered-menu
  picker that replaces 4 duplicate _pick_location/_pick_server_type/_pick_plan
  functions across hetzner and hostinger (156 lines -> 71 lines)
- Replace ensure_fly_token() (53 lines) with ensure_api_token_with_provider()
  plus a flyctl CLI auth pre-check (17 lines)
- Replace ensure_render_api_key() (38 lines + _save_render_api_key 8 lines)
  with ensure_api_token_with_provider() (6 lines)

Net reduction: 156 lines removed across 5 files. No functionality changes.

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 01:42:22 -08:00
A
57b1714668
test: Add 57 tests for previously untested exported utility functions (#409)
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>
2026-02-11 01:39:45 -08:00
B
b9c704a0ff docs: Sync README matrix with manifest.json (post-cycle)
Updated stats from "15 agents. 30 clouds. 420 combinations" to "15 agents. 32 clouds. 444 combinations".
Added Netcup and RamNode columns to matrix table.

Discovery cycle completed 19 PRs:
- 18 gap-filling PRs (Contabo + Hostinger)
- 2 new cloud providers (Netcup, RamNode)

Agent: team-lead
2026-02-11 09:39:36 +00:00
A
1576577ed8
feat: Add RamNode cloud provider with OpenStack API support (#408)
Add RamNode budget VPS cloud provider ($0.006/hr) with full OpenStack API integration.

Implementation:
- ramnode/lib/common.sh: OpenStack Keystone v3 auth + Compute API wrapper
- ramnode/claude.sh, ramnode/aider.sh, ramnode/goose.sh: 3 agent scripts
- manifest.json: Added ramnode cloud entry + 15 matrix entries (3 implemented)
- ramnode/README.md: Complete documentation
- test/record.sh: Live cycle testing for RamNode (_live_ramnode function)
- test/mock.sh: URL stripping for Identity/Compute/Network APIs

Technical details:
- Auth: RAMNODE_USERNAME + RAMNODE_PASSWORD + RAMNODE_PROJECT_ID
- APIs: Identity (5000/v3), Compute (8774/v2.1), Network (9696/v2.0)
- Token-based authentication (X-Auth-Token header)
- Server provisioning with cloud-init via base64-encoded userdata
- SSH key management via OpenStack keypairs API

Agent: cloud-scout-1

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-02-11 01:36:02 -08:00
A
93a043cef8
feat: Add Netcup cloud provider support (#407)
Add Netcup as a new cloud provider - a German budget VPS provider
with REST API support starting at €3.86/mo.

Changes:
- Created netcup/lib/common.sh with session-based REST API primitives
- Added Netcup to manifest.json clouds section
- Added 15 matrix entries (claude/aider/goose implemented, rest missing)
- Implemented netcup/claude.sh, netcup/aider.sh, netcup/goose.sh
- Created netcup/README.md with usage documentation

Netcup uses session-based authentication requiring:
- NETCUP_CUSTOMER_NUMBER
- NETCUP_API_KEY
- NETCUP_API_PASSWORD

API launched Oct 2025, replaces legacy SOAP service (deprecated May 2026).

Agent: cloud-scout-2

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-02-11 01:34:59 -08:00
A
bc4df8d181
feat: Add Hostinger interpreter support (#400)
Agent: gap-filler-hostinger-1

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 01:30:35 -08:00
A
ae74b0264b
feat: Add Contabo opencode support (#402)
Agent: gap-filler-contabo-3

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 01:30:22 -08:00
A
cb69cbdec4
feat: Add Hostinger amazonq support (#406)
Agent: gap-filler-hostinger-2

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 01:30:17 -08:00
A
bccbefb83d
feat: Add Hostinger gptme support (#405)
Agent: gap-filler-hostinger-3

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 01:30:14 -08:00
A
efd535fecd
feat: Add Contabo codex support (#404)
Agent: gap-filler-contabo-1

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 01:30:04 -08:00