AI Web FeedsAI Web FeedsOpen web AI reader
Documentation

CLI Integration in Workflows

How the ai-web-feeds CLI powers our CI/CD pipeline

Source: apps/web/content/docs/development/cli-workflows.mdx

CLI Integration in GitHub Actions

The ai-web-feeds CLI is the backbone of our CI/CD pipeline. Every workflow leverages CLI commands for consistent, reliable automation.

🎯 Why CLI-First Workflows?

Benefits

  1. Consistency: Same commands in CI/CD and local development
  2. Testability: CLI is fully tested (90%+ coverage)
  3. Maintainability: Logic in Python, not YAML
  4. Reusability: One command, many workflows
  5. Debugging: Run exact CI command locally

Anti-Pattern ❌

# DON'T: Duplicate logic in YAML
- name: Validate feeds
  run: |
    # Inline Python validation shell logic
    # ... 50 lines of shell script validation logic

Best Practice ✅

# DO: Use CLI command
- name: Validate feeds
  run: uv run ai-web-feeds validate all --strict

🔧 Available CLI Commands

Validation Commands

validate - Comprehensive Feed Validation

Purpose: Validate feed data, schemas, URLs, and parsing

Workflow Usage:

# Validate all feeds
- name: Validate all feeds
  run: uv run ai-web-feeds validate all

# Schema validation only
- name: Validate schema
  run: uv run ai-web-feeds validate feeds --strict

# Check URL accessibility
- name: Check feed URLs
  run: uv run ai-web-feeds validate http

# Validate specific feeds (for PR changes)
- name: Validate changed feeds
  run: |
    CHANGED_FEEDS=$(git diff origin/main -- data/feeds.yaml | grep -oP 'url:\s*\K\S+')
    uv run ai-web-feeds validate feeds --file $CHANGED_FEEDS

Options:

  • validate all - Run schema and reference checks
  • validate feeds - Validate feeds.yaml against feeds.schema.json
  • validate topics - Validate topics.yaml
  • validate http - Test URL accessibility from the database
  • --strict - Fail on warnings
  • --timeout - Request timeout (default: 30s)
  • --feeds - Validate specific feed URLs

Exit Codes:

  • 0 - All validations passed
  • 1 - Validation failures
  • 2 - Schema errors

test - Run Test Suite

Purpose: Execute pytest test suite with coverage

Workflow Usage:

# Full test suite
- name: Run tests
  run: uv run ai-web-feeds test coverage

# Quick tests only
- name: Quick test
  run: uv run ai-web-feeds test quick

# Specific test markers
- name: Unit tests
  run: uv run ai-web-feeds test unit

Options:

  • --coverage - Generate coverage report
  • --quick - Fast tests only (no slow/integration)
  • --marker - Run specific test markers (unit, integration, e2e)
  • --verbose - Detailed output

Output:

  • Creates reports/coverage/ directory
  • Generates coverage.xml for Codecov
  • Exit code 1 if tests fail or coverage below 90%

Analytics Commands

analytics - Generate Feed Statistics

Purpose: Calculate feed metrics and insights

Workflow Usage:

# Generate analytics CSV
- name: Generate analytics
  run: uv run ai-web-feeds analytics export --output data/analytics.csv

# Display in workflow
- name: Show analytics
  run: uv run ai-web-feeds analytics summary

# Track changes
- name: Analytics diff
  run: |
    uv run ai-web-feeds analytics export --output /tmp/new.csv
    diff data/analytics.csv /tmp/new.csv || echo "Analytics changed"

Options:

  • summary - Display summary metrics
  • trending - Display active topics
  • velocity - Display publication velocity
  • snapshot - Generate a daily analytics snapshot
  • export --output <file> - Export analytics as CSV

Metrics:

  • Total feed count
  • Feeds per source type
  • Topic coverage
  • Language distribution
  • Feed health status
  • Update frequency statistics

stats - Display Feed Statistics

Purpose: Show human-readable feed statistics

Workflow Usage:

# Post stats as PR comment
- name: Generate stats
  id: stats
  run: |
    STATS=$(uv run ai-web-feeds stats show)
    echo "stats<<EOF" >> $GITHUB_OUTPUT
    echo "$STATS" >> $GITHUB_OUTPUT
    echo "EOF" >> $GITHUB_OUTPUT

- name: Comment PR
  uses: actions/github-script@v7
  with:
    script: |
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: ${{ steps.stats.outputs.stats }}
      })

Subcommands and options:

  • show - Display total feed count, verified count, and source-type distribution
  • show --database <url> - Read stats from a specific database URL

Export Commands

export - Export Feed Data

Purpose: Generate output in various formats

Workflow Usage:

# Export to JSON for artifacts
- name: Export feeds
  run: uv run ai-web-feeds export json --output feeds.json

- name: Upload artifact
  uses: actions/upload-artifact@v4
  with:
    name: feed-data
    path: feeds.json

