sumac-home 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": "Edit|Write",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "f=$(cat | python3 -c 'import json,sys; print(json.load(sys.stdin).get(\"tool_input\",{}).get(\"file_path\",\"\"))'); case \"$f\" in *.py) uv run ruff format \"$f\" ;; esac"
10
+ }
11
+ ]
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ data/**/*.jsonl merge=union
@@ -0,0 +1,20 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v3
14
+ with:
15
+ enable-cache: true
16
+ - run: uv sync --all-groups
17
+ - run: uv run ruff format --check .
18
+ - run: uv run ruff check .
19
+ - run: uv run ty check
20
+ - run: uv run pytest
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .ruff_cache/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.5
2
+ Name: sumac-home
3
+ Version: 0.1.0
4
+ Summary: Home grocery inventory app
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: pydantic>=2
7
+ Requires-Dist: rich>=13
8
+ Requires-Dist: sealedlog>=0.1.0
9
+ Requires-Dist: typer>=0.12
10
+ Description-Content-Type: text/markdown
11
+
12
+ # sumac
13
+
14
+ **🍋 sumac: home grocery inventory app**
15
+
16
+ Encrypted-at-rest grocery inventory for a household sharing one git repo and one passphrase.
17
+ Locations, products, and quantities are never visible to someone holding the repo without the
18
+ passphrase — not even in file or directory names. See `docs/FORMAT.md` for the on-disk format
19
+ and threat model, and `docs/LAYOUT.md` for what's read-only vs mutable.
20
+
21
+ ## Install
22
+
23
+ Published on PyPI as `sumac-home` (`sumac` was taken); the command is still `sumac`.
24
+
25
+ ```sh
26
+ uv tool install sumac-home # or: pip install sumac-home
27
+ ```
28
+
29
+ For development (this checkout):
30
+
31
+ ```sh
32
+ uv sync
33
+ ```
34
+
35
+ Depends on [`sealedlog`](https://pypi.org/project/sealedlog/) (the encrypted append-only log
36
+ primitive) from PyPI.
37
+
38
+ ## Passphrase
39
+
40
+ Set `SUMAC_PASSPHRASE`, or sumac will prompt interactively. The passphrase is shared by every
41
+ user of the household's vault.
42
+
43
+ ## Usage
44
+
45
+ ```sh
46
+ sumac init # once, creates data/
47
+ sumac config add-location "Fridge" --id fridge
48
+ sumac config add-location "Pantry" --id pantry
49
+ sumac config show
50
+
51
+ sumac add purchase milk 2 l --to fridge
52
+ sumac add consumption milk 1 l --from fridge
53
+ sumac add movement rice 1 kg --from pantry --to fridge
54
+ sumac snapshot fridge "milk=1/l" "eggs=6/ct" # reconciliation: resets fridge's products
55
+
56
+ sumac status # current inventory, all locations
57
+ sumac status fridge # current inventory, one location
58
+ sumac find milk # where is milk right now?
59
+ sumac log # full ordered event log
60
+ sumac verify # re-authenticate every line; check actors
61
+ ```
62
+
63
+ All commands take `--data-dir` (default `data`, or `$SUMAC_DATA_DIR`).
64
+
65
+ ### Locations nest
66
+
67
+ A location can have a parent, so shelves, doors, bins, drawers — anything — nest under a
68
+ container to arbitrary depth. There's no separate "shelf" or "grid" type; a sub-location is just
69
+ another location with `--parent` set.
70
+
71
+ ```sh
72
+ sumac config add-location "Door" --id fridge-door --parent fridge
73
+ sumac config add-array "Shelf" --parent fridge --count 4 # Shelf 1..4 under fridge
74
+ sumac config add-grid "Bin" --parent pantry --rows 3 --cols 4 # Bin R1C1..R3C4 under pantry
75
+ sumac config show # renders the tree
76
+ ```
77
+
78
+ `sumac status <location>` and `sumac find` both include everything nested under a location, not
79
+ just that exact node — `sumac status fridge` sums the fridge itself, its door, and its shelves in
80
+ one pass. Query a sub-location directly (e.g. `sumac status fridge-door`) to scope to just that
81
+ node and its own descendants.
82
+
83
+ ## Development
84
+
85
+ ```sh
86
+ uv run ruff format .
87
+ uv run ruff check .
88
+ uv run ty check
89
+ uv run pytest
90
+ ```
@@ -0,0 +1,79 @@
1
+ # sumac
2
+
3
+ **🍋 sumac: home grocery inventory app**
4
+
5
+ Encrypted-at-rest grocery inventory for a household sharing one git repo and one passphrase.
6
+ Locations, products, and quantities are never visible to someone holding the repo without the
7
+ passphrase — not even in file or directory names. See `docs/FORMAT.md` for the on-disk format
8
+ and threat model, and `docs/LAYOUT.md` for what's read-only vs mutable.
9
+
10
+ ## Install
11
+
12
+ Published on PyPI as `sumac-home` (`sumac` was taken); the command is still `sumac`.
13
+
14
+ ```sh
15
+ uv tool install sumac-home # or: pip install sumac-home
16
+ ```
17
+
18
+ For development (this checkout):
19
+
20
+ ```sh
21
+ uv sync
22
+ ```
23
+
24
+ Depends on [`sealedlog`](https://pypi.org/project/sealedlog/) (the encrypted append-only log
25
+ primitive) from PyPI.
26
+
27
+ ## Passphrase
28
+
29
+ Set `SUMAC_PASSPHRASE`, or sumac will prompt interactively. The passphrase is shared by every
30
+ user of the household's vault.
31
+
32
+ ## Usage
33
+
34
+ ```sh
35
+ sumac init # once, creates data/
36
+ sumac config add-location "Fridge" --id fridge
37
+ sumac config add-location "Pantry" --id pantry
38
+ sumac config show
39
+
40
+ sumac add purchase milk 2 l --to fridge
41
+ sumac add consumption milk 1 l --from fridge
42
+ sumac add movement rice 1 kg --from pantry --to fridge
43
+ sumac snapshot fridge "milk=1/l" "eggs=6/ct" # reconciliation: resets fridge's products
44
+
45
+ sumac status # current inventory, all locations
46
+ sumac status fridge # current inventory, one location
47
+ sumac find milk # where is milk right now?
48
+ sumac log # full ordered event log
49
+ sumac verify # re-authenticate every line; check actors
50
+ ```
51
+
52
+ All commands take `--data-dir` (default `data`, or `$SUMAC_DATA_DIR`).
53
+
54
+ ### Locations nest
55
+
56
+ A location can have a parent, so shelves, doors, bins, drawers — anything — nest under a
57
+ container to arbitrary depth. There's no separate "shelf" or "grid" type; a sub-location is just
58
+ another location with `--parent` set.
59
+
60
+ ```sh
61
+ sumac config add-location "Door" --id fridge-door --parent fridge
62
+ sumac config add-array "Shelf" --parent fridge --count 4 # Shelf 1..4 under fridge
63
+ sumac config add-grid "Bin" --parent pantry --rows 3 --cols 4 # Bin R1C1..R3C4 under pantry
64
+ sumac config show # renders the tree
65
+ ```
66
+
67
+ `sumac status <location>` and `sumac find` both include everything nested under a location, not
68
+ just that exact node — `sumac status fridge` sums the fridge itself, its door, and its shelves in
69
+ one pass. Query a sub-location directly (e.g. `sumac status fridge-door`) to scope to just that
70
+ node and its own descendants.
71
+
72
+ ## Development
73
+
74
+ ```sh
75
+ uv run ruff format .
76
+ uv run ruff check .
77
+ uv run ty check
78
+ uv run pytest
79
+ ```
@@ -0,0 +1,61 @@
1
+ # On-disk format and threat model
2
+
3
+ ## Threat model
4
+
5
+ Anyone holding the repo but not the passphrase must learn nothing about the home's layout or
6
+ its contents: no location names, no product names, no quantities. Accepted leakage: record
7
+ count, approximate record size, commit timestamps, and OS usernames.
8
+
9
+ Households share one passphrase. Ownership of a user's log is a convention
10
+ (`getpass.getuser()`), not enforced by file permissions — the design makes violations
11
+ *detectable* (`sumac verify`), not impossible.
12
+
13
+ ## Layout
14
+
15
+ ```
16
+ data/
17
+ vault.json # plaintext: format version, Argon2id params, salt, verifier
18
+ config.jsonl.enc # encrypted JSONL, append-only: location definitions
19
+ log/<osuser>.jsonl # encrypted JSONL, append-only, one per user: changes and snapshots
20
+ ```
21
+
22
+ Every path component is a fixed literal or an OS username — never derived from a location or
23
+ product name.
24
+
25
+ ## Sealed line
26
+
27
+ The line-sealing primitive — AEAD, nonces, base64 framing, AAD, Argon2id key derivation, and
28
+ the wrong-passphrase verifier — is not sumac's own code. It's provided by
29
+ [`sealedlog`](https://pypi.org/project/sealedlog/), a standalone library extracted from an
30
+ earlier version of this app; see its `docs/FORMAT.md` for the full spec. Summary of what that
31
+ buys sumac:
32
+
33
+ Each line is `base64(nonce‖ciphertext‖tag)`, sealed with XChaCha20-Poly1305 and a fresh random
34
+ 24-byte nonce. Appending is a byte-append, so git packs the history well. This costs per-line
35
+ ciphertext overhead and leaks record count and approximate size — accepted per the threat model.
36
+
37
+ ## AAD binding
38
+
39
+ Every sealed line is bound to the stream it belongs to via associated data built from sumac's
40
+ namespace (`sumac.NAMESPACE`, `"sumac"`) and the `stream_id` (`"config"` or `"log:<osuser>"`) —
41
+ see `sealedlog`'s AAD scheme for the exact byte layout. A line copied out of one stream into
42
+ another fails to authenticate. This is what makes the ownership convention auditable: it can't
43
+ stop a user from truncating their own file, but it prevents laundering a record into someone
44
+ else's history.
45
+
46
+ ## Key derivation
47
+
48
+ The key is derived from the shared passphrase via Argon2id (`sealedlog.Vault`), with a random
49
+ salt and the KDF params stored in `vault.json` alongside sumac's own `format_version`. A
50
+ `verifier` — a known plaintext sealed at vault-creation time — lets `sealedlog.Vault.unlock`
51
+ reject a wrong passphrase immediately with `WrongPassphraseError`, instead of producing garbage
52
+ downstream. `sumac.vault` wraps `Vault.create`/`Vault.unlock` with sumac's namespace baked in so
53
+ call sites can't typo it.
54
+
55
+ ## Versioning
56
+
57
+ Every record carries `schema_version`. A reader that encounters a record from a newer schema
58
+ than it understands raises an "upgrade sumac" error rather than guessing.
59
+
60
+ `data/**/*.jsonl` is declared `merge=union` in `.gitattributes`: correct for append-only
61
+ streams, and it keeps concurrent pushes from conflicting.
@@ -0,0 +1,21 @@
1
+ # Read-only vs mutable
2
+
3
+ Read-only-ness is a convention, not enforced by file permissions. `sumac verify` detects
4
+ violations after the fact; nothing prevents them at write time except `store.append`'s check
5
+ that a `log:<osuser>` stream matches the current OS user.
6
+
7
+ | Path | Mutability | Notes |
8
+ | --- | --- | --- |
9
+ | `src/sumac/` | Read-only for users | Pulled with the app; users don't edit it. |
10
+ | `pyproject.toml`, `uv.lock`, CI, hooks | Read-only for users | App-level tooling. |
11
+ | `data/vault.json` | Written once, by `sumac init` | KDF params and verifier; not append-only. |
12
+ | `data/config.jsonl.enc` | Mutable by any user | Shared location registry; append-only. |
13
+ | `data/log/<osuser>.jsonl` | Mutable only by `<osuser>` | Everyone else: read-only. |
14
+
15
+ A user appends their own changes and snapshots to `data/log/<their-username>.jsonl` and never
16
+ writes into another user's log. Corrections are new records carrying `supersedes: <record-id>`;
17
+ no one ever rewrites or deletes a line in a log that isn't theirs — or, for that matter, in
18
+ their own, since the format is append-only end to end.
19
+
20
+ Run `sumac verify` after pulling to confirm every line in every log still authenticates under
21
+ its own stream, and that no record's `actor` field disagrees with the file it lives in.
@@ -0,0 +1,136 @@
1
+ # sumac — encrypted home grocery inventory
2
+
3
+ ## Context
4
+
5
+ `/workspace` is a greenfield repo (`README.md`, `prompt.md`, one commit). `prompt.md` specifies a Python
6
+ app that logs a household's grocery inventory across locations, with everything sensitive encrypted at
7
+ rest so the repo can be pushed to a normal git remote.
8
+
9
+ The threat model is narrow and worth stating, because it drives the whole design: **anyone holding the
10
+ repo but not the passphrase must learn nothing about the home's layout or its contents.** That means the
11
+ location config is encrypted too, and no filename, directory name, or git metadata may be derived from a
12
+ location or product name. What we knowingly accept as leakage: record count, approximate record size,
13
+ timestamps of commits, and OS usernames.
14
+
15
+ Households share one passphrase. Users `git pull` the app, append their own records, and push. Ownership
16
+ is a convention (`getpass.getuser()`), not file permissions — so the design makes violations *detectable*
17
+ rather than impossible.
18
+
19
+ Decisions taken with the user: passphrase from `SUMAC_PASSPHRASE` else interactive prompt; Typer + rich
20
+ for the CLI; one JSONL log per user.
21
+
22
+ ## On-disk layout
23
+
24
+ ```
25
+ data/ # mutable — users append here
26
+ vault.json # plaintext header: format version, KDF params, salt, verifier
27
+ config.jsonl.enc # encrypted JSONL, append-only; latest revision wins
28
+ log/<osuser>.jsonl # encrypted JSONL, one per user, append-only
29
+ src/sumac/ # read-only for users
30
+ ```
31
+
32
+ Every path component is a fixed literal or an OS username. Nothing is derived from user data.
33
+
34
+ **Sealed line** — `base64(nonce‖ciphertext‖tag)` + `\n`, XChaCha20-Poly1305, fresh 24-byte nonce per line.
35
+ Appending is a byte-append; git packs it well.
36
+
37
+ **AAD binds each line to its stream**: `b"sumac/v1|" + stream_id`, where `stream_id` is `"config"` or
38
+ `"log:<osuser>"`. A ciphertext line copied out of alice's log into bob's fails to open. This is what makes
39
+ the ownership convention auditable — it can't stop alice from truncating her own file, but no one can
40
+ launder a record into someone else's history.
41
+
42
+ **`vault.json`** holds `format_version`, Argon2id params (`salt`, `opslimit`, `memlimit`), and a `verifier`
43
+ line sealed under AAD `sumac/v1|verifier`. A wrong passphrase fails there with a clear message instead of
44
+ producing garbage downstream.
45
+
46
+ ## Modules
47
+
48
+ One scope each; `models.py` is standalone so the data model can be edited without touching anything else.
49
+
50
+ | Module | Responsibility |
51
+ | --- | --- |
52
+ | `sumac/__init__.py` | `__version__`, `SCHEMA_VERSION`, `FORMAT_VERSION` |
53
+ | `sumac/models.py` | Frozen dataclasses. No I/O, no crypto, no pydantic imports. |
54
+ | `sumac/schemas.py` | Pydantic v2 models at ingest boundaries + `to_domain()` converters |
55
+ | `sumac/crypto.py` | `derive_key`, `seal_line`, `open_line`, `new_header`, `check_passphrase` |
56
+ | `sumac/passphrase.py` | env-then-prompt resolution, key caching within a process |
57
+ | `sumac/paths.py` | Data-dir layout; the single place path names are constructed |
58
+ | `sumac/store.py` | Encrypted JSONL append/iterate over a `stream_id` |
59
+ | `sumac/config.py` | Location layout on top of `store` |
60
+ | `sumac/ledger.py` | Fold snapshots + changes → current inventory |
61
+ | `sumac/render.py` | rich tables/panels — kept out of `cli.py` so command logic stays testable |
62
+ | `sumac/cli.py` | Typer app |
63
+ | `sumac/errors.py` | Exception hierarchy the CLI maps to exit codes |
64
+
65
+ ## Data model (`models.py`)
66
+
67
+ - `Location(id, name, parent_id, metadata)` — flat with optional parent, so sublocations nest arbitrarily.
68
+ - `Product(id, name, unit, category, metadata)`
69
+ - `Quantity(amount: Decimal, unit: str)` — mismatched units raise rather than silently coerce.
70
+ - `ChangeKind` enum: `purchase | consumption | waste | discovery | correction | movement`.
71
+ - `InventoryChange(..., product_id, quantity, from_location, to_location, ...)` — a delta or a transfer.
72
+ - `SnapshotEntry(product_id, quantity, metadata)`; `InventorySnapshot(location_id, entries, ...)` — the
73
+ full observed state of one location at one time.
74
+ - `Record(schema_version, type, id, ts, actor, supersedes, payload)` — the envelope on every JSONL line.
75
+
76
+ `metadata: Mapping[str, JsonValue]` on products, changes, snapshots and snapshot entries carries seller- or
77
+ user-supplied extras beyond the core model; validated as JSON-serialisable at ingest, otherwise untouched.
78
+
79
+ Corrections never rewrite: a new record carries `supersedes: <record-id>`.
80
+
81
+ ## Ledger semantics (`ledger.py`)
82
+
83
+ 1. Read all logs, drop any record id named by a `supersedes` field.
84
+ 2. Order by `(ts, actor, id)` for determinism across machines.
85
+ 3. Baseline per location = its most recent snapshot at or before the query time; a snapshot **resets** that
86
+ location's products rather than merging into them.
87
+ 4. Apply changes after that snapshot; `movement` applies a negative delta at `from_location` and a positive
88
+ one at `to_location`.
89
+
90
+ ## Ownership and versioning
91
+
92
+ - `store.append()` refuses any `stream_id` other than the current user's — with the AAD binding above, that
93
+ is the mechanism, and `docs/LAYOUT.md` documents which paths are read-only by convention.
94
+ - `sumac verify` re-opens every line of every log under its own AAD and reports lines that fail plus records
95
+ whose `actor` disagrees with the owning file.
96
+ - `SCHEMA_VERSION` on every record; readers reject records from a newer schema with an "upgrade sumac" error.
97
+ - `.gitattributes`: `merge=union` on `data/**/*.jsonl` — correct for append-only streams and it keeps
98
+ concurrent pushes from conflicting.
99
+
100
+ ## CLI
101
+
102
+ `init`, `config show|add-location`, `add` (a change), `snapshot`, `status [location]`, `find <product>`,
103
+ `log`, `verify`. Rendering lives in `render.py`.
104
+
105
+ ## Tooling
106
+
107
+ - `pyproject.toml` (uv, `uv.lock` committed): `pynacl`, `pydantic>=2`, `rich`, `typer`; dev group `ruff`,
108
+ `ty`, `pytest`. PyNaCl for XChaCha20-Poly1305 — `cryptography` only ships the 12-byte-nonce variant.
109
+ - `.claude/settings.json`: `PostToolUse` hook on `Edit|Write` running `uv run ruff format` on edited `.py`.
110
+ - `.github/workflows/ci.yml`: `uv sync` → `ruff format --check` → `ruff check` → `ty check` → `pytest`.
111
+ - Docs: `README.md` (usage), `docs/FORMAT.md` (on-disk format + threat model), `docs/LAYOUT.md`
112
+ (read-only vs mutable). Concise; no narration.
113
+
114
+ ## Build order
115
+
116
+ 1. Scaffolding: `pyproject.toml`, ruff/ty config, CI, Claude hook, doc skeletons.
117
+ 2. `models.py` + `schemas.py` + tests.
118
+ 3. `crypto.py`, `passphrase.py`, `paths.py`, `store.py` + tests.
119
+ 4. `config.py` + `ledger.py` + tests.
120
+ 5. `cli.py` + `render.py`, docs filled in.
121
+
122
+ ## Verification
123
+
124
+ Tests: crypto round-trip; wrong passphrase fails at the verifier; a line moved between streams fails to
125
+ open; `append` rejects a foreign `stream_id`; ledger cases (snapshot reset, movement, supersede, unit
126
+ mismatch); config latest-revision-wins; and a leak test walking `data/` asserting every path component is a
127
+ fixed literal or a known username.
128
+
129
+ End-to-end, against a scratch data dir:
130
+
131
+ ```
132
+ SUMAC_PASSPHRASE=test uv run sumac init
133
+ … add-location, add, snapshot, status, verify
134
+ grep -r "pantry\|fridge" data/ # must find nothing
135
+ uv run ruff format --check . && uv run ruff check . && uv run ty check && uv run pytest
136
+ ```
@@ -0,0 +1,48 @@
1
+ Make a Python app with encrypted event store to log the inventory of a home’s groceries in different locations.
2
+
3
+ Make it fully generic, reading the config for the locations from a file that is encrypted.
4
+
5
+ Encryption is with a universal passphrase that can be shared between users of the app. The goal of
6
+ encryption is for the grocery records to be encrypted at rest (the config of the location layout and the content of groceries at those locations).
7
+ The app must not leak these secrets through any files it creates (e.g. by using location names as
8
+ folder names).
9
+
10
+ The form of encryption will be per-line AEAD: each JSONL record is independently encrypted (XChaCha20-Poly1305, random nonce per line, base64'd), so appending a line is a byte-append and git packs it well. This costs you per-line ciphertext overhead and leaks record count and approximate size, but we accept that.
11
+
12
+ The users of the app will pull down the app and update the mutable parts.
13
+
14
+ The app users should expect some parts of the app to be read only and some parts to be mutable, and the app itself should be versioned.
15
+
16
+ Users cannot mutate another user's historical records; they correct or supersede them by appending new records.
17
+
18
+ Note that the read-only-ness is a convention and not achieved by file permissions. The name of the user can be obtained from the OS (`getpass.getuser()` in Python).
19
+
20
+ The datasets should be read-only for the given user, no user should be able to edit someone else’s data.
21
+
22
+ The program should be documented (but concisely, not narrated extensively). Use uv to manage the dependencies and include Claude Code hooks that tell an agent to run ruff format when editing the code.
23
+
24
+ Keep the data model of the objects stored standalone in a module so that it can be easily edited.
25
+
26
+ The product inventory should be able to carry extra metadata (such as might be provided by a grocery seller, or by users) beyond the core data model.
27
+
28
+ Opt for modules dedicated to each of the scopes of functionality, do not overburden any one module. Use Pydantic for validation at the boundaries where data is ingested, and frozen dataclasses elsewhere.
29
+
30
+ Use the rich library for the terminal app.
31
+
32
+ Use ty in a GitHub CI check on the software.
33
+
34
+ Use JSONL for the format of inventory logging, users will write that (users being the people living there).
35
+
36
+ Model inventory logging around two primitives: inventory snapshots and inventory changes.
37
+
38
+ A snapshot records the observed quantities of items at a particular location/sublocation at a specific point in time.
39
+
40
+ A change records a delta or transfer to/from inventory (e.g. purchase, consumption, waste, discovery, correction, or movement between locations).
41
+
42
+ Treat changes as the normal operational input and snapshots as explicit observations/reconciliation points.
43
+
44
+ The current inventory should be derivable from a snapshot plus subsequent changes.
45
+
46
+ Write concisely and avoid ‘slop’.
47
+
48
+ Initially, create a plan, do not go all in
@@ -0,0 +1,40 @@
1
+ [project]
2
+ name = "sumac-home"
3
+ version = "0.1.0"
4
+ description = "Home grocery inventory app"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "sealedlog>=0.1.0",
9
+ "pydantic>=2",
10
+ "rich>=13",
11
+ "typer>=0.12",
12
+ ]
13
+
14
+ [project.scripts]
15
+ sumac = "sumac.cli:main"
16
+
17
+ [build-system]
18
+ requires = ["hatchling"]
19
+ build-backend = "hatchling.build"
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/sumac"]
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "pynacl>=1.5",
27
+ "ruff>=0.6",
28
+ "ty>=0.0.1a1",
29
+ "pytest>=8",
30
+ ]
31
+
32
+ [tool.ruff]
33
+ line-length = 100
34
+ target-version = "py312"
35
+
36
+ [tool.ruff.lint]
37
+ select = ["E", "F", "I", "UP", "B"]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
@@ -0,0 +1,5 @@
1
+ __version__ = "0.1.0"
2
+
3
+ SCHEMA_VERSION = 1
4
+ FORMAT_VERSION = 1
5
+ NAMESPACE = "sumac"