- Python 3.11+
- Git
git clone https://github.com/IFindMe/Facebook-cli.git
cd Facebook-cli
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"# All tests
pytest tests/ -v
# With coverage
pytest tests/ --cov=src/fb --cov-report=term-missing
# Specific test file
pytest tests/unit/test_errors.py -vmypy src/fb/ --ignore-missing-imports# Check
ruff check src/fb/
ruff format src/fb/ --check
# Auto-fix
ruff check src/fb/ --fix
ruff format src/fb/src/fb/
├── cli.py # Click entry point (33 commands)
├── api/
│ ├── client.py # HTTP client (auth, retries, rate limits)
│ ├── pagination.py # Cursor pagination
│ └── versioning.py # API version management
├── auth/
│ ├── oauth.py # OAuth authorization-code flow
│ ├── tokens.py # Token storage and lifecycle
│ └── permissions.py # Scope management
├── domains/
│ ├── pages.py # Page operations
│ ├── posts.py # Post operations
│ ├── comments.py # Comment operations
│ ├── media.py # Media uploads
│ ├── messenger.py # Page messaging
│ └── insights.py # Analytics
├── output/
│ ├── json.py # JSON envelope output
│ ├── human.py # Human-readable output
│ └── redact.py # Token redaction
├── errors.py # Error taxonomy (exit codes 2-12)
├── config.py # XDG config loading
├── security.py # Credential storage
├── capabilities.py # Capability discovery
├── escape_hatch.py # fb api command
├── validation.py # Input validation
└── version.py # Version constants
The CLI follows a layered domain architecture (Option B):
High-level command → Domain service → Meta API client → Official Meta APIs
- cli.py — Click groups/commands (thin, delegates only)
- domains/ — Domain services (thin, ~100-200 lines each)
- api/ — HTTP client with auth injection, retries, rate limiting
- output/ — Formatting layer (JSON/human/redact)
- errors.py — Centralized error taxonomy
- Add
src/fb/domains/new_feature.py— thin service: args → API path/params → client → structured data - Add a Click group in
cli.pydelegating to it - Add fixtures/tests in
tests/mock_api/fixtures/+tests/unit/
No existing files change structurally.
- Domains never import other domains
dataanderrorare mutually exclusive in JSON envelope- Single version constant in
version.py; no other file hardcodes a version - Tokens never in argv, logs, or plaintext config
| Layer | Tool | Coverage |
|---|---|---|
| CLI parsing | Click CliRunner |
flags, help, exit codes |
| Validation | pytest | bounds, conflicts, access_token rejection |
| API client | respx |
version/base URL, auth injection, retries, rate-limit |
| Pagination | pytest | cursor follow, caps, pass-through |
| Error translation | pytest | Meta code → taxonomy matrix |
| Tokens | pytest + fake keyring | login, expiry, refresh, revoke, fallback |
| Security | pytest | redaction, argv-safety, perms, secure-delete |
respx— HTTP mock transportCredentialStore— fake keyring for tests- Ephemeral-port socket for OAuth
FB_CONFIG_DIRenv for filesystem isolation
docs/product/facebook-cli-overview.md— What/why/whodocs/product/capability-matrix.md— Full capability status matrixdocs/product/api-research.md— Meta API landscape (2026)docs/architecture/architecture.md— Options comparison, Option Bdocs/architecture/cli-design.md— Command tree, JSON contract, paginationdocs/architecture/authentication.md— OAuth flow, token storagedocs/architecture/security.md— Threat model, security boundariesdocs/architecture/testing-strategy.md— Test architectureCOMMANDS.md— Single source of truth for all commands
- Branch from
main - Run tests, mypy, ruff before committing
- Squash merge for clean history
- Commit messages: imperative mood, concise
# Pre-commit checks
pytest tests/ -x -q && mypy src/fb/ --ignore-missing-imports && ruff check src/fb/