Enhance project wizard with comprehensive options

New features:
- Multi-line project brief input (saved to docs/PROJECT_BRIEF.md)
- Git remote options: Gitea, GitHub, both, or neither
- Syncthing sync/exclude option (auto-updates .stignore)
- Network access levels: local, Tailscale, public Internet
- Subdomain routing with Traefik config generation
- Database selection: SQLite, PostgreSQL, TimescaleDB, Redis
- License picker: MIT, Apache 2.0, proprietary
- .env.example generation from selected MCPs/databases
- Deploy script and deployment docs for web projects
- Auto-install gum if missing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Hutson Cappelmann
2026-01-26 17:08:18 -05:00
parent 98235b12b8
commit 84996e4bf2

View File

@@ -1,26 +1,32 @@
#!/usr/bin/env bash
#
# newproject - Interactive project scaffolding tool
# newproject - Interactive project scaffolding wizard
# Uses gum for beautiful terminal UI
#
set -e
# Colors
CYAN='\033[0;36m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Config
PROJECTS_DIR="$HOME/Projects"
TEMPLATE_REPO="git@git.htsn.io:hutson/template-project.git"
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
#######################################
@@ -69,11 +75,16 @@ PROJECT_TYPE=$(gum choose \
--header "Project Type")
#######################################
# Project Description
# Project Brief (Multi-line)
#######################################
echo ""
gum style --foreground 245 "Brief project description:"
PROJECT_DESC=$(gum input --placeholder "A brief description of what this project does" --width 60)
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
@@ -81,7 +92,6 @@ PROJECT_DESC=$(gum input --placeholder "A brief description of what this project
echo ""
gum style --foreground 245 "Select MCPs to enable (space to select, enter to confirm):"
# Define available MCPs with descriptions
MCPS=$(gum choose --no-limit \
"exa (web search, research)" \
"Ref (documentation lookup)" \
@@ -96,18 +106,80 @@ MCPS=$(gum choose --no-limit \
--selected "exa (web search, research),Ref (documentation lookup)")
#######################################
# Git Options
# Git Remote Options
#######################################
echo ""
gum style --foreground 245 "Git repository options:"
GIT_INIT=$(gum choose "Yes" "No" --header "Initialize git repository?")
GITEA_CREATE="No"
GIT_REMOTE="None"
if [ "$GIT_INIT" = "Yes" ]; then
GITEA_CREATE=$(gum choose "Yes" "No" --header "Create repo on Gitea (git.htsn.io)?")
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
#######################################
@@ -116,28 +188,54 @@ gum style --foreground 245 "Additional options:"
ADDITIONAL=$(gum choose --no-limit \
"spec-kit (spec-driven development)" \
"Docker support" \
"pre-commit hooks" \
"GitHub Actions CI" \
"Docker support" \
".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 60 --margin "1 0" --padding "1 2" \
"📋 Project Summary" \
"" \
"Name: $PROJECT_NAME" \
"Type: $PROJECT_TYPE" \
"Path: $PROJECT_PATH" \
"Description: $PROJECT_DESC" \
"" \
"MCPs: $(echo "$MCPS" | tr '\n' ', ' | sed 's/, $//')" \
"" \
"Git: $GIT_INIT | Gitea: $GITEA_CREATE" \
"Extras: $(echo "$ADDITIONAL" | tr '\n' ', ' | sed 's/, $//')"
--align left --width 65 --margin "1 0" --padding "1 2" \
"$SUMMARY"
echo ""
CONFIRM=$(gum choose "Create Project" "Cancel" --header "Proceed?")
@@ -153,26 +251,35 @@ fi
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"
#######################################
# Generate .claude/settings.json (MCP config)
# 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"
# Build disabled MCPs list
ALL_MCPS=("exa" "Ref" "ticktick" "beeper" "airtable" "claude-in-chrome" "shopping" "proton-mail" "weather")
ENABLED_MCPS=""
# Parse selected MCPs
for mcp in "${ALL_MCPS[@]}"; do
if echo "$MCPS" | grep -qi "^$mcp "; then
ENABLED_MCPS="$ENABLED_MCPS $mcp"
fi
done
# Generate settings.json with disabled MCPs
cat > "$PROJECT_PATH/.claude/settings.json" << 'SETTINGS_EOF'
{
"mcpServers": {
@@ -201,47 +308,54 @@ SETTINGS_EOF
#######################################
gum spin --spinner dot --title "Generating CLAUDE.md..." -- sleep 0.3
# Determine type-specific content
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
# Check if spec-kit selected
USE_SPECKIT="No"
if echo "$ADDITIONAL" | grep -q "spec-kit"; then
USE_SPECKIT="Yes"
fi
# Generate CLAUDE.md
cat > "$PROJECT_PATH/CLAUDE.md" << CLAUDE_EOF
# $PROJECT_NAME - Claude Code Guidelines
@@ -254,7 +368,7 @@ $PROJECT_DESC
**Key Directories**:
- \`src/\` - Main source code
- \`tests/\` - Test files
- \`docs/\` - Documentation
- \`docs/\` - Documentation (see \`docs/PROJECT_BRIEF.md\` for full context)
---
@@ -308,24 +422,15 @@ Run after completing significant features:
## Pre-Commit Checklist
\`\`\`bash
# Type safety
$TYPE_CHECK_CMD
# Linting
$LINT_CMD
# Tests
$TEST_CMD
# Code review (for significant changes)
/code-review:code-review
\`\`\`
---
CLAUDE_EOF
# Add spec-kit section if selected
if [ "$USE_SPECKIT" = "Yes" ]; then
cat >> "$PROJECT_PATH/CLAUDE.md" << 'SPECKIT_EOF'
## Spec-Driven Development
@@ -345,21 +450,18 @@ if [ "$USE_SPECKIT" = "Yes" ]; then
SPECKIT_EOF
fi
# Add MCP configuration section
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\`. To re-enable, remove from the disabled list or use \`/config\` → MCP Servers.
**Disabled MCPs** are configured in \`.claude/settings.json\`.
---
## Central Configuration Reference
This project can access Hutson's central homelab configuration files. These are synced across all machines via Syncthing.
| File | Purpose | Usage |
|------|---------|-------|
| \`~/.secrets\` | API keys, tokens, credentials | \`source ~/.secrets\` then use \`\$VAR_NAME\` |
@@ -370,13 +472,37 @@ This project can access Hutson's central homelab configuration files. These are
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]
MCP_EOF
NOTES_EOF
#######################################
# Generate README.md
@@ -392,10 +518,10 @@ $PROJECT_DESC
\`\`\`bash
# Install dependencies
# [Add install command]
$INSTALL_CMD
# Run development server
# [Add dev command]
$DEV_CMD
# Run tests
$TEST_CMD
@@ -404,6 +530,7 @@ $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
#######################################
@@ -451,6 +578,332 @@ htmlcov/
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
#######################################
@@ -467,81 +920,47 @@ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>'"
fi
#######################################
# Gitea Repo
# Git Remotes
#######################################
if [ "$GITEA_CREATE" = "Yes" ] && [ -n "$GITEA_TOKEN" ]; then
GITEA_URL=""
GITHUB_URL=""
if [[ "$GIT_REMOTE" == *"Gitea"* ]] && [ -n "$GITEA_TOKEN" ]; then
gum spin --spinner dot --title "Creating Gitea repository..." -- sleep 0.5
# Create repo via API
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\": false,
\"private\": true,
\"auto_init\": false
}" 2>/dev/null)
if echo "$RESPONSE" | grep -q "\"name\":\"$PROJECT_NAME\""; then
# Add remote and push
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
GITEA_URL="(failed to create)"
GITHUB_URL="(gh CLI not installed - create manually)"
fi
elif [ "$GITEA_CREATE" = "Yes" ]; then
GITEA_URL="(GITEA_TOKEN not set in ~/.secrets)"
fi
#######################################
# Spec-kit init
#######################################
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
#######################################
# Docker support
#######################################
if echo "$ADDITIONAL" | grep -q "Docker support"; then
gum spin --spinner dot --title "Adding Docker support..." -- sleep 0.3
cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF'
# Customize this Dockerfile for your project
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
cat > "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_EOF'
version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
environment:
- ENV=development
volumes:
- .:/app
COMPOSE_EOF
fi
#######################################
@@ -558,15 +977,27 @@ 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 to create)" ] && [ "$GITEA_URL" != "(GITEA_TOKEN not set in ~/.secrets)" ]; then
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 ""