Add Context Studio Wizard — project scaffolding CLI
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
cd5b28919c
commit
09ff27be92
19 changed files with 1132 additions and 1 deletions
28
lib/core.sh
Normal file
28
lib/core.sh
Normal file
|
|
@ -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"
|
||||
}
|
||||
101
lib/project.sh
Normal file
101
lib/project.sh
Normal file
|
|
@ -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" <<EOF
|
||||
# $project_name
|
||||
|
||||
## Quick Start
|
||||
|
||||
### First time setup
|
||||
\`\`\`bash
|
||||
# Open in devcontainer (VS Code)
|
||||
code .
|
||||
# → Reopen in Container
|
||||
|
||||
# Or via CLI
|
||||
docker build -t $( slugify "$project_name" ) .devcontainer/
|
||||
docker run -it --rm \\
|
||||
-v "\$(pwd)":/workspace \\
|
||||
-v "\$HOME/.context-studio/core":/opt/context-studio/core \\
|
||||
-e ANTHROPIC_API_KEY="\$ANTHROPIC_API_KEY" \\
|
||||
$( slugify "$project_name" ) bash
|
||||
\`\`\`
|
||||
|
||||
### Start Context Studio
|
||||
\`\`\`bash
|
||||
CS_WORKFLOW_DIR=/workspace/workflow node /opt/context-studio/core/core/start.js
|
||||
\`\`\`
|
||||
|
||||
### Start headless (no Electron UI)
|
||||
\`\`\`bash
|
||||
CS_WORKFLOW_DIR=/workspace/workflow node /opt/context-studio/core/core/start.js --ui-mode=headless
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
success "Project structure created"
|
||||
}
|
||||
|
||||
create_devcontainer() {
|
||||
local project_dir="$1"
|
||||
local project_name="$2"
|
||||
local slug
|
||||
slug="$(slugify "$project_name")"
|
||||
|
||||
info "Generating devcontainer config..."
|
||||
|
||||
# Dockerfile
|
||||
sed "s/{{PROJECT_SLUG}}/$slug/g" \
|
||||
"$WIZARD_DIR/templates/Dockerfile" \
|
||||
> "$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/"
|
||||
}
|
||||
73
lib/utils.sh
Normal file
73
lib/utils.sh
Normal file
|
|
@ -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/-$//'
|
||||
}
|
||||
166
lib/workflow.sh
Normal file
166
lib/workflow.sh
Normal file
|
|
@ -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" <<EOF
|
||||
{
|
||||
"name": "$(slugify "$project_name")",
|
||||
"version": "1.0.0",
|
||||
"description": "$project_desc",
|
||||
"techStack": "$tech_stack",
|
||||
"requirements": {
|
||||
"tools": [],
|
||||
"cliProviders": [
|
||||
{
|
||||
"name": "claude-code",
|
||||
"command": "claude",
|
||||
"versionFlag": "--version",
|
||||
"installHint": "npm install -g @anthropic-ai/claude-code"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# system.json
|
||||
cp "$WIZARD_DIR/templates/system.json" "$workflow_dir/system.json"
|
||||
|
||||
# agents.json — based on preset
|
||||
local agents_template
|
||||
case "$preset" in
|
||||
standard) agents_template="$WIZARD_DIR/templates/agents-standard.json" ;;
|
||||
*) agents_template="$WIZARD_DIR/templates/agents-minimal.json" ;;
|
||||
esac
|
||||
|
||||
sed "s/{{PROJECT_NAME}}/$project_name/g; s/{{PROJECT_DESC}}/$project_desc/g" \
|
||||
"$agents_template" > "$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" <<EOF
|
||||
# $project_name — Project Vision
|
||||
|
||||
## Description
|
||||
$project_desc
|
||||
|
||||
## Tech Stack
|
||||
$tech_stack
|
||||
|
||||
## Goals
|
||||
- [ ] Define requirements
|
||||
- [ ] Implement core features
|
||||
- [ ] Test and review
|
||||
|
||||
## Roadmap
|
||||
- Phase 1: Project setup and scaffolding
|
||||
- Phase 2: Core implementation
|
||||
- Phase 3: Testing and hardening
|
||||
EOF
|
||||
|
||||
# CLAUDE.md
|
||||
cat > "$workflow_dir/CLAUDE.md" <<EOF
|
||||
# $project_name
|
||||
|
||||
## Project Overview
|
||||
$project_desc
|
||||
|
||||
**Tech Stack**: $tech_stack
|
||||
|
||||
## Context Studio Setup
|
||||
- Workflow directory: \`CS_WORKFLOW_DIR\`
|
||||
- Core directory: \`CS_CORE_DIR\`
|
||||
- Start system: \`node \$CS_CORE_DIR/core/start.js\`
|
||||
|
||||
## Agent Roles
|
||||
See \`roles/\` directory for agent role definitions.
|
||||
Read \`project-docs/project-vision.md\` for project goals.
|
||||
|
||||
## Communication
|
||||
Use \`/sm <agent> "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" <<EOF
|
||||
{
|
||||
"model": "sonnet",
|
||||
"spinnerTipsEnabled": false,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(*)",
|
||||
"Read(*)",
|
||||
"Write(*)",
|
||||
"Edit(*)",
|
||||
"Glob(*)",
|
||||
"Grep(*)",
|
||||
"Task",
|
||||
"SlashCommand:/prime:*",
|
||||
"SlashCommand:/sm:*"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(node \$CS_CORE_DIR/core/stop.js:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# CLAUDE.md per agent
|
||||
cat > "$workflow_dir/agents/$agent_id/CLAUDE.md" <<EOF
|
||||
# Agent: $agent_id
|
||||
|
||||
Load your role with \`/prime\` at session start.
|
||||
EOF
|
||||
}
|
||||
|
||||
clone_workflow() {
|
||||
local project_dir="$1"
|
||||
local repo_url="$2"
|
||||
local workflow_dir="$project_dir/workflow"
|
||||
|
||||
info "Cloning workflow from $repo_url..."
|
||||
git clone "$repo_url" "$workflow_dir" \
|
||||
|| die "Failed to clone workflow repo: $repo_url"
|
||||
success "Workflow cloned to $workflow_dir"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue