Event-sourced CLI inventory tracker: "Where did I put my 10mm socket?"
# Add locations (places are immovable; containers can hold things and be moved)
wherehouse add "Garage" --type place
wherehouse add "Toolbox" --type container
wherehouse add "Garage:Toolbox:10mm socket" --type leaf
# Search by name
wherehouse scry "socket"
# → Garage:Toolbox:10mm socket
# Move something
wherehouse move "Garage:Toolbox" --to "Basement"
# View full event history
wherehouse history "Basement:Toolbox:10mm socket"
# Browse everything in a web UI
wherehouse serveYou know you own a 10mm socket wrench. You used it last week. Where is it now?
Wherehouse tracks every entity's location with a complete audit trail. Event-sourced architecture means you can see where things were, when they moved, and rebuild the entire state from history.
- Event-Sourced — append-only event log; projections are derived and rebuildable
- Unified Entity Model — locations and items are both entities; hierarchy via colon-separated paths (
Garage:Toolbox:Wrench) - Hierarchical Paths — place > container > leaf nesting with colon-path addressing
- Status Tracking — mark entities as
ok,missing,borrowed,loaned, orremoved - Full History — every move, rename, and status change is recorded with actor and timestamp
- Web UI — local HTTP server for browsing, searching, adding, and editing inventory
- Network Storage Ready — SQLite WAL mode works with NFS/SMB mounts
- Multi-User Attribution — trust-based; tracks who made changes, no permissions enforcement
- Single File Database — entire inventory in one portable SQLite file
Requirements: Go 1.25+, SQLite 3.x (embedded via modernc.org/sqlite)
git clone https://github.com/asphaltbuffet/wherehouse.git
cd wherehouse
# Build
go build -o dist/wherehouse .
# Or with mise
mise run build
# Install to user bin
mkdir -p ~/.local/bin
cp dist/wherehouse ~/.local/bin/Standalone install:
nix profile install github:asphaltbuffet/wherehouse/v0.3.0Home Manager — add as an input and load the bundled module:
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
home-manager.url = "github:nix-community/home-manager";
wherehouse.url = "github:asphaltbuffet/wherehouse/v0.3.0";
wherehouse.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, home-manager, wherehouse, ... }: {
homeConfigurations."alice" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = [
wherehouse.homeManagerModules.default
{ programs.wherehouse.enable = true; }
];
};
};
}wherehouse config init
# → Created config at ~/.config/wherehouse/wherehouse.tomlThe database is created automatically on first use at ~/.local/share/wherehouse/wherehouse.db.
Entities have three types:
| Type | Description |
|---|---|
place |
Immovable location (room, building, shelf) |
container |
Movable holder (box, toolbox, bag) |
leaf |
Individual item that holds nothing |
# Build a hierarchy
wherehouse add "Garage" --type place
wherehouse add "Toolbox" --type container
wherehouse add "Garage:Toolbox" --type container # nested path creates under Toolbox
# Add individual items
wherehouse add "Garage:Toolbox:10mm socket" --type leaf
wherehouse add "Garage:Toolbox:Ratchet" --type leafPaths are colon-separated from the root. Providing a nested path like Garage:Toolbox:Wrench will add Wrench under the existing Garage:Toolbox entity.
# Search by name (substring matching)
wherehouse scry "socket"
# → Garage:Toolbox:10mm socket
# List all entities
wherehouse scry
# JSON output for scripting
wherehouse scry "socket" --json# Move a container (and everything in it) to a new parent
wherehouse move "Garage:Toolbox" --to "Basement"
# Move a single item
wherehouse move "Garage:Toolbox:Ratchet" --to "Garage:Pegboard"Only container and leaf entities are movable. place entities cannot be moved.
# Full event timeline for an entity (newest first)
wherehouse history "Basement:Toolbox:10mm socket"
# JSON output
wherehouse history "Basement:Toolbox" --json# Mark something as missing
wherehouse status "Basement:Toolbox:10mm socket" --set missing
# Mark it found again
wherehouse status "Basement:Toolbox:10mm socket" --set ok
# Record a loan with a note
wherehouse status "Garage:Ladder" --set loaned --note "lent to Bob"
# Valid statuses: ok, missing, borrowed, loaned, removed# List everything
wherehouse list
# List under a specific path
wherehouse list --under "Garage:Toolbox"
# Filter by type or status
wherehouse list --type container
wherehouse list --status missingwherehouse rename "Garage:Toolbox" --to "Tool Chest"wherehouse remove "Garage:Tool Chest:Broken Wrench"
wherehouse remove "Garage:Old Box" --note "disposed"# Export all events as NDJSON (one JSON object per line)
wherehouse export
# Suppress the "no events" warning when the database is empty
wherehouse export --quietThe --json flag is accepted silently (the command always emits NDJSON).
# Start local web server (default: http://127.0.0.1:8080)
wherehouse serve
# Custom port or bind address
wherehouse serve --port 9090
wherehouse serve --bind 0.0.0.0 # share on LANOpen http://localhost:8080 in your browser. From the UI you can browse the full entity tree, search, add entities, edit names, and toggle item status.
wherehouse <command> [flags]
Entity Management:
add <path> Add an entity (--type place|container|leaf)
move <path> Move an entity to a new parent (--to <dest>)
rename <path> Rename an entity (--to <new-name>)
remove <path> Remove an entity from the inventory
status <path> Change entity status (--set ok|missing|borrowed|loaned|removed)
list List entities (--under, --type, --status filters)
scry [<name>] Search entities by name, or list all
history <path> Show full event timeline for an entity
export Export all events as NDJSON to stdout
Web UI:
serve Start local web server (--port, --bind)
Configuration:
config init Create config file with defaults (--local, --force)
config check Validate config file(s)
config path Show config file path(s)
Global Flags:
-h, --help Show help
--version Show version
--config <path> Custom config file path
--no-config Skip all config files (use defaults only)
--db <path> Override database path
--as <identity> Override user identity
--json Output as JSON
-q, --quiet Quiet mode (-q minimal, -qq silent)
Config file (in priority order):
--config <path>flag$WHEREHOUSE_CONFIGenvironment variable./wherehouse.toml(current directory)~/.config/wherehouse/wherehouse.toml(default)
Data: ~/.local/share/wherehouse/wherehouse.db
wherehouse config initGenerated ~/.config/wherehouse/wherehouse.toml:
[database]
path = "~/.local/share/wherehouse/wherehouse.db"
[logging]
level = "warn"
# file_path = "~/.local/state/wherehouse/wherehouse.log"
# max_size_mb = 10
# max_backups = 3
[user]
default_identity = ""
os_username_map = {}
[output]
default_format = "human"
quiet = falseexport WHEREHOUSE_DATABASE_PATH="/mnt/nas/wherehouse.db"
export WHEREHOUSE_CONFIG="$HOME/projects/workshop/wherehouse.toml"
export WHEREHOUSE_LOG_PATH="/var/log/wherehouse/wherehouse.log"
export WHEREHOUSE_OUTPUT_DEFAULT_FORMAT="json"programs.wherehouse = {
enable = true;
settings = {
database.path = "~/.local/share/wherehouse/wherehouse.db";
user = {
defaultIdentity = "";
osUsernameMap = { jdoe = "John Doe"; };
};
logging = {
level = "warn";
};
output = {
defaultFormat = "human";
quiet = false;
};
};
};| Option | Type | Default | Description |
|---|---|---|---|
settings.database.path |
string | XDG data dir | Path to SQLite database file |
settings.logging.filePath |
string | XDG state dir | Path to log file |
settings.logging.level |
"debug"…"error" |
"warn" |
Minimum log level |
settings.logging.maxSizeMB |
int | 0 (disabled) |
Max log size before rotation |
settings.logging.maxBackups |
int | 3 |
Old rotated files to keep |
settings.user.defaultIdentity |
string | OS username | Display name for attribution |
settings.user.osUsernameMap |
attrset | {} |
Map OS usernames to display names |
settings.output.defaultFormat |
"human" | "json" |
"human" |
Default output format |
settings.output.quiet |
bool | false |
Suppress non-essential output |
- Events are the source of truth (append-only log, never modified)
- Projections are derived state (rebuildable from events)
- Replay ordered strictly by
event_id— timestamps are informational only - No undo — corrections create new compensating events
Events table (source of truth):
CREATE TABLE events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
timestamp_utc TEXT NOT NULL,
actor_user_id TEXT NOT NULL,
payload TEXT NOT NULL, -- JSON
note TEXT
);Projection tables (derived, rebuildable):
locations_current— current entity hierarchyitems_current— current entity stateprojects_current— active and completed projects
- Go 1.25+
- mise (recommended for task automation)
# Build
mise run build # → dist/wherehouse
# Test (race detector + coverage)
mise run test
# Lint
mise run lint
# Full pipeline
mise run dev # binary lands in dist/<os>_<arch>_<variant>/wherehouseMultiple processes accessing the same database, or a network mount with locking issues.
ps aux | grep wherehouseFor network storage, verify NFSv4/SMB file locking support.
# Check database size
ls -lh ~/.local/share/wherehouse/wherehouse.db