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 <noreply@anthropic.com>
This commit is contained in:
parent
48011dd915
commit
dcc7a2aed7
2 changed files with 132 additions and 16 deletions
114
lib/container.sh
Normal file
114
lib/container.sh
Normal file
|
|
@ -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" <<WRAPPER
|
||||
#!/usr/bin/env bash
|
||||
# Claude Code wrapper — runs claude inside the agents container.
|
||||
# CS Core on the host calls this instead of the real claude binary.
|
||||
CONTAINER_NAME="${container_name}"
|
||||
RUNTIME="\$(command -v podman || command -v docker || true)"
|
||||
|
||||
if [[ -z "\$RUNTIME" ]]; then
|
||||
echo "[claude-wrapper] Error: podman or docker not found" >&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" <<START
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="\$SCRIPT_DIR"
|
||||
CONTAINER_NAME="${container_name}"
|
||||
IMAGE_NAME="${slug}"
|
||||
RUNTIME="\$(command -v podman || command -v docker || true)"
|
||||
CS_CORE="\${CS_CORE_DIR:-\$HOME/.context-studio/core}"
|
||||
|
||||
if [[ -z "\$RUNTIME" ]]; then
|
||||
echo "Error: podman or docker not found." >&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" <<STOP
|
||||
#!/usr/bin/env bash
|
||||
CONTAINER_NAME="${container_name}"
|
||||
RUNTIME="\$(command -v podman || command -v docker || true)"
|
||||
echo "→ Stopping \$CONTAINER_NAME..."
|
||||
"\$RUNTIME" rm -f "\$CONTAINER_NAME" 2>/dev/null && echo "→ Done." || echo "→ Container was not running."
|
||||
STOP
|
||||
chmod +x "$project_dir/stop.sh"
|
||||
|
||||
success "Generated: start.sh stop.sh bin/claude"
|
||||
}
|
||||
34
wizard.sh
34
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 ""
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue