From dcc7a2aed7181fc46295eb62916100992386e673 Mon Sep 17 00:00:00 2001 From: Karamelmar Date: Mon, 9 Mar 2026 12:29:38 +0100 Subject: [PATCH] Implement Option B: CS Core on host, agents inside container - New lib/container.sh generates three files per project: - bin/claude: wrapper that routes every claude call into the agents container via `podman/docker exec --workdir $PWD` - start.sh: builds image if missing, starts detached container with project mounted at same absolute path as host, prepends bin/ to PATH, then launches CS Core on the host - stop.sh: stops and removes the agents container - Container mounts project at identical host path so CS Core's working directory paths resolve correctly inside the container - TTY detection in wrapper: uses -it when stdin is a terminal, -i otherwise (node-pty compatibility) - Container stopped automatically when CS Core exits Co-Authored-By: Claude Sonnet 4.6 --- lib/container.sh | 114 +++++++++++++++++++++++++++++++++++++++++++++++ wizard.sh | 34 +++++++------- 2 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 lib/container.sh diff --git a/lib/container.sh b/lib/container.sh new file mode 100644 index 0000000..94c4b65 --- /dev/null +++ b/lib/container.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# container.sh — generate Option B container scripts for a project + +generate_container_scripts() { + local project_dir="$1" + local project_name="$2" + local slug="$3" + local container_name="cs-${slug}" + + info "Generating container scripts (Option B)..." + + mkdir -p "$project_dir/bin" + + # ── bin/claude — wrapper that runs claude inside the agents container ── + cat > "$project_dir/bin/claude" <&2 + exit 1 +fi + +if ! "\$RUNTIME" container inspect "\$CONTAINER_NAME" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then + echo "[claude-wrapper] Error: container '\$CONTAINER_NAME' is not running." >&2 + echo "[claude-wrapper] Run ./start.sh from your project directory first." >&2 + exit 1 +fi + +# Pass through TTY if available, relay working directory into container +if [ -t 0 ]; then + exec "\$RUNTIME" exec -it --workdir "\$PWD" "\$CONTAINER_NAME" claude "\$@" +else + exec "\$RUNTIME" exec -i --workdir "\$PWD" "\$CONTAINER_NAME" claude "\$@" +fi +WRAPPER + chmod +x "$project_dir/bin/claude" + + # ── start.sh — build image, start container, launch CS Core ────────── + cat > "$project_dir/start.sh" <&2; exit 1 +fi +if [[ ! -d "\$CS_CORE" ]]; then + echo "Error: context-studio-core not found at \$CS_CORE" >&2 + echo "Run the Context Studio Wizard first." >&2; exit 1 +fi + +# ── Build image if missing ─────────────────────────────────────────────── +if ! "\$RUNTIME" image exists "\$IMAGE_NAME" 2>/dev/null; then + echo "→ Building container image '\$IMAGE_NAME'..." + "\$RUNTIME" build -t "\$IMAGE_NAME" "\$PROJECT_DIR/.devcontainer/" \ + || { echo "Image build failed." >&2; exit 1; } +fi + +# ── Stop stale container ───────────────────────────────────────────────── +"\$RUNTIME" rm -f "\$CONTAINER_NAME" 2>/dev/null || true + +# ── Start agents container ─────────────────────────────────────────────── +# Mount project at the same absolute path so host and container paths match. +# CS Core sets agent working dirs to host paths; the wrapper relays PWD. +echo "→ Starting agents container '\$CONTAINER_NAME'..." +"\$RUNTIME" run -d \\ + --name "\$CONTAINER_NAME" \\ + -v "\$PROJECT_DIR:\$PROJECT_DIR" \\ + -v "\$HOME/.anthropic:\$HOME/.anthropic:ro" \\ + -e ANTHROPIC_API_KEY="\${ANTHROPIC_API_KEY:-}" \\ + -e CS_WORKFLOW_DIR="\$PROJECT_DIR/workflow" \\ + -e PROJECT_ROOT_DIR="\$PROJECT_DIR" \\ + "\$IMAGE_NAME" \\ + sleep infinity + +echo "→ Container ready." + +# ── Put claude wrapper first on PATH ──────────────────────────────────── +export PATH="\$PROJECT_DIR/bin:\$PATH" + +# ── Launch CS Core on the host ─────────────────────────────────────────── +echo "→ Starting Context Studio Core..." +CS_CORE_DIR="\$CS_CORE" \\ +CS_WORKFLOW_DIR="\$PROJECT_DIR/workflow" \\ +PROJECT_ROOT_DIR="\$PROJECT_DIR" \\ +node "\$CS_CORE/core/start.js" + +# ── CS Core exited — stop container ───────────────────────────────────── +echo "→ Stopping agents container..." +"\$RUNTIME" rm -f "\$CONTAINER_NAME" 2>/dev/null || true +START + chmod +x "$project_dir/start.sh" + + # ── stop.sh — forcefully stop the container ─────────────────────────── + cat > "$project_dir/stop.sh" </dev/null && echo "→ Done." || echo "→ Container was not running." +STOP + chmod +x "$project_dir/stop.sh" + + success "Generated: start.sh stop.sh bin/claude" +} diff --git a/wizard.sh b/wizard.sh index e8b8656..82595cd 100755 --- a/wizard.sh +++ b/wizard.sh @@ -11,6 +11,7 @@ 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() { @@ -91,8 +92,12 @@ confirm_summary() { build_project() { header "Building Project" + 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:-}" @@ -120,25 +125,22 @@ print_next_steps() { echo -e "${BOLD}${GREEN} ➜ Enter your project now:${RESET}" echo -e "${BOLD}${CYAN} cd \"$PROJECT_DIR\"${RESET}" echo "" - echo -e "${BOLD}Next steps:${RESET}" + echo -e "${BOLD}Start everything:${RESET}" + echo -e " ${CYAN}./start.sh${RESET}" + echo -e " Builds the image (first time), starts the agents container," + echo -e " then launches Context Studio Core on your host." echo "" - echo -e " ${CYAN}Option A — VS Code devcontainer:${RESET}" - echo -e " code \"$PROJECT_DIR\"" - echo -e " → \"Reopen in Container\"" + echo -e "${BOLD}Stop the container:${RESET}" + echo -e " ${CYAN}./stop.sh${RESET}" echo "" - echo -e " ${CYAN}Option B — CLI (must be inside the project folder):${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 -e "${BOLD}How it works:${RESET}" + echo -e " • Context Studio Core runs on your host (Electron UI, no display issues)" + echo -e " • Claude Code agents run inside container ${CYAN}cs-${slug}${RESET}" + echo -e " • ${CYAN}bin/claude${RESET} intercepts every agent call and routes it into the container" + echo -e " • Project files are mounted at the same path on host and in container" 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 -e "${BOLD}VS Code (for editing):${RESET}" + echo -e " ${CYAN}code \"$PROJECT_DIR\"${RESET} → \"Reopen in Container\"" echo "" }