Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,5 @@ Thumbs.db
.ruff_cache/

# Local opencode config
AGENTS.md
.agents/

30 changes: 30 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# apiauth

## Purpose
CLI tool for API key and JWT lifecycle management with encrypted local store — generate, store, verify, rotate, and revoke keys with an encrypted local keystore.

## Build & Test Commands
- Install: `pip install -e .` or `pip install apiauth`
- Test: `pytest tests/` (or `python -m pytest tests/ -v --tb=short`)
- Lint: `ruff check src/ --target-version py310`
- Build: `pip wheel . --wheel-dir dist/`
- CLI check: `apiauth --version && apiauth --help`

## Architecture
Key directories:
- `src/apiauth/` — Main package (CLI, keystore, crypto, commands)
- `tests/` — Test suite
- `.github/workflows/` — CI/CD (auto-code-review.yml, ci.yml, publish.yml)
- `dist/` — Built distributions

## Conventions
- Language: Python 3.10+
- Test framework: pytest
- CI: GitHub Actions (matrix: Python 3.10, 3.11, 3.12, 3.13)
- Linting: ruff (line-length 120, target py310)
- Formatting: ruff
- Package layout: src/ layout with setuptools
- Type checking: py.typed included
- Dependencies: click, cryptography, pyjwt, rich, python-dateutil
- CLI entry point: apiauth.cli:cli
- Master branch: master
22 changes: 15 additions & 7 deletions src/apiauth/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
from rich.table import Table
from typing import Any

# Ensure UTF-8 output on Windows consoles that default to cp1252
if sys.platform == "win32":
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
pass

from . import __version__
from .keygen import create_api_key_entry, create_jwt_entry, rotate_jwt, rotate_key
from .keystore import Keystore
Expand Down Expand Up @@ -176,7 +184,7 @@ def list_keys(ctx: click.Context, service: str | None, json_output: bool, show_e
# ── show ──────────────────────────────────────────────────────────────


@cli.command(name="list")
@cli.command(name="show")
@click.argument("key_id")
@click.pass_context
def show(ctx: click.Context, key_id: str) -> None:
Expand All @@ -199,7 +207,7 @@ def show(ctx: click.Context, key_id: str) -> None:
# ── rotate ────────────────────────────────────────────────────────────


@cli.command(name="list")
@cli.command(name="rotate")
@click.argument("key_id")
@click.option("--expiry-days", "-e", type=int, default=None, help="New expiry in days")
@click.pass_context
Expand Down Expand Up @@ -230,7 +238,7 @@ def rotate(ctx: click.Context, key_id: str, expiry_days: int | None) -> None:
# ── revoke ────────────────────────────────────────────────────────────


@cli.command(name="list")
@cli.command(name="revoke")
@click.argument("key_id")
@click.pass_context
def revoke(ctx: click.Context, key_id: str) -> None:
Expand All @@ -249,7 +257,7 @@ def revoke(ctx: click.Context, key_id: str) -> None:
# ── verify ────────────────────────────────────────────────────────────


@cli.command(name="list")
@cli.command(name="verify")
@click.argument("api_key")
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
@click.pass_context
Expand Down Expand Up @@ -347,7 +355,7 @@ def import_key(
# ── export ────────────────────────────────────────────────────────────


@cli.command(name="list")
@cli.command(name="export")
@click.option("--format", "-f", "fmt", type=click.Choice(["env", "json", "dotenv", "github-actions"]), default="env")
@click.option("--service", "-s", default=None, help="Filter by service")
@click.pass_context
Expand Down Expand Up @@ -432,7 +440,7 @@ def _export_github_actions(active: list[dict]) -> None:
# ── audit ─────────────────────────────────────────────────────────────


@cli.command(name="list")
@cli.command(name="audit")
@click.pass_context
def audit(ctx: click.Context) -> None:
"""Audit keystore: find expired, expiring, and revoked keys."""
Expand Down Expand Up @@ -491,7 +499,7 @@ def audit(ctx: click.Context) -> None:
# ── stats ─────────────────────────────────────────────────────────────


@cli.command(name="list")
@cli.command(name="stats")
@click.pass_context
def stats(ctx: click.Context) -> None:
"""Show keystore statistics."""
Expand Down