"use client"; import type React from "react"; import { Book, Code2, Terminal, Copy, Check, Server, Package, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useState, createContext, useContext } from "react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; type CLIMode = "npm" | "npx"; const CLIModeContext = createContext<{ mode: CLIMode; setMode: (mode: CLIMode) => void; }>({ mode: "npx", setMode: () => {}, }); const CLIModeProvider = ({ children }: { children: React.ReactNode }) => { const [mode, setMode] = useState("npx"); return ( {children} ); }; const CLIModeSwitch = ({ className = "" }: { className?: string }) => { const { mode, setMode } = useContext(CLIModeContext); return (
); }; const CLICodeBlock = ({ children, language = "bash", }: { children: React.ReactNode; language?: string; }) => { const { mode } = useContext(CLIModeContext); const [copied, setCopied] = useState(false); const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Failed to copy:", err); } }; // Convert commands based on mode const convertCommands = (text: string) => { if (mode === "npx") { return text .replace(/^lerobot /gm, "npx lerobot@latest ") .replace(/# lerobot /gm, "# npx lerobot@latest "); } return text; }; const codeText = typeof children === "string" ? children : children?.toString() || ""; const convertedCode = convertCommands(codeText); return (
{convertedCode}
); }; const CodeBlock = ({ children, language = "typescript", }: { children: React.ReactNode; language?: string; }) => { const [copied, setCopied] = useState(false); const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Failed to copy:", err); } }; const codeText = typeof children === "string" ? children : children?.toString() || ""; return (
{codeText}
); }; export function DocsSection() { return (

Docs

Complete API reference for all lerobot.js packages

WEB
@lerobot/web
NODE
@lerobot/node
CLI
lerobot
{/* Browser Requirements */}

Browser Requirements

Chromium 89+ with WebSerial and WebUSB API support
HTTPS or localhost
User gesture required for initial port selection
{/* Quick Start */}

Quick Start

Complete workflow from discovery to teleoperation.

{`import { findPort, releaseMotors, calibrate, teleoperate } from "@lerobot/web"; // 1. find and connect to hardware like a robot arm const findProcess = await findPort(); const robots = await findProcess.result; const robot = robots[0]; // 2. release the motors and put them into the homing position await releaseMotors(robot); // 3. calibrate the motors by moving each motor through its full range of motion const calibrationProcess = await calibrate({ robot, onProgress: (message) => console.log(message), onLiveUpdate: (data) => console.log("Live positions:", data), }); // when done, stop calibration and get the min/max ranges for each motor calibrationProcess.stop(); const calibrationData = await calibrationProcess.result; // 4. start controlling the robot arm with your keyboard const teleop = await teleoperate({ robot, calibrationData, teleop: { type: "keyboard" }, }); teleop.start(); // 5. stop control (run this when you're done) teleop.stop();`}
{/* API Reference */}

API Reference

{/* findPort */}

findPort(config?)

Discovers and connects to robotics hardware using WebSerial API.

{`// Interactive Mode (Default) - Shows port dialog const findProcess = await findPort(); const robots = await findProcess.result; // RobotConnection[] // Auto-Connect Mode - Reconnects to known robots const findProcess = await findPort({ robotConfigs: [ { robotType: "so100_follower", robotId: "left_arm", serialNumber: "USB123" } ], onMessage: (msg) => console.log(msg), });`}
Options
  • robotConfigs?: RobotConfig[] - Auto-connect to these known robots
  • onMessage?: (message: string) => void{" "} - Progress messages callback
Returns:{" "} FindPortProcess
  • result: Promise<RobotConnection[]>{" "} - Array of robot connections
  • stop(): void - Cancel discovery process
{/* calibrate */}

calibrate(config)

Calibrates motor homing offsets and records range of motion.

{`const calibrationProcess = await calibrate({ robot, onProgress: (message) => { console.log(message); // "⚙️ Setting motor homing offsets" }, onLiveUpdate: (data) => { // Real-time motor positions during range recording Object.entries(data).forEach(([motor, info]) => { console.log(\`\${motor}: \${info.current} (range: \${info.range})\`); }); }, }); // Move robot through full range of motion... calibrationProcess.stop(); // Stop range recording const calibrationData = await calibrationProcess.result;`}
Options
  • robot: RobotConnection - Connected robot from findPort()
  • onProgress?: (message: string) => void{" "} - Progress messages callback
  • •{" "} onLiveUpdate?: (data: LiveCalibrationData) => void {" "} - Real-time position updates
Returns:{" "} CalibrationProcess
  • result: Promise<CalibrationResults>{" "} - Python-compatible format
  • stop(): void - Stop calibration process
{/* teleoperate */}

teleoperate(config)

Enables real-time robot control with extensible input devices.

{`// Keyboard Teleoperation const keyboardTeleop = await teleoperate({ robot, calibrationData: savedCalibrationData, teleop: { type: "keyboard" }, onStateUpdate: (state) => { console.log(\`Active: \${state.isActive}\`); console.log(\`Motors:\`, state.motorConfigs); }, }); // Direct Teleoperation const directTeleop = await teleoperate({ robot, calibrationData: savedCalibrationData, teleop: { type: "direct" }, });`}
Options
  • robot: RobotConnection - Connected robot from findPort()
  • teleop: TeleoperatorConfig - Teleoperator configuration:
    • {`{ type: "keyboard" }`} - Keyboard control
    • {`{ type: "direct" }`} - Direct programmatic control
  • •{" "} calibrationData?: {`{ [motorName: string]: any }`} {" "} - Calibration data from calibrate()
  • •{" "} onStateUpdate?: (state: TeleoperationState) => void {" "} - State change callback
Returns:{" "} TeleoperationProcess
  • start(): void - Begin teleoperation
  • stop(): void - Stop teleoperation and clear states
  • getState(): TeleoperationState - Current state and motor positions
  • teleoperator: BaseWebTeleoperator - Access teleoperator-specific methods
  • disconnect(): Promise<void> - Stop and disconnect
{/* releaseMotors */}

releaseMotors(robot, motorIds?)

Releases motor torque so robot can be moved freely by hand.

{`// Release all motors for calibration await releaseMotors(robot); // Release specific motors only await releaseMotors(robot, [1, 2, 3]);`}
Options
  • robot: RobotConnection - Connected robot
  • motorIds?: number[] - Specific motor IDs (default: all motors for robot type)
{/* record */}

record(config)

Records robot teleoperator data and camera streams in the LeRobot dataset format.

{`import { teleoperate, record } from "@lerobot/web"; // Start teleoperation first const teleoperationProcess = await teleoperate({ robot: connectedRobot, teleop: { type: "keyboard" }, calibrationData: calibrationData, }); // Create recording with teleoperator const recordProcess = await record({ teleoperator: teleoperationProcess.teleoperator, videoStreams: { main: mainCameraStream, wrist: wristCameraStream, }, robotType: "so100", options: { fps: 30, taskDescription: "Pick and place task", onStateUpdate: (state) => { console.log(\`Recording: \${state.frameCount} frames\`); }, }, }); // Start both recording and teleoperation teleoperationProcess.start(); recordProcess.start(); // Manage recording during operation recordProcess.nextEpisode(); // Start new episode if needed // Stop recording when finished const robotData = await recordProcess.stop(); await recordProcess.exportForLeRobot("zip-download");`}
Options
  • teleoperator: WebTeleoperator - The teleoperator to record from
  • videoStreams?: {`{ [name: string]: MediaStream }`} - Optional camera streams
  • robotType?: string - Robot metadata (e.g., "so100")
  • options?: RecordOptions - Optional configuration:
    • fps?: number - Target frames per second (default: 30)
    • taskDescription?: string - Task description
    • onStateUpdate?: (state: RecordingState) => void - State changes
Returns: RecordProcess
  • start(): void - Start recording
  • stop(): Promise<RobotRecordingData> - Stop recording
  • getState(): RecordingState - Current recording state
  • getEpisodeCount(): number - Get number of episodes
  • nextEpisode(): Promise<number> - Start new episode
  • clearEpisodes(): void - Delete all episodes
  • addCamera(name: string, stream: MediaStream): void - Add camera dynamically
  • exportForLeRobot(format?: "blobs" | "zip" | "zip-download"): Promise<any> - Export dataset
{/* Node.js Requirements */}

Node.js Requirements

Node.js 18+ (Windows, macOS, Linux)
Serial port access (may require permissions)
SO-100 robot hardware with USB connection
{/* Quick Start */}

Quick Start

Complete workflow from discovery to teleoperation in Node.js.

{`import { findPort, connectPort, releaseMotors, calibrate, teleoperate } from "@lerobot/node"; // 1. find available robot ports console.log("🔍 finding available robot ports..."); const findProcess = await findPort(); const robots = await findProcess.result; if (robots.length === 0) { console.log("❌ no robots found. check connections."); process.exit(1); } // 2. connect to the first robot found console.log(\`✅ found \${robots.length} robot(s). connecting to first one...\`); const robot = await connectPort(robots[0].path, robots[0].robotType); // 3. release motors for manual positioning console.log("🔓 releasing motors for manual positioning..."); await releaseMotors(robot); // 4. calibrate motors by moving through full range console.log("⚙️ starting calibration..."); const calibrationProcess = await calibrate({ robot, onProgress: (message) => console.log(message), onLiveUpdate: (data) => console.log("live positions:", data), }); // move robot through its range, then stop calibration console.log("👋 move robot through full range, press enter when done..."); process.stdin.once("data", () => { calibrationProcess.stop(); }); const calibrationData = await calibrationProcess.result; console.log("✅ calibration complete!"); // 5. control robot with keyboard console.log("🎮 starting keyboard control..."); const teleop = await teleoperate({ robot, calibrationData, teleop: { type: "keyboard" }, }); teleop.start();`}
{/* How It Works */}

How It Works

Beginner Flow: findPort() → connectPort() → Use Robot

Most users should start with findPort() for discovery, then connectPort() for connection:

{`// ✅ recommended: discover then connect const findProcess = await findPort(); const robots = await findProcess.result; const robot = await connectPort(robots[0].path, robots[0].robotType);`}

Advanced: Direct Connection with connectPort()

Only use connectPort() when you already know the exact port:

{`// ⚡ advanced: direct connection to known port const robot = await connectPort("/dev/ttyUSB0", "so100_follower");`}
{/* API Reference */}

API Reference

{/* findPort */}

findPort(config?)

Discovers available robotics hardware on serial ports. Unlike the web version, this only discovers ports - connection happens separately with connectPort().

{`// Discover all available robots const findProcess = await findPort(); const robots = await findProcess.result; console.log(\`Found \${robots.length} robot(s):\`); robots.forEach((robot) => { console.log(\`- \${robot.robotType} on \${robot.path}\`); }); // Connect to specific robot const robot = await connectPort(robots[0].path, robots[0].robotType);`}
Returns: FindPortProcess
  • result: Promise<DiscoveredRobot[]>{" "} - Array of discovered robots with path, robotType, and other metadata
  • stop(): void - Cancel discovery process
{/* connectPort */}

connectPort(port, robotType, robotId?)

Creates a connection to a robot on the specified serial port.

{`// Connect to SO-100 follower arm const robot = await connectPort( "/dev/ttyUSB0", // Serial port path "so100_follower", // Robot type "my_robot_arm" // Custom robot ID ); // Windows const robot = await connectPort("COM4", "so100_follower", "my_robot"); // Connection is ready to use console.log(\`Connected to \${robot.robotType} on \${robot.port.path}\`);`}
Parameters
  • port: string - Serial port path (e.g., /dev/ttyUSB0, COM4)
  • •{" "} robotType: "so100_follower" | "so100_leader" {" "} - Type of robot
  • robotId?: string - Custom identifier for your robot
{/* calibrate */}

calibrate(config)

Calibrates motor homing offsets and records range of motion.

{`const calibrationProcess = await calibrate({ robot, onProgress: (message) => { console.log(message); // "⚙️ Setting motor homing offsets" }, onLiveUpdate: (data) => { // Real-time motor positions during range recording Object.entries(data).forEach(([motor, info]) => { console.log(\`\${motor}: \${info.current} (range: \${info.range})\`); }); }, }); // Move robot through full range of motion... // When finished, stop calibration calibrationProcess.stop(); const calibrationData = await calibrationProcess.result; // Save to file (Python-compatible format) import { writeFileSync } from "fs"; writeFileSync( "./my_robot_calibration.json", JSON.stringify(calibrationData, null, 2) );`}
Returns: CalibrationProcess
  • result: Promise<CalibrationResults>{" "} - Python-compatible calibration data
  • stop(): void - Stop calibration process
{/* teleoperate */}

teleoperate(config)

Real-time robot control with keyboard input.{" "} Smooth, responsive movement optimized for Node.js.

{`const teleop = await teleoperate({ robot, teleop: { type: "keyboard" }, onStateUpdate: (state) => { console.log(\`Active: \${state.isActive}\`); state.motorConfigs.forEach((motor) => { console.log(\`\${motor.name}: \${motor.currentPosition}\`); }); }, }); // Start keyboard control teleop.start(); // Control will be active until stopped setTimeout(() => teleop.stop(), 60000);`}
Keyboard Controls (SO-100)
Arrow Keys: Shoulder pan/lift
WASD: Elbow flex, wrist flex
Q/E: Wrist roll
O/C: Gripper open/close
ESC: Emergency stop
Ctrl+C: Exit
{/* releaseMotors */}

releaseMotors(robot)

Releases motor torque so robot can be moved freely by hand.

{`// Release all motors for calibration await releaseMotors(robot); console.log("Motors released - you can now move the robot freely");`}
{/* Serial Port Permissions */}

Serial Port Permissions

Linux/macOS

{`# Add user to dialout group (Linux) sudo usermod -a -G dialout $USER # Set permissions (macOS) sudo chmod 666 /dev/tty.usbserial-*`}

Windows

No additional setup required.

{/* Commands */}

Available Commands

{/* find-port */}

find-port

Interactive port discovery using cable detection.

{`lerobot find-port`}
Interactive Process
  • • Lists current ports
  • • Prompts to unplug USB cable
  • • Detects which port disappeared
  • • Prompts to reconnect cable
{/* calibrate */}

calibrate

Calibrate robot motors and save calibration data.

{`lerobot calibrate --robot.type=so100_follower --robot.port=/dev/ttyUSB0 --robot.id=my_arm`}
Options
  • --robot.type - Robot type (so100_follower, so100_leader)
  • --robot.port - Serial port (/dev/ttyUSB0, COM4)
  • --robot.id - Robot identifier (default: "default")
  • --output - Custom output path for calibration file
Storage Location
~/.cache/huggingface/lerobot/calibration/robots/{robot_type}/{robot_id}.json
{/* teleoperate */}

teleoperate

Control robot through keyboard input.

{`lerobot teleoperate --robot.type=so100_follower --robot.port=/dev/ttyUSB0 --robot.id=my_arm`}
Options
  • --robot.type - Robot type (e.g., so100_follower)
  • --robot.port - Serial port (e.g., /dev/ttyUSB0, COM4)
  • --robot.id - Robot identifier (default: "default")
  • --teleop.type - Teleoperator type (default: "keyboard")
  • --duration - Duration in seconds, 0 = unlimited (default: "0")
Keyboard Controls
  • w/s - Elbow flex/extend
  • a/d - Wrist down/up
  • q/e - Wrist roll left/right
  • o/c - Gripper open/close
  • Arrow keys - Shoulder lift/pan
  • Ctrl+C - Stop and exit
{/* release-motors */}

release-motors

Release robot motors for manual positioning.

{`lerobot release-motors --robot.type=so100_follower --robot.port=/dev/ttyUSB0 --robot.id=my_arm`}
Options
  • --robot.type - Robot type (e.g., so100_follower)
  • --robot.port - Serial port (e.g., /dev/ttyUSB0, COM4)
  • --robot.id - Robot identifier (default: "default")
  • --motors - Specific motor IDs to release (comma-separated)
); }