Skip to content

Repository files navigation

📦 Stash Manager

Give a ZenithProxy bot a stash and let it scan, search, organize, and report what it finds.

Build Downloads License

Join the Discord

Quick startCommandsDatabase setupAPI

Stash Manager is a ZenithProxy plugin for people who want a bot to understand a Minecraft storage system instead of treating it like a pile of random chests. It remembers scanned inventories, finds items from Discord, plans dedicated storage lanes, and produces a shareable workbook that tells you what needs to be built.

✨ What it can do

Capability What you get
🔎 Scan a stash The bot walks the region, opens supported containers, and records their contents.
📦 Understand shulkers Nested items, empty boxes, bulk boxes, and mixed kits are kept distinct.
🧭 Plan storage lanes See how many lanes and double chests each exact item type needs.
🗂️ Organize safely Known bulk shulkers are sorted first, then loose items are packed into matching boxes.
🔍 Find and retrieve items Search the index or ask the bot to collect an item or saved kit.
💾 Remember everything PostgreSQL keeps scans, labels, regions, assignments, and kits across restarts.
📊 Share useful reports Download a styled XLSX lane plan without exposing stash coordinates.
🤖 Connect your stack Use the JSON API, Prometheus metrics, Grafana, or webhook events.

Important

Run stash scan before stash organize. The organizer uses the latest scan to protect mixed shulkers and size every lane. If permanent space is short, registered import chests can hold newly reconciled bulk shulkers until you expand the stash.

🚀 Quick start

  1. Install the JAR that matches your ZenithProxy target.
  2. Stand at one corner of the stash and run stash pos1.
  3. Stand at the opposite corner and run stash pos2.
  4. Run stash scan, then check the layout with stash lanes.
  5. Run stash organize. A Good to go report means everything has permanent space; otherwise, make sure import chests are registered for temporary staging.
stash pos1
stash pos2
stash scan
stash lanes
stash organize

Use stash lanes export whenever you want the full Overview, What to Build, and All Lanes workbook.

📥 Installation

  1. Build the plugin JAR (or download from Releases)
  2. Choose the JAR whose +<target> suffix exactly matches your ZenithProxy Minecraft target and place it in ZenithProxy's plugins/ directory
  3. Restart ZenithProxy

Supported targets are 1.21.4, 1.21.8, 1.21.11, 26.1.2 (the 26.1 family), and 26.2.0 (the 26.2 family).

🛠️ Building from source

Requires Java 25. Builds for older targets still emit Java 21 bytecode.

./gradlew test collectVersionJars

Stonecutter builds all five targets and collects the JARs in build/libs/. Each filename includes its Minecraft target. To build one target, run a task such as ./gradlew :1.21.4:shadowJar.

CI Notes

GitHub Actions protects release builds with dependency review, Gradle wrapper validation, SHA-256 checksums, and artifact provenance. Release tags must match plugin_version in gradle.properties. Artifact attestations require a public repository unless the organization uses a qualifying GitHub plan.

🎮 Commands

All commands work through Discord, the terminal, and in game chat.

🔎 Scanning

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 Stop the active 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

📚 Index and search

Command Description
stash list [page] Paginated list of indexed containers
stash export Export index to CSV (file attachment in Discord)
stash clear Clear the memory index while keeping region positions
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

💾 Database

Command Description
stash db status Show database connection info and row counts
stash db clear Delete all data from the database

🗺️ Saved regions

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

🎒 Kits and retrieval

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

🧭 Organizer

Command Description
stash organize Start sorting items across containers by type
stash organize stop Stop the organizer during a run
stash organize resume Arm a saved restart checkpoint for its normal cooldown and resume checks
stash organize discard confirm Permanently discard a saved plan and queue without moving game items
stash organize status Show organizer state and progress
stash lanes Show lane count, per item lane sizes, and required double chest construction
stash lanes export Download the styled, coordinate free lane planning workbook as XLSX
stash import Assign the chest currently being faced as an organizer intake chest
stash import remove Remove the intake role from the chest currently being faced
stash import list List persisted intake chest block positions
stash import purge Preview removal of every persisted import assignment
stash import purge confirm Remove every import assignment without altering chest contents

How it stays safe

