chore: scaffold payroll extension

Repo tooling and LNbits extension metadata only — no runtime code yet, so
this lands separately from the feature work per the workspace commit rules
(cross-cutting concerns commit first).

- MIT licence, .gitignore, README
- Makefile + pyproject wired for the standard aio fork lint pipeline
  (black + ruff + mypy) and pytest
- config.json declaring id/version/tile so the extension is installable
  from the aiolabs catalog once there is something to install

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj
This commit is contained in:
Padreug 2026-08-31 13:36:32 +02:00
commit a211a8ab8b
7 changed files with 191 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
__pycache__/
*.py[cod]
*.egg-info/
.venv/
node_modules/
*.sqlite3
.DS_Store
.pytest_cache/
.ruff_cache/
.mypy_cache/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 aiolabs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

32
Makefile Normal file
View file

@ -0,0 +1,32 @@
all: format check
format: black ruff
check: mypy checkblack checkruff
mypy:
uv run mypy .
black:
uv run black .
ruff:
uv run ruff check . --fix
checkruff:
uv run ruff check .
checkblack:
uv run black --check .
test:
PYTHONUNBUFFERED=1 \
DEBUG=true \
uv run pytest
install-pre-commit-hook:
@echo "Installing pre-commit hook to git"
uv run pre-commit install
pre-commit:
uv run pre-commit run --all-files

42
README.md Normal file
View file

@ -0,0 +1,42 @@
# Payroll
Scheduled recurring payouts between LNbits wallets, driven by the instance
**super user**.
Pick a user from the account directory, point the payout at one of their
wallets, set an amount + currency, a start date, how often it pays and how
many times — Payroll then moves the money on schedule, from a source wallet
you control, and keeps a ledger of every attempt.
## Status
Early. Built for `aiolabs` LNbits instances; see `docs/` for the data model
and operational notes.
## Who can use it
Everything under `/payroll/api/v1/` that reads the account directory or
touches a contract is gated on `check_super_user` — payroll moves money
between accounts the operator does not own, so wallet-level admin keys are
deliberately *not* enough. (`require_admin_key` authorises writes to your
own wallet only; it is not instance-admin. See the auth table in the
workspace `CLAUDE.md`.)
Add `payroll` to `LNBITS_ADMIN_EXTENSIONS` so the extension is hidden from
ordinary users in the UI as well.
## Development
```
make # black + ruff + mypy
make test # pytest
```
The dev LNbits compose mounts `~/dev/shared/extensions/` at `/shared` with
`LNBITS_EXTENSIONS_PATH=/shared`, so this checkout *is* the installed
extension. FakeWallet is enough — no regtest needed for anything except
end-to-end Lightning behaviour.
## Licence
MIT

23
config.json Normal file
View file

@ -0,0 +1,23 @@
{
"id": "payroll",
"version": "0.1.0",
"name": "Payroll",
"repo": "https://git.atitlan.io/aiolabs/payroll",
"short_description": "Scheduled recurring salary payouts between LNbits wallets",
"description": "",
"tile": "/payroll/static/image/payroll.png",
"min_lnbits_version": "1.4.1",
"contributors": [
{
"name": "padreug",
"uri": "https://git.atitlan.io/padreug",
"role": "Author"
}
],
"images": [],
"license": "MIT",
"paid_features": "",
"tags": ["Finance"],
"donate": "",
"hidden": false
}

63
pyproject.toml Normal file
View file

@ -0,0 +1,63 @@
[project]
name = "lnbits-payroll"
version = "0.0.0"
requires-python = ">=3.10,<3.13"
description = "Recurring salary payouts between LNbits wallets, scheduled by the instance super user."
authors = [{ name = "aiolabs", email = "dev@aiolabs.dev" }]
urls = { Homepage = "https://git.atitlan.io/aiolabs/payroll", Repository = "https://git.atitlan.io/aiolabs/payroll" }
dependencies = [ "lnbits>1" ]
[dependency-groups]
dev = [
"black",
"pytest-asyncio",
"pytest",
"mypy",
"pre-commit",
"ruff",
]
[tool.poetry]
package-mode = false
[tool.mypy]
plugins = ["pydantic.mypy"]
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true
[tool.pytest.ini_options]
log_cli = false
testpaths = [
"tests"
]
[tool.black]
line-length = 88
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B"]
ignore = ["C901"]
fixable = ["ALL"]
unfixable = []
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.lint.pep8-naming]
classmethod-decorators = [
"validator",
"root_validator",
]
[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = [
"fastapi.Depends",
"fastapi.Query",
]

BIN
static/image/payroll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B