Files
agentrhq__authsome/AGENTS.md
T
Manoj Bajaj d9936d5699 feat!: replace init and scan with authsome onboard
Unifies first-run setup into a single idempotent command that registers
identity, completes claim, and imports API keys from env. Persists
--base-url in client config for remote daemon connections.

Closes #434

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 02:22:10 -07:00

164 lines
10 KiB
Markdown

# AGENTS.md
This file provides guidance to AI coding agents (Claude Code, Codex, Copilot, etc.) when working with this repository.
## Commands
Always use `uv run` to execute commands — do not use `python`, `python3`, `pip`, or bare tool names directly.
```bash
# Install in editable mode (required before running anything)
uv pip install -e ".[dev]"
# Run all tests
uv run pytest
# Run a single test file
uv run pytest tests/test_client.py
# Run a single test by name
uv run pytest tests/test_client.py::test_login_pkce -v
# Run tests with coverage
uv run pytest --cov=authsome
```
Run linting and type checks:
```bash
# Lint and auto-fix
uv run ruff check --fix src/ tests/
# Format
uv run ruff format src/ tests/
# Type check
uv run ty check src/
```
The CLI entry point after install:
```bash
uv run authsome onboard
uv run authsome login github
uv run authsome provider list
```
## Engineering principles
> For the full set of contribution guidelines, design principles, and AI-assisted development practices, see [CONTRIBUTING.md](CONTRIBUTING.md). Everything in that document applies to AI agents as well as human contributors.
These rules govern all changes to this codebase — apply them without exception.
**YAGNI.** Implement only what the current task demands. Don't build for hypothetical future requirements.
**Use trusted libraries over reinventing.** Reach for a well-maintained dependency before writing your own crypto, HTTP client, or token parser.
**Deep modules over shallow ones.** Prefer a small surface area with rich internals over many thin wrappers. More files is not more modular.
**Composition over inheritance.** Prefer small collaborators wired together through explicit dependencies over inheritance hierarchies. Use inheritance only when there is a real subtype relationship and composition would make the design less clear.
**Single responsibility and separation of concerns.** Auth authenticates. Vault stores credentials. CLI presents output. A flow must not write to storage; storage must not know about OAuth. If a function is hard to name, it's doing too many things.
**No premature optimization.** Don't add caching, batching, or concurrency before a measured performance problem exists. Simple and slow is fixable; complex and wrong is not.
**Don't do it just because you can.** Clever is a cost. If a feature, abstraction, or refactor doesn't solve a real problem that exists today, skip it.
**Leave it better than you found it.** Fix a nearby typo, remove a dead import, or clarify a confusing comment while you're in the area — not the whole file, just the immediate vicinity.
**Comment the why, not the what.** Use Google-style docstrings for public interfaces. Inline comments explain non-obvious invariants, workarounds, or hidden constraints — not what the code already says.
**Update docs with code.** If you change behavior, update the relevant docstring, `README.md`, or `CONTRIBUTING.md` in the same commit.
## AI agent rules
**Verify before claiming done.** Run `uv run pytest`, `uv run ruff check`, and `uv run ty check`. Confirm they pass before stating work is complete. Never assume.
**Minimal blast radius.** Change only what was asked. Don't refactor, rename, or reorganize while implementing a feature — save cleanup for a dedicated commit.
**Surface uncertainty.** If multiple valid approaches exist, present the tradeoffs. Don't silently pick one and hide the decision.
**No hallucinated APIs.** If unsure a method or parameter exists, search the codebase before using it.
**Use real runtime types.** Do not hide missing imports or circular dependencies behind quoted annotations, `TYPE_CHECKING` imports, or postponed annotations. If an annotation names a class from another module, import it normally and let import-time failures reveal architectural cycles. Fix those cycles at the ownership boundary. Use `Self` for same-class return types.
**Read before write.** Understand the existing implementation before modifying it.
**Prefer reversible changes.** Avoid destructive operations without explicit user confirmation.
**Small, focused commits.** One logical change per commit. Resist the urge to batch unrelated changes.
**Explain the why in commit messages.** The diff shows what changed; the message says why.
**Never commit directly to `main`.** All changes must go through a pull request. Create a feature or chore branch, push it, and open a PR — even for single-line fixes.
---
## Conventions
**Commits and PRs:** Use [Conventional Commits](https://www.conventionalcommits.org/) style — `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`. Mark breaking changes with `!` (e.g., `feat!:`) or a `BREAKING CHANGE:` footer.
**Branches:** Use Gitflow-style branch names with a short kebab-case description. Prefer `feature/<description>` for new capabilities, `bugfix/<description>` for non-urgent fixes, `hotfix/<description>` for urgent production fixes, `release/<version>` for release preparation, and `support/<description>` for maintenance lines.
**Pre-commit:** This repo uses `pre-commit` with ruff for lint and format. Run `pre-commit run --all-files` before finishing any change. The hook runs automatically on `git commit`.
**Available skills:** `ruff`, `uv`, `ty`, `uv-trusted-publish-github-action`, `release-please-changelog` are installed in `.claude/skills/`.
## Architecture
**Identity (`src/authsome/identity/local.py`)** manages local Ed25519 key pairs and `did:key` DIDs. Key material lives at `~/.authsome/identities/<handle>.key` (mode `0600`); metadata at `~/.authsome/identities/<handle>.json`. An Identity is a cryptographic agent — it is not a credential namespace. Credential namespacing is owned by a Vault (see below).
**Principal & Vault domain models (`src/authsome/identity/principal.py`)** define the two concepts that own credentials. A **Principal** is a non-cryptographic logical partition (human or team) identified by an opaque `PrincipalId`. A **Vault** is a named credential store owned by exactly one Principal and identified by an opaque `VaultId`. Credentials are scoped to a vault: `vault:<vault_id>:...`. An Identity claims membership in a Principal via an `IdentityClaimRecord`; the claim must be accepted before vault access is granted.
**Five server-owned registries** persist in the relational Store (`src/authsome/server/store/repositories.py`), backed by SQLite at `~/.authsome/server/authsome.db` by default (or Postgres via `AUTHSOME_DATABASE_URL`):
| Registry | Table | Authoritative for |
|----------|-------|-------------------|
| `IdentityRegistry` | `identity_registrations` | Handle → DID mapping (PoP JWT validation) |
| `PrincipalRegistry` | `principals` | PrincipalId → email |
| `VaultRegistry` | `vaults` | VaultId → VaultHandle |
| `IdentityClaimRegistry` | `identity_claims` | Identity → Principal claim + ClaimStatus |
| `PrincipalVaultBindingRegistry` | `principal_vault_bindings` | Principal → default Vault binding |
**PoP Auth (`src/authsome/identity/proof.py`)** implements Proof-of-Possession JWT creation and validation. Every protected daemon request carries `Authorization: PoP <jwt>` signed with the local Ed25519 key. The JWT is bound to the specific HTTP method, path, and body SHA-256. The daemon validates the signature, checks the `jti` replay cache, and confirms `sub` (handle) → `iss` (DID) via the Identity Registry.
**CredentialService (`src/authsome/server/credential_service.py`)** is the authentication and credential lifecycle coordinator. It owns OAuth flows, token refresh, login/logout/revoke. Lives in `server/` because it coordinates `auth/` flows with `vault/` storage and `audit/` logging. Constructed with keyword-only `(credentials, providers, identity, principal_id, principal_role, vault_id)` — a `CredentialRepository` and `ProviderRepository` are injected rather than a raw vault; all credential store keys are namespaced as `vault:<vault_id>:...`. The caller (server dependency injection, `routes/_deps.py`) resolves `vault_id` from the `PrincipalVaultBindingRegistry` before constructing `CredentialService`. Runtime knobs (host, port, base URL, token/session TTLs, analytics) come from `ServerSettings` (`src/authsome/server/settings.py`, a `pydantic-settings` `BaseSettings` read from `AUTHSOME_*`).
**Flows (`src/authsome/auth/flows/`)** implement the `AuthFlow.authenticate()` interface. Each flow returns a `ConnectionRecord`. The `auth/` module is a leaf — it imports nothing from `vault/`, `audit/`, or `server/`.
| Flow | Class | Notes |
|------|-------|-------|
| `pkce` | `PkceFlow` | Spins up an HTTP server on port **7999** for the OAuth callback |
| `device_code` | `DeviceCodeFlow` | Polls token endpoint; no browser needed |
| `dcr_pkce` | `DcrPkceFlow` | Dynamic Client Registration then PKCE |
| `api_key` | `ApiKeyFlow` | Prompts via secure browser bridge |
**Provider Registry** resolves providers in this order: custom providers stored in the vault under the `providers` collection override bundled JSON in `src/authsome/auth/bundled_providers/`. Bundled providers (GitHub, Google, Okta, Linear, OpenAI) are loaded via `importlib.resources`.
**Vault (`src/authsome/vault/`)** is the encrypted KV store. The master key lives at `~/.authsome/server/master.key` (mode `0600`) or in the OS keyring. All credential blobs are encrypted at rest; the CredentialService reads and writes plaintext through the Vault without knowing encryption details.
**Storage** uses a DiskStore-backed KV at `~/.authsome/server/kv_store/`. Store keys follow the pattern:
```
vault:<vault_id>:<provider>:connection:<connection_name>
vault:<vault_id>:<provider>:metadata
vault:<vault_id>:<provider>:state
server:<provider>:client
```
**Config** (`GlobalConfig`) is stored in the KV store under `config/global`. Key fields: `active_identity` (the handle of the current identity), `vault_id` (the active vault resolved at `authsome onboard`). Encryption mode is set via `config.encryption.mode` (`local_key` or `keyring`).
**CLI (`src/authsome/cli/main.py`)** is Click-based. All commands support `--json` for machine-readable output. `authsome onboard` creates the local identity, registers it with the daemon, imports API keys from env, and writes `active_identity` to config.
## Agent skills
### Issue tracker
Issues live in GitHub Issues for `agentrhq/authsome`. See `docs/agents/issue-tracker.md`.
### Triage labels
Default canonical labels (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`.
### Domain docs
Single-context layout — one `CONTEXT.md` + `docs/adr/` at the repo root. See `docs/agents/domain.md`.