Official plugins for the Robonine educational robotics platform, maintained by Robonine. Licensed under MIT.
| Plugin | Slug | Description |
|---|---|---|
| ArUco detector | robonine/aruco | Detects ArUco fiducial markers in a camera feed; exposes detections to other plugins |
| Calibrate motors | robonine/calibrate-motors | Place joints in home position and save servo offsets |
| Calibrate robot | robonine/calibrate-robot | Move joints through their full range to set encoder limits |
| Control robot | robonine/control-robot | Manually move each joint using on-screen sliders |
| Force sensor | robonine/force-sensor | Read and display force sensor measurements |
| OpenCV | robonine/opencv | Loads OpenCV.js and exposes it as a background service |
| Set motor IDs | robonine/set-motor-ids | Sequentially assign IDs to servos |
| Teleoperate | robonine/teleoperate | Mirror a leader arm to a follower arm with live camera feed |
| WebMCP | robonine/webmcp | Exposes robot state and control to AI assistants via the Model Context Protocol |
npm install
npm run buildOutput is written to dist/ as one .robo9 file per plugin — a gzip-compressed ESM bundle ready to be served by the platform.
To build a single plugin, pass its slug:
node --import tsx/esm scripts/buildPlugins.ts arucoWhat gets bundled: everything except React. React is not bundled — plugins share the host app's React instance via window.__ROBONINE__. Plugin-local node_modules (e.g. @mcp-b/webmcp-polyfill in webmcp/) are bundled into that plugin's output.
See the Plugin SDK for the full getting-started guide. This section documents the complete API surface.
npm install
npm run typecheckmy-plugin/
src/
index.ts # re-exports manifest, PluginRoot, and optionally PluginService
manifest.ts # plugin metadata
plugin.tsx # React UI component (PluginRoot)
service.ts # optional background service (PluginService)
translations.ts # i18n strings
src/index.ts
export{manifest}from'./manifest'export{PluginRoot}from'./plugin'export{PluginService}from'./service'// omit if no serviceexport{manifestasdefault}from'./manifest'importtype{PluginManifest}from'@robonine/plugin-sdk'exportconstmanifest: PluginManifest={sdkVersion: '1',vendor: 'your-name',// lowercase, URL-safe namespaceslug: 'my-plugin',// lowercase, URL-safe identifiername: {en: 'My plugin',ru: 'Мой плагин'},description: {en: 'What it does.',ru: 'Что делает.'},icon: 'Wrench',// Lucide icon name, or inline SVG stringscopes: ['robot.control'],// Optional — if this plugin exposes a service to others:provides: 'my-service',// Optional — other plugins this one depends on:dependencies: [{vendor: 'robonine',slug: 'opencv'}],}Declare every capability your plugin needs. The platform enforces these at install and runtime.
| Scope | Grants |
|---|---|
| (none) | UI only, no hardware access |
robot.read | Read servo positions and register values |
robot.control | Send position commands |
robot.calibration | Write calibration data to EEPROM |
robot.config | Write servo configuration |
robot.leader | Second independent robot connection (leader role) |
robot.local | Requires physical local presence |
camera.read | Access camera feed |
install | Register a background service (PluginService) |
user.auth | Require user sign-in |
user.read | Read user name and email |
The context prop passed to PluginRoot.
interfacePluginContext{locale: string// Robot access — pass 'default' for the main arm, 'leader' for the second arm.// Requires the robot.leader scope to use 'leader'.robot(role: 'default'|'leader'): RobotHandle// Access a service provided by another installed plugin (requires install scope on that plugin).// Returns null if the plugin is not installed or its service hasn't started.service(slug: string): unknown|null// Camera feeds available in the current session.cameras: CameraHandle[]// 3D robot visualisation component.WorldView: React.ForwardRefExoticComponent<WorldViewProps&React.RefAttributes<WorldViewApi>>// Pre-styled UI primitives matching the platform theme.ui: {Button: React.ComponentType<React.ButtonHTMLAttributes<HTMLButtonElement>&{variant?: string;className?: string}>}}interfaceRobotHandle{connection: {connected: boolean}openConnectDialog(): void// Prompts the user to confirm before starting motion. Returns true if confirmed.showSafetyWarning(): Promise<boolean>// Joint-to-servo mapping for the connected model. Null until a robot is connected.robotConfig: RobotConfig|nullservo: ServoHandle}interfaceRobotConfig{modelId: stringjointServoId: Record<string,number>}interfaceServoHandle{readJointPositions(): Promise<number[]|null>setJointPositions(positions: number[]): Promise<void>// Registers a hardware emergency stop. Returns a cleanup function to unregister.registerEmergencyStop(): ()=>void}interfaceCameraHandle{id: stringlabel: stringsource: 'local'|'remote'stream: MediaStream}interfaceWorldViewProps{motionMode?: 'instant'onLoad?: (joints: JointInfo[])=>void}interfaceWorldViewApi{setJoint(name: string,value: number): void}interfaceJointInfo{name: string}A background service runs for the lifetime of the plugin and can be accessed by other plugins via context.service(slug). Export a PluginService factory and declare provides and install in the manifest.
importtype{PluginServiceFactory}from'@robonine/plugin-sdk'exportconstPluginService: PluginServiceFactory=(ctx)=>{// ctx is PluginServiceContext (see below).// Return any object — other plugins receive it from context.service(slug).return{doSomething(){/* … */},}}The ctx argument passed to a PluginServiceFactory.
interfacePluginServiceContext{// Access services from other installed plugins.service(slug: string): unknown|null// High-level platform APIs available to services:getRobotPosition(role: ConnectionRole): Promise<RobotPosition|null>listConnectedRobots(): Promise<ConnectedRobot[]>listUserRobots(): Promise<UserRobot[]>listUserPaths(): Promise<MotionPath[]>readPath(id: string): Promise<MotionPath|null>stopRobot(role: ConnectionRole): Promise<void>moveToPosition(x: number,y: number,z: number,role: ConnectionRole): Promise<void>goHome(role: ConnectionRole): Promise<void>executePath(id: string,role: ConnectionRole): Promise<void>}typeConnectionRole='default'|'leader'English (en) is the only required locale. Additional languages are optional.
// translations.tsexportconsttranslations={en: {title: 'My plugin',connectPrompt: 'Connect your robot to get started.'},ru: {title: 'Мой плагин',connectPrompt: 'Подключите робота, чтобы начать.'},}satisfiesRecord<string,Record<string,string>>// plugin.tsxconstt=useMemo(()=>translations[context.localeaskeyoftypeoftranslations]??translations.en,[context.locale],)