#!/usr/bin/env bash # # newproject - Interactive project scaffolding wizard # Uses gum for beautiful terminal UI # set -e # Config PROJECTS_DIR="$HOME/Projects" STIGNORE_FILE="$PROJECTS_DIR/.stignore" # Source central config if available [ -f ~/.secrets ] && source ~/.secrets [ -f ~/.hosts ] && source ~/.hosts ####################################### # Check/Install gum ####################################### if ! command -v gum &> /dev/null; then echo "Installing gum (terminal UI toolkit)..." if command -v brew &> /dev/null; then brew install gum else echo "❌ Please install gum: https://github.com/charmbracelet/gum" exit 1 fi fi ####################################### # Header ####################################### clear gum style \ --foreground 212 --border-foreground 212 --border double \ --align center --width 60 --margin "1 2" --padding "1 2" \ '🚀 New Project Wizard' '' 'Create a new project with Claude Code' ####################################### # Project Name ####################################### echo "" gum style --foreground 245 "Enter your project name (lowercase, hyphens ok):" PROJECT_NAME=$(gum input --placeholder "my-awesome-project" --width 40) if [ -z "$PROJECT_NAME" ]; then gum style --foreground 196 "❌ Project name is required" exit 1 fi # Validate name if [[ ! "$PROJECT_NAME" =~ ^[a-z0-9-]+$ ]]; then gum style --foreground 196 "❌ Project name must be lowercase alphanumeric with hyphens only" exit 1 fi PROJECT_PATH="$PROJECTS_DIR/$PROJECT_NAME" if [ -d "$PROJECT_PATH" ]; then gum style --foreground 196 "❌ Project already exists at $PROJECT_PATH" exit 1 fi ####################################### # Project Type ####################################### echo "" gum style --foreground 245 "Select project type:" PROJECT_TYPE=$(gum choose \ "python-fastapi" \ "python-generic" \ "typescript-react" \ "typescript-node" \ "generic" \ --header "Project Type") ####################################### # Project Brief (Multi-line) ####################################### echo "" gum style --foreground 245 "Describe your project (Ctrl+D or Esc when done):" gum style --foreground 240 --italic "Include goals, features, tech preferences, constraints..." echo "" PROJECT_BRIEF=$(gum write --placeholder "Describe your project in detail..." --width 70 --height 8) # Extract first line as short description PROJECT_DESC=$(echo "$PROJECT_BRIEF" | head -1) ####################################### # MCP Selection ####################################### echo "" gum style --foreground 245 "Select MCPs to enable (space to select, enter to confirm):" MCPS=$(gum choose --no-limit \ "exa (web search, research)" \ "Ref (documentation lookup)" \ "ticktick (task management)" \ "beeper (messaging)" \ "airtable (database)" \ "claude-in-chrome (browser automation)" \ "shopping (Amazon)" \ "proton-mail (email)" \ "weather (forecasts)" \ --header "MCPs (space=select, enter=confirm)" \ --selected "exa (web search, research),Ref (documentation lookup)") ####################################### # Git Remote Options ####################################### echo "" gum style --foreground 245 "Git repository options:" GIT_INIT=$(gum choose "Yes" "No" --header "Initialize git repository?") GIT_REMOTE="None" if [ "$GIT_INIT" = "Yes" ]; then GIT_REMOTE=$(gum choose \ "Gitea (git.htsn.io) - Private" \ "GitHub (github.com) - Public" \ "Both (Gitea + GitHub)" \ "None (local only)" \ --header "Where to push?") fi ####################################### # Syncthing Options ####################################### echo "" gum style --foreground 245 "Syncthing sync options:" SYNCTHING_OPT=$(gum choose \ "Exclude from sync (recommended for git repos)" \ "Include in sync" \ --header "Sync this project via Syncthing?") ####################################### # Network/Traefik Options ####################################### echo "" gum style --foreground 245 "Does this project need web access (subdomain routing)?" NETWORK_ACCESS=$(gum choose \ "No web access needed" \ "Local network only (10.10.10.x)" \ "Local + Tailscale (remote access)" \ "Public Internet (via Cloudflare)" \ --header "Network access level") SUBDOMAIN="" DEPLOY_TARGET="" if [ "$NETWORK_ACCESS" != "No web access needed" ]; then echo "" gum style --foreground 245 "Subdomain for this project:" SUBDOMAIN=$(gum input --placeholder "$PROJECT_NAME" --value "$PROJECT_NAME" --width 30) SUBDOMAIN="${SUBDOMAIN}.htsn.io" echo "" gum style --foreground 245 "Deployment target:" DEPLOY_TARGET=$(gum choose \ "docker-host (10.10.10.206)" \ "trading-vm (10.10.10.221)" \ "saltbox (10.10.10.100)" \ "Other/Manual" \ --header "Where will this deploy?") fi ####################################### # Database Options ####################################### echo "" gum style --foreground 245 "Database requirements:" DATABASE=$(gum choose \ "None" \ "SQLite (local file)" \ "PostgreSQL" \ "TimescaleDB (time-series)" \ "Redis (cache/queue)" \ "PostgreSQL + Redis" \ --header "Database") ####################################### # Additional Options ####################################### echo "" gum style --foreground 245 "Additional options:" ADDITIONAL=$(gum choose --no-limit \ "spec-kit (spec-driven development)" \ "Docker support" \ "pre-commit hooks" \ "GitHub Actions CI" \ ".env.example (from ~/.secrets)" \ --header "Additional features (space=select)") ####################################### # License ####################################### echo "" gum style --foreground 245 "License:" LICENSE=$(gum choose \ "MIT" \ "Apache 2.0" \ "Proprietary (no license file)" \ "None" \ --header "License type") ####################################### # Confirm ####################################### echo "" # Build summary SUMMARY="📋 Project Summary Name: $PROJECT_NAME Type: $PROJECT_TYPE Path: $PROJECT_PATH MCPs: $(echo "$MCPS" | tr '\n' ', ' | sed 's/, $//') Git: $GIT_INIT | Remote: $GIT_REMOTE Syncthing: $SYNCTHING_OPT Database: $DATABASE License: $LICENSE" if [ -n "$SUBDOMAIN" ]; then SUMMARY="$SUMMARY Network: $NETWORK_ACCESS Subdomain: $SUBDOMAIN Deploy to: $DEPLOY_TARGET" fi gum style --foreground 212 --border-foreground 212 --border rounded \ --align left --width 65 --margin "1 0" --padding "1 2" \ "$SUMMARY" echo "" CONFIRM=$(gum choose "Create Project" "Cancel" --header "Proceed?") if [ "$CONFIRM" = "Cancel" ]; then gum style --foreground 196 "❌ Cancelled" exit 0 fi ####################################### # Create Project ####################################### echo "" gum spin --spinner dot --title "Creating project directory..." -- sleep 0.5 mkdir -p "$PROJECT_PATH" mkdir -p "$PROJECT_PATH/src" mkdir -p "$PROJECT_PATH/tests" mkdir -p "$PROJECT_PATH/docs" ####################################### # Save Project Brief ####################################### if [ -n "$PROJECT_BRIEF" ]; then gum spin --spinner dot --title "Saving project brief..." -- sleep 0.2 cat > "$PROJECT_PATH/docs/PROJECT_BRIEF.md" << BRIEF_EOF # $PROJECT_NAME - Project Brief $PROJECT_BRIEF --- *Generated by newproject wizard on $(date '+%Y-%m-%d')* BRIEF_EOF fi ####################################### # Generate .claude/settings.json ####################################### gum spin --spinner dot --title "Generating MCP configuration..." -- sleep 0.3 mkdir -p "$PROJECT_PATH/.claude" ALL_MCPS=("exa" "Ref" "ticktick" "beeper" "airtable" "claude-in-chrome" "shopping" "proton-mail" "weather") cat > "$PROJECT_PATH/.claude/settings.json" << 'SETTINGS_EOF' { "mcpServers": { SETTINGS_EOF first=true for mcp in "${ALL_MCPS[@]}"; do if ! echo "$MCPS" | grep -qi "^$mcp "; then if [ "$first" = true ]; then first=false else echo "," >> "$PROJECT_PATH/.claude/settings.json" fi printf ' "%s": { "disabled": true }' "$mcp" >> "$PROJECT_PATH/.claude/settings.json" fi done cat >> "$PROJECT_PATH/.claude/settings.json" << 'SETTINGS_EOF' } } SETTINGS_EOF ####################################### # Generate CLAUDE.md ####################################### gum spin --spinner dot --title "Generating CLAUDE.md..." -- sleep 0.3 case "$PROJECT_TYPE" in python-fastapi) TECH_STACK="Python 3.12+, FastAPI, SQLModel, Pydantic, Alembic" TYPE_CHECK_CMD="mypy --strict src/" LINT_CMD="ruff check src/ && black src/" TEST_CMD="pytest" INSTALL_CMD="pip install -e '.[dev]'" DEV_CMD="uvicorn src.main:app --reload" ;; python-generic) TECH_STACK="Python 3.12+" TYPE_CHECK_CMD="mypy --strict src/" LINT_CMD="ruff check src/ && black src/" TEST_CMD="pytest" INSTALL_CMD="pip install -e '.[dev]'" DEV_CMD="python -m src.main" ;; typescript-react) TECH_STACK="TypeScript, React 18, Bun, TailwindCSS" TYPE_CHECK_CMD="bun run tsc --noEmit" LINT_CMD="bun run lint" TEST_CMD="bun test" INSTALL_CMD="bun install" DEV_CMD="bun run dev" ;; typescript-node) TECH_STACK="TypeScript, Node.js/Bun" TYPE_CHECK_CMD="bun run tsc --noEmit" LINT_CMD="bun run lint" TEST_CMD="bun test" INSTALL_CMD="bun install" DEV_CMD="bun run dev" ;; generic) TECH_STACK="[Customize]" TYPE_CHECK_CMD="# Add type check command" LINT_CMD="# Add lint command" TEST_CMD="# Add test command" INSTALL_CMD="# Add install command" DEV_CMD="# Add dev command" ;; esac USE_SPECKIT="No" if echo "$ADDITIONAL" | grep -q "spec-kit"; then USE_SPECKIT="Yes" fi cat > "$PROJECT_PATH/CLAUDE.md" << CLAUDE_EOF # $PROJECT_NAME - Claude Code Guidelines ## Project Overview $PROJECT_DESC **Tech Stack**: $TECH_STACK **Key Directories**: - \`src/\` - Main source code - \`tests/\` - Test files - \`docs/\` - Documentation (see \`docs/PROJECT_BRIEF.md\` for full context) --- ## Type Safety (Strict Enforcement) **Type safety is non-negotiable. All code must pass strict type checking.** \`\`\`bash # Required before every commit $TYPE_CHECK_CMD \`\`\` - Complete type hints on ALL functions (params + return types) - No \`any\` types (TypeScript) or \`# type: ignore\` without justification - Money/prices: Always \`Decimal\`, never \`float\` --- ## Required Tool Usage ### 1. Documentation Lookup (Priority Order) \`\`\` 1. Ref Tools (private docs) → mcp__Ref__ref_search_documentation(query="topic ref_src=private") 2. Ref Tools (public docs) → mcp__Ref__ref_search_documentation(query="topic") 3. Exa Tools → mcp__exa__get_code_context_exa(query="...") (last resort) \`\`\` ### 2. Code Review Run after completing significant features: \`\`\` /code-review:code-review \`\`\` ### 3. Git Commits \`\`\` /commit-commands:commit /commit-commands:commit-push-pr # For complete workflow \`\`\` ### 4. Frontend Design (if applicable) \`\`\` /frontend-design:frontend-design \`\`\` --- ## Pre-Commit Checklist \`\`\`bash $TYPE_CHECK_CMD $LINT_CMD $TEST_CMD \`\`\` --- CLAUDE_EOF if [ "$USE_SPECKIT" = "Yes" ]; then cat >> "$PROJECT_PATH/CLAUDE.md" << 'SPECKIT_EOF' ## Spec-Driven Development **All features and significant changes should follow spec-driven development.** ``` 1. /speckit.specify → Define requirements (what & why) 2. /speckit.plan → Specify technology and architecture 3. /speckit.tasks → Break plan into actionable tasks 4. /speckit.implement → Build features according to plan 5. /speckit.clarify → Resolve ambiguities as needed ``` --- SPECKIT_EOF fi cat >> "$PROJECT_PATH/CLAUDE.md" << MCP_EOF ## MCP Configuration **Enabled MCPs for this project:** $(echo "$MCPS" | sed 's/^/- /') **Disabled MCPs** are configured in \`.claude/settings.json\`. --- ## Central Configuration Reference | File | Purpose | Usage | |------|---------|-------| | \`~/.secrets\` | API keys, tokens, credentials | \`source ~/.secrets\` then use \`\$VAR_NAME\` | | \`~/.hosts\` | IPs, hostnames, service URLs | \`source ~/.hosts\` then use \`\$IP_*\` or \`\$HOST_*\` | | \`~/.ssh/config\` | SSH aliases for all homelab hosts | \`ssh pve\`, \`ssh truenas\`, \`ssh docker-host\`, etc. | **When adding new credentials or hosts:** 1. Add to the central files (\`~/.secrets\` or \`~/.hosts\`) 2. Files sync via Syncthing to all machines 3. Never commit secrets to git - use environment variables MCP_EOF # Add deployment info if applicable if [ -n "$SUBDOMAIN" ]; then cat >> "$PROJECT_PATH/CLAUDE.md" << DEPLOY_EOF --- ## Deployment | Setting | Value | |---------|-------| | **Subdomain** | https://$SUBDOMAIN | | **Deploy Target** | $DEPLOY_TARGET | | **Network Access** | $NETWORK_ACCESS | **Deploy command:** \`\`\`bash ./scripts/deploy.sh \`\`\` DEPLOY_EOF fi cat >> "$PROJECT_PATH/CLAUDE.md" << 'NOTES_EOF' --- ## Project-Specific Notes [Add project-specific patterns, gotchas, and conventions here] NOTES_EOF ####################################### # Generate README.md ####################################### gum spin --spinner dot --title "Generating README.md..." -- sleep 0.2 cat > "$PROJECT_PATH/README.md" << README_EOF # $PROJECT_NAME $PROJECT_DESC ## Quick Start \`\`\`bash # Install dependencies $INSTALL_CMD # Run development server $DEV_CMD # Run tests $TEST_CMD \`\`\` ## Development See [CLAUDE.md](CLAUDE.md) for development guidelines and conventions. See [docs/PROJECT_BRIEF.md](docs/PROJECT_BRIEF.md) for full project context. README_EOF ####################################### # Generate .gitignore ####################################### gum spin --spinner dot --title "Generating .gitignore..." -- sleep 0.2 cat > "$PROJECT_PATH/.gitignore" << 'GITIGNORE_EOF' # Dependencies node_modules/ .venv/ venv/ __pycache__/ *.pyc # Build outputs dist/ build/ .next/ *.egg-info/ # Environment .env .env.local .env.*.local # IDE .idea/ .vscode/ *.swp *.swo # OS .DS_Store Thumbs.db # Testing coverage/ .coverage htmlcov/ .pytest_cache/ # Logs *.log logs/ GITIGNORE_EOF ####################################### # Generate .env.example ####################################### if echo "$ADDITIONAL" | grep -q ".env.example"; then gum spin --spinner dot --title "Generating .env.example..." -- sleep 0.3 cat > "$PROJECT_PATH/.env.example" << 'ENVEX_EOF' # Copy this to .env and fill in values # Most values available in ~/.secrets # Application ENV=development LOG_LEVEL=INFO ENVEX_EOF # Add relevant env vars based on selections if echo "$MCPS" | grep -qi "exa"; then echo "# Exa (web search)" >> "$PROJECT_PATH/.env.example" echo "EXA_API_KEY=\${EXA_API_KEY}" >> "$PROJECT_PATH/.env.example" echo "" >> "$PROJECT_PATH/.env.example" fi if [ "$DATABASE" = "PostgreSQL" ] || [ "$DATABASE" = "PostgreSQL + Redis" ] || [ "$DATABASE" = "TimescaleDB (time-series)" ]; then echo "# Database" >> "$PROJECT_PATH/.env.example" echo "DATABASE_URL=postgresql://user:password@localhost:5432/$PROJECT_NAME" >> "$PROJECT_PATH/.env.example" echo "" >> "$PROJECT_PATH/.env.example" fi if [ "$DATABASE" = "Redis (cache/queue)" ] || [ "$DATABASE" = "PostgreSQL + Redis" ]; then echo "# Redis" >> "$PROJECT_PATH/.env.example" echo "REDIS_URL=redis://localhost:6379" >> "$PROJECT_PATH/.env.example" echo "" >> "$PROJECT_PATH/.env.example" fi fi ####################################### # License ####################################### if [ "$LICENSE" = "MIT" ]; then gum spin --spinner dot --title "Adding MIT license..." -- sleep 0.2 cat > "$PROJECT_PATH/LICENSE" << 'MIT_EOF' MIT License Copyright (c) 2026 Hutson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. MIT_EOF elif [ "$LICENSE" = "Apache 2.0" ]; then gum spin --spinner dot --title "Adding Apache 2.0 license..." -- sleep 0.2 cat > "$PROJECT_PATH/LICENSE" << 'APACHE_EOF' Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ Copyright 2026 Hutson Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. APACHE_EOF fi ####################################### # Docker support ####################################### if echo "$ADDITIONAL" | grep -q "Docker support"; then gum spin --spinner dot --title "Adding Docker support..." -- sleep 0.3 if [[ "$PROJECT_TYPE" == python* ]]; then cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF' FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "-m", "src.main"] DOCKER_EOF else cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF' FROM oven/bun:1 WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . CMD ["bun", "run", "start"] DOCKER_EOF fi # docker-compose with database if selected cat > "$PROJECT_PATH/docker-compose.yml" << COMPOSE_EOF version: '3.8' services: app: build: . ports: - "8000:8000" environment: - ENV=development volumes: - .:/app COMPOSE_EOF if [ "$DATABASE" = "PostgreSQL" ] || [ "$DATABASE" = "PostgreSQL + Redis" ]; then cat >> "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_PG_EOF' depends_on: - postgres postgres: image: postgres:16-alpine environment: POSTGRES_USER: app POSTGRES_PASSWORD: changeme POSTGRES_DB: app volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" volumes: postgres_data: COMPOSE_PG_EOF fi if [ "$DATABASE" = "Redis (cache/queue)" ] || [ "$DATABASE" = "PostgreSQL + Redis" ]; then cat >> "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_REDIS_EOF' redis: image: redis:7-alpine ports: - "6379:6379" COMPOSE_REDIS_EOF fi if [ "$DATABASE" = "TimescaleDB (time-series)" ]; then cat >> "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_TS_EOF' depends_on: - timescaledb timescaledb: image: timescale/timescaledb:latest-pg16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: changeme POSTGRES_DB: app volumes: - timescale_data:/var/lib/postgresql/data ports: - "5432:5432" volumes: timescale_data: COMPOSE_TS_EOF fi fi ####################################### # Spec-kit structure ####################################### if [ "$USE_SPECKIT" = "Yes" ]; then gum spin --spinner dot --title "Initializing spec-kit structure..." -- sleep 0.3 mkdir -p "$PROJECT_PATH/specs" mkdir -p "$PROJECT_PATH/plans" mkdir -p "$PROJECT_PATH/tasks" touch "$PROJECT_PATH/specs/.gitkeep" touch "$PROJECT_PATH/plans/.gitkeep" touch "$PROJECT_PATH/tasks/.gitkeep" fi ####################################### # Deploy script (if web access needed) ####################################### if [ -n "$SUBDOMAIN" ]; then gum spin --spinner dot --title "Creating deploy script..." -- sleep 0.3 mkdir -p "$PROJECT_PATH/scripts" # Extract deploy host case "$DEPLOY_TARGET" in "docker-host"*) DEPLOY_HOST="docker-host" ;; "trading-vm"*) DEPLOY_HOST="trading-vm" ;; "saltbox"*) DEPLOY_HOST="saltbox" ;; *) DEPLOY_HOST="docker-host" ;; esac cat > "$PROJECT_PATH/scripts/deploy.sh" << DEPLOY_EOF #!/usr/bin/env bash # Deploy $PROJECT_NAME to $DEPLOY_HOST set -e DEPLOY_HOST="$DEPLOY_HOST" PROJECT_NAME="$PROJECT_NAME" SUBDOMAIN="$SUBDOMAIN" echo "🚀 Deploying \$PROJECT_NAME to \$DEPLOY_HOST..." # Build and push docker build -t \$PROJECT_NAME:latest . # Copy to remote (or use registry) # docker save \$PROJECT_NAME:latest | ssh \$DEPLOY_HOST docker load echo "✅ Deployed to https://\$SUBDOMAIN" DEPLOY_EOF chmod +x "$PROJECT_PATH/scripts/deploy.sh" # Generate Traefik config stub mkdir -p "$PROJECT_PATH/deploy" cat > "$PROJECT_PATH/deploy/traefik.yml" << TRAEFIK_EOF # Traefik dynamic configuration for $PROJECT_NAME # Copy to /opt/traefik/config/$PROJECT_NAME.yml on traefik host http: routers: $PROJECT_NAME: rule: "Host(\`$SUBDOMAIN\`)" service: $PROJECT_NAME entryPoints: - websecure tls: certResolver: letsencrypt services: $PROJECT_NAME: loadBalancer: servers: - url: "http://${DEPLOY_HOST}:8000" TRAEFIK_EOF # Note about network access in docs cat > "$PROJECT_PATH/docs/DEPLOYMENT.md" << NETDOC_EOF # Deployment Guide ## Network Access: $NETWORK_ACCESS **Subdomain:** https://$SUBDOMAIN **Deploy Target:** $DEPLOY_TARGET ### Setup Steps 1. **Deploy the application:** \`\`\`bash ./scripts/deploy.sh \`\`\` 2. **Configure Traefik:** Copy \`deploy/traefik.yml\` to the Traefik config directory: \`\`\`bash scp deploy/traefik.yml traefik:/opt/traefik/config/$PROJECT_NAME.yml \`\`\` NETDOC_EOF if [ "$NETWORK_ACCESS" = "Public Internet (via Cloudflare)" ]; then cat >> "$PROJECT_PATH/docs/DEPLOYMENT.md" << 'CFDOC_EOF' 3. **Add Cloudflare DNS:** - Add A record pointing to your public IP - Or use Cloudflare Tunnel for added security 4. **Verify SSL:** Traefik will auto-provision Let's Encrypt certificate CFDOC_EOF elif [ "$NETWORK_ACCESS" = "Local + Tailscale (remote access)" ]; then cat >> "$PROJECT_PATH/docs/DEPLOYMENT.md" << 'TSDOC_EOF' 3. **Tailscale Access:** - Ensure deploy target is on Tailscale network - Access via Tailscale IP or MagicDNS name 4. **No public DNS needed** - access via Tailscale only TSDOC_EOF else cat >> "$PROJECT_PATH/docs/DEPLOYMENT.md" << 'LOCDOC_EOF' 3. **Local Access Only:** - Add entry to local DNS (Pi-hole) or /etc/hosts - Access only from 10.10.10.x network LOCDOC_EOF fi fi ####################################### # Syncthing Configuration ####################################### if [ "$SYNCTHING_OPT" = "Exclude from sync (recommended for git repos)" ]; then gum spin --spinner dot --title "Adding to Syncthing ignore list..." -- sleep 0.3 if [ -f "$STIGNORE_FILE" ]; then # Check if already in ignore file if ! grep -q "^$PROJECT_NAME$" "$STIGNORE_FILE" 2>/dev/null; then echo "$PROJECT_NAME" >> "$STIGNORE_FILE" fi fi fi ####################################### # Git Init ####################################### if [ "$GIT_INIT" = "Yes" ]; then gum spin --spinner dot --title "Initializing git repository..." -- \ git -C "$PROJECT_PATH" init -q gum spin --spinner dot --title "Creating initial commit..." -- \ bash -c "cd '$PROJECT_PATH' && git add -A && git commit -q -m 'Initial project setup Generated with newproject wizard. Co-Authored-By: Claude Opus 4.5 '" fi ####################################### # Git Remotes ####################################### GITEA_URL="" GITHUB_URL="" if [[ "$GIT_REMOTE" == *"Gitea"* ]] && [ -n "$GITEA_TOKEN" ]; then gum spin --spinner dot --title "Creating Gitea repository..." -- sleep 0.5 RESPONSE=$(curl -s -X POST "https://git.htsn.io/api/v1/user/repos" \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"$PROJECT_NAME\", \"description\": \"$PROJECT_DESC\", \"private\": true, \"auto_init\": false }" 2>/dev/null) if echo "$RESPONSE" | grep -q "\"name\":\"$PROJECT_NAME\""; then git -C "$PROJECT_PATH" remote add origin "git@git.htsn.io:hutson/$PROJECT_NAME.git" 2>/dev/null || true gum spin --spinner dot --title "Pushing to Gitea..." -- \ git -C "$PROJECT_PATH" push -u origin main -q 2>/dev/null || true GITEA_URL="https://git.htsn.io/hutson/$PROJECT_NAME" fi fi if [[ "$GIT_REMOTE" == *"GitHub"* ]]; then if command -v gh &> /dev/null; then gum spin --spinner dot --title "Creating GitHub repository..." -- sleep 0.5 gh repo create "$PROJECT_NAME" --public --source="$PROJECT_PATH" --push 2>/dev/null || true GITHUB_URL="https://github.com/$(gh api user -q .login)/$PROJECT_NAME" # If both remotes, rename if [ -n "$GITEA_URL" ]; then git -C "$PROJECT_PATH" remote rename origin gitea 2>/dev/null || true git -C "$PROJECT_PATH" remote add github "git@github.com:$(gh api user -q .login)/$PROJECT_NAME.git" 2>/dev/null || true fi else GITHUB_URL="(gh CLI not installed - create manually)" fi fi ####################################### # Success! ####################################### echo "" gum style --foreground 82 --border-foreground 82 --border double \ --align center --width 60 --margin "1 2" --padding "1 2" \ '✅ Project Created Successfully!' \ '' \ "Path: $PROJECT_PATH" echo "" gum style --foreground 245 "Next steps:" echo "" echo " cd $PROJECT_PATH" if [ "$USE_SPECKIT" = "Yes" ]; then echo " /speckit.specify # Define requirements" else echo " # Review docs/PROJECT_BRIEF.md" echo " # Start coding!" fi if [ -n "$GITEA_URL" ] && [ "$GITEA_URL" != "(failed)" ]; then echo "" echo " Gitea: $GITEA_URL" fi if [ -n "$GITHUB_URL" ] && [[ "$GITHUB_URL" != *"not installed"* ]]; then echo "" echo " GitHub: $GITHUB_URL" fi if [ -n "$SUBDOMAIN" ]; then echo "" echo " Subdomain: https://$SUBDOMAIN (configure Traefik)" fi echo ""