Prerequisites

Before running docsfy, make sure your environment has Python, uv, git, one supported AI CLI with credentials, and a valid ADMIN_KEY.

Python and uv

docsfy requires Python 3.12+.

[project]
name = "docsfy"
version = "0.1.0"
description = "AI-powered documentation generator - generates polished static HTML docs from GitHub repos"
requires-python = ">=3.12"
dependencies = [
    "ai-cli-runner",
    "fastapi",
    "uvicorn",
    "pydantic-settings",
    "python-simple-logger",
    "aiosqlite",
    "jinja2",
    "markdown",
    "pygments",
    "python-multipart>=0.0.22",
]

The lock file enforces the same minimum Python version:

version = 1
revision = 3
requires-python = ">=3.12"

The project workflow uses uv for install, running, and tests:

RUN uv sync --frozen --no-dev
ENTRYPOINT ["uv", "run", "--no-sync", "uvicorn", "docsfy.main:app", "--host", "0.0.0.0", "--port", "8000"]
[env.unittests]
deps = ["uv"]
commands = [["uv", "run", "--extra", "dev", "pytest", "-n", "auto", "tests"]]

Note: Settings loads environment variables from .env, so your local config must be present there.

model_config = SettingsConfigDict(
    env_file=".env",
    env_file_encoding="utf-8",
    extra="ignore",
)

git is required

docsfy uses git to clone repositories and resolve commit SHAs:

result = subprocess.run(
    ["git", "clone", "--depth", "1", "--", repo_url, str(repo_path)],
    capture_output=True,
    text=True,
    timeout=300,
)
if result.returncode != 0:
    msg = f"Clone failed: {result.stderr or result.stdout}"
    raise RuntimeError(msg)
sha_result = subprocess.run(
    ["git", "rev-parse", "HEAD"],
    cwd=repo_path,
    capture_output=True,
    text=True,
)

Local-path generation also requires a real git repo (.git must exist):

if not (repo_p / ".git").exists():
    raise HTTPException(
        status_code=400,
        detail=f"Not a git repository (no .git directory): '{gen_request.repo_path}'",
    )

Supported AI providers, CLIs, and credentials

Supported providers are fixed to claude, gemini, and cursor:

ai_provider: Literal["claude", "gemini", "cursor"] | None = None
assert VALID_AI_PROVIDERS == frozenset({"claude", "gemini", "cursor"})

AI_CLI_TIMEOUT must be greater than zero:

ai_cli_timeout: int = Field(default=60, gt=0)

The container image installs all three AI CLIs:

# Install Claude Code CLI (installs to ~/.local/bin)
RUN /bin/bash -o pipefail -c "curl -fsSL https://claude.ai/install.sh | bash"

# Install Cursor Agent CLI (installs to ~/.local/bin)
RUN /bin/bash -o pipefail -c "curl -fsSL https://cursor.com/install | bash"

# Configure npm for non-root global installs and install Gemini CLI
RUN mkdir -p /home/appuser/.npm-global \
    && npm config set prefix '/home/appuser/.npm-global' \
    && npm install -g @google/gemini-cli

Credential/config variables expected in .env:

AI_PROVIDER=claude
AI_MODEL=claude-opus-4-6[1m]
AI_CLI_TIMEOUT=60

# Claude - Option 1: API Key
# ANTHROPIC_API_KEY=

# Claude - Option 2: Vertex AI
# CLAUDE_CODE_USE_VERTEX=1
# CLOUD_ML_REGION=
# ANTHROPIC_VERTEX_PROJECT_ID=

# Gemini
# GEMINI_API_KEY=

# Cursor
# CURSOR_API_KEY=

The app checks provider CLI readiness before generation:

cli_flags = ["--trust"] if ai_provider == "cursor" else None
available, msg = await check_ai_cli_available(
    ai_provider, ai_model, cli_flags=cli_flags
)
if not available:
    await update_project_status(
        project_name,
        ai_provider,
        ai_model,
        status="error",
        owner=owner,
        error_message=msg,
    )
    return

Tip: You only need credentials for the provider selected in AI_PROVIDER, but that provider’s CLI must be installed and authenticated.

Mandatory ADMIN_KEY setup

ADMIN_KEY is required and must be at least 16 characters.

# REQUIRED - Admin key for user management (minimum 16 characters)
ADMIN_KEY=your-secure-admin-key-here-min-16-chars

Startup fails fast if ADMIN_KEY is missing or too short:

settings = get_settings()
if not settings.admin_key:
    logger.error("ADMIN_KEY environment variable is required")
    raise SystemExit(1)

if len(settings.admin_key) < 16:
    logger.error("ADMIN_KEY must be at least 16 characters long")
    raise SystemExit(1)

ADMIN_KEY is also used as the admin login secret:

if username == "admin" and api_key == settings.admin_key:
    is_admin = True
    authenticated = True

And as the HMAC secret for API key hashing:

secret = hmac_secret or os.getenv("ADMIN_KEY", "")
if not secret:
    msg = "ADMIN_KEY environment variable is required for key hashing"
    raise RuntimeError(msg)

Warning: Rotating ADMIN_KEY invalidates existing API key hashes, and ADMIN_KEY users cannot rotate this through the API ("ADMIN_KEY users cannot rotate keys. Change the ADMIN_KEY env var instead.").

Minimal .env baseline

ADMIN_KEY=<your-min-16-char-secret>
AI_PROVIDER=claude
AI_MODEL=claude-opus-4-6[1m]
AI_CLI_TIMEOUT=60
LOG_LEVEL=INFO

For local HTTP (non-HTTPS) development, this optional setting is available:

# Set to false for local HTTP development
# SECURE_COOKIES=false

docker-compose also expects .env:

services:
  docsfy:
    env_file: .env