Skip to content

Basic Usage

This document covers the fundamental usage patterns for Zaojun, from simple checks to more advanced scenarios including caching for improved performance.

Command Structure

The basic Zaojun command follows this pattern:

zaojun [PYPROJECT_TOML] [OPTIONS]

Where: - [PYPROJECT_TOML]: Optional path to a pyproject.toml file (defaults to ./pyproject.toml) - [OPTIONS]: Various command-line options to control behavior

Default Behavior

When run without any arguments, Zaojun:

  1. Looks for pyproject.toml in the current directory
  2. Checks all dependencies in the [project.dependencies] section
  3. Shows detailed output with emoji indicators
  4. Returns exit code 1 if any updates are needed
# Basic usage - check current directory
zaojun

Specifying a pyproject.toml File

You can check dependencies in any pyproject.toml file:

# Check a specific file
zaojun /path/to/project/pyproject.toml

# Check a file in a parent directory
zaojun ../pyproject.toml

# Check a file with a different name
zaojun my-dependencies.toml

Persistent Defaults via Config

Add a [tool.zaojun] section to your pyproject.toml to set defaults that apply on every run without repeating CLI flags:

[tool.zaojun]
cache = true
groups = true
min-age = 7

Running zaojun with this config is equivalent to zaojun --cache --groups --min-age 7. CLI flags always override config values — pass --no-cache to disable caching for a single run even when cache = true is configured.

See the Command Reference for the full list of supported keys and precedence rules.

Caching for Performance

Zaojun includes a caching system to improve performance for repeated dependency checks. By default, caching is disabled to maintain backward compatibility.

Enabling Caching

# Enable caching for faster repeated checks
zaojun --cache

# Enable caching and show statistics
zaojun --cache --cache-stats

# Clear existing cache and enable caching
zaojun --clear-cache --cache

Cache Benefits

  • First run with --cache: Fetches data from PyPI and caches it
  • Subsequent runs: Uses cached data (10-100x faster)
  • Offline capability: Partial functionality when PyPI is unavailable
  • Rate limiting: Reduces load on PyPI infrastructure

Cache Management

# Show cache statistics
zaojun --cache-stats

# Clear all cache entries
zaojun --clear-cache

# Disable caching (default behavior)
zaojun --no-cache

Cache Location

Cache files are stored in: - Linux/Unix: ~/.cache/zaojun/pypi_cache/ - macOS: ~/Library/Caches/zaojun/pypi_cache/ - Windows: %LOCALAPPDATA%\zaojun\pypi_cache\

Cache entries expire after 24 hours and are automatically cleaned up.

Understanding Version Constraints

Zaojun understands various Python version constraint formats:

Exact Versions

dependencies = [
    "package==1.2.3",      # Exactly version 1.2.3
]

Version Ranges

dependencies = [
    "package>=1.0.0",      # Version 1.0.0 or higher
    "package<2.0.0",       # Version less than 2.0.0
    "package>=1.0.0,<2.0.0", # Between 1.0.0 and 2.0.0
]

Compatible Releases

dependencies = [
    "package~=1.2.3",      # >=1.2.3, ==1.2.*
    "package~=1.2",        # >=1.2, ==1.*
]

Multiple Constraints

dependencies = [
    "package>=1.0.0,!=1.2.3,<2.0.0",  # Multiple conditions
]

JSON Output

Use --format json to get machine-readable output suitable for scripts and CI pipelines:

zaojun --format json
{
  "dependencies": [
    {"name": "httpx", "spec": "~=0.28.1", "latest": "0.28.1", "status": "up_to_date"},
    {"name": "flask", "spec": "~=2.3.0", "latest": "3.0.0", "status": "incompatible_update"}
  ],
  "needs_update": true
}

The needs_update top-level field mirrors the exit code (true = exit 1). Parse it directly:

zaojun --format json | jq '.needs_update'
# false

zaojun --format json | jq '[.dependencies[] | select(.status != "up_to_date")]'
# [...list of packages needing attention...]