# Export OPML artifact
- name: Export OPML
  run: uv run ai-web-feeds export opml --output data/feeds.opml

Options:

  • json --output <file> - Export catalog JSON
  • opml --output <file> - Export OPML
  • csv --output <file> - Export CSV
  • all --output-dir <dir> - Export JSON and OPML variants

opml - OPML Management

Purpose: Generate OPML feed lists from database-backed sources

Workflow Usage:

# Export to OPML
- name: Generate OPML
  run: uv run ai-web-feeds opml all --output data/feeds.opml

# Export categorized OPML
- name: Generate categorized OPML
  run: uv run ai-web-feeds opml categorized --output data/feeds.categorized.opml

# Filtered OPML
- name: Generate filtered OPML
  run: uv run ai-web-feeds opml filtered data/feeds.filtered.opml --topic machine-learning

Subcommands:

  • all - Generate OPML for all database sources
  • categorized - Generate source-type grouped OPML
  • filtered - Generate OPML from topic, type, tag, or verified filters

Options:

  • --output - Output file for all and categorized
  • --topic - Filter by canonical topic
  • --type - Filter by source type
  • --tag - Filter by tag
  • --verified - Include only verified sources

Enrichment Commands

enrich - Enhance Feed Metadata

Purpose: Add/update feed metadata automatically

Workflow Usage:

# Enrich all feeds
- name: Enrich feeds
  run: uv run ai-web-feeds enrich all --output data/feeds.enriched.yaml

# Enrich specific feed
- name: Enrich new feed
  run: |
    FEED_URL="${{ github.event.inputs.feed_url }}"
    uv run ai-web-feeds enrich one <feed-id> --input data/feeds.yaml

# Fix schema issues
- name: Fix schema
  run: uv run ai-web-feeds enrich all

# Fetch feed metadata
- name: Fetch metadata
  run: uv run ai-web-feeds fetch one <feed-id>

Options:

  • all --input <file> --output <file> - Enrich a full catalog
  • one <feed-id> --input <file> - Enrich one configured source
  • --database - Store enriched source metadata in a selected database
  • --schema - Write the enriched JSON Schema

Enrichment Process:

  1. Fetches feed content
  2. Extracts title, description, language
  3. Detects feed type (RSS/Atom)
  4. Validates against schema
  5. Adds missing required fields
  6. Updates timestamps

🔄 Workflow Patterns

Pattern 1: Incremental Validation

Use Case: Only validate feeds changed in PR

name: Validate Changed Feeds

on:
  pull_request:
    paths:
      - "data/feeds.yaml"

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Need history for diff

      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Get changed feeds
        id: changes
        run: |
          # Extract URLs from diff
          CHANGED=$(git diff origin/${{ github.base_ref }} -- data/feeds.yaml | \
                    grep -oP '^\+\s+url:\s*\K\S+' | \
                    tr '\n' ' ')
          echo "feeds=$CHANGED" >> $GITHUB_OUTPUT

      - name: Validate changed feeds
        if: steps.changes.outputs.feeds != ''
        run: uv run ai-web-feeds validate feeds --file ${{ steps.changes.outputs.feeds }}

Pattern 2: Matrix Validation

Use Case: Validate feeds in parallel for speed

name: Parallel Feed Validation

