name: Deploy Site on: release: types: [published] push: branches: [main] paths: - 'website/**' - 'skills/**' - 'optional-skills/**' - '.github/workflows/deploy-site.yml' workflow_dispatch: inputs: skills_index_run_id: description: 'Optional Build Skills Index run ID whose skills-index artifact should be deployed' required: false type: string rebuild_skills_index: description: 'Force a fresh multi-source crawl instead of reusing the latest healthy index' required: false default: false type: boolean permissions: contents: read actions: read pages: write id-token: write concurrency: group: pages cancel-in-progress: false jobs: deploy-vercel: # Triggered automatically on release publish (production cuts) and # manually via `gh workflow run deploy-site.yml` when an out-of-band # main commit needs to ship live before the next release tag — e.g. # a skills-index PR that doesn't touch website/** paths and so # doesn't auto-deploy via the deploy-docs path. if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - name: Trigger Vercel Deploy run: curl -X POST "${{ secrets.VERCEL_DEPLOY_HOOK }}" deploy-docs: if: github.repository == 'NousResearch/hermes-agent' runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deploy.outputs.page_url }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 cache: npm cache-dependency-path: website/package-lock.json - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.11' - name: Install PyYAML for skill extraction run: pip install pyyaml==6.0.2 httpx==0.28.1 - name: Prepare skills index (unified multi-source catalog) env: GH_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }} SKILLS_INDEX_RUN_ID: ${{ github.event.inputs.skills_index_run_id || '' }} REBUILD_SKILLS_INDEX: ${{ github.event.inputs.rebuild_skills_index || 'false' }} run: | # The unified external catalog is expensive to crawl and can burn # through the repository installation's GitHub API quota when several # docs deploys land close together. Normal docs deploys therefore # reuse the latest healthy catalog: first the artifact from a # scheduled skills-index run, then the currently live index. Only a # manual force rebuild does a fresh crawl here. # # If we do crawl, the build remains fatal. build_skills_index.py runs # the health check BEFORE writing and exits non-zero on source # collapse, keeping the last good Pages deployment live instead of # publishing a degenerate catalog. set -euo pipefail INDEX_PATH="website/static/api/skills-index.json" mkdir -p "$(dirname "$INDEX_PATH")" validate_index() { python3 - "$INDEX_PATH" <<'PY' import json import sys from pathlib import Path path = Path(sys.argv[1]) try: data = json.loads(path.read_text(encoding="utf-8")) except Exception as exc: print(f"invalid skills index JSON: {exc}", file=sys.stderr) sys.exit(1) skills = data.get("skills") if not isinstance(skills, list) or len(skills) < 1500: count = len(skills) if isinstance(skills, list) else "missing" print(f"skills index too small: {count}", file=sys.stderr) sys.exit(1) print(f"skills index ready: {len(skills)} skills") PY } if [ "$REBUILD_SKILLS_INDEX" = "true" ]; then python3 scripts/build_skills_index.py validate_index exit 0 fi if [ -n "$SKILLS_INDEX_RUN_ID" ]; then tmpdir="$(mktemp -d)" echo "Downloading skills-index artifact from run $SKILLS_INDEX_RUN_ID" if gh run download "$SKILLS_INDEX_RUN_ID" --name skills-index --dir "$tmpdir"; then candidate="$(find "$tmpdir" -name skills-index.json -type f | head -n 1 || true)" if [ -n "$candidate" ]; then cp "$candidate" "$INDEX_PATH" if validate_index; then exit 0 fi fi fi echo "::warning::Could not use skills-index artifact from run $SKILLS_INDEX_RUN_ID; trying live index" fi echo "Downloading currently live skills index" if curl -fsSL --retry 3 --retry-delay 5 \ "https://hermes-agent.nousresearch.com/docs/api/skills-index.json" \ -o "$INDEX_PATH" && validate_index; then exit 0 fi echo "::warning::Live skills index unavailable or unhealthy; falling back to a fresh crawl" rm -f "$INDEX_PATH" python3 scripts/build_skills_index.py validate_index - name: Extract skill metadata for dashboard run: python3 website/scripts/extract-skills.py - name: Regenerate per-skill docs pages + catalogs run: python3 website/scripts/generate-skill-docs.py - name: Install dependencies run: npm ci working-directory: website - name: Build Docusaurus run: npm run build working-directory: website - name: Stage deployment run: | mkdir -p _site/docs cp -r website/build/* _site/docs/ # llms.txt / llms-full.txt are also published at the site root # (https://hermes-agent.nousresearch.com/llms.txt) because some # agents and IDE plugins probe the classic root-level path rather # than /docs/llms.txt. Same file, two URLs, one source of truth. if [ -f website/build/llms.txt ]; then cp website/build/llms.txt _site/llms.txt fi if [ -f website/build/llms-full.txt ]; then cp website/build/llms-full.txt _site/llms-full.txt fi - name: Upload artifact uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 with: path: _site - name: Deploy to GitHub Pages id: deploy uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4