Imported from llevintza/energlens (
AGENTS.md). Install upstream withnpx skills add llevintza/energlens. Copyright stays with the author.
Energlens — agent instructions
Electricity bill tracker. FastAPI + PostgreSQL backend, React/Vite SPA frontend,
and an energlens-ingest CLI that extracts bill PDFs with Claude. See
README.md for the layout and local setup.
Environment
This repo uses direnv. .envrc is committed and holds no secrets; it loads
your gitignored .env and exports it, so anything you run from this directory
inherits the same variables. After cloning:
gh auth login # or put a PAT in .env — see below
cp .env.example .env
direnv allow
Authenticate before direnv allow: .envrc reads the gh keyring at the
moment it evaluates, so logging in afterwards leaves the exported token empty
until you direnv reload or open a new shell. Editing .env reloads on its
own — direnv watches that file.
Without direnv, reproduce both halves before launching a harness. Sourcing
.env alone exports the blank placeholder and skips the keyring fallback:
set -a; . ./.env; set +a
: "${GITHUB_MCP_PAT:=$(gh auth token 2>/dev/null)}"; export GITHUB_MCP_PAT
GitHub MCP server
The github MCP server is configured at project scope for every harness, and
authenticates with a per-contributor GitHub token read from the
GITHUB_MCP_PAT environment variable. No token is committed — the config files
reference the variable name only.
| Harness | Committed config |
|---|---|
| Claude Code | .mcp.json |
| Copilot CLI | .mcp.json |
| Cursor | .cursor/mcp.json (note: ${env:VAR} syntax) |
| Gemini CLI | .gemini/settings.json |
| Codex | .codex/config.toml (trusted projects only) |
| Muse Code | user-level only — see README.md |
If the github MCP tools are missing or the server reports 401/failed, the
cause is almost always an unset GITHUB_MCP_PAT. To repair:
- In a fresh clone,
direnv allow— the.envrcguard names the problem on entering the directory. gh auth login, thendirenv reload—.envrcalready read the keyring, and authenticating does not re-trigger it on its own.- Or mint a PAT (see below) and set
GITHUB_MCP_PATin.env; that path needs no reload, because direnv watches.env.
Do not work around a missing token by putting a literal token in any committed
file, and do not add the token to .claude/settings.local.json or another
harness-specific settings file — that fixes one harness and silently leaves the
rest broken.
Token permissions, minted at https://github.com/settings/personal-access-tokens:
a fine-grained PAT needs Contents: Read and write, plus Workflows: Read and
write to edit files under .github/workflows/. On a classic PAT those are the
repo and workflow scopes — workflow is classic-only and is not offered in
the fine-grained UI. gh auth token returns the active account's token, which
carries classic scopes.
Commands
Everything runs through make from the repo root. Use it rather than raw uv,
npm or alembic: the targets know the working directories, and the database
ones run a preflight that explains failures instead of raising
ConnectionRefusedError.
| Task | Command |
|---|---|
| Set up a clone | make setup |
| Start Postgres | make db-up |
| The gate — run before calling anything done | make check |
| One backend test | make test-backend PYTEST_ARGS="-k currency" |
| New migration | make migration m="add reference to bill" |
| Check migrations match the models | make migrate-check |
| Reset the database | make db-reset (drops both databases) |
| Run the app | make dev-api, make dev-web |
| Check the deployed API answers | make api-preflight |
| Check the deployed API's OpenAPI contract | make api-smoke |
| Check the deployed site loads | make smoke-web |
| Everything else | make help |
make check is exactly what the merge-gating CI jobs run, so the two cannot
disagree. deploy-frontend.yml is not part of it — that is a deploy, not a
gate, and it depends on production-only repository variables. Neither is
make api-preflight or make api-smoke, for the same reason: they probe a live
production host, and a gate that depends on Render's uptime blocks merges when
Render is down. Nor is make smoke-web: it fetches the live site, which tells
you nothing about the branch in front of you.
Conventions
- Python is managed with
uvinbackend/andingest/; themaketargets wrap it. - Node is pinned at 22 by
.nvmrc—actions/setup-nodereads it throughnode-version-file,nvm usereads it locally. Two consumers cannot read it and so keep literal copies:enginesinfrontend/package.json(enforced rather than advisory, becausefrontend/.npmrcsetsengine-strict=true, sonpm cifails instead of warning) andREADME.md.backend/tests/test_toolchain.pyfails if either copy drifts. - Database identity — user, port, all three database names — is defined in
scripts/db.env.scripts/db.shandscripts/pgdev.shsource it; the environment overrides it, soPGPORT=5433 make db-upworks. Four consumers cannot read it when they need it and so keep literal copies —backend/app/config.py, the CI service block,docker-compose.ymland.env.example.backend/tests/test_config.pyfails if any copy drifts, so changedb.envand let the test tell you what else to update. - Three databases:
energlens(yours),energlens_test(every table dropped on each test run), andenerglens_migrations(throwaway, rebuilt by Alembic alone formake migrate-check— checking migrations against the test database would compare the models with themselves and never see drift). - Because two of those are destroyed routinely,
scripts/db.shandconftest.pyboth refuse to run when the target host is not localhost — whether that host comes fromPGHOSTor fromDATABASE_URL. - Never commit
.env, tokens, credentials, real bill PDFs, or.claude/settings.local.json.