From 09ff27be9246b8e949373d32021ea176950a1f18 Mon Sep 17 00:00:00 2001 From: Karamelmar Date: Mon, 9 Mar 2026 11:49:04 +0100 Subject: [PATCH] =?UTF-8?q?Add=20Context=20Studio=20Wizard=20=E2=80=94=20p?= =?UTF-8?q?roject=20scaffolding=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wizard.sh: interactive bash wizard that scaffolds new CS projects - lib/: utils, core install, project structure, workflow generation - templates/: Dockerfile, devcontainer.json, agent presets (minimal/standard), system.json, roles, hook rules - README: setup, usage, mobility workflow Co-Authored-By: Claude Sonnet 4.6 --- README.md | 114 ++++++++++++++++- lib/core.sh | 28 +++++ lib/project.sh | 101 +++++++++++++++ lib/utils.sh | 73 +++++++++++ lib/workflow.sh | 166 +++++++++++++++++++++++++ templates/Dockerfile | 40 ++++++ templates/agents-minimal.json | 92 ++++++++++++++ templates/agents-standard.json | 149 ++++++++++++++++++++++ templates/devcontainer.json | 36 ++++++ templates/hooks/rules/coordinator.json | 17 +++ templates/hooks/rules/default.json | 4 + templates/roles/coder-role.md | 19 +++ templates/roles/coordinator-role.md | 20 +++ templates/roles/researcher-role.md | 18 +++ templates/roles/reviewer-role.md | 17 +++ templates/roles/subcoordinator-role.md | 19 +++ templates/roles/tester-role.md | 17 +++ templates/system.json | 43 +++++++ wizard.sh | 160 ++++++++++++++++++++++++ 19 files changed, 1132 insertions(+), 1 deletion(-) create mode 100644 lib/core.sh create mode 100644 lib/project.sh create mode 100644 lib/utils.sh create mode 100644 lib/workflow.sh create mode 100644 templates/Dockerfile create mode 100644 templates/agents-minimal.json create mode 100644 templates/agents-standard.json create mode 100644 templates/devcontainer.json create mode 100644 templates/hooks/rules/coordinator.json create mode 100644 templates/hooks/rules/default.json create mode 100644 templates/roles/coder-role.md create mode 100644 templates/roles/coordinator-role.md create mode 100644 templates/roles/researcher-role.md create mode 100644 templates/roles/reviewer-role.md create mode 100644 templates/roles/subcoordinator-role.md create mode 100644 templates/roles/tester-role.md create mode 100644 templates/system.json create mode 100755 wizard.sh diff --git a/README.md b/README.md index b4ddbaa..ca1daf3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,114 @@ -# ContextStudioWizard +# Context Studio Wizard +> Scaffold a fully configured multi-agent AI development environment in seconds. + +--- + +## What it does + +One command. One conversation. A complete project drops out: + +``` +~/.context-studio/core/ ← installed once, shared globally + +~/projects/my-project/ +├── .devcontainer/ ← Node 22 · Rust · Claude Code · Electron deps +├── workflow/ ← agents, roles, A2A config, project docs +└── src/ ← your code +``` + +Open in devcontainer → start Context Studio → talk to your agent team. + +--- + +## Prerequisites + +| Tool | Purpose | +|------|---------| +| `git` | Clone repos | +| `docker` or `podman` | Run devcontainer | +| SSH key → `github.com` | Access context-studio-core | +| `ANTHROPIC_API_KEY` | Claude agents | + +--- + +## Usage + +```bash +./wizard.sh +``` + +The wizard guides you through: + +1. **Core setup** — clones `context-studio-core` to `~/.context-studio/core/` (once) +2. **Project name & location** +3. **Workflow** — generate from scratch _or_ clone an existing repo +4. **Agent preset** _(if generating)_ + + | Preset | Agents | + |--------|--------| + | `minimal` | coordinator · 2× coder · researcher · tester | + | `standard` | 2× coordinator · 3× coder · 2× researcher · tester · reviewer | + +5. **Done** — git repo initialized, devcontainer ready + +--- + +## Open the project + +**VS Code** +```bash +code ~/projects/my-project # → "Reopen in Container" +``` + +**CLI** +```bash +cd ~/projects/my-project +docker build -t my-project .devcontainer/ +docker run -it --rm \ + -v "$(pwd)":/workspace \ + -v "$HOME/.context-studio/core":/opt/context-studio/core \ + -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \ + my-project bash +``` + +**Start Context Studio** _(inside container)_ +```bash +node $CS_CORE_DIR/core/start.js # Electron UI +node $CS_CORE_DIR/core/start.js --ui-mode=headless # servers only +``` + +--- + +## Mobility + +Commit your project. On any machine: + +```bash +git clone +cd my-project +code . # done +``` + +Core is re-cloned automatically on first run. Only `ANTHROPIC_API_KEY` is needed on the host. + +--- + +## Repository layout + +``` +wizard.sh ← entry point +lib/ + utils.sh ← prompts, colors, helpers + core.sh ← global core install/update + project.sh ← devcontainer + project scaffold + workflow.sh ← generate or clone workflow config +templates/ + Dockerfile ← Node 22 + Rust + build tools + Claude Code + devcontainer.json ← mounts, env vars, VS Code extensions + agents-minimal.json ← 5-agent preset + agents-standard.json ← 9-agent preset + system.json ← A2A server defaults + roles/ ← coordinator, coder, researcher, tester, reviewer + hooks/rules/ ← per-role tool restrictions +``` diff --git a/lib/core.sh b/lib/core.sh new file mode 100644 index 0000000..586eb76 --- /dev/null +++ b/lib/core.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# core.sh — manage the global context-studio-core installation + +CS_CORE_REPO="git@github.com:KlausUllrich/context-studio-core.git" +CS_HOME="${CS_HOME:-$HOME/.context-studio}" +CS_CORE_DIR="$CS_HOME/core" + +setup_core() { + header "Context Studio Core" + + if [[ -d "$CS_CORE_DIR/.git" ]]; then + success "Core already installed at $CS_CORE_DIR" + ask_yn _update "Update core to latest?" "n" + if [[ "$_update" == "y" ]]; then + info "Pulling latest core..." + git -C "$CS_CORE_DIR" pull --ff-only \ + || warn "Pull failed — continuing with existing version" + success "Core updated" + fi + return 0 + fi + + info "Cloning context-studio-core → $CS_CORE_DIR" + mkdir -p "$CS_HOME" + git clone "$CS_CORE_REPO" "$CS_CORE_DIR" \ + || die "Failed to clone context-studio-core. Check your SSH key and network." + success "Core installed at $CS_CORE_DIR" +} diff --git a/lib/project.sh b/lib/project.sh new file mode 100644 index 0000000..a562198 --- /dev/null +++ b/lib/project.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# project.sh — create project directory and devcontainer + +WIZARD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +create_project_structure() { + local project_dir="$1" + local project_name="$2" + + info "Creating project structure at $project_dir..." + + mkdir -p "$project_dir/src" + mkdir -p "$project_dir/.devcontainer" + + # .gitignore + cat > "$project_dir/.gitignore" <<'EOF' +# Dependencies +node_modules/ +.pnp/ +.pnp.js + +# Build outputs +dist/ +build/ +*.AppImage +*.dmg +*.exe + +# Environment +.env +.env.local +.env.*.local + +# Runtime data +workflow/data/registry.db +workflow/users/*/session-history/ + +# Logs +*.log +npm-debug.log* + +# OS +.DS_Store +Thumbs.db +EOF + + # README + cat > "$project_dir/README.md" < "$project_dir/.devcontainer/Dockerfile" + + # devcontainer.json + sed "s/{{PROJECT_NAME}}/$project_name/g; s/{{PROJECT_SLUG}}/$slug/g" \ + "$WIZARD_DIR/templates/devcontainer.json" \ + > "$project_dir/.devcontainer/devcontainer.json" + + success "Devcontainer config written to $project_dir/.devcontainer/" +} diff --git a/lib/utils.sh b/lib/utils.sh new file mode 100644 index 0000000..d950501 --- /dev/null +++ b/lib/utils.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# utils.sh — colors, prompts, helpers + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +RESET='\033[0m' + +info() { echo -e "${CYAN}${BOLD}[info]${RESET} $*"; } +success() { echo -e "${GREEN}${BOLD}[ok]${RESET} $*"; } +warn() { echo -e "${YELLOW}${BOLD}[warn]${RESET} $*"; } +error() { echo -e "${RED}${BOLD}[error]${RESET} $*" >&2; } +die() { error "$*"; exit 1; } +header() { echo -e "\n${BOLD}${CYAN}━━━ $* ━━━${RESET}\n"; } + +# ask VAR "prompt" "default" +ask() { + local var="$1" prompt="$2" default="$3" + if [[ -n "$default" ]]; then + echo -ne "${BOLD}${prompt}${RESET} ${CYAN}[${default}]${RESET}: " + else + echo -ne "${BOLD}${prompt}${RESET}: " + fi + read -r input + if [[ -z "$input" && -n "$default" ]]; then + eval "$var=\"$default\"" + else + eval "$var=\"$input\"" + fi +} + +# ask_yn VAR "prompt" "y|n" +ask_yn() { + local var="$1" prompt="$2" default="$3" + local options + if [[ "$default" == "y" ]]; then options="Y/n"; else options="y/N"; fi + echo -ne "${BOLD}${prompt}${RESET} ${CYAN}[${options}]${RESET}: " + read -r input + input="${input:-$default}" + if [[ "$input" =~ ^[Yy]$ ]]; then + eval "$var=y" + else + eval "$var=n" + fi +} + +# ask_choice VAR "prompt" option1 option2 ... +ask_choice() { + local var="$1" prompt="$2"; shift 2 + local options=("$@") + echo -e "${BOLD}${prompt}${RESET}" + for i in "${!options[@]}"; do + echo -e " ${CYAN}$((i+1))${RESET}) ${options[$i]}" + done + echo -ne "Choice ${CYAN}[1]${RESET}: " + read -r input + input="${input:-1}" + if [[ "$input" =~ ^[0-9]+$ ]] && (( input >= 1 && input <= ${#options[@]} )); then + eval "$var=\"${options[$((input-1))]}\"" + else + eval "$var=\"${options[0]}\"" + fi +} + +require_cmd() { + command -v "$1" &>/dev/null || die "Required command not found: $1" +} + +slugify() { + echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//;s/-$//' +} diff --git a/lib/workflow.sh b/lib/workflow.sh new file mode 100644 index 0000000..c179a0a --- /dev/null +++ b/lib/workflow.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# workflow.sh — generate or clone workflow config + +WIZARD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# generate_workflow PROJECT_DIR PROJECT_NAME PROJECT_DESC TECH_STACK PRESET +generate_workflow() { + local project_dir="$1" + local project_name="$2" + local project_desc="$3" + local tech_stack="$4" + local preset="$5" + local workflow_dir="$project_dir/workflow" + + info "Generating workflow config ($preset preset)..." + + mkdir -p "$workflow_dir/agents" + mkdir -p "$workflow_dir/roles" + mkdir -p "$workflow_dir/hooks/rules" + mkdir -p "$workflow_dir/project-docs/tasks" + mkdir -p "$workflow_dir/users/default/tasks" + mkdir -p "$workflow_dir/data" + + # workflow.json + cat > "$workflow_dir/workflow.json" < "$workflow_dir/agents.json" + + # Create agent directories from agents.json + local agent_ids + agent_ids=$(grep '"id"' "$workflow_dir/agents.json" | sed 's/.*"id": *"\([^"]*\)".*/\1/') + for agent_id in $agent_ids; do + _create_agent_dir "$workflow_dir" "$agent_id" + done + + # Copy role files + cp "$WIZARD_DIR/templates/roles/"*.md "$workflow_dir/roles/" 2>/dev/null || true + + # Copy hook rules + cp "$WIZARD_DIR/templates/hooks/rules/"*.json "$workflow_dir/hooks/rules/" 2>/dev/null || true + + # project-docs/project-vision.md + cat > "$workflow_dir/project-docs/project-vision.md" < "$workflow_dir/CLAUDE.md" < "message"\` to send messages between agents. +EOF + + success "Workflow generated at $workflow_dir" +} + +_create_agent_dir() { + local workflow_dir="$1" + local agent_id="$2" + local agent_dir="$workflow_dir/agents/$agent_id/.claude" + + mkdir -p "$agent_dir/hooks" + mkdir -p "$agent_dir/commands" + + # settings.json + cat > "$agent_dir/settings.json" < "$workflow_dir/agents/$agent_id/CLAUDE.md" < "message"` +- Monitor agent status +- Escalate blockers to the user + +## Principles +- Delegate implementation to coders — do not write code yourself +- Delegate research to researchers +- Delegate testing to testers +- Keep the user informed of progress diff --git a/templates/roles/researcher-role.md b/templates/roles/researcher-role.md new file mode 100644 index 0000000..11d1827 --- /dev/null +++ b/templates/roles/researcher-role.md @@ -0,0 +1,18 @@ +# Researcher Role + +You are a Researcher in a multi-agent development team managed by Context Studio. + +## Responsibilities +- Investigate APIs, libraries, patterns, and documentation +- Produce concise research reports for the coordinator and coders +- Answer technical questions with evidence + +## Communication +- Receive research tasks via A2A messages +- Report findings back via `/sm kai "findings"` or the requesting agent +- Store research results in `project-docs/` when relevant + +## Principles +- Prefer official documentation over Stack Overflow +- Summarize findings — do not dump raw docs +- Flag uncertainties and alternatives diff --git a/templates/roles/reviewer-role.md b/templates/roles/reviewer-role.md new file mode 100644 index 0000000..c3bcf81 --- /dev/null +++ b/templates/roles/reviewer-role.md @@ -0,0 +1,17 @@ +# Reviewer Role + +You are a Code Reviewer in a multi-agent development team managed by Context Studio. + +## Responsibilities +- Review code written by developers +- Enforce coding standards and best practices +- Flag security issues, performance problems, and design smells + +## Communication +- Receive review tasks via A2A messages +- Report review results back to coordinator via `/sm kai "review complete"` + +## Principles +- Be constructive — suggest improvements, not just problems +- Distinguish must-fix from nice-to-have +- Check for security vulnerabilities (injection, XSS, auth bypass, etc.) diff --git a/templates/roles/subcoordinator-role.md b/templates/roles/subcoordinator-role.md new file mode 100644 index 0000000..bd27338 --- /dev/null +++ b/templates/roles/subcoordinator-role.md @@ -0,0 +1,19 @@ +# Sub-coordinator Role + +You are a Sub-coordinator in a multi-agent development team managed by Context Studio. + +## Responsibilities +- Manage a group of agents for a specific workstream (e.g. backend, frontend) +- Receive delegated work from the Lead Coordinator +- Break it down further and delegate to specialists in your group +- Report consolidated progress back to the Lead Coordinator + +## Communication +- Receive tasks from Lead Coordinator (`kai`) via A2A messages +- Delegate to agents in your group via `/sm "task"` +- Report back to kai via `/sm kai "status"` + +## Principles +- Do not implement code yourself — delegate to coders +- Track all delegated tasks and follow up on completion +- Escalate blockers to the Lead Coordinator diff --git a/templates/roles/tester-role.md b/templates/roles/tester-role.md new file mode 100644 index 0000000..5ba0804 --- /dev/null +++ b/templates/roles/tester-role.md @@ -0,0 +1,17 @@ +# Tester Role + +You are a Tester in a multi-agent development team managed by Context Studio. + +## Responsibilities +- Write and run tests for features implemented by coders +- Report bugs with reproduction steps +- Validate acceptance criteria + +## Communication +- Receive testing tasks via A2A messages +- Report test results and bugs back to coordinator via `/sm kai "results"` + +## Principles +- Test edge cases, not just happy paths +- Write automated tests where possible +- Keep bug reports actionable: what failed, what was expected, how to reproduce diff --git a/templates/system.json b/templates/system.json new file mode 100644 index 0000000..1ef3aa5 --- /dev/null +++ b/templates/system.json @@ -0,0 +1,43 @@ +{ + "version": "1.0", + "registry": { + "database": "./data/registry.db", + "port": 8000 + }, + "system": { + "startup_delay": 1500, + "message_timeout": 30000, + "max_message_size": 1048576, + "heartbeat_interval": 30000, + "heartbeat_timeout": 5000, + "heartbeat_failure_threshold": 3, + "log_level": "info", + "debug_mode": false + }, + "security": { + "max_queue_size_per_agent": 1000, + "max_messages_per_agent": 5000, + "max_sse_connections_per_agent": 5, + "rate_limit_enabled": true, + "max_requests_per_minute_per_agent": 100, + "sanitize_input": true, + "bypass_localhost": true + }, + "monitor": { + "pane_detection_max_retries": 5, + "pane_cache_duration": 300000, + "auto_recovery_interval": 60000, + "sse_reconnect_initial_delay": 200, + "sse_reconnect_max_delay": 5000 + }, + "a2aInjection": { + "chunkSize": 8192, + "chunkDelayMs": 5, + "scaledDelayBaseMs": 50, + "scaledDelayPerChunkMs": 40, + "scaledDelayMinMs": 100, + "busyFlagTimeoutMs": 30000, + "injectionTimeoutMs": 600000, + "debugMode": false + } +} diff --git a/wizard.sh b/wizard.sh new file mode 100755 index 0000000..bd9b221 --- /dev/null +++ b/wizard.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ Context Studio Wizard — Project Setup ║ +# ╚══════════════════════════════════════════════════════════════════╝ +set -euo pipefail + +WIZARD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$WIZARD_DIR/lib/utils.sh" +source "$WIZARD_DIR/lib/core.sh" +source "$WIZARD_DIR/lib/project.sh" +source "$WIZARD_DIR/lib/workflow.sh" + +# ── Prerequisites ────────────────────────────────────────────────────────── +check_prerequisites() { + header "Checking Prerequisites" + require_cmd git + if command -v docker &>/dev/null; then + CONTAINER_CMD="docker" + elif command -v podman &>/dev/null; then + CONTAINER_CMD="podman" + else + die "Neither docker nor podman found. Install one to use devcontainers." + fi + success "git: $(git --version)" + success "$CONTAINER_CMD: $($CONTAINER_CMD --version | head -1)" +} + +# ── Project info ─────────────────────────────────────────────────────────── +collect_project_info() { + header "Project Details" + + ask PROJECT_NAME "Project name" "" + [[ -z "$PROJECT_NAME" ]] && die "Project name is required." + + local default_dir="$HOME/projects/$(slugify "$PROJECT_NAME")" + ask PROJECT_DIR "Project location" "$default_dir" + [[ -z "$PROJECT_DIR" ]] && die "Project location is required." + PROJECT_DIR="${PROJECT_DIR/#\~/$HOME}" + + if [[ -e "$PROJECT_DIR" ]]; then + warn "Directory already exists: $PROJECT_DIR" + ask_yn _continue "Continue anyway?" "n" + [[ "$_continue" != "y" ]] && die "Aborted." + fi +} + +# ── Workflow source ──────────────────────────────────────────────────────── +collect_workflow_info() { + header "Workflow Configuration" + + ask_choice WORKFLOW_SOURCE "How do you want to configure your workflow?" \ + "Generate from scratch" \ + "Clone from existing repo" + + if [[ "$WORKFLOW_SOURCE" == "Clone from existing repo" ]]; then + ask WORKFLOW_REPO "Workflow repo URL" "" + [[ -z "$WORKFLOW_REPO" ]] && die "Repo URL is required." + else + ask PROJECT_DESC "Project description" "A software project" + ask TECH_STACK "Tech stack (e.g. Node.js, Rust, Python)" "Node.js" + ask_choice AGENT_PRESET "Agent preset" \ + "minimal (5 agents: coordinator, 2 coders, researcher, tester)" \ + "standard (9 agents: 2 coordinators, 3 coders, 2 researchers, tester, reviewer)" + # Normalize to short key + case "$AGENT_PRESET" in + minimal*) AGENT_PRESET="minimal" ;; + *) AGENT_PRESET="standard" ;; + esac + fi +} + +# ── Summary & confirm ────────────────────────────────────────────────────── +confirm_summary() { + header "Summary" + echo -e " ${BOLD}Project name${RESET} : $PROJECT_NAME" + echo -e " ${BOLD}Location${RESET} : $PROJECT_DIR" + echo -e " ${BOLD}Core${RESET} : $CS_CORE_DIR" + if [[ "${WORKFLOW_SOURCE:-}" == "Clone from existing repo" ]]; then + echo -e " ${BOLD}Workflow${RESET} : clone $WORKFLOW_REPO" + else + echo -e " ${BOLD}Workflow${RESET} : generate ($AGENT_PRESET preset)" + echo -e " ${BOLD}Description${RESET} : ${PROJECT_DESC:-}" + echo -e " ${BOLD}Tech stack${RESET} : ${TECH_STACK:-}" + fi + echo "" + ask_yn _ok "Create project?" "y" + [[ "$_ok" != "y" ]] && die "Aborted." +} + +# ── Build ────────────────────────────────────────────────────────────────── +build_project() { + header "Building Project" + + create_project_structure "$PROJECT_DIR" "$PROJECT_NAME" + create_devcontainer "$PROJECT_DIR" "$PROJECT_NAME" + + 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" + fi + + # Init git repo + 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")" + + header "Done!" + success "Project created at: $PROJECT_DIR" + echo "" + echo -e "${BOLD}Next steps:${RESET}" + echo "" + echo -e " ${CYAN}Option A — VS Code devcontainer:${RESET}" + echo -e " code \"$PROJECT_DIR\"" + echo -e " → \"Reopen in Container\"" + echo "" + echo -e " ${CYAN}Option B — CLI:${RESET}" + echo -e " cd \"$PROJECT_DIR\"" + echo -e " $CONTAINER_CMD build -t $slug .devcontainer/" + echo -e " $CONTAINER_CMD run -it --rm \\" + echo -e " -v \"\$(pwd)\":/workspace \\" + echo -e " -v \"\$HOME/.context-studio/core\":/opt/context-studio/core \\" + echo -e " -e ANTHROPIC_API_KEY=\"\$ANTHROPIC_API_KEY\" \\" + echo -e " $slug bash" + echo "" + echo -e " ${CYAN}Inside container — start Context Studio:${RESET}" + echo -e " node \$CS_CORE_DIR/core/start.js" + echo -e " # or headless (no Electron):" + echo -e " node \$CS_CORE_DIR/core/start.js --ui-mode=headless" + echo "" +} + +# ── Main ─────────────────────────────────────────────────────────────────── +main() { + echo "" + echo -e "${BOLD}${CYAN}╔══════════════════════════════════════╗${RESET}" + echo -e "${BOLD}${CYAN}║ Context Studio Wizard v1.0 ║${RESET}" + echo -e "${BOLD}${CYAN}╚══════════════════════════════════════╝${RESET}" + + check_prerequisites + setup_core + collect_project_info + collect_workflow_info + confirm_summary + build_project + print_next_steps +} + +main "$@"