docs · concepts
Manifests.
Every Rune carries two manifests, and they answer different questions. rune.toml answers 'who is this package'; rune.jsonc answers 'what does this script need at runtime'. They overlap on identity and stay separate on intent.
rune.toml
The CLI reads rune.toml when it packs an archive for publishing. It's a TOML file with three sections you'll write and a few you might.
[package]
name = "ward" # required, lowercase + hyphens
version = "0.1.0" # required, semver
language = "typescript" # required: "typescript" | "wasm"
entry = "index.ts" # required, relative to manifest
description = "Permissions + web dashboard for Paper"
license = "MIT"
homepage = "https://github.com/your/ward"
repository = "https://github.com/your/ward"
keywords = ["permissions", "web", "rank-up"]
[publish]
include = ["**/*.ts", "rune.jsonc", "README*", "LICENSE*"]
exclude = ["**/*.test.ts", "dist/**"]
[capabilities]
required = ["host:bukkit", "host:plugin:vault", "host:plugin:papi"]
[dependencies]
"@rune/sdk" = "^0.4.0"Package identity
name must match the regex ^(?:@[a-z0-9-]+/)?[a-z0-9-]+$. Names without a scope are claimed first-come-first-served on the registry; scoped names (@yourname/foo) are reserved to the user who owns the scope. version must be valid semver and increases monotonically per publish.
Files
publish.include is the file allowlist that ends up in the archive. Default is ["**/*", "!.*"]; override when your project has a build step or sibling directories you don't want shipped. publish.exclude applies after include so you can allow a broad pattern and surgically remove test files.
Capabilities (advisory today)
capabilities.required declares which host privileges your Rune asks for. The list is part of the manifest hash, so it's part of the version's identity; adding a capability requires a new version. Runtime enforcement is reserved — see capabilities for the currently-tracked strings.
rune.jsonc
The host reads rune.jsonc at script load. It tells the runtime which third-party Paper plugins your script depends on and what aliases to bind. Comments are allowed. JSONC merges last-wins between the global plugins/Rune/rune.jsonc and your script's local copy.
{
// Optional Paper plugins your script wants to interop with.
// The host generates .d.ts files for declared plugins on enable.
"plugins": {
"PlaceholderAPI": {
"alias": "papi",
"package": "me.clip.placeholderapi"
},
"Vault": {
"alias": "vault",
"package": "net.milkbowl.vault"
}
},
// Global names to bind into the script's runtime. Anything reachable
// from the classpath can be aliased.
"aliases": {
"mm": "net.kyori.adventure.text.minimessage.MiniMessage",
"Component": "net.kyori.adventure.text.Component",
"NamedTextColor": "net.kyori.adventure.text.format.NamedTextColor"
}
}Plugin declarations
Each entry under pluginstells the host: "at enable time, reflect this Java package and write its types to types/<alias>.d.ts; at runtime, bind the plugin's API surface as a global named <alias>." If the underlying plugin isn't installed the alias becomes undefined at runtime — guard with typeof papi !== "undefined" if it's optional.
Aliases
The aliases map is for binding individual classes (often Adventure or static helper classes) into globals so you don't have to write the fully-qualified package name every time. Aliases land on the global scope; the host emits matching .d.ts declarations so TypeScript is happy.
What goes where
Don't try to consolidate. Identity belongs in rune.toml because it's what the registry signs and stores. Runtime wiring belongs in rune.jsonc because it's what the host has to know to bring your script up. Keeping them split means a Rune can run unpublished (no rune.toml needed at all if you only use it locally) and a published Rune can declare runtime config that the registry doesn't have to understand.