ScapePath is a passive OSRS account-progression companion. It reads your locally available account state (skills, quests, achievement diaries, inventory, equipment, bank, and wealth) and shows it in a side panel, along with a preview of the exact data it synchronizes to your ScapePath account when you choose to connect.
- Read-only account view. The plugin reads your account state through public RuneLite APIs, builds a normalized snapshot, and displays it — plus a preview of the exact JSON it would sync — inside RuneLite. It never automates gameplay, sends input, or reads any credential.
- Opt-in HTTPS sync to ScapePath. In the plugin's side panel you can connect your
ScapePath account:
- On the ScapePath website Profile, generate a one-time connection code.
- Enter that code in the plugin panel and press Connect.
- The plugin exchanges the code for a ScapePath device token over HTTPS
(
POST /api/runelite/link) and stores only that token locally (never shown again). - While connected, your snapshot is synced over HTTPS (
POST /api/runelite/sync,Authorization: Bearer <device token>) — automatically (throttled to at most once every few minutes) and via Sync now. - Disconnect from the panel (or from the website) revokes the connection.
- Nothing is sent until you connect, only your own account state is ever sent, and the device token is a revocable ScapePath credential — never a RuneScape/Jagex/Google/ RuneLite credential, password, cookie, or session token. If the network is unavailable the plugin keeps working locally and retries later; it never blocks the client. See PAYLOAD.md for the exact contract and privacy disclosure.
It never collects RuneScape/Jagex, Google, or RuneLite credentials, passwords, or session cookies; never automates gameplay or sends inputs; uses no reflection, native code, or extra dependencies.
- JDK 11
- The Gradle wrapper (
./gradlew) — no separate Gradle install needed.
./gradlew buildRuns compileJava, compileTestJava, and the JUnit test suite. Produces
build/libs/.
Useful tasks:
./gradlew test # unit tests only
./gradlew run # launch RuneLite with ScapePath side-loaded (dev mode)
./gradlew shadowJar # fat jar (build/libs/scapepath-<version>-all.jar)There are two supported ways to run the plugin against a real client:
src/test/java/com/scapepath/plugin/ScapePathPluginTest.java side-loads the plugin
via ExternalPluginManager.loadBuiltin(...) and launches RuneLite with
--developer-mode --debug. This pulls the RuneLite client from repo.runelite.net
at latest.release.
./gradlew runThen in the client:
- Open the Configuration (wrench) panel.
- Search ScapePath — it appears in the plugin list.
- Toggle it on, then open the ScapePath side panel (navigation button).
- Under ScapePath connection, paste a connection code from the website Profile and press Connect, then use Sync now / Disconnect. The config panel's Account Sync → Automatic sync toggle controls periodic background syncing.
./gradlew shadowJar→build/libs/scapepath-<version>-all.jar(or use the plain built classes).- Launch RuneLite with
--developer-modeand use the developer plugin side-loader pointed at this project's build output.
- Builds cleanly on JDK 11.
- Plugin appears as ScapePath in the config panel and starts/stops without errors.
- No stack traces on startup/shutdown.
- Until you connect, no outbound network requests are made by this plugin.
- Account state is read locally and shown in the panel. Once you connect with a one-time code, your own account snapshot is synced to ScapePath over HTTPS (and only then).
- Identity: account hash (stable id, not a credential), RSN, account type
(via
VarbitID.IRONMAN), world. - Skills: every skill in
Skill.values()(level + XP), total level, total XP, and combat level (viaExperience.getCombatLevel). - Quests: per-quest
{id, name, state}overQuest.values()(state viaQuest.getState), completed count, total count, and quest points (VarPlayerID.QP). - Achievement Diaries: every region/tier
{region, tier, completed}(12 regions × 4 tiers = 48), completed-tier count, total. Completion read from per-tier varbits; Karamja Easy/Medium/Hard use theATJUN_*_DONEvarbits (complete at value 2). - Inventory: occupied slots as
{id, quantity, slot}. - Equipment: worn items as
{id, quantity, equipment-slot}. - Bank (interface-gated): stacks
{id, quantity, slot}, unique-item count, bank coins, and an estimated value from RuneLite's local price cache. Only available once the bank is opened; see the freshness model below. - Wealth: GP on hand (inventory coins), bank GP and estimated bank value (both
nulluntil the bank is synced — never conflated with zero).
Data is rebuilt on login, item-container changes, and bank open/close, and displayed in the ScapePath side-panel. It is transmitted only after you connect (see "How it works"), and only ever your own account's state.
Three states are kept distinct and "unknown" is never shown as "empty":
| State | When | Section |
|---|---|---|
| Not synced | bank never opened this session | UNAVAILABLE, no BankData |
| Current | bank open right now | COMPLETE, BankData + timestamp |
| Stale | opened earlier, now closed | STALE, cached BankData + timestamp |
The cache is reset on logout so one account's bank is never shown for another.
com.scapepath.plugin
├── ScapePath constants (config group, version)
├── ScapePathPlugin lifecycle + orchestration (events, client-thread refresh)
├── ScapePathConfig config panel (Connection, Account Sync)
├── game
│ ├── GameStateAccessor read-only seam over RuneLite Client (+ containers, prices)
│ ├── RuneLiteGameStateAccessor live impl (client-thread reads; injects ItemManager)
│ ├── OsrsAccountType normalized account type (from account-type varbit)
│ ├── BankTracker stateful interface-gated bank cache + freshness
│ └── DiaryDefinitions auditable region/tier → varbit table (Karamja special)
├── connection
│ ├── ConnectionState DISCONNECTED / CONNECTING / CONNECTED / SYNCING / OFFLINE / ERROR
│ ├── ConnectionManager link/sync/disconnect lifecycle; dispatches all network off-thread
│ ├── TokenStore device-token storage seam (testable)
│ └── ConfigTokenStore TokenStore backed by RuneLite ConfigManager (hidden key)
├── collector
│ ├── AccountDataCollector contract for one snapshot section
│ ├── CollectorContext read-only inputs (incl. GameStateAccessor)
│ ├── CollectorRegistry assembles registered collectors → snapshot
│ ├── IdentityCollector IDENTITY section
│ ├── SkillsCollector SKILLS section
│ ├── QuestCollector QUESTS section (state + quest points)
│ ├── AchievementDiaryCollector ACHIEVEMENT_DIARIES section
│ ├── InventoryCollector INVENTORY section
│ ├── EquipmentCollector EQUIPMENT section
│ ├── BankCollector BANK section (interface-gated freshness)
│ └── WealthCollector WEALTH section (GP on hand, bank GP, est. value)
├── snapshot
│ ├── SnapshotSectionType 14 categories defined; 8 collected today (Identity, Skills,
│ │ Quests, Diaries, Inventory, Equipment, Bank, Wealth)
│ ├── SourceFreshness COMPLETE / PARTIAL / STALE / UNAVAILABLE
│ ├── SectionData marker for typed payloads
│ ├── CollectedSection one section + freshness metadata
│ ├── AccountSnapshot normalized, immutable account snapshot model
│ ├── SnapshotService holds latest snapshot, rebuilds on demand
│ └── data
│ ├── IdentityData typed IDENTITY payload
│ ├── SkillData one skill (name/level/xp)
│ ├── SkillsData all skills + totals + combat level
│ ├── QuestSnapshot one quest (id/name/state)
│ ├── QuestsData all quests + completed/total + quest points
│ ├── DiaryTierSnapshot one region/tier (region/tier/completed)
│ ├── AchievementDiaryData all tiers + completed/total
│ ├── ItemSnapshot one stack (id/quantity/slot)
│ ├── InventoryData occupied inventory slots
│ ├── EquipmentData worn items
│ ├── BankData bank stacks + coins + estimated value + source
│ └── WealthData GP on hand, bank GP, estimated bank value
├── transport
│ ├── JsonWriter deterministic, reflection-free JSON builder
│ ├── SnapshotPayloadSerializer AccountSnapshot → versioned JSON (no HTTP)
│ ├── PayloadPreview local "what would be sent" description
│ ├── ScapePathTransport HTTPS transport seam (link/sync/disconnect)
│ └── OkHttpScapePathTransport the ONE networking class (RuneLite-bundled OkHttp, HTTPS)
└── ui
└── ScapePathPanel side panel: connection controls + snapshot view + payload preview
Updates are event-driven, never a busy poll. GameStateChanged (login), StatChanged,
WorldChanged, ItemContainerChanged (inventory/equipment/bank), WidgetLoaded/
WidgetClosed (bank interface BANKMAIN), and a targeted VarbitChanged (only the
quest-points varp or a known diary varbit) mark the snapshot dirty; GameTick rebuilds
once per tick at most, coalescing bursts (e.g. the ~23 StatChanged events at login)
into a single rebuild. A BANK container change caches the bank into BankTracker with
a timestamp; opening/closing the bank flips its current/stale flag; logout resets it.
All rebuilds read the client on the client thread.
ScapePath never collects RuneScape / Jagex / Google / RuneLite credentials, passwords, or
session cookies, and never automates gameplay or sends inputs. Syncing is opt-in
(nothing is sent until you connect with a one-time code), read-only, HTTPS-only to
https://www.scapepath.com, authenticated with a revocable ScapePath device token, and
sends only your own account's state. PAYLOAD.md discloses exactly what is
transmitted. Uses no reflection, native code, Runtime.exec, or extra dependencies; the
device token is stored under a hidden config key and never logged or shown.
BSD 2-Clause. See LICENSE.