Rename the `rails/` directory to `tools/` for agent discoverability — agents frequently failed to locate helper scripts due to the non-intuitive directory name. Add backward-compat symlink `rails/ → tools/`. New tool suites: - Authentik: auth-token, user-list, user-create, group-list, app-list, flow-list, admin-status (8 scripts) - Coolify: team-list, project-list, service-list, service-status, deploy, env-set (7 scripts) - Woodpecker: pipeline-list, pipeline-status, pipeline-trigger (3 stubs) - GLPI: session-init, computer-list, ticket-list, ticket-create, user-list (6 scripts) - Health: stack-health.sh — stack-wide connectivity check Infrastructure: - Shared credential loader at tools/_lib/credentials.sh - install.sh creates symlink + chmod on tool scripts - All ~253 rails/ path references updated across 68+ files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
494 lines
17 KiB
Bash
Executable File
494 lines
17 KiB
Bash
Executable File
#!/bin/bash
|
|
# init-project.sh - Bootstrap a project for AI-assisted development
|
|
# Usage: init-project.sh [OPTIONS]
|
|
#
|
|
# Creates CLAUDE.md, AGENTS.md, and standard directories using templates.
|
|
# Optionally initializes git labels and milestones.
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TEMPLATE_DIR="$HOME/.config/mosaic/templates/agent"
|
|
GIT_SCRIPT_DIR="$HOME/.config/mosaic/tools/git"
|
|
SEQUENTIAL_MCP_SCRIPT="$HOME/.config/mosaic/bin/mosaic-ensure-sequential-thinking"
|
|
|
|
# Defaults
|
|
PROJECT_NAME=""
|
|
PROJECT_TYPE=""
|
|
REPO_URL=""
|
|
TASK_PREFIX=""
|
|
PROJECT_DESCRIPTION=""
|
|
SKIP_LABELS=false
|
|
SKIP_CI=false
|
|
CICD_DOCKER=false
|
|
DRY_RUN=false
|
|
declare -a CICD_SERVICES=()
|
|
CICD_BRANCHES="main,develop"
|
|
|
|
show_help() {
|
|
cat <<'EOF'
|
|
Usage: init-project.sh [OPTIONS]
|
|
|
|
Bootstrap a project for AI-assisted development.
|
|
|
|
Options:
|
|
-n, --name <name> Project name (required)
|
|
-t, --type <type> Project type: nestjs-nextjs, django, generic (default: auto-detect)
|
|
-r, --repo <url> Git remote URL
|
|
-p, --prefix <prefix> Orchestrator task prefix (e.g., MS, UC)
|
|
-d, --description <desc> One-line project description
|
|
--skip-labels Skip creating git labels and milestones
|
|
--skip-ci Skip copying CI pipeline files
|
|
--cicd-docker Generate Docker build/push/link pipeline steps
|
|
--cicd-service <name:path> Service for Docker CI (repeatable, requires --cicd-docker)
|
|
--cicd-branches <list> Branches for Docker builds (default: main,develop)
|
|
--dry-run Show what would be created without creating anything
|
|
-h, --help Show this help
|
|
|
|
Examples:
|
|
# Full bootstrap with auto-detection
|
|
init-project.sh --name "My App" --description "A web application"
|
|
|
|
# Specific type
|
|
init-project.sh --name "My API" --type django --prefix MA
|
|
|
|
# Dry run
|
|
init-project.sh --name "Test" --type generic --dry-run
|
|
|
|
# With Docker CI/CD pipeline
|
|
init-project.sh --name "My App" --cicd-docker \
|
|
--cicd-service "my-api:src/api/Dockerfile" \
|
|
--cicd-service "my-web:src/web/Dockerfile"
|
|
|
|
Project Types:
|
|
nestjs-nextjs NestJS + Next.js monorepo (pnpm + TurboRepo)
|
|
django Django project (pytest + ruff + mypy)
|
|
typescript Standalone TypeScript/Next.js project
|
|
python-fastapi Python FastAPI project (pytest + ruff + mypy + uv)
|
|
python-library Python library/SDK (pytest + ruff + mypy + uv)
|
|
generic Generic project (uses base templates)
|
|
auto Auto-detect from project files (default)
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-n|--name)
|
|
PROJECT_NAME="$2"
|
|
shift 2
|
|
;;
|
|
-t|--type)
|
|
PROJECT_TYPE="$2"
|
|
shift 2
|
|
;;
|
|
-r|--repo)
|
|
REPO_URL="$2"
|
|
shift 2
|
|
;;
|
|
-p|--prefix)
|
|
TASK_PREFIX="$2"
|
|
shift 2
|
|
;;
|
|
-d|--description)
|
|
PROJECT_DESCRIPTION="$2"
|
|
shift 2
|
|
;;
|
|
--skip-labels)
|
|
SKIP_LABELS=true
|
|
shift
|
|
;;
|
|
--skip-ci)
|
|
SKIP_CI=true
|
|
shift
|
|
;;
|
|
--cicd-docker)
|
|
CICD_DOCKER=true
|
|
shift
|
|
;;
|
|
--cicd-service)
|
|
CICD_SERVICES+=("$2")
|
|
shift 2
|
|
;;
|
|
--cicd-branches)
|
|
CICD_BRANCHES="$2"
|
|
shift 2
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
show_help
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
echo "Run with --help for usage" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate required args
|
|
if [[ -z "$PROJECT_NAME" ]]; then
|
|
echo "Error: --name is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Auto-detect project type if not specified
|
|
detect_project_type() {
|
|
# Monorepo (pnpm + turbo or npm workspaces with NestJS)
|
|
if [[ -f "pnpm-workspace.yaml" ]] || [[ -f "turbo.json" ]]; then
|
|
echo "nestjs-nextjs"
|
|
return
|
|
fi
|
|
if [[ -f "package.json" ]] && grep -q '"workspaces"' package.json 2>/dev/null; then
|
|
echo "nestjs-nextjs"
|
|
return
|
|
fi
|
|
# Django
|
|
if [[ -f "manage.py" ]] && [[ -f "pyproject.toml" ]]; then
|
|
echo "django"
|
|
return
|
|
fi
|
|
# FastAPI
|
|
if [[ -f "pyproject.toml" ]] && grep -q "fastapi" pyproject.toml 2>/dev/null; then
|
|
echo "python-fastapi"
|
|
return
|
|
fi
|
|
# Standalone TypeScript
|
|
if [[ -f "tsconfig.json" ]] && [[ -f "package.json" ]]; then
|
|
echo "typescript"
|
|
return
|
|
fi
|
|
# Python library/tool
|
|
if [[ -f "pyproject.toml" ]]; then
|
|
echo "python-library"
|
|
return
|
|
fi
|
|
echo "generic"
|
|
}
|
|
|
|
if [[ -z "$PROJECT_TYPE" || "$PROJECT_TYPE" == "auto" ]]; then
|
|
PROJECT_TYPE=$(detect_project_type)
|
|
echo "Auto-detected project type: $PROJECT_TYPE"
|
|
fi
|
|
|
|
# Derive defaults
|
|
if [[ -z "$REPO_URL" ]]; then
|
|
REPO_URL=$(git remote get-url origin 2>/dev/null || echo "")
|
|
fi
|
|
|
|
if [[ -z "$TASK_PREFIX" ]]; then
|
|
# Generate prefix from project name initials
|
|
TASK_PREFIX=$(echo "$PROJECT_NAME" | sed 's/[^A-Za-z ]//g' | awk '{for(i=1;i<=NF;i++) printf toupper(substr($i,1,1))}')
|
|
if [[ -z "$TASK_PREFIX" ]]; then
|
|
TASK_PREFIX="PRJ"
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "$PROJECT_DESCRIPTION" ]]; then
|
|
PROJECT_DESCRIPTION="$PROJECT_NAME"
|
|
fi
|
|
|
|
PROJECT_DIR=$(basename "$(pwd)")
|
|
|
|
# Detect quality gates, source dir, and stack info based on type
|
|
case "$PROJECT_TYPE" in
|
|
nestjs-nextjs)
|
|
export QUALITY_GATES="pnpm typecheck && pnpm lint && pnpm test"
|
|
export SOURCE_DIR="apps"
|
|
export BUILD_COMMAND="pnpm build"
|
|
export TEST_COMMAND="pnpm test"
|
|
export LINT_COMMAND="pnpm lint"
|
|
export TYPECHECK_COMMAND="pnpm typecheck"
|
|
export FRONTEND_STACK="Next.js + React + TailwindCSS + Shadcn/ui"
|
|
export BACKEND_STACK="NestJS + Prisma ORM"
|
|
export DATABASE_STACK="PostgreSQL"
|
|
export TESTING_STACK="Vitest + Playwright"
|
|
export DEPLOYMENT_STACK="Docker + docker-compose"
|
|
export CONFIG_FILES="turbo.json, pnpm-workspace.yaml, tsconfig.json"
|
|
;;
|
|
django)
|
|
export QUALITY_GATES="ruff check . && mypy . && pytest tests/"
|
|
export SOURCE_DIR="src"
|
|
export BUILD_COMMAND="pip install -e ."
|
|
export TEST_COMMAND="pytest tests/"
|
|
export LINT_COMMAND="ruff check ."
|
|
export TYPECHECK_COMMAND="mypy ."
|
|
export FRONTEND_STACK="N/A"
|
|
export BACKEND_STACK="Django / Django REST Framework"
|
|
export DATABASE_STACK="PostgreSQL"
|
|
export TESTING_STACK="pytest + pytest-django"
|
|
export DEPLOYMENT_STACK="Docker + docker-compose"
|
|
export CONFIG_FILES="pyproject.toml"
|
|
export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
|
|
;;
|
|
typescript)
|
|
PKG_MGR="npm"
|
|
[[ -f "pnpm-lock.yaml" ]] && PKG_MGR="pnpm"
|
|
[[ -f "yarn.lock" ]] && PKG_MGR="yarn"
|
|
export QUALITY_GATES="$PKG_MGR run lint && $PKG_MGR run typecheck && $PKG_MGR test"
|
|
export SOURCE_DIR="src"
|
|
export BUILD_COMMAND="$PKG_MGR run build"
|
|
export TEST_COMMAND="$PKG_MGR test"
|
|
export LINT_COMMAND="$PKG_MGR run lint"
|
|
export TYPECHECK_COMMAND="npx tsc --noEmit"
|
|
export FRAMEWORK="TypeScript"
|
|
export PACKAGE_MANAGER="$PKG_MGR"
|
|
export FRONTEND_STACK="N/A"
|
|
export BACKEND_STACK="N/A"
|
|
export DATABASE_STACK="N/A"
|
|
export TESTING_STACK="Vitest or Jest"
|
|
export DEPLOYMENT_STACK="TBD"
|
|
export CONFIG_FILES="tsconfig.json, package.json"
|
|
# Detect Next.js
|
|
if grep -q '"next"' package.json 2>/dev/null; then
|
|
export FRAMEWORK="Next.js"
|
|
export FRONTEND_STACK="Next.js + React"
|
|
fi
|
|
;;
|
|
python-fastapi)
|
|
export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
|
|
export QUALITY_GATES="uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy src/ && uv run pytest --cov"
|
|
export SOURCE_DIR="src"
|
|
export BUILD_COMMAND="uv sync --all-extras"
|
|
export TEST_COMMAND="uv run pytest --cov"
|
|
export LINT_COMMAND="uv run ruff check src/ tests/"
|
|
export TYPECHECK_COMMAND="uv run mypy src/"
|
|
export FRONTEND_STACK="N/A"
|
|
export BACKEND_STACK="FastAPI"
|
|
export DATABASE_STACK="TBD"
|
|
export TESTING_STACK="pytest + httpx"
|
|
export DEPLOYMENT_STACK="Docker"
|
|
export CONFIG_FILES="pyproject.toml"
|
|
;;
|
|
python-library)
|
|
export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
|
|
export QUALITY_GATES="uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy src/ && uv run pytest --cov"
|
|
export SOURCE_DIR="src"
|
|
export BUILD_COMMAND="uv sync --all-extras"
|
|
export TEST_COMMAND="uv run pytest --cov"
|
|
export LINT_COMMAND="uv run ruff check src/ tests/"
|
|
export TYPECHECK_COMMAND="uv run mypy src/"
|
|
export BUILD_SYSTEM="hatchling"
|
|
export FRONTEND_STACK="N/A"
|
|
export BACKEND_STACK="N/A"
|
|
export DATABASE_STACK="N/A"
|
|
export TESTING_STACK="pytest"
|
|
export DEPLOYMENT_STACK="PyPI / Gitea Packages"
|
|
export CONFIG_FILES="pyproject.toml"
|
|
;;
|
|
*)
|
|
export QUALITY_GATES="echo 'No quality gates configured — update CLAUDE.md'"
|
|
export SOURCE_DIR="src"
|
|
export BUILD_COMMAND="echo 'No build command configured'"
|
|
export TEST_COMMAND="echo 'No test command configured'"
|
|
export LINT_COMMAND="echo 'No lint command configured'"
|
|
export TYPECHECK_COMMAND="echo 'No typecheck command configured'"
|
|
export FRONTEND_STACK="TBD"
|
|
export BACKEND_STACK="TBD"
|
|
export DATABASE_STACK="TBD"
|
|
export TESTING_STACK="TBD"
|
|
export DEPLOYMENT_STACK="TBD"
|
|
export CONFIG_FILES="TBD"
|
|
;;
|
|
esac
|
|
|
|
# Export common variables
|
|
export PROJECT_NAME
|
|
export PROJECT_DESCRIPTION
|
|
export PROJECT_DIR
|
|
export REPO_URL
|
|
export TASK_PREFIX
|
|
|
|
echo "=== Project Bootstrap ==="
|
|
echo " Name: $PROJECT_NAME"
|
|
echo " Type: $PROJECT_TYPE"
|
|
echo " Prefix: $TASK_PREFIX"
|
|
echo " Description: $PROJECT_DESCRIPTION"
|
|
echo " Repo: ${REPO_URL:-'(not set)'}"
|
|
echo " Directory: $(pwd)"
|
|
echo ""
|
|
|
|
# Select template directory
|
|
STACK_TEMPLATE_DIR="$TEMPLATE_DIR/projects/$PROJECT_TYPE"
|
|
if [[ ! -d "$STACK_TEMPLATE_DIR" ]]; then
|
|
STACK_TEMPLATE_DIR="$TEMPLATE_DIR"
|
|
echo "No stack-specific templates found for '$PROJECT_TYPE', using generic templates."
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo "[DRY RUN] Would create:"
|
|
echo " - Validate sequential-thinking MCP hard requirement"
|
|
echo " - CLAUDE.md (from $STACK_TEMPLATE_DIR/CLAUDE.md.template)"
|
|
echo " - AGENTS.md (from $STACK_TEMPLATE_DIR/AGENTS.md.template)"
|
|
echo " - docs/scratchpads/"
|
|
echo " - docs/reports/qa-automation/{pending,in-progress,done,escalated}"
|
|
echo " - docs/reports/deferred/"
|
|
echo " - docs/tasks/"
|
|
echo " - docs/releases/"
|
|
echo " - docs/templates/"
|
|
if [[ "$SKIP_CI" != true ]]; then
|
|
echo " - .woodpecker/codex-review.yml"
|
|
echo " - .woodpecker/schemas/*.json"
|
|
fi
|
|
if [[ "$SKIP_LABELS" != true ]]; then
|
|
echo " - Standard git labels (epic, feature, bug, task, documentation, security, breaking)"
|
|
echo " - Milestone: 0.0.1 - Pre-MVP Foundation"
|
|
echo " - Milestone policy: 0.0.x pre-MVP, 0.1.0 for MVP release"
|
|
fi
|
|
if [[ "$CICD_DOCKER" == true ]]; then
|
|
echo " - Docker build/push/link steps appended to .woodpecker.yml"
|
|
for svc in "${CICD_SERVICES[@]}"; do
|
|
echo " - docker-build-${svc%%:*}"
|
|
done
|
|
echo " - link-packages"
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# Enforce sequential-thinking MCP hard requirement.
|
|
if [[ ! -x "$SEQUENTIAL_MCP_SCRIPT" ]]; then
|
|
echo "Error: Missing sequential-thinking setup helper: $SEQUENTIAL_MCP_SCRIPT" >&2
|
|
echo "Install/repair Mosaic at ~/.config/mosaic before bootstrapping projects." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if "$SEQUENTIAL_MCP_SCRIPT" >/dev/null 2>&1; then
|
|
echo "Verified sequential-thinking MCP configuration"
|
|
else
|
|
echo "Error: sequential-thinking MCP setup failed (hard requirement)." >&2
|
|
echo "Run: $SEQUENTIAL_MCP_SCRIPT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Create CLAUDE.md
|
|
if [[ -f "CLAUDE.md" ]]; then
|
|
echo "CLAUDE.md already exists — skipping (rename or delete to recreate)"
|
|
else
|
|
if [[ -f "$STACK_TEMPLATE_DIR/CLAUDE.md.template" ]]; then
|
|
envsubst < "$STACK_TEMPLATE_DIR/CLAUDE.md.template" > CLAUDE.md
|
|
echo "Created CLAUDE.md"
|
|
else
|
|
echo "Warning: No CLAUDE.md template found at $STACK_TEMPLATE_DIR" >&2
|
|
fi
|
|
fi
|
|
|
|
# Create AGENTS.md
|
|
if [[ -f "AGENTS.md" ]]; then
|
|
echo "AGENTS.md already exists — skipping (rename or delete to recreate)"
|
|
else
|
|
if [[ -f "$STACK_TEMPLATE_DIR/AGENTS.md.template" ]]; then
|
|
envsubst < "$STACK_TEMPLATE_DIR/AGENTS.md.template" > AGENTS.md
|
|
echo "Created AGENTS.md"
|
|
else
|
|
echo "Warning: No AGENTS.md template found at $STACK_TEMPLATE_DIR" >&2
|
|
fi
|
|
fi
|
|
|
|
# Create directories
|
|
mkdir -p \
|
|
docs/scratchpads \
|
|
docs/reports/qa-automation/pending \
|
|
docs/reports/qa-automation/in-progress \
|
|
docs/reports/qa-automation/done \
|
|
docs/reports/qa-automation/escalated \
|
|
docs/reports/deferred \
|
|
docs/tasks \
|
|
docs/releases \
|
|
docs/templates
|
|
echo "Created docs/scratchpads/, docs/reports/*, docs/tasks/, docs/releases/, docs/templates/"
|
|
|
|
# Set up CI/CD pipeline
|
|
if [[ "$SKIP_CI" != true ]]; then
|
|
CODEX_DIR="$HOME/.config/mosaic/tools/codex"
|
|
if [[ -d "$CODEX_DIR/woodpecker" ]]; then
|
|
mkdir -p .woodpecker/schemas
|
|
cp "$CODEX_DIR/woodpecker/codex-review.yml" .woodpecker/
|
|
cp "$CODEX_DIR/schemas/"*.json .woodpecker/schemas/
|
|
echo "Created .woodpecker/ with Codex review pipeline"
|
|
else
|
|
echo "Codex pipeline templates not found — skipping CI setup"
|
|
fi
|
|
fi
|
|
|
|
# Generate Docker build/push/link pipeline steps
|
|
if [[ "$CICD_DOCKER" == true ]]; then
|
|
CICD_SCRIPT="$HOME/.config/mosaic/tools/cicd/generate-docker-steps.sh"
|
|
if [[ -x "$CICD_SCRIPT" ]]; then
|
|
# Parse org and repo from git remote
|
|
CICD_REGISTRY=""
|
|
CICD_ORG=""
|
|
CICD_REPO_NAME=""
|
|
if [[ -n "$REPO_URL" ]]; then
|
|
# Extract host from https://host/org/repo.git or git@host:org/repo.git
|
|
CICD_REGISTRY=$(echo "$REPO_URL" | sed -E 's|https?://([^/]+)/.*|\1|; s|git@([^:]+):.*|\1|')
|
|
CICD_ORG=$(echo "$REPO_URL" | sed -E 's|https?://[^/]+/([^/]+)/.*|\1|; s|git@[^:]+:([^/]+)/.*|\1|')
|
|
CICD_REPO_NAME=$(echo "$REPO_URL" | sed -E 's|.*/([^/]+?)(\.git)?$|\1|')
|
|
fi
|
|
|
|
if [[ -n "$CICD_REGISTRY" && -n "$CICD_ORG" && -n "$CICD_REPO_NAME" && ${#CICD_SERVICES[@]} -gt 0 ]]; then
|
|
# Build service args
|
|
SVC_ARGS=""
|
|
for svc in "${CICD_SERVICES[@]}"; do
|
|
SVC_ARGS="$SVC_ARGS --service $svc"
|
|
done
|
|
|
|
echo ""
|
|
echo "Generating Docker CI/CD pipeline steps..."
|
|
|
|
# Add kaniko_setup anchor to variables section if .woodpecker.yml exists
|
|
if [[ -f ".woodpecker.yml" ]]; then
|
|
# Append Docker steps to existing pipeline
|
|
"$CICD_SCRIPT" \
|
|
--registry "$CICD_REGISTRY" \
|
|
--org "$CICD_ORG" \
|
|
--repo "$CICD_REPO_NAME" \
|
|
$SVC_ARGS \
|
|
--branches "$CICD_BRANCHES" >> .woodpecker.yml
|
|
echo "Appended Docker build/push/link steps to .woodpecker.yml"
|
|
else
|
|
echo "Warning: No .woodpecker.yml found — generate quality gates first, then re-run with --cicd-docker" >&2
|
|
fi
|
|
else
|
|
if [[ ${#CICD_SERVICES[@]} -eq 0 ]]; then
|
|
echo "Warning: --cicd-docker requires at least one --cicd-service" >&2
|
|
else
|
|
echo "Warning: Could not parse registry/org/repo from git remote — specify --repo" >&2
|
|
fi
|
|
fi
|
|
else
|
|
echo "Docker CI/CD generator not found at $CICD_SCRIPT — skipping" >&2
|
|
fi
|
|
fi
|
|
|
|
# Initialize labels and milestones
|
|
if [[ "$SKIP_LABELS" != true ]]; then
|
|
LABEL_SCRIPT="$SCRIPT_DIR/init-repo-labels.sh"
|
|
if [[ -x "$LABEL_SCRIPT" ]]; then
|
|
echo ""
|
|
echo "Initializing git labels and milestones..."
|
|
"$LABEL_SCRIPT"
|
|
else
|
|
echo "Label init script not found — skipping label setup"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Bootstrap Complete ==="
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Review and customize CLAUDE.md"
|
|
echo " 2. Review and customize AGENTS.md"
|
|
echo " 3. Update quality gate commands if needed"
|
|
echo " 4. Commit: git add CLAUDE.md AGENTS.md docs/ .woodpecker/ && git commit -m 'feat: Bootstrap project for AI development'"
|
|
if [[ "$SKIP_CI" != true ]]; then
|
|
echo " 5. Add 'codex_api_key' secret to Woodpecker CI"
|
|
fi
|
|
if [[ "$CICD_DOCKER" == true ]]; then
|
|
echo " 6. Add 'gitea_username' and 'gitea_token' secrets to Woodpecker CI"
|
|
echo " (token needs package:write scope)"
|
|
fi
|