Metadata-Version: 2.5
Name: sumac-home
Version: 0.1.5
Summary: Home grocery inventory app
Requires-Python: >=3.12
Requires-Dist: pydantic>=2
Requires-Dist: rich>=13
Requires-Dist: sealedlog>=0.1.0
Requires-Dist: typer>=0.12
Provides-Extra: ask
Requires-Dist: mistralrs>=0.9.2; extra == 'ask'
Description-Content-Type: text/markdown

# sumac

**🍋 sumac: home grocery inventory app**

Encrypted-at-rest grocery inventory for a household sharing one git repo and one passphrase.
Locations, products, and quantities are never visible to someone holding the repo without the
passphrase — not even in file or directory names. See `docs/FORMAT.md` for the on-disk format
and threat model, and `docs/LAYOUT.md` for what's read-only vs mutable.

## Install

Published on PyPI as `sumac-home` (`sumac` was taken); the command is still `sumac`.

```sh
uv tool install sumac-home     # or: pip install sumac-home
```

For development (this checkout):

```sh
uv sync
```

Depends on [`sealedlog`](https://pypi.org/project/sealedlog/) (the encrypted append-only log
primitive) from PyPI.

## Passphrase

Set `SUMAC_PASSPHRASE`, or sumac will prompt interactively. The passphrase is shared by every
user of the household's vault.

## Usage

```sh
sumac init                                              # once, creates data/
sumac config add-location "Fridge" --id fridge
sumac config add-location "Pantry" --id pantry
sumac config show

sumac add purchase milk 2 l --to fridge
sumac add consumption milk 1 l --from fridge
sumac add movement rice 1 kg --from pantry --to fridge
sumac snapshot fridge "milk=1/l" "eggs=6/ct"            # reconciliation: resets fridge's products

sumac status                                             # current inventory, all locations
sumac status fridge                                      # current inventory, one location
sumac find milk                                           # where is milk right now?
sumac log                                                 # full ordered event log
sumac verify                                              # re-authenticate every line; check actors
```

All commands take `--data-dir` (default `data`, or `$SUMAC_DATA_DIR`).

### Locations nest

A location can have a parent, so shelves, doors, bins, drawers — anything — nest under a
container to arbitrary depth. There's no separate "shelf" or "grid" type; a sub-location is just
another location with `--parent` set.

```sh
sumac config add-location "Door" --id fridge-door --parent fridge
sumac config add-array "Shelf" --parent fridge --count 4       # Shelf 1..4 under fridge
sumac config add-grid "Bin" --parent pantry --rows 3 --cols 4  # Bin R1C1..R3C4 under pantry
sumac config show                                              # renders the tree
```

`sumac status <location>` and `sumac find` both include everything nested under a location, not
just that exact node — `sumac status fridge` sums the fridge itself, its door, and its shelves in
one pass. Query a sub-location directly (e.g. `sumac status fridge-door`) to scope to just that
node and its own descendants.

## Evaluating agent behavior

`sumac ask`'s agent (below) has a behavioural eval suite — a seeded inventory and a set of
find/add/remove/reject scenarios, run against either a real local model or a deployed Modal
endpoint. It's how a prompt or model change actually gets checked, not just tried once by hand.

```sh
uv run pytest evals -v --eval-model qwen3.5-4b
```

See `evals/README.md` for the full guide (comparing models, comparing prompt variants, reading the
output) and `docs/MODAL.md` if you want faster iteration against a deployed Modal endpoint instead
of local inference.

## Development

See `docs/DEVELOPMENT.md`.

## Optional: natural-language input (`sumac ask`)

`sumac ask` parses freeform text ("consume 1 jar of jam") into structured commands using a local
LLM via [`mistralrs`](https://github.com/EricLBuehler/mistral.rs). It's an optional dependency
group, the rest of `sumac` works without it.

```sh
uv sync --group ask          # CPU/Metal — no GPU required
```

### NVIDIA GPU acceleration

```sh
uv sync --no-group ask --group ask-cuda
```

**Known upstream issue:** mistral.rs's published CUDA wheels for `0.9.1`/`0.9.2` have a broken
`RPATH` that prevents the extension from loading at all (tracked upstream:
[EricLBuehler/mistral.rs#2411](https://github.com/EricLBuehler/mistral.rs/issues/2411)). Until
that's fixed upstream, `ask-cuda` uses a wheel built from source with a local patch — see
`scripts/build-mistralrs-cuda.sh`.

To (re)build it:

```sh
./scripts/build-mistralrs-cuda.sh v0.9.2
```

This clones mistral.rs into `.build/` (gitignored), builds with `maturin` against whatever CUDA
compute capability is present on the machine you run it on (confirm with `cuobjdump --list-elf
<extension>.so | grep sm_` if you need to check), and patches the resulting wheel to embed a real
copy of `libcuda.so.1` from the system driver instead of the incorrectly-vendored one the build
produces by default — `uv` doesn't preserve symlinks from wheel zips on install, so it's a full
copy, not a link. The patched wheel lands in `vendor/wheels/` (gitignored — too large for git)
and `pyproject.toml`'s `ask-cuda` group points at it via a local path source.

**This wheel is tied to the host machine, in two separate ways.** The compiled extension is built
for whichever GPU's compute capability was present at build time — it is not portable to a
different GPU architecture. And the embedded `libcuda.so.1` is a byte-for-byte copy of *this
machine's* driver — if you update the NVIDIA driver, reinstall the OS, swap GPUs, or move to a
different machine, rebuild rather than reuse the wheel. Requires the CUDA toolkit and a Rust
toolchain (`rustc >= 1.94`) to build; neither is needed just to *run* `sumac`.

To verify a build run `uv run python -c "import mistralrs"` which should import silently without
erroring.