on:
  push:
    branches: [main]

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.feeds.outputs.matrix }}
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Generate feed matrix
        id: feeds
        run: |
          # Extract all feed URLs into JSON array
          FEEDS=$(uv run python -c "
          import yaml, json
          with open('data/feeds.yaml') as f:
              data = yaml.safe_load(f)
          feeds = [item['url'] for item in data['feeds']]
          # Split into chunks of 10
          chunks = [feeds[i:i+10] for i in range(0, len(feeds), 10)]
          print(json.dumps({'chunk': list(range(len(chunks)))}))
          ")
          echo "matrix=$FEEDS" >> $GITHUB_OUTPUT

  validate:
    needs: prepare
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Validate chunk ${{ matrix.chunk }}
        run: |
          # Get feeds for this chunk
          FEEDS=$(uv run python -c "
          import yaml
          with open('data/feeds.yaml') as f:
              data = yaml.safe_load(f)
          feeds = [item['url'] for item in data['feeds']]
          chunk = feeds[${{ matrix.chunk }}*10:(${{ matrix.chunk }}+1)*10]
          print(' '.join(chunk))
          ")
          uv run ai-web-feeds validate feeds --file $FEEDS

Pattern 3: Conditional Workflow Steps

Use Case: Run different CLI commands based on file changes

name: Smart Validation

on: [pull_request]

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      feeds: ${{ steps.filter.outputs.feeds }}
      python: ${{ steps.filter.outputs.python }}
      web: ${{ steps.filter.outputs.web }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            feeds:
              - 'data/feeds.yaml'
            python:
              - 'packages/**/*.py'
              - 'apps/cli/**/*.py'
            web:
              - 'apps/web/**/*'

  validate-feeds:
    needs: detect-changes
    if: needs.detect-changes.outputs.feeds == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5
      - name: Validate feeds
        run: uv run ai-web-feeds validate all --strict

  test-python:
    needs: detect-changes
    if: needs.detect-changes.outputs.python == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5
      - name: Run Python tests
        run: uv run ai-web-feeds test coverage

  test-web:
    needs: detect-changes
    if: needs.detect-changes.outputs.web == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - name: Test web
        run: |
          cd apps/web
          pnpm install
          pnpm lint
          pnpm build

Pattern 4: PR Comments with CLI Output

Use Case: Post CLI results as PR comments

name: Post Feed Stats

on:
  pull_request:
    paths:
      - "data/feeds.yaml"

jobs:
  stats:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Generate stats
        id: stats
        run: |
          {
            echo 'stats<<EOF'
            uv run ai-web-feeds stats show
            echo EOF
          } >> $GITHUB_OUTPUT

      - name: Generate analytics
        id: analytics
        run: |
          {
            echo 'analytics<<EOF'
            uv run ai-web-feeds analytics summary
            echo EOF
          } >> $GITHUB_OUTPUT

      - name: Comment PR
        uses: actions/github-script@v7
        with:
          script: |
            const stats = `${{ steps.stats.outputs.stats }}`;
            const analytics = `${{ steps.analytics.outputs.analytics }}`;

            const body = `## 📊 Feed Statistics

            ${stats}

            ## 📈 Analytics

            \`\`\`
            ${analytics}
            \`\`\`
            `;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body
            });

Pattern 5: Workflow Artifacts

Use Case: Save CLI output as downloadable artifacts

name: Generate Feed Reports

on:
  schedule:
    - cron: "0 0 * * 0" # Weekly on Sunday

jobs:
  reports:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Generate reports
        run: |
          mkdir -p reports

          # Analytics report
          uv run ai-web-feeds analytics export --output reports/analytics.csv

          # Export feeds
          uv run ai-web-feeds export json --output reports/feeds.json

          # OPML export
          uv run ai-web-feeds opml all --output reports/feeds.opml
          uv run ai-web-feeds opml categorized --output reports/feeds.categorized.opml

          # Validation report
          uv run ai-web-feeds validate all > reports/validation.txt || true

          # Stats
          uv run ai-web-feeds stats show > reports/stats.md

      - name: Upload reports
        uses: actions/upload-artifact@v4
        with:
          name: weekly-reports
          path: reports/
          retention-days: 90

🎨 Workflow Reports

Use the registered CLI commands directly in workflow report jobs.

- name: Generate PR report inputs
  run: |
    uv run ai-web-feeds stats show > reports/stats.txt
    uv run ai-web-feeds analytics export --output reports/analytics.csv
    uv run ai-web-feeds validate all --strict > reports/validation.txt

🐛 Debugging CLI in Workflows

Enable Verbose Output

- name: Validate with debug
  run: uv run ai-web-feeds validate all
  env:
    AIWEBFEEDS_LOG_LEVEL: DEBUG

Capture Logs

- name: Validate and save logs
  run: |
    uv run ai-web-feeds validate all 2>&1 | tee validation.log

- name: Upload logs
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: validation-logs
    path: validation.log

Test CLI Locally

# Run exact command from workflow
uv run ai-web-feeds validate all --strict

# With environment variables
AIWEBFEEDS_LOG_LEVEL=DEBUG uv run ai-web-feeds validate all

📊 Monitoring & Metrics

Track CLI Command Usage

Add telemetry to CLI commands:

# In CLI command
import time
from loguru import logger

start = time.time()
# ... command logic ...
duration = time.time() - start

logger.info(f"Command completed in {duration:.2f}s")

# In workflow
- name: Track validation time
  run: |
    START=$(date +%s)
    uv run ai-web-feeds validate all
    END=$(date +%s)
    DURATION=$((END - START))
    echo "validation_duration=$DURATION" >> $GITHUB_OUTPUT

Workflow Performance

name: Performance Tracking

on: [push]

jobs:
  benchmark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Benchmark CLI commands
        run: |
          echo "## CLI Performance" > benchmark.md

          time_command() {
            START=$(date +%s.%N)
            $1
            END=$(date +%s.%N)
            DURATION=$(echo "$END - $START" | bc)
            echo "- $1: ${DURATION}s" >> benchmark.md
          }

          time_command "uv run ai-web-feeds validate feeds"
          time_command "uv run ai-web-feeds analytics summary"
          time_command "uv run ai-web-feeds export json --output benchmark-feeds.json"

          cat benchmark.md


Last Updated: October 2025

CLI Integration in Workflows | AI Web Feeds