You can also set this as a persistent default in [tool.zaojun]:

[tool.zaojun]
format = "json"

Output Interpretation

Status Indicators

Zaojun uses three types of indicators:

  1. ✅ Up to date

    ✅️ package==1.2.3 is up to date
    
    The latest version on PyPI matches your constraint.

  2. ⚠️ Compatible update available

    ⚠️ package: ~=1.2.3 → Latest: 1.2.5
    
    A newer version exists that satisfies your version constraints.

  3. 💥 Major-version bump available

    💥 package: >=2.0 → Latest: 3.0.0 (major version bump)
    
    The latest version satisfies your spec but crosses a major version boundary — likely has breaking changes. Triggers exit code 1 by default; suppress with --major-ok (show 💥 but exit 0) or --no-flag-major (treat as ⚠️).

  4. ❌ Incompatible update available

    ❌ package: ==1.2.3 → Latest: 2.0.0
    
    A newer version exists that does NOT satisfy your version constraints.

  5. ⏳ Update quarantined (too new)

    ⏳ package: 2.0.0 is only 2 day(s) old (min-age: 7)
    
    The latest version is newer than your --min-age threshold. No action required yet.

Example Scenarios

Scenario 1: Everything up to date

$ zaojun
Checking dependencies in /home/user/project/pyproject.toml
✅️ httpx~=0.28.1 is up to date
✅️ packaging~=26.0 is up to date
✅️ cyclopts>=2.0.0 is up to date

Scenario 2: Mixed status

$ zaojun
Checking dependencies in /home/user/project/pyproject.toml
✅️ httpx~=0.28.1 is up to date
⚠️ packaging: ~=23.0  Latest: 24.0
 typer: ==0.9.0  Latest: 1.0.0

Library Mode

If you maintain a Python library (a package consumed by other packages), use --library to check that your dependency constraints are appropriate for a library:

zaojun --library

Library mode runs two checks for each dependency:

  1. Constraint hygiene (local, no network): flags specifier styles that create version conflicts for downstream consumers:
  2. pydantic==2.5.0 — exact pins block all other versions
  3. pydantic~=2.5.3 — patch-level compatible release is too tight
  4. pydantic>=2.0,<2.5 — tight upper bound; use <3 (major boundary) instead
  5. pydantic (bare) — warns that consumers get no minimum guarantee

  6. Incompatible staleness (PyPI): flags dependencies where the latest version falls outside your allowed range — e.g., ~=1.0 when 2.x is current.

Compatible updates are always silent in library mode. If pydantic>=2.0 is your spec and the latest is 2.8, there is nothing to flag — your range already covers it.

--min-age, --groups, --cache, and --short all work with --library.

# Full library check with quarantine and caching
zaojun --library --min-age 7 --groups --cache

Supply-Chain Quarantine

Supply chain attacks often target the window between a package being published and users installing it. Use --min-age N to skip flagging updates younger than N days:

# Wait 7 days before considering an update actionable
zaojun --min-age 7

# Stricter policy — wait 30 days
zaojun --min-age 30 --groups --cache

Quarantined packages show ⏳ and do not trigger exit code 1. If a package's release date is unknown (e.g. old cache entry), the age check is skipped.

Vulnerability Scanning

zaojun automatically surfaces known CVEs and security advisories from the PyPI vulnerability feed. No extra configuration is needed — the data arrives in the same HTTP response already fetched per package (zero additional requests).

When a package has open advisories, its line gains a 🔒 indicator and a detail block appears at the end of the run:

⚠️  requests: ~=2.28.0 → Latest: 2.32.3  🔒 1 vuln

Vulnerabilities
───────────────
requests
  PYSEC-2024-xxx (CVE-2024-35195)
    Fixed in: 2.32.0
    Verify that cert verification is enforced for redirect targets using HTTPS.

Packages with open (non-ignored) advisories set exit code 1.

Suppressing advisories with vuln-ignore

Add a vuln-ignore list to [tool.zaojun] to suppress advisories you have already triaged or cannot immediately fix:

