This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Microbot Hub is a community plugin repository for the Microbot RuneLite client. It maintains a separation between core client functionality and community-contributed plugins, allowing rapid plugin development without affecting client stability. Each plugin is independently built, versioned, and packaged for GitHub Releases.
The build system uses Gradle with custom plugin discovery and packaging:
- Dynamic Plugin Discovery:
build.gradlescanssrc/main/java/net/runelite/client/plugins/microbot/for directories containing*Plugin.javafiles - Per-Plugin Source Sets: Each discovered plugin gets its own Gradle source set, compile task, and shadow JAR task
- Gradle Helper Scripts: Core build logic lives in:
gradle/project-config.gradle- centralized configuration (JDK version, paths, GitHub release URLs, client version)gradle/plugin-utils.gradle- plugin discovery, descriptor parsing, JAR creation, SHA256 hashing
# Build all plugins
./gradlew clean build
# Build specific plugin(s) only (much faster for iteration)
./gradlew build -PpluginList=PestControlPlugin
./gradlew build -PpluginList=PestControlPlugin,AutoMiningPlugin
# Run tests (tests have access to all plugin source sets)
./gradlew test
# Generate plugins.json metadata file with SHA256 hashes (requires exact JDK 11)
./gradlew generatePluginsJson
# Copy plugin documentation to public/docs/
./gradlew copyPluginDocs
# Launch RuneLite debug session with plugins from Microbot.java
./gradlew run --args='--debug'
# Validate JDK version
./gradlew validateJdkVersionEach plugin lives in its own package under src/main/java/net/runelite/client/plugins/microbot/<pluginname>/:
<pluginname>/
├── <PluginName>Plugin.java # Main plugin class with @PluginDescriptor
├── <PluginName>Script.java # Script logic extending Script class
├── <PluginName>Config.java # Configuration interface (optional)
├── <PluginName>Overlay.java # UI overlay (optional)
└── Additional support classes
Matching resources under src/main/resources/net/runelite/client/plugins/microbot/<pluginname>/:
<pluginname>/
├── dependencies.txt # Maven coordinates (optional)
└── docs/
├── README.md # Plugin documentation
└── assets/ # Screenshots, icons, etc.
Every plugin must have a @PluginDescriptor annotation with these required fields:
name- Display name (usePluginConstants.DEFAULT_PREFIXor create custom prefix)version- Semantic version string (store instatic final String versionfield)minClientVersion- Minimum Microbot client version required
Important optional fields:
authors- Array of author namesdescription- Brief description shown in plugin paneltags- Array of tags for categorizationiconUrl- URL to icon image (shown in client hub)cardUrl- URL to card image (shown on website)enabledByDefault- UsePluginConstants.DEFAULT_ENABLED(currentlyfalse)isExternal- UsePluginConstants.IS_EXTERNAL(currentlytrue)
Example:
@PluginDescriptor(
name = PluginConstants.MOCROSOFT + "Pest Control",
description = "Supports all boats, portals, and shields.",
tags = {"pest control", "minigames"},
authors = { "Mocrosoft" },
version = PestControlPlugin.version,
minClientVersion = "1.9.6",
iconUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/icon.png",
cardUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/card.png",
enabledByDefault = PluginConstants.DEFAULT_ENABLED,
isExternal = PluginConstants.IS_EXTERNAL
)
@Slf4j
public class PestControlPlugin extends Plugin {
static final String version = "2.2.7";
// ...
}The PluginConstants.java file is shared across all plugins (included in each JAR during build). It contains:
- Standardized plugin name prefixes (e.g.,
DEFAULT_PREFIX,MOCROSOFT,BOLADO) - Global defaults:
DEFAULT_ENABLED = false,IS_EXTERNAL = true
When creating a new plugin prefix, add it to PluginConstants.java for consistency.
If a plugin needs additional libraries beyond the Microbot client:
- Create
src/main/resources/net/runelite/client/plugins/microbot/<pluginname>/dependencies.txt - Add Maven coordinates, one per line:
com.google.guava:guava:33.2.0-jre org.apache.commons:commons-lang3:3.14.0 - The build system automatically includes these in the plugin's shadow JAR
- Edit
src/test/java/net/runelite/client/Microbot.java - Add your plugin class to the
debugPluginsarray:private static final Class<?>[] debugPlugins = { YourPlugin.class, AutoLoginPlugin.class };
- Run
./gradlew run --args='--debug'or use your IDE's run configuration
- Tests live in
src/test/java/ - Test classes have access to all plugin source sets (configured in
build.gradle) - Use
./gradlew testto run all tests
- Always increment the plugin version when making changes (even small fixes)
- Store version in a static field:
static final String version = "1.2.3"; - Follow semantic versioning:
MAJOR.MINOR.PATCH - The version is used for JAR naming, GitHub release assets, and
plugins.jsongeneration
Based on recent commits:
- Use conventional commit prefixes:
fix:,feat:,docs:, etc. - Include PR references when applicable:
fix: description (#123) - Work on feature branches, merge to
development, create PRs tomain - Current branch:
development, main branch:main
- Build plugins:
./gradlew build - Generate metadata:
./gradlew generatePluginsJson(requires JDK 11 exactly) - Copy documentation:
./gradlew copyPluginDocs - Upload
build/libs/<pluginname>-<version>.jarand updatedpublic/docs/plugins.jsonas assets on the GitHub release tagged with<version>(orlatest-releasefor the stable tag):https://github.com/chsami/Microbot-Hub/releases/download/<tag>/<pluginname>-<version>.jar
- Java Version: JDK 11 (configured in
project-config.gradlewithTARGET_JDK_VERSION = 11, vendorADOPTIUM) - Microbot Client Dependency: Defaults to the latest version resolved via
https://microbot.cloud/api/version/client, falling back to2.0.61if lookup fails. Artifacts come from GitHub Releases (https://github.com/chsami/Microbot/releases/download/<version>/microbot-<version>.jar). Override with-PmicrobotClientVersion=<version>or-PmicrobotClientVersion=latest, or supply a local JAR for offline work via-PmicrobotClientPath=/absolute/path/to/microbot-<version>.jar - Plugin Release Tag:
plugins.jsonuses a stable release tag (latest-release) so download URLs stay constant:https://github.com/chsami/Microbot-Hub/releases/download/latest-release/<plugin>-<version>.jar. Override with-PpluginsReleaseTag=<tag>if needed. - Shadow JAR Excludes: Common exclusions defined in
plugin-utils.gradleincludedocs/**,dependencies.txt, metadata files, and module-info - Reproducible Builds: JAR tasks disable file timestamps, use reproducible file order, and normalize file permissions to
0644 - Descriptor Parsing: Build system uses regex to extract plugin metadata from Java source files (see
getPluginDescriptorInfoinplugin-utils.gradle)
When you run ./gradlew build:
- Scans
src/main/java/net/runelite/client/plugins/microbot/for directories - Finds directories containing a file matching
*Plugin.java - Creates a plugin object with:
name(class name without .java),sourceSetName(directory name),dir,javaFile - Filters by
-PpluginListif provided - For each plugin:
- Creates dedicated source set
- Configures compilation classpath with Microbot client
- Creates shadow JAR task with plugin-specific dependencies
- Parses
@PluginDescriptorfor metadata - Computes SHA256 hash of JAR for
plugins.json
- Plugins extending
SchedulablePluginimplementgetStartCondition()andgetStopCondition()for scheduler integration - Use
@Injectfor dependency injection (configs, overlays, scripts) - Config classes use
@Providesmethods to register withConfigManager - Overlays are registered in
startUp(), unregistered inshutDown() - Use
@Subscribefor event handling (ChatMessage, GameTick, etc.)
Scripts run on a scheduled executor thread, but certain RuneLite API calls (widgets, game objects, etc.) must run on the client thread:
// Use invoke() for client thread operations
TrialInfo info = Microbot.getClientThread().invoke(() -> TrialInfo.getCurrent(client));
// For void operations
Microbot.getClientThread().invoke(() -> {
// client thread code here
});Always use Microbot.getClientThread().invoke() when accessing:
- Widgets (
client.getWidget(),widget.isHidden()) - Game objects that aren't cached
- Player world view (
client.getLocalPlayer().getWorldView()) BoatLocation.fromLocal()- accesses player world view internallyTrialInfo.getCurrent()- accesses widgets internally- Any RuneLite API that throws "must be called on client thread"