Step What happens
1 The scan identifies empty, bulk, mixed, and unknown shulkers.
2 Every exact item gets its own storage class. Blocks, stairs, slabs, trapdoors, armor, tools, and weapons are never folded together. Fortune and Silk Touch tools remain separate.
3 Mixed shulkers and returned kits are opened one at a time and their loose cargo is staged through registered import chests. The bot never guesses from a majority item.
4 Staged cargo is packed into exact-item bulk shulkers, filling a matching partial box before using an empty one.
5 Each completed bulk shulker goes to its assigned lane. When no suitable lane exists, it waits safely in an import chest instead.
6 Partial matching shulkers are filled before empty ones are used. Each mixed box is fully cleared and its empty box is reused before the next mixed box starts.

The capacity check uses each item's real stack size and the free room inside matching shulkers. stash lanes turns that into plain lane and double chest counts. stash lanes export gives you a styled workbook with a summary, a build list, and the full lane breakdown.

Supported regions may mix hopper-fed staircase lanes with direct-access chest banks. Hopper lanes are followed through the inventories they actually feed, so narrow and wide stair steps remain separate from neighboring chains. In a stacked bank, each contiguous vertical stack is one lane. Physical inventories are claimed once, and duplicate scan evidence cannot create a second lane or assign the same storage class twice. Unstructured standalone chests are still left alone unless they are explicitly registered as imports.

Caution

After upgrading from an older version, run a fresh stash scan before organizing. Current scans record both physical shulker instances and the X/Z footprint of every double chest. Older rows stay blocked instead of guessing in dense storage banks.

Import chests

Normal standalone chests are left alone. Face a chest and run stash import when you want the organizer to drain it into the storage lanes. When permanent lane space is short, an import chest may also hold newly reconciled bulk shulkers as temporary staging. It never becomes a permanent lane, and a later scan and organize run can move those boxes once suitable lanes exist. Facing either half of a double chest assigns or removes the whole chest.

Mixed-shulker reconciliation also needs at least one registered import chest. It uses imports as a small, reusable transfer buffer: unload one mixed box, repack those exact items, then continue. The organizer moves those stacks into known-empty inventory slots, so matching keep-list gear is not accidentally swept into the returned kit. Follow-up work remembers which item type went into which import chest, including partial deposits split across several chests. It no longer pairs every item type with every staging chest. If a connection drops during a deposit, only that attempted item/chest pair needs a cautious recheck. Older checkpoints remain usable; already queued work is retained, and an older mixed box in progress uses its conservative fallback until that box is finished.

Import capacity is checked from the live container window for the exact cargo being moved. Recent checks help the bot go straight to a chest with usable space, including compatible partial stacks. Those observations expire after five minutes and refresh whenever the organizer visits that chest. Known full chests are checked last, and still get a live recheck before a capacity stop. Routine capacity misses are grouped into console/debug summaries.

Packing searches try matching partial boxes first, then known empty boxes. Within each group, recent observations come first, followed by nearer chests. Both halves of a known double chest count as one visit. Recent failed searches are checked last for five minutes, unless the indexed box stock changes; uncertain and apparently full sources remain available as a final live fallback.

Packing boxes are checked for actual stack space before pickup. A box that cannot accept the cargo is skipped so the bot can use another matching box or an empty one. Completed boxes go directly to their assigned lane, including a box that fills up while more loose cargo remains aboard. The bot tries other chests in that same lane if the intake is full, then uses imports if the entire lane is full. No extra lane or follow-up move task is created for a successful direct delivery.

The completion message calls out how many boxes and item types are waiting in imports. If no import can accept the current cargo, the organizer recovers the active reconciliation shulker into the bot inventory and saves the remaining queue. Free compatible space if needed, then run stash organize resume; a fresh scan is not required while that checkpoint remains valid. An unreachable destination still stops safely and reports whether its checkpoint can be resumed.

Pausing for other work

Scans and organizer runs yield when another Zenith automation request needs Baritone or the inventory manager. The job keeps a checkpoint, waits through the configured five minute cooldown, then rebuilds its live container or reconciliation state before continuing.

Running stash stop during that cooldown cancels the scan checkpoint immediately. It does not wait for the old gate or start a delayed return trip, so a stopped scan no longer remains YIELDED.

Organizer plans are also journaled to disk. A proxy restart or plugin redeploy reloads the saved queue, waits through the same cooldown and quiet checks, verifies the dimension, then reconstructs the live inventory or shulker step. stash organize status shows whether restart recovery is waiting. Use stash organize discard confirm only when you intentionally want to abandon that saved job.