[tool.zaojun]
vuln-ignore = [
    "CVE-2024-35195",           # specific CVE, all packages
    "GHSA-gc5v-m9x4-r6x2",     # specific GHSA, all packages
    "requests",                 # all advisories for requests, any version
    "pillow == 10.3.0",         # suppress only while PyPI latest is 10.3.0
    "urllib3 >= 1.0, < 2.0",    # suppress for the entire 1.x line
]

The version in package entries is compared against the latest version on PyPI. A == X.Y.Z entry automatically re-alerts when a newer release appears — no stale ignore entries.

Addressing Discovered Vulnerabilities

When zaojun flags a vulnerability in one of your direct dependencies, update the lower bound in your specifier to require a fixed version:

[project.dependencies]
requests = ">=2.33.0"   # was >=2.0; GHSA-gc5v-m9x4-r6x2 fixed in 2.33.0

zaojun only checks packages listed in [project.dependencies] (and [dependency-groups] when --groups is used). It does not inspect transitive dependencies — advisories on packages you don't declare directly will not appear in the report.

Common Patterns

Regular Project Maintenance

# Check your project regularly with caching for speed
zaojun --cache

# If updates are needed, update your constraints
# Then update your dependencies
uv sync -U --all-groups

# Clear cache periodically for fresh data
zaojun --clear-cache --cache

Development Workflow with Caching

# Daily development checks (fast with caching)
zaojun --cache --cache-stats --groups

# Before committing code
zaojun --short --cache

# Force fresh data when needed
zaojun --clear-cache --cache-stats

Checking Before Commits

# Run before committing to ensure dependencies are current
zaojun --short --cache

# If you see output, consider updating

Integration with Other Tools

# Combine with grep to extract package names needing updates
zaojun --short --cache | grep -E "^[⚠❌]" | cut -d' ' -f2

# Count how many packages need updates
zaojun --short --cache | grep -c -E "^[⚠❌]"

# Check cache effectiveness
zaojun --cache --cache-stats | grep "Hits:"

Working with Different File Structures

Monorepo Structure

# Check each project in a monorepo
zaojun packages/app/pyproject.toml
zaojun packages/lib/pyproject.toml
zaojun tools/scripts/pyproject.toml

Nested Projects

# Check a nested project
zaojun src/backend/pyproject.toml

# Check multiple levels
zaojun ../../shared-lib/pyproject.toml

Temporary Files

# Check a generated pyproject.toml
zaojun /tmp/test-deps.toml --cache

# Check from stdin (advanced usage)
cat deps.toml | python -c "
import sys, tempfile, subprocess
with tempfile.NamedTemporaryFile(mode='w', suffix='.toml') as f:
    f.write(sys.stdin.read())
    f.flush()
    subprocess.run(['zaojun', f.name, '--cache'])
"

Error Handling

File Not Found

$ zaojun non-existent.toml
Error: File 'non-existent.toml' not found

Invalid TOML

$ zaojun invalid.toml
Error: Invalid TOML in 'invalid.toml'

Network Issues

$ zaojun
Checking dependencies in /home/user/project/pyproject.toml
 Network error for httpx: HTTPStatusError('404 Not Found')

Invalid Dependency Format

$ zaojun
Checking dependencies in /home/user/project/pyproject.toml
 Error checking 'invalid-package-@-1.0': ValueError("Invalid dependency format 'invalid-package-@-1.0'")

Best Practices

  1. Run Regularly: Check dependencies at least weekly
  2. Use Caching: Enable --cache for faster repeated checks during development
  3. Use in CI: Add to your continuous integration pipeline (consider --no-cache for consistency)
  4. Review Before Updating: Understand what changes in new versions
  5. Test After Updates: Always test your application after dependency updates
  6. Use Version Pinning: Consider pinning exact versions in production
  7. Monitor Cache: Use --cache-stats periodically to ensure cache effectiveness
  8. Clear Cache: Use --clear-cache when you suspect stale data or after major dependency changes

Next Steps