A ZenithProxy plugin that scans, indexes, and provides Discord-queryable access to container inventories in a Minecraft world. Supports PostgreSQL persistence, a built-in REST API with Prometheus metrics, webhook notifications, and full configuration via Discord commands.
We have a Discord server! Feel free to join for up-to-date information: https://discord.gg/8ACQJUtQse
- Region-based container scanning — tick-driven state machine walks to, opens, and reads every container in a defined area
- Container types — chests, barrels, shulker boxes, hoppers, dispensers, droppers
- Shulker introspection — reads nested inventory contents via NBT
- Double chest deduplication — large chests are merged into a single entry
- Return-to-start — bot pathfinds back to its original position after a scan completes
- Container labels — assign custom names to containers for easy identification
- Saved regions — name and persist scan regions in the database for reuse
- Stash organizer — automated item sorting across containers by type with column detection, shulker packing, and overflow handling
- PostgreSQL persistence — all scanned containers stored in a database for long-term querying
- REST API — embedded HTTP server with JSON endpoints and Prometheus-format metrics
- Webhook notifications — POST JSON payloads to external services (n8n, etc.) on scan completion
- Safe staged updates — checks GitHub releases, optionally downloads the next plugin JAR, and loads it on the next restart
- Full Discord configuration — every setting is viewable and changeable via Discord commands
- CSV export — export the full index as a CSV file attachment in Discord
- Build the plugin JAR (or download from Releases)
- Place
stash-manager-1.1.0.jarin ZenithProxy'splugins/directory - Restart ZenithProxy
Requires Java 21+.
./gradlew buildThe output JAR will be in build/libs/.
GitHub Actions now enforces a few supply-chain integrity checks during CI and release builds.
- Pull requests run dependency review and may fail if a new dependency introduces a moderate-or-higher known vulnerability.
- Build jobs validate the Gradle wrapper, generate SHA-256 checksums for release JARs, and create GitHub artifact provenance attestations.
- Tag builds verify that the pushed tag matches
plugin_versioningradle.properties. For example,v2.0.0must matchplugin_version=2.0.0. - Artifact attestations only work on public repositories for GitHub Free/Pro/Team plans. If you copy this workflow to a private repo, the attestation step may need to be removed or gated unless you are on a premium GitHub tier.
All commands work via Discord, terminal, and in-game chat.
| Command | Description |
|---|---|
stash pos1 [x y z] |
Set scan region corner 1 (defaults to player position) |
stash pos2 [x y z] |
Set scan region corner 2 (defaults to player position) |
stash scan |
Start scanning containers in the defined region |
stash stop |
Abort an in-progress scan |
stash update |
Check GitHub releases and stage the latest JAR for the next restart |
stash update check |
Check whether a newer release exists without downloading it |
stash status |
Show scan state, region, container counts, DB/API status |
| Command | Description |
|---|---|
stash list [page] |
Paginated list of indexed containers |
stash export |
Export index to CSV (file attachment in Discord) |
stash clear |
Clear the in-memory index (region positions retained) |
stash clearall |
Clear both memory index and database |
stash summary |
Detailed index summary with item type breakdown |
stash label <x> <y> <z> <label> |
Assign a label to a container |
stash labels |
List all labeled containers |
stashsearch <item> |
Search for containers holding matching items |
| Command | Description |
|---|---|
stash db status |
Show database connection info and row counts |
stash db clear |
Delete all data from the database |
| Command | Description |
|---|---|
stash region save <name> |
Save the current pos1/pos2 as a named region |
stash region load <name> |
Load a saved region into pos1/pos2 |
stash region list |
List all saved regions |
stash region delete <name> |
Delete a saved region |
These commands use the indexed container data stored in PostgreSQL, so the database must be enabled and connected first.
| Command | Description |
|---|---|
stash kit list |
List all saved kits |
stash kit show <name> |
Show the saved contents of a kit |
stash kit snapshot <name> |
Save the player's current main inventory as a kit |
stash kit add <name> <item_id> <count> |
Add or replace one item entry in a kit |
stash kit remove <name> <item_id> |
Remove one item entry from a kit |
stash kit delete <name> |
Delete a saved kit |
stash get <item_id> [count] |
Start retrieving one item from indexed containers |
stash get kit <name> |
Start retrieving every item listed in a saved kit |
stash get status |
Show retrieval progress and remaining items |
stash get stop |
Stop the active retrieval task |
| Command | Description |
|---|---|
stash organize |
Start sorting items across containers by type |
stash organize stop |
Stop the organizer mid-run |
stash organize status |
Show organizer state and progress |
| Command | Description |
|---|---|
stashsupply add |
Mark the nearest container as a supply chest |
stashsupply remove <id> |
Remove a supply chest by index |
stashsupply list |
List all registered supply chests |
All settings can be viewed and changed at runtime via Discord. Changes are saved automatically.
| Command | Description |
|---|---|
stash config |
Show all current configuration values |
| Scanner | |
stash config scanDelay <ticks> |
Ticks between container reads (1–200) |
stash config openTimeout <ticks> |
Max wait ticks for container open response (1–600) |
stash config maxContainers <count> |
Container cap per scan session (1–100000) |
stash config waypointDistance <blocks> |
Walk distance for unloaded chunks (1–256) |
stash config returnToStart <on|off> |
Return bot to start position after scan |
| Database | |
stash config db enable |
Enable PostgreSQL persistence |
stash config db disable |
Disable database and disconnect |
stash config db url <jdbc-url> |
Set JDBC connection URL |
stash config db user <username> |
Set database username |
stash config db password <password> |
Set database password |
stash config db poolSize <size> |
Connection pool size (1–20) |
stash config db connect |
Connect (or reconnect) to the database |
| API | |
stash config api enable |
Enable the REST API |
stash config api disable |
Disable API and stop the server |
stash config api port <port> |
Set API listen port (1–65535) |
stash config api bind <address> |
Set API bind address |
stash config api key <key> |
Set Bearer token for API authentication |
stash config api threads <count> |
Set HTTP thread pool size (1–16) |
stash config api start |
Start the API server |
stash config api stop |
Stop the API server |
| Webhook | |
stash config webhook <url> |
Set webhook URL (use off to clear) |
| Updates | |
stash config updates |
Show updater settings and the last check result |
stash config updates checkOnLoad <on|off> |
Enable/disable startup update checks |
stash config updates autoDownload <on|off> |
Automatically stage new releases during startup checks |
Saved automatically via ZenithProxy's plugin config system.
| Setting | Default | Description |
|---|---|---|
enabled |
true |
Enable/disable the module |
scanDelayTicks |
5 |
Ticks between container reads |
openTimeoutTicks |
60 |
Max wait for container open response |
maxContainers |
2048 |
Container cap per scan session |
waypointDistance |
48 |
Walk distance for unloaded chunks |
returnToStart |
true |
Pathfind back to starting position after scan |
| Setting | Default | Description |
|---|---|---|
organizerEnabled |
true |
Enable/disable the stash organizer |
organizerClickCooldownTicks |
3 |
Ticks between inventory slot clicks |
organizerOpenTimeoutTicks |
60 |
Max wait ticks for container open |
condenseMinItems |
1 |
Minimum loose items to justify shulker packing |
| Setting | Default | Description |
|---|---|---|
databaseEnabled |
false |
Enable database persistence |
databaseUrl |
jdbc:postgresql://localhost:5432/stashmanager |
JDBC connection URL |
databaseUser |
stashmanager |
Database username |
databasePassword |
(empty) | Database password |
databasePoolSize |
3 |
HikariCP connection pool size |
| Setting | Default | Description |
|---|---|---|
apiEnabled |
false |
Enable the embedded HTTP API |
apiBindAddress |
0.0.0.0 |
Listen address |
apiPort |
8585 |
Listen port |
apiThreads |
2 |
HTTP handler thread pool size |
apiKey |
(empty) | Bearer token for authentication (empty = no auth) |
| Setting | Default | Description |
|---|---|---|
webhookUrl |
(empty) | URL to POST scan-completion payloads to |
| Setting | Default | Description |
|---|---|---|
updateCheckOnLoad |
true |
Check GitHub for a newer plugin release during startup |
updateAutoDownload |
false |
Download and stage a newer plugin JAR automatically during startup checks |
The database lets your scanned containers persist across restarts so you can search them anytime via Discord — no external tools required.
Windows:
- Download the installer from postgresql.org/download/windows
- Run the installer. Use the defaults — just set a password for the
postgressuperuser when prompted (remember this password) - The installer includes pgAdmin (a GUI) and adds PostgreSQL as a Windows service that starts automatically
Linux (Debian/Ubuntu):
sudo apt update && sudo apt install postgresql
sudo systemctl enable --now postgresqlmacOS (Homebrew):
brew install postgresql@16
brew services start postgresql@16Open a terminal (or SQL Shell (psql) on Windows, found in your Start menu after installing PostgreSQL).
Connect as the superuser:
# Linux / macOS
sudo -u postgres psql
# Windows (SQL Shell will prompt you — press Enter for defaults, then enter
# the superuser password you set during install)Then run:
CREATE USER stashmanager WITH PASSWORD 'pick_a_password';
CREATE DATABASE stashmanager OWNER stashmanager;
\qThat's it for the database side — the plugin creates all tables automatically.
Run these commands in Discord (or terminal / in-game chat):
stash config db url jdbc:postgresql://localhost:5432/stashmanager
stash config db user stashmanager
stash config db password pick_a_password
stash config db enable
stash config db connect
You should see a "Database Connected" confirmation. From this point on, every scan saves its results to the database and all stash list, stash export, and stashsearch commands query from it automatically.
stash db status
This shows the connection state and how many containers/items are stored.
- Persistence — container data survives plugin/proxy restarts
- Faster searches —
stashsearchqueries the database instead of scanning memory - History —
scan_historytable tracks every scan run with timestamps and counts - Bulk export —
stash exportpulls from the database for complete CSV dumps
| Table | Contents |
|---|---|
containers |
Position, type, dimension, item count, first/last seen timestamps, label |
container_items |
Slot, item ID, display name, count per container |
scan_history |
Start/end time, container count, status per scan run |
regions |
Named scan regions with pos1/pos2 coordinates |
config |
Key-value plugin configuration pairs |
storage_chests |
Registered supply chest positions |
keep_items |
Items the organizer should leave in place |
When enabled, the API server exposes the following endpoints. All endpoints require a Authorization: Bearer <apiKey> header if an API key is configured.
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/status |
Scanner status (state, region, counts, uptime) |
GET |
/api/v1/containers?page=1&size=50 |
Paginated container list |
GET |
/api/v1/search?item=diamond |
Search containers by item name |
GET |
/api/v1/stats |
Aggregate statistics (totals, types, top items) |
GET |
/api/v1/metrics |
Prometheus-format metrics |
GET |
/api/v1/organizer |
Organizer state and task progress |
GET |
/api/v1/regions |
Saved region list |
POST |
/api/v1/webhook/test |
Send a test webhook payload |
curl -H "Authorization: Bearer mykey" http://localhost:8585/api/v1/statsThe /api/v1/metrics endpoint returns metrics in Prometheus exposition format:
stashmanager_containers_total 1234
stashmanager_items_total 56789
stashmanager_scan_state 0
stashmanager_database_connected 1
stashmanager_api_uptime_seconds 3600
stash_organizer_active 0
stash_organizer_tasks_completed 0
stash_organizer_tasks_total 0
Add this as a Prometheus scrape target and build Grafana dashboards from the stashmanager_* metrics.
Set a webhook URL and the plugin will POST a JSON payload when each scan completes:
stash config webhook https://your-n8n-instance.example.com/webhook/stash
Payload format:
{
"event": "scan_complete",
"containersScanned": 150,
"timestamp": "2025-01-15T12:00:00Z"
}