ContextStudioWizard/wizard.sh
Karamelmar 7c9b61bfce Fix end-to-end startup: project registration, credentials, trust dialog, ready marker
- start.sh: auto-register project in ~/.config/context-studio/projects/ before
  launching Electron — without this acquireProjectLock() silently skips writing
  the lock file, waitForServers() never finds the registry port, all agent ports
  stay null (localhost:null errors)

- start.sh: mount all known Claude Code credential locations into container
  (~/.claude/.credentials.json, ~/.claude.json, $CLAUDE_CONFIG_DIR variants)
  not just ~/.anthropic which was empty on this system

- bin/claude: create /tmp/cs-ready-<agentId> on host after 3s delay so CS Core's
  CLI ready marker poll resolves instead of timing out after 10s

- workflow.sh: add hasTrustDialogAccepted:true to all agent settings.json so
  claude goes straight to priming without the folder trust dialog

- prereqs.sh: add ensure_api_key() — checks all credential locations, prompts
  with masked input if none found, offers to save to shell profile

- wizard.sh: trap SIGINT for graceful abort — gum confirm popup, reverts created
  project dir and cloned core dir, leaves installed packages untouched

- core.sh: set _WIZARD_CORE_CLONED=true before clone for cleanup tracking

- electron-config.js: increase serverStartupTimeout 30s→90s (config file in
  core/config/, not source — safe to edit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 21:20:25 +01:00

291 lines
12 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ╔══════════════════════════════════════════════════════════════════╗
# ║ Context Studio Wizard — Project Setup ║
# ╚══════════════════════════════════════════════════════════════════╝
set -uo pipefail
WIZARD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ── Bootstrap gum ────────────────────────────────────────────────────────
# Must happen before sourcing any lib that uses gum.
_install_gum() {
echo " Installing gum..."
if command -v pacman &>/dev/null; then
sudo pacman -Sy --noconfirm gum
elif command -v apt-get &>/dev/null; then
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" \
| sudo tee /etc/apt/sources.list.d/charm.list >/dev/null
sudo apt-get update -qq && sudo apt-get install -y gum
elif command -v dnf &>/dev/null; then
sudo dnf install -y 'https://repo.charm.sh/rpm/charm-repo.rpm' 2>/dev/null || true
sudo dnf install -y gum
elif command -v yum &>/dev/null; then
sudo yum install -y 'https://repo.charm.sh/rpm/charm-repo.rpm' 2>/dev/null || true
sudo yum install -y gum
elif command -v zypper &>/dev/null; then
local ver="0.14.5"
curl -sL "https://github.com/charmbracelet/gum/releases/download/v${ver}/gum_${ver}_Linux_x86_64.tar.gz" \
| tar -xz -C /tmp gum
sudo mv /tmp/gum /usr/local/bin/gum
else
echo "Cannot install gum automatically."
echo "Install it manually: https://github.com/charmbracelet/gum"
exit 1
fi
}
if ! command -v gum &>/dev/null; then
echo ""
echo " gum is required for the wizard UI (https://github.com/charmbracelet/gum)"
_install_gum
command -v gum &>/dev/null || { echo "gum installation failed."; exit 1; }
fi
# ── Source libs ──────────────────────────────────────────────────────────
source "$WIZARD_DIR/lib/utils.sh"
source "$WIZARD_DIR/lib/prereqs.sh"
source "$WIZARD_DIR/lib/core.sh"
source "$WIZARD_DIR/lib/project.sh"
source "$WIZARD_DIR/lib/workflow.sh"
source "$WIZARD_DIR/lib/container.sh"
# ── Find existing projects base dir ──────────────────────────────────────
find_projects_dir() {
local candidates=("Projects" "projects" "Project" "project" "Dev" "dev" "Workspace" "workspace" "Code" "code" "src" "Src")
for candidate in "${candidates[@]}"; do
if [[ -d "$HOME/$candidate" ]]; then
echo "$HOME/$candidate"
return
fi
done
echo "$HOME/Projects"
}
# ── Banner ────────────────────────────────────────────────────────────────
show_banner() {
clear
echo ""
gum style \
--border double \
--border-foreground "$C_MAUVE" \
--foreground "$C_MAUVE" \
--bold \
--align center \
--width 54 \
--margin "0 2" \
--padding "1 8" \
"🧙 Context Studio Wizard" \
"" \
"$(gum style --foreground "$C_SKY" --italic --bold "Scaffold your multi-agent AI")" \
"$(gum style --foreground "$C_SKY" --italic --bold "development environment")"
echo ""
}
# ── Project info ──────────────────────────────────────────────────────────
collect_project_info() {
header "Project Details"
ask PROJECT_NAME "Project name" ""
if [[ -z "$PROJECT_NAME" ]]; then die "Project name is required."; fi
local projects_base
projects_base="$(find_projects_dir)"
local default_dir="$projects_base/$(slugify "$PROJECT_NAME")"
ask PROJECT_DIR "Project location" "$default_dir"
if [[ -z "$PROJECT_DIR" ]]; then die "Project location is required."; fi
PROJECT_DIR="${PROJECT_DIR/#\~/$HOME}"
if [[ -e "$PROJECT_DIR" ]]; then
warn "Directory already exists: $PROJECT_DIR"
ask_yn _continue "Continue anyway?" "n"
if [[ "$_continue" != "y" ]]; then die "Aborted."; fi
fi
}
# ── Workflow source ───────────────────────────────────────────────────────
collect_workflow_info() {
header "Workflow Configuration"
ask_choice WORKFLOW_SOURCE "Workflow source" \
"Generate from scratch" \
"Clone from existing repo"
if [[ "$WORKFLOW_SOURCE" == "Clone from existing repo" ]]; then
ask WORKFLOW_REPO "Workflow repo URL" ""
if [[ -z "$WORKFLOW_REPO" ]]; then die "Repo URL is required."; fi
else
ask PROJECT_DESC "Project description" "A software project"
ask TECH_STACK "Tech stack" "Node.js"
ask_choice AGENT_PRESET "Agent preset" \
"minimal — 5 agents (coordinator · 2× coder · researcher · tester)" \
"standard — 9 agents (2× coordinator · 3× coder · 2× researcher · tester · reviewer)"
case "$AGENT_PRESET" in
minimal*) AGENT_PRESET="minimal" ;;
*) AGENT_PRESET="standard" ;;
esac
fi
}
# ── Summary ───────────────────────────────────────────────────────────────
confirm_summary() {
header "Summary"
local workflow_line
if [[ "${WORKFLOW_SOURCE:-}" == "Clone from existing repo" ]]; then
workflow_line="clone ${WORKFLOW_REPO:-}"
else
workflow_line="generate (${AGENT_PRESET:-minimal} preset)"
fi
gum style \
--border rounded \
--border-foreground "$C_MAUVE" \
--padding "1 3" \
--margin "0 2" \
"$(gum style --foreground "$C_SKY" --bold "Project") $(gum style --foreground "$C_TEXT" "$PROJECT_NAME")" \
"$(gum style --foreground "$C_SKY" --bold "Location") $(gum style --foreground "$C_TEXT" "$PROJECT_DIR")" \
"$(gum style --foreground "$C_SKY" --bold "Core") $(gum style --foreground "$C_TEXT" "$CS_CORE_DIR")" \
"$(gum style --foreground "$C_SKY" --bold "Workflow") $(gum style --foreground "$C_TEXT" "$workflow_line")"
echo ""
ask_yn _ok "Create project?" "y"
if [[ "$_ok" != "y" ]]; then die "Aborted."; fi
}
# ── Cleanup state tracking ────────────────────────────────────────────────
_WIZARD_CORE_CLONED=false
_WIZARD_PROJECT_CREATED=false
wizard_cleanup() {
local removed=false
if [[ "$_WIZARD_PROJECT_CREATED" == "true" && -n "${PROJECT_DIR:-}" && -d "$PROJECT_DIR" ]]; then
rm -rf "$PROJECT_DIR"
gum style --foreground "$C_GREEN" " ✓ Removed $PROJECT_DIR"
removed=true
fi
if [[ "$_WIZARD_CORE_CLONED" == "true" && -d "$CS_CORE_DIR" ]]; then
rm -rf "$CS_CORE_DIR"
gum style --foreground "$C_GREEN" " ✓ Removed $CS_CORE_DIR"
removed=true
fi
if [[ "$removed" == "false" ]]; then
gum style --foreground "$C_SURFACE" " Nothing to revert."
fi
}
handle_sigint() {
trap '' INT # block further Ctrl+C while dialog is open
echo ""
if gum confirm \
--affirmative "Yes, quit" \
--negative "No, continue" \
--default=No \
--prompt.foreground "$C_YELLOW" \
--selected.background "$C_RED" \
--selected.foreground "$C_BASE" \
--unselected.foreground "$C_TEXT" \
" Abort setup and revert changes?"; then
echo ""
gum style --foreground "$C_YELLOW" --bold " Reverting changes..."
wizard_cleanup
echo ""
gum style --foreground "$C_RED" --bold " Setup aborted."
echo ""
exit 130
fi
trap 'handle_sigint' INT # re-arm after "No, continue"
}
# ── Build ─────────────────────────────────────────────────────────────────
build_project() {
header "Building Project"
_WIZARD_PROJECT_CREATED=true
local slug
slug="$(slugify "$PROJECT_NAME")"
create_project_structure "$PROJECT_DIR" "$PROJECT_NAME"
create_devcontainer "$PROJECT_DIR" "$PROJECT_NAME"
generate_container_scripts "$PROJECT_DIR" "$PROJECT_NAME" "$slug"
if [[ "${WORKFLOW_SOURCE:-}" == "Clone from existing repo" ]]; then
clone_workflow "$PROJECT_DIR" "${WORKFLOW_REPO:-}"
else
generate_workflow "$PROJECT_DIR" "$PROJECT_NAME" \
"${PROJECT_DESC:-A software project}" \
"${TECH_STACK:-Node.js}" \
"${AGENT_PRESET:-minimal}"
fi
spin "Initializing git repo..." bash -c "
git -C '$PROJECT_DIR' init -q
git -C '$PROJECT_DIR' add .
git -C '$PROJECT_DIR' commit -q -m 'Initial project setup via Context Studio Wizard'
"
success "Git repo initialized"
}
# ── Done ──────────────────────────────────────────────────────────────────
print_next_steps() {
local slug
slug="$(slugify "$PROJECT_NAME")"
echo ""
gum style \
--border double \
--border-foreground "$C_GREEN" \
--foreground "$C_GREEN" \
--bold \
--align center \
--width 54 \
--margin "0 2" \
--padding "1 4" \
"✓ Project Created Successfully"
echo ""
gum style --foreground "$C_MAUVE" --bold --margin "0 2" " ➜ Enter your project:"
gum style --foreground "$C_SKY" --bold --margin "0 4" "cd \"$PROJECT_DIR\""
echo ""
gum style --foreground "$C_MAUVE" --bold --margin "0 2" " Available commands:"
echo ""
gum style --margin "0 4" \
"$(gum style --foreground "$C_SKY" --bold "./start.sh") — start container + Context Studio" \
"$(gum style --foreground "$C_SKY" --bold "./stop.sh") — stop the agents container" \
"$(gum style --foreground "$C_SKY" --bold "./update.sh") — update core, claude-code, OS packages"
echo ""
gum style \
--border normal \
--border-foreground "$C_YELLOW" \
--foreground "$C_YELLOW" \
--padding "0 2" \
--margin "0 2" \
"⚠ First run: start.sh builds the container image." \
" Expect 515 min on first start. Subsequent starts are instant."
echo ""
gum style --foreground "$C_SURFACE" --margin "0 2" \
"VS Code: code \"$PROJECT_DIR\" → Reopen in Container"
echo ""
}
# ── Main ──────────────────────────────────────────────────────────────────
main() {
trap 'handle_sigint' INT
show_banner
check_prerequisites
setup_core
collect_project_info
collect_workflow_info
confirm_summary
build_project
print_next_steps
trap - INT
}
main "$@"