An upstream disconnect now pauses scans and organizer runs too. Auto reconnect and a later manual reconnect use the same checkpoint: the job stays frozen through failed login attempts, waits until Zenith reports the bot fully online, then finishes the cooldown and quiet checks before resuming. Scanner checkpoints survive reconnects in the same proxy process; organizer checkpoints also survive a full proxy restart.

Routine progress, item actions, retries, and recoveries stay in console and stash debug. Discord receives the job start, actionable blockers, and one completion or terminal failure message. Progress records include completed and remaining tasks, mixed boxes decomposed, and the loose-item tasks generated by reconciliation, so a growing queue does not hide what phase the organizer is in.

When someone connects as the controlling proxy client, the active job pauses immediately and sends a warning in game and on Discord. Use /swap to move into spectator mode within ten minutes. If control is still active when that grace period ends, the saved job checkpoint is discarded; container moves already completed in the world are not rolled back.

stash debug recent records successful job starts and progress as well as failures. Handoffs add organize_preempted, organize_resumed, proxy_control_grace_started, proxy_control_released, or proxy_control_grace_expired. Connection recovery adds connection_outage_started, connection_attempt_started, connection_login_failed, connection_recovery_armed, and the normal job resume event, including the saved state and task counts needed to trace a resume.

Keeping lane assignments stable

Enable PostgreSQL if you want an item to keep the same lane across future runs. Without the database, assignments last only for the current proxy session.

API fields for lane planning

The organizer endpoint exposes the same report under lane_capacity, lane_storage, and lane_construction. Live organizer fields also show whether import staging is active, how many boxes were staged, and how many permanent lane gaps remain. Coordinates are excluded from the shareable construction report.

📤 Supply chests

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

⚙️ Live configuration

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 to 200)
stash config openTimeout <ticks> Max wait ticks for container open response (1 to 600)
stash config maxContainers <count> Container cap per scan session (1 to 100000)
stash config preemptionCooldown <seconds> Minimum scan/organize pause after another automation task takes control (1 to 3600)
stash config controlGrace <seconds> Time a controlling proxy client has to use /swap before the job is aborted (60 to 3600)
stash config waypointDistance <blocks> Walk distance for unloaded chunks (1 to 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 to 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 to 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 to 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

⚙️ Configuration reference

Saved automatically via ZenithProxy's plugin config system.

🔎 Scanner

Setting Default Description
enabled true Enable/disable the module
scanDelayTicks 5 Ticks between container reads
openTimeoutTicks 400 Max wait for container open response
maxContainers 2048 Container cap per scan session
waypointDistance 48 Walk distance for unloaded chunks
scanPreemptionCooldownSeconds 300 Minimum scan/organize pause after yielding to another automation task
proxyControlGraceSeconds 600 Grace period for a controlling client to switch to spectator
returnToStart true Pathfind back to starting position after scan

🧭 Organizer

Setting Default Description
organizerEnabled true Enable/disable the stash organizer
organizerClickCooldownTicks 6 Ticks between inventory slot clicks
organizerOpenTimeoutTicks 60 Max wait ticks for container open
organizerWalkTimeoutTicks 1200 Max walk time for dense storage layouts
condenseMinItems 1 Minimum loose items to justify shulker packing

💾 Database

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

🌐 API server

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)

🔔 Webhook

Setting Default Description
webhookUrl (empty) URL that receives completed scan payloads

🔄 Plugin updates

The updater reads ZenithProxy's native Minecraft codec version at runtime, then stages only a release asset whose +<target>.jar suffix and plugin metadata match that target. If the installed plugin targets another version, a compatible JAR with the same release version can replace it on restart.

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

🗄️ Database setup

The database keeps scanned containers available across restarts, so you can search them from Discord whenever you need them. No separate database tools are required after setup.

1. Install PostgreSQL

Windows:

  1. Download the installer from postgresql.org/download/windows
  2. Run the installer. Keep the defaults and set a password for the postgres superuser when prompted.
  3. 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 postgresql

macOS (Homebrew):

brew install postgresql@16
brew services start postgresql@16

2. Create the database

Open 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;
\q

That is all you need on the database side. The plugin creates its tables automatically.

3. Connect the plugin

Run these commands in Discord, the terminal, or 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.

✅ Check the connection

stash db status

This shows the connection state and how many containers/items are stored.

What the database gives you

Benefit What it means
💾 Persistence Container data survives plugin and proxy restarts.
🔍 Faster searches stashsearch queries stored data instead of scanning memory.
🕒 History scan_history records every run with timestamps and counts.
📄 Complete exports stash export can pull the full index from PostgreSQL.
🧭 Stable lane assignments Each exact bulk item keeps the same dedicated lane on future organization runs.
Database tables created automatically

The plugin owns and updates these tables. You do not need to create them by hand.

Table Contents
containers Position, type, dimension, item count, first/last seen timestamps, label
container_items Slot, item ID, display name, count per container
container_shulkers Physical shulker slot and color per container, including empty boxes
scan_history Start/end time, container count, status per scan run
regions Named scan regions with pos1/pos2 coordinates
config Plugin configuration keys and values
storage_chests Registered supply chest positions
keep_items Items the organizer should leave in place
column_assignments Item type -> assigned organize column (top chest position), kept stable across runs

🌐 REST API

When enabled, the API server exposes the following endpoints. All endpoints require a Authorization: Bearer <apiKey> header if an API key is configured.

Endpoints

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 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

An organizer that stops with a problem reports state: "FAILED" and failed: true, not DONE. The organizer response includes last_failure_reason, last_failure_state, and last_failure_timestamp (Unix milliseconds). Those details stay with a saved checkpoint; after resuming, they describe the previous failure, not the current job state.

While organizing, the bot refreshes the contents of indexed storage containers it opens. Both indexed halves of a double chest stay in sync, and temporary packing shulkers are not added to the stash index. These are snapshots of visited containers, not a full rescan: hoppers can move items afterward. Run stash scan after organizing for a complete audit.

For database health, check database_last_write_attempt and database_last_write_success in /api/v1/status, alongside database_write_healthy. A zero timestamp means no write has been attempted or completed since startup. last_inventory_observation records the latest organizer observation, even if its database write failed. These timestamps use Unix milliseconds.

Transfer confirmations, lane handoffs, recoveries, and failures are also written to the console and log files. Failed transfers include the clicked slot, request acceptance, and exact-stack counts before and after the attempted move. Routine progress stays out of Discord. Taking items requires a matching inventory increase; depositing requires a matching decrease across the bot's inventory. A hopper draining the chest won't leave a phantom undelivered item. Transfer events include the requested amount, confirmed amount, and observed destination gain. Cargo-related aborts also include the acquired, deposited, and remaining task counts.

Example request

curl -H "Authorization: Bearer mykey" http://localhost:8585/api/v1/stats

📊 Prometheus and Grafana

The /api/v1/metrics endpoint returns metrics in Prometheus exposition format:

stash_containers_total 1234
stash_items_total 56789
stash_scanner_state 0
stash_database_connected 1
stash_organizer_active 0
stash_organizer_failed 0
stash_organizer_last_failure_timestamp_seconds 0
stash_database_last_write_attempt_timestamp_seconds 0
stash_database_last_write_success_timestamp_seconds 0
stash_inventory_last_observation_timestamp_seconds 0
stash_organizer_tasks_completed 0
stash_organizer_tasks_total 0
stash_organizer_preemptions_total 0
stash_organizer_preemption_cooldown_remaining_seconds 0
stash_organizer_staged_shulkers 0
stash_organizer_staging_storage_classes 0
stash_organizer_permanent_lane_gaps 0
stash_lane_capacity_ready 1
stash_lanes_detected 24
stash_lanes_assignable 22
stash_lanes_required 18
stash_lanes_spare 4
stash_lanes_shortfall 0
stash_shulkers_mixed 3
stash_shulkers_unclassified 0
stash_proxy_control_active 0
stash_connection_recovery_pending 0
stash_connection_outages_total 0
stash_connection_recoveries_total 0
stash_proxy_control_grace_remaining_seconds 0

Follow the local Grafana setup guide to start PostgreSQL, Prometheus, and Grafana, then import the included coordinate-free dashboard for either one bot or a fleet without mixing their database records.

🔔 n8n and webhook integration

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"
}

📜 License

This project is licensed under the GNU Affero General Public License v3.0 only.

About

A stash management plugin for Zenith Proxy.

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages