mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-09 11:10:10 +00:00
feat: spawn name prompt + kebab resource naming across all clouds (#1507)
* feat: add spawn name prompt and project confirmation to GCP flow
Ask for spawn name upfront (before auth), derive kebab-case default for
VM naming, and confirm the current GCP project before using it.
New interaction order:
1. Spawn name: "My Dev Box" → kebab "my-dev-box" exported as
GCP_INSTANCE_NAME_KEBAB
2. gcloud auth + project confirm: "Current project: X Keep? [Y/n]"
If no → project picker shown
3. SSH key
4. Machine type picker (existing)
5. Zone picker (existing)
6. Instance name prompt: "Instance name [my-dev-box]: "
User can press Enter to accept or type a custom name
New functions:
_to_kebab_case() — lowercases, replaces non-alnum with hyphens
_gcp_prompt_spawn_name() — prompts for display name, exports kebab default;
honours SPAWN_NAME env var set by CLI (--name flag)
Modified:
_gcp_resolve_project() — adds Y/n confirmation when project already set
get_server_name() — shows kebab default in prompt, accepts Enter
cloud_authenticate() — calls _gcp_prompt_spawn_name first
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* feat: add spawn name prompt to all clouds via shared/common.sh
Move _to_kebab_case() and prompt_spawn_name() to shared/common.sh so all
clouds get upfront spawn name prompting and kebab-based resource naming.
shared/common.sh:
+ _to_kebab_case() — "My Dev Box" → "my-dev-box"
+ prompt_spawn_name() — asks for display name, exports SPAWN_NAME_DISPLAY
and SPAWN_NAME_KEBAB; skips if already set;
honours SPAWN_NAME env var from CLI --name flag
~ get_resource_name() — replaces silent SPAWN_NAME fallback with a visible
prefilled default: "Enter server name [my-dev-box]: "
Per-cloud changes (cloud_authenticate gains prompt_spawn_name first):
hetzner, fly, aws, daytona, digitalocean, sprite — one-line change each
gcp/lib/common.sh:
- Remove _to_kebab_case() (now in shared)
- Remove _gcp_prompt_spawn_name() (now in shared as prompt_spawn_name)
~ cloud_authenticate: _gcp_prompt_spawn_name → prompt_spawn_name
~ get_server_name: simplified back to get_validated_server_name
(shared get_resource_name now shows the kebab default in the prompt)
Result — every cloud shows this flow upfront:
Spawn name (e.g. "My Dev Box"): My Claude Box
ℹ Resource name: my-claude-box
...
Enter server name [my-claude-box]: ⏎
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix: use "Use project '...'?" instead of "Keep this project?" in GCP prompt
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ff261f3544
commit
d5690a8b11
8 changed files with 69 additions and 17 deletions
|
|
@ -266,7 +266,7 @@ list_servers() {
|
|||
# Cloud adapter interface
|
||||
# ============================================================
|
||||
|
||||
cloud_authenticate() { ensure_aws_cli; ensure_ssh_key; }
|
||||
cloud_authenticate() { prompt_spawn_name; ensure_aws_cli; ensure_ssh_key; }
|
||||
cloud_provision() { create_server "$1"; }
|
||||
cloud_wait_ready() { verify_server_connectivity "${LIGHTSAIL_SERVER_IP}"; wait_for_cloud_init "${LIGHTSAIL_SERVER_IP}" 60; }
|
||||
cloud_run() { run_server "${LIGHTSAIL_SERVER_IP}" "$1"; }
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ list_servers() {
|
|||
# Cloud adapter interface
|
||||
# ============================================================
|
||||
|
||||
cloud_authenticate() { ensure_daytona_cli; ensure_daytona_token; }
|
||||
cloud_authenticate() { prompt_spawn_name; ensure_daytona_cli; ensure_daytona_token; }
|
||||
cloud_provision() { create_server "$1"; }
|
||||
cloud_wait_ready() { wait_for_cloud_init; }
|
||||
cloud_run() { run_server "$1"; }
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ for d in droplets:
|
|||
# Cloud adapter interface
|
||||
# ============================================================
|
||||
|
||||
cloud_authenticate() { register_cleanup_trap; ensure_do_token; ensure_ssh_key; }
|
||||
cloud_authenticate() { prompt_spawn_name; register_cleanup_trap; ensure_do_token; ensure_ssh_key; }
|
||||
cloud_provision() { create_server "$1"; }
|
||||
cloud_wait_ready() { verify_server_connectivity "${DO_SERVER_IP}"; wait_for_cloud_init "${DO_SERVER_IP}" 60; }
|
||||
cloud_run() { run_server "${DO_SERVER_IP}" "$1"; }
|
||||
|
|
|
|||
|
|
@ -486,7 +486,7 @@ for a in apps:
|
|||
# Cloud adapter interface
|
||||
# ============================================================
|
||||
|
||||
cloud_authenticate() { ensure_fly_cli; ensure_fly_token; }
|
||||
cloud_authenticate() { prompt_spawn_name; ensure_fly_cli; ensure_fly_token; }
|
||||
cloud_provision() { create_server "$1"; }
|
||||
cloud_wait_ready() { wait_for_cloud_init; }
|
||||
cloud_run() { run_server "$1"; }
|
||||
|
|
|
|||
|
|
@ -145,15 +145,25 @@ _gcp_pick_project() {
|
|||
interactive_pick "GCP_PROJECT" "" "GCP projects" _gcp_project_options
|
||||
}
|
||||
|
||||
# Resolve and export GCP_PROJECT — prompt interactively if not already set
|
||||
# Resolve and export GCP_PROJECT — confirm existing or pick interactively
|
||||
_gcp_resolve_project() {
|
||||
# Check env var and gcloud config
|
||||
local project="${GCP_PROJECT:-$(gcloud config get-value project 2>/dev/null)}"
|
||||
if [[ "${project}" == "(unset)" ]]; then project=""; fi
|
||||
|
||||
# If not set, offer an interactive project picker
|
||||
# When a project is already set, confirm before using it
|
||||
if [[ -n "${project}" && "${SPAWN_NON_INTERACTIVE:-}" != "1" ]]; then
|
||||
local confirm
|
||||
confirm=$(safe_read "Use project '${project}'? [Y/n]: ") || confirm=""
|
||||
confirm="${confirm:-y}"
|
||||
if [[ "${confirm}" =~ ^[nN] ]]; then
|
||||
project=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# If not set (or user chose to change), offer an interactive project picker
|
||||
if [[ -z "${project}" ]]; then
|
||||
log_info "No GCP project configured — fetching your projects..."
|
||||
log_info "Fetching your GCP projects..."
|
||||
project=$(_gcp_pick_project)
|
||||
fi
|
||||
|
||||
|
|
@ -373,7 +383,7 @@ list_servers() {
|
|||
# Cloud adapter interface
|
||||
# ============================================================
|
||||
|
||||
cloud_authenticate() { ensure_gcloud; ensure_ssh_key; }
|
||||
cloud_authenticate() { prompt_spawn_name; ensure_gcloud; ensure_ssh_key; }
|
||||
cloud_provision() { create_server "$1"; }
|
||||
cloud_wait_ready() { verify_server_connectivity "${GCP_SERVER_IP}"; wait_for_cloud_init "${GCP_SERVER_IP}" 60; }
|
||||
cloud_run() { run_server "${GCP_SERVER_IP}" "$1"; }
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ list_servers() {
|
|||
# Cloud adapter interface
|
||||
# ============================================================
|
||||
|
||||
cloud_authenticate() { ensure_hcloud_token; ensure_ssh_key; }
|
||||
cloud_authenticate() { prompt_spawn_name; ensure_hcloud_token; ensure_ssh_key; }
|
||||
cloud_provision() {
|
||||
local exit_code=0
|
||||
create_server "$1" || exit_code=$?
|
||||
|
|
|
|||
|
|
@ -511,6 +511,46 @@ validated_read() {
|
|||
done
|
||||
}
|
||||
|
||||
# Convert a display name to a valid kebab-case resource identifier.
|
||||
# "My Dev Box" → "my-dev-box" "Claude 2024!" → "claude-2024"
|
||||
_to_kebab_case() {
|
||||
printf '%s' "${1}" \
|
||||
| tr '[:upper:]' '[:lower:]' \
|
||||
| sed 's/[^a-z0-9-]/-/g' \
|
||||
| sed 's/-\{2,\}/-/g' \
|
||||
| sed 's/^-//;s/-$//'
|
||||
}
|
||||
|
||||
# Ask for a human-readable spawn name upfront, then derive a kebab-case
|
||||
# default used for resource naming on every cloud.
|
||||
# Idempotent — safe to call multiple times; skips prompt if already done.
|
||||
# Respects SPAWN_NAME when set by the CLI (e.g. spawn gcp claude --name "My Box").
|
||||
# Exports: SPAWN_NAME_DISPLAY, SPAWN_NAME_KEBAB
|
||||
prompt_spawn_name() {
|
||||
# Already prompted this session — nothing to do
|
||||
if [[ -n "${SPAWN_NAME_KEBAB:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local display_name
|
||||
if [[ -n "${SPAWN_NAME:-}" ]]; then
|
||||
display_name="${SPAWN_NAME}"
|
||||
log_info "Spawn name: ${display_name}"
|
||||
else
|
||||
echo "" >&2
|
||||
display_name=$(safe_read 'Spawn name (e.g. "My Dev Box"): ') || display_name=""
|
||||
[[ -z "${display_name}" ]] && display_name="spawn"
|
||||
fi
|
||||
|
||||
local kebab
|
||||
kebab=$(_to_kebab_case "${display_name}")
|
||||
[[ -z "${kebab}" ]] && kebab="spawn"
|
||||
|
||||
export SPAWN_NAME_DISPLAY="${display_name}"
|
||||
export SPAWN_NAME_KEBAB="${kebab}"
|
||||
log_info "Resource name: ${kebab}"
|
||||
}
|
||||
|
||||
# Generic function to get resource name from environment or prompt
|
||||
# Usage: get_resource_name ENV_VAR_NAME PROMPT_TEXT
|
||||
# Returns: Resource name via stdout
|
||||
|
|
@ -520,22 +560,24 @@ get_resource_name() {
|
|||
local prompt_text="${2}"
|
||||
local resource_value="${!env_var_name}"
|
||||
|
||||
# First check platform-specific env var
|
||||
# Platform-specific env var takes absolute precedence
|
||||
if [[ -n "${resource_value}" ]]; then
|
||||
log_info "Using ${prompt_text%:*} from environment: ${resource_value}"
|
||||
echo "${resource_value}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Then check for SPAWN_NAME (set by CLI)
|
||||
if [[ -n "${SPAWN_NAME:-}" ]]; then
|
||||
log_info "Using spawn name: ${SPAWN_NAME}"
|
||||
echo "${SPAWN_NAME}"
|
||||
return 0
|
||||
# Show spawn name kebab as a pre-filled default (press Enter to accept)
|
||||
local default_name="${SPAWN_NAME_KEBAB:-}"
|
||||
local effective_prompt="${prompt_text}"
|
||||
if [[ -n "${default_name}" ]]; then
|
||||
effective_prompt="${prompt_text%:*} [${default_name}]: "
|
||||
fi
|
||||
|
||||
local name
|
||||
name=$(safe_read "${prompt_text}")
|
||||
name=$(safe_read "${effective_prompt}") || name=""
|
||||
[[ -z "${name}" && -n "${default_name}" ]] && name="${default_name}"
|
||||
|
||||
if [[ -z "${name}" ]]; then
|
||||
log_error "${prompt_text%:*} is required but not provided"
|
||||
log_error ""
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ destroy_server() {
|
|||
# Wrapper for spawn_agent compatibility (sprite uses get_sprite_name)
|
||||
get_server_name() { get_sprite_name; }
|
||||
|
||||
cloud_authenticate() { ensure_sprite_installed; ensure_sprite_authenticated; }
|
||||
cloud_authenticate() { prompt_spawn_name; ensure_sprite_installed; ensure_sprite_authenticated; }
|
||||
cloud_provision() {
|
||||
SPRITE_NAME="$1"
|
||||
ensure_sprite_exists "${SPRITE_NAME}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue