- Go 82.2%
- templ 17.2%
- CSS 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .builds | ||
| cmd/hima | ||
| docs/design | ||
| internal | ||
| pkg | ||
| scripts | ||
| .air.toml | ||
| .gitattributes | ||
| .gitignore | ||
| .golangci.toml | ||
| .tokeignore | ||
| CLAUDE.md | ||
| components.json | ||
| CONTRIBUTING.md | ||
| go.mod | ||
| go.sum | ||
| go.tool.mod | ||
| go.tool.sum | ||
| hima.example.toml | ||
| hk.pkl | ||
| LICENSE | ||
| mise.toml | ||
| README.md | ||
Hima
A CLI for your home inventory: what you own, what you paid, and the receipts that prove it. Hima means "home" in a Norwegian dialect.
It is one binary and one SQLite database. Your receipts stay the files they already are, in a directory you point hima at; the database holds what they mean. Nothing is uploaded anywhere, and nothing needs a server.
hima inbox # what is waiting
hima inbox triage # work through it, in $EDITOR
hima items triage --gaps value
hima items list in:loftet tag:verktøy
hima export items --format pdf -o inventory.pdf
Pre-1.0. The command surface is settling but not settled, and the schema still changes between tags. Migrations run on open and are meant to be safe; back up before upgrading anyway.
Installing
go install src.timharek.no/tim/hima/cmd/hima@latest
Release tarballs, with checksums, are attached to each tag on the repository.
Configuring
hima runs with no config file at all — one home, a database under
$XDG_CONFIG_HOME/hima, receipts in ~/Documents/receipts. That is enough to
try it and not enough to live in, so start from the annotated example:
curl -O https://src.timharek.no/tim/hima/raw/branch/main/hima.example.toml
mv hima.example.toml hima.toml # then edit it
hima.toml is looked for in the working directory, then in
$XDG_CONFIG_HOME/hima (falling back to ~/.config/hima). --config names
one explicitly, and HIMA_* environment variables override anything in it.
hima config prints what was actually loaded, from wherever it came. It is
the command that still works when the database does not, which is what you
want when a path looks wrong.
Homes
A home is which property — the house, the cabin — and every item, tag and
store is scoped to one. Homes come from the config: hima.toml decides
which exist, hima only keeps rows so the other tables have a stable key.
[[homes]]
name = "Default"
short = "d"
address = "Parkveien 1, 5001 Bergen"
Matching is by short, so renaming a home keeps everything filed under it. A
home dropped from the config is never deleted — its data waits for the entry
to come back. --home hytta picks which one a command works on; without it,
default_home decides.
The day-to-day
Receipts
Drop files into inbox/ under your receipts_path — PDFs, photographs,
whatever the shop gave you. hima takes the formats a household actually files
as proof: PDF, images (including the .heic an iPhone writes and the multipage
TIFF a scanner writes), plain text and RTF, Word and LibreOffice documents,
spreadsheets and CSV, and saved emails. Anything else is left exactly where
it is and named in the scan output rather than imported — an archive is only
readable if it holds evidence and not the notes you keep beside it.
If your scanner writes something else, add it:
extra_document_types = ["webarchive", ".tif"]
It adds and cannot subtract. A household that could switch PDFs off is one typo away from an inbox that silently imports nothing.
hima inbox # scan, then say what is waiting
hima inbox triage --view # open them in your viewer, file them in $EDITOR
hima inbox triage opens one buffer with every waiting document in it,
prefilled with what hima could work out: the date and shop from the filename,
and the kind and spelling from what you decided about the last receipt named
like it. Fix what is wrong, save once, and all of it is applied together. A
buffer saved byte-identical is an abort, so quitting out of one you did not
mean to open costs nothing.
hima inbox on its own says what is waiting, oldest first and cut at the same
--limit the buffer uses, so it is the pile triage is about to offer.
Filing a receipt moves the file out of the inbox, keeping the name it arrived under. A file that is later renamed or moved is recognised by its contents and followed rather than imported twice.
--needs-items, on either command, is the other pile: filed purchases with
nothing entered from them yet.
Items
hima items add # $EDITOR on a TOML description
hima items add -t "Drill" --value 1499 --tag verktøy --in loftet
hima items add -t "Keycaps" --value 89USD --purchased-at 2025-11-14
hima items 42 # one item in full
hima items edit 42
Any flag skips the editor, and --editor has them prefill the buffer instead.
A value can carry its own currency — what was paid and what it was paid in is
one thing, said one way.
Items are never deleted. hima items deactivate 42 marks one gone; something
you no longer have was still bought, is still on a receipt, and may still be
under warranty.
Searching
The same query language works in hima items, hima items list,
hima items triage and hima export items. Everything in a query has to
match; bare words match a title or any prop.
tag:verktøy a plain tag
prosjekt:kjøkken a tag in a namespace — any namespace works, and one
farge:blå exists as soon as something is filed under it
in:loftet where it is: a part of the house
owner:Tim who it belongs to
store:Komplett where it was bought
status:sold active, inactive, broken, lost, sold
Values match on part of a name, so in:kon finds Kontoret. Terms cannot
contain spaces.
Triage, and gaps that stay gaps
hima items triage --gaps value
hima items triage offers the items missing something — a value, a date, a
store, somewhere they are, tags, an owner, a receipt — with what is missing
written above each block. --gaps works one hole at a time, which is how a
large backlog actually gets finished: an afternoon of pricing rather than an
afternoon of everything.
An item nothing more can be said about — a present with no receipt, a
hand-me-down nobody priced — gets leave in the buffer and stops being
offered. That answers the question; it does not fill the gap. The item
still counts as incomplete, and filling it in later clears nothing. A backlog
that can never reach zero is a backlog nobody works through.
The house
A home and a house are the same building seen from two sides: the home is which property, and the house is what that property is made of — rooms, windows, the roof, circuits.
hima house add # a buffer of [[part]] blocks
hima house # the tree, under the home it belongs to
hima house due # what is old enough to worry about
Parts have an age, a condition and an expected life, which is what makes
hima house due possible — a question receipts and tags cannot answer.
Lifespans per kind are in the config, seeded from the ranges the Norwegian
building trade works with; override the ones you disagree with.
Where a thing is is a part of the house, not a tag, and it is single-valued:
"where is my drill" has one answer. An --in that names no part is refused
rather than created — a typo must not add a wing to the house.
Events
Something that happened to the house: a fence built, a bathroom refurbished, a leak found.
hima events add "Nytt gjerde" --kind improvement --on 2026-05-02
hima events # the timeline, newest first
hima events edit nytt-gjerde
An event is a folder under events_path with an index.md in it —
frontmatter for what it was and when, markdown for what happened, and the
photographs beside it. It names the parts of the house it touched and the
receipts it cost, and its cost is the sum of those receipts rather than a
number typed twice.
Exporting
hima export items --format pdf -o inventory.pdf
hima export items --format csv in:loftet
hima export receipts --format zip --kind expense -o 2025.zip
export items is what you own — as a PDF it is the document an insurer asks
for, grouped, valued, totalled and naming the receipt behind each line. As
CSV it is a row per item for a spreadsheet. export receipts is what you
spent, and --format zip bundles the documents themselves alongside CSV rows
for them.
Both take the same filters as the command they are named after, and both say on stderr what they left out — an export that quietly covers less than it lists is the wrong answer nobody checks.
Money
Amounts are exact decimals, not floats: sums do not drift, and a total that disagrees with a spreadsheet by a few øre costs an afternoon to explain.
A purchase made in another currency keeps what was paid, in the currency it was paid in, plus what that was worth at home on the day it was bought and the rate that got from one to the other. Rates come from Norges Bank by default, or the ECB via Frankfurter, or nowhere at all:
[exchange]
provider = "norges-bank" # or "frankfurter", or "none"
An amount hima could not convert is stored, shown, left out of every total, and reported. A rate that could not be fetched must not stop a triage session, and a foreign number added into a column of kroner is worse than a gap. A rate you type yourself is recorded as yours, so a figure somebody reasoned about never looks like a fetched one.
What hima will not do to your data
- A receipt whose file has gone is flagged and reported, never removed. An unmounted disk must not destroy rows.
- Duplicates and unreadable files are reported, not resolved.
- Deleting a receipt detaches its items, never deletes them. A thing you
own outlives its paperwork.
--purgeis the one command that deletes a file, and only when you ask for it. - Nothing is deleted to tidy up.
Backups
Two things hold everything: the SQLite database (database_path) and the
directories it describes (receipts_path, events_path).
Never copy the .db file while hima is writing to it. Take a snapshot with
SQLite instead:
sqlite3 /path/to/hima.db "VACUUM INTO '/backups/hima-$(date +%F).db'"
The receipts and events directories are plain files — back them up with whatever you already use. The database must not live in a synced directory: Syncthing and friends corrupt SQLite. The documents themselves sync fine, and sync-conflict files are skipped and named rather than indexed.
To restore, point database_path and the directories at the restored copies
and run hima receipts and hima events.
Design notes
docs/design/ holds what has been thought through: what was decided, and as
importantly what was rejected and why. remote.md is
where the server, the webapp and the phone go.
The web-era version of hima — templ, Tailwind, a server — lives on the web
branch. This is the rewrite that came after it.
Contributing
Anyone can contribute. See the contribution guidelines.
Open pull requests, report bugs on the issue tracker.
Licensed under the GPL-3.0. See LICENSE.