rune

rune · 1.0.0

Write your Paper plugin in TypeScript. In your Paper plugin.

curl -fsSL https://runemc.dev/install.sh | bash
@Listener
export class WelcomeMessage {
  @EventHandler(Events.PlayerJoinEvent)
  onJoin(e: PlayerJoinEvent) {
    const player = e.getPlayer();
    player.sendMessage(`Welcome, ${player.getName()}.`);
  }
}

Embedded, not adjacent.

Other plugin scripts run as a sidecar process, serializing every player action through a socket or HTTP boundary. Rune embeds Node.js inside the Paper JVM via the Panama FFM API. A method call from Java into JS is an upcall, not a round-trip.

// no fetch, no socket, no JSON. the java object is right there.
const world = rune.bukkit.getWorlds().get(0);
const spawn = world.getSpawnLocation();

Polyglot from day one.

TypeScript ships first; Wasm is already wired via Wasmtime. Python, Lua, and Rust-to-Wasm are next. The host API is language-agnostic, so what your Rune sees is the same across runtimes.

@Listener
export class Welcome {
  @EventHandler(Events.PlayerJoinEvent)
  onJoin(e: PlayerJoinEvent) {
    e.getPlayer().sendMessage("welcome.");
  }
}

Types from your actual classpath.

On enable, the plugin reflects every Bukkit, Paper, Adventure, and declared third-party plugin class on the classpath and writes them to .d.ts files. Your IDE autocompletes against the server you are shipping for, not a hand-maintained type package.

// rune.jsonc
{
  "plugins": {
    "Vault": { "alias": "vault", "package": "net.milkbowl.vault" }
  }
}

// salary.ts
@Listener
export class Salary {
  @EventHandler(Events.PlayerJoinEvent)
  onJoin(e: PlayerJoinEvent) {
    vault.economy.deposit(e.getPlayer(), 100);
  }
}

And when you ship: publish to Runebook.

Runebook is the package registry for Runes. Content-addressed blobs, immutable manifests, prominent capability disclosure. Operators see exactly what your Rune will do on their server before they install it.

browse runebook