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
- Consistency: Same commands in CI/CD and local development
- Testability: CLI is fully tested (90%+ coverage)
- Maintainability: Logic in Python, not YAML
- Reusability: One command, many workflows
- 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 logicBest 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_FEEDSOptions:
validate all- Run schema and reference checksvalidate feeds- Validatefeeds.yamlagainstfeeds.schema.jsonvalidate topics- Validatetopics.yamlvalidate 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 passed1- Validation failures2- 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 unitOptions:
--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.xmlfor 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 metricstrending- Display active topicsvelocity- Display publication velocitysnapshot- Generate a daily analytics snapshotexport --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 distributionshow --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.opmlOptions:
json --output <file>- Export catalog JSONopml --output <file>- Export OPMLcsv --output <file>- Export CSVall --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-learningSubcommands:
all- Generate OPML for all database sourcescategorized- Generate source-type grouped OPMLfiltered- Generate OPML from topic, type, tag, or verified filters
Options:
--output- Output file forallandcategorized--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 catalogone <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:
- Fetches feed content
- Extracts title, description, language
- Detects feed type (RSS/Atom)
- Validates against schema
- Adds missing required fields
- 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 $FEEDSPattern 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 buildPattern 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: DEBUGCapture 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.logTest 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_OUTPUTWorkflow 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📚 Related Documentation
- GitHub Actions Workflows - Complete workflow reference
- CLI Commands - Full CLI documentation
- Testing - Testing guide
- Contributing - Contribution workflow
Last Updated: October 2025