The plugin manifest
Every plugin carries rim-plugin.json at the root of its folder or bundle. This page lists each
field the desktop parser accepts, in the order of PluginManifest in manifest.rs, with the
install rules of validation.rs, both in src-tauri/src/plugin_host/. The web app installs by
their port in the SDK, manifestRules.ts; both pass protocol/manifest-vectors.json, and a
plugin's tests run them with validateManifest (the SDK).
Reading and checking #
- The file is JSON with snake_case keys. Unknown fields are ignored and left out of the hash.
- A field with a default may be left out, but
nullis refused for it.author,network_origins,output_schema,preset_argsandslottakenullas absent. - A missing field or a wrong type fails with
failed to parse manifest: <reason>, the manifest's own checks withvalidation error: <reason>, the install checks withinvalid manifest: <reason>and, in the UI,{ kind: "INVALID_MANIFEST", data }withdata.kindone of the tags below. The first failing check is reported. The desktop repeats the checks at every start.
Identity and runtime #
manifest_version(integer, required):1; anything else fails withunsupported manifest_version: <n>.plugin_id(string, required): a reverse-domain id such asdev.example.greeter, at least three dot-separated segments of ASCII letters, digits and-(plugin_id must have at least 3 segments,contains an empty segment,segment contains invalid characters). Ids startingdev.rim.orcom.rim.fail withreserved plugin_id prefix: <id>. The id names the install folder,~/.rim/plugins/<plugin_id>/, and every command,plugin.<plugin_id>.<id>.display_name(string, required, not empty): the name in Settings, in the permission prompt, as the palette category of the plugin's commands and in the side rail's Plugins menu.version(string, required, not empty): free-form. The bundler names its file<plugin_id>-<version>.rimbundle.description(string, required, may be empty) andauthor(string ornull, defaultnull): the permission prompt quotes the first, cut at 600 characters, and says "by <author>".runtime_kind(string, required): another value fails withunknown variant."DesktopLocalProcess": a program the desktop app starts, speaking protocol v1 over stdio. It runs as the user; capabilities govern only what the host does for it."WebSandboxed": one ES module in a sandboxed iframe, on the desktop and the web, reaching only what its capabilities andnetwork_originsallow (web plugins).
entrypoint(string, required, not empty):DesktopLocalProcess: a file in the plugin folder that the host runs directly, with no arguments and the plugin folder as working directory, so it needs a#!line and the executable bit. A missing file fails at start withentrypoint not found.WebSandboxed: a bundle path (/-separated segments ofA-Z a-z 0-9 . _ -, none empty or starting with., at most 255 bytes) ending in.mjsor.jsand not underpanels/. OtherwiseINVALID_ENTRYPOINT:invalid entrypoint `<path>`: <reason>.
capabilities #
A list of strings, default []: the permissions the plugin asks for, in a prompt shown after
install and from Settings → Plugins → Permissions. Revoking any capability stops the plugin.
| Name | Kinds | What it guards |
|---|---|---|
ui.panel |
both | rim.ui.update_panel: the panel state host.panels sets |
ui.notify |
both | Notifications a panel shows with rim.ui.notification.show |
chat.read |
both | rim.chat.list_messages, get_message, search_messages |
chat.write |
both | rim.chat.post_message |
chat.read.encrypted |
both | The reads in encrypted DMs and groups; needs chat.read |
chat.write.encrypted |
both | Posting in encrypted DMs and groups; needs chat.write |
network.fetch |
WebSandboxed |
Connecting to the origins in network_origins |
A manifest may also declare keys of its own, plugin.<plugin_id>.<verb>, for a command's
required_capabilities to name. The permission prompt lists only the names above, so the app
offers no way to approve such a key, and a command that requires one is refused.
A key fails with INVALID_CAPABILITY_KEY (invalid capability key `<key>`: <reason>) when:
- it is shorter than 4 or longer than 128 bytes, or
network.fetchon aDesktopLocalProcessplugin, orlocal.process,fs.read,fs.writeornetworkon aWebSandboxedone; - it starts with a reserved namespace:
system.,internal.,admin.oraudit.; - it is neither a name above nor starts with
plugin.<plugin_id>., which refusesevents.read,local.process,networkandfs.*on a local process too: nothing implements what they guard; - its verb is empty, uses anything but
a-z 0-9 _ ., starts or ends with.or contains.., or any segment of the key issystem,internal,adminoraudit; - it is an encrypted chat name without its plain one (
chat.read.encrypted requires chat.read).
network_origins #
A list of strings, absent by default, for WebSandboxed only: the origins the plugin's frame may
connect to once network.fetch is approved. The prompt names each host. It fails with
INVALID_NETWORK_ORIGINS (invalid network_origins: <reason>) when:
- it is present on a
DesktopLocalProcessplugin or withoutnetwork.fetch, absent whilenetwork.fetchis declared, lists fewer than 1 or more than 16 origins, or one twice; - an entry is not
https://host[:port]orwss://host[:port]with a lowercase host and no path, trailing slash, query, fragment, userinfo or wildcard, or its port is empty, starts with0or exceeds 65535; - an entry has the host and port of the app's or the RIM API's own origin, default ports aside.
commands #
A list, default []. Each entry joins the shared command registry as plugin.<plugin_id>.<id>:
the palette, keybinds, macros and, when allowed, the AI agent.
id,title,description(strings, required): the key in the SDK'scommands, the palette's line and its detail.keybind_capable(bool, defaultfalse): offered when binding a key.dangerous(bool, defaultfalse): the user confirms every run, the agent only proposes it.required_context(strings, default[]):"conversation","message"or"workspace". It runs only there, withconversation_idandthread_id,message_id,conversation_idandthread_id, orworkspace_idadded toargs; an argument of one of those names fails withRESERVED_ARGUMENT. Other values are not checked.required_capabilities(strings, default[]): capabilities approved before the command runs, or it fails withCAPABILITY_DENIED. They are not compared withcapabilitiesat install.arguments(list, default[]): by convention objects withname,type,requiredanddescription. The palette and keybinds pass no arguments, so they leave out a command with arequiredone. The agent's tool schema takesdescription, andtypewhen it isstring,integer,number,boolean,objectorarray.ai_callable(bool, defaultfalse): offers the command to the agent once the user allows it in the plugin's AI access settings. The tool nameplugin__<plugin_id, dots as _>__<id>must match[A-Za-z0-9_-]{1,64}, or the command is not offered.output_schema(JSON ornull, defaultnull): the JSON Schema of the payload. Anai_callablecommand fails withREQUIRES_MANUAL_REVIEW, and does not install, without one or when its root is not"type": "object", sets"additionalProperties": true, or has neitherpropertiesnoroneOf,anyOf,allOf,$defsoritems. Inproperties,items, the combinators and$defs, a name whose letters and digits, lowercased, containinstallidorinstallationidfails withINSTALL_ID_LEAK: a per-install id would let the model link one person across workspaces.
keybind_suggestions and context_actions #
keybind_suggestions (default []) are keys the desktop lists in the keybind settings until the
command is bound; it binds nothing itself. Each has keystroke (such as Ctrl+Alt+P),
command_id (a command of this manifest, unqualified) and context (GLOBAL, COMPOSER,
MESSAGE_LIST, THREAD_VIEW, CONVERSATION_VIEW, PANEL or COMMAND_PALETTE; empty means
GLOBAL), all required strings, and preset_args (optional JSON, unused by the app). An unknown
command fails with keybind_suggestion references undeclared command: '<id>'.
context_actions (default []) are entries of a message's context menu. Each has id, title
(the label) and command_id, required strings, and context_types (default []): empty shows
the entry on every message, otherwise it shows when the list holds "message". An unknown command
fails with context_action '<id>' references undeclared command: '<command_id>'.
panels #
A list, default []. A panel is the folder panels/<id>/ with an index.html, shown in a
sandboxed frame while its plugin runs (loading).
id(string, required): the folder's name; forWebSandboxed, one bundle path segment, orINVALID_PANEL.title(string, required) anddescription(string, default""): the title names the panel's row in the side rail's Plugins menu, beside the plugin's name, and the palette command "Open <title>".slot(string ornull, optional):"tool_panel", the default, is a narrow column beside the tool panel, next to the conversation;"main_area"takes the main area in place of the conversation. Panels show while a conversation is the main surface. Other values fail to parse.
settings_schema #
A list, default [], of { key, title, description, value_type, is_secret }: key, title and
value_type are required strings, description defaults to "", is_secret to false. Nothing
reads it yet: no settings page is built from it, and the panel bridge only logs rim.settings.*.
The content hash #
The desktop records a hash of the manifest at install and when the user approves AI commands: the
SHA-256, in lowercase hex, of the parsed manifest serialized as compact JSON, with the fields in
the order above, defaults filled in and unknown fields gone. Whitespace, key order and spelled-out
defaults do not change it. network_origins and a panel's slot are left out when absent, so a
manifest without them hashes as on a host without those fields; other absent options are null.
It gates AI consent. When the hash differs from the recorded one and the manifest declares
ai_callable commands, or the user approved some, the agent runs none of the plugin's commands
until the user consents again (manifest changed — re-consent required for AI commands). Any
change counts, since the old command set cannot be read from a hash.
Simple mode #
A folder without rim-plugin.json may declare its manifest in comments at the top of main.ts,
main.js or main.py, the first that exists. simple_manifest.rs reads the # and // lines
up to the first other non-empty line:
# rim-plugin: dev.example.my-tool
# rim-name: My Tool
# rim-version: 1.0.0
# rim-command: do_thing | Do Thing | Does a thing
rim-pluginandrim-nameare required,rim-versiondefaults to0.1.0, andrim-description,rim-authorand a comma-separatedrim-capabilitiesare optional.rim-commandrepeats and isid | title [| description]. An empty value, an unknownrim-key or a malformedrim-commandfails with its line number.- The result is a
DesktopLocalProcessmanifest without panels, keybind suggestions, context actions or settings, whose commands have every flag off. - Its entrypoint is
npx tsx main.ts,node main.jsorpython main.py. The host runs an entrypoint as a file path, so such a plugin installs but fails to start withentrypoint not found. A plugin that has to run usesrim-plugin.json.
Example #
The manifest of plugin-github-prs, a web
plugin with a network permission and a tool panel, verbatim:
{
"manifest_version": 1,
"plugin_id": "chat.rim.github-prs",
"display_name": "GitHub Pull Requests",
"version": "0.0.1",
"author": "RIM team",
"runtime_kind": "WebSandboxed",
"entrypoint": "main.mjs",
"description": "Lists the open pull requests of public GitHub repositories you choose. Talks only to GitHub, without a token.",
"capabilities": ["ui.panel", "network.fetch"],
"network_origins": ["https://api.github.com", "https://avatars.githubusercontent.com"],
"commands": [
{
"id": "refresh",
"title": "Pull requests: Refresh",
"description": "Fetches the open pull requests again.",
"keybind_capable": true
},
{
"id": "add_repo",
"title": "Pull requests: Add repository",
"description": "Adds a public GitHub repository to the list, at most 10.",
"arguments": [
{
"name": "repo",
"type": "string",
"required": true,
"description": "The repository as owner/name, such as rust-lang/rust."
}
]
},
{
"id": "remove_repo",
"title": "Pull requests: Remove repository",
"description": "Removes a repository from the list.",
"arguments": [
{
"name": "repo",
"type": "string",
"required": true,
"description": "The repository as owner/name."
}
]
}
],
"panels": [
{
"id": "prs",
"title": "Pull requests",
"description": "Open pull requests of the repositories on your list.",
"slot": "tool_panel"
}
]
}